Skip to content

Commit cc9a9ec

Browse files
committed
mir_build: check annotated functions w/out callers
1 parent ce602ac commit cc9a9ec

File tree

16 files changed

+223
-98
lines changed

16 files changed

+223
-98
lines changed

Cargo.lock

+1
Original file line numberDiff line numberDiff line change
@@ -4158,6 +4158,7 @@ dependencies = [
41584158
"rustc_apfloat",
41594159
"rustc_arena",
41604160
"rustc_ast",
4161+
"rustc_attr_parsing",
41614162
"rustc_data_structures",
41624163
"rustc_errors",
41634164
"rustc_fluent_macro",

compiler/rustc_mir_build/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ rustc_abi = { path = "../rustc_abi" }
1212
rustc_apfloat = "0.2.0"
1313
rustc_arena = { path = "../rustc_arena" }
1414
rustc_ast = { path = "../rustc_ast" }
15+
rustc_attr_parsing = { path = "../rustc_attr_parsing" }
1516
rustc_data_structures = { path = "../rustc_data_structures" }
1617
rustc_errors = { path = "../rustc_errors" }
1718
rustc_fluent_macro = { path = "../rustc_fluent_macro" }

compiler/rustc_mir_build/messages.ftl

+6
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,12 @@ mir_build_extern_static_requires_unsafe_unsafe_op_in_unsafe_fn_allowed =
118118
.note = extern statics are not controlled by the Rust type system: invalid data, aliasing violations or data races will cause undefined behavior
119119
.label = use of extern static
120120
121+
mir_build_force_inline =
122+
`{$callee}` is incompatible with `#[rustc_force_inline]`
123+
.attr = annotation here
124+
.callee = `{$callee}` defined here
125+
.note = incompatible due to: {$reason}
126+
121127
mir_build_inform_irrefutable = `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant
122128
123129
mir_build_initializing_type_with_requires_unsafe =

compiler/rustc_mir_build/src/builder/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ use rustc_span::{Span, Symbol, sym};
2929
use super::lints;
3030
use crate::builder::expr::as_place::PlaceBuilder;
3131
use crate::builder::scope::DropKind;
32+
use crate::check_inline;
3233

3334
pub(crate) fn closure_saved_names_of_captured_variables<'tcx>(
3435
tcx: TyCtxt<'tcx>,
@@ -80,6 +81,7 @@ pub(crate) fn mir_build<'tcx>(tcx: TyCtxtAt<'tcx>, def: LocalDefId) -> Body<'tcx
8081
};
8182

8283
lints::check(tcx, &body);
84+
check_inline::check_force_inline(tcx, &body);
8385

8486
// The borrow checker will replace all the regions here with its own
8587
// inference variables. There's no point having non-erased regions here.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
use rustc_attr_parsing::InlineAttr;
2+
use rustc_hir::def_id::DefId;
3+
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
4+
use rustc_middle::mir::{Body, TerminatorKind};
5+
use rustc_middle::ty;
6+
use rustc_middle::ty::TyCtxt;
7+
use rustc_span::sym;
8+
9+
/// Check that a body annotated with `#[rustc_force_inline]` will not fail to inline based on its
10+
/// definition alone (irrespective of any specific caller).
11+
pub(crate) fn check_force_inline<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>) {
12+
let def_id = body.source.def_id();
13+
if !tcx.hir().body_owner_kind(def_id).is_fn_or_closure() || !def_id.is_local() {
14+
return;
15+
}
16+
let InlineAttr::Force { attr_span, .. } = tcx.codegen_fn_attrs(def_id).inline else {
17+
return;
18+
};
19+
20+
if let Err(reason) =
21+
is_inline_valid_on_fn(tcx, def_id).and_then(|_| is_inline_valid_on_body(tcx, body))
22+
{
23+
tcx.dcx().emit_err(crate::errors::InvalidForceInline {
24+
attr_span,
25+
callee_span: tcx.def_span(def_id),
26+
callee: tcx.def_path_str(def_id),
27+
reason,
28+
});
29+
}
30+
}
31+
32+
pub fn is_inline_valid_on_fn<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> Result<(), &'static str> {
33+
let codegen_attrs = tcx.codegen_fn_attrs(def_id);
34+
if tcx.has_attr(def_id, sym::rustc_no_mir_inline) {
35+
return Err("#[rustc_no_mir_inline]");
36+
}
37+
38+
// FIXME(#127234): Coverage instrumentation currently doesn't handle inlined
39+
// MIR correctly when Modified Condition/Decision Coverage is enabled.
40+
if tcx.sess.instrument_coverage_mcdc() {
41+
return Err("incompatible with MC/DC coverage");
42+
}
43+
44+
let ty = tcx.type_of(def_id);
45+
if match ty.instantiate_identity().kind() {
46+
ty::FnDef(..) => tcx.fn_sig(def_id).instantiate_identity().c_variadic(),
47+
ty::Closure(_, args) => args.as_closure().sig().c_variadic(),
48+
_ => false,
49+
} {
50+
return Err("C variadic");
51+
}
52+
53+
if codegen_attrs.flags.contains(CodegenFnAttrFlags::COLD) {
54+
return Err("cold");
55+
}
56+
57+
// Intrinsic fallback bodies are automatically made cross-crate inlineable,
58+
// but at this stage we don't know whether codegen knows the intrinsic,
59+
// so just conservatively don't inline it. This also ensures that we do not
60+
// accidentally inline the body of an intrinsic that *must* be overridden.
61+
if tcx.has_attr(def_id, sym::rustc_intrinsic) {
62+
return Err("callee is an intrinsic");
63+
}
64+
65+
Ok(())
66+
}
67+
68+
pub fn is_inline_valid_on_body<'tcx>(
69+
_: TyCtxt<'tcx>,
70+
body: &Body<'tcx>,
71+
) -> Result<(), &'static str> {
72+
if body
73+
.basic_blocks
74+
.iter()
75+
.any(|bb| matches!(bb.terminator().kind, TerminatorKind::TailCall { .. }))
76+
{
77+
return Err("can't inline functions with tail calls");
78+
}
79+
80+
Ok(())
81+
}

