diff --git a/Cargo.lock b/Cargo.lock index cd8960df300..03821af3d8b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -581,7 +581,7 @@ dependencies = [ [[package]] name = "cargo-util-schemas" -version = "0.14.3" +version = "0.15.0" dependencies = [ "jiff", "schemars", diff --git a/Cargo.toml b/Cargo.toml index b2fc229f6d9..5c31276f86c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,7 @@ cargo-platform = { path = "crates/cargo-platform", version = "0.3.3" } cargo-test-macro = { version = "0.4.14", path = "crates/cargo-test-macro" } cargo-test-support = { version = "0.11.4", path = "crates/cargo-test-support" } cargo-util = { version = "0.2.32", path = "crates/cargo-util" } -cargo-util-schemas = { version = "0.14.2", path = "crates/cargo-util-schemas" } +cargo-util-schemas = { version = "0.15.0", path = "crates/cargo-util-schemas" } cargo-util-terminal = { version = "0.1.2", path = "crates/cargo-util-terminal" } cargo_metadata = "0.23.1" clap = "4.6.0" diff --git a/crates/cargo-util-schemas/Cargo.toml b/crates/cargo-util-schemas/Cargo.toml index 8ed89cc2f77..29c37c76146 100644 --- a/crates/cargo-util-schemas/Cargo.toml +++ b/crates/cargo-util-schemas/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cargo-util-schemas" -version = "0.14.3" +version = "0.15.0" rust-version = "1.97" # MSRV:1 edition.workspace = true license.workspace = true diff --git a/crates/cargo-util-schemas/manifest.schema.json b/crates/cargo-util-schemas/manifest.schema.json index 219ff8a9793..33535e6c696 100644 --- a/crates/cargo-util-schemas/manifest.schema.json +++ b/crates/cargo-util-schemas/manifest.schema.json @@ -1437,6 +1437,13 @@ "null" ], "default": null + }, + "frame-pointers": { + "type": [ + "string", + "null" + ], + "default": null } } }, diff --git a/crates/cargo-util-schemas/src/manifest/mod.rs b/crates/cargo-util-schemas/src/manifest/mod.rs index 2b0ca539b0b..eb40526b627 100644 --- a/crates/cargo-util-schemas/src/manifest/mod.rs +++ b/crates/cargo-util-schemas/src/manifest/mod.rs @@ -941,6 +941,7 @@ pub struct TomlProfile { pub trim_paths: Option, /// Unstable feature `hint-mostly-unused` pub hint_mostly_unused: Option, + pub frame_pointers: Option, } impl TomlProfile { @@ -1036,6 +1037,10 @@ impl TomlProfile { if let Some(v) = profile.hint_mostly_unused { self.hint_mostly_unused = Some(v); } + + if let Some(v) = &profile.frame_pointers { + self.frame_pointers = Some(v.clone()); + } } } diff --git a/doc/book/src/reference/profiles.md b/doc/book/src/reference/profiles.md index 5ad011e2af2..9538c058a1f 100644 --- a/doc/book/src/reference/profiles.md +++ b/doc/book/src/reference/profiles.md @@ -258,6 +258,25 @@ whether or not [`rpath`] is enabled. [`-C rpath` flag]: ../../rustc/codegen-options/index.html#rpath [`rpath`]: https://en.wikipedia.org/wiki/Rpath +### frame-pointers + +The `frame-pointers` setting controls the [`-C force-frame-pointers` flag] +which controls whether frame pointers are forced in generated code. Frame +pointers are useful for profiling as they enable reliable stack unwinding. + +```toml +[profile.release] +frame-pointers = "on" +``` + +The valid options are: +- `"on"`: Force frame pointers to be enabled. +- `"default"`: Use the compiler's target-specific default. + +When not specified, the compiler's target-specific default is used. + +[`-C force-frame-pointers` flag]: ../../rustc/codegen-options/index.html#force-frame-pointers + ## Default profiles ### dev diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs index 3f406a15e93..233fe92895a 100644 --- a/src/compiler/mod.rs +++ b/src/compiler/mod.rs @@ -110,7 +110,7 @@ use crate::util::interning::InternedString; use crate::util::machine_message::{self, Message}; use crate::util::{add_path_args, internal, path_args}; use crate::workspace::manifest::TargetSourcePath; -use crate::workspace::profiles::{PanicStrategy, Profile, StripInner}; +use crate::workspace::profiles::{FramePointers, PanicStrategy, Profile, StripInner}; use crate::workspace::{Feature, PackageId, Target}; use cargo_util::{ProcessBuilder, ProcessError, paths}; @@ -1264,6 +1264,7 @@ fn build_base_args( rustflags: profile_rustflags, trim_paths, hint_mostly_unused: profile_hint_mostly_unused, + frame_pointers, .. } = unit.profile.clone(); let hints = unit.pkg.hints().cloned().unwrap_or_default(); @@ -1493,6 +1494,13 @@ fn build_base_args( cmd.arg("-C").arg(format!("strip={}", strip)); } + if let Some(frame_pointers) = frame_pointers { + let val = match frame_pointers { + FramePointers::On => "on", + }; + cmd.arg("-C").arg(format!("force-frame-pointers={}", val)); + } + if unit.is_std { // -Zforce-unstable-if-unmarked prevents the accidental use of // unstable crates within the sysroot (such as "extern crate libc" or diff --git a/src/workspace/parser/mod.rs b/src/workspace/parser/mod.rs index 6e57423d0b6..1d48e813473 100644 --- a/src/workspace/parser/mod.rs +++ b/src/workspace/parser/mod.rs @@ -2605,6 +2605,15 @@ pub fn validate_profile( } } + if let Some(frame_pointers) = &root.frame_pointers { + if frame_pointers != "on" && frame_pointers != "default" { + bail!( + "`frame-pointers` setting of `{frame_pointers}` is not a valid setting, \ + must be `\"on\"` or `\"default\"`.", + ); + } + } + Ok(()) } diff --git a/src/workspace/profiles.rs b/src/workspace/profiles.rs index 1f2509a8911..5219c5f34c9 100644 --- a/src/workspace/profiles.rs +++ b/src/workspace/profiles.rs @@ -587,6 +587,14 @@ fn merge_profile(profile: &mut Profile, toml: &TomlProfile) { if let Some(hint_mostly_unused) = toml.hint_mostly_unused { profile.hint_mostly_unused = Some(hint_mostly_unused); } + if let Some(ref frame_pointers) = toml.frame_pointers { + profile.frame_pointers = match frame_pointers.as_str() { + "on" => Some(FramePointers::On), + "default" => None, + // This should be validated in TomlProfile::validate + _ => panic!("invalid frame-pointers value `{}`", frame_pointers), + }; + } profile.strip = match toml.strip { Some(StringOrBool::Bool(true)) => Strip::Resolved(StripInner::Named("symbols".into())), Some(StringOrBool::Bool(false)) => Strip::Resolved(StripInner::None), @@ -638,6 +646,8 @@ pub struct Profile { pub trim_paths: Option, #[serde(skip_serializing_if = "Option::is_none")] pub hint_mostly_unused: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub frame_pointers: Option, } impl Default for Profile { @@ -660,6 +670,7 @@ impl Default for Profile { rustflags: vec![], trim_paths: None, hint_mostly_unused: None, + frame_pointers: None, } } } @@ -690,6 +701,7 @@ compact_debug! { rustflags trim_paths hint_mostly_unused + frame_pointers )] } } @@ -756,7 +768,12 @@ impl Profile { self.debug_assertions, self.overflow_checks, self.rpath, - (self.incremental, self.panic, self.strip), + ( + self.incremental, + self.panic, + self.strip, + self.frame_pointers, + ), &self.rustflags, &self.trim_paths, ) @@ -978,6 +995,24 @@ impl Ord for Strip { } } +/// The setting for controlling frame pointers in generated code. +#[derive( + Clone, Copy, PartialEq, Eq, Debug, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize, +)] +#[serde(rename_all = "kebab-case")] +pub enum FramePointers { + On, +} + +impl fmt::Display for FramePointers { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + FramePointers::On => "on", + } + .fmt(f) + } +} + /// Flags used in creating `Unit`s to indicate the purpose for the target, and /// to ensure the target's dependencies have the correct settings. /// diff --git a/tests/testsuite/config.rs b/tests/testsuite/config.rs index c1c1c96e308..0a0df1f3ed2 100644 --- a/tests/testsuite/config.rs +++ b/tests/testsuite/config.rs @@ -1677,6 +1677,7 @@ fn all_profile_options() { rustflags: None, trim_paths: None, hint_mostly_unused: None, + frame_pointers: Some("on".to_string()), }; let mut overrides = BTreeMap::new(); let key = cargo_toml::ProfilePackageSpec::Spec(PackageIdSpec::parse("foo").unwrap()); diff --git a/tests/testsuite/profile_settings.rs b/tests/testsuite/profile_settings.rs index 8ede705121f..8fe714c2fb4 100644 --- a/tests/testsuite/profile_settings.rs +++ b/tests/testsuite/profile_settings.rs @@ -957,3 +957,108 @@ fn profile_hint_mostly_unused_nightly() { ) .run(); } + +#[cargo_test] +fn frame_pointers_on() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + edition = "2015" + + [profile.release] + frame-pointers = "on" + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("build --release -v") + .with_stderr_data(str![[r#" +[COMPILING] foo v0.1.0 ([ROOT]/foo) +[RUNNING] `rustc [..] -C force-frame-pointers=on [..]` +[FINISHED] `release` profile [optimized] target(s) in [ELAPSED]s + +"#]]) + .run(); +} + +#[cargo_test] +fn frame_pointers_unspecified() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + edition = "2015" + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("build -v") + .with_stderr_does_not_contain("[RUNNING] `rustc [..] -C force-frame-pointers[..]`") + .run(); +} + +#[cargo_test] +fn frame_pointers_default_overrides_parent() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + edition = "2015" + + [profile.release] + frame-pointers = "on" + + [profile.myprofile] + inherits = "release" + frame-pointers = "default" + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("build --profile myprofile -v") + .with_stderr_does_not_contain("[RUNNING] `rustc [..] -C force-frame-pointers[..]`") + .run(); +} + +#[cargo_test] +fn frame_pointers_invalid_value() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + edition = "2015" + + [profile.release] + frame-pointers = "invalid" + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("build --release") + .with_status(101) + .with_stderr_data(str![[r#" +[ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml` + +Caused by: + `frame-pointers` setting of `invalid` is not a valid setting, must be `"on"` or `"default"`. + +"#]]) + .run(); +}