Skip to content

Commit 9c66b20

Browse files
committed
mir_transform: implement forced inlining
Adds `#[inline(must)]` and `#[inline(required)]` which is similar to always inlining but reports an error and warning respectively if the inlining was not possible, and which always attempts to inline required-annotated items, regardless of optimisation levels.
1 parent f6648f2 commit 9c66b20

File tree

25 files changed

+763
-41
lines changed

25 files changed

+763
-41
lines changed

compiler/rustc_attr/src/builtin.rs

+21
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,27 @@ pub enum InlineAttr {
4747
Hint,
4848
Always,
4949
Never,
50+
/// `#[inline(must)]` always attempts to inline in the MIR inliner, regardless of optimisation
51+
/// level, and emits a warn-by-default lint if this is not possible.
52+
Must {
53+
attr_span: Span,
54+
reason: Option<Symbol>,
55+
},
56+
/// `#[inline(required)]` always attempts to inline in the MIR inliner, regardless of
57+
/// optimisation level, and emits a error-by-default lint if this is not possible.
58+
Required {
59+
attr_span: Span,
60+
reason: Option<Symbol>,
61+
},
62+
}
63+
64+
impl InlineAttr {
65+
pub fn always(&self) -> bool {
66+
match self {
67+
InlineAttr::Always | InlineAttr::Must { .. } | InlineAttr::Required { .. } => true,
68+
InlineAttr::None | InlineAttr::Hint | InlineAttr::Never => false,
69+
}
70+
}
5071
}
5172

5273
#[derive(Clone, Encodable, Decodable, Debug, PartialEq, Eq, HashStable_Generic)]

compiler/rustc_codegen_gcc/src/attributes.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ fn inline_attr<'gcc, 'tcx>(
2020
) -> Option<FnAttribute<'gcc>> {
2121
match inline {
2222
InlineAttr::Hint => Some(FnAttribute::Inline),
23-
InlineAttr::Always => Some(FnAttribute::AlwaysInline),
23+
InlineAttr::Always | InlineAttr::Required { .. } | InlineAttr::Must { .. } => {
24+
Some(FnAttribute::AlwaysInline)
25+
}
2426
InlineAttr::Never => {
2527
if cx.sess().target.arch != "amdgpu" {
2628
Some(FnAttribute::NoInline)

compiler/rustc_codegen_llvm/src/attributes.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@ fn inline_attr<'ll>(cx: &CodegenCx<'ll, '_>, inline: InlineAttr) -> Option<&'ll
3737
}
3838
match inline {
3939
InlineAttr::Hint => Some(AttributeKind::InlineHint.create_attr(cx.llcx)),
40-
InlineAttr::Always => Some(AttributeKind::AlwaysInline.create_attr(cx.llcx)),
40+
InlineAttr::Always | InlineAttr::Required { .. } | InlineAttr::Must { .. } => {
41+
Some(AttributeKind::AlwaysInline.create_attr(cx.llcx))
42+
}
4143
InlineAttr::Never => {
4244
if cx.sess().target.arch != "amdgpu" {
4345
Some(AttributeKind::NoInline.create_attr(cx.llcx))

compiler/rustc_codegen_ssa/src/codegen_attrs.rs

+62-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use rustc_ast::{MetaItemInner, MetaItemKind, ast, attr};
1+
use rustc_ast::{MetaItem, MetaItemInner, MetaItemKind, ast, attr};
22
use rustc_attr::{InlineAttr, InstructionSetAttr, OptimizeAttr, list_contains_name};
33
use rustc_data_structures::fx::FxHashMap;
44
use rustc_errors::codes::*;
@@ -17,7 +17,7 @@ use rustc_middle::ty::{self as ty, TyCtxt};
1717
use rustc_session::parse::feature_err;
1818
use rustc_session::{Session, lint};
1919
use rustc_span::symbol::Ident;
20-
use rustc_span::{Span, sym};
20+
use rustc_span::{Span, Symbol, sym};
2121
use rustc_target::spec::{SanitizerSet, abi};
2222

2323
use crate::errors::{self, MissingFeatures, TargetFeatureDisableOrEnable};
@@ -525,10 +525,36 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs {
525525
struct_span_code_err!(tcx.dcx(), attr.span, E0534, "expected one argument")
526526
.emit();
527527
InlineAttr::None
528+
} else if list_contains_name(items, sym::must) && tcx.features().required_inlining {
529+
parse_inline_must_required(
530+
tcx,
531+
items,
532+
attr.span,
533+
sym::must,
534+
|attr_span, reason| InlineAttr::Must { attr_span, reason },
535+
)
536+
} else if list_contains_name(items, sym::required)
537+
&& tcx.features().required_inlining
538+
{
539+
parse_inline_must_required(
540+
tcx,
541+
items,
542+
attr.span,
543+
sym::required,
544+
|attr_span, reason| InlineAttr::Required { attr_span, reason },
545+
)
528546
} else if list_contains_name(items, sym::always) {
529547
InlineAttr::Always
530548
} else if list_contains_name(items, sym::never) {
531549
InlineAttr::Never
550+
} else if tcx.features().required_inlining {
551+
struct_span_code_err!(tcx.dcx(), items[0].span(), E0535, "invalid argument")
552+
.with_help(
553+
"valid inline arguments are `required`, `must`, `always` and `never`",
554+
)
555+
.emit();
556+
557+
InlineAttr::None
532558
} else {
533559
struct_span_code_err!(tcx.dcx(), items[0].span(), E0535, "invalid argument")
534560
.with_help("valid inline arguments are `always` and `never`")
@@ -586,7 +612,7 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs {
586612
// is probably a poor usage of `#[inline(always)]` and easily avoided by not using the attribute.
587613
if tcx.features().target_feature_11
588614
&& tcx.is_closure_like(did.to_def_id())
589-
&& codegen_fn_attrs.inline != InlineAttr::Always
615+
&& !codegen_fn_attrs.inline.always()
590616
{
591617
let owner_id = tcx.parent(did.to_def_id());
592618
if tcx.def_kind(owner_id).has_codegen_attrs() {
@@ -600,8 +626,7 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs {
600626
// purpose functions as they wouldn't have the right target features
601627
// enabled. For that reason we also forbid #[inline(always)] as it can't be
602628
// respected.
603-
if !codegen_fn_attrs.target_features.is_empty() && codegen_fn_attrs.inline == InlineAttr::Always
604-
{
629+
if !codegen_fn_attrs.target_features.is_empty() && codegen_fn_attrs.inline.always() {
605630
if let Some(span) = inline_span {
606631
tcx.dcx().span_err(
607632
span,
@@ -611,7 +636,7 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs {
611636
}
612637
}
613638

614-
if !codegen_fn_attrs.no_sanitize.is_empty() && codegen_fn_attrs.inline == InlineAttr::Always {
639+
if !codegen_fn_attrs.no_sanitize.is_empty() && codegen_fn_attrs.inline.always() {
615640
if let (Some(no_sanitize_span), Some(inline_span)) = (no_sanitize_span, inline_span) {
616641
let hir_id = tcx.local_def_id_to_hir_id(did);
617642
tcx.node_span_lint(
@@ -706,6 +731,37 @@ pub fn check_tied_features(
706731
None
707732
}
708733

734+
fn parse_inline_must_required<'tcx>(
735+
tcx: TyCtxt<'tcx>,
736+
items: &[MetaItemInner],
737+
attr_span: Span,
738+
expected_symbol: Symbol,
739+
create: impl Fn(Span, Option<Symbol>) -> InlineAttr,
740+
) -> InlineAttr {
741+
match items.iter().find(|i| i.has_name(expected_symbol)).expect("called on items w/out sym") {
742+
MetaItemInner::MetaItem(mi @ MetaItem { kind: MetaItemKind::Word, .. }) => {
743+
debug_assert!(mi.has_name(expected_symbol));
744+
create(attr_span, None)
745+
}
746+
nested => {
747+
if let Some((found_symbol, reason)) = nested.singleton_lit_list()
748+
&& reason.kind.is_str()
749+
{
750+
debug_assert_eq!(found_symbol, expected_symbol);
751+
create(attr_span, reason.kind.str())
752+
} else {
753+
struct_span_code_err!(tcx.dcx(), attr_span, E0535, "invalid argument")
754+
.with_help(format!(
755+
"expected one string argument to `#[inline({})]`",
756+
expected_symbol.as_str()
757+
))
758+
.emit();
759+
create(attr_span, None)
760+
}
761+
}
762+
}
763+
}
764+
709765
/// Checks if the provided DefId is a method in a trait impl for a trait which has track_caller
710766
/// applied to the method prototype.
711767
fn should_inherit_track_caller(tcx: TyCtxt<'_>, def_id: DefId) -> bool {

compiler/rustc_feature/src/unstable.rs

+2
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,8 @@ declare_features! (
221221
(internal, prelude_import, "1.2.0", None),
222222
/// Used to identify crates that contain the profiler runtime.
223223
(internal, profiler_runtime, "1.18.0", None),
224+
/// Allows using `#[inline(required)]`/`#[inline(must)]`
225+
(internal, required_inlining, "CURRENT_RUSTC_VERSION", None),
224226
/// Allows using `rustc_*` attributes (RFC 572).
225227
(internal, rustc_attrs, "1.0.0", None),
226228
/// Allows using the `#[stable]` and `#[unstable]` attributes.

compiler/rustc_lint_defs/src/builtin.rs

+38
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ declare_lint_pass! {
6767
MISSING_ABI,
6868
MISSING_FRAGMENT_SPECIFIER,
6969
MISSING_UNSAFE_ON_EXTERN,
70+
MUST_INLINE,
7071
MUST_NOT_SUSPEND,
7172
NAMED_ARGUMENTS_USED_POSITIONALLY,
7273
NEVER_TYPE_FALLBACK_FLOWING_INTO_UNSAFE,
@@ -88,6 +89,7 @@ declare_lint_pass! {
8889
REFINING_IMPL_TRAIT_REACHABLE,
8990
RENAMED_AND_REMOVED_LINTS,
9091
REPR_TRANSPARENT_EXTERNAL_PRIVATE_FIELDS,
92+
REQUIRED_INLINE,
9193
RUST_2021_INCOMPATIBLE_CLOSURE_CAPTURES,
9294
RUST_2021_INCOMPATIBLE_OR_PATTERNS,
9395
RUST_2021_PREFIXES_INCOMPATIBLE_SYNTAX,
@@ -5082,3 +5084,39 @@ declare_lint! {
50825084
};
50835085
crate_level_only
50845086
}
5087+
5088+
declare_lint! {
5089+
/// The `must_inline` lint is emitted when a function annotated with
5090+
/// `#[inline(must)]` was not able to be inlined.
5091+
///
5092+
/// ### Explanation
5093+
///
5094+
/// Functions can be marked as `#[inline(must)]` in the standard
5095+
/// library if they must be inlined in order to guarantee performance
5096+
/// characteristics or some other similar guarantee.
5097+
///
5098+
/// In some circumstances, these functions cannot be inlined and a
5099+
/// reason will be provided, this can either be rectified or the
5100+
/// lint can be silenced if the risk is acceptable.
5101+
pub MUST_INLINE,
5102+
Warn,
5103+
"`#[inline(must)]`-annotated function could not be inlined"
5104+
}
5105+
5106+
declare_lint! {
5107+
/// The `required_inline` lint is emitted when a function annotated with
5108+
/// `#[inline(required)]` was not able to be inlined.
5109+
///
5110+
/// ### Explanation
5111+
///
5112+
/// Functions can be marked as `#[inline(required)]` in the standard
5113+
/// library if they are required to be inlined in order to uphold
5114+
/// security properties or some other similar guarantee.
5115+
///
5116+
/// In some circumstances, these functions cannot be inlined and a
5117+
/// reason will be provided, this can either be rectified or the
5118+
/// lint can be silenced if the risk is acceptable.
5119+
pub REQUIRED_INLINE,
5120+
Deny,
5121+
"`#[inline(required)]`-annotated function could not be inlined"
5122+
}

compiler/rustc_middle/src/mir/mono.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use std::fmt;
22
use std::hash::Hash;
33

4-
use rustc_attr::InlineAttr;
54
use rustc_data_structures::base_n::{BaseNString, CASE_INSENSITIVE, ToBaseN};
65
use rustc_data_structures::fingerprint::Fingerprint;
76
use rustc_data_structures::fx::FxIndexMap;
@@ -138,9 +137,10 @@ impl<'tcx> MonoItem<'tcx> {
138137
// creating one copy of this `#[inline]` function which may
139138
// conflict with upstream crates as it could be an exported
140139
// symbol.
141-
match tcx.codegen_fn_attrs(instance.def_id()).inline {
142-
InlineAttr::Always => InstantiationMode::LocalCopy,
143-
_ => InstantiationMode::GloballyShared { may_conflict: true },
140+
if tcx.codegen_fn_attrs(instance.def_id()).inline.always() {
141+
InstantiationMode::LocalCopy
142+
} else {
143+
InstantiationMode::GloballyShared { may_conflict: true }
144144
}
145145
}
146146
MonoItem::Static(..) | MonoItem::GlobalAsm(..) => {

compiler/rustc_mir_transform/messages.ftl

+11
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,17 @@ mir_transform_must_not_suspend = {$pre}`{$def_path}`{$post} held across a suspen
2525
.help = consider using a block (`{"{ ... }"}`) to shrink the value's scope, ending before the suspend point
2626
mir_transform_operation_will_panic = this operation will panic at runtime
2727
28+
mir_transform_required_inline =
29+
`{$callee}` could not be inlined into `{$caller}` but {$requires_or_must} inlined
30+
.call = ...`{$callee}` called here
31+
.attr = inlining due to this annotation
32+
.caller = within `{$caller}`...
33+
.callee = `{$callee}` defined here
34+
.note = could not be inlined due to: {$reason}
35+
36+
mir_transform_required_inline_justification =
37+
`{$callee}` {$requires_or_must} inlined to: {$sym}
38+
2839
mir_transform_unaligned_packed_ref = reference to packed field is unaligned
2940
.note = packed structs are only aligned by one byte, and many modern architectures penalize unaligned field accesses
3041
.note_ub = creating a misaligned reference is undefined behavior (even if that reference is never dereferenced)

compiler/rustc_mir_transform/src/cross_crate_inline.rs

+6-4
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ use rustc_middle::ty::TyCtxt;
88
use rustc_session::config::{InliningThreshold, OptLevel};
99
use rustc_span::sym;
1010

11-
use crate::{inline, pass_manager as pm};
12-
1311
pub(super) fn provide(providers: &mut Providers) {
1412
providers.cross_crate_inlinable = cross_crate_inlinable;
1513
}
@@ -46,7 +44,10 @@ fn cross_crate_inlinable(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
4644
// #[inline(never)] to force code generation.
4745
match codegen_fn_attrs.inline {
4846
InlineAttr::Never => return false,
49-
InlineAttr::Hint | InlineAttr::Always => return true,
47+
InlineAttr::Hint
48+
| InlineAttr::Always
49+
| InlineAttr::Required { .. }
50+
| InlineAttr::Must { .. } => return true,
5051
_ => {}
5152
}
5253

@@ -59,7 +60,8 @@ fn cross_crate_inlinable(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
5960
// Don't do any inference if codegen optimizations are disabled and also MIR inlining is not
6061
// enabled. This ensures that we do inference even if someone only passes -Zinline-mir,
6162
// which is less confusing than having to also enable -Copt-level=1.
62-
if matches!(tcx.sess.opts.optimize, OptLevel::No) && !pm::should_run_pass(tcx, &inline::Inline)
63+
if matches!(tcx.sess.opts.optimize, OptLevel::No)
64+
&& !crate::inline::should_run_pass_for_item(tcx, def_id.to_def_id())
6365
{
6466
return false;
6567
}

compiler/rustc_mir_transform/src/errors.rs

+27-1
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic};
44
use rustc_middle::mir::AssertKind;
55
use rustc_middle::ty::TyCtxt;
66
use rustc_session::lint::{self, Lint};
7-
use rustc_span::Span;
87
use rustc_span::def_id::DefId;
8+
use rustc_span::{Span, Symbol};
99

1010
use crate::fluent_generated as fluent;
1111

@@ -136,3 +136,29 @@ pub(crate) struct MustNotSuspendReason {
136136
#[note(mir_transform_note2)]
137137
#[help]
138138
pub(crate) struct UndefinedTransmute;
139+
140+
#[derive(LintDiagnostic)]
141+
#[diag(mir_transform_required_inline)]
142+
#[note]
143+
pub(crate) struct RequiredInline {
144+
pub requires_or_must: &'static str,
145+
#[label(mir_transform_caller)]
146+
pub caller_span: Span,
147+
#[label(mir_transform_callee)]
148+
pub callee_span: Span,
149+
#[label(mir_transform_attr)]
150+
pub attr_span: Span,
151+
#[label(mir_transform_call)]
152+
pub call_span: Span,
153+
pub callee: String,
154+
pub caller: String,
155+
pub reason: &'static str,
156+
#[subdiagnostic]
157+
pub justification: Option<RequiredInlineJustification>,
158+
}
159+
160+
#[derive(Subdiagnostic)]
161+
#[note(mir_transform_required_inline_justification)]
162+
pub(crate) struct RequiredInlineJustification {
163+
pub sym: Symbol,
164+
}

0 commit comments

Comments
 (0)