compiler/rustc_mir_build/src/errors.rs

+12
Original file line numberDiff line numberDiff line change
@@ -1107,3 +1107,15 @@ impl<'a> Subdiagnostic for Rust2024IncompatiblePatSugg<'a> {
11071107
);
11081108
}
11091109
}
1110+
1111+
#[derive(Diagnostic)]
1112+
#[diag(mir_build_force_inline)]
1113+
#[note]
1114+
pub(crate) struct InvalidForceInline {
1115+
#[primary_span]
1116+
pub attr_span: Span,
1117+
#[label(mir_build_callee)]
1118+
pub callee_span: Span,
1119+
pub callee: String,
1120+
pub reason: &'static str,
1121+
}

compiler/rustc_mir_build/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
// "Go to file" feature to silently ignore all files in the module, probably
1616
// because it assumes that "build" is a build-output directory. See #134365.
1717
mod builder;
18+
pub mod check_inline;
1819
mod check_tail_calls;
1920
mod check_unsafety;
2021
mod errors;

compiler/rustc_mir_transform/src/inline.rs

+7-45
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,12 @@ use rustc_hir::def_id::DefId;
1010
use rustc_index::Idx;
1111
use rustc_index::bit_set::BitSet;
1212
use rustc_middle::bug;
13-
use rustc_middle::middle::codegen_fn_attrs::{CodegenFnAttrFlags, CodegenFnAttrs};
13+
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrs;
1414
use rustc_middle::mir::visit::*;
1515
use rustc_middle::mir::*;
1616
use rustc_middle::ty::{self, Instance, InstanceKind, Ty, TyCtxt, TypeFlags, TypeVisitableExt};
1717
use rustc_session::config::{DebugInfo, OptLevel};
1818
use rustc_span::source_map::Spanned;
19-
use rustc_span::sym;
2019
use tracing::{debug, instrument, trace, trace_span};
2120

2221
use crate::cost_checker::CostChecker;
@@ -120,7 +119,6 @@ trait Inliner<'tcx> {
120119
callsite: &CallSite<'tcx>,
121120
callee_body: &Body<'tcx>,
122121
callee_attrs: &CodegenFnAttrs,
123-
cross_crate_inlinable: bool,
124122
) -> Result<(), &'static str>;
125123

