Skip to content

Commit d6b6117

Browse files
committed
Fixed a recursive inling bug, added a test for it
1 parent cfe88fa commit d6b6117

File tree

2 files changed

+89
-4
lines changed

2 files changed

+89
-4
lines changed

src/attributes.rs

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,65 @@ use rustc_attr_parsing::InlineAttr;
66
use rustc_attr_parsing::InstructionSetAttr;
77
#[cfg(feature = "master")]
88
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
9+
use rustc_middle::mir::TerminatorKind;
910
use rustc_middle::ty;
1011

1112
use crate::context::CodegenCx;
1213
use crate::gcc_util::to_gcc_features;
13-
14-
/// Get GCC attribute for the provided inline heuristic.
14+
/// Checks if the function `instance` is recursively inline.
15+
/// Returns `false` if a functions is guranteed to be non-recursive, and `true` if it *might* be recursive.
16+
fn resursively_inline<'gcc, 'tcx>(
17+
cx: &CodegenCx<'gcc, 'tcx>,
18+
instance: ty::Instance<'tcx>,
19+
) -> bool {
20+
// No body, so we can't check if this is recursively inline, so we assume it is.
21+
if !cx.tcx.is_mir_available(instance.def_id()) {
22+
return true;
23+
}
24+
// `expect_local` ought to never fail: we should be checking a function within this codegen unit.
25+
let body = cx.tcx.optimized_mir(instance.def_id());
26+
for block in body.basic_blocks.iter() {
27+
let Some(terminator) = &block.terminator else { continue };
28+
// I assmume that the resursive-inline issue applies only to functions, and not to drops.
29+
// In principle, a recursive, `#[inline(always)]` drop could(?) exist, but I don't think it does.
30+
let TerminatorKind::Call { func, .. } = &terminator.kind else { continue };
31+
let Some((def, _args)) = func.const_fn_def() else { continue };
32+
// Check if the called function is recursively inline.
33+
if matches!(
34+
cx.tcx.codegen_fn_attrs(def).inline,
35+
InlineAttr::Always | InlineAttr::Force { .. }
36+
) {
37+
return true;
38+
}
39+
}
40+
false
41+
}
42+
/// Get GCC attribute for the provided inline heuristic, attached to `instance`.
1543
#[cfg(feature = "master")]
1644
#[inline]
1745
fn inline_attr<'gcc, 'tcx>(
1846
cx: &CodegenCx<'gcc, 'tcx>,
1947
inline: InlineAttr,
48+
instance: ty::Instance<'tcx>,
2049
) -> Option<FnAttribute<'gcc>> {
2150
match inline {
51+
InlineAttr::Always => {
52+
// We can't simply always return `always_inline` unconditionally.
53+
// It is *NOT A HINT* and does not work for recurisve functions.
54+
//
55+
// So, it can only be applied *if*:
56+
// The current function does not call any functions makred `#[inline(always)]`.
57+
//
58+
// That prevents issues steming from recursive `#[inline(always)]` at a *relatively* small cost.
59+
// We *only* need to check all the terminators of a function marked with this attribute.
60+
if resursively_inline(cx, instance) {
61+
Some(FnAttribute::Inline)
62+
} else {
63+
Some(FnAttribute::AlwaysInline)
64+
}
65+
}
2266
InlineAttr::Hint => Some(FnAttribute::Inline),
23-
InlineAttr::Always | InlineAttr::Force { .. } => Some(FnAttribute::AlwaysInline),
67+
InlineAttr::Force { .. } => Some(FnAttribute::AlwaysInline),
2468
InlineAttr::Never => {
2569
if cx.sess().target.arch != "amdgpu" {
2670
Some(FnAttribute::NoInline)
@@ -52,7 +96,7 @@ pub fn from_fn_attrs<'gcc, 'tcx>(
5296
} else {
5397
codegen_fn_attrs.inline
5498
};
55-
if let Some(attr) = inline_attr(cx, inline) {
99+
if let Some(attr) = inline_attr(cx, inline, instance) {
56100
if let FnAttribute::AlwaysInline = attr {
57101
func.add_attribute(FnAttribute::Inline);
58102
}

tests/run/always_inline.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Compiler:
2+
//
3+
// Run-time:
4+
// status: 0
5+
6+
7+
#![feature(no_core)]
8+
#![no_std]
9+
#![no_core]
10+
#![no_main]
11+
12+
extern crate mini_core;
13+
use mini_core::*;
14+
#[unsafe(no_mangle)]
15+
#[inline(always)]
16+
fn fib(n:u8)->u8{
17+
if n == 0{return 1};
18+
if n == 1{return 1};
19+
fib(n - 1) + fib(n - 2)
20+
}
21+
#[unsafe(no_mangle)]
22+
fn fib_b(n:u8)->u8{
23+
if n == 0{return 1};
24+
if n == 1{return 1};
25+
fib_a(n - 1) + fib_a(n - 2)
26+
}
27+
#[inline(always)]
28+
#[unsafe(no_mangle)]
29+
fn fib_a(n:u8)->u8{
30+
if n == 0{return 1};
31+
if n == 1{return 1};
32+
fib_b(n - 1) + fib_b(n - 2)
33+
}
34+
#[no_mangle]
35+
extern "C" fn main(argc: i32, _argv: *const *const u8) -> i32 {
36+
if fib(2) != fib_a(2){
37+
unsafe{intrinsics::abort()};
38+
}
39+
0
40+
}
41+

0 commit comments

Comments
 (0)