Skip to content

Commit

Permalink
Rollup merge of #134030 - folkertdev:min-fn-align, r=workingjubilee
Browse files Browse the repository at this point in the history
add `-Zmin-function-alignment`

tracking issue: #82232

This PR adds the `-Zmin-function-alignment=<align>` flag, that specifies a minimum alignment for all* functions.

### Motivation

This feature is requested by RfL [here](#128830):

> i.e. the equivalents of `-fmin-function-alignment` ([GCC](https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html#index-fmin-function-alignment_003dn), Clang does not support it) / `-falign-functions` ([GCC](https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html#index-falign-functions), [Clang](https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang1-falign-functions)).
>
> For the Linux kernel, the behavior wanted is that of GCC's `-fmin-function-alignment` and Clang's `-falign-functions`, i.e. align all functions, including cold functions.
>
> There is [`feature(fn_align)`](#82232), but we need to do it globally.

### Behavior

The `fn_align` feature does not have an RFC. It was decided at the time that it would not be necessary, but maybe we feel differently about that now? In any case, here are the semantics of this flag:

- `-Zmin-function-alignment=<align>` specifies the minimum alignment of all* functions
- the `#[repr(align(<align>))]` attribute can be used to override the function alignment on a per-function basis: when `-Zmin-function-alignment` is specified, the attribute's value is only used when it is higher than the value passed to `-Zmin-function-alignment`.
- the target may decide to use a higher value (e.g. on x86_64 the minimum that LLVM generates is 16)
- The highest supported alignment in rust is `2^29`: I checked a bunch of targets, and they all emit the `.p2align        29` directive for targets that align functions at all (some GPU stuff does not have function alignment).

*: Only with `build-std` would the minimum alignment also be applied to `std` functions.

---

cc `@ojeda`

r? `@workingjubilee` you were active on the tracking issue
  • Loading branch information
matthiaskrgr authored Jan 11, 2025
2 parents fb65a3e + 47573bf commit b8e230a
Show file tree
Hide file tree
Showing 8 changed files with 145 additions and 3 deletions.
6 changes: 5 additions & 1 deletion compiler/rustc_codegen_llvm/src/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,11 @@ pub(crate) fn llfn_attrs_from_instance<'ll, 'tcx>(
let allocated_pointer = AttributeKind::AllocatedPointer.create_attr(cx.llcx);
attributes::apply_to_llfn(llfn, AttributePlace::Argument(0), &[allocated_pointer]);
}
if let Some(align) = codegen_fn_attrs.alignment {
// function alignment can be set globally with the `-Zmin-function-alignment=<n>` flag;
// the alignment from a `#[repr(align(<n>))]` is used if it specifies a higher alignment.
if let Some(align) =
Ord::max(cx.tcx.sess.opts.unstable_opts.min_function_alignment, codegen_fn_attrs.alignment)
{
llvm::set_alignment(llfn, align);
}
if let Some(backchain) = backchain_attr(cx) {
Expand Down
8 changes: 6 additions & 2 deletions compiler/rustc_codegen_ssa/src/mir/naked_asm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,13 @@ fn prefix_and_suffix<'tcx>(

let attrs = tcx.codegen_fn_attrs(instance.def_id());
let link_section = attrs.link_section.map(|symbol| symbol.as_str().to_string());
let align = attrs.alignment.map(|a| a.bytes()).unwrap_or(4);

// See https://sourceware.org/binutils/docs/as/ARM-Directives.html for info on these directives.
// function alignment can be set globally with the `-Zmin-function-alignment=<n>` flag;
// the alignment from a `#[repr(align(<n>))]` is used if it specifies a higher alignment.
// if no alignment is specified, an alignment of 4 bytes is used.
let min_function_alignment = tcx.sess.opts.unstable_opts.min_function_alignment;
let align = Ord::max(min_function_alignment, attrs.alignment).map(|a| a.bytes()).unwrap_or(4);

// In particular, `.arm` can also be written `.code 32` and `.thumb` as `.code 16`.
let (arch_prefix, arch_suffix) = if is_arm {
(
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_interface/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use rustc_session::{CompilerIO, EarlyDiagCtxt, Session, build_session, filesearc
use rustc_span::edition::{DEFAULT_EDITION, Edition};
use rustc_span::source_map::{RealFileLoader, SourceMapInputs};
use rustc_span::{FileName, SourceFileHashAlgorithm, sym};
use rustc_target::abi::Align;
use rustc_target::spec::{
CodeModel, FramePointer, LinkerFlavorCli, MergeFunctions, OnBrokenPipe, PanicStrategy,
RelocModel, RelroLevel, SanitizerSet, SplitDebuginfo, StackProtector, TlsModel, WasmCAbi,
Expand Down Expand Up @@ -807,6 +808,7 @@ fn test_unstable_options_tracking_hash() {
tracked!(location_detail, LocationDetail { file: true, line: false, column: false });
tracked!(maximal_hir_to_mir_coverage, true);
tracked!(merge_functions, Some(MergeFunctions::Disabled));
tracked!(min_function_alignment, Some(Align::EIGHT));
tracked!(mir_emit_retag, true);
tracked!(mir_enable_passes, vec![("DestProp".to_string(), false)]);
tracked!(mir_keep_place_mention, true);
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_session/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2892,6 +2892,7 @@ pub(crate) mod dep_tracking {
use std::num::NonZero;
use std::path::PathBuf;

use rustc_abi::Align;
use rustc_data_structures::fx::FxIndexMap;
use rustc_data_structures::stable_hasher::Hash64;
use rustc_errors::LanguageIdentifier;
Expand Down Expand Up @@ -3012,6 +3013,7 @@ pub(crate) mod dep_tracking {
InliningThreshold,
FunctionReturn,
WasmCAbi,
Align,
);

impl<T1, T2> DepTrackingHash for (T1, T2)
Expand Down
19 changes: 19 additions & 0 deletions compiler/rustc_session/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::num::{IntErrorKind, NonZero};
use std::path::PathBuf;
use std::str;

use rustc_abi::Align;
use rustc_data_structures::fx::FxIndexMap;
use rustc_data_structures::profiling::TimePassesFormat;
use rustc_data_structures::stable_hasher::Hash64;
Expand Down Expand Up @@ -482,6 +483,7 @@ mod desc {
pub(crate) const parse_wasm_c_abi: &str = "`legacy` or `spec`";
pub(crate) const parse_mir_include_spans: &str =
"either a boolean (`yes`, `no`, `on`, `off`, etc), or `nll` (default: `nll`)";
pub(crate) const parse_align: &str = "a number that is a power of 2 between 1 and 2^29";
}

pub mod parse {
Expand Down Expand Up @@ -1561,6 +1563,21 @@ pub mod parse {

true
}

pub(crate) fn parse_align(slot: &mut Option<Align>, v: Option<&str>) -> bool {
let mut bytes = 0u64;
if !parse_number(&mut bytes, v) {
return false;
}

let Ok(align) = Align::from_bytes(bytes) else {
return false;
};

*slot = Some(align);

true
}
}

options! {
Expand Down Expand Up @@ -1921,6 +1938,8 @@ options! {
"gather metadata statistics (default: no)"),
metrics_dir: Option<PathBuf> = (None, parse_opt_pathbuf, [UNTRACKED],
"the directory metrics emitted by rustc are dumped into (implicitly enables default set of metrics)"),
min_function_alignment: Option<Align> = (None, parse_align, [TRACKED],
"align all functions to at least this many bytes. Must be a power of 2"),
mir_emit_retag: bool = (false, parse_bool, [TRACKED],
"emit Retagging MIR statements, interpreted e.g., by miri; implies -Zmir-opt-level=0 \
(default: no)"),
Expand Down
24 changes: 24 additions & 0 deletions src/doc/unstable-book/src/compiler-flags/min-function-alignment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# `min-function-alignment`

The tracking issue for this feature is: https://github.com/rust-lang/rust/issues/82232.

------------------------

The `-Zmin-function-alignment=<align>` flag specifies the minimum alignment of functions for which code is generated.
The `align` value must be a power of 2, other values are rejected.

Note that `-Zbuild-std` (or similar) is required to apply this minimum alignment to standard library functions.
By default, these functions come precompiled and their alignments won't respect the `min-function-alignment` flag.

This flag is equivalent to:

- `-fmin-function-alignment` for [GCC](https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html#index-fmin-function-alignment_003dn)
- `-falign-functions` for [Clang](https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang1-falign-functions)

The specified alignment is a minimum. A higher alignment can be specified for specific functions by using the [`repr(align(...))`](https://github.com/rust-lang/rust/issues/82232) feature and annotating the function with a `#[repr(align(<align>))]` attribute. The attribute's value is ignored when it is lower than the value passed to `min-function-alignment`.

There are two additional edge cases for this flag:

- targets have a minimum alignment for functions (e.g. on x86_64 the lowest that LLVM generates is 16 bytes).
A `min-function-alignment` value lower than the target's minimum has no effect.
- the maximum alignment supported by rust (and LLVM) is `2^29`. Trying to set a higher value results in an error.
43 changes: 43 additions & 0 deletions tests/codegen/min-function-alignment.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//@ revisions: align16 align1024
//@ compile-flags: -C no-prepopulate-passes -Z mir-opt-level=0
//@ [align16] compile-flags: -Zmin-function-alignment=16
//@ [align1024] compile-flags: -Zmin-function-alignment=1024

#![crate_type = "lib"]
#![feature(fn_align)]

// functions without explicit alignment use the global minimum
//
// CHECK-LABEL: @no_explicit_align
// align16: align 16
// align1024: align 1024
#[no_mangle]
pub fn no_explicit_align() {}

// CHECK-LABEL: @lower_align
// align16: align 16
// align1024: align 1024
#[no_mangle]
#[repr(align(8))]
pub fn lower_align() {}

// the higher value of min-function-alignment and repr(align) wins out
//
// CHECK-LABEL: @higher_align
// align16: align 32
// align1024: align 1024
#[no_mangle]
#[repr(align(32))]
pub fn higher_align() {}

// cold functions follow the same rules as other functions
//
// in GCC, the `-falign-functions` does not apply to cold functions, but
// `-Zmin-function-alignment` applies to all functions.
//
// CHECK-LABEL: @no_explicit_align_cold
// align16: align 16
// align1024: align 1024
#[no_mangle]
#[cold]
pub fn no_explicit_align_cold() {}
44 changes: 44 additions & 0 deletions tests/codegen/naked-fn/min-function-alignment.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//@ compile-flags: -C no-prepopulate-passes -Copt-level=0 -Zmin-function-alignment=16
//@ needs-asm-support
//@ ignore-arm no "ret" mnemonic

#![feature(naked_functions, fn_align)]
#![crate_type = "lib"]

// functions without explicit alignment use the global minimum
//
// CHECK: .balign 16
#[no_mangle]
#[naked]
pub unsafe extern "C" fn naked_no_explicit_align() {
core::arch::naked_asm!("ret")
}

// CHECK: .balign 16
#[no_mangle]
#[repr(align(8))]
#[naked]
pub unsafe extern "C" fn naked_lower_align() {
core::arch::naked_asm!("ret")
}

// CHECK: .balign 32
#[no_mangle]
#[repr(align(32))]
#[naked]
pub unsafe extern "C" fn naked_higher_align() {
core::arch::naked_asm!("ret")
}

// cold functions follow the same rules as other functions
//
// in GCC, the `-falign-functions` does not apply to cold functions, but
// `-Zmin-function-alignment` applies to all functions.
//
// CHECK: .balign 16
#[no_mangle]
#[cold]
#[naked]
pub unsafe extern "C" fn no_explicit_align_cold() {
core::arch::naked_asm!("ret")
}

0 comments on commit b8e230a

Please sign in to comment.