mir_build: check annotated functions w/out callers
This commit is contained in:
parent
ce602acfc2
commit
cc9a9ecccb
16 changed files with 223 additions and 98 deletions
|
@ -12,6 +12,7 @@ rustc_abi = { path = "../rustc_abi" }
|
|||
rustc_apfloat = "0.2.0"
|
||||
rustc_arena = { path = "../rustc_arena" }
|
||||
rustc_ast = { path = "../rustc_ast" }
|
||||
rustc_attr_parsing = { path = "../rustc_attr_parsing" }
|
||||
rustc_data_structures = { path = "../rustc_data_structures" }
|
||||
rustc_errors = { path = "../rustc_errors" }
|
||||
rustc_fluent_macro = { path = "../rustc_fluent_macro" }
|
||||
|
|
|
@ -118,6 +118,12 @@ mir_build_extern_static_requires_unsafe_unsafe_op_in_unsafe_fn_allowed =
|
|||
.note = extern statics are not controlled by the Rust type system: invalid data, aliasing violations or data races will cause undefined behavior
|
||||
.label = use of extern static
|
||||
|
||||
mir_build_force_inline =
|
||||
`{$callee}` is incompatible with `#[rustc_force_inline]`
|
||||
.attr = annotation here
|
||||
.callee = `{$callee}` defined here
|
||||
.note = incompatible due to: {$reason}
|
||||
|
||||
mir_build_inform_irrefutable = `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant
|
||||
|
||||
mir_build_initializing_type_with_requires_unsafe =
|
||||
|
|
|
@ -29,6 +29,7 @@ use rustc_span::{Span, Symbol, sym};
|
|||
use super::lints;
|
||||
use crate::builder::expr::as_place::PlaceBuilder;
|
||||
use crate::builder::scope::DropKind;
|
||||
use crate::check_inline;
|
||||
|
||||
pub(crate) fn closure_saved_names_of_captured_variables<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
|
@ -80,6 +81,7 @@ pub(crate) fn mir_build<'tcx>(tcx: TyCtxtAt<'tcx>, def: LocalDefId) -> Body<'tcx
|
|||
};
|
||||
|
||||
lints::check(tcx, &body);
|
||||
check_inline::check_force_inline(tcx, &body);
|
||||
|
||||
// The borrow checker will replace all the regions here with its own
|
||||
// inference variables. There's no point having non-erased regions here.
|
||||
|
|
81
compiler/rustc_mir_build/src/check_inline.rs
Normal file
81
compiler/rustc_mir_build/src/check_inline.rs
Normal file
|
@ -0,0 +1,81 @@
|
|||
use rustc_attr_parsing::InlineAttr;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
|
||||
use rustc_middle::mir::{Body, TerminatorKind};
|
||||
use rustc_middle::ty;
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
use rustc_span::sym;
|
||||
|
||||
/// Check that a body annotated with `#[rustc_force_inline]` will not fail to inline based on its
|
||||
/// definition alone (irrespective of any specific caller).
|
||||
pub(crate) fn check_force_inline<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>) {
|
||||
let def_id = body.source.def_id();
|
||||
if !tcx.hir().body_owner_kind(def_id).is_fn_or_closure() || !def_id.is_local() {
|
||||
return;
|
||||
}
|
||||
let InlineAttr::Force { attr_span, .. } = tcx.codegen_fn_attrs(def_id).inline else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Err(reason) =
|
||||
is_inline_valid_on_fn(tcx, def_id).and_then(|_| is_inline_valid_on_body(tcx, body))
|
||||
{
|
||||
tcx.dcx().emit_err(crate::errors::InvalidForceInline {
|
||||
attr_span,
|
||||
callee_span: tcx.def_span(def_id),
|
||||
callee: tcx.def_path_str(def_id),
|
||||
reason,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_inline_valid_on_fn<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> Result<(), &'static str> {
|
||||
let codegen_attrs = tcx.codegen_fn_attrs(def_id);
|
||||
if tcx.has_attr(def_id, sym::rustc_no_mir_inline) {
|
||||
return Err("#[rustc_no_mir_inline]");
|
||||
}
|
||||
|
||||
// FIXME(#127234): Coverage instrumentation currently doesn't handle inlined
|
||||
// MIR correctly when Modified Condition/Decision Coverage is enabled.
|
||||
if tcx.sess.instrument_coverage_mcdc() {
|
||||
return Err("incompatible with MC/DC coverage");
|
||||
}
|
||||
|
||||
let ty = tcx.type_of(def_id);
|
||||
if match ty.instantiate_identity().kind() {
|
||||
ty::FnDef(..) => tcx.fn_sig(def_id).instantiate_identity().c_variadic(),
|
||||
ty::Closure(_, args) => args.as_closure().sig().c_variadic(),
|
||||
_ => false,
|
||||
} {
|
||||
return Err("C variadic");
|
||||
}
|
||||
|
||||
if codegen_attrs.flags.contains(CodegenFnAttrFlags::COLD) {
|
||||
return Err("cold");
|
||||
}
|
||||
|
||||
// Intrinsic fallback bodies are automatically made cross-crate inlineable,
|
||||
// but at this stage we don't know whether codegen knows the intrinsic,
|
||||
// so just conservatively don't inline it. This also ensures that we do not
|
||||
// accidentally inline the body of an intrinsic that *must* be overridden.
|
||||
if tcx.has_attr(def_id, sym::rustc_intrinsic) {
|
||||
return Err("callee is an intrinsic");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn is_inline_valid_on_body<'tcx>(
|
||||
_: TyCtxt<'tcx>,
|
||||
body: &Body<'tcx>,
|
||||
) -> Result<(), &'static str> {
|
||||
if body
|
||||
.basic_blocks
|
||||
.iter()
|
||||
.any(|bb| matches!(bb.terminator().kind, TerminatorKind::TailCall { .. }))
|
||||
{
|
||||
return Err("can't inline functions with tail calls");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1107,3 +1107,15 @@ impl<'a> Subdiagnostic for Rust2024IncompatiblePatSugg<'a> {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Diagnostic)]
|
||||
#[diag(mir_build_force_inline)]
|
||||
#[note]
|
||||
pub(crate) struct InvalidForceInline {
|
||||
#[primary_span]
|
||||
pub attr_span: Span,
|
||||
#[label(mir_build_callee)]
|
||||
pub callee_span: Span,
|
||||
pub callee: String,
|
||||
pub reason: &'static str,
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
// "Go to file" feature to silently ignore all files in the module, probably
|
||||
// because it assumes that "build" is a build-output directory. See #134365.
|
||||
mod builder;
|
||||
pub mod check_inline;
|
||||
mod check_tail_calls;
|
||||
mod check_unsafety;
|
||||
mod errors;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue