Skip to content

Commit bcfc9b5

Browse files
inline at the callsite & warn when target features mismatch
Co-authored-by: Jamie Cunliffe <[email protected]>
1 parent b2dd217 commit bcfc9b5

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
@@ -645,6 +645,8 @@ declare_features! (
645645
(unstable, super_let, "1.88.0", Some(139076)),
646646
/// Allows subtrait items to shadow supertrait items.
647647
(unstable, supertrait_item_shadowing, "1.86.0", Some(89151)),
648+
/// Allows the use of target_feature when a function is marked inline(always).
649+
(unstable, target_feature_inline_always, "CURRENT_RUSTC_VERSION", Some(145574)),
648650
/// Allows using `#[thread_local]` on `static` items.
649651
(unstable, thread_local, "1.0.0", Some(29594)),
650652
/// 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
@@ -5138,3 +5138,57 @@ declare_lint! {
51385138
"detects tail calls of functions marked with `#[track_caller]`",
51395139
@feature_gate = explicit_tail_calls;
51405140
}
5141+
declare_lint! {
5142+
/// The `inline_always_mismatching_target_features` lint will trigger when a
5143+
/// function with the `#[inline(always)]` and `#[target_feature(enable = "...")]`
5144+
/// attributes is called and cannot be inlined due to missing target features in the caller.
5145+
///
5146+
/// ### Example
5147+
///
5148+
/// ```rust,ignore (fails on x86_64)
5149+
/// #[inline(always)]
5150+
/// #[target_feature(enable = "fp16")]
5151+
/// unsafe fn callee() {
5152+
/// // operations using fp16 types
5153+
/// }
5154+
///
5155+
/// // Caller does not enable the required target feature
5156+
/// fn caller() {
5157+
/// unsafe { callee(); }
5158+
/// }
5159+
///
5160+
/// fn main() {
5161+
/// caller();
5162+
/// }
5163+
/// ```
5164+
///
5165+
/// This will produce:
5166+
///
5167+
/// ```text
5168+
/// warning: call to `#[inline(always)]`-annotated `callee` requires the same target features. Function will not have `alwaysinline` attribute applied
5169+
/// --> $DIR/builtin.rs:5192:14
5170+
/// |
5171+
/// 10 | unsafe { callee(); }
5172+
/// | ^^^^^^^^
5173+
/// |
5174+
/// note: `fp16` target feature enabled in `callee` here but missing from `caller`
5175+
/// --> $DIR/builtin.rs:5185:1
5176+
/// |
5177+
/// 3 | #[target_feature(enable = "fp16")]
5178+
/// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
5179+
/// 4 | unsafe fn callee() {
5180+
/// | ------------------
5181+
/// = note: `#[warn(inline_always_mismatching_target_features)]` on by default
5182+
/// warning: 1 warning emitted
5183+
/// ```
5184+
///
5185+
/// ### Explanation
5186+
///
5187+
/// Inlining a function with a target feature attribute into a caller that
5188+
/// lacks the corresponding target feature can lead to unsound behavior.
5189+
/// LLVM may select the wrong instructions or registers, or reorder
5190+
/// operations, potentially resulting in runtime errors.
5191+
pub INLINE_ALWAYS_MISMATCHING_TARGET_FEATURES,
5192+
Warn,
5193+
r#"detects when a function annotated with `#[inline(always)]` and `#[target_feature(enable = "..")]` is inlined into a caller without the required target feature"#,
5194+
}

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
@@ -116,6 +116,7 @@ declare_passes! {
116116
mod add_subtyping_projections : Subtyper;
117117
mod check_inline : CheckForceInline;
118118
mod check_call_recursion : CheckCallRecursion, CheckDropRecursion;
119+
mod check_inline_always_target_features: CheckInlineAlwaysTargetFeature;
119120
mod check_alignment : CheckAlignment;
120121
mod check_enums : CheckEnums;
121122
mod check_const_item_mutation : CheckConstItemMutation;
@@ -383,6 +384,9 @@ fn mir_built(tcx: TyCtxt<'_>, def: LocalDefId) -> &Steal<Body<'_>> {
383384
// MIR-level lints.
384385
&Lint(check_inline::CheckForceInline),
385386
&Lint(check_call_recursion::CheckCallRecursion),
387+
// Check callee's target features match callers target features when
388+
// using `#[inline(always)]`
389+
&Lint(check_inline_always_target_features::CheckInlineAlwaysTargetFeature),
386390
&Lint(check_packed_ref::CheckPackedRef),
387391
&Lint(check_const_item_mutation::CheckConstItemMutation),
388392
&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
@@ -2154,6 +2154,7 @@ symbols! {
21542154
target_family,
21552155
target_feature,
21562156
target_feature_11,
2157+
target_feature_inline_always,
21572158
target_has_atomic,
21582159
target_has_atomic_equal_alignment,
21592160
target_has_atomic_load_store,

0 commit comments

Comments
 (0)