Add -Z small-data-threshold

This flag allows specifying the threshold size above which LLVM should
not consider placing small objects in a .sdata or .sbss section.

Support is indicated in the target options via the
small-data-threshold-support target option, which can indicate either an
LLVM argument or an LLVM module flag.  To avoid duplicate specifications
in a large number of targets, the default value for support is
DefaultForArch, which is translated to a concrete value according to the
target's architecture.
This commit is contained in:
Paul Menage 2024-09-10 12:19:16 -07:00
parent 33855f80d4
commit 3810386bbe
10 changed files with 241 additions and 4 deletions

View File

@ -27,7 +27,7 @@ use rustc_span::source_map::Spanned;
use rustc_span::{Span, DUMMY_SP};
use rustc_target::abi::call::FnAbi;
use rustc_target::abi::{HasDataLayout, TargetDataLayout, VariantIdx};
use rustc_target::spec::{HasTargetSpec, RelocModel, Target, TlsModel};
use rustc_target::spec::{HasTargetSpec, RelocModel, SmallDataThresholdSupport, Target, TlsModel};
use smallvec::SmallVec;
use crate::back::write::to_llvm_code_model;
@ -387,6 +387,24 @@ pub(crate) unsafe fn create_module<'ll>(
}
}
match (sess.opts.unstable_opts.small_data_threshold, sess.target.small_data_threshold_support())
{
// Set up the small-data optimization limit for architectures that use
// an LLVM module flag to control this.
(Some(threshold), SmallDataThresholdSupport::LlvmModuleFlag(flag)) => {
let flag = SmallCStr::new(flag.as_ref());
unsafe {
llvm::LLVMRustAddModuleFlagU32(
llmod,
llvm::LLVMModFlagBehavior::Error,
flag.as_c_str().as_ptr(),
threshold as u32,
)
}
}
_ => (),
};
// Insert `llvm.ident` metadata.
//
// On the wasm targets it will get hooked up to the "producer" sections

View File

@ -14,7 +14,7 @@ use rustc_middle::bug;
use rustc_session::config::{PrintKind, PrintRequest};
use rustc_session::Session;
use rustc_span::symbol::Symbol;
use rustc_target::spec::{MergeFunctions, PanicStrategy};
use rustc_target::spec::{MergeFunctions, PanicStrategy, SmallDataThresholdSupport};
use rustc_target::target_features::{RUSTC_SPECIAL_FEATURES, RUSTC_SPECIFIC_FEATURES};
use crate::back::write::create_informational_target_machine;
@ -125,6 +125,18 @@ unsafe fn configure_llvm(sess: &Session) {
for arg in sess_args {
add(&(*arg), true);
}
match (
sess.opts.unstable_opts.small_data_threshold,
sess.target.small_data_threshold_support(),
) {
// Set up the small-data optimization limit for architectures that use
// an LLVM argument to control this.
(Some(threshold), SmallDataThresholdSupport::LlvmArg(arg)) => {
add(&format!("--{arg}={threshold}"), false)
}
_ => (),
};
}
if sess.opts.unstable_opts.llvm_time_trace {

View File

@ -847,6 +847,7 @@ fn test_unstable_options_tracking_hash() {
tracked!(share_generics, Some(true));
tracked!(show_span, Some(String::from("abc")));
tracked!(simulate_remapped_rust_src_base, Some(PathBuf::from("/rustc/abc")));
tracked!(small_data_threshold, Some(16));
tracked!(split_lto_unit, Some(true));
tracked!(src_hash_algorithm, Some(SourceFileHashAlgorithm::Sha1));
tracked!(stack_protector, StackProtector::All);

View File

@ -113,6 +113,8 @@ session_split_lto_unit_requires_lto = `-Zsplit-lto-unit` requires `-Clto`, `-Clt
session_target_requires_unwind_tables = target requires unwind tables, they cannot be disabled with `-C force-unwind-tables=no`
session_target_small_data_threshold_not_supported = `-Z small-data-threshold` is not supported for target {$target_triple} and will be ignored
session_target_stack_protector_not_supported = `-Z stack-protector={$stack_protector}` is not supported for target {$target_triple} and will be ignored
session_unleashed_feature_help_named = skipping check for `{$gate}` feature

View File

@ -182,6 +182,12 @@ pub(crate) struct StackProtectorNotSupportedForTarget<'a> {
pub(crate) target_triple: &'a TargetTriple,
}
#[derive(Diagnostic)]
#[diag(session_target_small_data_threshold_not_supported)]
pub(crate) struct SmallDataThresholdNotSupportedForTarget<'a> {
pub(crate) target_triple: &'a TargetTriple,
}
#[derive(Diagnostic)]
#[diag(session_branch_protection_requires_aarch64)]
pub(crate) struct BranchProtectionRequiresAArch64;

View File

@ -2014,6 +2014,8 @@ written to standard error output)"),
simulate_remapped_rust_src_base: Option<PathBuf> = (None, parse_opt_pathbuf, [TRACKED],
"simulate the effect of remap-debuginfo = true at bootstrapping by remapping path \
to rust's source base directory. only meant for testing purposes"),
small_data_threshold: Option<usize> = (None, parse_opt_number, [TRACKED],
"Set the threshold for objects to be stored in a \"small data\" section"),
span_debug: bool = (false, parse_bool, [UNTRACKED],
"forward proc_macro::Span's `Debug` impl to `Span`"),
/// o/w tests have closure@path

View File

@ -30,8 +30,8 @@ use rustc_span::source_map::{FilePathMapping, SourceMap};
use rustc_span::{FileNameDisplayPreference, RealFileName, Span, Symbol};
use rustc_target::asm::InlineAsmArch;
use rustc_target::spec::{
CodeModel, DebuginfoKind, PanicStrategy, RelocModel, RelroLevel, SanitizerSet, SplitDebuginfo,
StackProtector, Target, TargetTriple, TlsModel,
CodeModel, DebuginfoKind, PanicStrategy, RelocModel, RelroLevel, SanitizerSet,
SmallDataThresholdSupport, SplitDebuginfo, StackProtector, Target, TargetTriple, TlsModel,
};
use crate::code_stats::CodeStats;
@ -1278,6 +1278,14 @@ fn validate_commandline_args_with_session_available(sess: &Session) {
}
}
if sess.opts.unstable_opts.small_data_threshold.is_some() {
if sess.target.small_data_threshold_support() == SmallDataThresholdSupport::None {
sess.dcx().emit_warn(errors::SmallDataThresholdNotSupportedForTarget {
target_triple: &sess.opts.target_triple,
})
}
}
if sess.opts.unstable_opts.branch_protection.is_some() && sess.target.arch != "aarch64" {
sess.dcx().emit_err(errors::BranchProtectionRequiresAArch64);
}

View File

@ -855,6 +855,43 @@ impl ToJson for RelroLevel {
}
}
#[derive(Clone, Debug, PartialEq, Hash)]
pub enum SmallDataThresholdSupport {
None,
DefaultForArch,
LlvmModuleFlag(StaticCow<str>),
LlvmArg(StaticCow<str>),
}
impl FromStr for SmallDataThresholdSupport {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s == "none" {
Ok(Self::None)
} else if s == "default-for-arch" {
Ok(Self::DefaultForArch)
} else if let Some(flag) = s.strip_prefix("llvm-module-flag=") {
Ok(Self::LlvmModuleFlag(flag.to_string().into()))
} else if let Some(arg) = s.strip_prefix("llvm-arg=") {
Ok(Self::LlvmArg(arg.to_string().into()))
} else {
Err(())
}
}
}
impl ToJson for SmallDataThresholdSupport {
fn to_json(&self) -> Value {
match self {
Self::None => "none".to_json(),
Self::DefaultForArch => "default-for-arch".to_json(),
Self::LlvmModuleFlag(flag) => format!("llvm-module-flag={flag}").to_json(),
Self::LlvmArg(arg) => format!("llvm-arg={arg}").to_json(),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Hash)]
pub enum MergeFunctions {
Disabled,
@ -2392,6 +2429,9 @@ pub struct TargetOptions {
/// Whether the target supports XRay instrumentation.
pub supports_xray: bool,
/// Whether the targets supports -Z small-data-threshold
small_data_threshold_support: SmallDataThresholdSupport,
}
/// Add arguments for the given flavor and also for its "twin" flavors
@ -2609,6 +2649,7 @@ impl Default for TargetOptions {
entry_name: "main".into(),
entry_abi: Conv::C,
supports_xray: false,
small_data_threshold_support: SmallDataThresholdSupport::DefaultForArch,
}
}
}
@ -2884,6 +2925,15 @@ impl Target {
Some(Ok(()))
})).unwrap_or(Ok(()))
} );
($key_name:ident, SmallDataThresholdSupport) => ( {
obj.remove("small-data-threshold-support").and_then(|o| o.as_str().and_then(|s| {
match s.parse::<SmallDataThresholdSupport>() {
Ok(support) => base.small_data_threshold_support = support,
_ => return Some(Err(format!("'{s}' is not a valid value for small-data-threshold-support."))),
}
Some(Ok(()))
})).unwrap_or(Ok(()))
} );
($key_name:ident, PanicStrategy) => ( {
let name = (stringify!($key_name)).replace("_", "-");
obj.remove(&name).and_then(|o| o.as_str().and_then(|s| {
@ -3321,6 +3371,7 @@ impl Target {
key!(supported_sanitizers, SanitizerSet)?;
key!(generate_arange_section, bool);
key!(supports_stack_protector, bool);
key!(small_data_threshold_support, SmallDataThresholdSupport)?;
key!(entry_name);
key!(entry_abi, Conv)?;
key!(supports_xray, bool);
@ -3415,6 +3466,30 @@ impl Target {
}
}
}
/// Return the target's small data threshold support, converting
/// `DefaultForArch` into a concrete value.
pub fn small_data_threshold_support(&self) -> SmallDataThresholdSupport {
match &self.options.small_data_threshold_support {
// Avoid having to duplicate the small data support in every
// target file by supporting a default value for each
// architecture.
SmallDataThresholdSupport::DefaultForArch => match self.arch.as_ref() {
"mips" | "mips64" | "mips32r6" => {
SmallDataThresholdSupport::LlvmArg("mips-ssection-threshold".into())
}
"hexagon" => {
SmallDataThresholdSupport::LlvmArg("hexagon-small-data-threshold".into())
}
"m68k" => SmallDataThresholdSupport::LlvmArg("m68k-ssection-threshold".into()),
"riscv32" | "riscv64" => {
SmallDataThresholdSupport::LlvmModuleFlag("SmallDataLimit".into())
}
_ => SmallDataThresholdSupport::None,
},
s => s.clone(),
}
}
}
impl ToJson for Target {
@ -3577,6 +3652,7 @@ impl ToJson for Target {
target_option_val!(c_enum_min_bits);
target_option_val!(generate_arange_section);
target_option_val!(supports_stack_protector);
target_option_val!(small_data_threshold_support);
target_option_val!(entry_name);
target_option_val!(entry_abi);
target_option_val!(supports_xray);

View File

@ -0,0 +1,20 @@
# `small-data-threshold`
-----------------------
This flag controls the maximum static variable size that may be included in the
"small data sections" (.sdata, .sbss) supported by some architectures (RISCV,
MIPS, M68K, Hexagon). Can be set to `0` to disable the use of small data
sections.
Target support is indicated by the `small_data_threshold_support` target
option which can be:
- `none` (`SmallDataThresholdSupport::None`) for no support
- `default-for-arch` (`SmallDataThresholdSupport::DefaultForArch`) which
is automatically translated into an appropriate value for the target.
- `llvm-module-flag=<flag_name>`
(`SmallDataThresholdSupport::LlvmModuleFlag`) for specifying the
threshold via an LLVM module flag
- `llvm-arg=<arg_name>` (`SmallDataThresholdSupport::LlvmArg`) for
specifying the threshold via an LLVM argument.

View File

@ -0,0 +1,92 @@
// Test for -Z small_data_threshold=...
//@ revisions: RISCV MIPS HEXAGON M68K
//@ assembly-output: emit-asm
//@ compile-flags: -Z small_data_threshold=4
//@ [RISCV] compile-flags: --target=riscv32im-unknown-none-elf
//@ [RISCV] needs-llvm-components: riscv
//@ [MIPS] compile-flags: --target=mips-unknown-linux-uclibc -C relocation-model=static
//@ [MIPS] compile-flags: -C llvm-args=-mgpopt -C llvm-args=-mlocal-sdata
//@ [MIPS] compile-flags: -C target-feature=+noabicalls
//@ [MIPS] needs-llvm-components: mips
//@ [HEXAGON] compile-flags: --target=hexagon-unknown-linux-musl -C target-feature=+small-data
//@ [HEXAGON] compile-flags: -C llvm-args=--hexagon-statics-in-small-data
//@ [HEXAGON] needs-llvm-components: hexagon
//@ [M68K] compile-flags: --target=m68k-unknown-linux-gnu
//@ [M68K] needs-llvm-components: m68k
#![feature(no_core, lang_items)]
#![no_std]
#![no_core]
#![crate_type = "lib"]
#[lang = "sized"]
trait Sized {}
#[lang = "drop_in_place"]
fn drop_in_place<T>(_: *mut T) {}
#[used]
#[no_mangle]
// U is below the threshold, should be in sdata
static mut U: u16 = 123;
#[used]
#[no_mangle]
// V is below the threshold, should be in sbss
static mut V: u16 = 0;
#[used]
#[no_mangle]
// W is at the threshold, should be in sdata
static mut W: u32 = 123;
#[used]
#[no_mangle]
// X is at the threshold, should be in sbss
static mut X: u32 = 0;
#[used]
#[no_mangle]
// Y is over the threshold, should be in its own .data section
static mut Y: u64 = 123;
#[used]
#[no_mangle]
// Z is over the threshold, should be in its own .bss section
static mut Z: u64 = 0;
// Currently, only MIPS and RISCV successfully put any objects in the small data
// sections so the U/V/W/X tests are skipped on Hexagon and M68K
//@ RISCV: .section .sdata,
//@ RISCV-NOT: .section
//@ RISCV: U:
//@ RISCV: .section .sbss
//@ RISCV-NOT: .section
//@ RISCV: V:
//@ RISCV: .section .sdata
//@ RISCV-NOT: .section
//@ RISCV: W:
//@ RISCV: .section .sbss
//@ RISCV-NOT: .section
//@ RISCV: X:
//@ MIPS: .section .sdata,
//@ MIPS-NOT: .section
//@ MIPS: U:
//@ MIPS: .section .sbss
//@ MIPS-NOT: .section
//@ MIPS: V:
//@ MIPS: .section .sdata
//@ MIPS-NOT: .section
//@ MIPS: W:
//@ MIPS: .section .sbss
//@ MIPS-NOT: .section
//@ MIPS: X:
//@ CHECK: .section .data.Y,
//@ CHECK-NOT: .section
//@ CHECK: Y:
//@ CHECK: .section .bss.Z,
//@ CHECK-NOT: .section
//@ CHECK: Z: