diff --git a/compiler/rustc_codegen_llvm/src/attributes.rs b/compiler/rustc_codegen_llvm/src/attributes.rs index f8454fd9960b6..f84cafb0c4fa0 100644 --- a/compiler/rustc_codegen_llvm/src/attributes.rs +++ b/compiler/rustc_codegen_llvm/src/attributes.rs @@ -472,7 +472,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=` flag; + // the alignment from a `#[repr(align())]` 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) { diff --git a/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs b/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs index cac3cc587cb45..8df270abc8173 100644 --- a/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs +++ b/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs @@ -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=` flag; + // the alignment from a `#[repr(align())]` 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 { ( diff --git a/compiler/rustc_interface/src/tests.rs b/compiler/rustc_interface/src/tests.rs index d103f7f45e26c..5648b9a231131 100644 --- a/compiler/rustc_interface/src/tests.rs +++ b/compiler/rustc_interface/src/tests.rs @@ -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, @@ -802,6 +803,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); diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index 4784a4d1953df..ea6bb18d5a5fc 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -2891,6 +2891,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; @@ -3011,6 +3012,7 @@ pub(crate) mod dep_tracking { InliningThreshold, FunctionReturn, WasmCAbi, + Align, ); impl DepTrackingHash for (T1, T2) diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index 3873a203bd36c..69cee65c5cdbd 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -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; @@ -455,6 +456,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 { @@ -1533,6 +1535,21 @@ pub mod parse { true } + + pub(crate) fn parse_align(slot: &mut Option, 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! { @@ -1888,6 +1905,8 @@ options! { "gather metadata statistics (default: no)"), metrics_dir: Option = (None, parse_opt_pathbuf, [UNTRACKED], "the directory metrics emitted by rustc are dumped into (implicitly enables default set of metrics)"), + min_function_alignment: Option = (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)"), diff --git a/tests/codegen/min-function-alignment.rs b/tests/codegen/min-function-alignment.rs new file mode 100644 index 0000000000000..48d32228b64ba --- /dev/null +++ b/tests/codegen/min-function-alignment.rs @@ -0,0 +1,30 @@ +//@ compile-flags: -C no-prepopulate-passes -Z mir-opt-level=0 -Zmin-function-alignment=16 + +#![crate_type = "lib"] +#![feature(fn_align)] + +// functions without explicit alignment use the global minimum +// +// CHECK: align 16 +#[no_mangle] +pub fn no_explicit_align() {} + +// CHECK: align 16 +#[no_mangle] +#[repr(align(8))] +pub fn lower_align() {} + +// CHECK: align 32 +#[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: align 16 +#[no_mangle] +#[cold] +pub fn no_explicit_align_cold() {} diff --git a/tests/codegen/naked-fn/min-function-alignment.rs b/tests/codegen/naked-fn/min-function-alignment.rs new file mode 100644 index 0000000000000..1330d796d397f --- /dev/null +++ b/tests/codegen/naked-fn/min-function-alignment.rs @@ -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") +}