Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ cargo-platform = { path = "crates/cargo-platform", version = "0.3.3" }
cargo-test-macro = { version = "0.4.10", path = "crates/cargo-test-macro" }
cargo-test-support = { version = "0.11.0", path = "crates/cargo-test-support" }
cargo-util = { version = "0.2.28", path = "crates/cargo-util" }
cargo-util-schemas = { version = "0.13.0", path = "crates/cargo-util-schemas" }
cargo-util-schemas = { version = "0.14.0", path = "crates/cargo-util-schemas" }
cargo_metadata = "0.23.1"
clap = "4.5.60"
clap_complete = { version = "4.5.66", features = ["unstable-dynamic"] }
Expand Down
2 changes: 1 addition & 1 deletion crates/cargo-util-schemas/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "cargo-util-schemas"
version = "0.13.1"
version = "0.14.0"
rust-version = "1.94" # MSRV:1
edition.workspace = true
license.workspace = true
Expand Down
7 changes: 7 additions & 0 deletions crates/cargo-util-schemas/manifest.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1437,6 +1437,13 @@
"null"
],
"default": null
},
"frame-pointers": {
"type": [
"string",
"null"
],
"default": null
}
}
},
Expand Down
5 changes: 5 additions & 0 deletions crates/cargo-util-schemas/src/manifest/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -941,6 +941,7 @@ pub struct TomlProfile {
pub trim_paths: Option<TomlTrimPaths>,
/// Unstable feature `hint-mostly-unused`
pub hint_mostly_unused: Option<bool>,
pub frame_pointers: Option<String>,
}

impl TomlProfile {
Expand Down Expand Up @@ -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());
}
}
}

Expand Down
11 changes: 10 additions & 1 deletion src/cargo/core/compiler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ pub use crate::core::compiler::unit::Unit;
pub use crate::core::compiler::unit::UnitIndex;
pub use crate::core::compiler::unit::UnitInterner;
use crate::core::manifest::TargetSourcePath;
use crate::core::profiles::{PanicStrategy, Profile, StripInner};
use crate::core::profiles::{FramePointers, PanicStrategy, Profile, StripInner};
use crate::core::{Feature, PackageId, Target, Verbosity};
use crate::lints::get_key_value;
use crate::util::OnceExt;
Expand Down Expand Up @@ -1228,6 +1228,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();
Expand Down Expand Up @@ -1450,6 +1451,14 @@ fn build_base_args(
cmd.arg("-C").arg(format!("strip={}", strip));
}

if let Some(frame_pointers) = frame_pointers {
let val = match frame_pointers {
FramePointers::ForceOn => "on",
FramePointers::ForceOff => "off",
};
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
Expand Down
40 changes: 39 additions & 1 deletion src/cargo/core/profiles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,15 @@ 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() {
"force-on" => Some(FramePointers::ForceOn),
"force-off" => Some(FramePointers::ForceOff),
"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),
Expand Down Expand Up @@ -633,6 +642,8 @@ pub struct Profile {
pub trim_paths: Option<TomlTrimPaths>,
#[serde(skip_serializing_if = "Option::is_none")]
pub hint_mostly_unused: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub frame_pointers: Option<FramePointers>,
}

impl Default for Profile {
Expand All @@ -655,6 +666,7 @@ impl Default for Profile {
rustflags: vec![],
trim_paths: None,
hint_mostly_unused: None,
frame_pointers: None,
}
}
}
Expand Down Expand Up @@ -685,6 +697,7 @@ compact_debug! {
rustflags
trim_paths
hint_mostly_unused
frame_pointers
)]
}
}
Expand Down Expand Up @@ -751,7 +764,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,
)
Expand Down Expand Up @@ -973,6 +991,26 @@ 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 {
ForceOn,
ForceOff,
}

impl fmt::Display for FramePointers {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
FramePointers::ForceOn => "force-on",
FramePointers::ForceOff => "force-off",
}
.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.
///
Expand Down
12 changes: 12 additions & 0 deletions src/cargo/util/toml/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2595,6 +2595,18 @@ pub fn validate_profile(
}
}

if let Some(frame_pointers) = &root.frame_pointers {
if frame_pointers != "force-on"
&& frame_pointers != "force-off"
&& frame_pointers != "default"
{
bail!(
"`frame-pointers` setting of `{frame_pointers}` is not a valid setting, \
must be `\"force-on\"`, `\"force-off\"`, or `\"default\"`.",
);
}
}

Ok(())
}

Expand Down
20 changes: 20 additions & 0 deletions src/doc/src/reference/profiles.md
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,26 @@ 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 = "force-on"
```

The valid options are:
- `"force-on"`: Force frame pointers to be enabled.
- `"force-off"`: Don't force frame pointers (the compiler may still emit them based on target defaults).
- `"default"`: Use the compiler's target-specific default.
Comment on lines +274 to +275
Copy link
Member

@weihanglo weihanglo Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is still a bit confusing. It sounds like depending target just like the default bvehavior. Though I guess it is probably more a rustc issue.


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
Expand Down
1 change: 1 addition & 0 deletions tests/testsuite/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1675,6 +1675,7 @@ fn all_profile_options() {
rustflags: None,
trim_paths: None,
hint_mostly_unused: None,
frame_pointers: Some("force-on".to_string()),
};
let mut overrides = BTreeMap::new();
let key = cargo_toml::ProfilePackageSpec::Spec(PackageIdSpec::parse("foo").unwrap());
Expand Down
133 changes: 133 additions & 0 deletions tests/testsuite/profiles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -956,3 +956,136 @@ fn profile_hint_mostly_unused_nightly() {
)
.run();
}

#[cargo_test]
fn frame_pointers_force_on() {
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.1.0"
edition = "2015"

[profile.release]
frame-pointers = "force-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_force_off() {
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.1.0"
edition = "2015"

[profile.release]
frame-pointers = "force-off"
"#,
)
.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=off [..]`
[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 = "force-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 `"force-on"`, `"force-off"`, or `"default"`.

"#]])
.run();
}