Skip to content

Commit a12d31d

Browse files
committed
Auto merge of rust-lang#102963 - ilammy:xray-basic, r=estebank
Add `-Z instrument-xray` flag Implement MCP rust-lang/compiler-team#561, adding `-Z instrument-xray` flag which enables XRay instrumentation in LLVM.
2 parents 8996ea9 + 54b26f4 commit a12d31d

35 files changed

+312
-9
lines changed

compiler/rustc_codegen_llvm/src/attributes.rs

+33-5
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,8 @@ pub fn frame_pointer_type_attr<'ll>(cx: &CodegenCx<'ll, '_>) -> Option<&'ll Attr
118118

119119
/// Tell LLVM what instrument function to insert.
120120
#[inline]
121-
fn instrument_function_attr<'ll>(cx: &CodegenCx<'ll, '_>) -> Option<&'ll Attribute> {
121+
fn instrument_function_attr<'ll>(cx: &CodegenCx<'ll, '_>) -> SmallVec<[&'ll Attribute; 4]> {
122+
let mut attrs = SmallVec::new();
122123
if cx.sess().opts.unstable_opts.instrument_mcount {
123124
// Similar to `clang -pg` behavior. Handled by the
124125
// `post-inline-ee-instrument` LLVM pass.
@@ -127,14 +128,41 @@ fn instrument_function_attr<'ll>(cx: &CodegenCx<'ll, '_>) -> Option<&'ll Attribu
127128
// See test/CodeGen/mcount.c in clang.
128129
let mcount_name = cx.sess().target.mcount.as_ref();
129130

130-
Some(llvm::CreateAttrStringValue(
131+
attrs.push(llvm::CreateAttrStringValue(
131132
cx.llcx,
132133
"instrument-function-entry-inlined",
133134
&mcount_name,
134-
))
135-
} else {
136-
None
135+
));
136+
}
137+
if let Some(options) = &cx.sess().opts.unstable_opts.instrument_xray {
138+
// XRay instrumentation is similar to __cyg_profile_func_{enter,exit}.
139+
// Function prologue and epilogue are instrumented with NOP sleds,
140+
// a runtime library later replaces them with detours into tracing code.
141+
if options.always {
142+
attrs.push(llvm::CreateAttrStringValue(cx.llcx, "function-instrument", "xray-always"));
143+
}
144+
if options.never {
145+
attrs.push(llvm::CreateAttrStringValue(cx.llcx, "function-instrument", "xray-never"));
146+
}
147+
if options.ignore_loops {
148+
attrs.push(llvm::CreateAttrString(cx.llcx, "xray-ignore-loops"));
149+
}
150+
// LLVM will not choose the default for us, but rather requires specific
151+
// threshold in absence of "xray-always". Use the same default as Clang.
152+
let threshold = options.instruction_threshold.unwrap_or(200);
153+
attrs.push(llvm::CreateAttrStringValue(
154+
cx.llcx,
155+
"xray-instruction-threshold",
156+
&threshold.to_string(),
157+
));
158+
if options.skip_entry {
159+
attrs.push(llvm::CreateAttrString(cx.llcx, "xray-skip-entry"));
160+
}
161+
if options.skip_exit {
162+
attrs.push(llvm::CreateAttrString(cx.llcx, "xray-skip-exit"));
163+
}
137164
}
165+
attrs
138166
}
139167

