about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_codegen_llvm/src/attributes.rs38
-rw-r--r--compiler/rustc_error_messages/locales/en-US/session.ftl2
-rw-r--r--compiler/rustc_interface/src/tests.rs2
-rw-r--r--compiler/rustc_session/src/config.rs26
-rw-r--r--compiler/rustc_session/src/errors.rs6
-rw-r--r--compiler/rustc_session/src/options.rs73
-rw-r--r--compiler/rustc_session/src/session.rs4
-rw-r--r--compiler/rustc_target/src/spec/aarch64_linux_android.rs1
-rw-r--r--compiler/rustc_target/src/spec/aarch64_unknown_linux_gnu.rs1
-rw-r--r--compiler/rustc_target/src/spec/aarch64_unknown_linux_musl.rs1
-rw-r--r--compiler/rustc_target/src/spec/mod.rs6
-rw-r--r--compiler/rustc_target/src/spec/x86_64_linux_android.rs1
-rw-r--r--compiler/rustc_target/src/spec/x86_64_unknown_freebsd.rs1
-rw-r--r--compiler/rustc_target/src/spec/x86_64_unknown_linux_gnu.rs1
-rw-r--r--compiler/rustc_target/src/spec/x86_64_unknown_linux_musl.rs1
-rw-r--r--compiler/rustc_target/src/spec/x86_64_unknown_netbsd.rs1
-rw-r--r--compiler/rustc_target/src/spec/x86_64_unknown_openbsd.rs1
-rw-r--r--src/doc/unstable-book/src/compiler-flags/instrument-xray.md39
-rw-r--r--src/tools/compiletest/src/header.rs2
-rw-r--r--src/tools/compiletest/src/util.rs13
-rw-r--r--src/tools/tidy/src/ui_tests.rs2
-rw-r--r--tests/codegen/instrument-xray/basic.rs9
-rw-r--r--tests/codegen/instrument-xray/options-combine.rs12
-rw-r--r--tests/codegen/instrument-xray/options-override.rs11
-rw-r--r--tests/rustdoc-ui/z-help.stdout9
-rw-r--r--tests/ui/instrument-xray/flags-always-never-1.rs7
-rw-r--r--tests/ui/instrument-xray/flags-always-never-1.stderr2
-rw-r--r--tests/ui/instrument-xray/flags-always-never-2.rs9
-rw-r--r--tests/ui/instrument-xray/flags-basic.rs9
-rw-r--r--tests/ui/instrument-xray/flags-dupe-always.rs7
-rw-r--r--tests/ui/instrument-xray/flags-dupe-always.stderr2
-rw-r--r--tests/ui/instrument-xray/flags-dupe-ignore-loops.rs7
-rw-r--r--tests/ui/instrument-xray/flags-dupe-ignore-loops.stderr2
-rw-r--r--tests/ui/instrument-xray/target-not-supported.rs9
-rw-r--r--tests/ui/instrument-xray/target-not-supported.stderr4
35 files changed, 312 insertions, 9 deletions
diff --git a/compiler/rustc_codegen_llvm/src/attributes.rs b/compiler/rustc_codegen_llvm/src/attributes.rs
index 54ac7a46cf2..7a4ec494c8e 100644
--- a/compiler/rustc_codegen_llvm/src/attributes.rs
+++ b/compiler/rustc_codegen_llvm/src/attributes.rs
@@ -118,7 +118,8 @@ pub fn frame_pointer_type_attr<'ll>(cx: &CodegenCx<'ll, '_>) -> Option<&'ll Attr
 
 /// Tell LLVM what instrument function to insert.
 #[inline]
