-
Notifications
You must be signed in to change notification settings - Fork 105
Fix nested always-inline call chains #976
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,13 +4,17 @@ use gccjit::Function; | |
| #[cfg(feature = "master")] | ||
| use rustc_abi::{CanonAbi, InterruptKind}; | ||
| #[cfg(feature = "master")] | ||
| use rustc_data_structures::fx::FxHashSet; | ||
| #[cfg(feature = "master")] | ||
| use rustc_hir::attrs::InlineAttr; | ||
| use rustc_hir::attrs::InstructionSetAttr; | ||
| #[cfg(feature = "master")] | ||
| use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; | ||
| #[cfg(feature = "master")] | ||
| use rustc_middle::mir::TerminatorKind; | ||
| use rustc_middle::ty; | ||
| #[cfg(feature = "master")] | ||
| use rustc_span::def_id::DefId; | ||
| use rustc_target::callconv::FnAbi; | ||
| #[cfg(feature = "master")] | ||
| use rustc_target::spec::Arch; | ||
|
|
@@ -20,32 +24,48 @@ use crate::base; | |
| use crate::context::CodegenCx; | ||
| use crate::gcc_util::to_gcc_features; | ||
|
|
||
| /// 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. | ||
| /// Check forced-inline call chains for cycles. Merely calling another | ||
| /// always-inline function is not recursion. | ||
| #[cfg(feature = "master")] | ||
| fn recursively_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. | ||
|
Comment on lines
-38
to
-39
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please keep this comment as it is useful. |
||
| 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 { .. } | ||
| ) { | ||
| // Keep the DFS on the heap: valid forced-inline chains can be arbitrarily | ||
| // deep, independently of the compiler thread's remaining call stack. | ||
| let mut pending: Vec<(DefId, bool)> = vec![(instance.def_id(), false)]; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a comment to explain what the bool represents in this Vec. |
||
| let mut active = FxHashSet::default(); | ||
| while let Some((def, finishing)) = pending.pop() { | ||
| if finishing { | ||
| active.remove(&def); | ||
| cx.inline_recursion.borrow_mut().insert(def, false); | ||
| continue; | ||
| } | ||
| let cached = cx.inline_recursion.borrow().get(&def).copied(); | ||
| if cached == Some(false) { | ||
| continue; | ||
| } | ||
| if cached == Some(true) || active.contains(&def) || !cx.tcx.is_mir_available(def) { | ||
| let mut cache = cx.inline_recursion.borrow_mut(); | ||
| cache.insert(def, true); | ||
| for caller in active { | ||
| cache.insert(caller, true); | ||
| } | ||
| return true; | ||
| } | ||
| active.insert(def); | ||
| pending.push((def, true)); | ||
| for block in cx.tcx.optimized_mir(def).basic_blocks.iter().rev() { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this loop in reverse order because calls are terminators, so at the end of basic blocks? |
||
| let Some(ref terminator) = block.terminator else { continue }; | ||
| let TerminatorKind::Call { ref func, .. } = terminator.kind else { continue }; | ||
| let Some((callee, _)) = func.const_fn_def() else { continue }; | ||
| if matches!( | ||
| cx.tcx.codegen_fn_attrs(callee).inline, | ||
| InlineAttr::Always | InlineAttr::Force { .. } | ||
| ) { | ||
| pending.push((callee, false)); | ||
| } | ||
| } | ||
| } | ||
| false | ||
| } | ||
|
|
@@ -60,14 +80,8 @@ fn inline_attr<'gcc, '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. | ||
| // GCC cannot force recursive call chains inline. Preserve the | ||
| // guarantee for acyclic chains, including nested intrinsic wrappers. | ||
| if recursively_inline(cx, instance) { | ||
| Some(FnAttribute::Inline) | ||
| } else { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,6 +20,8 @@ use rustc_middle::ty::{self, ExistentialTraitRef, Instance, Ty, TyCtxt}; | |
| #[cfg(feature = "master")] | ||
| use rustc_session::config::DebugInfo; | ||
| use rustc_session::{PointerAuthSchema, Session}; | ||
| #[cfg(feature = "master")] | ||
| use rustc_span::def_id::DefId; | ||
| use rustc_span::{DUMMY_SP, Span, Symbol}; | ||
| use rustc_target::spec::{HasTargetSpec, HasX86AbiOpt, Target, TlsModel, X86Abi}; | ||
|
|
||
|
|
@@ -49,6 +51,8 @@ pub struct CodegenCx<'gcc, 'tcx> { | |
|
|
||
| pub functions: RefCell<FxHashMap<String, Function<'gcc>>>, | ||
| pub intrinsics: RefCell<FxHashMap<String, Function<'gcc>>>, | ||
| #[cfg(feature = "master")] | ||
| pub inline_recursion: RefCell<FxHashMap<DefId, bool>>, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add a comment to explain why we need this field and what it is used for and what does the bool represent. |
||
|
|
||
| pub tls_model: gccjit::TlsModel, | ||
|
|
||
|
|
@@ -263,6 +267,8 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { | |
| function_address_names: Default::default(), | ||
| functions: RefCell::new(functions), | ||
| intrinsics: RefCell::new(FxHashMap::default()), | ||
| #[cfg(feature = "master")] | ||
| inline_recursion: Default::default(), | ||
|
|
||
| tls_model, | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| //@ assembly-output: emit-asm | ||
| //@ only-x86_64 | ||
| //@ compile-flags: -Copt-level=0 | ||
|
|
||
| #![crate_type = "lib"] | ||
| #![no_std] | ||
|
|
||
| // At -O0, an ordinary inline hint is insufficient: the entire chain must retain | ||
| // always_inline even though its wrappers call other always-inline functions. | ||
| #[inline(always)] | ||
| fn leaf(x: u32) -> u32 { | ||
| x ^ 0xa5a5 | ||
| } | ||
|
|
||
| #[inline(always)] | ||
| fn mid(x: u32) -> u32 { | ||
| leaf(x).wrapping_mul(3) | ||
| } | ||
|
|
||
| #[inline(always)] | ||
| fn top(x: u32) -> u32 { | ||
| mid(x).wrapping_add(7) | ||
| } | ||
|
|
||
| // CHECK-LABEL: {{^"?_?}}entry{{"?}}: | ||
| // CHECK-NOT: call | ||
| // CHECK: ret | ||
| #[no_mangle] | ||
| pub fn entry(x: u32) -> u32 { | ||
| top(x) | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Explain in the doc comment what is the return value.
View changes since the review