Skip to content

Commit f90cc35

Browse files
authored
Rollup merge of #145932 - JamieCunliffe:target-feature-inlining, r=jackh726
Allow `inline(always)` with a target feature behind a unstable feature `target_feature_inline_always`. Rather than adding the inline always attribute to the function definition, we add it to the callsite. We can then check that the target features match and that the call would be safe to inline. If the function isn't inlined due to a mismatch, we emit a warning informing the user that the function can't be inlined due to the target feature mismatch. See tracking issue #145574
2 parents 3a6ae11 + bcfc9b5 commit f90cc35

File tree

16 files changed

+400
-27
lines changed

16 files changed

+400
-27
lines changed

compiler/rustc_codegen_llvm/src/attributes.rs

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,18 @@ pub(crate) fn apply_to_callsite(callsite: &Value, idx: AttributePlace, attrs: &[
2929
}
3030

3131
/// Get LLVM attribute for the provided inline heuristic.
32-
#[inline]
33-
fn inline_attr<'ll>(cx: &CodegenCx<'ll, '_>, inline: InlineAttr) -> Option<&'ll Attribute> {
32+
pub(crate) fn inline_attr<'ll, 'tcx>(
33+
cx: &CodegenCx<'ll, 'tcx>,
34+
instance: ty::Instance<'tcx>,
35+
) -> Option<&'ll Attribute> {
36+
// `optnone` requires `noinline`
37+
let codegen_fn_attrs = cx.tcx.codegen_fn_attrs(instance.def_id());
38+
let inline = match (codegen_fn_attrs.inline, &codegen_fn_attrs.optimize) {
39+
(_, OptimizeAttr::DoNotOptimize) => InlineAttr::Never,
40+
(InlineAttr::None, _) if instance.def.requires_inline(cx.tcx) => InlineAttr::Hint,
41+
(inline, _) => inline,
42+
};
43+
3444
if !cx.tcx.sess.opts.unstable_opts.inline_llvm {
3545
// disable LLVM inlining
3646
return Some(AttributeKind::NoInline.create_attr(cx.llcx));
@@ -346,14 +356,6 @@ pub(crate) fn llfn_attrs_from_instance<'ll, 'tcx>(
346356
OptimizeAttr::Speed => {}
347357
}
348358

349-
// `optnone` requires `noinline`
350-
let inline = match (codegen_fn_attrs.inline, &codegen_fn_attrs.optimize) {
351-
(_, OptimizeAttr::DoNotOptimize) => InlineAttr::Never,
352-
(InlineAttr::None, _) if instance.def.requires_inline(cx.tcx) => InlineAttr::Hint,
353-
(inline, _) => inline,
354-
};
355-
to_add.extend(inline_attr(cx, inline));
356-
357359
if cx.sess().must_emit_unwind_tables() {
358360
to_add.push(uwtable_attr(cx.llcx, cx.sess().opts.unstable_opts.use_sync_unwind));
359361
}
@@ -488,6 +490,14 @@ pub(crate) fn llfn_attrs_from_instance<'ll, 'tcx>(
488490
let function_features =
489491
codegen_fn_attrs.target_features.iter().map(|f| f.name.as_str()).collect::<Vec<&str>>();
490492

493+
// Apply function attributes as per usual if there are no user defined
494+
// target features otherwise this will get applied at the callsite.
495+
if function_features.is_empty() {
496+
if let Some(inline_attr) = inline_attr(cx, instance) {
497+
to_add.push(inline_attr);
498+
}
499+
}
500+
491501
let function_features = function_features
492502
.iter()
493503
// Convert to LLVMFeatures and filter out unavailable ones
@@ -517,6 +527,7 @@ pub(crate) fn llfn_attrs_from_instance<'ll, 'tcx>(
517527
let function_features = function_features.iter().map(|s| s.as_str());
518528
let target_features: String =
519529
global_features.chain(function_features).intersperse(",").collect();
530+
520531
if !target_features.is_empty() {
521532
to_add.push(llvm::CreateAttrStringValue(cx.llcx, "target-features", &target_features));
522533
}

compiler/rustc_codegen_llvm/src/builder.rs

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1392,7 +1392,7 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
13921392
fn call(
13931393
&mut self,
13941394
llty: &'ll Type,
1395-
fn_attrs: Option<&CodegenFnAttrs>,
1395+
fn_call_attrs: Option<&CodegenFnAttrs>,
13961396
fn_abi: Option<&FnAbi<'tcx, Ty<'tcx>>>,
13971397
llfn: &'ll Value,
13981398
args: &[&'ll Value],
@@ -1409,10 +1409,10 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
14091409
}
14101410

14111411
// Emit CFI pointer type membership test
1412-
self.cfi_type_test(fn_attrs, fn_abi, instance, llfn);
1412+
self.cfi_type_test(fn_call_attrs, fn_abi, instance, llfn);
14131413

14141414
// Emit KCFI operand bundle
1415-
let kcfi_bundle = self.kcfi_operand_bundle(fn_attrs, fn_abi, instance, llfn);
1415+
let kcfi_bundle = self.kcfi_operand_bundle(fn_call_attrs, fn_abi, instance, llfn);
14161416
if let Some(kcfi_bundle) = kcfi_bundle.as_ref().map(|b| b.as_ref()) {
14171417
bundles.push(kcfi_bundle);
14181418
}
@@ -1429,6 +1429,29 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
14291429
c"".as_ptr(),
14301430
)
14311431
};
1432+
1433+
if let Some(instance) = instance {
1434+
// Attributes on the function definition being called
1435+
let fn_defn_attrs = self.cx.tcx.codegen_fn_attrs(instance.def_id());
1436+
if let Some(fn_call_attrs) = fn_call_attrs
1437+
&& !fn_call_attrs.target_features.is_empty()
1438+
// If there is an inline attribute and a target feature that matches
1439+
// we will add the attribute to the callsite otherwise we'll omit
1440+
// this and not add the attribute to prevent soundness issues.
1441+
&& let Some(inlining_rule) = attributes::inline_attr(&self.cx, instance)
1442+
&& self.cx.tcx.is_target_feature_call_safe(
1443+
&fn_call_attrs.target_features,
1444+
&fn_defn_attrs.target_features,
1445+
)
1446+
{
1447+
attributes::apply_to_callsite(
1448+
call,
1449+
llvm::AttributePlace::Function,
1450+
&[inlining_rule],
1451+
);
1452+
}
1453+
}
1454+
14321455
if let Some(fn_abi) = fn_abi {
14331456
fn_abi.apply_attrs_callsite(self, call);
14341457
}

compiler/rustc_codegen_ssa/src/codegen_attrs.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -428,9 +428,16 @@ fn check_result(
428428
// llvm/llvm-project#70563).
429429
if !codegen_fn_attrs.target_features.is_empty()
430430
&& matches!(codegen_fn_attrs.inline, InlineAttr::Always)
431+
&& !tcx.features().target_feature_inline_always()
431432
&& let Some(span) = interesting_spans.inline
432433
{
433-
tcx.dcx().span_err(span, "cannot use `#[inline(always)]` with `#[target_feature]`");
434+
feature_err(
435+
tcx.sess,
436+
sym::target_feature_inline_always,
437+
span,
438+
"cannot use `#[inline(always)]` with `#[target_feature]`",
439+
)
440+
.emit();
434441
}
435442

436443
// warn that inline has no effect when no_sanitize is present

compiler/rustc_feature/src/unstable.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,8 @@ declare_features! (
642642
(unstable, super_let, "1.88.0", Some(139076)),
643643
/// Allows subtrait items to shadow supertrait items.
644644
(unstable, supertrait_item_shadowing, "1.86.0", Some(89151)),
645+
/// Allows the use of target_feature when a function is marked inline(always).
646+
(unstable, target_feature_inline_always, "CURRENT_RUSTC_VERSION", Some(145574)),
645647
/// Allows using `#[thread_local]` on `static` items.
646648
(unstable, thread_local, "1.0.0", Some(29594)),
647649
/// Allows defining `trait X = A + B;` alias items.

compiler/rustc_lint_defs/src/builtin.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5141,3 +5141,57 @@ declare_lint! {
51415141
"detects tail calls of functions marked with `#[track_caller]`",
51425142
@feature_gate = explicit_tail_calls;
51435143
}
5144+
declare_lint! {
5145+
/// The `inline_always_mismatching_target_features` lint will trigger when a
5146+
/// function with the `#[inline(always)]` and `#[target_feature(enable = "...")]`
5147+
/// attributes is called and cannot be inlined due to missing target features in the caller.
5148+
///
5149+
/// ### Example
5150+
///
5151+
/// ```rust,ignore (fails on x86_64)
5152+
/// #[inline(always)]
5153+
/// #[target_feature(enable = "fp16")]
5154+
/// unsafe fn callee() {
5155+
/// // operations using fp16 types
5156+
/// }
5157+
///
5158+
/// // Caller does not enable the required target feature
5159+
/// fn caller() {
5160+
/// unsafe { callee(); }
5161+
/// }
5162+
///
5163+
/// fn main() {
5164+
/// caller();
5165+
/// }
5166+
/// ```
5167+
///
5168+
/// This will produce:
5169+
///
5170+
/// ```text
5171+
/// warning: call to `#[inline(always)]`-annotated `callee` requires the same target features. Function will not have `alwaysinline` attribute applied
5172+
/// --> $DIR/builtin.rs:5192:14
5173+
/// |
5174+
/// 10 | unsafe { callee(); }
5175+
/// | ^^^^^^^^
5176+
/// |
5177+
/// note: `fp16` target feature enabled in `callee` here but missing from `caller`
5178+
/// --> $DIR/builtin.rs:5185:1
5179+
/// |
5180+
/// 3 | #[target_feature(enable = "fp16")]
5181+
/// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
5182+
/// 4 | unsafe fn callee() {
5183+
/// | ------------------
5184+
/// = note: `#[warn(inline_always_mismatching_target_features)]` on by default
5185+
/// warning: 1 warning emitted
5186+
/// ```
5187+
///
5188+
/// ### Explanation
5189+
///
5190+
/// Inlining a function with a target feature attribute into a caller that
5191+
/// lacks the corresponding target feature can lead to unsound behavior.
5192+
/// LLVM may select the wrong instructions or registers, or reorder
5193+
/// operations, potentially resulting in runtime errors.
5194+
pub INLINE_ALWAYS_MISMATCHING_TARGET_FEATURES,
5195+
Warn,
5196+
r#"detects when a function annotated with `#[inline(always)]` and `#[target_feature(enable = "..")]` is inlined into a caller without the required target feature"#,
5197+
}

compiler/rustc_middle/src/middle/codegen_fn_attrs.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ pub enum TargetFeatureKind {
8282
Forced,
8383
}
8484

85-
#[derive(Copy, Clone, Debug, TyEncodable, TyDecodable, HashStable)]
85+
#[derive(Copy, Clone, Debug, Eq, PartialEq, TyEncodable, TyDecodable, HashStable)]
8686
pub struct TargetFeature {
8787
/// The name of the target feature (e.g. "avx")
8888
pub name: Symbol,
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
use rustc_hir::attrs::InlineAttr;
2+
use rustc_middle::middle::codegen_fn_attrs::{TargetFeature, TargetFeatureKind};
3+
use rustc_middle::mir::{Body, TerminatorKind};
4+
use rustc_middle::ty::{self, TyCtxt};
5+
6+
use crate::pass_manager::MirLint;
7+
8+
pub(super) struct CheckInlineAlwaysTargetFeature;
9+
10+
impl<'tcx> MirLint<'tcx> for CheckInlineAlwaysTargetFeature {
11+
fn run_lint(&self, tcx: TyCtxt<'tcx>, body: &Body<'tcx>) {
12+
check_inline_always_target_features(tcx, body)
13+
}
14+
}
15+
16+
/// `#[target_feature]`-annotated functions can be marked `#[inline]` and will only be inlined if
17+
/// the target features match (as well as all of the other inlining heuristics). `#[inline(always)]`
18+
/// will always inline regardless of matching target features, which can result in errors from LLVM.
19+
/// However, it is desirable to be able to always annotate certain functions (e.g. SIMD intrinsics)
20+
/// as `#[inline(always)]` but check the target features match in Rust to avoid the LLVM errors.
21+
///
22+
/// We check the caller and callee target features to ensure that this can
23+
/// be done or emit a lint.
24+
#[inline]
25+
fn check_inline_always_target_features<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>) {
26+
let caller_def_id = body.source.def_id().expect_local();
27+
if !tcx.def_kind(caller_def_id).has_codegen_attrs() {
28+
return;
29+
}
30+
31+
let caller_codegen_fn_attrs = tcx.codegen_fn_attrs(caller_def_id);
32+
33+
for bb in body.basic_blocks.iter() {
34+
let terminator = bb.terminator();
35+
match &terminator.kind {
36+
TerminatorKind::Call { func, .. } | TerminatorKind::TailCall { func, .. } => {
37+
let fn_ty = func.ty(body, tcx);
38+
let ty::FnDef(callee_def_id, _) = *fn_ty.kind() else {
39+
continue;
40+
};
41+
42+
if !tcx.def_kind(callee_def_id).has_codegen_attrs() {
43+
continue;
44+
}
45+
let callee_codegen_fn_attrs = tcx.codegen_fn_attrs(callee_def_id);
46+
if callee_codegen_fn_attrs.inline != InlineAttr::Always
47+
|| callee_codegen_fn_attrs.target_features.is_empty()
48+
{
49+
continue;
50+
}
51+
52+
// Scan the users defined target features and ensure they
53+
// match the caller.
54+
if tcx.is_target_feature_call_safe(
55+
&callee_codegen_fn_attrs.target_features,
56+
&caller_codegen_fn_attrs
57+
.target_features
58+
.iter()
59+
.cloned()
60+
.chain(tcx.sess.target_features.iter().map(|feat| TargetFeature {
61+
name: *feat,
62+
kind: TargetFeatureKind::Implied,
63+
}))
64+
.collect::<Vec<_>>(),
65+
) {
66+
continue;
67+
}
68+
69+
let callee_only: Vec<_> = callee_codegen_fn_attrs
70+
.target_features
71+
.iter()
72+
.filter(|it| !caller_codegen_fn_attrs.target_features.contains(it))
73+
.filter(|it| !matches!(it.kind, TargetFeatureKind::Implied))
74+
.map(|it| it.name.as_str())
75+
.collect();
76+
77+
crate::errors::emit_inline_always_target_feature_diagnostic(
78+
tcx,
79+
terminator.source_info.span,
80+
callee_def_id,
81+
caller_def_id.into(),
82+
&callee_only,
83+
);
84+
}
85+
_ => (),
86+
}
87+
}
88+
}

compiler/rustc_mir_transform/src/errors.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,54 @@ use rustc_errors::codes::*;
22
use rustc_errors::{Diag, LintDiagnostic};
33
use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic};
44
use rustc_middle::mir::AssertKind;
5+
use rustc_middle::query::Key;
56
use rustc_middle::ty::TyCtxt;
67
use rustc_session::lint::{self, Lint};
78
use rustc_span::def_id::DefId;
89
use rustc_span::{Ident, Span, Symbol};
910

1011
use crate::fluent_generated as fluent;
1112

13+
/// Emit diagnostic for calls to `#[inline(always)]`-annotated functions with a
14+
/// `#[target_feature]` attribute where the caller enables a different set of target features.
15+
pub(crate) fn emit_inline_always_target_feature_diagnostic<'a, 'tcx>(
16+
tcx: TyCtxt<'tcx>,
17+
call_span: Span,
18+
callee_def_id: DefId,
19+
caller_def_id: DefId,
20+
callee_only: &[&'a str],
21+
) {
22+
let callee = tcx.def_path_str(callee_def_id);
23+
let caller = tcx.def_path_str(caller_def_id);
24+
25+
tcx.node_span_lint(
26+
lint::builtin::INLINE_ALWAYS_MISMATCHING_TARGET_FEATURES,
27+
tcx.local_def_id_to_hir_id(caller_def_id.as_local().unwrap()),
28+
call_span,
29+
|lint| {
30+
lint.primary_message(format!(
31+
"call to `#[inline(always)]`-annotated `{callee}` \
32+
requires the same target features to be inlined"
33+
));
34+
lint.note("function will not be inlined");
35+
36+
lint.note(format!(
37+
"the following target features are on `{callee}` but missing from `{caller}`: {}",
38+
callee_only.join(", ")
39+
));
40+
lint.span_note(callee_def_id.default_span(tcx), format!("`{callee}` is defined here"));
41+
42+
let feats = callee_only.join(",");
43+
lint.span_suggestion(
44+
tcx.def_span(caller_def_id).shrink_to_lo(),
45+
format!("add `#[target_feature]` attribute to `{caller}`"),
46+
format!("#[target_feature(enable = \"{feats}\")]\n"),
47+
lint::Applicability::MaybeIncorrect,
48+
);
49+
},
50+
);
51+
}
52+
1253
#[derive(LintDiagnostic)]
1354
#[diag(mir_transform_unconditional_recursion)]
1455
#[help]

compiler/rustc_mir_transform/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ declare_passes! {
117117
mod add_subtyping_projections : Subtyper;
118118
mod check_inline : CheckForceInline;
119119
mod check_call_recursion : CheckCallRecursion, CheckDropRecursion;
120+
mod check_inline_always_target_features: CheckInlineAlwaysTargetFeature;
120121
mod check_alignment : CheckAlignment;
121122
mod check_enums : CheckEnums;
122123
mod check_const_item_mutation : CheckConstItemMutation;
@@ -384,6 +385,9 @@ fn mir_built(tcx: TyCtxt<'_>, def: LocalDefId) -> &Steal<Body<'_>> {
384385
// MIR-level lints.
385386
&Lint(check_inline::CheckForceInline),
386387
&Lint(check_call_recursion::CheckCallRecursion),
388+
// Check callee's target features match callers target features when
389+
// using `#[inline(always)]`
390+
&Lint(check_inline_always_target_features::CheckInlineAlwaysTargetFeature),
387391
&Lint(check_packed_ref::CheckPackedRef),
388392
&Lint(check_const_item_mutation::CheckConstItemMutation),
389393
&Lint(function_item_references::FunctionItemReferences),

compiler/rustc_span/src/symbol.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2162,6 +2162,7 @@ symbols! {
21622162
target_family,
21632163
target_feature,
21642164
target_feature_11,
2165+
target_feature_inline_always,
21652166
target_has_atomic,
21662167
target_has_atomic_equal_alignment,
21672168
target_has_atomic_load_store,

0 commit comments

Comments
 (0)