-fn instrument_function_attr<'ll>(cx: &CodegenCx<'ll, '_>) -> Option<&'ll Attribute> {
+fn instrument_function_attr<'ll>(cx: &CodegenCx<'ll, '_>) -> SmallVec<[&'ll Attribute; 4]> {
+    let mut attrs = SmallVec::new();
     if cx.sess().opts.unstable_opts.instrument_mcount {
         // Similar to `clang -pg` behavior. Handled by the
         // `post-inline-ee-instrument` LLVM pass.
@@ -127,14 +128,41 @@ fn instrument_function_attr<'ll>(cx: &CodegenCx<'ll, '_>) -> Option<&'ll Attribu
         // See test/CodeGen/mcount.c in clang.
         let mcount_name = cx.sess().target.mcount.as_ref();
 
-        Some(llvm::CreateAttrStringValue(
+        attrs.push(llvm::CreateAttrStringValue(
             cx.llcx,
             "instrument-function-entry-inlined",
             &mcount_name,
-        ))
-    } else {
-        None
+        ));
+    }
+    if let Some(options) = &cx.sess().opts.unstable_opts.instrument_xray {
+        // XRay instrumentation is similar to __cyg_profile_func_{enter,exit}.
+        // Function prologue and epilogue are instrumented with NOP sleds,
+        // a runtime library later replaces them with detours into tracing code.
+        if options.always {
+            attrs.push(llvm::CreateAttrStringValue(cx.llcx, "function-instrument", "xray-always"));
+        }
+        if options.never {
+            attrs.push(llvm::CreateAttrStringValue(cx.llcx, "function-instrument", "xray-never"));
+        }
+        if options.ignore_loops {
+            attrs.push(llvm::CreateAttrString(cx.llcx, "xray-ignore-loops"));
+        }
+        // LLVM will not choose the default for us, but rather requires specific
+        // threshold in absence of "xray-always". Use the same default as Clang.
+        let threshold = options.instruction_threshold.unwrap_or(200);
+        attrs.push(llvm::CreateAttrStringValue(
+            cx.llcx,
+            "xray-instruction-threshold",
+            &threshold.to_string(),
+        ));
+        if options.skip_entry {
+            attrs.push(llvm::CreateAttrString(cx.llcx, "xray-skip-entry"));
+        }
+        if options.skip_exit {
+            attrs.push(llvm::CreateAttrString(cx.llcx, "xray-skip-exit"));
+        }
     }
+    attrs
 }
 
 fn nojumptables_attr<'ll>(cx: &CodegenCx<'ll, '_>) -> Option<&'ll Attribute> {
diff --git a/compiler/rustc_error_messages/locales/en-US/session.ftl b/compiler/rustc_error_messages/locales/en-US/session.ftl
index 5984c201af0..fe553edab42 100644
--- a/compiler/rustc_error_messages/locales/en-US/session.ftl
+++ b/compiler/rustc_error_messages/locales/en-US/session.ftl
@@ -25,6 +25,8 @@ session_profile_sample_use_file_does_not_exist = file `{$path}` passed to `-C pr
 
 session_target_requires_unwind_tables = target requires unwind tables, they cannot be disabled with `-C force-unwind-tables=no`
 
+session_instrumentation_not_supported = {$us} instrumentation is not supported for this target
+
 session_sanitizer_not_supported = {$us} sanitizer is not supported for this target
 
 session_sanitizers_not_supported = {$us} sanitizers are not supported for this target
diff --git a/compiler/rustc_interface/src/tests.rs b/compiler/rustc_interface/src/tests.rs
index 5165ee424e3..0d3499ca9a0 100644
--- a/compiler/rustc_interface/src/tests.rs
+++ b/compiler/rustc_interface/src/tests.rs
@@ -5,6 +5,7 @@ use rustc_data_structures::fx::FxHashSet;
 use rustc_errors::{emitter::HumanReadableErrorType, registry, ColorConfig};
 use rustc_session::config::rustc_optgroups;
 use rustc_session::config::Input;
+use rustc_session::config::InstrumentXRay;
 use rustc_session::config::TraitSolver;
 use rustc_session::config::{build_configuration, build_session_options, to_crate_config};
 use rustc_session::config::{
@@ -755,6 +756,7 @@ fn test_unstable_options_tracking_hash() {
     tracked!(inline_mir_threshold, Some(123));
     tracked!(instrument_coverage, Some(InstrumentCoverage::All));
     tracked!(instrument_mcount, true);
+    tracked!(instrument_xray, Some(InstrumentXRay::default()));
     tracked!(link_only, true);
     tracked!(llvm_plugins, vec![String::from("plugin_name")]);
     tracked!(location_detail, LocationDetail { file: true, line: false, column: false });
diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs
index 973d860118e..7d2fdf94baa 100644
--- a/compiler/rustc_session/src/config.rs
+++ b/compiler/rustc_session/src/config.rs
@@ -174,6 +174,25 @@ pub enum InstrumentCoverage {
     Off,
 }
 
+/// Settings for `-Z instrument-xray` flag.
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
+pub struct InstrumentXRay {
+    /// `-Z instrument-xray=always`, force instrumentation
+    pub always: bool,
+    /// `-Z instrument-xray=never`, disable instrumentation
+    pub never: bool,
+    /// `-Z instrument-xray=ignore-loops`, ignore presence of loops,
+    /// instrument functions based only on instruction count
+    pub ignore_loops: bool,
+    /// `-Z instrument-xray=instruction-threshold=N`, explicitly set instruction threshold
+    /// for instrumentation, or `None` to use compiler's default
+    pub instruction_threshold: Option<usize>,
+    /// `-Z instrument-xray=skip-entry`, do not instrument function entry
+    pub skip_entry: bool,
+    /// `-Z instrument-xray=skip-exit`, do not instrument function exit
+    pub skip_exit: bool,
+}
+
 #[derive(Clone, PartialEq, Hash, Debug)]
 pub enum LinkerPluginLto {
     LinkerPlugin(PathBuf),
@@ -2805,9 +2824,9 @@ impl PpMode {
 pub(crate) mod dep_tracking {
     use super::{
         BranchProtection, CFGuard, CFProtection, CrateType, DebugInfo, ErrorOutputType,
-        InstrumentCoverage, LdImpl, LinkerPluginLto, LocationDetail, LtoCli, OomStrategy, OptLevel,
-        OutputType, OutputTypes, Passes, SourceFileHashAlgorithm, SplitDwarfKind,
-        SwitchWithOptPath, SymbolManglingVersion, TraitSolver, TrimmedDefPaths,
+        InstrumentCoverage, InstrumentXRay, LdImpl, LinkerPluginLto, LocationDetail, LtoCli,
+        OomStrategy, OptLevel, OutputType, OutputTypes, Passes, SourceFileHashAlgorithm,
+        SplitDwarfKind, SwitchWithOptPath, SymbolManglingVersion, TraitSolver, TrimmedDefPaths,
     };
     use crate::lint;
     use crate::options::WasiExecModel;
@@ -2876,6 +2895,7 @@ pub(crate) mod dep_tracking {
         CodeModel,
         TlsModel,
         InstrumentCoverage,
+        InstrumentXRay,
         CrateType,
         MergeFunctions,
         PanicStrategy,
diff --git a/compiler/rustc_session/src/errors.rs b/compiler/rustc_session/src/errors.rs
index 8e8fba5e236..c851145440b 100644
--- a/compiler/rustc_session/src/errors.rs
+++ b/compiler/rustc_session/src/errors.rs
@@ -72,6 +72,12 @@ pub struct ProfileSampleUseFileDoesNotExist<'a> {
 pub struct TargetRequiresUnwindTables;
 
 #[derive(Diagnostic)]
+#[diag(session_instrumentation_not_supported)]
+pub struct InstrumentationNotSupported {
+    pub us: String,
+}
+
+#[derive(Diagnostic)]
 #[diag(session_sanitizer_not_supported)]
 pub struct SanitizerNotSupported {
     pub us: String,
diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs
index 61cb81aec3d..b9723d35e58 100644
--- a/compiler/rustc_session/src/options.rs
+++ b/compiler/rustc_session/src/options.rs
@@ -380,6 +380,7 @@ mod desc {
     pub const parse_dump_mono_stats: &str = "`markdown` (default) or `json`";
     pub const parse_instrument_coverage: &str =
         "`all` (default), `except-unused-generics`, `except-unused-functions`, or `off`";
+    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`";
     pub const parse_unpretty: &str = "`string` or `string=string`";
     pub const parse_treat_err_as_bug: &str = "either no value or a number bigger than 0";
     pub const parse_trait_solver: &str =
@@ -869,6 +870,68 @@ mod parse {
         true
     }
 
+    pub(crate) fn parse_instrument_xray(
+        slot: &mut Option<InstrumentXRay>,
+        v: Option<&str>,
+    ) -> bool {
+        if v.is_some() {
+            let mut bool_arg = None;
+            if parse_opt_bool(&mut bool_arg, v) {
+                *slot = if bool_arg.unwrap() { Some(InstrumentXRay::default()) } else { None };
+                return true;
+            }
+        }
+
+        let mut options = slot.get_or_insert_default();
+        let mut seen_always = false;
+        let mut seen_never = false;
+        let mut seen_ignore_loops = false;
+        let mut seen_instruction_threshold = false;
+        let mut seen_skip_entry = false;
+        let mut seen_skip_exit = false;
+        for option in v.into_iter().map(|v| v.split(',')).flatten() {
+            match option {
+                "always" if !seen_always && !seen_never => {
+                    options.always = true;
+                    options.never = false;
+                    seen_always = true;
+                }
+                "never" if !seen_never && !seen_always => {
+                    options.never = true;
+                    options.always = false;
+                    seen_never = true;
+                }
+                "ignore-loops" if !seen_ignore_loops => {
+                    options.ignore_loops = true;
+                    seen_ignore_loops = true;
+                }
+                option
+                    if option.starts_with("instruction-threshold")
+                        && !seen_instruction_threshold =>
+                {
+                    let Some(("instruction-threshold", n)) = option.split_once('=') else {
+                        return false;
+                    };
+                    match n.parse() {
+                        Ok(n) => options.instruction_threshold = Some(n),
+                        Err(_) => return false,
+                    }
+                    seen_instruction_threshold = true;
+                }
+                "skip-entry" if !seen_skip_entry => {
+                    options.skip_entry = true;
+                    seen_skip_entry = true;
+                }
+                "skip-exit" if !seen_skip_exit => {
+                    options.skip_exit = true;
+                    seen_skip_exit = true;
+                }
+                _ => return false,
+            }
+        }
+        true
+    }
+
     pub(crate) fn parse_treat_err_as_bug(slot: &mut Option<NonZeroUsize>, v: Option<&str>) -> bool {
         match v {
             Some(s) => {
@@ -1397,6 +1460,16 @@ options! {
         `=off` (default)"),
     instrument_mcount: bool = (false, parse_bool, [TRACKED],
         "insert function instrument code for mcount-based tracing (default: no)"),
+    instrument_xray: Option<InstrumentXRay> = (None, parse_instrument_xray, [TRACKED],
+        "insert function instrument code for XRay-based tracing (default: no)
+         Optional extra settings:
+         `=always`
+         `=never`
+         `=ignore-loops`
+         `=instruction-threshold=N`
+         `=skip-entry`
+         `=skip-exit`
+         Multiple options can be combined with commas."),
     keep_hygiene_data: bool = (false, parse_bool, [UNTRACKED],
         "keep hygiene data after analysis (default: no)"),
     layout_seed: Option<u64> = (None, parse_opt_number, [TRACKED],
diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs
index 8a0176f6391..fe6ac80fde6 100644
--- a/compiler/rustc_session/src/session.rs
+++ b/compiler/rustc_session/src/session.rs
@@ -1589,6 +1589,10 @@ fn validate_commandline_args_with_session_available(sess: &Session) {
     {
         sess.emit_err(errors::SplitDebugInfoUnstablePlatform { debuginfo: sess.split_debuginfo() });
     }
+
+    if sess.opts.unstable_opts.instrument_xray.is_some() && !sess.target.options.supports_xray {
+        sess.emit_err(errors::InstrumentationNotSupported { us: "XRay".to_string() });
+    }
 }
 
 /// Holds data on the current incremental compilation session, if there is one.
diff --git a/compiler/rustc_target/src/spec/aarch64_linux_android.rs b/compiler/rustc_target/src/spec/aarch64_linux_android.rs
index c85f7f62a42..daa946ccd51 100644
--- a/compiler/rustc_target/src/spec/aarch64_linux_android.rs
+++ b/compiler/rustc_target/src/spec/aarch64_linux_android.rs
@@ -19,6 +19,7 @@ pub fn target() -> Target {
                 | SanitizerSet::MEMTAG
                 | SanitizerSet::SHADOWCALLSTACK
                 | SanitizerSet::ADDRESS,
+            supports_xray: true,
             ..super::android_base::opts()
         },
     }
diff --git a/compiler/rustc_target/src/spec/aarch64_unknown_linux_gnu.rs b/compiler/rustc_target/src/spec/aarch64_unknown_linux_gnu.rs
index 3006044d54a..36d54f1d7cc 100644
--- a/compiler/rustc_target/src/spec/aarch64_unknown_linux_gnu.rs
+++ b/compiler/rustc_target/src/spec/aarch64_unknown_linux_gnu.rs
@@ -17,6 +17,7 @@ pub fn target() -> Target {
                 | SanitizerSet::MEMTAG
                 | SanitizerSet::THREAD
                 | SanitizerSet::HWADDRESS,
+            supports_xray: true,
             ..super::linux_gnu_base::opts()
         },
     }
diff --git a/compiler/rustc_target/src/spec/aarch64_unknown_linux_musl.rs b/compiler/rustc_target/src/spec/aarch64_unknown_linux_musl.rs
index 002d0dac2a6..9c299fed6be 100644
--- a/compiler/rustc_target/src/spec/aarch64_unknown_linux_musl.rs
+++ b/compiler/rustc_target/src/spec/aarch64_unknown_linux_musl.rs
@@ -3,6 +3,7 @@ use crate::spec::{Target, TargetOptions};
 pub fn target() -> Target {
     let mut base = super::linux_musl_base::opts();
     base.max_atomic_width = Some(128);
+    base.supports_xray = true;
 
     Target {
         llvm_target: "aarch64-unknown-linux-musl".into(),
diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs
index a094c2c5452..bc1920e3424 100644
--- a/compiler/rustc_target/src/spec/mod.rs
+++ b/compiler/rustc_target/src/spec/mod.rs
@@ -1718,6 +1718,9 @@ pub struct TargetOptions {
     /// The ABI of entry function.
     /// Default value is `Conv::C`, i.e. C call convention
     pub entry_abi: Conv,
+
+    /// Whether the target supports XRay instrumentation.
+    pub supports_xray: bool,
 }
 
 /// Add arguments for the given flavor and also for its "twin" flavors
@@ -1937,6 +1940,7 @@ impl Default for TargetOptions {
             supports_stack_protector: true,
             entry_name: "main".into(),
             entry_abi: Conv::C,
+            supports_xray: false,
         }
     }
 }
@@ -2592,6 +2596,7 @@ impl Target {
         key!(supports_stack_protector, bool);
         key!(entry_name);
         key!(entry_abi, Conv)?;
+        key!(supports_xray, bool);
 
         if base.is_builtin {
             // This can cause unfortunate ICEs later down the line.
@@ -2845,6 +2850,7 @@ impl ToJson for Target {
         target_option_val!(supports_stack_protector);
         target_option_val!(entry_name);
         target_option_val!(entry_abi);
+        target_option_val!(supports_xray);
 
         if let Some(abi) = self.default_adjusted_cabi {
             d.insert("default-adjusted-cabi".into(), Abi::name(abi).to_json());
diff --git a/compiler/rustc_target/src/spec/x86_64_linux_android.rs b/compiler/rustc_target/src/spec/x86_64_linux_android.rs
index 9c913784855..a3bdb5f5465 100644
--- a/compiler/rustc_target/src/spec/x86_64_linux_android.rs
+++ b/compiler/rustc_target/src/spec/x86_64_linux_android.rs
@@ -8,6 +8,7 @@ pub fn target() -> Target {
     base.max_atomic_width = Some(64);
     base.add_pre_link_args(LinkerFlavor::Gnu(Cc::Yes, Lld::No), &["-m64"]);
     base.stack_probes = StackProbeType::X86;
+    base.supports_xray = true;
 
     Target {
         llvm_target: "x86_64-linux-android".into(),
diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_freebsd.rs b/compiler/rustc_target/src/spec/x86_64_unknown_freebsd.rs
index 98988ab3595..b41e5842aad 100644
--- a/compiler/rustc_target/src/spec/x86_64_unknown_freebsd.rs
+++ b/compiler/rustc_target/src/spec/x86_64_unknown_freebsd.rs
@@ -8,6 +8,7 @@ pub fn target() -> Target {
     base.stack_probes = StackProbeType::X86;
     base.supported_sanitizers =
         SanitizerSet::ADDRESS | SanitizerSet::CFI | SanitizerSet::MEMORY | SanitizerSet::THREAD;
+    base.supports_xray = true;
 
     Target {
         llvm_target: "x86_64-unknown-freebsd".into(),
diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_linux_gnu.rs b/compiler/rustc_target/src/spec/x86_64_unknown_linux_gnu.rs
index a91ab365b66..9af1049b870 100644
--- a/compiler/rustc_target/src/spec/x86_64_unknown_linux_gnu.rs
+++ b/compiler/rustc_target/src/spec/x86_64_unknown_linux_gnu.rs
@@ -12,6 +12,7 @@ pub fn target() -> Target {
         | SanitizerSet::LEAK
         | SanitizerSet::MEMORY
         | SanitizerSet::THREAD;
+    base.supports_xray = true;
 
     Target {
         llvm_target: "x86_64-unknown-linux-gnu".into(),
diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_linux_musl.rs b/compiler/rustc_target/src/spec/x86_64_unknown_linux_musl.rs
index 9087dc3df60..bf4cf7d7bec 100644
--- a/compiler/rustc_target/src/spec/x86_64_unknown_linux_musl.rs
+++ b/compiler/rustc_target/src/spec/x86_64_unknown_linux_musl.rs
@@ -12,6 +12,7 @@ pub fn target() -> Target {
         | SanitizerSet::LEAK
         | SanitizerSet::MEMORY
         | SanitizerSet::THREAD;
+    base.supports_xray = true;
 
     Target {
         llvm_target: "x86_64-unknown-linux-musl".into(),
diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_netbsd.rs b/compiler/rustc_target/src/spec/x86_64_unknown_netbsd.rs
index 64ae425d8c0..74c434935ba 100644
--- a/compiler/rustc_target/src/spec/x86_64_unknown_netbsd.rs
+++ b/compiler/rustc_target/src/spec/x86_64_unknown_netbsd.rs
@@ -11,6 +11,7 @@ pub fn target() -> Target {
         | SanitizerSet::LEAK
         | SanitizerSet::MEMORY
         | SanitizerSet::THREAD;
+    base.supports_xray = true;
 
     Target {
         llvm_target: "x86_64-unknown-netbsd".into(),
diff --git a/compiler/rustc_target/src/spec/x86_64_unknown_openbsd.rs b/compiler/rustc_target/src/spec/x86_64_unknown_openbsd.rs
index 66b8e20226f..8e4d42a0aca 100644
--- a/compiler/rustc_target/src/spec/x86_64_unknown_openbsd.rs
+++ b/compiler/rustc_target/src/spec/x86_64_unknown_openbsd.rs
@@ -6,6 +6,7 @@ pub fn target() -> Target {
     base.max_atomic_width = Some(64);
     base.add_pre_link_args(LinkerFlavor::Gnu(Cc::Yes, Lld::No), &["-m64"]);
     base.stack_probes = StackProbeType::X86;
+    base.supports_xray = true;
 
     Target {
         llvm_target: "x86_64-unknown-openbsd".into(),
diff --git a/src/doc/unstable-book/src/compiler-flags/instrument-xray.md b/src/doc/unstable-book/src/compiler-flags/instrument-xray.md
new file mode 100644
index 00000000000..7fb33cd68b4
--- /dev/null
+++ b/src/doc/unstable-book/src/compiler-flags/instrument-xray.md
@@ -0,0 +1,39 @@
+# `instrument-xray`
+
+The tracking issue for this feature is: [#102921](https://github.com/rust-lang/rust/issues/102921).
+
+------------------------
+
+Enable generation of NOP sleds for XRay function tracing instrumentation.
+For more information on XRay,
+read [LLVM documentation](https://llvm.org/docs/XRay.html),
+and/or the [XRay whitepaper](http://research.google.com/pubs/pub45287.html).
+
+Set the `-Z instrument-xray` compiler flag in order to enable XRay instrumentation.
+
+  - `-Z instrument-xray` – use the default settings
+  - `-Z instrument-xray=skip-exit` – configure a custom setting
+  - `-Z instrument-xray=ignore-loops,instruction-threshold=300` –
+    multiple settings separated by commas
+
+Supported options:
+
+  - `always` – force instrumentation of all functions
+  - `never` – do no instrument any functions
+  - `ignore-loops` – ignore presence of loops,
+    instrument functions based only on instruction count
+  - `instruction-threshold=10` – set a different instruction threshold for instrumentation
+  - `skip-entry` – do no instrument function entry
+  - `skip-exit` – do no instrument function exit
+
+The default settings are:
+
+  - instrument both entry & exit from functions
+  - instrument functions with at least 200 instructions,
+    or containing a non-trivial loop
+
+Note that `-Z instrument-xray` only enables generation of NOP sleds
+which on their own don't do anything useful.
+In order to actually trace the functions,
+you will need to link a separate runtime library of your choice,
+such as Clang's [XRay Runtime Library](https://www.llvm.org/docs/XRay.html#xray-runtime-library).
diff --git a/src/tools/compiletest/src/header.rs b/src/tools/compiletest/src/header.rs
index 45fd87bea9b..e11ebca6ea9 100644
--- a/src/tools/compiletest/src/header.rs
+++ b/src/tools/compiletest/src/header.rs
@@ -941,6 +941,7 @@ pub fn make_test_description<R: Read>(
     let has_hwasan = util::HWASAN_SUPPORTED_TARGETS.contains(&&*config.target);
     let has_memtag = util::MEMTAG_SUPPORTED_TARGETS.contains(&&*config.target);
     let has_shadow_call_stack = util::SHADOWCALLSTACK_SUPPORTED_TARGETS.contains(&&*config.target);
+    let has_xray = util::XRAY_SUPPORTED_TARGETS.contains(&&*config.target);
 
     // For tests using the `needs-rust-lld` directive (e.g. for `-Zgcc-ld=lld`), we need to find
     // whether `rust-lld` is present in the compiler under test.
@@ -1019,6 +1020,7 @@ pub fn make_test_description<R: Read>(
                 && config.parse_name_directive(ln, "needs-sanitizer-shadow-call-stack")
         );
         reason!(!config.can_unwind() && config.parse_name_directive(ln, "needs-unwind"));
+        reason!(!has_xray && config.parse_name_directive(ln, "needs-xray"));
         reason!(
             config.target == "wasm32-unknown-unknown"
                 && config.parse_name_directive(ln, directives::CHECK_RUN_RESULTS)
diff --git a/src/tools/compiletest/src/util.rs b/src/tools/compiletest/src/util.rs
index ff7e8df9878..67f49bb6397 100644
--- a/src/tools/compiletest/src/util.rs
+++ b/src/tools/compiletest/src/util.rs
@@ -78,6 +78,19 @@ pub const MEMTAG_SUPPORTED_TARGETS: &[&str] =
 
 pub const SHADOWCALLSTACK_SUPPORTED_TARGETS: &[&str] = &["aarch64-linux-android"];
 
+pub const XRAY_SUPPORTED_TARGETS: &[&str] = &[
+    "aarch64-linux-android",
+    "aarch64-unknown-linux-gnu",
+    "aarch64-unknown-linux-musl",
+    "x86_64-linux-android",
+    "x86_64-unknown-freebsd",
+    "x86_64-unknown-linux-gnu",
+    "x86_64-unknown-linux-musl",
+    "x86_64-unknown-netbsd",
+    "x86_64-unknown-none-linuxkernel",
+    "x86_64-unknown-openbsd",
+];
+
 pub fn make_new_path(path: &str) -> String {
     assert!(cfg!(windows));
     // Windows just uses PATH as the library search path, so we have to
diff --git a/src/tools/tidy/src/ui_tests.rs b/src/tools/tidy/src/ui_tests.rs
index 83551a1d820..ef3abb9514f 100644
--- a/src/tools/tidy/src/ui_tests.rs
+++ b/src/tools/tidy/src/ui_tests.rs
@@ -9,7 +9,7 @@ use std::path::Path;
 
 const ENTRY_LIMIT: usize = 1000;
 // FIXME: The following limits should be reduced eventually.
-const ROOT_ENTRY_LIMIT: usize = 939;
+const ROOT_ENTRY_LIMIT: usize = 940;
 const ISSUES_ENTRY_LIMIT: usize = 2001;
 
 fn check_entries(path: &Path, bad: &mut bool) {
diff --git a/tests/codegen/instrument-xray/basic.rs b/tests/codegen/instrument-xray/basic.rs
new file mode 100644
index 00000000000..d3e49d53174
--- /dev/null
+++ b/tests/codegen/instrument-xray/basic.rs
@@ -0,0 +1,9 @@
+// Checks that `-Z instrument-xray` produces expected instrumentation.
+//
+// needs-xray
+// compile-flags: -Z instrument-xray=always
+
+#![crate_type = "lib"]
+
+// CHECK: attributes #{{.*}} "function-instrument"="xray-always"
+pub fn function() {}
diff --git a/tests/codegen/instrument-xray/options-combine.rs b/tests/codegen/instrument-xray/options-combine.rs
new file mode 100644
index 00000000000..f7e500b65f6
--- /dev/null
+++ b/tests/codegen/instrument-xray/options-combine.rs
@@ -0,0 +1,12 @@
+// Checks that `-Z instrument-xray` options can be specified multiple times.
+//
+// needs-xray
+// compile-flags: -Z instrument-xray=skip-exit
+// compile-flags: -Z instrument-xray=instruction-threshold=123
+// compile-flags: -Z instrument-xray=instruction-threshold=456
+
+#![crate_type = "lib"]
+
+// CHECK:      attributes #{{.*}} "xray-instruction-threshold"="456" "xray-skip-exit"
+// CHECK-NOT:  attributes #{{.*}} "xray-instruction-threshold"="123"
+pub fn function() {}
diff --git a/tests/codegen/instrument-xray/options-override.rs b/tests/codegen/instrument-xray/options-override.rs
new file mode 100644
index 00000000000..00f81837902
--- /dev/null
+++ b/tests/codegen/instrument-xray/options-override.rs
@@ -0,0 +1,11 @@
+// Checks that the last `-Z instrument-xray` option wins.
+//
+// needs-xray
+// compile-flags: -Z instrument-xray=always
+// compile-flags: -Z instrument-xray=never
+
+#![crate_type = "lib"]
+
+// CHECK:      attributes #{{.*}} "function-instrument"="xray-never"
+// CHECK-NOT:  attributes #{{.*}} "function-instrument"="xray-always"
+pub fn function() {}
diff --git a/tests/rustdoc-ui/z-help.stdout b/tests/rustdoc-ui/z-help.stdout
index 706db892cb3..6c1684201a2 100644
--- a/tests/rustdoc-ui/z-help.stdout
+++ b/tests/rustdoc-ui/z-help.stdout
@@ -70,6 +70,15 @@
         `=except-unused-functions`
         `=off` (default)
     -Z                       instrument-mcount=val -- insert function instrument code for mcount-based tracing (default: no)
+    -Z                         instrument-xray=val -- insert function instrument code for XRay-based tracing (default: no)
+         Optional extra settings:
+         `=always`
+         `=never`
+         `=ignore-loops`
+         `=instruction-threshold=N`
+         `=skip-entry`
+         `=skip-exit`
+         Multiple options can be combined with commas.
     -Z                       keep-hygiene-data=val -- keep hygiene data after analysis (default: no)
     -Z                             layout-seed=val -- seed layout randomization
     -Z                   link-native-libraries=val -- link native libraries in the linker invocation (default: yes)
diff --git a/tests/ui/instrument-xray/flags-always-never-1.rs b/tests/ui/instrument-xray/flags-always-never-1.rs
new file mode 100644
index 00000000000..4dd43439eb7
--- /dev/null
+++ b/tests/ui/instrument-xray/flags-always-never-1.rs
@@ -0,0 +1,7 @@
+// Checks that `-Z instrument-xray` does not allow `always` and `never` simultaneously.
+//
+// needs-xray
+// compile-flags: -Z instrument-xray=always,never
+// error-pattern: incorrect value `always,never` for unstable option `instrument-xray`
+
+fn main() {}
diff --git a/tests/ui/instrument-xray/flags-always-never-1.stderr b/tests/ui/instrument-xray/flags-always-never-1.stderr
new file mode 100644
index 00000000000..e211c6f6025
--- /dev/null
+++ b/tests/ui/instrument-xray/flags-always-never-1.stderr
@@ -0,0 +1,2 @@
+error: incorrect value `always,never` for unstable option `instrument-xray` - 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` was expected
+
diff --git a/tests/ui/instrument-xray/flags-always-never-2.rs b/tests/ui/instrument-xray/flags-always-never-2.rs
new file mode 100644
index 00000000000..7310aa0a0d2
--- /dev/null
+++ b/tests/ui/instrument-xray/flags-always-never-2.rs
@@ -0,0 +1,9 @@
+// Checks that `-Z instrument-xray` allows `always` and `never` sequentially.
+// (The last specified setting wins, like `-Z instrument-xray=no` as well.)
+//
+// needs-xray
+// compile-flags: -Z instrument-xray=always
+// compile-flags: -Z instrument-xray=never
+// check-pass
+
+fn main() {}
diff --git a/tests/ui/instrument-xray/flags-basic.rs b/tests/ui/instrument-xray/flags-basic.rs
new file mode 100644
index 00000000000..b97f0dd8a07
--- /dev/null
+++ b/tests/ui/instrument-xray/flags-basic.rs
@@ -0,0 +1,9 @@
+// Verifies basic `-Z instrument-xray` flags.
+//
+// needs-xray
+// compile-flags: -Z instrument-xray
+// compile-flags: -Z instrument-xray=skip-exit
+// compile-flags: -Z instrument-xray=ignore-loops,instruction-threshold=300
+// check-pass
+
+fn main() {}
diff --git a/tests/ui/instrument-xray/flags-dupe-always.rs b/tests/ui/instrument-xray/flags-dupe-always.rs
new file mode 100644
index 00000000000..407f3e2aa5d
--- /dev/null
+++ b/tests/ui/instrument-xray/flags-dupe-always.rs
@@ -0,0 +1,7 @@
+// Checks that `-Z instrument-xray` does not allow duplicates.
+//
+// needs-xray
+// compile-flags: -Z instrument-xray=always,always
+// error-pattern: incorrect value `always,always` for unstable option `instrument-xray`
+
+fn main() {}
diff --git a/tests/ui/instrument-xray/flags-dupe-always.stderr b/tests/ui/instrument-xray/flags-dupe-always.stderr
new file mode 100644
index 00000000000..d1ac113fa43
--- /dev/null
+++ b/tests/ui/instrument-xray/flags-dupe-always.stderr
@@ -0,0 +1,2 @@
+error: incorrect value `always,always` for unstable option `instrument-xray` - 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` was expected
+
diff --git a/tests/ui/instrument-xray/flags-dupe-ignore-loops.rs b/tests/ui/instrument-xray/flags-dupe-ignore-loops.rs
new file mode 100644
index 00000000000..75b210a6547
--- /dev/null
+++ b/tests/ui/instrument-xray/flags-dupe-ignore-loops.rs
@@ -0,0 +1,7 @@
+// Checks that `-Z instrument-xray` does not allow duplicates.
+//
+// needs-xray
+// compile-flags: -Z instrument-xray=ignore-loops,ignore-loops
+// error-pattern: incorrect value `ignore-loops,ignore-loops` for unstable option `instrument-xray`
+
+fn main() {}
diff --git a/tests/ui/instrument-xray/flags-dupe-ignore-loops.stderr b/tests/ui/instrument-xray/flags-dupe-ignore-loops.stderr
new file mode 100644
index 00000000000..52f6b33075b
--- /dev/null
+++ b/tests/ui/instrument-xray/flags-dupe-ignore-loops.stderr
@@ -0,0 +1,2 @@
+error: incorrect value `ignore-loops,ignore-loops` for unstable option `instrument-xray` - 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` was expected
+
diff --git a/tests/ui/instrument-xray/target-not-supported.rs b/tests/ui/instrument-xray/target-not-supported.rs
new file mode 100644
index 00000000000..e6bdd23e8fc
--- /dev/null
+++ b/tests/ui/instrument-xray/target-not-supported.rs
@@ -0,0 +1,9 @@
+// Verifies that `-Z instrument-xray` cannot be used with unsupported targets,
+//
+// needs-llvm-components: x86
+// compile-flags: -Z instrument-xray --target x86_64-apple-darwin
+// error-pattern: error: XRay instrumentation is not supported for this target
+
+#![feature(no_core)]
+#![no_core]
+#![no_main]
diff --git a/tests/ui/instrument-xray/target-not-supported.stderr b/tests/ui/instrument-xray/target-not-supported.stderr
new file mode 100644
index 00000000000..6e3b0c8a380
--- /dev/null
+++ b/tests/ui/instrument-xray/target-not-supported.stderr
@@ -0,0 +1,4 @@
+error: XRay instrumentation is not supported for this target
+
+error: aborting due to previous error
+