140168
fn nojumptables_attr<'ll>(cx: &CodegenCx<'ll, '_>) -> Option<&'ll Attribute> {

compiler/rustc_error_messages/locales/en-US/session.ftl

+2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ session_profile_sample_use_file_does_not_exist = file `{$path}` passed to `-C pr
2525
2626
session_target_requires_unwind_tables = target requires unwind tables, they cannot be disabled with `-C force-unwind-tables=no`
2727
28+
session_instrumentation_not_supported = {$us} instrumentation is not supported for this target
29+
2830
session_sanitizer_not_supported = {$us} sanitizer is not supported for this target
2931
3032
session_sanitizers_not_supported = {$us} sanitizers are not supported for this target

compiler/rustc_interface/src/tests.rs

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use rustc_data_structures::fx::FxHashSet;
55
use rustc_errors::{emitter::HumanReadableErrorType, registry, ColorConfig};
66
use rustc_session::config::rustc_optgroups;
77
use rustc_session::config::Input;
8+
use rustc_session::config::InstrumentXRay;
89
use rustc_session::config::TraitSolver;
910
use rustc_session::config::{build_configuration, build_session_options, to_crate_config};
1011
use rustc_session::config::{
@@ -755,6 +756,7 @@ fn test_unstable_options_tracking_hash() {
755756
tracked!(inline_mir_threshold, Some(123));
756757
tracked!(instrument_coverage, Some(InstrumentCoverage::All));
757758
tracked!(instrument_mcount, true);
759+
tracked!(instrument_xray, Some(InstrumentXRay::default()));
758760
tracked!(link_only, true);
759761
tracked!(llvm_plugins, vec![String::from("plugin_name")]);
760762
tracked!(location_detail, LocationDetail { file: true, line: false, column: false });

compiler/rustc_session/src/config.rs

+23-3
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,25 @@ pub enum InstrumentCoverage {
174174
Off,
175175
}
176176

177+
/// Settings for `-Z instrument-xray` flag.
178+
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
179+
pub struct InstrumentXRay {
180+
/// `-Z instrument-xray=always`, force instrumentation
181+
pub always: bool,
182+
/// `-Z instrument-xray=never`, disable instrumentation
183+
pub never: bool,
184+
/// `-Z instrument-xray=ignore-loops`, ignore presence of loops,
185+
/// instrument functions based only on instruction count
186+
pub ignore_loops: bool,
187+
/// `-Z instrument-xray=instruction-threshold=N`, explicitly set instruction threshold
188+
/// for instrumentation, or `None` to use compiler's default
189+
pub instruction_threshold: Option<usize>,
190+
/// `-Z instrument-xray=skip-entry`, do not instrument function entry
191+
pub skip_entry: bool,
192+
/// `-Z instrument-xray=skip-exit`, do not instrument function exit
193+
pub skip_exit: bool,
194+
}
195+
177196
#[derive(Clone, PartialEq, Hash, Debug)]
178197
pub enum LinkerPluginLto {
179198
LinkerPlugin(PathBuf),
@@ -2805,9 +2824,9 @@ impl PpMode {
28052824
pub(crate) mod dep_tracking {
28062825
use super::{
28072826
BranchProtection, CFGuard, CFProtection, CrateType, DebugInfo, ErrorOutputType,
2808-
InstrumentCoverage, LdImpl, LinkerPluginLto, LocationDetail, LtoCli, OomStrategy, OptLevel,
2809-
OutputType, OutputTypes, Passes, SourceFileHashAlgorithm, SplitDwarfKind,
2810-
SwitchWithOptPath, SymbolManglingVersion, TraitSolver, TrimmedDefPaths,
2827+
InstrumentCoverage, InstrumentXRay, LdImpl, LinkerPluginLto, LocationDetail, LtoCli,
2828+
OomStrategy, OptLevel, OutputType, OutputTypes, Passes, SourceFileHashAlgorithm,
2829+
SplitDwarfKind, SwitchWithOptPath, SymbolManglingVersion, TraitSolver, TrimmedDefPaths,
28112830
};
28122831
use crate::lint;
28132832
use crate::options::WasiExecModel;
@@ -2876,6 +2895,7 @@ pub(crate) mod dep_tracking {
28762895
CodeModel,
28772896
TlsModel,
28782897
InstrumentCoverage,
2898+
InstrumentXRay,
28792899
CrateType,
28802900
MergeFunctions,
28812901
PanicStrategy,

compiler/rustc_session/src/errors.rs

+6
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,12 @@ pub struct ProfileSampleUseFileDoesNotExist<'a> {
7171
#[diag(session_target_requires_unwind_tables)]
7272
pub struct TargetRequiresUnwindTables;
7373

74+
#[derive(Diagnostic)]
75+
#[diag(session_instrumentation_not_supported)]
76+
pub struct InstrumentationNotSupported {
77+
pub us: String,
78+
}
79+
7480
#[derive(Diagnostic)]
7581
#[diag(session_sanitizer_not_supported)]
7682
pub struct SanitizerNotSupported {

compiler/rustc_session/src/options.rs

+73
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,7 @@ mod desc {
380380
pub const parse_dump_mono_stats: &str = "`markdown` (default) or `json`";
381381
pub const parse_instrument_coverage: &str =
382382
"`all` (default), `except-unused-generics`, `except-unused-functions`, or `off`";
383+
pub const parse_instrument_xray: &str = "either a boolean (`yes`, `no`, `on`, `off`, etc), or a comma separated list of settings: `always` or `never` (mutually exclusive), `ignore-loops`, `instruction-threshold=N`, `skip-entry`, `skip-exit`";
383384
pub const parse_unpretty: &str = "`string` or `string=string`";
384385
pub const parse_treat_err_as_bug: &str = "either no value or a number bigger than 0";
385386
pub const parse_trait_solver: &str =
@@ -869,6 +870,68 @@ mod parse {
869870
true
870871
}
871872

873+
pub(crate) fn parse_instrument_xray(
874+
slot: &mut Option<InstrumentXRay>,
875+
v: Option<&str>,
876+
) -> bool {
877+
if v.is_some() {
878+
let mut bool_arg = None;
879+
if parse_opt_bool(&mut bool_arg, v) {
880+
*slot = if bool_arg.unwrap() { Some(InstrumentXRay::default()) } else { None };
881+
return true;
882+
}
883+
}
884+
885+
let mut options = slot.get_or_insert_default();
886+
let mut seen_always = false;
887+
let mut seen_never = false;
888+
let mut seen_ignore_loops = false;
889+
let mut seen_instruction_threshold = false;
890+
let mut seen_skip_entry = false;
891+
let mut seen_skip_exit = false;
892+
for option in v.into_iter().map(|v| v.split(',')).flatten() {
893+
match option {
894+
"always" if !seen_always && !seen_never => {
895+
options.always = true;
896+
options.never = false;
897+
seen_always = true;
898+
}
899+
"never" if !seen_never && !seen_always => {
900+
options.never = true;
901+
options.always = false;
902+
seen_never = true;
903+
}
904+
"ignore-loops" if !seen_ignore_loops => {
905+
options.ignore_loops = true;
906+
seen_ignore_loops = true;
907+
}
908+
option
909+
if option.starts_with("instruction-threshold")
910+
&& !seen_instruction_threshold =>
911+
{
912+
let Some(("instruction-threshold", n)) = option.split_once('=') else {
913+
return false;
914+
};
915+
match n.parse() {
916+
Ok(n) => options.instruction_threshold = Some(n),
917+
Err(_) => return false,
918+
}
919+
seen_instruction_threshold = true;
920+
}
921+
"skip-entry" if !seen_skip_entry => {
922+
options.skip_entry = true;
923+
seen_skip_entry = true;
924+
}
925+
"skip-exit" if !seen_skip_exit => {
926+
options.skip_exit = true;
927+
seen_skip_exit = true;
928+
}
929+
_ => return false,
930+
}
931+
}
932+
true
933+
}
934+
872935
pub(crate) fn parse_treat_err_as_bug(slot: &mut Option<NonZeroUsize>, v: Option<&str>) -> bool {
873936
match v {
874937
Some(s) => {
@@ -1397,6 +1460,16 @@ options! {
13971460
`=off` (default)"),
13981461
instrument_mcount: bool = (false, parse_bool, [TRACKED],
13991462
"insert function instrument code for mcount-based tracing (default: no)"),
1463+
instrument_xray: Option<InstrumentXRay> = (None, parse_instrument_xray, [TRACKED],
1464+
"insert function instrument code for XRay-based tracing (default: no)
1465+
Optional extra settings:
1466+
`=always`
1467+
`=never`
1468+
`=ignore-loops`
1469+
`=instruction-threshold=N`
1470+
`=skip-entry`
1471+
`=skip-exit`
1472+
Multiple options can be combined with commas."),
14001473
keep_hygiene_data: bool = (false, parse_bool, [UNTRACKED],
14011474
"keep hygiene data after analysis (default: no)"),
14021475
layout_seed: Option<u64> = (None, parse_opt_number, [TRACKED],

compiler/rustc_session/src/session.rs

+4
Original file line numberDiff line numberDiff line change
@@ -1589,6 +1589,10 @@ fn validate_commandline_args_with_session_available(sess: &Session) {
15891589
{
15901590
sess.emit_err(errors::SplitDebugInfoUnstablePlatform { debuginfo: sess.split_debuginfo() });
15911591
}
1592+
1593+
if sess.opts.unstable_opts.instrument_xray.is_some() && !sess.target.options.supports_xray {
1594+
sess.emit_err(errors::InstrumentationNotSupported { us: "XRay".to_string() });
1595+
}
15921596
}
15931597

15941598
/// Holds data on the current incremental compilation session, if there is one.

compiler/rustc_target/src/spec/aarch64_linux_android.rs

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ pub fn target() -> Target {
1919
| SanitizerSet::MEMTAG
2020
| SanitizerSet::SHADOWCALLSTACK
2121
| SanitizerSet::ADDRESS,
22+
supports_xray: true,
2223
..super::android_base::opts()
2324
},
2425
}

compiler/rustc_target/src/spec/aarch64_unknown_linux_gnu.rs

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ pub fn target() -> Target {
1717
| SanitizerSet::MEMTAG
1818
| SanitizerSet::THREAD
1919
| SanitizerSet::HWADDRESS,
20+
supports_xray: true,
2021
..super::linux_gnu_base::opts()
2122
},
2223
}

compiler/rustc_target/src/spec/aarch64_unknown_linux_musl.rs

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::spec::{Target, TargetOptions};
33
pub fn target() -> Target {
44
let mut base = super::linux_musl_base::opts();
55
base.max_atomic_width = Some(128);
6+
base.supports_xray = true;
67

78
Target {
89
llvm_target: "aarch64-unknown-linux-musl".into(),

compiler/rustc_target/src/spec/mod.rs

+6
Original file line numberDiff line numberDiff line change
@@ -1718,6 +1718,9 @@ pub struct TargetOptions {
17181718
/// The ABI of entry function.
17191719
/// Default value is `Conv::C`, i.e. C call convention
17201720
pub entry_abi: Conv,
1721+
1722+
/// Whether the target supports XRay instrumentation.
1723+
pub supports_xray: bool,
17211724
}
17221725

17231726
/// Add arguments for the given flavor and also for its "twin" flavors
@@ -1937,6 +1940,7 @@ impl Default for TargetOptions {
19371940
supports_stack_protector: true,
19381941
entry_name: "main".into(),
19391942
entry_abi: Conv::C,
1943+
supports_xray: false,
19401944
}
19411945
}
19421946
}
@@ -2592,6 +2596,7 @@ impl Target {
25922596
key!(supports_stack_protector, bool);
25932597
key!(entry_name);
25942598
key!(entry_abi, Conv)?;
2599+
key!(supports_xray, bool);
25952600

25962601
if base.is_builtin {
25972602
// This can cause unfortunate ICEs later down the line.
@@ -2845,6 +2850,7 @@ impl ToJson for Target {
28452850
target_option_val!(supports_stack_protector);
28462851
target_option_val!(entry_name);
28472852
target_option_val!(entry_abi);
2853+
target_option_val!(supports_xray);
28482854

28492855
if let Some(abi) = self.default_adjusted_cabi {
28502856
d.insert("default-adjusted-cabi".into(), Abi::name(abi).to_json());

compiler/rustc_target/src/spec/x86_64_linux_android.rs

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ pub fn target() -> Target {
88
base.max_atomic_width = Some(64);
99
base.add_pre_link_args(LinkerFlavor::Gnu(Cc::Yes, Lld::No), &["-m64"]);
1010
base.stack_probes = StackProbeType::X86;
11+
base.supports_xray = true;
1112

1213
Target {
1314
llvm_target: "x86_64-linux-android".into(),

compiler/rustc_target/src/spec/x86_64_unknown_freebsd.rs

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ pub fn target() -> Target {
88
base.stack_probes = StackProbeType::X86;
99
base.supported_sanitizers =
1010
SanitizerSet::ADDRESS | SanitizerSet::CFI | SanitizerSet::MEMORY | SanitizerSet::THREAD;
11+
base.supports_xray = true;
1112

1213
Target {
1314
llvm_target: "x86_64-unknown-freebsd".into(),

compiler/rustc_target/src/spec/x86_64_unknown_linux_gnu.rs

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ pub fn target() -> Target {
1212
| SanitizerSet::LEAK
1313
| SanitizerSet::MEMORY
1414
| SanitizerSet::THREAD;
15+
base.supports_xray = true;
1516

1617
Target {
1718
llvm_target: "x86_64-unknown-linux-gnu".into(),

compiler/rustc_target/src/spec/x86_64_unknown_linux_musl.rs

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ pub fn target() -> Target {
1212
| SanitizerSet::LEAK
1313
| SanitizerSet::MEMORY
1414
| SanitizerSet::THREAD;
15+
base.supports_xray = true;
1516

1617
Target {
1718
llvm_target: "x86_64-unknown-linux-musl".into(),

compiler/rustc_target/src/spec/x86_64_unknown_netbsd.rs

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ pub fn target() -> Target {
1111
| SanitizerSet::LEAK
1212
| SanitizerSet::MEMORY
1313
| SanitizerSet::THREAD;
14+
base.supports_xray = true;
1415

1516
Target {
1617
llvm_target: "x86_64-unknown-netbsd".into(),

compiler/rustc_target/src/spec/x86_64_unknown_openbsd.rs

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ pub fn target() -> Target {
66
base.max_atomic_width = Some(64);
77
base.add_pre_link_args(LinkerFlavor::Gnu(Cc::Yes, Lld::No), &["-m64"]);
88
base.stack_probes = StackProbeType::X86;
9+
base.supports_xray = true;
910

1011
Target {
1112
llvm_target: "x86_64-unknown-openbsd".into(),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# `instrument-xray`
2+
3+
The tracking issue for this feature is: [#102921](https://github.com/rust-lang/rust/issues/102921).
4+
5+
------------------------
6+
7+
Enable generation of NOP sleds for XRay function tracing instrumentation.
8+
For more information on XRay,
9+
read [LLVM documentation](https://llvm.org/docs/XRay.html),
10+
and/or the [XRay whitepaper](http://research.google.com/pubs/pub45287.html).
11+
12+
Set the `-Z instrument-xray` compiler flag in order to enable XRay instrumentation.
13+
14+
- `-Z instrument-xray` – use the default settings
15+
- `-Z instrument-xray=skip-exit` – configure a custom setting
16+
- `-Z instrument-xray=ignore-loops,instruction-threshold=300`
17+
multiple settings separated by commas
18+
19+
Supported options:
20+
21+
- `always` – force instrumentation of all functions
22+
- `never` – do no instrument any functions
23+
- `ignore-loops` – ignore presence of loops,
24+
instrument functions based only on instruction count
25+
- `instruction-threshold=10` – set a different instruction threshold for instrumentation
26+
- `skip-entry` – do no instrument function entry
27+
- `skip-exit` – do no instrument function exit
28+
29+
The default settings are:
30+
31+
- instrument both entry & exit from functions
32+
- instrument functions with at least 200 instructions,
33+
or containing a non-trivial loop
34+
35+
Note that `-Z instrument-xray` only enables generation of NOP sleds
36+
which on their own don't do anything useful.
37+
In order to actually trace the functions,
38+
you will need to link a separate runtime library of your choice,
39+
such as Clang's [XRay Runtime Library](https://www.llvm.org/docs/XRay.html#xray-runtime-library).

0 commit comments

Comments
 (0)