Introduce deduced parameter attributes, and use them for deducing readonly
on
indirect immutable freeze by-value function parameters. Right now, `rustc` only examines function signatures and the platform ABI when determining the LLVM attributes to apply to parameters. This results in missed optimizations, because there are some attributes that can be determined via analysis of the MIR making up the function body. In particular, `readonly` could be applied to most indirectly-passed by-value function arguments (specifically, those that are freeze and are observed not to be mutated), but it currently is not. This patch introduces the machinery that allows `rustc` to determine those attributes. It consists of a query, `deduced_param_attrs`, that, when evaluated, analyzes the MIR of the function to determine supplementary attributes. The results of this query for each function are written into the crate metadata so that the deduced parameter attributes can be applied to cross-crate functions. In this patch, we simply check the parameter for mutations to determine whether the `readonly` attribute should be applied to parameters that are indirect immutable freeze by-value. More attributes could conceivably be deduced in the future: `nocapture` and `noalias` come to mind. Adding `readonly` to indirect function parameters where applicable enables some potential optimizations in LLVM that are discussed in [issue 103103] and [PR 103070] around avoiding stack-to-stack memory copies that appear in functions like `core::fmt::Write::write_fmt` and `core::panicking::assert_failed`. These functions pass a large structure unchanged by value to a subfunction that also doesn't mutate it. Since the structure in this case is passed as an indirect parameter, it's a pointer from LLVM's perspective. As a result, the intermediate copy of the structure that our codegen emits could be optimized away by LLVM's MemCpyOptimizer if it knew that the pointer is `readonly nocapture noalias` in both the caller and callee. We already pass `nocapture noalias`, but we're missing `readonly`, as we can't determine whether a by-value parameter is mutated by examining the signature in Rust. I didn't have much success with having LLVM infer the `readonly` attribute, even with fat LTO; it seems that deducing it at the MIR level is necessary. No large benefits should be expected from this optimization *now*; LLVM needs some changes (discussed in [PR 103070]) to more aggressively use the `noalias nocapture readonly` combination in its alias analysis. I have some LLVM patches for these optimizations and have had them looked over. With all the patches applied locally, I enabled LLVM to remove all the `memcpy`s from the following code: ```rust fn main() { println!("Hello {}", 3); } ``` which is a significant codegen improvement over the status quo. I expect that if this optimization kicks in in multiple places even for such a simple program, then it will apply to Rust code all over the place. [issue 103103]: https://github.com/rust-lang/rust/issues/103103 [PR 103070]: https://github.com/rust-lang/rust/pull/103070
This commit is contained in:
parent
b1ab3b738a
commit
da630ac79d
14 changed files with 393 additions and 9 deletions
|
@ -4,6 +4,7 @@ use rustc_middle::ty::layout::{
|
|||
fn_can_unwind, FnAbiError, HasParamEnv, HasTyCtxt, LayoutCx, LayoutOf, TyAndLayout,
|
||||
};
|
||||
use rustc_middle::ty::{self, Ty, TyCtxt};
|
||||
use rustc_session::config::OptLevel;
|
||||
use rustc_span::def_id::DefId;
|
||||
use rustc_target::abi::call::{
|
||||
ArgAbi, ArgAttribute, ArgAttributes, ArgExtension, Conv, FnAbi, PassMode, Reg, RegKind,
|
||||
|
@ -384,7 +385,7 @@ fn fn_abi_new_uncached<'tcx>(
|
|||
conv,
|
||||
can_unwind: fn_can_unwind(cx.tcx(), fn_def_id, sig.abi),
|
||||
};
|
||||
fn_abi_adjust_for_abi(cx, &mut fn_abi, sig.abi)?;
|
||||
fn_abi_adjust_for_abi(cx, &mut fn_abi, sig.abi, fn_def_id)?;
|
||||
debug!("fn_abi_new_uncached = {:?}", fn_abi);
|
||||
Ok(cx.tcx.arena.alloc(fn_abi))
|
||||
}
|
||||
|
@ -394,6 +395,7 @@ fn fn_abi_adjust_for_abi<'tcx>(
|
|||
cx: &LayoutCx<'tcx, TyCtxt<'tcx>>,
|
||||
fn_abi: &mut FnAbi<'tcx, Ty<'tcx>>,
|
||||
abi: SpecAbi,
|
||||
fn_def_id: Option<DefId>,
|
||||
) -> Result<(), FnAbiError<'tcx>> {
|
||||
if abi == SpecAbi::Unadjusted {
|
||||
return Ok(());
|
||||
|
@ -404,7 +406,18 @@ fn fn_abi_adjust_for_abi<'tcx>(
|
|||
|| abi == SpecAbi::RustIntrinsic
|
||||
|| abi == SpecAbi::PlatformIntrinsic
|
||||
{
|
||||
let fixup = |arg: &mut ArgAbi<'tcx, Ty<'tcx>>| {
|
||||
// Look up the deduced parameter attributes for this function, if we have its def ID and
|
||||
// we're optimizing in non-incremental mode. We'll tag its parameters with those attributes
|
||||
// as appropriate.
|
||||
let deduced_param_attrs = if cx.tcx.sess.opts.optimize != OptLevel::No
|
||||
&& cx.tcx.sess.opts.incremental.is_none()
|
||||
{
|
||||
fn_def_id.map(|fn_def_id| cx.tcx.deduced_param_attrs(fn_def_id)).unwrap_or_default()
|
||||
} else {
|
||||
&[]
|
||||
};
|
||||
|
||||
let fixup = |arg: &mut ArgAbi<'tcx, Ty<'tcx>>, arg_idx: Option<usize>| {
|
||||
if arg.is_ignore() {
|
||||
return;
|
||||
}
|
||||
|
@ -451,10 +464,30 @@ fn fn_abi_adjust_for_abi<'tcx>(
|
|||
// so we pick an appropriately sized integer type instead.
|
||||
arg.cast_to(Reg { kind: RegKind::Integer, size });
|
||||
}
|
||||
|
||||
// If we deduced that this parameter was read-only, add that to the attribute list now.
|
||||
//
|
||||
// The `readonly` parameter only applies to pointers, so we can only do this if the
|
||||
// argument was passed indirectly. (If the argument is passed directly, it's an SSA
|
||||
// value, so it's implicitly immutable.)
|
||||
if let (Some(arg_idx), &mut PassMode::Indirect { ref mut attrs, .. }) =
|
||||
(arg_idx, &mut arg.mode)
|
||||
{
|
||||
// The `deduced_param_attrs` list could be empty if this is a type of function
|
||||
// we can't deduce any parameters for, so make sure the argument index is in
|
||||
// bounds.
|
||||
if let Some(deduced_param_attrs) = deduced_param_attrs.get(arg_idx) {
|
||||
if deduced_param_attrs.read_only {
|
||||
attrs.regular.insert(ArgAttribute::ReadOnly);
|
||||
debug!("added deduced read-only attribute");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
fixup(&mut fn_abi.ret);
|
||||
for arg in fn_abi.args.iter_mut() {
|
||||
fixup(arg);
|
||||
|
||||
fixup(&mut fn_abi.ret, None);
|
||||
for (arg_idx, arg) in fn_abi.args.iter_mut().enumerate() {
|
||||
fixup(arg, Some(arg_idx));
|
||||
}
|
||||
} else {
|
||||
fn_abi.adjust_for_foreign_abi(cx, abi)?;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue