Skip to content

Commit d8e2d24

Browse files
authored
Merge pull request #666 from FractalFir/master
Fixed a recursive inling bug, added a test for it
2 parents 3cffea7 + f111416 commit d8e2d24

File tree

2 files changed

+103
-3
lines changed

2 files changed

+103
-3
lines changed

src/attributes.rs

+50-3
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,68 @@ 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;
1314

14-
/// Get GCC attribute for the provided inline heuristic.
15+
/// Checks if the function `instance` is recursively inline.
16+
/// Returns `false` if a functions is guaranteed to be non-recursive, and `true` if it *might* be recursive.
17+
#[cfg(feature = "master")]
18+
fn resursively_inline<'gcc, 'tcx>(
19+
cx: &CodegenCx<'gcc, 'tcx>,
20+
instance: ty::Instance<'tcx>,
21+
) -> bool {
22+
// No body, so we can't check if this is recursively inline, so we assume it is.
23+
if !cx.tcx.is_mir_available(instance.def_id()) {
24+
return true;
25+
}
26+
// `expect_local` ought to never fail: we should be checking a function within this codegen unit.
27+
let body = cx.tcx.optimized_mir(instance.def_id());
28+
for block in body.basic_blocks.iter() {
29+
let Some(ref terminator) = block.terminator else { continue };
30+
// I assume that the recursive-inline issue applies only to functions, and not to drops.
31+
// In principle, a recursive, `#[inline(always)]` drop could(?) exist, but I don't think it does.
32+
let TerminatorKind::Call { ref func, .. } = terminator.kind else { continue };
33+
let Some((def, _args)) = func.const_fn_def() else { continue };
34+
// Check if the called function is recursively inline.
35+
if matches!(
36+
cx.tcx.codegen_fn_attrs(def).inline,
37+
InlineAttr::Always | InlineAttr::Force { .. }
38+
) {
39+
return true;
40+
}
41+
}
42+
false
43+
}
44+
45+
/// Get GCC attribute for the provided inline heuristic, attached to `instance`.
1546
#[cfg(feature = "master")]
1647
#[inline]
1748
fn inline_attr<'gcc, 'tcx>(
1849
cx: &CodegenCx<'gcc, 'tcx>,
1950
inline: InlineAttr,
51+
instance: ty::Instance<'tcx>,
2052
) -> Option<FnAttribute<'gcc>> {
2153
match inline {
54+
InlineAttr::Always => {
55+
// We can't simply always return `always_inline` unconditionally.
56+
// It is *NOT A HINT* and does not work for recursive functions.
57+
//
58+
// So, it can only be applied *if*:
59+
// The current function does not call any functions marked `#[inline(always)]`.
60+
//
61+
// That prevents issues steming from recursive `#[inline(always)]` at a *relatively* small cost.
62+
// We *only* need to check all the terminators of a function marked with this attribute.
63+
if resursively_inline(cx, instance) {
64+
Some(FnAttribute::Inline)
65+
} else {
66+
Some(FnAttribute::AlwaysInline)
67+
}
68+
}
2269
InlineAttr::Hint => Some(FnAttribute::Inline),
23-
InlineAttr::Always | InlineAttr::Force { .. } => Some(FnAttribute::AlwaysInline),
70+
InlineAttr::Force { .. } => Some(FnAttribute::AlwaysInline),
2471
InlineAttr::Never => {
2572
if cx.sess().target.arch != "amdgpu" {
2673
Some(FnAttribute::NoInline)
@@ -52,7 +99,7 @@ pub fn from_fn_attrs<'gcc, 'tcx>(
5299
} else {
53100
codegen_fn_attrs.inline
54101
};
55-
if let Some(attr) = inline_attr(cx, inline) {
102+
if let Some(attr) = inline_attr(cx, inline, instance) {
56103
if let FnAttribute::AlwaysInline = attr {
57104
func.add_attribute(FnAttribute::Inline);
58105
}

tests/run/always_inline.rs

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

0 commit comments

Comments
 (0)