126124
// How many callsites in a body are we allowed to inline? We need to limit this in order
@@ -196,7 +194,6 @@ impl<'tcx> Inliner<'tcx> for ForceInliner<'tcx> {
196194
_: &CallSite<'tcx>,
197195
callee_body: &Body<'tcx>,
198196
callee_attrs: &CodegenFnAttrs,
199-
_: bool,
200197
) -> Result<(), &'static str> {
201198
if callee_body.tainted_by_errors.is_some() {
202199
return Err("body has errors");
@@ -215,14 +212,6 @@ impl<'tcx> Inliner<'tcx> for ForceInliner<'tcx> {
215212
// inline-asm is detected. LLVM will still possibly do an inline later on
216213
// if the no-attribute function ends up with the same instruction set anyway.
217214
Err("cannot move inline-asm across instruction sets")
218-
} else if callee_body
219-
.basic_blocks
220-
.iter()
221-
.any(|bb| matches!(bb.terminator().kind, TerminatorKind::TailCall { .. }))
222-
{
223-
// FIXME(explicit_tail_calls): figure out how exactly functions containing tail
224-
// calls can be inlined (and if they even should)
225-
Err("can't inline functions with tail calls")
226215
} else {
227216
Ok(())
228217
}
@@ -348,7 +337,6 @@ impl<'tcx> Inliner<'tcx> for NormalInliner<'tcx> {
348337
callsite: &CallSite<'tcx>,
349338
callee_body: &Body<'tcx>,
350339
callee_attrs: &CodegenFnAttrs,
351-
cross_crate_inlinable: bool,
352340
) -> Result<(), &'static str> {
353341
let tcx = self.tcx();
354342

@@ -358,7 +346,7 @@ impl<'tcx> Inliner<'tcx> for NormalInliner<'tcx> {
358346

