Merge from rustc

This commit is contained in:
The Miri Cronjob Bot 2024-09-11 05:12:24 +00:00
commit 19fa141a6d
270 changed files with 3040 additions and 2364 deletions

View file

@ -812,6 +812,44 @@ pub(super) fn expand_asm<'cx>(
})
}
pub(super) fn expand_naked_asm<'cx>(
ecx: &'cx mut ExtCtxt<'_>,
sp: Span,
tts: TokenStream,
) -> MacroExpanderResult<'cx> {
ExpandResult::Ready(match parse_args(ecx, sp, tts, false) {
Ok(args) => {
let ExpandResult::Ready(mac) = expand_preparsed_asm(ecx, args) else {
return ExpandResult::Retry(());
};
let expr = match mac {
Ok(mut inline_asm) => {
// for future compatibility, we always set the NORETURN option.
//
// When we turn `asm!` into `naked_asm!` with this implementation, we can drop
// the `options(noreturn)`, which makes the upgrade smooth when `naked_asm!`
// starts disallowing the `noreturn` option in the future
inline_asm.options |= ast::InlineAsmOptions::NORETURN;
P(ast::Expr {
id: ast::DUMMY_NODE_ID,
kind: ast::ExprKind::InlineAsm(P(inline_asm)),
span: sp,
attrs: ast::AttrVec::new(),
tokens: None,
})
}
Err(guar) => DummyResult::raw_expr(sp, Some(guar)),
};
MacEager::expr(expr)
}
Err(err) => {
let guar = err.emit();
DummyResult::any(sp, guar)
}
})
}
pub(super) fn expand_global_asm<'cx>(
ecx: &'cx mut ExtCtxt<'_>,
sp: Span,

View file

