about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorantoyo <antoyo@users.noreply.github.com>2025-05-11 09:50:58 -0400
committerGitHub <noreply@github.com>2025-05-11 09:50:58 -0400
commitd8e2d247388923bd2d775ec75ad218ac7cb77c19 (patch)
treec385011095bc21a4cbd249fa64544e379f67c190 /src
parent3cffea796947a808fe56994b90341f143a9c7bf1 (diff)
parentf111416e43a36a1ee062a2194eae37c39d0f0be1 (diff)
downloadrust-d8e2d247388923bd2d775ec75ad218ac7cb77c19.tar.gz
rust-d8e2d247388923bd2d775ec75ad218ac7cb77c19.zip
Merge pull request #666 from FractalFir/master
Fixed a recursive inling bug, added a test for it
Diffstat (limited to 'src')
-rw-r--r--src/attributes.rs53
1 files changed, 50 insertions, 3 deletions
diff --git a/src/attributes.rs b/src/attributes.rs
index 8bc1b770243..f933119d0ba 100644
--- a/src/attributes.rs
+++ b/src/attributes.rs
@@ -6,21 +6,68 @@ use rustc_attr_parsing::InlineAttr;
 use rustc_attr_parsing::InstructionSetAttr;
 #[cfg(feature = "master")]
 use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
+use rustc_middle::mir::TerminatorKind;
 use rustc_middle::ty;
 
 use crate::context::CodegenCx;
 use crate::gcc_util::to_gcc_features;
 
-/// Get GCC attribute for the provided inline heuristic.
+/// Checks if the function `instance` is recursively inline.
+/// Returns `false` if a functions is guaranteed to be non-recursive, and `true` if it *might* be recursive.
+#[cfg(feature = "master")]
+fn resursively_inline<'gcc, 'tcx>(
+    cx: &CodegenCx<'gcc, 'tcx>,
+    instance: ty::Instance<'tcx>,
+) -> bool {
+    // No body, so we can't check if this is recursively inline, so we assume it is.
+    if !cx.tcx.is_mir_available(instance.def_id()) {
+        return true;
+    }
+    // `expect_local` ought to never fail: we should be checking a function within this codegen unit.
+    let body = cx.tcx.optimized_mir(instance.def_id());
+    for block in body.basic_blocks.iter() {
+        let Some(ref terminator) = block.terminator else { continue };
+        // I assume that the recursive-inline issue applies only to functions, and not to drops.
+        // In principle, a recursive, `#[inline(always)]` drop could(?) exist, but I don't think it does.
+        let TerminatorKind::Call { ref func, .. } = terminator.kind else { continue };
+        let Some((def, _args)) = func.const_fn_def() else { continue };
+        // Check if the called function is recursively inline.
+        if matches!(
+            cx.tcx.codegen_fn_attrs(def).inline,
+            InlineAttr::Always | InlineAttr::Force { .. }
+        ) {
+            return true;
+        }
+    }
+    false
+}
+
+/// Get GCC attribute for the provided inline heuristic, attached to `instance`.
 #[cfg(feature = "master")]
 #[inline]
 fn inline_attr<'gcc, 'tcx>(
     cx: &CodegenCx<'gcc, 'tcx>,
     inline: InlineAttr,
+    instance: ty::Instance<'tcx>,
 ) -> Option<FnAttribute<'gcc>> {
     match inline {
+        InlineAttr::Always => {
+            // We can't simply always return `always_inline` unconditionally.
+            // It is *NOT A HINT* and does not work for recursive functions.
+            //
+            // So, it can only be applied *if*:
+            // The current function does not call any functions marked `#[inline(always)]`.
+            //
+            // That prevents issues steming from recursive `#[inline(always)]` at a *relatively* small cost.
+            // We *only* need to check all the terminators of a function marked with this attribute.
+            if resursively_inline(cx, instance) {
+                Some(FnAttribute::Inline)
+            } else {
+                Some(FnAttribute::AlwaysInline)
+            }
+        }
         InlineAttr::Hint => Some(FnAttribute::Inline),
-        InlineAttr::Always | InlineAttr::Force { .. } => Some(FnAttribute::AlwaysInline),
+        InlineAttr::Force { .. } => Some(FnAttribute::AlwaysInline),
         InlineAttr::Never => {
             if cx.sess().target.arch != "amdgpu" {
                 Some(FnAttribute::NoInline)
@@ -52,7 +99,7 @@ pub fn from_fn_attrs<'gcc, 'tcx>(
         } else {
             codegen_fn_attrs.inline
         };
-        if let Some(attr) = inline_attr(cx, inline) {
+        if let Some(attr) = inline_attr(cx, inline, instance) {
             if let FnAttribute::AlwaysInline = attr {
                 func.add_attribute(FnAttribute::Inline);
             }