359347
let mut threshold = if self.caller_is_inline_forwarder {
360348
tcx.sess.opts.unstable_opts.inline_mir_forwarder_threshold.unwrap_or(30)
361-
} else if cross_crate_inlinable {
349+
} else if tcx.cross_crate_inlinable(callsite.callee.def_id()) {
362350
tcx.sess.opts.unstable_opts.inline_mir_hint_threshold.unwrap_or(100)
363351
} else {
364352
tcx.sess.opts.unstable_opts.inline_mir_threshold.unwrap_or(50)
@@ -587,16 +575,8 @@ fn try_inlining<'tcx, I: Inliner<'tcx>>(
587575
check_mir_is_available(inliner, caller_body, callsite.callee)?;
588576

589577
let callee_attrs = tcx.codegen_fn_attrs(callsite.callee.def_id());
590-
let cross_crate_inlinable = tcx.cross_crate_inlinable(callsite.callee.def_id());
591-
check_codegen_attributes(inliner, callsite, callee_attrs, cross_crate_inlinable)?;
592-
593-
// Intrinsic fallback bodies are automatically made cross-crate inlineable,
594-
// but at this stage we don't know whether codegen knows the intrinsic,
595-
// so just conservatively don't inline it. This also ensures that we do not
596-
// accidentally inline the body of an intrinsic that *must* be overridden.
597-
if tcx.has_attr(callsite.callee.def_id(), sym::rustc_intrinsic) {
598-
return Err("callee is an intrinsic");
599-
}
578+
rustc_mir_build::check_inline::is_inline_valid_on_fn(tcx, callsite.callee.def_id())?;
579+
check_codegen_attributes(inliner, callsite, callee_attrs)?;
600580

601581
let terminator = caller_body[callsite.block].terminator.as_ref().unwrap();
602582
let TerminatorKind::Call { args, destination, .. } = &terminator.kind else { bug!() };
@@ -610,7 +590,8 @@ fn try_inlining<'tcx, I: Inliner<'tcx>>(
610590
}
611591

612592
let callee_body = try_instance_mir(tcx, callsite.callee.def)?;
613-
inliner.check_callee_mir_body(callsite, callee_body, callee_attrs, cross_crate_inlinable)?;
593+
rustc_mir_build::check_inline::is_inline_valid_on_body(tcx, callee_body)?;
594+
inliner.check_callee_mir_body(callsite, callee_body, callee_attrs)?;
614595

615596
let Ok(callee_body) = callsite.callee.try_instantiate_mir_and_normalize_erasing_regions(
616597
tcx,
@@ -775,38 +756,19 @@ fn check_codegen_attributes<'tcx, I: Inliner<'tcx>>(
775756
inliner: &I,
776757
callsite: &CallSite<'tcx>,
777758
callee_attrs: &CodegenFnAttrs,
778-
cross_crate_inlinable: bool,
779759
) -> Result<(), &'static str> {
780760
let tcx = inliner.tcx();
781-
if tcx.has_attr(callsite.callee.def_id(), sym::rustc_no_mir_inline) {
782-
return Err("#[rustc_no_mir_inline]");
783-
}
784-
785761
if let InlineAttr::Never = callee_attrs.inline {
786762
return Err("never inline attribute");
787763
}
788764

789-
// FIXME(#127234): Coverage instrumentation currently doesn't handle inlined
790-
// MIR correctly when Modified Condition/Decision Coverage is enabled.
791-
if tcx.sess.instrument_coverage_mcdc() {
792-
return Err("incompatible with MC/DC coverage");
793-
}
794-
795765
// Reachability pass defines which functions are eligible for inlining. Generally inlining
796766
// other functions is incorrect because they could reference symbols that aren't exported.
797767
let is_generic = callsite.callee.args.non_erasable_generics().next().is_some();
798-
if !is_generic && !cross_crate_inlinable {
768+
if !is_generic && !tcx.cross_crate_inlinable(callsite.callee.def_id()) {
799769
return Err("not exported");
800770
}
801771

802-
if callsite.fn_sig.c_variadic() {
803-
return Err("C variadic");
804-
}
805-
806-
if callee_attrs.flags.contains(CodegenFnAttrFlags::COLD) {
807-
return Err("cold");
808-
}
809-
810772
let codegen_fn_attrs = tcx.codegen_fn_attrs(inliner.caller_def_id());
811773
if callee_attrs.no_sanitize != codegen_fn_attrs.no_sanitize {
812774
return Err("incompatible sanitizer set");

tests/ui/force-inlining/deny-async.rs

+2-3
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,17 @@
88

99
#[rustc_no_mir_inline]
1010
#[rustc_force_inline]
11+
//~^ ERROR `callee` is incompatible with `#[rustc_force_inline]`
1112
pub fn callee() {
1213
}
1314

1415
#[rustc_no_mir_inline]
1516
#[rustc_force_inline = "the test requires it"]
17+
//~^ ERROR `callee_justified` is incompatible with `#[rustc_force_inline]`
1618
pub fn callee_justified() {
1719
}
1820

1921
async fn async_caller() {
2022
callee();
21-
//~^ ERROR `callee` could not be inlined into `async_caller::{closure#0}` but is required to be inlined
22-
2323
callee_justified();
24-
//~^ ERROR `callee_justified` could not be inlined into `async_caller::{closure#0}` but is required to be inlined
2524
}
+16-11
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,24 @@
1-
error: `callee` could not be inlined into `async_caller::{closure#0}` but is required to be inlined
2-
--> $DIR/deny-async.rs:20:5
1+
error: `callee` is incompatible with `#[rustc_force_inline]`
2+
--> $DIR/deny-async.rs:10:1
33
|
4-
LL | callee();
5-
| ^^^^^^^^ ...`callee` called here
4+
LL | #[rustc_force_inline]
5+
| ^^^^^^^^^^^^^^^^^^^^^
6+
LL |
7+
LL | pub fn callee() {
8+
| --------------- `callee` defined here
69
|
7-
= note: could not be inlined due to: #[rustc_no_mir_inline]
10+
= note: incompatible due to: #[rustc_no_mir_inline]
811

9-
error: `callee_justified` could not be inlined into `async_caller::{closure#0}` but is required to be inlined
10-
--> $DIR/deny-async.rs:23:5
12+
error: `callee_justified` is incompatible with `#[rustc_force_inline]`
13+
--> $DIR/deny-async.rs:16:1
1114
|
12-
LL | callee_justified();
13-
| ^^^^^^^^^^^^^^^^^^ ...`callee_justified` called here
15+
LL | #[rustc_force_inline = "the test requires it"]
16+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
17+
LL |
18+
LL | pub fn callee_justified() {
19+
| ------------------------- `callee_justified` defined here
1420
|
15-
= note: could not be inlined due to: #[rustc_no_mir_inline]
16-
= note: `callee_justified` is required to be inlined to: the test requires it
21+
= note: incompatible due to: #[rustc_no_mir_inline]
1722

1823
error: aborting due to 2 previous errors
1924

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//@ build-fail
1+
//@ check-fail
22
//@ compile-flags: --crate-type=lib
33
#![allow(internal_features)]
44
#![feature(rustc_attrs)]
@@ -7,20 +7,19 @@
77

88
#[rustc_no_mir_inline]
99
#[rustc_force_inline]
10+
//~^ ERROR `callee` is incompatible with `#[rustc_force_inline]`
1011
pub fn callee() {
1112
}
1213

1314
#[rustc_no_mir_inline]
1415
#[rustc_force_inline = "the test requires it"]
16+
//~^ ERROR `callee_justified` is incompatible with `#[rustc_force_inline]`
1517
pub fn callee_justified() {
1618
}
1719

1820
pub fn caller() {
1921
(|| {
2022
callee();
21-
//~^ ERROR `callee` could not be inlined into `caller::{closure#0}` but is required to be inlined
22-
2323
callee_justified();
24-
//~^ ERROR `callee_justified` could not be inlined into `caller::{closure#0}` but is required to be inlined
2524
})();
2625
}

0 commit comments

Comments
 (0)