@ -94,6 +94,7 @@ pub fn register_builtin_macros(resolver: &mut dyn ResolverExpand) {
line: source_util::expand_line,
log_syntax: log_syntax::expand_log_syntax,
module_path: source_util::expand_mod,
naked_asm: asm::expand_naked_asm,
option_env: env::expand_option_env,
pattern_type: pattern_type::expand,
std_panic: edition_panic::expand_panic,

View file

@ -326,6 +326,8 @@ fn mk_main(cx: &mut TestCtxt<'_>) -> P<ast::Item> {
let main_attr = ecx.attr_word(sym::rustc_main, sp);
// #[coverage(off)]
let coverage_attr = ecx.attr_nested_word(sym::coverage, sym::off, sp);
// #[allow(missing_docs)]
let missing_docs_attr = ecx.attr_nested_word(sym::allow, sym::missing_docs, sp);
// pub fn main() { ... }
let main_ret_ty = ecx.ty(sp, ast::TyKind::Tup(ThinVec::new()));
@ -355,7 +357,7 @@ fn mk_main(cx: &mut TestCtxt<'_>) -> P<ast::Item> {
let main = P(ast::Item {
ident: main_id,
attrs: thin_vec![main_attr, coverage_attr],
attrs: thin_vec![main_attr, coverage_attr, missing_docs_attr],
id: ast::DUMMY_NODE_ID,
kind: main,
vis: ast::Visibility { span: sp, kind: ast::VisibilityKind::Public, tokens: None },

View file

@ -9,13 +9,13 @@
#[repr(simd)]
#[derive(Copy, Clone, PartialEq, Debug)]
struct f32x4(pub f32, pub f32, pub f32, pub f32);
struct f32x4(pub [f32; 4]);
use std::intrinsics::simd::*;
fn main() {
let x = f32x4(1.0, 2.0, 3.0, 4.0);
let y = f32x4(2.0, 1.0, 4.0, 3.0);
let x = f32x4([1.0, 2.0, 3.0, 4.0]);
let y = f32x4([2.0, 1.0, 4.0, 3.0]);
#[cfg(not(any(target_arch = "mips", target_arch = "mips64")))]
let nan = f32::NAN;
@ -24,13 +24,13 @@ fn main() {
#[cfg(any(target_arch = "mips", target_arch = "mips64"))]
let nan = f32::from_bits(f32::NAN.to_bits() - 1);
let n = f32x4(nan, nan, nan, nan);
let n = f32x4([nan, nan, nan, nan]);
unsafe {
let min0 = simd_fmin(x, y);
let min1 = simd_fmin(y, x);
assert_eq!(min0, min1);
let e = f32x4(1.0, 1.0, 3.0, 3.0);
let e = f32x4([1.0, 1.0, 3.0, 3.0]);
assert_eq!(min0, e);
let minn = simd_fmin(x, n);
assert_eq!(minn, x);
@ -40,7 +40,7 @@ fn main() {
let max0 = simd_fmax(x, y);
let max1 = simd_fmax(y, x);
assert_eq!(max0, max1);
let e = f32x4(2.0, 2.0, 4.0, 4.0);
let e = f32x4([2.0, 2.0, 4.0, 4.0]);
assert_eq!(max0, e);
let maxn = simd_fmax(x, n);
assert_eq!(maxn, x);

View file

@ -166,7 +166,7 @@ fn main() {
enum Never {}
}
foo(I64X2(0, 0));
foo(I64X2([0, 0]));
transmute_fat_pointer();
@ -204,7 +204,7 @@ fn rust_call_abi() {
}
#[repr(simd)]
struct I64X2(i64, i64);
struct I64X2([i64; 2]);
#[allow(improper_ctypes_definitions)]
extern "C" fn foo(_a: I64X2) {}

View file

@ -272,7 +272,6 @@ fn fat_lto(
}*/
}
};
let mut serialized_bitcode = Vec::new();
{
info!("using {:?} as a base module", module.name);
@ -317,7 +316,6 @@ fn fat_lto(
unimplemented!("from uncompressed file")
}
}
serialized_bitcode.push(bc_decoded);
}
save_temp_bitcode(cgcx, &module, "lto.input");
@ -337,7 +335,7 @@ fn fat_lto(
// of now.
module.module_llvm.temp_dir = Some(tmp_path);
Ok(LtoModuleCodegen::Fat { module, _serialized_bitcode: serialized_bitcode })
Ok(LtoModuleCodegen::Fat(module))
}
pub struct ModuleBuffer(PathBuf);

View file

@ -314,7 +314,6 @@ fn fat_lto(
}
}
};
let mut serialized_bitcode = Vec::new();
{
let (llcx, llmod) = {
let llvm = &module.module_llvm;
@ -342,9 +341,7 @@ fn fat_lto(
serialized_modules.sort_by(|module1, module2| module1.1.cmp(&module2.1));
// For all serialized bitcode files we parse them and link them in as we did
// above, this is all mostly handled in C++. Like above, though, we don't
// know much about the memory management here so we err on the side of being
// save and persist everything with the original module.
// above, this is all mostly handled in C++.
let mut linker = Linker::new(llmod);
for (bc_decoded, name) in serialized_modules {
let _timer = cgcx
@ -355,7 +352,6 @@ fn fat_lto(
info!("linking {:?}", name);
let data = bc_decoded.data();
linker.add(data).map_err(|()| write::llvm_err(dcx, LlvmError::LoadBitcode { name }))?;
serialized_bitcode.push(bc_decoded);
}
drop(linker);
save_temp_bitcode(cgcx, &module, "lto.input");
@ -372,7 +368,7 @@ fn fat_lto(
}
}
Ok(LtoModuleCodegen::Fat { module, _serialized_bitcode: serialized_bitcode })
Ok(LtoModuleCodegen::Fat(module))
}
pub(crate) struct Linker<'a>(&'a mut llvm::Linker<'a>);

View file

@ -41,18 +41,14 @@ pub struct ThinShared<B: WriteBackendMethods> {
}
pub enum LtoModuleCodegen<B: WriteBackendMethods> {
Fat {
module: ModuleCodegen<B::Module>,
_serialized_bitcode: Vec<SerializedModule<B::ModuleBuffer>>,
},
Fat(ModuleCodegen<B::Module>),
Thin(ThinModule<B>),
}
impl<B: WriteBackendMethods> LtoModuleCodegen<B> {
pub fn name(&self) -> &str {
match *self {
LtoModuleCodegen::Fat { .. } => "everything",
LtoModuleCodegen::Fat(_) => "everything",
LtoModuleCodegen::Thin(ref m) => m.name(),
}
}
@ -68,7 +64,7 @@ impl<B: WriteBackendMethods> LtoModuleCodegen<B> {
cgcx: &CodegenContext<B>,
) -> Result<ModuleCodegen<B::Module>, FatalError> {
match self {
LtoModuleCodegen::Fat { mut module, .. } => {
LtoModuleCodegen::Fat(mut module) => {
B::optimize_fat(cgcx, &mut module)?;
Ok(module)
}
@ -81,7 +77,7 @@ impl<B: WriteBackendMethods> LtoModuleCodegen<B> {
pub fn cost(&self) -> u64 {
match *self {
// Only one module with fat LTO, so the cost doesn't matter.
LtoModuleCodegen::Fat { .. } => 0,
LtoModuleCodegen::Fat(_) => 0,
LtoModuleCodegen::Thin(ref m) => m.cost(),
}
}

View file

@ -11,7 +11,7 @@ This will cause an error:
#![feature(repr_simd)]
#[repr(simd)]
struct Bad<T>(T, T, T, T);
struct Bad<T>([T; 4]);
```
This will not:
@ -20,5 +20,5 @@ This will not:
#![feature(repr_simd)]
#[repr(simd)]
struct Good(u32, u32, u32, u32);
struct Good([u32; 4]);
```

View file

@ -1,6 +1,6 @@
A `#[simd]` attribute was applied to an empty tuple struct.
A `#[simd]` attribute was applied to an empty or multi-field struct.
Erroneous code example:
Erroneous code examples:
```compile_fail,E0075
#![feature(repr_simd)]
@ -9,9 +9,15 @@ Erroneous code example:
struct Bad; // error!
```
The `#[simd]` attribute can only be applied to non empty tuple structs, because
it doesn't make sense to try to use SIMD operations when there are no values to
operate on.
```compile_fail,E0075
#![feature(repr_simd)]
#[repr(simd)]
struct Bad([u32; 1], [u32; 1]); // error!
```
The `#[simd]` attribute can only be applied to a single-field struct, because
the one field must be the array of values in the vector.
Fixed example:
@ -19,5 +25,5 @@ Fixed example:
#![feature(repr_simd)]
#[repr(simd)]
struct Good(u32); // ok!
struct Good([u32; 2]); // ok!
```

View file

@ -1,4 +1,4 @@
All types in a tuple struct aren't the same when using the `#[simd]`
The type of the field in a tuple struct isn't an array when using the `#[simd]`
attribute.
Erroneous code example:
@ -7,12 +7,12 @@ Erroneous code example:
#![feature(repr_simd)]
#[repr(simd)]
struct Bad(u16, u32, u32 u32); // error!
struct Bad(u16); // error!
```
When using the `#[simd]` attribute to automatically use SIMD operations in tuple
struct, the types in the struct must all be of the same type, or the compiler
will trigger this error.
structs, if you want a single-lane vector then the field must be a 1-element
array, or the compiler will trigger this error.
Fixed example:
@ -20,5 +20,5 @@ Fixed example:
#![feature(repr_simd)]
#[repr(simd)]
struct Good(u32, u32, u32, u32); // ok!
struct Good([u16; 1]); // ok!
```

View file

@ -7,7 +7,7 @@ Erroneous code example:
#![feature(repr_simd)]
#[repr(simd)]
struct Bad(String); // error!
struct Bad([String; 2]); // error!
```
When using the `#[simd]` attribute on a tuple struct, the elements in the tuple
@ -19,5 +19,5 @@ Fixed example:
#![feature(repr_simd)]
#[repr(simd)]
struct Good(u32, u32, u32, u32); // ok!
struct Good([u32; 4]); // ok!
```

View file

@ -23,11 +23,11 @@ The generic type has to be a SIMD type. Example:
#[repr(simd)]
#[derive(Copy, Clone)]
struct i32x2(i32, i32);
struct i32x2([i32; 2]);
extern "rust-intrinsic" {
fn simd_add<T>(a: T, b: T) -> T;
}
unsafe { simd_add(i32x2(0, 0), i32x2(1, 2)); } // ok!
unsafe { simd_add(i32x2([0, 0]), i32x2([1, 2])); } // ok!
```

View file

@ -168,6 +168,19 @@ impl Lifetime {
(LifetimeSuggestionPosition::Normal, self.ident.span)
}
}
pub fn suggestion(&self, new_lifetime: &str) -> (Span, String) {
debug_assert!(new_lifetime.starts_with('\''));
let (pos, span) = self.suggestion_position();
let code = match pos {
LifetimeSuggestionPosition::Normal => format!("{new_lifetime}"),
LifetimeSuggestionPosition::Ampersand => format!("{new_lifetime} "),
LifetimeSuggestionPosition::ElidedPath => format!("<{new_lifetime}>"),
LifetimeSuggestionPosition::ElidedPathArgument => format!("{new_lifetime}, "),
LifetimeSuggestionPosition::ObjectDefault => format!("+ {new_lifetime}"),
};
(span, code)
}
}
/// A `Path` is essentially Rust's notion of a name; for instance,

View file

@ -1064,20 +1064,29 @@ fn check_simd(tcx: TyCtxt<'_>, sp: Span, def_id: LocalDefId) {
struct_span_code_err!(tcx.dcx(), sp, E0075, "SIMD vector cannot be empty").emit();
return;
}
let e = fields[FieldIdx::ZERO].ty(tcx, args);
if !fields.iter().all(|f| f.ty(tcx, args) == e) {
struct_span_code_err!(tcx.dcx(), sp, E0076, "SIMD vector should be homogeneous")
.with_span_label(sp, "SIMD elements must have the same type")
let array_field = &fields[FieldIdx::ZERO];
let array_ty = array_field.ty(tcx, args);
let ty::Array(element_ty, len_const) = array_ty.kind() else {
struct_span_code_err!(
tcx.dcx(),
sp,
E0076,
"SIMD vector's only field must be an array"
)
.with_span_label(tcx.def_span(array_field.did), "not an array")
.emit();
return;
};
if let Some(second_field) = fields.get(FieldIdx::from_u32(1)) {
struct_span_code_err!(tcx.dcx(), sp, E0075, "SIMD vector cannot have multiple fields")
.with_span_label(tcx.def_span(second_field.did), "excess field")
.emit();
return;
}
let len = if let ty::Array(_ty, c) = e.kind() {
c.try_eval_target_usize(tcx, tcx.param_env(def.did()))
} else {
Some(fields.len() as u64)
};
if let Some(len) = len {
if let Some(len) = len_const.try_eval_target_usize(tcx, tcx.param_env(def.did())) {
if len == 0 {
struct_span_code_err!(tcx.dcx(), sp, E0075, "SIMD vector cannot be empty").emit();
return;
@ -1097,16 +1106,9 @@ fn check_simd(tcx: TyCtxt<'_>, sp: Span, def_id: LocalDefId) {
// These are scalar types which directly match a "machine" type
// Yes: Integers, floats, "thin" pointers
// No: char, "fat" pointers, compound types
match e.kind() {
ty::Param(_) => (), // pass struct<T>(T, T, T, T) through, let monomorphization catch errors
ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::RawPtr(_, _) => (), // struct(u8, u8, u8, u8) is ok
ty::Array(t, _) if matches!(t.kind(), ty::Param(_)) => (), // pass struct<T>([T; N]) through, let monomorphization catch errors
ty::Array(t, _clen)
if matches!(
t.kind(),
ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::RawPtr(_, _)
) =>
{ /* struct([f32; 4]) is ok */ }
match element_ty.kind() {
ty::Param(_) => (), // pass struct<T>([T; 4]) through, let monomorphization catch errors
ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::RawPtr(_, _) => (), // struct([u8; 4]) is ok
_ => {
struct_span_code_err!(
tcx.dcx(),

View file

@ -1191,23 +1191,7 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> {
(generics.span, "<'a>".to_owned())
};
let lifetime_sugg = match lifetime_ref.suggestion_position() {
(hir::LifetimeSuggestionPosition::Normal, span) => {
(span, "'a".to_owned())
}
(hir::LifetimeSuggestionPosition::Ampersand, span) => {
(span, "'a ".to_owned())
}
(hir::LifetimeSuggestionPosition::ElidedPath, span) => {
(span, "<'a>".to_owned())
}
(hir::LifetimeSuggestionPosition::ElidedPathArgument, span) => {
(span, "'a, ".to_owned())
}
(hir::LifetimeSuggestionPosition::ObjectDefault, span) => {
(span, "+ 'a".to_owned())
}
};
let lifetime_sugg = lifetime_ref.suggestion("'a");
let suggestions = vec![lifetime_sugg, new_param_sugg];
diag.span_label(

View file

@ -426,12 +426,6 @@ impl MissingDoc {
article: &'static str,
desc: &'static str,
) {
// If we're building a test harness, then warning about
// documentation is probably not really relevant right now.
if cx.sess().opts.test {
return;
}
// Only check publicly-visible items, using the result from the privacy pass.
// It's an option so the crate root can also use this function (it doesn't
// have a `NodeId`).

View file

@ -181,6 +181,10 @@ impl<'tcx> Interner for TyCtxt<'tcx> {
}
}
fn evaluation_is_concurrent(&self) -> bool {
self.sess.threads() > 1
}
fn expand_abstract_consts<T: TypeFoldable<TyCtxt<'tcx>>>(self, t: T) -> T {
self.expand_abstract_consts(t)
}

View file

@ -1091,29 +1091,21 @@ impl<'tcx> Ty<'tcx> {
}
pub fn simd_size_and_type(self, tcx: TyCtxt<'tcx>) -> (u64, Ty<'tcx>) {
match self.kind() {
Adt(def, args) => {
assert!(def.repr().simd(), "`simd_size_and_type` called on non-SIMD type");
let variant = def.non_enum_variant();
let f0_ty = variant.fields[FieldIdx::ZERO].ty(tcx, args);
match f0_ty.kind() {
// If the first field is an array, we assume it is the only field and its
// elements are the SIMD components.
Array(f0_elem_ty, f0_len) => {
// FIXME(repr_simd): https://github.com/rust-lang/rust/pull/78863#discussion_r522784112
// The way we evaluate the `N` in `[T; N]` here only works since we use
// `simd_size_and_type` post-monomorphization. It will probably start to ICE
// if we use it in generic code. See the `simd-array-trait` ui test.
(f0_len.eval_target_usize(tcx, ParamEnv::empty()), *f0_elem_ty)
}
// Otherwise, the fields of this Adt are the SIMD components (and we assume they
// all have the same type).
_ => (variant.fields.len() as u64, f0_ty),
}
}
_ => bug!("`simd_size_and_type` called on invalid type"),
}
let Adt(def, args) = self.kind() else {
bug!("`simd_size_and_type` called on invalid type")
};
assert!(def.repr().simd(), "`simd_size_and_type` called on non-SIMD type");
let variant = def.non_enum_variant();
assert_eq!(variant.fields.len(), 1);
let field_ty = variant.fields[FieldIdx::ZERO].ty(tcx, args);
let Array(f0_elem_ty, f0_len) = field_ty.kind() else {
bug!("Simd type has non-array field type {field_ty:?}")
};
// FIXME(repr_simd): https://github.com/rust-lang/rust/pull/78863#discussion_r522784112
// The way we evaluate the `N` in `[T; N]` here only works since we use
// `simd_size_and_type` post-monomorphization. It will probably start to ICE
// if we use it in generic code. See the `simd-array-trait` ui test.
(f0_len.eval_target_usize(tcx, ParamEnv::empty()), *f0_elem_ty)
}
#[inline]

View file

@ -32,12 +32,6 @@ pub(super) use self::AddCallGuards::*;
impl<'tcx> crate::MirPass<'tcx> for AddCallGuards {
fn run_pass(&self, _tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
self.add_call_guards(body);
}
}
impl AddCallGuards {
pub(super) fn add_call_guards(&self, body: &mut Body<'_>) {
let mut pred_count: IndexVec<_, _> =
body.basic_blocks.predecessors().iter().map(|ps| ps.len()).collect();
pred_count[START_BLOCK] += 1;

View file

@ -40,35 +40,34 @@ pub(super) struct AddMovesForPackedDrops;
impl<'tcx> crate::MirPass<'tcx> for AddMovesForPackedDrops {
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
debug!("add_moves_for_packed_drops({:?} @ {:?})", body.source, body.span);
add_moves_for_packed_drops(tcx, body);
}
}
fn add_moves_for_packed_drops<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
let patch = add_moves_for_packed_drops_patch(tcx, body);
patch.apply(body);
}
let def_id = body.source.def_id();
let mut patch = MirPatch::new(body);
let param_env = tcx.param_env(def_id);
fn add_moves_for_packed_drops_patch<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>) -> MirPatch<'tcx> {
let def_id = body.source.def_id();
let mut patch = MirPatch::new(body);
let param_env = tcx.param_env(def_id);
for (bb, data) in body.basic_blocks.iter_enumerated() {
let loc = Location { block: bb, statement_index: data.statements.len() };
let terminator = data.terminator();
for (bb, data) in body.basic_blocks.iter_enumerated() {
let loc = Location { block: bb, statement_index: data.statements.len() };
let terminator = data.terminator();
match terminator.kind {
TerminatorKind::Drop { place, .. }
if util::is_disaligned(tcx, body, param_env, place) =>
{
add_move_for_packed_drop(tcx, body, &mut patch, terminator, loc, data.is_cleanup);
match terminator.kind {
TerminatorKind::Drop { place, .. }
if util::is_disaligned(tcx, body, param_env, place) =>
{
add_move_for_packed_drop(
tcx,
body,
&mut patch,
terminator,
loc,
data.is_cleanup,
);
}
_ => {}
}
_ => {}
}
}
patch
patch.apply(body);
}
}
fn add_move_for_packed_drop<'tcx>(

View file

@ -60,7 +60,9 @@ impl<'tcx> crate::MirPass<'tcx> for AddRetag {
let basic_blocks = body.basic_blocks.as_mut();
let local_decls = &body.local_decls;
let needs_retag = |place: &Place<'tcx>| {
!place.is_indirect_first_projection() // we're not really interested in stores to "outside" locations, they are hard to keep track of anyway
// We're not really interested in stores to "outside" locations, they are hard to keep
// track of anyway.
!place.is_indirect_first_projection()
&& may_contain_reference(place.ty(&*local_decls, tcx).ty, /*depth*/ 3, tcx)
&& !local_decls[place.local].is_deref_temp()
};
@ -129,9 +131,9 @@ impl<'tcx> crate::MirPass<'tcx> for AddRetag {
StatementKind::Assign(box (ref place, ref rvalue)) => {
let add_retag = match rvalue {
// Ptr-creating operations already do their own internal retagging, no
// need to also add a retag statement.
// *Except* if we are deref'ing a Box, because those get desugared to directly working
// with the inner raw pointer! That's relevant for `RawPtr` as Miri otherwise makes it
// need to also add a retag statement. *Except* if we are deref'ing a
// Box, because those get desugared to directly working with the inner
// raw pointer! That's relevant for `RawPtr` as Miri otherwise makes it
// a NOP when the original pointer is already raw.
Rvalue::RawPtr(_mutbl, place) => {
// Using `is_box_global` here is a bit sketchy: if this code is

View file

@ -51,18 +51,14 @@ impl<'a, 'tcx> MutVisitor<'tcx> for SubTypeChecker<'a, 'tcx> {
// // gets transformed to
// let temp: rval_ty = rval;
// let place: place_ty = temp as place_ty;
fn subtype_finder<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
let patch = MirPatch::new(body);
let mut checker = SubTypeChecker { tcx, patcher: patch, local_decls: &body.local_decls };
for (bb, data) in body.basic_blocks.as_mut_preserves_cfg().iter_enumerated_mut() {
checker.visit_basic_block_data(bb, data);
}
checker.patcher.apply(body);
}
impl<'tcx> crate::MirPass<'tcx> for Subtyper {
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
subtype_finder(tcx, body);
let patch = MirPatch::new(body);
let mut checker = SubTypeChecker { tcx, patcher: patch, local_decls: &body.local_decls };
for (bb, data) in body.basic_blocks.as_mut_preserves_cfg().iter_enumerated_mut() {
checker.visit_basic_block_data(bb, data);
}
checker.patcher.apply(body);
}
}

View file

@ -123,6 +123,7 @@ impl<'tcx> Visitor<'tcx> for ConstMutationChecker<'_, 'tcx> {
self.super_statement(stmt, loc);
self.target_local = None;
}
fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, loc: Location) {
if let Rvalue::Ref(_, BorrowKind::Mut { .. }, place) = rvalue {
let local = place.local;

View file

@ -27,37 +27,34 @@ impl<'tcx> crate::MirPass<'tcx> for CopyProp {
#[instrument(level = "trace", skip(self, tcx, body))]
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
debug!(def_id = ?body.source.def_id());
propagate_ssa(tcx, body);
}
}
fn propagate_ssa<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
let param_env = tcx.param_env_reveal_all_normalized(body.source.def_id());
let ssa = SsaLocals::new(tcx, body, param_env);
let param_env = tcx.param_env_reveal_all_normalized(body.source.def_id());
let ssa = SsaLocals::new(tcx, body, param_env);
let fully_moved = fully_moved_locals(&ssa, body);
debug!(?fully_moved);
let fully_moved = fully_moved_locals(&ssa, body);
debug!(?fully_moved);
let mut storage_to_remove = BitSet::new_empty(fully_moved.domain_size());
for (local, &head) in ssa.copy_classes().iter_enumerated() {
if local != head {
storage_to_remove.insert(head);
let mut storage_to_remove = BitSet::new_empty(fully_moved.domain_size());
for (local, &head) in ssa.copy_classes().iter_enumerated() {
if local != head {
storage_to_remove.insert(head);
}
}
}
let any_replacement = ssa.copy_classes().iter_enumerated().any(|(l, &h)| l != h);
let any_replacement = ssa.copy_classes().iter_enumerated().any(|(l, &h)| l != h);
Replacer {
tcx,
copy_classes: ssa.copy_classes(),
fully_moved,
borrowed_locals: ssa.borrowed_locals(),
storage_to_remove,
}
.visit_body_preserves_cfg(body);
Replacer {
tcx,
copy_classes: ssa.copy_classes(),
fully_moved,
borrowed_locals: ssa.borrowed_locals(),
storage_to_remove,
}
.visit_body_preserves_cfg(body);
if any_replacement {
crate::simplify::remove_unused_definitions(body);
if any_replacement {
crate::simplify::remove_unused_definitions(body);
}
}
}
@ -140,7 +137,8 @@ impl<'tcx> MutVisitor<'tcx> for Replacer<'_, 'tcx> {
fn visit_operand(&mut self, operand: &mut Operand<'tcx>, loc: Location) {
if let Operand::Move(place) = *operand
// A move out of a projection of a copy is equivalent to a copy of the original projection.
// A move out of a projection of a copy is equivalent to a copy of the original
// projection.
&& !place.is_indirect_first_projection()
&& !self.fully_moved.contains(place.local)
{

View file

@ -74,12 +74,11 @@ pub(super) struct CoverageCounters {
}
impl CoverageCounters {
/// Makes [`BcbCounter`] `Counter`s and `Expressions` for the `BasicCoverageBlock`s directly or
/// indirectly associated with coverage spans, and accumulates additional `Expression`s
/// representing intermediate values.
/// Ensures that each BCB node needing a counter has one, by creating physical
/// counters or counter expressions for nodes and edges as required.
pub(super) fn make_bcb_counters(
basic_coverage_blocks: &CoverageGraph,
bcb_has_coverage_spans: impl Fn(BasicCoverageBlock) -> bool,
bcb_needs_counter: impl Fn(BasicCoverageBlock) -> bool,
) -> Self {
let num_bcbs = basic_coverage_blocks.num_nodes();
@ -91,8 +90,7 @@ impl CoverageCounters {
expressions_memo: FxHashMap::default(),
};
MakeBcbCounters::new(&mut this, basic_coverage_blocks)
.make_bcb_counters(bcb_has_coverage_spans);
MakeBcbCounters::new(&mut this, basic_coverage_blocks).make_bcb_counters(bcb_needs_counter);
this
}
@ -241,10 +239,7 @@ impl CoverageCounters {
}
}
/// Traverse the `CoverageGraph` and add either a `Counter` or `Expression` to every BCB, to be
/// injected with coverage spans. `Expressions` have no runtime overhead, so if a viable expression
/// (adding or subtracting two other counters or expressions) can compute the same result as an
/// embedded counter, an `Expression` should be used.
/// Helper struct that allows counter creation to inspect the BCB graph.
struct MakeBcbCounters<'a> {
coverage_counters: &'a mut CoverageCounters,
basic_coverage_blocks: &'a CoverageGraph,
@ -258,36 +253,21 @@ impl<'a> MakeBcbCounters<'a> {
Self { coverage_counters, basic_coverage_blocks }
}
/// If two `BasicCoverageBlock`s branch from another `BasicCoverageBlock`, one of the branches
/// can be counted by `Expression` by subtracting the other branch from the branching
/// block. Otherwise, the `BasicCoverageBlock` executed the least should have the `Counter`.
/// One way to predict which branch executes the least is by considering loops. A loop is exited
/// at a branch, so the branch that jumps to a `BasicCoverageBlock` outside the loop is almost
/// always executed less than the branch that does not exit the loop.
fn make_bcb_counters(&mut self, bcb_has_coverage_spans: impl Fn(BasicCoverageBlock) -> bool) {
fn make_bcb_counters(&mut self, bcb_needs_counter: impl Fn(BasicCoverageBlock) -> bool) {
debug!("make_bcb_counters(): adding a counter or expression to each BasicCoverageBlock");
// Walk the `CoverageGraph`. For each `BasicCoverageBlock` node with an associated
// coverage span, add a counter. If the `BasicCoverageBlock` branches, add a counter or
// expression to each branch `BasicCoverageBlock` (if the branch BCB has only one incoming
// edge) or edge from the branching BCB to the branch BCB (if the branch BCB has multiple
// incoming edges).
// Traverse the coverage graph, ensuring that every node that needs a
// coverage counter has one.
//
// The `TraverseCoverageGraphWithLoops` traversal ensures that, when a loop is encountered,
// all `BasicCoverageBlock` nodes in the loop are visited before visiting any node outside
// the loop. The `traversal` state includes a `context_stack`, providing a way to know if
// the current BCB is in one or more nested loops or not.
// The traversal tries to ensure that, when a loop is encountered, all
// nodes within the loop are visited before visiting any nodes outside
// the loop. It also keeps track of which loop(s) the traversal is
// currently inside.
let mut traversal = TraverseCoverageGraphWithLoops::new(self.basic_coverage_blocks);
while let Some(bcb) = traversal.next() {
if bcb_has_coverage_spans(bcb) {
debug!("{:?} has at least one coverage span. Get or make its counter", bcb);
self.make_node_and_branch_counters(&traversal, bcb);
} else {
debug!(
"{:?} does not have any coverage spans. A counter will only be added if \
and when a covered BCB has an expression dependency.",
bcb,
);
let _span = debug_span!("traversal", ?bcb).entered();
if bcb_needs_counter(bcb) {
self.make_node_counter_and_out_edge_counters(&traversal, bcb);
}
}
@ -298,73 +278,66 @@ impl<'a> MakeBcbCounters<'a> {
);
}
fn make_node_and_branch_counters(
/// Make sure the given node has a node counter, and then make sure each of
/// its out-edges has an edge counter (if appropriate).
#[instrument(level = "debug", skip(self, traversal))]
fn make_node_counter_and_out_edge_counters(
&mut self,
traversal: &TraverseCoverageGraphWithLoops<'_>,
from_bcb: BasicCoverageBlock,
) {
// First, ensure that this node has a counter of some kind.
// We might also use its term later to compute one of the branch counters.
let from_bcb_operand = self.get_or_make_counter_operand(from_bcb);
// We might also use that counter to compute one of the out-edge counters.
let node_counter = self.get_or_make_node_counter(from_bcb);
let branch_target_bcbs = self.basic_coverage_blocks.successors[from_bcb].as_slice();
let successors = self.basic_coverage_blocks.successors[from_bcb].as_slice();
// If this node doesn't have multiple out-edges, or all of its out-edges
// already have counters, then we don't need to create edge counters.
let needs_branch_counters = branch_target_bcbs.len() > 1
&& branch_target_bcbs
.iter()
.any(|&to_bcb| self.branch_has_no_counter(from_bcb, to_bcb));
if !needs_branch_counters {
let needs_out_edge_counters = successors.len() > 1
&& successors.iter().any(|&to_bcb| self.edge_has_no_counter(from_bcb, to_bcb));
if !needs_out_edge_counters {
return;
}
debug!(
"{from_bcb:?} has some branch(es) without counters:\n {}",
branch_target_bcbs
.iter()
.map(|&to_bcb| {
format!("{from_bcb:?}->{to_bcb:?}: {:?}", self.branch_counter(from_bcb, to_bcb))
})
.collect::<Vec<_>>()
.join("\n "),
);
if tracing::enabled!(tracing::Level::DEBUG) {
let _span =
debug_span!("node has some out-edges without counters", ?from_bcb).entered();
for &to_bcb in successors {
debug!(?to_bcb, counter=?self.edge_counter(from_bcb, to_bcb));
}
}
// Of the branch edges that don't have counters yet, one can be given an expression
// (computed from the other edges) instead of a dedicated counter.
let expression_to_bcb = self.choose_preferred_expression_branch(traversal, from_bcb);
// Of the out-edges that don't have counters yet, one can be given an expression
// (computed from the other out-edges) instead of a dedicated counter.
let expression_to_bcb = self.choose_out_edge_for_expression(traversal, from_bcb);
// For each branch arm other than the one that was chosen to get an expression,
// For each out-edge other than the one that was chosen to get an expression,
// ensure that it has a counter (existing counter/expression or a new counter),
// and accumulate the corresponding terms into a single sum term.
let sum_of_all_other_branches: BcbCounter = {
let _span = debug_span!("sum_of_all_other_branches", ?expression_to_bcb).entered();
branch_target_bcbs
// and accumulate the corresponding counters into a single sum expression.
let sum_of_all_other_out_edges: BcbCounter = {
let _span = debug_span!("sum_of_all_other_out_edges", ?expression_to_bcb).entered();
successors
.iter()
.copied()
// Skip the chosen branch, since we'll calculate it from the other branches.
// Skip the chosen edge, since we'll calculate its count from this sum.
.filter(|&to_bcb| to_bcb != expression_to_bcb)
.fold(None, |accum, to_bcb| {
let _span = debug_span!("to_bcb", ?accum, ?to_bcb).entered();
let branch_counter = self.get_or_make_edge_counter_operand(from_bcb, to_bcb);
Some(self.coverage_counters.make_sum_expression(accum, branch_counter))
let edge_counter = self.get_or_make_edge_counter(from_bcb, to_bcb);
Some(self.coverage_counters.make_sum_expression(accum, edge_counter))
})
.expect("there must be at least one other branch")
.expect("there must be at least one other out-edge")
};
// For the branch that was chosen to get an expression, create that expression
// by taking the count of the node we're branching from, and subtracting the
// sum of all the other branches.
debug!(
"Making an expression for the selected expression_branch: \
{expression_to_bcb:?} (expression_branch predecessors: {:?})",
self.bcb_predecessors(expression_to_bcb),
);
// Now create an expression for the chosen edge, by taking the counter
// for its source node and subtracting the sum of its sibling out-edges.
let expression = self.coverage_counters.make_expression(
from_bcb_operand,
node_counter,
Op::Subtract,
sum_of_all_other_branches,
sum_of_all_other_out_edges,
);
debug!("{expression_to_bcb:?} gets an expression: {expression:?}");
if self.basic_coverage_blocks.bcb_has_multiple_in_edges(expression_to_bcb) {
self.coverage_counters.set_bcb_edge_counter(from_bcb, expression_to_bcb, expression);
@ -374,7 +347,7 @@ impl<'a> MakeBcbCounters<'a> {
}
#[instrument(level = "debug", skip(self))]
fn get_or_make_counter_operand(&mut self, bcb: BasicCoverageBlock) -> BcbCounter {
fn get_or_make_node_counter(&mut self, bcb: BasicCoverageBlock) -> BcbCounter {
// If the BCB already has a counter, return it.
if let Some(counter_kind) = self.coverage_counters.bcb_counters[bcb] {
debug!("{bcb:?} already has a counter: {counter_kind:?}");
@ -411,7 +384,7 @@ impl<'a> MakeBcbCounters<'a> {
.copied()
.fold(None, |accum, from_bcb| {
let _span = debug_span!("from_bcb", ?accum, ?from_bcb).entered();
let edge_counter = self.get_or_make_edge_counter_operand(from_bcb, bcb);
let edge_counter = self.get_or_make_edge_counter(from_bcb, bcb);
Some(self.coverage_counters.make_sum_expression(accum, edge_counter))
})
.expect("there must be at least one in-edge")
@ -422,7 +395,7 @@ impl<'a> MakeBcbCounters<'a> {
}
#[instrument(level = "debug", skip(self))]
fn get_or_make_edge_counter_operand(
fn get_or_make_edge_counter(
&mut self,
from_bcb: BasicCoverageBlock,
to_bcb: BasicCoverageBlock,
@ -431,13 +404,13 @@ impl<'a> MakeBcbCounters<'a> {
// a node counter instead, since it will have the same value.
if !self.basic_coverage_blocks.bcb_has_multiple_in_edges(to_bcb) {
assert_eq!([from_bcb].as_slice(), self.basic_coverage_blocks.predecessors[to_bcb]);
return self.get_or_make_counter_operand(to_bcb);
return self.get_or_make_node_counter(to_bcb);
}
// If the source BCB has only one successor (assumed to be the given target), an edge
// counter is unnecessary. Just get or make a counter for the source BCB.
if self.bcb_successors(from_bcb).len() == 1 {
return self.get_or_make_counter_operand(from_bcb);
return self.get_or_make_node_counter(from_bcb);
}
// If the edge already has a counter, return it.
@ -455,82 +428,79 @@ impl<'a> MakeBcbCounters<'a> {
self.coverage_counters.set_bcb_edge_counter(from_bcb, to_bcb, counter_kind)
}
/// Select a branch for the expression, either the recommended `reloop_branch`, or if none was
/// found, select any branch.
fn choose_preferred_expression_branch(
/// Choose one of the out-edges of `from_bcb` to receive an expression
/// instead of a physical counter, and returns that edge's target node.
///
/// - Precondition: The node must have at least one out-edge without a counter.
/// - Postcondition: The selected edge does not have an edge counter.
fn choose_out_edge_for_expression(
&self,
traversal: &TraverseCoverageGraphWithLoops<'_>,
from_bcb: BasicCoverageBlock,
) -> BasicCoverageBlock {
let good_reloop_branch = self.find_good_reloop_branch(traversal, from_bcb);
if let Some(reloop_target) = good_reloop_branch {
assert!(self.branch_has_no_counter(from_bcb, reloop_target));
if let Some(reloop_target) = self.find_good_reloop_edge(traversal, from_bcb) {
assert!(self.edge_has_no_counter(from_bcb, reloop_target));
debug!("Selecting reloop target {reloop_target:?} to get an expression");
reloop_target
} else {
let &branch_without_counter = self
.bcb_successors(from_bcb)
.iter()
.find(|&&to_bcb| self.branch_has_no_counter(from_bcb, to_bcb))
.expect(
"needs_branch_counters was `true` so there should be at least one \
branch",
);
debug!(
"Selecting any branch={:?} that still needs a counter, to get the \
`Expression` because there was no `reloop_branch`, or it already had a \
counter",
branch_without_counter
);
branch_without_counter
return reloop_target;
}
// We couldn't identify a "good" edge, so just choose any edge that
// doesn't already have a counter.
let arbitrary_target = self
.bcb_successors(from_bcb)
.iter()
.copied()
.find(|&to_bcb| self.edge_has_no_counter(from_bcb, to_bcb))
.expect("precondition: at least one out-edge without a counter");
debug!(?arbitrary_target, "selecting arbitrary out-edge to get an expression");
arbitrary_target
}
/// Tries to find a branch that leads back to the top of a loop, and that
/// doesn't already have a counter. Such branches are good candidates to
/// Tries to find an edge that leads back to the top of a loop, and that
/// doesn't already have a counter. Such edges are good candidates to
/// be given an expression (instead of a physical counter), because they
/// will tend to be executed more times than a loop-exit branch.
fn find_good_reloop_branch(
/// will tend to be executed more times than a loop-exit edge.
fn find_good_reloop_edge(
&self,
traversal: &TraverseCoverageGraphWithLoops<'_>,
from_bcb: BasicCoverageBlock,
) -> Option<BasicCoverageBlock> {
let branch_target_bcbs = self.bcb_successors(from_bcb);
let successors = self.bcb_successors(from_bcb);
// Consider each loop on the current traversal context stack, top-down.
for reloop_bcbs in traversal.reloop_bcbs_per_loop() {
let mut all_branches_exit_this_loop = true;
let mut all_edges_exit_this_loop = true;
// Try to find a branch that doesn't exit this loop and doesn't
// Try to find an out-edge that doesn't exit this loop and doesn't
// already have a counter.
for &branch_target_bcb in branch_target_bcbs {
// A branch is a reloop branch if it dominates any BCB that has
// an edge back to the loop header. (Other branches are exits.)
let is_reloop_branch = reloop_bcbs.iter().any(|&reloop_bcb| {
self.basic_coverage_blocks.dominates(branch_target_bcb, reloop_bcb)
for &target_bcb in successors {
// An edge is a reloop edge if its target dominates any BCB that has
// an edge back to the loop header. (Otherwise it's an exit edge.)
let is_reloop_edge = reloop_bcbs.iter().any(|&reloop_bcb| {
self.basic_coverage_blocks.dominates(target_bcb, reloop_bcb)
});
if is_reloop_branch {
all_branches_exit_this_loop = false;
if self.branch_has_no_counter(from_bcb, branch_target_bcb) {
// We found a good branch to be given an expression.
return Some(branch_target_bcb);
if is_reloop_edge {
all_edges_exit_this_loop = false;
if self.edge_has_no_counter(from_bcb, target_bcb) {
// We found a good out-edge to be given an expression.
return Some(target_bcb);
}
// Keep looking for another reloop branch without a counter.
// Keep looking for another reloop edge without a counter.
} else {
// This branch exits the loop.
// This edge exits the loop.
}
}
if !all_branches_exit_this_loop {
// We found one or more reloop branches, but all of them already
// have counters. Let the caller choose one of the exit branches.
debug!("All reloop branches had counters; skip checking the other loops");
if !all_edges_exit_this_loop {
// We found one or more reloop edges, but all of them already
// have counters. Let the caller choose one of the other edges.
debug!("All reloop edges had counters; skipping the other loops");
return None;
}
// All of the branches exit this loop, so keep looking for a good
// reloop branch for one of the outer loops.
// All of the out-edges exit this loop, so keep looking for a good
// reloop edge for one of the outer loops.
}
None
@ -547,15 +517,15 @@ impl<'a> MakeBcbCounters<'a> {
}
#[inline]
fn branch_has_no_counter(
fn edge_has_no_counter(
&self,
from_bcb: BasicCoverageBlock,
to_bcb: BasicCoverageBlock,
) -> bool {
self.branch_counter(from_bcb, to_bcb).is_none()
self.edge_counter(from_bcb, to_bcb).is_none()
}
fn branch_counter(
fn edge_counter(
&self,
from_bcb: BasicCoverageBlock,
to_bcb: BasicCoverageBlock,

View file

@ -279,7 +279,8 @@ fn inject_mcdc_statements<'tcx>(
basic_coverage_blocks: &CoverageGraph,
extracted_mappings: &ExtractedMappings,
) {
// Inject test vector update first because `inject_statement` always insert new statement at head.
// Inject test vector update first because `inject_statement` always insert new statement at
// head.
for &mappings::MCDCDecision {
span: _,
ref end_bcbs,

View file

@ -647,7 +647,8 @@ fn try_write_constant<'tcx>(
ty::FnDef(..) => {}
// Those are scalars, must be handled above.
ty::Bool | ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Char => throw_machine_stop_str!("primitive type with provenance"),
ty::Bool | ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Char =>
throw_machine_stop_str!("primitive type with provenance"),
ty::Tuple(elem_tys) => {
for (i, elem) in elem_tys.iter().enumerate() {

View file

@ -42,9 +42,9 @@ impl<'tcx> Visitor<'tcx> for DeduceReadOnly {
}
PlaceContext::NonMutatingUse(NonMutatingUseContext::RawBorrow) => {
// Whether mutating though a `&raw const` is allowed is still undecided, so we
// disable any sketchy `readonly` optimizations for now.
// But we only need to do this if the pointer would point into the argument.
// IOW: for indirect places, like `&raw (*local).field`, this surely cannot mutate `local`.
// disable any sketchy `readonly` optimizations for now. But we only need to do
// this if the pointer would point into the argument. IOW: for indirect places,
// like `&raw (*local).field`, this surely cannot mutate `local`.
!place.is_indirect()
}
PlaceContext::NonMutatingUse(..) | PlaceContext::NonUse(..) => {

View file

@ -69,8 +69,8 @@ fn find_duplicates(body: &Body<'_>) -> FxHashMap<BasicBlock, BasicBlock> {
// For example, if bb1, bb2 and bb3 are duplicates, we will first insert bb3 in same_hashes.
// Then we will see that bb2 is a duplicate of bb3,
// and insert bb2 with the replacement bb3 in the duplicates list.
// When we see bb1, we see that it is a duplicate of bb3, and therefore insert it in the duplicates list
// with replacement bb3.
// When we see bb1, we see that it is a duplicate of bb3, and therefore insert it in the
// duplicates list with replacement bb3.
// When the duplicates are removed, we will end up with only bb3.
for (bb, bbd) in body.basic_blocks.iter_enumerated().rev().filter(|(_, bbd)| !bbd.is_cleanup) {
// Basic blocks can get really big, so to avoid checking for duplicates in basic blocks
@ -105,7 +105,8 @@ struct BasicBlockHashable<'tcx, 'a> {
impl Hash for BasicBlockHashable<'_, '_> {
fn hash<H: Hasher>(&self, state: &mut H) {
hash_statements(state, self.basic_block_data.statements.iter());
// Note that since we only hash the kind, we lose span information if we deduplicate the blocks
// Note that since we only hash the kind, we lose span information if we deduplicate the
// blocks.
self.basic_block_data.terminator().kind.hash(state);
}
}

View file

@ -242,7 +242,7 @@ impl<'tcx> crate::MirPass<'tcx> for DestinationPropagation {
}
round_count += 1;
apply_merges(body, tcx, &merges, &merged_locals);
apply_merges(body, tcx, merges, merged_locals);
}
trace!(round_count);
@ -281,20 +281,20 @@ struct Candidates {
fn apply_merges<'tcx>(
body: &mut Body<'tcx>,
tcx: TyCtxt<'tcx>,
merges: &FxIndexMap<Local, Local>,
merged_locals: &BitSet<Local>,
merges: FxIndexMap<Local, Local>,
merged_locals: BitSet<Local>,
) {
let mut merger = Merger { tcx, merges, merged_locals };
merger.visit_body_preserves_cfg(body);
}
struct Merger<'a, 'tcx> {
struct Merger<'tcx> {
tcx: TyCtxt<'tcx>,
merges: &'a FxIndexMap<Local, Local>,
merged_locals: &'a BitSet<Local>,
merges: FxIndexMap<Local, Local>,
merged_locals: BitSet<Local>,
}
impl<'a, 'tcx> MutVisitor<'tcx> for Merger<'a, 'tcx> {
impl<'tcx> MutVisitor<'tcx> for Merger<'tcx> {
fn tcx(&self) -> TyCtxt<'tcx> {
self.tcx
}

View file

@ -261,8 +261,8 @@ fn evaluate_candidate<'tcx>(
// };
// ```
//
// Hoisting the `discriminant(Q)` out of the `A` arm causes us to compute the discriminant of an
// invalid value, which is UB.
// Hoisting the `discriminant(Q)` out of the `A` arm causes us to compute the discriminant
// of an invalid value, which is UB.
// In order to fix this, **we would either need to show that the discriminant computation of
// `place` is computed in all branches**.
// FIXME(#95162) For the moment, we adopt a conservative approach and

View file

@ -20,8 +20,8 @@ use tracing::{debug, instrument};
use crate::deref_separator::deref_finder;
/// During MIR building, Drop terminators are inserted in every place where a drop may occur.
/// However, in this phase, the presence of these terminators does not guarantee that a destructor will run,
/// as the target of the drop may be uninitialized.
/// However, in this phase, the presence of these terminators does not guarantee that a destructor
/// will run, as the target of the drop may be uninitialized.
/// In general, the compiler cannot determine at compile time whether a destructor will run or not.
///
/// At a high level, this pass refines Drop to only run the destructor if the
@ -30,10 +30,10 @@ use crate::deref_separator::deref_finder;
/// Once this is complete, Drop terminators in the MIR correspond to a call to the "drop glue" or
/// "drop shim" for the type of the dropped place.
///
/// This pass relies on dropped places having an associated move path, which is then used to determine
/// the initialization status of the place and its descendants.
/// It's worth noting that a MIR containing a Drop without an associated move path is probably ill formed,
/// as it would allow running a destructor on a place behind a reference:
/// This pass relies on dropped places having an associated move path, which is then used to
/// determine the initialization status of the place and its descendants.
/// It's worth noting that a MIR containing a Drop without an associated move path is probably ill
/// formed, as it would allow running a destructor on a place behind a reference:
///
/// ```text
/// fn drop_term<T>(t: &mut T) {
@ -377,8 +377,8 @@ impl<'a, 'tcx> ElaborateDropsCtxt<'a, 'tcx> {
);
}
// 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.
// The borrow checker requires that these locations are initialized before the
// assignment, so we just leave an unconditional drop.
assert!(!data.is_cleanup);
}
}

View file

@ -60,8 +60,9 @@ fn has_ffi_unwind_calls(tcx: TyCtxt<'_>, local_def_id: LocalDefId) -> bool {
let fn_def_id = match ty.kind() {
ty::FnPtr(..) => None,
&ty::FnDef(def_id, _) => {
// Rust calls cannot themselves create foreign unwinds (even if they use a non-Rust ABI).
// So the leak of the foreign unwind into Rust can only be elsewhere, not here.
// Rust calls cannot themselves create foreign unwinds (even if they use a non-Rust
// ABI). So the leak of the foreign unwind into Rust can only be elsewhere, not
// here.
if !tcx.is_foreign_item(def_id) {
continue;
}

View file

@ -92,8 +92,8 @@ impl<'tcx> FunctionItemRefChecker<'_, 'tcx> {
{
let mut span = self.nth_arg_span(args, arg_num);
if span.from_expansion() {
// The operand's ctxt wouldn't display the lint since it's inside a macro so
// we have to use the callsite's ctxt.
// The operand's ctxt wouldn't display the lint since it's
// inside a macro so we have to use the callsite's ctxt.
let callsite_ctxt = span.source_callsite().ctxt();
span = span.with_ctxt(callsite_ctxt);
}

View file

@ -119,53 +119,50 @@ impl<'tcx> crate::MirPass<'tcx> for GVN {
#[instrument(level = "trace", skip(self, tcx, body))]
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
debug!(def_id = ?body.source.def_id());
propagate_ssa(tcx, body);
}
}
fn propagate_ssa<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
let param_env = tcx.param_env_reveal_all_normalized(body.source.def_id());
let ssa = SsaLocals::new(tcx, body, param_env);
// Clone dominators as we need them while mutating the body.
let dominators = body.basic_blocks.dominators().clone();
let param_env = tcx.param_env_reveal_all_normalized(body.source.def_id());
let ssa = SsaLocals::new(tcx, body, param_env);
// Clone dominators because we need them while mutating the body.
let dominators = body.basic_blocks.dominators().clone();
let mut state = VnState::new(tcx, body, param_env, &ssa, &dominators, &body.local_decls);
ssa.for_each_assignment_mut(
body.basic_blocks.as_mut_preserves_cfg(),
|local, value, location| {
let value = match value {
// We do not know anything of this assigned value.
AssignedValue::Arg | AssignedValue::Terminator => None,
// Try to get some insight.
AssignedValue::Rvalue(rvalue) => {
let value = state.simplify_rvalue(rvalue, location);
// FIXME(#112651) `rvalue` may have a subtype to `local`. We can only mark `local` as
// reusable if we have an exact type match.
if state.local_decls[local].ty != rvalue.ty(state.local_decls, tcx) {
return;
let mut state = VnState::new(tcx, body, param_env, &ssa, dominators, &body.local_decls);
ssa.for_each_assignment_mut(
body.basic_blocks.as_mut_preserves_cfg(),
|local, value, location| {
let value = match value {
// We do not know anything of this assigned value.
AssignedValue::Arg | AssignedValue::Terminator => None,
// Try to get some insight.
AssignedValue::Rvalue(rvalue) => {
let value = state.simplify_rvalue(rvalue, location);
// FIXME(#112651) `rvalue` may have a subtype to `local`. We can only mark
// `local` as reusable if we have an exact type match.
if state.local_decls[local].ty != rvalue.ty(state.local_decls, tcx) {
return;
}
value
}
value
}
};
// `next_opaque` is `Some`, so `new_opaque` must return `Some`.
let value = value.or_else(|| state.new_opaque()).unwrap();
state.assign(local, value);
},
);
};
// `next_opaque` is `Some`, so `new_opaque` must return `Some`.
let value = value.or_else(|| state.new_opaque()).unwrap();
state.assign(local, value);
},
);
// Stop creating opaques during replacement as it is useless.
state.next_opaque = None;
// Stop creating opaques during replacement as it is useless.
state.next_opaque = None;
let reverse_postorder = body.basic_blocks.reverse_postorder().to_vec();
for bb in reverse_postorder {
let data = &mut body.basic_blocks.as_mut_preserves_cfg()[bb];
state.visit_basic_block_data(bb, data);
let reverse_postorder = body.basic_blocks.reverse_postorder().to_vec();
for bb in reverse_postorder {
let data = &mut body.basic_blocks.as_mut_preserves_cfg()[bb];
state.visit_basic_block_data(bb, data);
}
// For each local that is reused (`y` above), we remove its storage statements do avoid any
// difficulty. Those locals are SSA, so should be easy to optimize by LLVM without storage
// statements.
StorageRemover { tcx, reused_locals: state.reused_locals }.visit_body_preserves_cfg(body);
}
// For each local that is reused (`y` above), we remove its storage statements do avoid any
// difficulty. Those locals are SSA, so should be easy to optimize by LLVM without storage
// statements.
StorageRemover { tcx, reused_locals: state.reused_locals }.visit_body_preserves_cfg(body);
}
newtype_index! {
@ -261,7 +258,7 @@ struct VnState<'body, 'tcx> {
/// Cache the value of the `unsized_locals` features, to avoid fetching it repeatedly in a loop.
feature_unsized_locals: bool,
ssa: &'body SsaLocals,
dominators: &'body Dominators<BasicBlock>,
dominators: Dominators<BasicBlock>,
reused_locals: BitSet<Local>,
}
@ -271,7 +268,7 @@ impl<'body, 'tcx> VnState<'body, 'tcx> {
body: &Body<'tcx>,
param_env: ty::ParamEnv<'tcx>,
ssa: &'body SsaLocals,
dominators: &'body Dominators<BasicBlock>,
dominators: Dominators<BasicBlock>,
local_decls: &'body LocalDecls<'tcx>,
) -> Self {
// Compute a rough estimate of the number of values in the body from the number of
@ -480,7 +477,8 @@ impl<'body, 'tcx> VnState<'body, 'tcx> {
let pointer = self.evaluated[local].as_ref()?;
let mut mplace = self.ecx.deref_pointer(pointer).ok()?;
for proj in place.projection.iter().skip(1) {
// We have no call stack to associate a local with a value, so we cannot interpret indexing.
// We have no call stack to associate a local with a value, so we cannot
// interpret indexing.
if matches!(proj, ProjectionElem::Index(_)) {
return None;
}
@ -1382,7 +1380,8 @@ fn op_to_prop_const<'tcx>(
return Some(ConstValue::ZeroSized);
}
// Do not synthetize too large constants. Codegen will just memcpy them, which we'd like to avoid.
// Do not synthetize too large constants. Codegen will just memcpy them, which we'd like to
// avoid.
if !matches!(op.layout.abi, Abi::Scalar(..) | Abi::ScalarPair(..)) {
return None;
}
@ -1491,7 +1490,7 @@ impl<'tcx> VnState<'_, 'tcx> {
let other = self.rev_locals.get(index)?;
other
.iter()
.find(|&&other| self.ssa.assignment_dominates(self.dominators, other, loc))
.find(|&&other| self.ssa.assignment_dominates(&self.dominators, other, loc))
.copied()
}
}

View file

@ -568,7 +568,8 @@ impl<'tcx> Inliner<'tcx> {
// if the no-attribute function ends up with the same instruction set anyway.
return Err("Cannot move inline-asm across instruction sets");
} else if let TerminatorKind::TailCall { .. } = term.kind {
// FIXME(explicit_tail_calls): figure out how exactly functions containing tail calls can be inlined (and if they even should)
// FIXME(explicit_tail_calls): figure out how exactly functions containing tail
// calls can be inlined (and if they even should)
return Err("can't inline functions with tail calls");
} else {
work_list.extend(term.successors())

View file

@ -18,19 +18,13 @@ pub(super) enum InstSimplify {
AfterSimplifyCfg,
}
impl InstSimplify {
impl<'tcx> crate::MirPass<'tcx> for InstSimplify {
fn name(&self) -> &'static str {
match self {
InstSimplify::BeforeInline => "InstSimplify-before-inline",
InstSimplify::AfterSimplifyCfg => "InstSimplify-after-simplifycfg",
}
}
}
impl<'tcx> crate::MirPass<'tcx> for InstSimplify {
fn name(&self) -> &'static str {
self.name()
}
fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
sess.mir_opt_level() > 0

View file

@ -78,18 +78,16 @@ impl<'tcx> crate::MirPass<'tcx> for JumpThreading {
}
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 arena = &DroplessArena::default();
let mut finder = TOFinder {
tcx,
param_env,
ecx: InterpCx::new(tcx, DUMMY_SP, param_env, DummyMachine),
body,
arena: &arena,
map: &map,
loop_headers: &loop_headers,
arena,
map: Map::new(tcx, body, Some(MAX_PLACES)),
loop_headers: loop_headers(body),
opportunities: Vec::new(),
};
@ -105,7 +103,7 @@ impl<'tcx> crate::MirPass<'tcx> for JumpThreading {
// 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)));
assert!(to.chain.iter().all(|&block| !finder.loop_headers.contains(block)));
}
OpportunitySet::new(body, opportunities).apply(body);
}
@ -124,8 +122,8 @@ struct TOFinder<'tcx, 'a> {
param_env: ty::ParamEnv<'tcx>,
ecx: InterpCx<'tcx, DummyMachine>,
body: &'a Body<'tcx>,
map: &'a Map<'tcx>,
loop_headers: &'a BitSet<BasicBlock>,
map: Map<'tcx>,
loop_headers: BitSet<BasicBlock>,
/// We use an arena to avoid cloning the slices when cloning `state`.
arena: &'a DroplessArena,
opportunities: Vec<ThreadingOpportunity>,
@ -223,7 +221,7 @@ impl<'tcx, 'a> TOFinder<'tcx, 'a> {
}))
};
let conds = ConditionSet(conds);
state.insert_value_idx(discr, conds, self.map);
state.insert_value_idx(discr, conds, &self.map);
self.find_opportunity(bb, state, cost, 0);
}
@ -264,7 +262,7 @@ impl<'tcx, 'a> TOFinder<'tcx, 'a> {
// _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::BOTTOM);
state.flood_with_tail_elem(lhs.as_ref(), tail, &self.map, ConditionSet::BOTTOM);
}
}
@ -370,7 +368,7 @@ impl<'tcx, 'a> TOFinder<'tcx, 'a> {
self.opportunities.push(ThreadingOpportunity { chain: vec![bb], target: c.target })
};
if let Some(conditions) = state.try_get_idx(lhs, self.map)
if let Some(conditions) = state.try_get_idx(lhs, &self.map)
&& let Immediate::Scalar(Scalar::Int(int)) = *rhs
{
conditions.iter_matches(int).for_each(register_opportunity);
@ -406,7 +404,7 @@ impl<'tcx, 'a> TOFinder<'tcx, 'a> {
}
},
&mut |place, op| {
if let Some(conditions) = state.try_get_idx(place, self.map)
if let Some(conditions) = state.try_get_idx(place, &self.map)
&& let Ok(imm) = self.ecx.read_immediate_raw(op)
&& let Some(imm) = imm.right()
&& let Immediate::Scalar(Scalar::Int(int)) = *imm
@ -441,7 +439,7 @@ impl<'tcx, 'a> TOFinder<'tcx, 'a> {
// Transfer the conditions on the copied rhs.
Operand::Move(rhs) | Operand::Copy(rhs) => {
let Some(rhs) = self.map.find(rhs.as_ref()) else { return };
state.insert_place_idx(rhs, lhs, self.map);
state.insert_place_idx(rhs, lhs, &self.map);
}
}
}
@ -461,7 +459,7 @@ impl<'tcx, 'a> TOFinder<'tcx, 'a> {
Rvalue::CopyForDeref(rhs) => self.process_operand(bb, lhs, &Operand::Copy(*rhs), state),
Rvalue::Discriminant(rhs) => {
let Some(rhs) = self.map.find_discr(rhs.as_ref()) else { return };
state.insert_place_idx(rhs, lhs, self.map);
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) => {
@ -492,10 +490,10 @@ impl<'tcx, 'a> TOFinder<'tcx, 'a> {
}
// Transfer the conditions on the copy rhs, after inversing polarity.
Rvalue::UnaryOp(UnOp::Not, Operand::Move(place) | Operand::Copy(place)) => {
let Some(conditions) = state.try_get_idx(lhs, self.map) else { return };
let Some(conditions) = state.try_get_idx(lhs, &self.map) else { return };
let Some(place) = self.map.find(place.as_ref()) else { return };
let conds = conditions.map(self.arena, Condition::inv);
state.insert_value_idx(place, conds, self.map);
state.insert_value_idx(place, conds, &self.map);
}
// We expect `lhs ?= A`. We found `lhs = Eq(rhs, B)`.
// Create a condition on `rhs ?= B`.
@ -504,7 +502,7 @@ impl<'tcx, 'a> TOFinder<'tcx, 'a> {
box (Operand::Move(place) | Operand::Copy(place), Operand::Constant(value))
| box (Operand::Constant(value), Operand::Move(place) | Operand::Copy(place)),
) => {
let Some(conditions) = state.try_get_idx(lhs, self.map) else { return };
let Some(conditions) = state.try_get_idx(lhs, &self.map) else { return };
let Some(place) = self.map.find(place.as_ref()) else { return };
let equals = match op {
BinOp::Eq => ScalarInt::TRUE,
@ -528,7 +526,7 @@ impl<'tcx, 'a> TOFinder<'tcx, 'a> {
polarity: if c.matches(equals) { Polarity::Eq } else { Polarity::Ne },
..c
});
state.insert_value_idx(place, conds, self.map);
state.insert_value_idx(place, conds, &self.map);
}
_ => {}
@ -583,7 +581,7 @@ impl<'tcx, 'a> TOFinder<'tcx, 'a> {
StatementKind::Intrinsic(box NonDivergingIntrinsic::Assume(
Operand::Copy(place) | Operand::Move(place),
)) => {
let Some(conditions) = state.try_get(place.as_ref(), self.map) else { return };
let Some(conditions) = state.try_get(place.as_ref(), &self.map) else { return };
conditions.iter_matches(ScalarInt::TRUE).for_each(register_opportunity);
}
StatementKind::Assign(box (lhs_place, rhs)) => {
@ -631,7 +629,7 @@ impl<'tcx, 'a> TOFinder<'tcx, 'a> {
// We can recurse through this terminator.
let mut state = state();
if let Some(place_to_flood) = place_to_flood {
state.flood_with(place_to_flood.as_ref(), self.map, ConditionSet::BOTTOM);
state.flood_with(place_to_flood.as_ref(), &self.map, ConditionSet::BOTTOM);
}
self.find_opportunity(bb, state, cost.clone(), depth + 1);
}
@ -650,7 +648,7 @@ impl<'tcx, 'a> TOFinder<'tcx, 'a> {
let Some(discr) = discr.place() else { return };
let discr_ty = discr.ty(self.body, self.tcx).ty;
let Ok(discr_layout) = self.ecx.layout_of(discr_ty) else { return };
let Some(conditions) = state.try_get(discr.as_ref(), self.map) else { return };
let Some(conditions) = state.try_get(discr.as_ref(), &self.map) else { return };
if let Some((value, _)) = targets.iter().find(|&(_, target)| target == target_bb) {
let Some(value) = ScalarInt::try_from_uint(value, discr_layout.size) else { return };

View file

@ -1,8 +1,6 @@
//! A lint that checks for known panics like
//! overflows, division by zero,
//! out-of-bound access etc.
//! Uses const propagation to determine the
//! values of operands during checks.
//! A lint that checks for known panics like overflows, division by zero,
//! out-of-bound access etc. Uses const propagation to determine the values of
//! operands during checks.
use std::fmt::Debug;
@ -562,7 +560,8 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
let val = self.use_ecx(|this| this.ecx.binary_op(bin_op, &left, &right))?;
if matches!(val.layout.abi, Abi::ScalarPair(..)) {
// FIXME `Value` should properly support pairs in `Immediate`... but currently it does not.
// FIXME `Value` should properly support pairs in `Immediate`... but currently
// it does not.
let (val, overflow) = val.to_pair(&self.ecx);
Value::Aggregate {
variant: VariantIdx::ZERO,

View file

@ -16,8 +16,7 @@ use rustc_target::abi::{HasDataLayout, Size, TagEncoding, Variants};
/// Large([u32; 1024]),
/// }
/// ```
/// Instead of emitting moves of the large variant,
/// Perform a memcpy instead.
/// Instead of emitting moves of the large variant, perform a memcpy instead.
/// Based off of [this HackMD](https://hackmd.io/@ft4bxUsFT5CEUBmRKYHr7w/rJM8BBPzD).
///
/// In summary, what this does is at runtime determine which enum variant is active,
@ -34,10 +33,173 @@ impl<'tcx> crate::MirPass<'tcx> for EnumSizeOpt {
// https://github.com/rust-lang/rust/pull/85158#issuecomment-1101836457
sess.opts.unstable_opts.unsound_mir_opts || sess.mir_opt_level() >= 3
}
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
// NOTE: This pass may produce different MIR based on the alignment of the target
// platform, but it will still be valid.
self.optim(tcx, body);
let mut alloc_cache = FxHashMap::default();
let body_did = body.source.def_id();
let param_env = tcx.param_env_reveal_all_normalized(body_did);
let blocks = body.basic_blocks.as_mut();
let local_decls = &mut body.local_decls;
for bb in blocks {
bb.expand_statements(|st| {
let StatementKind::Assign(box (
lhs,
Rvalue::Use(Operand::Copy(rhs) | Operand::Move(rhs)),
)) = &st.kind
else {
return None;
};
let ty = lhs.ty(local_decls, tcx).ty;
let (adt_def, num_variants, alloc_id) =
self.candidate(tcx, param_env, ty, &mut alloc_cache)?;
let source_info = st.source_info;
let span = source_info.span;
let tmp_ty = Ty::new_array(tcx, tcx.types.usize, num_variants as u64);
let size_array_local = local_decls.push(LocalDecl::new(tmp_ty, span));
let store_live =
Statement { source_info, kind: StatementKind::StorageLive(size_array_local) };
let place = Place::from(size_array_local);
let constant_vals = ConstOperand {
span,
user_ty: None,
const_: Const::Val(
ConstValue::Indirect { alloc_id, offset: Size::ZERO },
tmp_ty,
),
};
let rval = Rvalue::Use(Operand::Constant(Box::new(constant_vals)));
let const_assign =
Statement { source_info, kind: StatementKind::Assign(Box::new((place, rval))) };
let discr_place = Place::from(
local_decls.push(LocalDecl::new(adt_def.repr().discr_type().to_ty(tcx), span)),
);
let store_discr = Statement {
source_info,
kind: StatementKind::Assign(Box::new((
discr_place,
Rvalue::Discriminant(*rhs),
))),
};
let discr_cast_place =
Place::from(local_decls.push(LocalDecl::new(tcx.types.usize, span)));
let cast_discr = Statement {
source_info,
kind: StatementKind::Assign(Box::new((
discr_cast_place,
Rvalue::Cast(
CastKind::IntToInt,
Operand::Copy(discr_place),
tcx.types.usize,
),
))),
};
let size_place =
Place::from(local_decls.push(LocalDecl::new(tcx.types.usize, span)));
let store_size = Statement {
source_info,
kind: StatementKind::Assign(Box::new((
size_place,
Rvalue::Use(Operand::Copy(Place {
local: size_array_local,
projection: tcx
.mk_place_elems(&[PlaceElem::Index(discr_cast_place.local)]),
})),
))),
};
let dst =
Place::from(local_decls.push(LocalDecl::new(Ty::new_mut_ptr(tcx, ty), span)));
let dst_ptr = Statement {
source_info,
kind: StatementKind::Assign(Box::new((
dst,
Rvalue::RawPtr(Mutability::Mut, *lhs),
))),
};
let dst_cast_ty = Ty::new_mut_ptr(tcx, tcx.types.u8);
let dst_cast_place =
Place::from(local_decls.push(LocalDecl::new(dst_cast_ty, span)));
let dst_cast = Statement {
source_info,
kind: StatementKind::Assign(Box::new((
dst_cast_place,
Rvalue::Cast(CastKind::PtrToPtr, Operand::Copy(dst), dst_cast_ty),
))),
};
let src =
Place::from(local_decls.push(LocalDecl::new(Ty::new_imm_ptr(tcx, ty), span)));
let src_ptr = Statement {
source_info,
kind: StatementKind::Assign(Box::new((
src,
Rvalue::RawPtr(Mutability::Not, *rhs),
))),
};
let src_cast_ty = Ty::new_imm_ptr(tcx, tcx.types.u8);
let src_cast_place =
Place::from(local_decls.push(LocalDecl::new(src_cast_ty, span)));
let src_cast = Statement {
source_info,
kind: StatementKind::Assign(Box::new((
src_cast_place,
Rvalue::Cast(CastKind::PtrToPtr, Operand::Copy(src), src_cast_ty),
))),
};
let deinit_old =
Statement { source_info, kind: StatementKind::Deinit(Box::new(dst)) };
let copy_bytes = Statement {
source_info,
kind: StatementKind::Intrinsic(Box::new(
NonDivergingIntrinsic::CopyNonOverlapping(CopyNonOverlapping {
src: Operand::Copy(src_cast_place),
dst: Operand::Copy(dst_cast_place),
count: Operand::Copy(size_place),
}),
)),
};
let store_dead =
Statement { source_info, kind: StatementKind::StorageDead(size_array_local) };
let iter = [
store_live,
const_assign,
store_discr,
cast_discr,
store_size,
dst_ptr,
dst_cast,
src_ptr,
src_cast,
deinit_old,
copy_bytes,
store_dead,
]
.into_iter();
st.make_nop();
Some(iter)
});
}
}
}
@ -82,6 +244,8 @@ impl EnumSizeOpt {
let ptr_sized_int = data_layout.ptr_sized_integer();
let target_bytes = ptr_sized_int.size().bytes() as usize;
let mut data = vec![0; target_bytes * num_discrs];
// We use a macro because `$bytes` can be u32 or u64.
macro_rules! encode_store {
($curr_idx: expr, $endian: expr, $bytes: expr) => {
let bytes = match $endian {
@ -116,184 +280,4 @@ impl EnumSizeOpt {
let alloc = tcx.reserve_and_set_memory_alloc(tcx.mk_const_alloc(alloc));
Some((*adt_def, num_discrs, *alloc_cache.entry(ty).or_insert(alloc)))
}
fn optim<'tcx>(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
let mut alloc_cache = FxHashMap::default();
let body_did = body.source.def_id();
let param_env = tcx.param_env_reveal_all_normalized(body_did);
let blocks = body.basic_blocks.as_mut();
let local_decls = &mut body.local_decls;
for bb in blocks {
bb.expand_statements(|st| {
if let StatementKind::Assign(box (
lhs,
Rvalue::Use(Operand::Copy(rhs) | Operand::Move(rhs)),
)) = &st.kind
{
let ty = lhs.ty(local_decls, tcx).ty;
let source_info = st.source_info;
let span = source_info.span;
let (adt_def, num_variants, alloc_id) =
self.candidate(tcx, param_env, ty, &mut alloc_cache)?;
let tmp_ty = Ty::new_array(tcx, tcx.types.usize, num_variants as u64);
let size_array_local = local_decls.push(LocalDecl::new(tmp_ty, span));
let store_live = Statement {
source_info,
kind: StatementKind::StorageLive(size_array_local),
};
let place = Place::from(size_array_local);
let constant_vals = ConstOperand {
span,
user_ty: None,
const_: Const::Val(
ConstValue::Indirect { alloc_id, offset: Size::ZERO },
tmp_ty,
),
};
let rval = Rvalue::Use(Operand::Constant(Box::new(constant_vals)));
let const_assign = Statement {
source_info,
kind: StatementKind::Assign(Box::new((place, rval))),
};
let discr_place = Place::from(
local_decls
.push(LocalDecl::new(adt_def.repr().discr_type().to_ty(tcx), span)),
);
let store_discr = Statement {
source_info,
kind: StatementKind::Assign(Box::new((
discr_place,
Rvalue::Discriminant(*rhs),
))),
};
let discr_cast_place =
Place::from(local_decls.push(LocalDecl::new(tcx.types.usize, span)));
let cast_discr = Statement {
source_info,
kind: StatementKind::Assign(Box::new((
discr_cast_place,
Rvalue::Cast(
CastKind::IntToInt,
Operand::Copy(discr_place),
tcx.types.usize,
),
))),
};
let size_place =
Place::from(local_decls.push(LocalDecl::new(tcx.types.usize, span)));
let store_size = Statement {
source_info,
kind: StatementKind::Assign(Box::new((
size_place,
Rvalue::Use(Operand::Copy(Place {
local: size_array_local,
projection: tcx
.mk_place_elems(&[PlaceElem::Index(discr_cast_place.local)]),
})),
))),
};
let dst = Place::from(
local_decls.push(LocalDecl::new(Ty::new_mut_ptr(tcx, ty), span)),
);
let dst_ptr = Statement {
source_info,
kind: StatementKind::Assign(Box::new((
dst,
Rvalue::RawPtr(Mutability::Mut, *lhs),
))),
};
let dst_cast_ty = Ty::new_mut_ptr(tcx, tcx.types.u8);
let dst_cast_place =
Place::from(local_decls.push(LocalDecl::new(dst_cast_ty, span)));
let dst_cast = Statement {
source_info,
kind: StatementKind::Assign(Box::new((
dst_cast_place,
Rvalue::Cast(CastKind::PtrToPtr, Operand::Copy(dst), dst_cast_ty),
))),
};
let src = Place::from(
local_decls.push(LocalDecl::new(Ty::new_imm_ptr(tcx, ty), span)),
);
let src_ptr = Statement {
source_info,
kind: StatementKind::Assign(Box::new((
src,
Rvalue::RawPtr(Mutability::Not, *rhs),
))),
};
let src_cast_ty = Ty::new_imm_ptr(tcx, tcx.types.u8);
let src_cast_place =
Place::from(local_decls.push(LocalDecl::new(src_cast_ty, span)));
let src_cast = Statement {
source_info,
kind: StatementKind::Assign(Box::new((
src_cast_place,
Rvalue::Cast(CastKind::PtrToPtr, Operand::Copy(src), src_cast_ty),
))),
};
let deinit_old =
Statement { source_info, kind: StatementKind::Deinit(Box::new(dst)) };
let copy_bytes = Statement {
source_info,
kind: StatementKind::Intrinsic(Box::new(
NonDivergingIntrinsic::CopyNonOverlapping(CopyNonOverlapping {
src: Operand::Copy(src_cast_place),
dst: Operand::Copy(dst_cast_place),
count: Operand::Copy(size_place),
}),
)),
};
let store_dead = Statement {
source_info,
kind: StatementKind::StorageDead(size_array_local),
};
let iter = [
store_live,
const_assign,
store_discr,
cast_discr,
store_size,
dst_ptr,
dst_cast,
src_ptr,
src_cast,
deinit_old,
copy_bytes,
store_dead,
]
.into_iter();
st.make_nop();
Some(iter)
} else {
None
}
});
}
}
}

View file

@ -87,6 +87,7 @@ mod match_branches;
mod mentioned_items;
mod multiple_return_terminators;
mod nrvo;
mod post_drop_elaboration;
mod prettify;
mod promote_consts;
mod ref_prop;
@ -168,8 +169,9 @@ fn remap_mir_for_const_eval_select<'tcx>(
let (method, place): (fn(Place<'tcx>) -> Operand<'tcx>, Place<'tcx>) =
match tupled_args.node {
Operand::Constant(_) => {
// there is no good way of extracting a tuple arg from a constant (const generic stuff)
// so we just create a temporary and deconstruct that.
// There is no good way of extracting a tuple arg from a constant
// (const generic stuff) so we just create a temporary and deconstruct
// that.
let local = body.local_decls.push(LocalDecl::new(ty, fn_span));
bb.statements.push(Statement {
source_info: SourceInfo::outermost(fn_span),
@ -222,14 +224,14 @@ fn is_mir_available(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
/// MIR associated with them.
fn mir_keys(tcx: TyCtxt<'_>, (): ()) -> FxIndexSet<LocalDefId> {
// All body-owners have MIR associated with them.
let mut set: FxIndexSet<_> = tcx.hir().body_owners().collect();
let set: FxIndexSet<_> = tcx.hir().body_owners().collect();
// Additionally, tuple struct/variant constructors have MIR, but
// they don't have a BodyId, so we need to build them separately.
struct GatherCtors<'a> {
set: &'a mut FxIndexSet<LocalDefId>,
struct GatherCtors {
set: FxIndexSet<LocalDefId>,
}
impl<'tcx> Visitor<'tcx> for GatherCtors<'_> {
impl<'tcx> Visitor<'tcx> for GatherCtors {
fn visit_variant_data(&mut self, v: &'tcx hir::VariantData<'tcx>) {
if let hir::VariantData::Tuple(_, _, def_id) = *v {
self.set.insert(def_id);
@ -237,9 +239,11 @@ fn mir_keys(tcx: TyCtxt<'_>, (): ()) -> FxIndexSet<LocalDefId> {
intravisit::walk_struct_def(self, v)
}
}
tcx.hir().visit_all_item_likes_in_crate(&mut GatherCtors { set: &mut set });
set
let mut gather_ctors = GatherCtors { set };
tcx.hir().visit_all_item_likes_in_crate(&mut gather_ctors);
gather_ctors.set
}
fn mir_const_qualif(tcx: TyCtxt<'_>, def: LocalDefId) -> ConstQualifs {
@ -477,10 +481,13 @@ pub fn run_analysis_to_runtime_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'
pm::run_passes(
tcx,
body,
&[&remove_uninit_drops::RemoveUninitDrops, &simplify::SimplifyCfg::RemoveFalseEdges],
&[
&remove_uninit_drops::RemoveUninitDrops,
&simplify::SimplifyCfg::RemoveFalseEdges,
&Lint(post_drop_elaboration::CheckLiveDrops),
],
None,
);
check_consts::post_drop_elaboration::check_live_drops(tcx, body); // FIXME: make this a MIR lint
}
debug!("runtime_mir_lowering({:?})", did);
@ -509,10 +516,12 @@ fn run_analysis_cleanup_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
/// Returns the sequence of passes that lowers analysis to runtime MIR.
fn run_runtime_lowering_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
let passes: &[&dyn MirPass<'tcx>] = &[
// These next passes must be executed together
// These next passes must be executed together.
&add_call_guards::CriticalCallEdges,
&reveal_all::RevealAll, // has to be done before drop elaboration, since we need to drop opaque types, too.
&add_subtyping_projections::Subtyper, // calling this after reveal_all ensures that we don't deal with opaque types
// Must be done before drop elaboration because we need to drop opaque types, too.
&reveal_all::RevealAll,
// Calling this after reveal_all ensures that we don't deal with opaque types.
&add_subtyping_projections::Subtyper,
&elaborate_drops::ElaborateDrops,
// This will remove extraneous landing pads which are no longer
// necessary as well as forcing any call in a non-unwinding
@ -521,8 +530,8 @@ fn run_runtime_lowering_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
// AddMovesForPackedDrops needs to run after drop
// elaboration.
&add_moves_for_packed_drops::AddMovesForPackedDrops,
// `AddRetag` needs to run after `ElaborateDrops` but before `ElaborateBoxDerefs`. Otherwise it should run fairly late,
// but before optimizations begin.
// `AddRetag` needs to run after `ElaborateDrops` but before `ElaborateBoxDerefs`.
// Otherwise it should run fairly late, but before optimizations begin.
&add_retag::AddRetag,
&elaborate_box_derefs::ElaborateBoxDerefs,
&coroutine::StateTransform,
@ -563,13 +572,15 @@ fn run_optimization_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
// Before inlining: trim down MIR with passes to reduce inlining work.
// Has to be done before inlining, otherwise actual call will be almost always inlined.
// Also simple, so can just do first
// Also simple, so can just do first.
&lower_slice_len::LowerSliceLenCalls,
// Perform instsimplify before inline to eliminate some trivial calls (like clone shims).
// Perform instsimplify before inline to eliminate some trivial calls (like clone
// shims).
&instsimplify::InstSimplify::BeforeInline,
// Perform inlining, which may add a lot of code.
&inline::Inline,
// Code from other crates may have storage markers, so this needs to happen after inlining.
// Code from other crates may have storage markers, so this needs to happen after
// inlining.
&remove_storage_markers::RemoveStorageMarkers,
// Inlining and instantiation may introduce ZST and useless drops.
&remove_zsts::RemoveZsts,
@ -586,7 +597,8 @@ fn run_optimization_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
&match_branches::MatchBranchSimplification,
// inst combine is after MatchBranchSimplification to clean up Ne(_1, false)
&multiple_return_terminators::MultipleReturnTerminators,
// After simplifycfg, it allows us to discover new opportunities for peephole optimizations.
// After simplifycfg, it allows us to discover new opportunities for peephole
// optimizations.
&instsimplify::InstSimplify::AfterSimplifyCfg,
&simplify::SimplifyLocals::BeforeConstProp,
&dead_store_elimination::DeadStoreElimination::Initial,

View file

@ -13,22 +13,18 @@ impl<'tcx> crate::MirPass<'tcx> for LowerSliceLenCalls {
}
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
lower_slice_len_calls(tcx, body)
}
}
let language_items = tcx.lang_items();
let Some(slice_len_fn_item_def_id) = language_items.slice_len_fn() else {
// there is no lang item to compare to :)
return;
};
fn lower_slice_len_calls<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
let language_items = tcx.lang_items();
let Some(slice_len_fn_item_def_id) = language_items.slice_len_fn() else {
// there is no lang item to compare to :)
return;
};
// The one successor remains unchanged, so no need to invalidate
let basic_blocks = body.basic_blocks.as_mut_preserves_cfg();
for block in basic_blocks {
// lower `<[_]>::len` calls
lower_slice_len_call(block, slice_len_fn_item_def_id);
// The one successor remains unchanged, so no need to invalidate
let basic_blocks = body.basic_blocks.as_mut_preserves_cfg();
for block in basic_blocks {
// lower `<[_]>::len` calls
lower_slice_len_call(block, slice_len_fn_item_def_id);
}
}
}

View file

@ -57,8 +57,9 @@ impl<'tcx> crate::MirPass<'tcx> for MatchBranchSimplification {
}
trait SimplifyMatch<'tcx> {
/// Simplifies a match statement, returning true if the simplification succeeds, false otherwise.
/// Generic code is written here, and we generally don't need a custom implementation.
/// Simplifies a match statement, returning true if the simplification succeeds, false
/// otherwise. Generic code is written here, and we generally don't need a custom
/// implementation.
fn simplify(
&mut self,
tcx: TyCtxt<'tcx>,
@ -240,7 +241,8 @@ impl<'tcx> SimplifyMatch<'tcx> for SimplifyToIf {
// Same value in both blocks. Use statement as is.
patch.add_statement(parent_end, f.kind.clone());
} else {
// Different value between blocks. Make value conditional on switch condition.
// Different value between blocks. Make value conditional on switch
// condition.
let size = tcx.layout_of(param_env.and(discr_ty)).unwrap().size;
let const_cmp = Operand::const_from_scalar(
tcx,
@ -394,14 +396,16 @@ impl<'tcx> SimplifyMatch<'tcx> for SimplifyToExp {
return None;
}
// We first compare the two branches, and then the other branches need to fulfill the same conditions.
// We first compare the two branches, and then the other branches need to fulfill the same
// conditions.
let mut expected_transform_kinds = Vec::new();
for (f, s) in iter::zip(first_stmts, second_stmts) {
let compare_type = match (&f.kind, &s.kind) {
// If two statements are exactly the same, we can optimize.
(f_s, s_s) if f_s == s_s => ExpectedTransformKind::Same(f_s),
// If two statements are assignments with the match values to the same place, we can optimize.
// If two statements are assignments with the match values to the same place, we
// can optimize.
(
StatementKind::Assign(box (lhs_f, Rvalue::Use(Operand::Constant(f_c)))),
StatementKind::Assign(box (lhs_s, Rvalue::Use(Operand::Constant(s_c)))),

View file

@ -10,7 +10,7 @@ pub(super) struct MentionedItems;
struct MentionedItemsVisitor<'a, 'tcx> {
tcx: TyCtxt<'tcx>,
body: &'a mir::Body<'tcx>,
mentioned_items: &'a mut Vec<Spanned<MentionedItem<'tcx>>>,
mentioned_items: Vec<Spanned<MentionedItem<'tcx>>>,
}
impl<'tcx> crate::MirPass<'tcx> for MentionedItems {
@ -23,9 +23,9 @@ impl<'tcx> crate::MirPass<'tcx> for MentionedItems {
}
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut mir::Body<'tcx>) {
let mut mentioned_items = Vec::new();
MentionedItemsVisitor { tcx, body, mentioned_items: &mut mentioned_items }.visit_body(body);
body.set_mentioned_items(mentioned_items);
let mut visitor = MentionedItemsVisitor { tcx, body, mentioned_items: Vec::new() };
visitor.visit_body(body);
body.set_mentioned_items(visitor.mentioned_items);
}
}
@ -82,7 +82,9 @@ impl<'tcx> Visitor<'tcx> for MentionedItemsVisitor<'_, 'tcx> {
source_ty.builtin_deref(true).map(|t| t.kind()),
target_ty.builtin_deref(true).map(|t| t.kind()),
) {
(Some(ty::Array(..)), Some(ty::Str | ty::Slice(..))) => false, // &str/&[T] unsizing
// &str/&[T] unsizing
(Some(ty::Array(..)), Some(ty::Str | ty::Slice(..))) => false,
_ => true,
};
if may_involve_vtable {

View file

@ -0,0 +1,13 @@
use rustc_const_eval::check_consts;
use rustc_middle::mir::*;
use rustc_middle::ty::TyCtxt;
use crate::MirLint;
pub(super) struct CheckLiveDrops;
impl<'tcx> MirLint<'tcx> for CheckLiveDrops {
fn run_lint(&self, tcx: TyCtxt<'tcx>, body: &Body<'tcx>) {
check_consts::post_drop_elaboration::check_live_drops(tcx, body);
}
}

View file

@ -63,7 +63,7 @@ impl<'tcx> crate::MirPass<'tcx> for ReorderLocals {
finder.visit_basic_block_data(bb, bbd);
}
// track everything in case there are some locals that we never saw,
// Track everything in case there are some locals that we never saw,
// such as in non-block things like debug info or in non-uses.
for local in body.local_decls.indices() {
finder.track(local);
@ -87,7 +87,7 @@ impl<'tcx> crate::MirPass<'tcx> for ReorderLocals {
fn permute<I: rustc_index::Idx + Ord, T>(data: &mut IndexVec<I, T>, map: &IndexSlice<I, I>) {
// FIXME: It would be nice to have a less-awkward way to apply permutations,
// but I don't know one that exists. `sort_by_cached_key` has logic for it
// but I don't know one that exists. `sort_by_cached_key` has logic for it
// internally, but not in a way that we're allowed to use here.
let mut enumerated: Vec<_> = std::mem::take(data).into_iter_enumerated().collect();
enumerated.sort_by_key(|p| map[p.0]);

View file

@ -1,16 +1,14 @@
//! A pass that promotes borrows of constant rvalues.
//!
//! The rvalues considered constant are trees of temps,
//! each with exactly one initialization, and holding
//! a constant value with no interior mutability.
//! They are placed into a new MIR constant body in
//! `promoted` and the borrow rvalue is replaced with
//! a `Literal::Promoted` using the index into `promoted`
//! of that constant MIR.
//! The rvalues considered constant are trees of temps, each with exactly one
//! initialization, and holding a constant value with no interior mutability.
//! They are placed into a new MIR constant body in `promoted` and the borrow
//! rvalue is replaced with a `Literal::Promoted` using the index into
//! `promoted` of that constant MIR.
//!
//! This pass assumes that every use is dominated by an
//! initialization and can otherwise silence errors, if
//! move analysis runs after promotion on broken MIR.
//! This pass assumes that every use is dominated by an initialization and can
//! otherwise silence errors, if move analysis runs after promotion on broken
//! MIR.
use std::assert_matches::assert_matches;
use std::cell::Cell;
@ -38,6 +36,7 @@ use tracing::{debug, instrument};
/// newly created `Constant`.
#[derive(Default)]
pub(super) struct PromoteTemps<'tcx> {
// Must use `Cell` because `run_pass` takes `&self`, not `&mut self`.
pub promoted_fragments: Cell<IndexVec<Promoted, Body<'tcx>>>,
}
@ -386,7 +385,8 @@ impl<'tcx> Validator<'_, 'tcx> {
fn validate_ref(&mut self, kind: BorrowKind, place: &Place<'tcx>) -> Result<(), Unpromotable> {
match kind {
// Reject these borrow types just to be safe.
// FIXME(RalfJung): could we allow them? Should we? No point in it until we have a usecase.
// FIXME(RalfJung): could we allow them? Should we? No point in it until we have a
// usecase.
BorrowKind::Fake(_) | BorrowKind::Mut { kind: MutBorrowKind::ClosureCapture } => {
return Err(Unpromotable);
}
@ -468,7 +468,8 @@ impl<'tcx> Validator<'_, 'tcx> {
let lhs_ty = lhs.ty(self.body, self.tcx);
if let ty::RawPtr(_, _) | ty::FnPtr(..) = lhs_ty.kind() {
// Raw and fn pointer operations are not allowed inside consts and thus not promotable.
// Raw and fn pointer operations are not allowed inside consts and thus not
// promotable.
assert_matches!(
op,
BinOp::Eq
@ -498,7 +499,8 @@ impl<'tcx> Validator<'_, 'tcx> {
Some(x) if x != 0 => {} // okay
_ => return Err(Unpromotable), // value not known or 0 -- not okay
}
// Furthermore, for signed division, we also have to exclude `int::MIN / -1`.
// Furthermore, for signed division, we also have to exclude `int::MIN /
// -1`.
if lhs_ty.is_signed() {
match rhs_val.map(|x| x.to_int(sz)) {
Some(-1) | None => {
@ -512,8 +514,11 @@ impl<'tcx> Validator<'_, 'tcx> {
};
let lhs_min = sz.signed_int_min();
match lhs_val.map(|x| x.to_int(sz)) {
Some(x) if x != lhs_min => {} // okay
_ => return Err(Unpromotable), // value not known or int::MIN -- not okay
// okay
Some(x) if x != lhs_min => {}
// value not known or int::MIN -- not okay
_ => return Err(Unpromotable),
}
}
_ => {}
@ -815,8 +820,8 @@ impl<'a, 'tcx> Promoter<'a, 'tcx> {
TerminatorKind::Call {
mut func, mut args, call_source: desugar, fn_span, ..
} => {
// This promoted involves a function call, so it may fail to evaluate.
// Let's make sure it is added to `required_consts` so that failure cannot get lost.
// This promoted involves a function call, so it may fail to evaluate. Let's
// make sure it is added to `required_consts` so that failure cannot get lost.
self.add_to_required = true;
self.visit_operand(&mut func, loc);

View file

@ -253,11 +253,8 @@ fn compute_replacement<'tcx>(
debug!(?targets);
let mut finder = ReplacementFinder {
targets: &mut targets,
can_perform_opt,
allowed_replacements: FxHashSet::default(),
};
let mut finder =
ReplacementFinder { targets, can_perform_opt, allowed_replacements: FxHashSet::default() };
let reachable_blocks = traversal::reachable_as_bitset(body);
for (bb, bbdata) in body.basic_blocks.iter_enumerated() {
// Only visit reachable blocks as we rely on dataflow.
@ -269,19 +266,19 @@ fn compute_replacement<'tcx>(
let allowed_replacements = finder.allowed_replacements;
return Replacer {
tcx,
targets,
targets: finder.targets,
storage_to_remove,
allowed_replacements,
any_replacement: false,
};
struct ReplacementFinder<'a, 'tcx, F> {
targets: &'a mut IndexVec<Local, Value<'tcx>>,
struct ReplacementFinder<'tcx, F> {
targets: IndexVec<Local, Value<'tcx>>,
can_perform_opt: F,
allowed_replacements: FxHashSet<(Local, Location)>,
}
impl<'tcx, F> Visitor<'tcx> for ReplacementFinder<'_, 'tcx, F>
impl<'tcx, F> Visitor<'tcx> for ReplacementFinder<'tcx, F>
where
F: FnMut(Place<'tcx>, Location) -> bool,
{

View file

@ -18,7 +18,61 @@ impl<'tcx> crate::MirPass<'tcx> for RemoveNoopLandingPads {
fn run_pass(&self, _tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
let def_id = body.source.def_id();
debug!(?def_id);
self.remove_nop_landing_pads(body)
// Skip the pass if there are no blocks with a resume terminator.
let has_resume = body
.basic_blocks
.iter_enumerated()
.any(|(_bb, block)| matches!(block.terminator().kind, TerminatorKind::UnwindResume));
if !has_resume {
debug!("remove_noop_landing_pads: no resume block in MIR");
return;
}
// make sure there's a resume block without any statements
let resume_block = {
let mut patch = MirPatch::new(body);
let resume_block = patch.resume_block();
patch.apply(body);
resume_block
};
debug!("remove_noop_landing_pads: resume block is {:?}", resume_block);
let mut jumps_folded = 0;
let mut landing_pads_removed = 0;
let mut nop_landing_pads = BitSet::new_empty(body.basic_blocks.len());
// This is a post-order traversal, so that if A post-dominates B
// then A will be visited before B.
let postorder: Vec<_> = traversal::postorder(body).map(|(bb, _)| bb).collect();
for bb in postorder {
debug!(" processing {:?}", bb);
if let Some(unwind) = body[bb].terminator_mut().unwind_mut() {
if let UnwindAction::Cleanup(unwind_bb) = *unwind {
if nop_landing_pads.contains(unwind_bb) {
debug!(" removing noop landing pad");
landing_pads_removed += 1;
*unwind = UnwindAction::Continue;
}
}
}
for target in body[bb].terminator_mut().successors_mut() {
if *target != resume_block && nop_landing_pads.contains(*target) {
debug!(" folding noop jump to {:?} to resume block", target);
*target = resume_block;
jumps_folded += 1;
}
}
let is_nop_landing_pad = self.is_nop_landing_pad(bb, body, &nop_landing_pads);
if is_nop_landing_pad {
nop_landing_pads.insert(bb);
}
debug!(" is_nop_landing_pad({:?}) = {}", bb, is_nop_landing_pad);
}
debug!("removed {:?} jumps and {:?} landing pads", jumps_folded, landing_pads_removed);
}
}
@ -82,61 +136,4 @@ impl RemoveNoopLandingPads {
| TerminatorKind::InlineAsm { .. } => false,
}
}
fn remove_nop_landing_pads(&self, body: &mut Body<'_>) {
// Skip the pass if there are no blocks with a resume terminator.
let has_resume = body
.basic_blocks
.iter_enumerated()
.any(|(_bb, block)| matches!(block.terminator().kind, TerminatorKind::UnwindResume));
if !has_resume {
debug!("remove_noop_landing_pads: no resume block in MIR");
return;
}
// make sure there's a resume block without any statements
let resume_block = {
let mut patch = MirPatch::new(body);
let resume_block = patch.resume_block();
patch.apply(body);
resume_block
};
debug!("remove_noop_landing_pads: resume block is {:?}", resume_block);
let mut jumps_folded = 0;
let mut landing_pads_removed = 0;
let mut nop_landing_pads = BitSet::new_empty(body.basic_blocks.len());
// This is a post-order traversal, so that if A post-dominates B
// then A will be visited before B.
let postorder: Vec<_> = traversal::postorder(body).map(|(bb, _)| bb).collect();
for bb in postorder {
debug!(" processing {:?}", bb);
if let Some(unwind) = body[bb].terminator_mut().unwind_mut() {
if let UnwindAction::Cleanup(unwind_bb) = *unwind {
if nop_landing_pads.contains(unwind_bb) {
debug!(" removing noop landing pad");
landing_pads_removed += 1;
*unwind = UnwindAction::Continue;
}
}
}
for target in body[bb].terminator_mut().successors_mut() {
if *target != resume_block && nop_landing_pads.contains(*target) {
debug!(" folding noop jump to {:?} to resume block", target);
*target = resume_block;
jumps_folded += 1;
}
}
let is_nop_landing_pad = self.is_nop_landing_pad(bb, body, &nop_landing_pads);
if is_nop_landing_pad {
nop_landing_pads.insert(bb);
}
debug!(" is_nop_landing_pad({:?}) = {}", bb, is_nop_landing_pad);
}
debug!("removed {:?} jumps and {:?} landing pads", jumps_folded, landing_pads_removed);
}
}

View file

@ -106,8 +106,9 @@ fn is_needs_drop_and_init<'tcx>(
// If its projection *is* present in `MoveData`, then the field may have been moved
// from separate from its parent. Recurse.
adt.variants().iter_enumerated().any(|(vid, variant)| {
// Enums have multiple variants, which are discriminated with a `Downcast` projection.
// Structs have a single variant, and don't use a `Downcast` projection.
// Enums have multiple variants, which are discriminated with a `Downcast`
// projection. Structs have a single variant, and don't use a `Downcast`
// projection.
let mpi = if adt.is_enum() {
let downcast =
move_path_children_matching(move_data, mpi, |x| x.is_downcast_to(vid));

View file

@ -1,26 +1,21 @@
use rustc_middle::mir::visit::Visitor;
use rustc_middle::mir::{traversal, Body, ConstOperand, Location};
pub(super) struct RequiredConstsVisitor<'a, 'tcx> {
required_consts: &'a mut Vec<ConstOperand<'tcx>>,
pub(super) struct RequiredConstsVisitor<'tcx> {
required_consts: Vec<ConstOperand<'tcx>>,
}
impl<'a, 'tcx> RequiredConstsVisitor<'a, 'tcx> {
fn new(required_consts: &'a mut Vec<ConstOperand<'tcx>>) -> Self {
RequiredConstsVisitor { required_consts }
}
impl<'tcx> RequiredConstsVisitor<'tcx> {
pub(super) fn compute_required_consts(body: &mut Body<'tcx>) {
let mut required_consts = Vec::new();
let mut required_consts_visitor = RequiredConstsVisitor::new(&mut required_consts);
let mut visitor = RequiredConstsVisitor { required_consts: Vec::new() };
for (bb, bb_data) in traversal::reverse_postorder(&body) {
required_consts_visitor.visit_basic_block_data(bb, bb_data);
visitor.visit_basic_block_data(bb, bb_data);
}
body.set_required_consts(required_consts);
body.set_required_consts(visitor.required_consts);
}
}
impl<'tcx> Visitor<'tcx> for RequiredConstsVisitor<'_, 'tcx> {
impl<'tcx> Visitor<'tcx> for RequiredConstsVisitor<'tcx> {
fn visit_const_operand(&mut self, constant: &ConstOperand<'tcx>, _: Location) {
if constant.const_.is_required_const() {
self.required_consts.push(*constant);

View file

@ -35,9 +35,9 @@ impl<'tcx> MutVisitor<'tcx> for RevealAllVisitor<'tcx> {
if place.projection.iter().all(|elem| !matches!(elem, ProjectionElem::OpaqueCast(_))) {
return;
}
// `OpaqueCast` projections are only needed if there are opaque types on which projections are performed.
// After the `RevealAll` pass, all opaque types are replaced with their hidden types, so we don't need these
// projections anymore.
// `OpaqueCast` projections are only needed if there are opaque types on which projections
// are performed. After the `RevealAll` pass, all opaque types are replaced with their
// hidden types, so we don't need these projections anymore.
place.projection = self.tcx.mk_place_elems(
&place
.projection

View file

@ -404,8 +404,7 @@ fn build_thread_local_shim<'tcx>(
let span = tcx.def_span(def_id);
let source_info = SourceInfo::outermost(span);
let mut blocks = IndexVec::with_capacity(1);
blocks.push(BasicBlockData {
let blocks = IndexVec::from_raw(vec![BasicBlockData {
statements: vec![Statement {
source_info,
kind: StatementKind::Assign(Box::new((
@ -415,7 +414,7 @@ fn build_thread_local_shim<'tcx>(
}],
terminator: Some(Terminator { source_info, kind: TerminatorKind::Return }),
is_cleanup: false,
});
}]);
new_body(
MirSource::from_instance(instance),
@ -1003,7 +1002,8 @@ fn build_fn_ptr_addr_shim<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId, self_ty: Ty<'t
let locals = local_decls_for_sig(&sig, span);
let source_info = SourceInfo::outermost(span);
// FIXME: use `expose_provenance` once we figure out whether function pointers have meaningful provenance.
// FIXME: use `expose_provenance` once we figure out whether function pointers have meaningful
// provenance.
let rvalue = Rvalue::Cast(
CastKind::FnPtrToPtr,
Operand::Move(Place::from(Local::new(1))),

View file

@ -381,7 +381,29 @@ impl<'tcx> crate::MirPass<'tcx> for SimplifyLocals {
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
trace!("running SimplifyLocals on {:?}", body.source);
simplify_locals(body, tcx);
// First, we're going to get a count of *actual* uses for every `Local`.
let mut used_locals = UsedLocals::new(body);
// Next, we're going to remove any `Local` with zero actual uses. When we remove those
// `Locals`, we're also going to subtract any uses of other `Locals` from the `used_locals`
// count. For example, if we removed `_2 = discriminant(_1)`, then we'll subtract one from
// `use_counts[_1]`. That in turn might make `_1` unused, so we loop until we hit a
// fixedpoint where there are no more unused locals.
remove_unused_definitions_helper(&mut used_locals, body);
// Finally, we'll actually do the work of shrinking `body.local_decls` and remapping the
// `Local`s.
let map = make_local_map(&mut body.local_decls, &used_locals);
// Only bother running the `LocalUpdater` if we actually found locals to remove.
if map.iter().any(Option::is_none) {
// Update references to all vars and tmps now
let mut updater = LocalUpdater { map, tcx };
updater.visit_body_preserves_cfg(body);
body.local_decls.shrink_to_fit();
}
}
}
@ -397,30 +419,6 @@ pub(super) fn remove_unused_definitions<'tcx>(body: &mut Body<'tcx>) {
remove_unused_definitions_helper(&mut used_locals, body);
}
fn simplify_locals<'tcx>(body: &mut Body<'tcx>, tcx: TyCtxt<'tcx>) {
// First, we're going to get a count of *actual* uses for every `Local`.
let mut used_locals = UsedLocals::new(body);
// Next, we're going to remove any `Local` with zero actual uses. When we remove those
// `Locals`, we're also going to subtract any uses of other `Locals` from the `used_locals`
// count. For example, if we removed `_2 = discriminant(_1)`, then we'll subtract one from
// `use_counts[_1]`. That in turn might make `_1` unused, so we loop until we hit a
// fixedpoint where there are no more unused locals.
remove_unused_definitions_helper(&mut used_locals, body);
// Finally, we'll actually do the work of shrinking `body.local_decls` and remapping the `Local`s.
let map = make_local_map(&mut body.local_decls, &used_locals);
// Only bother running the `LocalUpdater` if we actually found locals to remove.
if map.iter().any(Option::is_none) {
// Update references to all vars and tmps now
let mut updater = LocalUpdater { map, tcx };
updater.visit_body_preserves_cfg(body);
body.local_decls.shrink_to_fit();
}
}
/// Construct the mapping while swapping out unused stuff out from the `vec`.
fn make_local_map<V>(
local_decls: &mut IndexVec<Local, V>,

View file

@ -73,12 +73,13 @@ impl<'tcx> crate::MirPass<'tcx> for SimplifyComparisonIntegral {
_ => unreachable!(),
}
// delete comparison statement if it the value being switched on was moved, which means it can not be user later on
// delete comparison statement if it the value being switched on was moved, which means
// it can not be user later on
if opt.can_remove_bin_op_stmt {
bb.statements[opt.bin_op_stmt_idx].make_nop();
} else {
// if the integer being compared to a const integral is being moved into the comparison,
// e.g `_2 = Eq(move _3, const 'x');`
// if the integer being compared to a const integral is being moved into the
// comparison, e.g `_2 = Eq(move _3, const 'x');`
// we want to avoid making a double move later on in the switchInt on _3.
// So to avoid `switchInt(move _3) -> ['x': bb2, otherwise: bb1];`,
// we convert the move in the comparison statement to a copy.
@ -102,12 +103,15 @@ impl<'tcx> crate::MirPass<'tcx> for SimplifyComparisonIntegral {
// remove StorageDead (if it exists) being used in the assign of the comparison
for (stmt_idx, stmt) in bb.statements.iter().enumerate() {
if !matches!(stmt.kind, StatementKind::StorageDead(local) if local == opt.to_switch_on.local)
{
if !matches!(
stmt.kind,
StatementKind::StorageDead(local) if local == opt.to_switch_on.local
) {
continue;
}
storage_deads_to_remove.push((stmt_idx, opt.bb_idx));
// if we have StorageDeads to remove then make sure to insert them at the top of each target
// if we have StorageDeads to remove then make sure to insert them at the top of
// each target
for bb_idx in new_targets.all_targets() {
storage_deads_to_insert.push((
*bb_idx,
@ -207,7 +211,8 @@ fn find_branch_value_info<'tcx>(
(Constant(branch_value), Copy(to_switch_on) | Move(to_switch_on))
| (Copy(to_switch_on) | Move(to_switch_on), Constant(branch_value)) => {
let branch_value_ty = branch_value.const_.ty();
// we only want to apply this optimization if we are matching on integrals (and chars), as it is not possible to switch on floats
// we only want to apply this optimization if we are matching on integrals (and chars),
// as it is not possible to switch on floats
if !branch_value_ty.is_integral() && !branch_value_ty.is_char() {
return None;
};
@ -222,7 +227,8 @@ fn find_branch_value_info<'tcx>(
struct OptimizationInfo<'tcx> {
/// Basic block to apply the optimization
bb_idx: BasicBlock,
/// Statement index of Eq/Ne assignment that can be removed. None if the assignment can not be removed - i.e the statement is used later on
/// Statement index of Eq/Ne assignment that can be removed. None if the assignment can not be
/// removed - i.e the statement is used later on
bin_op_stmt_idx: usize,
/// Can remove Eq/Ne assignment
can_remove_bin_op_stmt: bool,

View file

@ -156,9 +156,9 @@ impl<'tcx> crate::MirPass<'tcx> for UnreachableEnumBranching {
};
true
}
// If and only if there is a variant that does not have a branch set,
// change the current of otherwise as the variant branch and set otherwise to unreachable.
// It transforms following code
// If and only if there is a variant that does not have a branch set, change the
// current of otherwise as the variant branch and set otherwise to unreachable. It
// transforms following code
// ```rust
// match c {
// Ordering::Less => 1,

View file

@ -26,7 +26,8 @@ impl crate::MirPass<'_> for UnreachablePropagation {
let terminator = bb_data.terminator();
let is_unreachable = match &terminator.kind {
TerminatorKind::Unreachable => true,
// This will unconditionally run into an unreachable and is therefore unreachable as well.
// This will unconditionally run into an unreachable and is therefore unreachable
// as well.
TerminatorKind::Goto { target } if unreachable_blocks.contains(target) => {
patch.patch_terminator(bb, TerminatorKind::Unreachable);
true
@ -85,8 +86,9 @@ fn remove_successors_from_switch<'tcx>(
// }
// }
//
// This generates a `switchInt() -> [0: 0, 1: 1, otherwise: unreachable]`, which allows us or LLVM to
// turn it into just `x` later. Without the unreachable, such a transformation would be illegal.
// This generates a `switchInt() -> [0: 0, 1: 1, otherwise: unreachable]`, which allows us or
// LLVM to turn it into just `x` later. Without the unreachable, such a transformation would be
// illegal.
//
// In order to preserve this information, we record reachable and unreachable targets as
// `Assume` statements in MIR.

View file

@ -388,10 +388,11 @@ impl<'a, 'tcx> Visitor<'tcx> for CfgChecker<'a, 'tcx> {
}
self.check_unwind_edge(location, unwind);
// The code generation assumes that there are no critical call edges. The assumption
// is used to simplify inserting code that should be executed along the return edge
// from the call. FIXME(tmiasko): Since this is a strictly code generation concern,
// the code generation should be responsible for handling it.
// The code generation assumes that there are no critical call edges. The
// assumption is used to simplify inserting code that should be executed along
// the return edge from the call. FIXME(tmiasko): Since this is a strictly code
// generation concern, the code generation should be responsible for handling
// it.
if self.mir_phase >= MirPhase::Runtime(RuntimePhase::Optimized)
&& self.is_critical_call_edge(target, unwind)
{
@ -404,8 +405,8 @@ impl<'a, 'tcx> Visitor<'tcx> for CfgChecker<'a, 'tcx> {
);
}
// The call destination place and Operand::Move place used as an argument might be
// passed by a reference to the callee. Consequently they cannot be packed.
// The call destination place and Operand::Move place used as an argument might
// be passed by a reference to the callee. Consequently they cannot be packed.
if is_within_packed(self.tcx, &self.body.local_decls, destination).is_some() {
// This is bad! The callee will expect the memory to be aligned.
self.fail(
@ -953,9 +954,9 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
}
AggregateKind::RawPtr(pointee_ty, mutability) => {
if !matches!(self.mir_phase, MirPhase::Runtime(_)) {
// It would probably be fine to support this in earlier phases,
// but at the time of writing it's only ever introduced from intrinsic lowering,
// so earlier things just `bug!` on it.
// It would probably be fine to support this in earlier phases, but at the
// time of writing it's only ever introduced from intrinsic lowering, so
// earlier things just `bug!` on it.
self.fail(location, "RawPtr should be in runtime MIR only");
}
@ -1109,10 +1110,10 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
}
UnOp::PtrMetadata => {
if !matches!(self.mir_phase, MirPhase::Runtime(_)) {
// It would probably be fine to support this in earlier phases,
// but at the time of writing it's only ever introduced from intrinsic lowering
// or other runtime-phase optimization passes,
// so earlier things can just `bug!` on it.
// It would probably be fine to support this in earlier phases, but at
// the time of writing it's only ever introduced from intrinsic
// lowering or other runtime-phase optimization passes, so earlier
// things can just `bug!` on it.
self.fail(location, "PtrMetadata should be in runtime MIR only");
}
@ -1506,7 +1507,8 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
}
if let TerminatorKind::TailCall { .. } = terminator.kind {
// FIXME(explicit_tail_calls): implement tail-call specific checks here (such as signature matching, forbidding closures, etc)
// FIXME(explicit_tail_calls): implement tail-call specific checks here (such
// as signature matching, forbidding closures, etc)
}
}
TerminatorKind::Assert { cond, .. } => {

View file

@ -1236,6 +1236,7 @@ symbols! {
mir_unwind_unreachable,
mir_variant,
miri,
missing_docs,
mmx_reg,
modifiers,
module,
@ -1255,6 +1256,7 @@ symbols! {
mut_preserve_binding_mode_2024,
mut_ref,
naked,
naked_asm,
naked_functions,
name,
names,

View file

@ -852,18 +852,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
impl<'hir, 'tcx> hir::intravisit::Visitor<'hir> for LifetimeReplaceVisitor<'tcx, '_> {
fn visit_lifetime(&mut self, lt: &'hir hir::Lifetime) {
if lt.res == self.needle {
let (pos, span) = lt.suggestion_position();
let new_lt = &self.new_lt;
let sugg = match pos {
hir::LifetimeSuggestionPosition::Normal => format!("{new_lt}"),
hir::LifetimeSuggestionPosition::Ampersand => format!("{new_lt} "),
hir::LifetimeSuggestionPosition::ElidedPath => format!("<{new_lt}>"),
hir::LifetimeSuggestionPosition::ElidedPathArgument => {
format!("{new_lt}, ")
}
hir::LifetimeSuggestionPosition::ObjectDefault => format!("+ {new_lt}"),
};
self.add_lt_suggs.push((span, sugg));
self.add_lt_suggs.push(lt.suggestion(self.new_lt));
}
}

View file

@ -237,7 +237,7 @@ pub fn supertrait_def_ids<I: Interner>(
cx: I,
trait_def_id: I::DefId,
) -> impl Iterator<Item = I::DefId> {
let mut set: HashSet<I::DefId> = HashSet::default();
let mut set = HashSet::default();
let mut stack = vec![trait_def_id];
set.insert(trait_def_id);

View file

@ -137,6 +137,8 @@ pub trait Interner:
f: impl FnOnce(&mut search_graph::GlobalCache<Self>) -> R,
) -> R;
fn evaluation_is_concurrent(&self) -> bool;
fn expand_abstract_consts<T: TypeFoldable<Self>>(self, t: T) -> T;
type GenericsOf: GenericsOf<Self>;
@ -404,4 +406,7 @@ impl<I: Interner> search_graph::Cx for I {
) -> R {
I::with_global_cache(self, mode, f)
}
fn evaluation_is_concurrent(&self) -> bool {
self.evaluation_is_concurrent()
}
}

View file

@ -44,22 +44,28 @@ impl<X: Cx> GlobalCache<X> {
cx: X,
input: X::Input,
result: X::Result,
origin_result: X::Result,
dep_node: X::DepNodeIndex,
additional_depth: usize,
encountered_overflow: bool,
nested_goals: NestedGoals<X>,
) {
let result = cx.mk_tracked(result, dep_node);
let result = cx.mk_tracked(origin_result, dep_node);
let entry = self.map.entry(input).or_default();
if encountered_overflow {
let with_overflow = WithOverflow { nested_goals, result };
let prev = entry.with_overflow.insert(additional_depth, with_overflow);
assert!(prev.is_none());
if let Some(prev) = &prev {
assert!(cx.evaluation_is_concurrent());
assert_eq!(cx.get_tracked(&prev.result), origin_result);
}
} else {
let prev = entry.success.replace(Success { additional_depth, nested_goals, result });
assert!(prev.is_none());
if let Some(prev) = &prev {
assert!(cx.evaluation_is_concurrent());
assert_eq!(cx.get_tracked(&prev.result), origin_result);
}
}
}

View file

@ -53,6 +53,8 @@ pub trait Cx: Copy {
mode: SolverMode,
f: impl FnOnce(&mut GlobalCache<Self>) -> R,
) -> R;
fn evaluation_is_concurrent(&self) -> bool;
}
pub trait Delegate {

View file

@ -42,6 +42,9 @@
# Unless you're developing for a target where Rust CI doesn't build a compiler
# toolchain or changing LLVM locally, you probably want to leave this enabled.
#
# Set this to `true` to download if CI llvm available otherwise it builds
# from `src/llvm-project`.
#
# Set this to `"if-unchanged"` to download only if the llvm-project has not
# been modified. You can also use this if you are unsure whether you're on a
# tier 1 target. All tier 1 targets are currently supported.
@ -236,7 +239,7 @@
# Instead of downloading the src/stage0 version of cargo-clippy specified,
# use this cargo-clippy binary instead as the stage0 snapshot cargo-clippy.
#
# Note that this option should be used with the same toolchain as the `rustc` option above.
# Note that this option should be used with the same toolchain as the `rustc` option above.
# Otherwise, clippy is likely to fail due to a toolchain conflict.
#cargo-clippy = "/path/to/cargo-clippy"

View file

@ -336,10 +336,10 @@ reverse!(reverse_u32, u32, |x| x as u32);
reverse!(reverse_u64, u64, |x| x as u64);
reverse!(reverse_u128, u128, |x| x as u128);
#[repr(simd)]
struct F64x4(f64, f64, f64, f64);
struct F64x4([f64; 4]);
reverse!(reverse_simd_f64x4, F64x4, |x| {
let x = x as f64;
F64x4(x, x, x, x)
F64x4([x, x, x, x])
});
macro_rules! rotate {

View file

@ -96,6 +96,7 @@ pub(crate) mod hack {
// We shouldn't add inline attribute to this since this is used in
// `vec!` macro mostly and causes perf regression. See #71204 for
// discussion and perf results.
#[allow(missing_docs)]
pub fn into_vec<T, A: Allocator>(b: Box<[T], A>) -> Vec<T, A> {
unsafe {
let len = b.len();
@ -105,6 +106,7 @@ pub(crate) mod hack {
}
#[cfg(not(no_global_oom_handling))]
#[allow(missing_docs)]
#[inline]
pub fn to_vec<T: ConvertVec, A: Allocator>(s: &[T], alloc: A) -> Vec<T, A> {
T::to_vec(s, alloc)

View file

@ -508,6 +508,7 @@ impl String {
// NB see the slice::hack module in slice.rs for more information
#[inline]
#[cfg(test)]
#[allow(missing_docs)]
pub fn from_str(_: &str) -> String {
panic!("not available with cfg(test)");
}

View file

@ -17,6 +17,20 @@ pub macro asm("assembly template", $(operands,)* $(options($(option),*))?) {
/* compiler built-in */
}
/// Inline assembly used in combination with `#[naked]` functions.
///
/// Refer to [Rust By Example] for a usage guide and the [reference] for
/// detailed information about the syntax and available options.
///
/// [Rust By Example]: https://doc.rust-lang.org/nightly/rust-by-example/unsafe/asm.html
/// [reference]: https://doc.rust-lang.org/nightly/reference/inline-assembly.html
#[unstable(feature = "naked_functions", issue = "90957")]
#[rustc_builtin_macro]
#[cfg(not(bootstrap))]
pub macro naked_asm("assembly template", $(operands,)* $(options($(option),*))?) {
/* compiler built-in */
}
/// Module-level inline assembly.
///
/// Refer to [Rust By Example] for a usage guide and the [reference] for

View file

@ -351,6 +351,9 @@ impl<T> MaybeUninit<T> {
/// but `MaybeUninit<&'static i32>::zeroed()` is not because references must not
/// be null.
///
/// Note that if `T` has padding bytes, those bytes are *not* preserved when the
/// `MaybeUninit<T>` value is returned from this function, so those bytes will *not* be zeroed.
///
/// Note that dropping a `MaybeUninit<T>` will never call `T`'s drop code.
/// It is your responsibility to make sure `T` gets dropped if it got initialized.
///

View file

@ -270,7 +270,7 @@ impl<T: ?Sized> *const T {
/// }
/// ```
#[stable(feature = "ptr_as_ref", since = "1.9.0")]
#[rustc_const_unstable(feature = "const_ptr_as_ref", issue = "91822")]
#[rustc_const_unstable(feature = "const_ptr_is_null", issue = "74939")]
#[inline]
pub const unsafe fn as_ref<'a>(self) -> Option<&'a T> {
// SAFETY: the caller must guarantee that `self` is valid
@ -302,7 +302,7 @@ impl<T: ?Sized> *const T {
/// ```
// FIXME: mention it in the docs for `as_ref` and `as_uninit_ref` once stabilized.
#[unstable(feature = "ptr_as_ref_unchecked", issue = "122034")]
#[rustc_const_unstable(feature = "const_ptr_as_ref", issue = "91822")]
#[rustc_const_unstable(feature = "ptr_as_ref_unchecked", issue = "122034")]
#[inline]
#[must_use]
pub const unsafe fn as_ref_unchecked<'a>(self) -> &'a T {
@ -336,7 +336,7 @@ impl<T: ?Sized> *const T {
/// ```
#[inline]
#[unstable(feature = "ptr_as_uninit", issue = "75402")]
#[rustc_const_unstable(feature = "const_ptr_as_ref", issue = "91822")]
#[rustc_const_unstable(feature = "ptr_as_uninit", issue = "75402")]
pub const unsafe fn as_uninit_ref<'a>(self) -> Option<&'a MaybeUninit<T>>
where
T: Sized,
@ -1664,7 +1664,7 @@ impl<T> *const [T] {
/// [allocated object]: crate::ptr#allocated-object
#[inline]
#[unstable(feature = "ptr_as_uninit", issue = "75402")]
#[rustc_const_unstable(feature = "const_ptr_as_ref", issue = "91822")]
#[rustc_const_unstable(feature = "ptr_as_uninit", issue = "75402")]
pub const unsafe fn as_uninit_slice<'a>(self) -> Option<&'a [MaybeUninit<T>]> {
if self.is_null() {
None

View file

@ -261,7 +261,7 @@ impl<T: ?Sized> *mut T {
/// }
/// ```
#[stable(feature = "ptr_as_ref", since = "1.9.0")]
#[rustc_const_unstable(feature = "const_ptr_as_ref", issue = "91822")]
#[rustc_const_unstable(feature = "const_ptr_is_null", issue = "74939")]
#[inline]
pub const unsafe fn as_ref<'a>(self) -> Option<&'a T> {
// SAFETY: the caller must guarantee that `self` is valid for a
@ -295,7 +295,7 @@ impl<T: ?Sized> *mut T {
/// ```
// FIXME: mention it in the docs for `as_ref` and `as_uninit_ref` once stabilized.
#[unstable(feature = "ptr_as_ref_unchecked", issue = "122034")]
#[rustc_const_unstable(feature = "const_ptr_as_ref", issue = "91822")]
#[rustc_const_unstable(feature = "ptr_as_ref_unchecked", issue = "122034")]
#[inline]
#[must_use]
pub const unsafe fn as_ref_unchecked<'a>(self) -> &'a T {
@ -334,7 +334,7 @@ impl<T: ?Sized> *mut T {
/// ```
#[inline]
#[unstable(feature = "ptr_as_uninit", issue = "75402")]
#[rustc_const_unstable(feature = "const_ptr_as_ref", issue = "91822")]
#[rustc_const_unstable(feature = "ptr_as_uninit", issue = "75402")]
pub const unsafe fn as_uninit_ref<'a>(self) -> Option<&'a MaybeUninit<T>>
where
T: Sized,
@ -580,7 +580,7 @@ impl<T: ?Sized> *mut T {
/// println!("{s:?}"); // It'll print: "[4, 2, 3]".
/// ```
#[stable(feature = "ptr_as_ref", since = "1.9.0")]
#[rustc_const_unstable(feature = "const_ptr_as_ref", issue = "91822")]
#[rustc_const_unstable(feature = "const_ptr_is_null", issue = "74939")]
#[inline]
pub const unsafe fn as_mut<'a>(self) -> Option<&'a mut T> {
// SAFETY: the caller must guarantee that `self` is be valid for
@ -616,7 +616,7 @@ impl<T: ?Sized> *mut T {
/// ```
// FIXME: mention it in the docs for `as_mut` and `as_uninit_mut` once stabilized.
#[unstable(feature = "ptr_as_ref_unchecked", issue = "122034")]
#[rustc_const_unstable(feature = "const_ptr_as_ref", issue = "91822")]
#[rustc_const_unstable(feature = "ptr_as_ref_unchecked", issue = "122034")]
#[inline]
#[must_use]
pub const unsafe fn as_mut_unchecked<'a>(self) -> &'a mut T {
@ -639,7 +639,7 @@ impl<T: ?Sized> *mut T {
/// the pointer is [convertible to a reference](crate::ptr#pointer-to-reference-conversion).
#[inline]
#[unstable(feature = "ptr_as_uninit", issue = "75402")]
#[rustc_const_unstable(feature = "const_ptr_as_ref", issue = "91822")]
#[rustc_const_unstable(feature = "ptr_as_uninit", issue = "75402")]
pub const unsafe fn as_uninit_mut<'a>(self) -> Option<&'a mut MaybeUninit<T>>
where
T: Sized,
@ -2016,7 +2016,7 @@ impl<T> *mut [T] {
/// [allocated object]: crate::ptr#allocated-object
#[inline]
#[unstable(feature = "ptr_as_uninit", issue = "75402")]
#[rustc_const_unstable(feature = "const_ptr_as_ref", issue = "91822")]
#[rustc_const_unstable(feature = "ptr_as_uninit", issue = "75402")]
pub const unsafe fn as_uninit_slice<'a>(self) -> Option<&'a [MaybeUninit<T>]> {
if self.is_null() {
None
@ -2068,7 +2068,7 @@ impl<T> *mut [T] {
/// [allocated object]: crate::ptr#allocated-object
#[inline]
#[unstable(feature = "ptr_as_uninit", issue = "75402")]
#[rustc_const_unstable(feature = "const_ptr_as_ref", issue = "91822")]
#[rustc_const_unstable(feature = "ptr_as_uninit", issue = "75402")]
pub const unsafe fn as_uninit_slice_mut<'a>(self) -> Option<&'a mut [MaybeUninit<T>]> {
if self.is_null() {
None

View file

@ -133,7 +133,7 @@ impl<T: Sized> NonNull<T> {
#[inline]
#[must_use]
#[unstable(feature = "ptr_as_uninit", issue = "75402")]
#[rustc_const_unstable(feature = "const_ptr_as_ref", issue = "91822")]
#[rustc_const_unstable(feature = "ptr_as_uninit", issue = "75402")]
pub const unsafe fn as_uninit_ref<'a>(self) -> &'a MaybeUninit<T> {
// SAFETY: the caller must guarantee that `self` meets all the
// requirements for a reference.
@ -157,7 +157,7 @@ impl<T: Sized> NonNull<T> {
#[inline]
#[must_use]
#[unstable(feature = "ptr_as_uninit", issue = "75402")]
#[rustc_const_unstable(feature = "const_ptr_as_ref", issue = "91822")]
#[rustc_const_unstable(feature = "ptr_as_uninit", issue = "75402")]
pub const unsafe fn as_uninit_mut<'a>(self) -> &'a mut MaybeUninit<T> {
// SAFETY: the caller must guarantee that `self` meets all the
// requirements for a reference.
@ -1563,7 +1563,7 @@ impl<T> NonNull<[T]> {
#[inline]
#[must_use]
#[unstable(feature = "ptr_as_uninit", issue = "75402")]
#[rustc_const_unstable(feature = "const_ptr_as_ref", issue = "91822")]
#[rustc_const_unstable(feature = "ptr_as_uninit", issue = "75402")]
pub const unsafe fn as_uninit_slice<'a>(self) -> &'a [MaybeUninit<T>] {
// SAFETY: the caller must uphold the safety contract for `as_uninit_slice`.
unsafe { slice::from_raw_parts(self.cast().as_ptr(), self.len()) }
@ -1628,7 +1628,7 @@ impl<T> NonNull<[T]> {
#[inline]
#[must_use]
#[unstable(feature = "ptr_as_uninit", issue = "75402")]
#[rustc_const_unstable(feature = "const_ptr_as_ref", issue = "91822")]
#[rustc_const_unstable(feature = "ptr_as_uninit", issue = "75402")]
pub const unsafe fn as_uninit_slice_mut<'a>(self) -> &'a mut [MaybeUninit<T>] {
// SAFETY: the caller must uphold the safety contract for `as_uninit_slice_mut`.
unsafe { slice::from_raw_parts_mut(self.cast().as_ptr(), self.len()) }

View file

@ -267,6 +267,7 @@ impl<R: ?Sized> BufReader<R> {
// This is only used by a test which asserts that the initialization-tracking is correct.
#[cfg(test)]
impl<R: ?Sized> BufReader<R> {
#[allow(missing_docs)]
pub fn initialized(&self) -> usize {
self.buf.initialized()
}

View file

@ -223,10 +223,10 @@ pub enum ErrorKind {
#[stable(feature = "rust1", since = "1.0.0")]
ConnectionReset,
/// The remote host is not reachable.
#[unstable(feature = "io_error_more", issue = "86442")]
#[stable(feature = "io_error_a_bit_more", since = "CURRENT_RUSTC_VERSION")]
HostUnreachable,
/// The network containing the remote host is not reachable.
#[unstable(feature = "io_error_more", issue = "86442")]
#[stable(feature = "io_error_a_bit_more", since = "CURRENT_RUSTC_VERSION")]
NetworkUnreachable,
/// The connection was aborted (terminated) by the remote server.
#[stable(feature = "rust1", since = "1.0.0")]
@ -243,7 +243,7 @@ pub enum ErrorKind {
#[stable(feature = "rust1", since = "1.0.0")]
AddrNotAvailable,
/// The system's networking is down.
#[unstable(feature = "io_error_more", issue = "86442")]
#[stable(feature = "io_error_a_bit_more", since = "CURRENT_RUSTC_VERSION")]
NetworkDown,
/// The operation failed because a pipe was closed.
#[stable(feature = "rust1", since = "1.0.0")]
@ -259,18 +259,18 @@ pub enum ErrorKind {
///
/// For example, a filesystem path was specified where one of the intermediate directory
/// components was, in fact, a plain file.
#[unstable(feature = "io_error_more", issue = "86442")]
#[stable(feature = "io_error_a_bit_more", since = "CURRENT_RUSTC_VERSION")]
NotADirectory,
/// The filesystem object is, unexpectedly, a directory.
///
/// A directory was specified when a non-directory was expected.
#[unstable(feature = "io_error_more", issue = "86442")]
#[stable(feature = "io_error_a_bit_more", since = "CURRENT_RUSTC_VERSION")]
IsADirectory,
/// A non-empty directory was specified where an empty directory was expected.
#[unstable(feature = "io_error_more", issue = "86442")]
#[stable(feature = "io_error_a_bit_more", since = "CURRENT_RUSTC_VERSION")]
DirectoryNotEmpty,
/// The filesystem or storage medium is read-only, but a write operation was attempted.
#[unstable(feature = "io_error_more", issue = "86442")]
#[stable(feature = "io_error_a_bit_more", since = "CURRENT_RUSTC_VERSION")]
ReadOnlyFilesystem,
/// Loop in the filesystem or IO subsystem; often, too many levels of symbolic links.
///
@ -285,7 +285,7 @@ pub enum ErrorKind {
///
/// With some network filesystems, notably NFS, an open file (or directory) can be invalidated
/// by problems with the network or server.
#[unstable(feature = "io_error_more", issue = "86442")]
#[stable(feature = "io_error_a_bit_more", since = "CURRENT_RUSTC_VERSION")]
StaleNetworkFileHandle,
/// A parameter was incorrect.
#[stable(feature = "rust1", since = "1.0.0")]
@ -319,13 +319,13 @@ pub enum ErrorKind {
/// The underlying storage (typically, a filesystem) is full.
///
/// This does not include out of quota errors.
#[unstable(feature = "io_error_more", issue = "86442")]
#[stable(feature = "io_error_a_bit_more", since = "CURRENT_RUSTC_VERSION")]
StorageFull,
/// Seek on unseekable file.
///
/// Seeking was attempted on an open file handle which is not suitable for seeking - for
/// example, on Unix, a named pipe opened with `File::open`.
#[unstable(feature = "io_error_more", issue = "86442")]
#[stable(feature = "io_error_a_bit_more", since = "CURRENT_RUSTC_VERSION")]
NotSeekable,
/// Filesystem quota was exceeded.
#[unstable(feature = "io_error_more", issue = "86442")]
@ -335,22 +335,22 @@ pub enum ErrorKind {
/// This might arise from a hard limit of the underlying filesystem or file access API, or from
/// an administratively imposed resource limitation. Simple disk full, and out of quota, have
/// their own errors.
#[unstable(feature = "io_error_more", issue = "86442")]
#[stable(feature = "io_error_a_bit_more", since = "CURRENT_RUSTC_VERSION")]
FileTooLarge,
/// Resource is busy.
#[unstable(feature = "io_error_more", issue = "86442")]
#[stable(feature = "io_error_a_bit_more", since = "CURRENT_RUSTC_VERSION")]
ResourceBusy,
/// Executable file is busy.
///
/// An attempt was made to write to a file which is also in use as a running program. (Not all
/// operating systems detect this situation.)
#[unstable(feature = "io_error_more", issue = "86442")]
#[stable(feature = "io_error_a_bit_more", since = "CURRENT_RUSTC_VERSION")]
ExecutableFileBusy,
/// Deadlock (avoided).
///
/// A file locking operation would result in deadlock. This situation is typically detected, if
/// at all, on a best-effort basis.
#[unstable(feature = "io_error_more", issue = "86442")]
#[stable(feature = "io_error_a_bit_more", since = "CURRENT_RUSTC_VERSION")]
Deadlock,
/// Cross-device or cross-filesystem (hard) link or rename.
#[unstable(feature = "io_error_more", issue = "86442")]
@ -358,7 +358,7 @@ pub enum ErrorKind {
/// Too many (hard) links to the same filesystem object.
///
/// The filesystem does not support making so many hardlinks to the same file.
#[unstable(feature = "io_error_more", issue = "86442")]
#[stable(feature = "io_error_a_bit_more", since = "CURRENT_RUSTC_VERSION")]
TooManyLinks,
/// A filename was invalid.
///
@ -369,7 +369,7 @@ pub enum ErrorKind {
///
/// When trying to run an external program, a system or process limit on the size of the
/// arguments would have been exceeded.
#[unstable(feature = "io_error_more", issue = "86442")]
#[stable(feature = "io_error_a_bit_more", since = "CURRENT_RUSTC_VERSION")]
ArgumentListTooLong,
/// This operation was interrupted.
///

View file

@ -32,7 +32,8 @@ impl Drop for Handler {
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd",
target_os = "solaris"
target_os = "solaris",
target_os = "illumos",
))]
mod imp {
#[cfg(not(all(target_os = "linux", target_env = "gnu")))]
@ -280,7 +281,7 @@ mod imp {
libc::SIGSTKSZ
}
#[cfg(target_os = "solaris")]
#[cfg(any(target_os = "solaris", target_os = "illumos"))]
unsafe fn get_stack_start() -> Option<*mut libc::c_void> {
let mut current_stack: libc::stack_t = crate::mem::zeroed();
assert_eq!(libc::stack_getbounds(&mut current_stack), 0);
@ -486,7 +487,12 @@ mod imp {
Some(guardaddr..guardaddr + page_size)
}
#[cfg(any(target_os = "macos", target_os = "openbsd", target_os = "solaris"))]
#[cfg(any(
target_os = "macos",
target_os = "openbsd",
target_os = "solaris",
target_os = "illumos",
))]
// FIXME: I am probably not unsafe.
unsafe fn current_guard() -> Option<Range<usize>> {
let stackptr = get_stack_start()?;
@ -569,7 +575,8 @@ mod imp {
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd",
target_os = "solaris"
target_os = "solaris",
target_os = "illumos",
)))]
mod imp {
pub unsafe fn init() {}

View file

@ -27,4 +27,5 @@ assertions = false
# Enable warnings during the LLVM compilation (when LLVM is changed, causing a compilation)
enable-warnings = true
# Will download LLVM from CI if available on your platform.
download-ci-llvm = "if-unchanged"
# If you intend to modify `src/llvm-project`, use `"if-unchanged"` or `false` instead.
download-ci-llvm = true

View file

@ -1,4 +1,4 @@
Change this file to make users of the `download-ci-llvm` configuration download
a new version of LLVM from CI, even if the LLVM submodule hasnt changed.
Last change is for: https://github.com/rust-lang/rust/pull/129116
Last change is for: https://github.com/rust-lang/rust/pull/129788

View file

@ -54,30 +54,34 @@ check-aux:
src/etc/test-float-parse \
$(BOOTSTRAP_ARGS)
# Run standard library tests in Miri.
$(Q)BOOTSTRAP_SKIP_TARGET_SANITY=1 \
$(BOOTSTRAP) miri --stage 2 \
$(Q)$(BOOTSTRAP) miri --stage 2 \
library/core \
library/alloc \
$(BOOTSTRAP_ARGS) \
--no-doc
# Some doctests use file system operations to demonstrate dealing with `Result`.
$(Q)MIRIFLAGS="-Zmiri-disable-isolation" \
$(BOOTSTRAP) miri --stage 2 \
library/core \
library/alloc \
$(BOOTSTRAP_ARGS) \
--doc
# In `std` we cannot test everything, so we skip some modules.
$(Q)MIRIFLAGS="-Zmiri-disable-isolation" \
$(BOOTSTRAP) miri --stage 2 library/std \
$(BOOTSTRAP_ARGS) \
--no-doc -- \
--skip fs:: --skip net:: --skip process:: --skip sys::pal::
$(Q)MIRIFLAGS="-Zmiri-disable-isolation" \
$(BOOTSTRAP) miri --stage 2 library/std \
$(BOOTSTRAP_ARGS) \
--doc -- \
--skip fs:: --skip net:: --skip process:: --skip sys::pal::
# Also test some very target-specific modules on other targets
# (making sure to cover an i686 target as well).
$(Q)MIRIFLAGS="-Zmiri-disable-isolation" BOOTSTRAP_SKIP_TARGET_SANITY=1 \
$(BOOTSTRAP) miri --stage 2 library/std \
$(BOOTSTRAP_ARGS) \
--target aarch64-apple-darwin,i686-pc-windows-msvc \
--no-doc -- \
time:: sync:: thread:: env::

View file

@ -2766,7 +2766,8 @@ impl Config {
);
}
b
// If download-ci-llvm=true we also want to check that CI llvm is available
b && llvm::is_ci_llvm_available(self, asserts)
}
Some(StringOrBool::String(s)) if s == "if-unchanged" => if_unchanged(),
Some(StringOrBool::String(other)) => {

View file

@ -250,4 +250,9 @@ pub const CONFIG_CHANGE_HISTORY: &[ChangeInfo] = &[
severity: ChangeSeverity::Info,
summary: "New option `llvm.enzyme` to control whether the llvm based autodiff tool (Enzyme) is built.",
},
ChangeInfo {
change_id: 129473,
severity: ChangeSeverity::Warning,
summary: "`download-ci-llvm = true` now checks if CI llvm is available and has become the default for the compiler profile",
},
];

View file

@ -53,12 +53,18 @@ impl TryFrom<&str> for OutputFormat {
}
}
/// Either an input crate, markdown file, or nothing (--merge=finalize).
pub(crate) enum InputMode {
/// The `--merge=finalize` step does not need an input crate to rustdoc.
NoInputMergeFinalize,
/// A crate or markdown file.
HasFile(Input),
}
/// Configuration options for rustdoc.
#[derive(Clone)]
pub(crate) struct Options {
// Basic options / Options passed directly to rustc
/// The crate root or Markdown file to load.
pub(crate) input: Input,
/// The name of the crate being documented.
pub(crate) crate_name: Option<String>,
/// Whether or not this is a bin crate
@ -179,7 +185,6 @@ impl fmt::Debug for Options {
}
f.debug_struct("Options")
.field("input", &self.input.source_name())
.field("crate_name", &self.crate_name)
.field("bin_crate", &self.bin_crate)
.field("proc_macro_crate", &self.proc_macro_crate)
@ -289,6 +294,12 @@ pub(crate) struct RenderOptions {
/// This field is only used for the JSON output. If it's set to true, no file will be created
/// and content will be displayed in stdout directly.
pub(crate) output_to_stdout: bool,
/// Whether we should read or write rendered cross-crate info in the doc root.
pub(crate) should_merge: ShouldMerge,
/// Path to crate-info for external crates.
pub(crate) include_parts_dir: Vec<PathToParts>,
/// Where to write crate-info
pub(crate) parts_out_dir: Option<PathToParts>,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
@ -348,7 +359,7 @@ impl Options {
early_dcx: &mut EarlyDiagCtxt,
matches: &getopts::Matches,
args: Vec<String>,
) -> Option<(Options, RenderOptions)> {
) -> Option<(InputMode, Options, RenderOptions)> {
// Check for unstable options.
nightly_options::check_nightly_options(early_dcx, matches, &opts());
@ -478,15 +489,17 @@ impl Options {
let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(early_dcx, matches);
let input = if describe_lints {
"" // dummy, this won't be used
InputMode::HasFile(make_input(early_dcx, ""))
} else {
match matches.free.as_slice() {
[] if matches.opt_str("merge").as_deref() == Some("finalize") => {
InputMode::NoInputMergeFinalize
}
[] => dcx.fatal("missing file operand"),
[input] => input,
[input] => InputMode::HasFile(make_input(early_dcx, input)),
_ => dcx.fatal("too many file operands"),
}
};
let input = make_input(early_dcx, input);
let externs = parse_externs(early_dcx, matches, &unstable_opts);
let extern_html_root_urls = match parse_extern_html_roots(matches) {
@ -494,6 +507,16 @@ impl Options {
Err(err) => dcx.fatal(err),
};
let parts_out_dir =
match matches.opt_str("parts-out-dir").map(|p| PathToParts::from_flag(p)).transpose() {
Ok(parts_out_dir) => parts_out_dir,
Err(e) => dcx.fatal(e),
};
let include_parts_dir = match parse_include_parts_dir(matches) {
Ok(include_parts_dir) => include_parts_dir,
Err(e) => dcx.fatal(e),
};
let default_settings: Vec<Vec<(String, String)>> = vec![
matches
.opt_str("default-theme")
@ -735,6 +758,10 @@ impl Options {
let extern_html_root_takes_precedence =
matches.opt_present("extern-html-root-takes-precedence");
let html_no_source = matches.opt_present("html-no-source");
let should_merge = match parse_merge(matches) {
Ok(result) => result,
Err(e) => dcx.fatal(format!("--merge option error: {e}")),
};
if generate_link_to_definition && (show_coverage || output_format != OutputFormat::Html) {
dcx.struct_warn(
@ -751,7 +778,6 @@ impl Options {
let unstable_features =
rustc_feature::UnstableFeatures::from_environment(crate_name.as_deref());
let options = Options {
input,
bin_crate,
proc_macro_crate,
error_format,
@ -823,16 +849,17 @@ impl Options {
no_emit_shared: false,
html_no_source,
output_to_stdout,
should_merge,
include_parts_dir,
parts_out_dir,
};
Some((options, render_options))
Some((input, options, render_options))
}
}
/// Returns `true` if the file given as `self.input` is a Markdown file.
pub(crate) fn markdown_input(&self) -> Option<&Path> {
self.input
.opt_path()
.filter(|p| matches!(p.extension(), Some(e) if e == "md" || e == "markdown"))
}
/// Returns `true` if the file given as `self.input` is a Markdown file.
pub(crate) fn markdown_input(input: &Input) -> Option<&Path> {
input.opt_path().filter(|p| matches!(p.extension(), Some(e) if e == "md" || e == "markdown"))
}
fn parse_remap_path_prefix(
@ -900,3 +927,71 @@ fn parse_extern_html_roots(
}
Ok(externs)
}
/// Path directly to crate-info file.
///
/// For example, `/home/user/project/target/doc.parts/<crate>/crate-info`.
#[derive(Clone, Debug)]
pub(crate) struct PathToParts(pub(crate) PathBuf);
impl PathToParts {
fn from_flag(path: String) -> Result<PathToParts, String> {
let mut path = PathBuf::from(path);
// check here is for diagnostics
if path.exists() && !path.is_dir() {
Err(format!(
"--parts-out-dir and --include-parts-dir expect directories, found: {}",
path.display(),
))
} else {
// if it doesn't exist, we'll create it. worry about that in write_shared
path.push("crate-info");
Ok(PathToParts(path))
}
}
}
/// Reports error if --include-parts-dir / crate-info is not a file
fn parse_include_parts_dir(m: &getopts::Matches) -> Result<Vec<PathToParts>, String> {
let mut ret = Vec::new();
for p in m.opt_strs("include-parts-dir") {
let p = PathToParts::from_flag(p)?;
// this is just for diagnostic
if !p.0.is_file() {
return Err(format!("--include-parts-dir expected {} to be a file", p.0.display()));
}
ret.push(p);
}
Ok(ret)
}
/// Controls merging of cross-crate information
#[derive(Debug, Clone)]
pub(crate) struct ShouldMerge {
/// Should we append to existing cci in the doc root
pub(crate) read_rendered_cci: bool,
/// Should we write cci to the doc root
pub(crate) write_rendered_cci: bool,
}
/// Extracts read_rendered_cci and write_rendered_cci from command line arguments, or
/// reports an error if an invalid option was provided
fn parse_merge(m: &getopts::Matches) -> Result<ShouldMerge, &'static str> {
match m.opt_str("merge").as_deref() {
// default = read-write
None => Ok(ShouldMerge { read_rendered_cci: true, write_rendered_cci: true }),
Some("none") if m.opt_present("include-parts-dir") => {
Err("--include-parts-dir not allowed if --merge=none")
}
Some("none") => Ok(ShouldMerge { read_rendered_cci: false, write_rendered_cci: false }),
Some("shared") if m.opt_present("parts-out-dir") || m.opt_present("include-parts-dir") => {
Err("--parts-out-dir and --include-parts-dir not allowed if --merge=shared")
}
Some("shared") => Ok(ShouldMerge { read_rendered_cci: true, write_rendered_cci: true }),
Some("finalize") if m.opt_present("parts-out-dir") => {
Err("--parts-out-dir not allowed if --merge=finalize")
}
Some("finalize") => Ok(ShouldMerge { read_rendered_cci: false, write_rendered_cci: true }),
Some(_) => Err("argument to --merge must be `none`, `shared`, or `finalize`"),
}
}

View file

@ -20,7 +20,7 @@ use rustc_interface::interface;
use rustc_lint::{late_lint_mod, MissingDoc};
use rustc_middle::hir::nested_filter;
use rustc_middle::ty::{ParamEnv, Ty, TyCtxt};
use rustc_session::config::{self, CrateType, ErrorOutputType, ResolveDocLinks};
use rustc_session::config::{self, CrateType, ErrorOutputType, Input, ResolveDocLinks};
pub(crate) use rustc_session::config::{Options, UnstableOptions};
use rustc_session::{lint, Session};
use rustc_span::symbol::sym;
@ -177,8 +177,8 @@ pub(crate) fn new_dcx(
/// Parse, resolve, and typecheck the given crate.
pub(crate) fn create_config(
input: Input,
RustdocOptions {
input,
crate_name,
proc_macro_crate,
error_format,

View file

@ -19,7 +19,7 @@ use rustc_errors::{ColorConfig, DiagCtxtHandle, ErrorGuaranteed, FatalError};
use rustc_hir::def_id::LOCAL_CRATE;
use rustc_hir::CRATE_HIR_ID;
use rustc_interface::interface;
use rustc_session::config::{self, CrateType, ErrorOutputType};
use rustc_session::config::{self, CrateType, ErrorOutputType, Input};
use rustc_session::lint;
use rustc_span::edition::Edition;
use rustc_span::symbol::sym;
@ -88,7 +88,11 @@ fn get_doctest_dir() -> io::Result<TempDir> {
TempFileBuilder::new().prefix("rustdoctest").tempdir()
}
pub(crate) fn run(dcx: DiagCtxtHandle<'_>, options: RustdocOptions) -> Result<(), ErrorGuaranteed> {
pub(crate) fn run(
dcx: DiagCtxtHandle<'_>,
input: Input,
options: RustdocOptions,
) -> Result<(), ErrorGuaranteed> {
let invalid_codeblock_attributes_name = crate::lint::INVALID_CODEBLOCK_ATTRIBUTES.name;
// See core::create_config for what's going on here.
@ -135,7 +139,7 @@ pub(crate) fn run(dcx: DiagCtxtHandle<'_>, options: RustdocOptions) -> Result<()
opts: sessopts,
crate_cfg: cfgs,
crate_check_cfg: options.check_cfgs.clone(),
input: options.input.clone(),
input: input.clone(),
output_file: None,
output_dir: None,
file_loader: None,

View file

@ -3,6 +3,7 @@
use std::fs::read_to_string;
use std::sync::{Arc, Mutex};
use rustc_session::config::Input;
use rustc_span::FileName;
use tempfile::tempdir;
@ -69,9 +70,8 @@ impl DocTestVisitor for MdCollector {
}
/// Runs any tests/code examples in the markdown file `options.input`.
pub(crate) fn test(options: Options) -> Result<(), String> {
use rustc_session::config::Input;
let input_str = match &options.input {
pub(crate) fn test(input: &Input, options: Options) -> Result<(), String> {
let input_str = match input {
Input::File(path) => {
read_to_string(path).map_err(|err| format!("{}: {err}", path.display()))?
}
@ -79,7 +79,7 @@ pub(crate) fn test(options: Options) -> Result<(), String> {
};
// Obviously not a real crate name, but close enough for purposes of doctests.
let crate_name = options.input.filestem().to_string();
let crate_name = input.filestem().to_string();
let temp_dir =
tempdir().map_err(|error| format!("failed to create temporary directory: {error:?}"))?;
let args_file = temp_dir.path().join("rustdoc-cfgs");
@ -96,8 +96,7 @@ pub(crate) fn test(options: Options) -> Result<(), String> {
let mut md_collector = MdCollector {
tests: vec![],
cur_path: vec![],
filename: options
.input
filename: input
.opt_path()
.map(ToOwned::to_owned)
.map(FileName::from)

View file

@ -16,12 +16,11 @@ use tracing::info;
use super::print_item::{full_path, item_path, print_item};
use super::sidebar::{print_sidebar, sidebar_module_like, ModuleLike, Sidebar};
use super::write_shared::write_shared;
use super::{collect_spans_and_sources, scrape_examples_help, AllTypes, LinkFromSrc, StylePath};
use crate::clean::types::ExternalLocation;
use crate::clean::utils::has_doc_flag;
use crate::clean::{self, ExternalCrate};
use crate::config::{ModuleSorting, RenderOptions};
use crate::config::{ModuleSorting, RenderOptions, ShouldMerge};
use crate::docfs::{DocFS, PathError};
use crate::error::Error;
use crate::formats::cache::Cache;
@ -30,6 +29,7 @@ use crate::formats::FormatRenderer;
use crate::html::escape::Escape;
use crate::html::format::{join_with_double_colon, Buffer};
use crate::html::markdown::{self, plain_text_summary, ErrorCodes, IdMap};
use crate::html::render::write_shared::write_shared;
use crate::html::url_parts_builder::UrlPartsBuilder;
use crate::html::{layout, sources, static_files};
use crate::scrape_examples::AllCallLocations;
@ -128,8 +128,10 @@ pub(crate) struct SharedContext<'tcx> {
pub(crate) span_correspondence_map: FxHashMap<rustc_span::Span, LinkFromSrc>,
/// The [`Cache`] used during rendering.
pub(crate) cache: Cache,
pub(crate) call_locations: AllCallLocations,
/// Controls whether we read / write to cci files in the doc root. Defaults read=true,
/// write=true
should_merge: ShouldMerge,
}
impl SharedContext<'_> {
@ -551,6 +553,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
span_correspondence_map: matches,
cache,
call_locations,
should_merge: options.should_merge,
};
let dst = output;
@ -640,92 +643,96 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
);
shared.fs.write(final_file, v)?;
// Generating settings page.
page.title = "Settings";
page.description = "Settings of Rustdoc";
page.root_path = "./";
page.rust_logo = true;
// if to avoid writing help, settings files to doc root unless we're on the final invocation
if shared.should_merge.write_rendered_cci {
// Generating settings page.
page.title = "Settings";
page.description = "Settings of Rustdoc";
page.root_path = "./";
page.rust_logo = true;
let sidebar = "<h2 class=\"location\">Settings</h2><div class=\"sidebar-elems\"></div>";
let v = layout::render(
&shared.layout,
&page,
sidebar,
|buf: &mut Buffer| {
write!(
buf,
"<div class=\"main-heading\">\
<h1>Rustdoc settings</h1>\
<span class=\"out-of-band\">\
<a id=\"back\" href=\"javascript:void(0)\" onclick=\"history.back();\">\
Back\
</a>\
</span>\
</div>\
<noscript>\
<section>\
You need to enable JavaScript be able to update your settings.\
</section>\
</noscript>\
<script defer src=\"{static_root_path}{settings_js}\"></script>",
static_root_path = page.get_static_root_path(),
settings_js = static_files::STATIC_FILES.settings_js,
);
// Pre-load all theme CSS files, so that switching feels seamless.
//
// When loading settings.html as a popover, the equivalent HTML is
// generated in main.js.
for file in &shared.style_files {
if let Ok(theme) = file.basename() {
write!(
buf,
"<link rel=\"preload\" href=\"{root_path}{theme}{suffix}.css\" \
as=\"style\">",
root_path = page.static_root_path.unwrap_or(""),
suffix = page.resource_suffix,
);
let sidebar = "<h2 class=\"location\">Settings</h2><div class=\"sidebar-elems\"></div>";
let v = layout::render(
&shared.layout,
&page,
sidebar,
|buf: &mut Buffer| {
write!(
buf,
"<div class=\"main-heading\">\
<h1>Rustdoc settings</h1>\
<span class=\"out-of-band\">\
<a id=\"back\" href=\"javascript:void(0)\" onclick=\"history.back();\">\
Back\
</a>\
</span>\
</div>\
<noscript>\
<section>\
You need to enable JavaScript be able to update your settings.\
</section>\
</noscript>\
<script defer src=\"{static_root_path}{settings_js}\"></script>",
static_root_path = page.get_static_root_path(),
settings_js = static_files::STATIC_FILES.settings_js,
);
// Pre-load all theme CSS files, so that switching feels seamless.
//
// When loading settings.html as a popover, the equivalent HTML is
// generated in main.js.
for file in &shared.style_files {
if let Ok(theme) = file.basename() {
write!(
buf,
"<link rel=\"preload\" href=\"{root_path}{theme}{suffix}.css\" \
as=\"style\">",
root_path = page.static_root_path.unwrap_or(""),
suffix = page.resource_suffix,
);
}
}
}
},
&shared.style_files,
);
shared.fs.write(settings_file, v)?;
},
&shared.style_files,
);
shared.fs.write(settings_file, v)?;
// Generating help page.
page.title = "Help";
page.description = "Documentation for Rustdoc";
page.root_path = "./";
page.rust_logo = true;
// Generating help page.
page.title = "Help";
page.description = "Documentation for Rustdoc";
page.root_path = "./";
page.rust_logo = true;
let sidebar = "<h2 class=\"location\">Help</h2><div class=\"sidebar-elems\"></div>";
let v = layout::render(
&shared.layout,
&page,
sidebar,
|buf: &mut Buffer| {
write!(
buf,
"<div class=\"main-heading\">\
<h1>Rustdoc help</h1>\
<span class=\"out-of-band\">\
<a id=\"back\" href=\"javascript:void(0)\" onclick=\"history.back();\">\
Back\
</a>\
</span>\
</div>\
<noscript>\
<section>\
<p>You need to enable JavaScript to use keyboard commands or search.</p>\
<p>For more information, browse the <a href=\"https://doc.rust-lang.org/rustdoc/\">rustdoc handbook</a>.</p>\
</section>\
</noscript>",
)
},
&shared.style_files,
);
shared.fs.write(help_file, v)?;
let sidebar = "<h2 class=\"location\">Help</h2><div class=\"sidebar-elems\"></div>";
let v = layout::render(
&shared.layout,
&page,
sidebar,
|buf: &mut Buffer| {
write!(
buf,
"<div class=\"main-heading\">\
<h1>Rustdoc help</h1>\
<span class=\"out-of-band\">\
<a id=\"back\" href=\"javascript:void(0)\" onclick=\"history.back();\">\
Back\
</a>\
</span>\
</div>\
<noscript>\
<section>\
<p>You need to enable JavaScript to use keyboard commands or search.</p>\
<p>For more information, browse the <a href=\"https://doc.rust-lang.org/rustdoc/\">rustdoc handbook</a>.</p>\
</section>\
</noscript>",
)
},
&shared.style_files,
);
shared.fs.write(help_file, v)?;
}
if shared.layout.scrape_examples_extension {
// if to avoid writing files to doc root unless we're on the final invocation
if shared.layout.scrape_examples_extension && shared.should_merge.write_rendered_cci {
page.title = "About scraped examples";
page.description = "How the scraped examples feature works in Rustdoc";
let v = layout::render(

View file

@ -61,6 +61,7 @@ use tracing::{debug, info};
pub(crate) use self::context::*;
pub(crate) use self::span_map::{collect_spans_and_sources, LinkFromSrc};
pub(crate) use self::write_shared::*;
use crate::clean::{self, ItemId, RenderedLink};
use crate::error::Error;
use crate::formats::cache::Cache;

View file

@ -39,7 +39,7 @@ use serde::{Deserialize, Serialize, Serializer};
use super::{collect_paths_for_type, ensure_trailing_slash, Context, RenderMode};
use crate::clean::{Crate, Item, ItemId, ItemKind};
use crate::config::{EmitType, RenderOptions};
use crate::config::{EmitType, PathToParts, RenderOptions, ShouldMerge};
use crate::docfs::PathError;
use crate::error::Error;
use crate::formats::cache::Cache;
@ -50,12 +50,11 @@ use crate::html::layout;
use crate::html::render::ordered_json::{EscapedJson, OrderedJson};
use crate::html::render::search_index::{build_index, SerializedSearchIndex};
use crate::html::render::sorted_template::{self, FileFormat, SortedTemplate};
use crate::html::render::{AssocItemLink, ImplRenderingParameters};
use crate::html::render::{AssocItemLink, ImplRenderingParameters, StylePath};
use crate::html::static_files::{self, suffix_path};
use crate::visit::DocVisitor;
use crate::{try_err, try_none};
/// Write cross-crate information files, static files, invocation-specific files, etc. to disk
pub(crate) fn write_shared(
cx: &mut Context<'_>,
krate: &Crate,
@ -70,13 +69,14 @@ pub(crate) fn write_shared(
let SerializedSearchIndex { index, desc } =
build_index(&krate, &mut Rc::get_mut(&mut cx.shared).unwrap().cache, tcx);
write_search_desc(cx, &krate, &desc)?; // does not need to be merged; written unconditionally
write_search_desc(cx, &krate, &desc)?; // does not need to be merged
let crate_name = krate.name(cx.tcx());
let crate_name = crate_name.as_str(); // rand
let crate_name_json = OrderedJson::serialize(crate_name).unwrap(); // "rand"
let external_crates = hack_get_external_crate_names(&cx.dst, &cx.shared.resource_suffix)?;
let info = CrateInfo {
version: CrateInfoVersion::V1,
src_files_js: SourcesPart::get(cx, &crate_name_json)?,
search_index_js: SearchIndexPart::get(index, &cx.shared.resource_suffix)?,
all_crates: AllCratesPart::get(crate_name_json.clone(), &cx.shared.resource_suffix)?,
@ -85,47 +85,103 @@ pub(crate) fn write_shared(
type_impl: TypeAliasPart::get(cx, krate, &crate_name_json)?,
};
let crates = vec![info]; // we have info from just one crate. rest will found in out dir
write_static_files(cx, &opt)?;
let dst = &cx.dst;
if opt.emit.is_empty() || opt.emit.contains(&EmitType::InvocationSpecific) {
if cx.include_sources {
write_rendered_cci::<SourcesPart, _>(SourcesPart::blank, dst, &crates)?;
}
write_rendered_cci::<SearchIndexPart, _>(SearchIndexPart::blank, dst, &crates)?;
write_rendered_cci::<AllCratesPart, _>(AllCratesPart::blank, dst, &crates)?;
if let Some(parts_out_dir) = &opt.parts_out_dir {
create_parents(&parts_out_dir.0)?;
try_err!(
fs::write(&parts_out_dir.0, serde_json::to_string(&info).unwrap()),
&parts_out_dir.0
);
}
write_rendered_cci::<TraitAliasPart, _>(TraitAliasPart::blank, dst, &crates)?;
write_rendered_cci::<TypeAliasPart, _>(TypeAliasPart::blank, dst, &crates)?;
match &opt.index_page {
Some(index_page) if opt.enable_index_page => {
let mut md_opts = opt.clone();
md_opts.output = cx.dst.clone();
md_opts.external_html = cx.shared.layout.external_html.clone();
try_err!(
crate::markdown::render(&index_page, md_opts, cx.shared.edition()),
&index_page
);
let mut crates = CrateInfo::read_many(&opt.include_parts_dir)?;
crates.push(info);
if opt.should_merge.write_rendered_cci {
write_not_crate_specific(
&crates,
&cx.dst,
opt,
&cx.shared.style_files,
cx.shared.layout.css_file_extension.as_deref(),
&cx.shared.resource_suffix,
cx.include_sources,
)?;
match &opt.index_page {
Some(index_page) if opt.enable_index_page => {
let mut md_opts = opt.clone();
md_opts.output = cx.dst.clone();
md_opts.external_html = cx.shared.layout.external_html.clone();
try_err!(
crate::markdown::render(&index_page, md_opts, cx.shared.edition()),
&index_page
);
}
None if opt.enable_index_page => {
write_rendered_cci::<CratesIndexPart, _>(
|| CratesIndexPart::blank(cx),
&cx.dst,
&crates,
&opt.should_merge,
)?;
}
_ => {} // they don't want an index page
}
None if opt.enable_index_page => {
write_rendered_cci::<CratesIndexPart, _>(|| CratesIndexPart::blank(cx), dst, &crates)?;
}
_ => {} // they don't want an index page
}
Rc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(false);
Ok(())
}
/// Writes the static files, the style files, and the css extensions
fn write_static_files(cx: &mut Context<'_>, options: &RenderOptions) -> Result<(), Error> {
let static_dir = cx.dst.join("static.files");
/// Writes files that are written directly to the `--out-dir`, without the prefix from the current
/// crate. These are the rendered cross-crate files that encode info from multiple crates (e.g.
/// search index), and the static files.
pub(crate) fn write_not_crate_specific(
crates: &[CrateInfo],
dst: &Path,
opt: &RenderOptions,
style_files: &[StylePath],
css_file_extension: Option<&Path>,
resource_suffix: &str,
include_sources: bool,
) -> Result<(), Error> {
write_rendered_cross_crate_info(crates, dst, opt, include_sources)?;
write_static_files(dst, opt, style_files, css_file_extension, resource_suffix)?;
Ok(())
}
cx.shared.fs.create_dir_all(&static_dir).map_err(|e| PathError::new(e, "static.files"))?;
fn write_rendered_cross_crate_info(
crates: &[CrateInfo],
dst: &Path,
opt: &RenderOptions,
include_sources: bool,
) -> Result<(), Error> {
let m = &opt.should_merge;
if opt.emit.is_empty() || opt.emit.contains(&EmitType::InvocationSpecific) {
if include_sources {
write_rendered_cci::<SourcesPart, _>(SourcesPart::blank, dst, &crates, m)?;
}
write_rendered_cci::<SearchIndexPart, _>(SearchIndexPart::blank, dst, &crates, m)?;
write_rendered_cci::<AllCratesPart, _>(AllCratesPart::blank, dst, &crates, m)?;
}
write_rendered_cci::<TraitAliasPart, _>(TraitAliasPart::blank, dst, &crates, m)?;
write_rendered_cci::<TypeAliasPart, _>(TypeAliasPart::blank, dst, &crates, m)?;
Ok(())
}
/// Writes the static files, the style files, and the css extensions.
/// Have to be careful about these, because they write to the root out dir.
fn write_static_files(
dst: &Path,
opt: &RenderOptions,
style_files: &[StylePath],
css_file_extension: Option<&Path>,
resource_suffix: &str,
) -> Result<(), Error> {
let static_dir = dst.join("static.files");
try_err!(fs::create_dir_all(&static_dir), &static_dir);
// Handle added third-party themes
for entry in &cx.shared.style_files {
for entry in style_files {
let theme = entry.basename()?;
let extension =
try_none!(try_none!(entry.path.extension(), &entry.path).to_str(), &entry.path);
@ -136,22 +192,24 @@ fn write_static_files(cx: &mut Context<'_>, options: &RenderOptions) -> Result<(
}
let bytes = try_err!(fs::read(&entry.path), &entry.path);
let filename = format!("{theme}{suffix}.{extension}", suffix = cx.shared.resource_suffix);
cx.shared.fs.write(cx.dst.join(filename), bytes)?;
let filename = format!("{theme}{resource_suffix}.{extension}");
let dst_filename = dst.join(filename);
try_err!(fs::write(&dst_filename, bytes), &dst_filename);
}
// When the user adds their own CSS files with --extend-css, we write that as an
// invocation-specific file (that is, with a resource suffix).
if let Some(ref css) = cx.shared.layout.css_file_extension {
if let Some(css) = css_file_extension {
let buffer = try_err!(fs::read_to_string(css), css);
let path = static_files::suffix_path("theme.css", &cx.shared.resource_suffix);
cx.shared.fs.write(cx.dst.join(path), buffer)?;
let path = static_files::suffix_path("theme.css", resource_suffix);
let dst_path = dst.join(path);
try_err!(fs::write(&dst_path, buffer), &dst_path);
}
if options.emit.is_empty() || options.emit.contains(&EmitType::Toolchain) {
if opt.emit.is_empty() || opt.emit.contains(&EmitType::Toolchain) {
static_files::for_each(|f: &static_files::StaticFile| {
let filename = static_dir.join(f.output_filename());
cx.shared.fs.write(filename, f.minified())
fs::write(&filename, f.minified()).map_err(|e| PathError::new(e, &filename))
})?;
}
@ -186,7 +244,8 @@ fn write_search_desc(
/// Contains pre-rendered contents to insert into the CCI template
#[derive(Serialize, Deserialize, Clone, Debug)]
struct CrateInfo {
pub(crate) struct CrateInfo {
version: CrateInfoVersion,
src_files_js: PartsAndLocations<SourcesPart>,
search_index_js: PartsAndLocations<SearchIndexPart>,
all_crates: PartsAndLocations<AllCratesPart>,
@ -195,6 +254,33 @@ struct CrateInfo {
type_impl: PartsAndLocations<TypeAliasPart>,
}
impl CrateInfo {
/// Read all of the crate info from its location on the filesystem
pub(crate) fn read_many(parts_paths: &[PathToParts]) -> Result<Vec<Self>, Error> {
parts_paths
.iter()
.map(|parts_path| {
let path = &parts_path.0;
let parts = try_err!(fs::read(&path), &path);
let parts: CrateInfo = try_err!(serde_json::from_slice(&parts), &path);
Ok::<_, Error>(parts)
})
.collect::<Result<Vec<CrateInfo>, Error>>()
}
}
/// Version for the format of the crate-info file.
///
/// This enum should only ever have one variant, representing the current version.
/// Gives pretty good error message about expecting the current version on deserialize.
///
/// Must be incremented (V2, V3, etc.) upon any changes to the search index or CrateInfo,
/// to provide better diagnostics about including an invalid file.
#[derive(Serialize, Deserialize, Clone, Debug)]
enum CrateInfoVersion {
V1,
}
/// Paths (relative to the doc root) and their pre-merge contents
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(transparent)]
@ -900,10 +986,14 @@ fn create_parents(path: &Path) -> Result<(), Error> {
fn read_template_or_blank<F, T: FileFormat>(
mut make_blank: F,
path: &Path,
should_merge: &ShouldMerge,
) -> Result<SortedTemplate<T>, Error>
where
F: FnMut() -> SortedTemplate<T>,
{
if !should_merge.read_rendered_cci {
return Ok(make_blank());
}
match fs::read_to_string(&path) {
Ok(template) => Ok(try_err!(SortedTemplate::from_str(&template), &path)),
Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(make_blank()),
@ -916,6 +1006,7 @@ fn write_rendered_cci<T: CciPart, F>(
mut make_blank: F,
dst: &Path,
crates_info: &[CrateInfo],
should_merge: &ShouldMerge,
) -> Result<(), Error>
where
F: FnMut() -> SortedTemplate<T::FileFormat>,
@ -924,7 +1015,8 @@ where
for (path, parts) in get_path_parts::<T>(dst, crates_info) {
create_parents(&path)?;
// read previous rendered cci from storage, append to them
let mut template = read_template_or_blank::<_, T::FileFormat>(&mut make_blank, &path)?;
let mut template =
read_template_or_blank::<_, T::FileFormat>(&mut make_blank, &path, should_merge)?;
for part in parts {
template.append(part);
}

View file

@ -1,3 +1,4 @@
use crate::config::ShouldMerge;
use crate::html::render::ordered_json::{EscapedJson, OrderedJson};
use crate::html::render::sorted_template::{Html, SortedTemplate};
use crate::html::render::write_shared::*;
@ -192,16 +193,17 @@ fn read_template_test() {
let path = path.path().join("file.html");
let make_blank = || SortedTemplate::<Html>::from_before_after("<div>", "</div>");
let template = read_template_or_blank(make_blank, &path).unwrap();
let should_merge = ShouldMerge { read_rendered_cci: true, write_rendered_cci: true };
let template = read_template_or_blank(make_blank, &path, &should_merge).unwrap();
assert_eq!(but_last_line(&template.to_string()), "<div></div>");
fs::write(&path, template.to_string()).unwrap();
let mut template = read_template_or_blank(make_blank, &path).unwrap();
let mut template = read_template_or_blank(make_blank, &path, &should_merge).unwrap();
template.append("<img/>".to_string());
fs::write(&path, template.to_string()).unwrap();
let mut template = read_template_or_blank(make_blank, &path).unwrap();
let mut template = read_template_or_blank(make_blank, &path, &should_merge).unwrap();
template.append("<br/>".to_string());
fs::write(&path, template.to_string()).unwrap();
let template = read_template_or_blank(make_blank, &path).unwrap();
let template = read_template_or_blank(make_blank, &path, &should_merge).unwrap();
assert_eq!(but_last_line(&template.to_string()), "<div><br/><img/></div>");
}

View file

@ -603,6 +603,33 @@ fn opts() -> Vec<RustcOptGroup> {
"path to function call information (for displaying examples in the documentation)",
)
}),
unstable("merge", |o| {
o.optopt(
"",
"merge",
"Controls how rustdoc handles files from previously documented crates in the doc root
none = Do not write cross-crate information to the --out-dir
shared = Append current crate's info to files found in the --out-dir
finalize = Write current crate's info and --include-parts-dir info to the --out-dir, overwriting conflicting files",
"none|shared|finalize",
)
}),
unstable("parts-out-dir", |o| {
o.optopt(
"",
"parts-out-dir",
"Writes trait implementations and other info for the current crate to provided path. Only use with --merge=none",
"path/to/doc.parts/<crate-name>",
)
}),
unstable("include-parts-dir", |o| {
o.optmulti(
"",
"include-parts-dir",
"Includes trait implementations and other crate info from provided path. Only use with --merge=finalize",
"path/to/doc.parts/<crate-name>",
)
}),
// deprecated / removed options
unstable("disable-minification", |o| o.optflagmulti("", "disable-minification", "removed")),
stable("plugin-path", |o| {
@ -697,6 +724,32 @@ fn run_renderer<'tcx, T: formats::FormatRenderer<'tcx>>(
}
}
/// Renders and writes cross-crate info files, like the search index. This function exists so that
/// we can run rustdoc without a crate root in the `--merge=finalize` mode. Cross-crate info files
/// discovered via `--include-parts-dir` are combined and written to the doc root.
fn run_merge_finalize(opt: config::RenderOptions) -> Result<(), error::Error> {
assert!(
opt.should_merge.write_rendered_cci,
"config.rs only allows us to return InputMode::NoInputMergeFinalize if --merge=finalize"
);
assert!(
!opt.should_merge.read_rendered_cci,
"config.rs only allows us to return InputMode::NoInputMergeFinalize if --merge=finalize"
);
let crates = html::render::CrateInfo::read_many(&opt.include_parts_dir)?;
let include_sources = !opt.html_no_source;
html::render::write_not_crate_specific(
&crates,
&opt.output,
&opt,
&opt.themes,
opt.extension_css.as_deref(),
&opt.resource_suffix,
include_sources,
)?;
Ok(())
}
fn main_args(
early_dcx: &mut EarlyDiagCtxt,
at_args: &[String],
@ -727,22 +780,35 @@ fn main_args(
// Note that we discard any distinction between different non-zero exit
// codes from `from_matches` here.
let (options, render_options) = match config::Options::from_matches(early_dcx, &matches, args) {
Some(opts) => opts,
None => return Ok(()),
};
let (input, options, render_options) =
match config::Options::from_matches(early_dcx, &matches, args) {
Some(opts) => opts,
None => return Ok(()),
};
let dcx =
core::new_dcx(options.error_format, None, options.diagnostic_width, &options.unstable_opts);
let dcx = dcx.handle();
match (options.should_test, options.markdown_input()) {
(true, Some(_)) => return wrap_return(dcx, doctest::test_markdown(options)),
(true, None) => return doctest::run(dcx, options),
(false, Some(input)) => {
let input = input.to_owned();
let input = match input {
config::InputMode::HasFile(input) => input,
config::InputMode::NoInputMergeFinalize => {
return wrap_return(
dcx,
run_merge_finalize(render_options)
.map_err(|e| format!("could not write merged cross-crate info: {e}")),
);
}
};
match (options.should_test, config::markdown_input(&input)) {
(true, Some(_)) => return wrap_return(dcx, doctest::test_markdown(&input, options)),
(true, None) => return doctest::run(dcx, input, options),
(false, Some(md_input)) => {
let md_input = md_input.to_owned();
let edition = options.edition;
let config = core::create_config(options, &render_options, using_internal_features);
let config =
core::create_config(input, options, &render_options, using_internal_features);
// `markdown::render` can invoke `doctest::make_test`, which
// requires session globals and a thread pool, so we use
@ -750,7 +816,7 @@ fn main_args(
return wrap_return(
dcx,
interface::run_compiler(config, |_compiler| {
markdown::render(&input, render_options, edition)
markdown::render(&md_input, render_options, edition)
}),
);
}
@ -775,7 +841,7 @@ fn main_args(
let scrape_examples_options = options.scrape_examples_options.clone();
let bin_crate = options.bin_crate;
let config = core::create_config(options, &render_options, using_internal_features);
let config = core::create_config(input, options, &render_options, using_internal_features);
interface::run_compiler(config, |compiler| {
let sess = &compiler.sess;

View file

@ -27,12 +27,33 @@ pub(crate) fn run_lints(krate: Crate, cx: &mut DocContext<'_>) -> Crate {
impl<'a, 'tcx> DocVisitor for Linter<'a, 'tcx> {
fn visit_item(&mut self, item: &Item) {
bare_urls::visit_item(self.cx, item);
check_code_block_syntax::visit_item(self.cx, item);
html_tags::visit_item(self.cx, item);
unescaped_backticks::visit_item(self.cx, item);
redundant_explicit_links::visit_item(self.cx, item);
unportable_markdown::visit_item(self.cx, item);
let Some(hir_id) = DocContext::as_local_hir_id(self.cx.tcx, item.item_id) else {
// If non-local, no need to check anything.
return;
};
let dox = item.doc_value();
if !dox.is_empty() {
let may_have_link = dox.contains(&[':', '['][..]);
let may_have_block_comment_or_html = dox.contains(&['<', '>']);
// ~~~rust
// // This is a real, supported commonmark syntax for block code
// ~~~
let may_have_code = dox.contains(&['~', '`', '\t'][..]) || dox.contains(" ");
if may_have_link {
bare_urls::visit_item(self.cx, item, hir_id, &dox);
redundant_explicit_links::visit_item(self.cx, item, hir_id);
}
if may_have_code {
check_code_block_syntax::visit_item(self.cx, item, &dox);
unescaped_backticks::visit_item(self.cx, item, hir_id, &dox);
}
if may_have_block_comment_or_html {
html_tags::visit_item(self.cx, item, hir_id, &dox);
unportable_markdown::visit_item(self.cx, item, hir_id, &dox);
} else if may_have_link {
unportable_markdown::visit_item(self.cx, item, hir_id, &dox);
}
}
self.visit_item_recur(item)
}

View file

@ -8,6 +8,7 @@ use std::sync::LazyLock;
use pulldown_cmark::{Event, Parser, Tag};
use regex::Regex;
use rustc_errors::Applicability;
use rustc_hir::HirId;
use rustc_resolve::rustdoc::source_span_for_markdown_range;
use tracing::trace;
@ -15,50 +16,43 @@ use crate::clean::*;
use crate::core::DocContext;
use crate::html::markdown::main_body_opts;
pub(super) fn visit_item(cx: &DocContext<'_>, item: &Item) {
let Some(hir_id) = DocContext::as_local_hir_id(cx.tcx, item.item_id) else {
// If non-local, no need to check anything.
return;
pub(super) fn visit_item(cx: &DocContext<'_>, item: &Item, hir_id: HirId, dox: &str) {
let report_diag = |cx: &DocContext<'_>, msg: &'static str, range: Range<usize>| {
let sp = source_span_for_markdown_range(cx.tcx, &dox, &range, &item.attrs.doc_strings)
.unwrap_or_else(|| item.attr_span(cx.tcx));
cx.tcx.node_span_lint(crate::lint::BARE_URLS, hir_id, sp, |lint| {
lint.primary_message(msg)
.note("bare URLs are not automatically turned into clickable links")
.multipart_suggestion(
"use an automatic link instead",
vec![
(sp.shrink_to_lo(), "<".to_string()),
(sp.shrink_to_hi(), ">".to_string()),
],
Applicability::MachineApplicable,
);
});
};
let dox = item.doc_value();
if !dox.is_empty() {
let report_diag = |cx: &DocContext<'_>, msg: &'static str, range: Range<usize>| {
let sp = source_span_for_markdown_range(cx.tcx, &dox, &range, &item.attrs.doc_strings)
.unwrap_or_else(|| item.attr_span(cx.tcx));
cx.tcx.node_span_lint(crate::lint::BARE_URLS, hir_id, sp, |lint| {
lint.primary_message(msg)
.note("bare URLs are not automatically turned into clickable links")
.multipart_suggestion(
"use an automatic link instead",
vec![
(sp.shrink_to_lo(), "<".to_string()),
(sp.shrink_to_hi(), ">".to_string()),
],
Applicability::MachineApplicable,
);
});
};
let mut p = Parser::new_ext(&dox, main_body_opts()).into_offset_iter();
let mut p = Parser::new_ext(&dox, main_body_opts()).into_offset_iter();
while let Some((event, range)) = p.next() {
match event {
Event::Text(s) => find_raw_urls(cx, &s, range, &report_diag),
// We don't want to check the text inside code blocks or links.
Event::Start(tag @ (Tag::CodeBlock(_) | Tag::Link { .. })) => {
while let Some((event, _)) = p.next() {
match event {
Event::End(end)
if mem::discriminant(&end) == mem::discriminant(&tag.to_end()) =>
{
break;
}
_ => {}
while let Some((event, range)) = p.next() {
match event {
Event::Text(s) => find_raw_urls(cx, &s, range, &report_diag),
// We don't want to check the text inside code blocks or links.
Event::Start(tag @ (Tag::CodeBlock(_) | Tag::Link { .. })) => {
while let Some((event, _)) = p.next() {
match event {
Event::End(end)
if mem::discriminant(&end) == mem::discriminant(&tag.to_end()) =>
{
break;
}
_ => {}
}
}
_ => {}
}
_ => {}
}
}
}

View file

@ -15,10 +15,8 @@ use crate::clean;
use crate::core::DocContext;
use crate::html::markdown::{self, RustCodeBlock};
pub(crate) fn visit_item(cx: &DocContext<'_>, item: &clean::Item) {
if let Some(def_id) = item.item_id.as_local_def_id()
&& let Some(dox) = &item.opt_doc_value()
{
pub(crate) fn visit_item(cx: &DocContext<'_>, item: &clean::Item, dox: &str) {
if let Some(def_id) = item.item_id.as_local_def_id() {
let sp = item.attr_span(cx.tcx);
let extra = crate::html::markdown::ExtraInfo::new(cx.tcx, def_id, sp);
for code_block in markdown::rust_code_blocks(dox, &extra) {

View file

@ -5,159 +5,149 @@ use std::ops::Range;
use std::str::CharIndices;
use pulldown_cmark::{BrokenLink, Event, LinkType, Parser, Tag, TagEnd};
use rustc_hir::HirId;
use rustc_resolve::rustdoc::source_span_for_markdown_range;
use crate::clean::*;
use crate::core::DocContext;
use crate::html::markdown::main_body_opts;
pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item) {
pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item, hir_id: HirId, dox: &str) {
let tcx = cx.tcx;
let Some(hir_id) = DocContext::as_local_hir_id(tcx, item.item_id)
// If non-local, no need to check anything.
else {
return;
};
let dox = item.doc_value();
if !dox.is_empty() {
let report_diag = |msg: String, range: &Range<usize>, is_open_tag: bool| {
let sp = match source_span_for_markdown_range(tcx, &dox, range, &item.attrs.doc_strings)
let report_diag = |msg: String, range: &Range<usize>, is_open_tag: bool| {
let sp = match source_span_for_markdown_range(tcx, &dox, range, &item.attrs.doc_strings) {
Some(sp) => sp,
None => item.attr_span(tcx),
};
tcx.node_span_lint(crate::lint::INVALID_HTML_TAGS, hir_id, sp, |lint| {
use rustc_lint_defs::Applicability;
lint.primary_message(msg);
// If a tag looks like `<this>`, it might actually be a generic.
// We don't try to detect stuff `<like, this>` because that's not valid HTML,
// and we don't try to detect stuff `<like this>` because that's not valid Rust.
let mut generics_end = range.end;
if let Some(Some(mut generics_start)) = (is_open_tag
&& dox[..generics_end].ends_with('>'))
.then(|| extract_path_backwards(&dox, range.start))
{
Some(sp) => sp,
None => item.attr_span(tcx),
};
tcx.node_span_lint(crate::lint::INVALID_HTML_TAGS, hir_id, sp, |lint| {
use rustc_lint_defs::Applicability;
lint.primary_message(msg);
// If a tag looks like `<this>`, it might actually be a generic.
// We don't try to detect stuff `<like, this>` because that's not valid HTML,
// and we don't try to detect stuff `<like this>` because that's not valid Rust.
let mut generics_end = range.end;
if let Some(Some(mut generics_start)) = (is_open_tag
&& dox[..generics_end].ends_with('>'))
.then(|| extract_path_backwards(&dox, range.start))
while generics_start != 0
&& generics_end < dox.len()
&& dox.as_bytes()[generics_start - 1] == b'<'
&& dox.as_bytes()[generics_end] == b'>'
{
while generics_start != 0
&& generics_end < dox.len()
&& dox.as_bytes()[generics_start - 1] == b'<'
&& dox.as_bytes()[generics_end] == b'>'
{
generics_end += 1;
generics_start -= 1;
if let Some(new_start) = extract_path_backwards(&dox, generics_start) {
generics_start = new_start;
}
if let Some(new_end) = extract_path_forward(&dox, generics_end) {
generics_end = new_end;
}
generics_end += 1;
generics_start -= 1;
if let Some(new_start) = extract_path_backwards(&dox, generics_start) {
generics_start = new_start;
}
if let Some(new_end) = extract_path_forward(&dox, generics_end) {
generics_end = new_end;
}
let generics_sp = match source_span_for_markdown_range(
tcx,
&dox,
&(generics_start..generics_end),
&item.attrs.doc_strings,
) {
Some(sp) => sp,
None => item.attr_span(tcx),
};
// Sometimes, we only extract part of a path. For example, consider this:
//
// <[u32] as IntoIter<u32>>::Item
// ^^^^^ unclosed HTML tag `u32`
//
// We don't have any code for parsing fully-qualified trait paths.
// In theory, we could add it, but doing it correctly would require
// parsing the entire path grammar, which is problematic because of
// overlap between the path grammar and Markdown.
//
// The example above shows that ambiguity. Is `[u32]` intended to be an
// intra-doc link to the u32 primitive, or is it intended to be a slice?
//
// If the below conditional were removed, we would suggest this, which is
// not what the user probably wants.
//
// <[u32] as `IntoIter<u32>`>::Item
//
// We know that the user actually wants to wrap the whole thing in a code
// block, but the only reason we know that is because `u32` does not, in
// fact, implement IntoIter. If the example looks like this:
//
// <[Vec<i32>] as IntoIter<i32>::Item
//
// The ideal fix would be significantly different.
if (generics_start > 0 && dox.as_bytes()[generics_start - 1] == b'<')
|| (generics_end < dox.len() && dox.as_bytes()[generics_end] == b'>')
{
return;
}
// multipart form is chosen here because ``Vec<i32>`` would be confusing.
lint.multipart_suggestion(
"try marking as source code",
vec![
(generics_sp.shrink_to_lo(), String::from("`")),
(generics_sp.shrink_to_hi(), String::from("`")),
],
Applicability::MaybeIncorrect,
);
}
});
};
let mut tags = Vec::new();
let mut is_in_comment = None;
let mut in_code_block = false;
let link_names = item.link_names(&cx.cache);
let mut replacer = |broken_link: BrokenLink<'_>| {
if let Some(link) =
link_names.iter().find(|link| *link.original_text == *broken_link.reference)
{
Some((link.href.as_str().into(), link.new_text.to_string().into()))
} else if matches!(
&broken_link.link_type,
LinkType::Reference | LinkType::ReferenceUnknown
) {
// If the link is shaped [like][this], suppress any broken HTML in the [this] part.
// The `broken_intra_doc_links` will report typos in there anyway.
Some((
broken_link.reference.to_string().into(),
broken_link.reference.to_string().into(),
))
} else {
None
}
};
let p = Parser::new_with_broken_link_callback(&dox, main_body_opts(), Some(&mut replacer))
.into_offset_iter();
for (event, range) in p {
match event {
Event::Start(Tag::CodeBlock(_)) => in_code_block = true,
Event::Html(text) | Event::InlineHtml(text) if !in_code_block => {
extract_tags(&mut tags, &text, range, &mut is_in_comment, &report_diag)
if let Some(new_end) = extract_path_forward(&dox, generics_end) {
generics_end = new_end;
}
Event::End(TagEnd::CodeBlock) => in_code_block = false,
_ => {}
let generics_sp = match source_span_for_markdown_range(
tcx,
&dox,
&(generics_start..generics_end),
&item.attrs.doc_strings,
) {
Some(sp) => sp,
None => item.attr_span(tcx),
};
// Sometimes, we only extract part of a path. For example, consider this:
//
// <[u32] as IntoIter<u32>>::Item
// ^^^^^ unclosed HTML tag `u32`
//
// We don't have any code for parsing fully-qualified trait paths.
// In theory, we could add it, but doing it correctly would require
// parsing the entire path grammar, which is problematic because of
// overlap between the path grammar and Markdown.
//
// The example above shows that ambiguity. Is `[u32]` intended to be an
// intra-doc link to the u32 primitive, or is it intended to be a slice?
//
// If the below conditional were removed, we would suggest this, which is
// not what the user probably wants.
//
// <[u32] as `IntoIter<u32>`>::Item
//
// We know that the user actually wants to wrap the whole thing in a code
// block, but the only reason we know that is because `u32` does not, in
// fact, implement IntoIter. If the example looks like this:
//
// <[Vec<i32>] as IntoIter<i32>::Item
//
// The ideal fix would be significantly different.
if (generics_start > 0 && dox.as_bytes()[generics_start - 1] == b'<')
|| (generics_end < dox.len() && dox.as_bytes()[generics_end] == b'>')
{
return;
}
// multipart form is chosen here because ``Vec<i32>`` would be confusing.
lint.multipart_suggestion(
"try marking as source code",
vec![
(generics_sp.shrink_to_lo(), String::from("`")),
(generics_sp.shrink_to_hi(), String::from("`")),
],
Applicability::MaybeIncorrect,
);
}
}
});
};
for (tag, range) in tags.iter().filter(|(t, _)| {
let t = t.to_lowercase();
!ALLOWED_UNCLOSED.contains(&t.as_str())
}) {
report_diag(format!("unclosed HTML tag `{tag}`"), range, true);
}
let mut tags = Vec::new();
let mut is_in_comment = None;
let mut in_code_block = false;
if let Some(range) = is_in_comment {
report_diag("Unclosed HTML comment".to_string(), &range, false);
let link_names = item.link_names(&cx.cache);
let mut replacer = |broken_link: BrokenLink<'_>| {
if let Some(link) =
link_names.iter().find(|link| *link.original_text == *broken_link.reference)
{
Some((link.href.as_str().into(), link.new_text.to_string().into()))
} else if matches!(&broken_link.link_type, LinkType::Reference | LinkType::ReferenceUnknown)
{
// If the link is shaped [like][this], suppress any broken HTML in the [this] part.
// The `broken_intra_doc_links` will report typos in there anyway.
Some((
broken_link.reference.to_string().into(),
broken_link.reference.to_string().into(),
))
} else {
None
}
};
let p = Parser::new_with_broken_link_callback(&dox, main_body_opts(), Some(&mut replacer))
.into_offset_iter();
for (event, range) in p {
match event {
Event::Start(Tag::CodeBlock(_)) => in_code_block = true,
Event::Html(text) | Event::InlineHtml(text) if !in_code_block => {
extract_tags(&mut tags, &text, range, &mut is_in_comment, &report_diag)
}
Event::End(TagEnd::CodeBlock) => in_code_block = false,
_ => {}
}
}
for (tag, range) in tags.iter().filter(|(t, _)| {
let t = t.to_lowercase();
!ALLOWED_UNCLOSED.contains(&t.as_str())
}) {
report_diag(format!("unclosed HTML tag `{tag}`"), range, true);
}
if let Some(range) = is_in_comment {
report_diag("Unclosed HTML comment".to_string(), &range, false);
}
}

View file

@ -24,12 +24,7 @@ struct LinkData {
display_link: String,
}
pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item) {
let Some(hir_id) = DocContext::as_local_hir_id(cx.tcx, item.item_id) else {
// If non-local, no need to check anything.
return;
};
pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item, hir_id: HirId) {
let hunks = prepare_to_doc_link_resolution(&item.attrs.doc_strings);
for (item_id, doc) in hunks {
if let Some(item_id) = item_id.or(item.def_id())

View file

@ -4,6 +4,7 @@ use std::ops::Range;
use pulldown_cmark::{BrokenLink, Event, Parser};
use rustc_errors::Diag;
use rustc_hir::HirId;
use rustc_lint_defs::Applicability;
use rustc_resolve::rustdoc::source_span_for_markdown_range;
@ -11,17 +12,8 @@ use crate::clean::Item;
use crate::core::DocContext;
use crate::html::markdown::main_body_opts;
pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item) {
pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item, hir_id: HirId, dox: &str) {
let tcx = cx.tcx;
let Some(hir_id) = DocContext::as_local_hir_id(tcx, item.item_id) else {
// If non-local, no need to check anything.
return;
};
let dox = item.doc_value();
if dox.is_empty() {
return;
}
let link_names = item.link_names(&cx.cache);
let mut replacer = |broken_link: BrokenLink<'_>| {

View file

@ -12,6 +12,7 @@
use std::collections::{BTreeMap, BTreeSet};
use rustc_hir::HirId;
use rustc_lint_defs::Applicability;
use rustc_resolve::rustdoc::source_span_for_markdown_range;
use {pulldown_cmark as cmarkn, pulldown_cmark_old as cmarko};
@ -19,17 +20,8 @@ use {pulldown_cmark as cmarkn, pulldown_cmark_old as cmarko};
use crate::clean::Item;
use crate::core::DocContext;
pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item) {
pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item, hir_id: HirId, dox: &str) {
let tcx = cx.tcx;
let Some(hir_id) = DocContext::as_local_hir_id(tcx, item.item_id) else {
// If non-local, no need to check anything.
return;
};
let dox = item.doc_value();
if dox.is_empty() {
return;
}
// P1: unintended strikethrough was fixed by requiring single-tildes to flank
// the same way underscores do, so nothing is done here

View file

@ -4,12 +4,12 @@ use std::intrinsics::simd::simd_div;
#[repr(simd)]
#[allow(non_camel_case_types)]
struct i32x2(i32, i32);
struct i32x2([i32; 2]);
fn main() {
unsafe {
let x = i32x2(1, 1);
let y = i32x2(1, 0);
let x = i32x2([1, 1]);
let y = i32x2([1, 0]);
simd_div(x, y); //~ERROR: Undefined Behavior: dividing by zero
}
}

View file

@ -4,12 +4,12 @@ use std::intrinsics::simd::simd_div;
#[repr(simd)]
#[allow(non_camel_case_types)]
struct i32x2(i32, i32);
struct i32x2([i32; 2]);
fn main() {
unsafe {
let x = i32x2(1, i32::MIN);
let y = i32x2(1, -1);
let x = i32x2([1, i32::MIN]);
let y = i32x2([1, -1]);
simd_div(x, y); //~ERROR: Undefined Behavior: overflow in signed division
}
}

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