Merge from rustc
This commit is contained in:
commit
19fa141a6d
270 changed files with 3040 additions and 2364 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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) {}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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>);
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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!
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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!
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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!
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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!
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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`).
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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(..) => {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)))),
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
13
compiler/rustc_mir_transform/src/post_drop_elaboration.rs
Normal file
13
compiler/rustc_mir_transform/src/post_drop_elaboration.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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]);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))),
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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, .. } => {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
///
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()) }
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
///
|
||||
|
|
|
|||
|
|
@ -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() {}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 hasn’t 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
|
||||
|
|
|
|||
|
|
@ -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::
|
||||
|
|
|
|||
|
|
@ -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)) => {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -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`"),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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<'_>| {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue