Skip to content

Commit 8c86bb4

Browse files
committed
rustdoc: Output target feature information
`#[target_feature]` attributes refer to a target-specific list of features. Enabling certain features can imply enabling other features. Certain features are always enabled on certain targets, since they are required by the target's ABI. Features can also be enabled indirectly based on other compiler flags. Feature information is ultimately known to `rustc`. Rather than force external tools to track it -- which may be wildly impractical due to `-C target-cpu` -- have `rustdoc` output `rustc`'s feature data.
1 parent 51548ce commit 8c86bb4

15 files changed

+258
-2
lines changed

Diff for: src/librustdoc/json/mod.rs

+60
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use std::io::{BufWriter, Write, stdout};
1414
use std::path::PathBuf;
1515
use std::rc::Rc;
1616

17+
use rustc_data_structures::fx::FxHashSet;
1718
use rustc_hir::def_id::{DefId, DefIdSet};
1819
use rustc_middle::ty::TyCtxt;
1920
use rustc_session::Session;
@@ -123,6 +124,58 @@ impl<'tcx> JsonRenderer<'tcx> {
123124
}
124125
}
125126

127+
fn target(sess: &rustc_session::Session) -> types::Target {
128+
// Build a set of which features are enabled on this target
129+
let globally_enabled_features: FxHashSet<&str> =
130+
sess.unstable_target_features.iter().map(|name| name.as_str()).collect();
131+
132+
// Build a map of target feature stability by feature name
133+
use rustc_target::target_features::Stability;
134+
let feature_stability: FxHashMap<&str, Stability> = sess
135+
.target
136+
.rust_target_features()
137+
.into_iter()
138+
.copied()
139+
.map(|(name, stability, _)| (name, stability))
140+
.collect();
141+
142+
types::Target {
143+
triple: sess.opts.target_triple.tuple().into(),
144+
target_features: sess
145+
.target
146+
.rust_target_features()
147+
.into_iter()
148+
.copied()
149+
.filter(|(_, stability, _)| {
150+
// Describe only target features which the user can toggle
151+
stability.toggle_allowed().is_ok()
152+
})
153+
.map(|(name, stability, implied_features)| {
154+
types::TargetFeature {
155+
name: name.into(),
156+
unstable_feature_gate: match stability {
157+
Stability::Unstable(feature_gate) => Some(feature_gate.as_str().into()),
158+
_ => None,
159+
},
160+
implies_features: implied_features
161+
.into_iter()
162+
.copied()
163+
.filter(|name| {
164+
// Imply only target features which the user can toggle
165+
feature_stability
166+
.get(name)
167+
.map(|stability| stability.toggle_allowed().is_ok())
168+
.unwrap_or(false)
169+
})
170+
.map(String::from)
171+
.collect(),
172+
globally_enabled: globally_enabled_features.contains(name),
173+
}
174+
})
175+
.collect(),
176+
}
177+
}
178+
126179
impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> {
127180
fn descr() -> &'static str {
128181
"json"
@@ -248,6 +301,12 @@ impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> {
248301
let e = ExternalCrate { crate_num: LOCAL_CRATE };
249302
let index = (*self.index).clone().into_inner();
250303

304+
// Note that tcx.rust_target_features is inappropriate here because rustdoc tries to run for
305+
// multiple targets: https://github.com/rust-lang/rust/pull/137632
306+
//
307+
// We want to describe a single target, so pass tcx.sess rather than tcx.
308+
let target = target(self.tcx.sess);
309+
251310
debug!("Constructing Output");
252311
let output_crate = types::Crate {
253312
root: self.id_from_item_default(e.def_id().into()),
@@ -288,6 +347,7 @@ impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> {
288347
)
289348
})
290349
.collect(),
350+
target,
291351
format_version: types::FORMAT_VERSION,
292352
};
293353
if let Some(ref out_dir) = self.out_dir {

Diff for: src/rustdoc-json-types/lib.rs

+57-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ pub type FxHashMap<K, V> = HashMap<K, V>; // re-export for use in src/librustdoc
3030
/// This integer is incremented with every breaking change to the API,
3131
/// and is returned along with the JSON blob as [`Crate::format_version`].
3232
/// Consuming code should assert that this value matches the format version(s) that it supports.
33-
pub const FORMAT_VERSION: u32 = 43;
33+
pub const FORMAT_VERSION: u32 = 44;
3434

3535
/// The root of the emitted JSON blob.
3636
///
@@ -52,11 +52,67 @@ pub struct Crate {
5252
pub paths: HashMap<Id, ItemSummary>,
5353
/// Maps `crate_id` of items to a crate name and html_root_url if it exists.
5454
pub external_crates: HashMap<u32, ExternalCrate>,
55+
/// Information about the target for which this documentation was generated
56+
pub target: Target,
5557
/// A single version number to be used in the future when making backwards incompatible changes
5658
/// to the JSON output.
5759
pub format_version: u32,
5860
}
5961

62+
/// Information about a target
63+
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
64+
pub struct Target {
65+
/// The target triple for which this documentation was generated
66+
pub triple: String,
67+
/// A list of `#[target_feature]` which exist for this target, along with their status in this
68+
/// compiler session
69+
pub target_features: Vec<TargetFeature>,
70+
}
71+
72+
/// Information about a target feature.
73+
///
74+
/// Rust target features are used to influence code generation, especially around selecting
75+
/// instructions which are not universally supported by the target architecture.
76+
///
77+
/// Target features are commonly enabled by the [`#[target_feature]` attribute][1] to influence code
78+
/// generation for a particular function, and less commonly enabled by compiler options like
79+
/// `-Ctarget-feature` or `-Ctarget-cpu`. Targets themselves automatically enable certain target
80+
/// features by default, for example because the target's ABI specification requires saving specific
81+
/// registers which only exist in an architectural extension.
82+
///
83+
/// Target features can imply other target features: for example, x86-64 `avx2` implies `avx`, and
84+
/// aarch64 `sve2` implies `sve`, since both of these architectural extensions depend on their
85+
/// predecessors.
86+
///
87+
/// Target features can be probed at compile time by [`#[cfg(target_feature)]`][2] or `cfg!(…)`
88+
/// conditional compilation to determine whether a target feature is enabled in a particular
89+
/// context.
90+
///
91+
/// [1]: https://doc.rust-lang.org/stable/reference/attributes/codegen.html#the-target_feature-attribute
92+
/// [2]: https://doc.rust-lang.org/reference/conditional-compilation.html#target_feature
93+
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
94+
pub struct TargetFeature {
95+
/// The name of this target feature.
96+
pub name: String,
97+
/// Other target features which are implied by this target feature, if any.
98+
pub implies_features: Vec<String>,
99+
/// If this target feature is unstable, the name of the associated language feature gate.
100+
pub unstable_feature_gate: Option<String>,
101+
/// Whether this feature is globally enabled for this compilation session.
102+
///
103+
/// Target features can be globally enabled implicitly as a result of the target's definition.
104+
/// For example, x86-64 hardware floating point ABIs require saving x87 and SSE2 registers,
105+
/// which in turn requires globally enabling the `x87` and `sse2` target features so that the
106+
/// generated machine code conforms to the target's ABI.
107+
///
108+
/// Target features can also be globally enabled explicitly as a result of compiler flags like
109+
/// [`-Ctarget-feature`][1] or [`-Ctarget-cpu`][2].
110+
///
111+
/// [1]: https://doc.rust-lang.org/beta/rustc/codegen-options/index.html#target-feature
112+
/// [2]: https://doc.rust-lang.org/beta/rustc/codegen-options/index.html#target-cpu
113+
pub globally_enabled: bool,
114+
}
115+
60116
/// Metadata of a crate, either the same crate on which `rustdoc` was invoked, or its dependency.
61117
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
62118
pub struct ExternalCrate {

Diff for: src/tools/compiletest/src/directive-list.rs

+3
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ const KNOWN_DIRECTIVE_NAMES: &[&str] = &[
176176
"only-32bit",
177177
"only-64bit",
178178
"only-aarch64",
179+
"only-aarch64-apple-darwin",
179180
"only-aarch64-unknown-linux-gnu",
180181
"only-apple",
181182
"only-arm",
@@ -189,6 +190,7 @@ const KNOWN_DIRECTIVE_NAMES: &[&str] = &[
189190
"only-gnu",
190191
"only-i686-pc-windows-gnu",
191192
"only-i686-pc-windows-msvc",
193+
"only-i686-unknown-linux-gnu",
192194
"only-ios",
193195
"only-linux",
194196
"only-loongarch64",
@@ -220,6 +222,7 @@ const KNOWN_DIRECTIVE_NAMES: &[&str] = &[
220222
"only-windows-msvc",
221223
"only-x86",
222224
"only-x86_64",
225+
"only-x86_64-apple-darwin",
223226
"only-x86_64-fortanix-unknown-sgx",
224227
"only-x86_64-pc-windows-gnu",
225228
"only-x86_64-pc-windows-msvc",

Diff for: src/tools/jsondocck/src/main.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ static LINE_PATTERN: LazyLock<Regex> = LazyLock::new(|| {
156156
r#"
157157
//@\s+
158158
(?P<negated>!?)
159-
(?P<cmd>[A-Za-z]+(?:-[A-Za-z]+)*)
159+
(?P<cmd>[A-Za-z0-9]+(?:-[A-Za-z0-9]+)*)
160160
(?P<args>.*)$
161161
"#,
162162
)

Diff for: src/tools/jsondoclint/src/validator/tests.rs

+4
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ fn errors_on_missing_links() {
4242
)]),
4343
paths: FxHashMap::default(),
4444
external_crates: FxHashMap::default(),
45+
target: rustdoc_json_types::Target { triple: "".to_string(), target_features: vec![] },
4546
format_version: rustdoc_json_types::FORMAT_VERSION,
4647
};
4748

@@ -112,6 +113,7 @@ fn errors_on_local_in_paths_and_not_index() {
112113
},
113114
)]),
114115
external_crates: FxHashMap::default(),
116+
target: rustdoc_json_types::Target { triple: "".to_string(), target_features: vec![] },
115117
format_version: rustdoc_json_types::FORMAT_VERSION,
116118
};
117119

@@ -216,6 +218,7 @@ fn errors_on_missing_path() {
216218
ItemSummary { crate_id: 0, path: vec!["foo".to_owned()], kind: ItemKind::Module },
217219
)]),
218220
external_crates: FxHashMap::default(),
221+
target: rustdoc_json_types::Target { triple: "".to_string(), target_features: vec![] },
219222
format_version: rustdoc_json_types::FORMAT_VERSION,
220223
};
221224

@@ -259,6 +262,7 @@ fn checks_local_crate_id_is_correct() {
259262
)]),
260263
paths: FxHashMap::default(),
261264
external_crates: FxHashMap::default(),
265+
target: rustdoc_json_types::Target { triple: "".to_string(), target_features: vec![] },
262266
format_version: FORMAT_VERSION,
263267
};
264268
check(&krate, &[]);

Diff for: tests/rustdoc-json/targets/aarch64_apple_darwin.rs

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//@ only-aarch64-apple-darwin
2+
3+
//@ is "$.target.triple" \"aarch64-apple-darwin\"
4+
//@ is "$.target.target_features[?(@.name=='vh')].globally_enabled" true
5+
//@ is "$.target.target_features[?(@.name=='sve')].globally_enabled" false
6+
//@ has "$.target.target_features[?(@.name=='sve2')].implies_features" '["sve"]'
7+
//@ is "$.target.target_features[?(@.name=='sve2')].unstable_feature_gate" null
8+
9+
// If this breaks due to stabilization, check rustc_target::target_features for a replacement
10+
//@ is "$.target.target_features[?(@.name=='cssc')].unstable_feature_gate" '"aarch64_unstable_target_feature"'
11+
//@ is "$.target.target_features[?(@.name=='v9a')].unstable_feature_gate" '"aarch64_ver_target_feature"'
12+
13+
// Ensure we don't look like x86-64
14+
//@ !has "$.target.target_features[?(@.name=='avx2')]"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
//@ only-aarch64
2+
3+
// If we enable SVE Bit Permute, we should see that it is enabled
4+
//@ compile-flags: -Ctarget-feature=+sve2-bitperm
5+
//@ is "$.target.target_features[?(@.name=='sve2-bitperm')].globally_enabled" true
6+
7+
// As well as its dependency chain
8+
//@ is "$.target.target_features[?(@.name=='sve2')].globally_enabled" true
9+
//@ is "$.target.target_features[?(@.name=='sve')].globally_enabled" true
10+
//@ is "$.target.target_features[?(@.name=='fp16')].globally_enabled" true
11+
//@ is "$.target.target_features[?(@.name=='neon')].globally_enabled" true
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//@ only-aarch64-unknown-linux-gnu
2+
3+
//@ is "$.target.triple" \"aarch64-unknown-linux-gnu\"
4+
//@ is "$.target.target_features[?(@.name=='vh')].globally_enabled" true
5+
//@ is "$.target.target_features[?(@.name=='sve')].globally_enabled" false
6+
//@ has "$.target.target_features[?(@.name=='sve2')].implies_features" '["sve"]'
7+
//@ is "$.target.target_features[?(@.name=='sve2')].unstable_feature_gate" null
8+
9+
// If this breaks due to stabilization, check rustc_target::target_features for a replacement
10+
//@ is "$.target.target_features[?(@.name=='cssc')].unstable_feature_gate" '"aarch64_unstable_target_feature"'
11+
//@ is "$.target.target_features[?(@.name=='v9a')].unstable_feature_gate" '"aarch64_ver_target_feature"'
12+
13+
// Ensure we don't look like x86-64
14+
//@ !has "$.target.target_features[?(@.name=='avx2')]"

Diff for: tests/rustdoc-json/targets/i686_pc_windows_msvc.rs

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//@ only-i686-pc-windows-msvc
2+
3+
//@ is "$.target.triple" \"i686-pc-windows-msvc\"
4+
//@ is "$.target.target_features[?(@.name=='sse2')].globally_enabled" true
5+
//@ is "$.target.target_features[?(@.name=='avx2')].globally_enabled" false
6+
//@ has "$.target.target_features[?(@.name=='avx2')].implies_features" '["avx"]'
7+
//@ is "$.target.target_features[?(@.name=='avx2')].unstable_feature_gate" null
8+
9+
// If this breaks due to stabilization, check rustc_target::target_features for a replacement
10+
//@ is "$.target.target_features[?(@.name=='amx-tile')].unstable_feature_gate" '"x86_amx_intrinsics"'
11+
//@ is "$.target.target_features[?(@.name=='x87')].unstable_feature_gate" '"x87_target_feature"'
12+
13+
// Ensure we don't look like aarch64
14+
//@ !has "$.target.target_features[?(@.name=='sve2')]"

Diff for: tests/rustdoc-json/targets/i686_unknown_linux_gnu.rs

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//@ only-i686-unknown-linux-gnu
2+
3+
//@ is "$.target.triple" \"i686-unknown-linux-gnu\"
4+
//@ is "$.target.target_features[?(@.name=='sse2')].globally_enabled" true
5+
//@ is "$.target.target_features[?(@.name=='avx2')].globally_enabled" false
6+
//@ has "$.target.target_features[?(@.name=='avx2')].implies_features" '["avx"]'
7+
//@ is "$.target.target_features[?(@.name=='avx2')].unstable_feature_gate" null
8+
9+
// If this breaks due to stabilization, check rustc_target::target_features for a replacement
10+
//@ is "$.target.target_features[?(@.name=='amx-tile')].unstable_feature_gate" '"x86_amx_intrinsics"'
11+
//@ is "$.target.target_features[?(@.name=='x87')].unstable_feature_gate" '"x87_target_feature"'
12+
13+
// Ensure we don't look like aarch64
14+
//@ !has "$.target.target_features[?(@.name=='sve2')]"

Diff for: tests/rustdoc-json/targets/x86_64_apple_darwin.rs

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//@ only-x86_64-apple-darwin
2+
3+
//@ is "$.target.triple" \"x86_64-apple-darwin\"
4+
//@ is "$.target.target_features[?(@.name=='?')].globally_enabled" true
5+
//@ is "$.target.target_features[?(@.name=='?')].globally_enabled" false
6+
//@ has "$.target.target_features[?(@.name=='avx2')].implies_features" '["avx"]'
7+
//@ is "$.target.target_features[?(@.name=='avx2')].unstable_feature_gate" null
8+
9+
// If this breaks due to stabilization, check rustc_target::target_features for a replacement
10+
//@ is "$.target.target_features[?(@.name=='amx-tile')].unstable_feature_gate" '"x86_amx_intrinsics"'
11+
//@ is "$.target.target_features[?(@.name=='x87')].unstable_feature_gate" '"x87_target_feature"'
12+
13+
// Ensure we don't look like aarch64
14+
//@ !has "$.target.target_features[?(@.name=='sve2')]"

Diff for: tests/rustdoc-json/targets/x86_64_pc_windows_gnu.rs

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//@ only-x86_64-pc-windows-gnu
2+
3+
//@ is "$.target.triple \"x86_64-pc-windows-gnu\"
4+
//@ is "$.target.target_features[?(@.name=='sse2')].globally_enabled" true
5+
//@ is "$.target.target_features[?(@.name=='avx2')].globally_enabled" false
6+
//@ has "$.target.target_features[?(@.name=='avx2')].implies_features" '["avx"]'
7+
//@ is "$.target.target_features[?(@.name=='avx2')].unstable_feature_gate" null
8+
9+
// If this breaks due to stabilization, check rustc_target::target_features for a replacement
10+
//@ is "$.target.target_features[?(@.name=='amx-tile')].unstable_feature_gate" '"x86_amx_intrinsics"'
11+
//@ is "$.target.target_features[?(@.name=='x87')].unstable_feature_gate" '"x87_target_feature"'
12+
13+
// Ensure we don't look like aarch64
14+
//@ !has "$.target.target_features[?(@.name=='sve2')]"

Diff for: tests/rustdoc-json/targets/x86_64_pc_windows_msvc.rs

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//@ only-x86_64-pc-windows-msvc
2+
3+
//@ is "$.target.triple" \"x86_64-pc-windows-msvc\"
4+
//@ is "$.target.target_features[?(@.name=='sse2')].globally_enabled" true
5+
//@ is "$.target.target_features[?(@.name=='avx2')].globally_enabled" false
6+
//@ has "$.target.target_features[?(@.name=='avx2')].implies_features" '["avx"]'
7+
//@ is "$.target.target_features[?(@.name=='avx2')].unstable_feature_gate" null
8+
9+
// If this breaks due to stabilization, check rustc_target::target_features for a replacement
10+
//@ is "$.target.target_features[?(@.name=='amx-tile')].unstable_feature_gate" '"x86_amx_intrinsics"'
11+
//@ is "$.target.target_features[?(@.name=='x87')].unstable_feature_gate" '"x87_target_feature"'
12+
13+
// Ensure we don't look like aarch64
14+
//@ !has "$.target.target_features[?(@.name=='sve2')]"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
//@ only-x86_64
2+
3+
// If we enable AVX2, we should see that it is enabled
4+
//@ compile-flags: -Ctarget-feature=+avx2
5+
//@ is "$.target.target_features[?(@.name=='avx2')].globally_enabled" true
6+
7+
// As well as its dependency chain
8+
//@ is "$.target.target_features[?(@.name=='avx')].globally_enabled" true
9+
//@ is "$.target.target_features[?(@.name=='sse4.2')].globally_enabled" true
10+
//@ is "$.target.target_features[?(@.name=='sse4.1')].globally_enabled" true
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//@ only-x86_64-unknown-linux-gnu
2+
3+
//@ is "$.target.triple" \"x86_64-unknown-linux-gnu\"
4+
//@ is "$.target.target_features[?(@.name=='sse2')].globally_enabled" true
5+
//@ is "$.target.target_features[?(@.name=='avx2')].globally_enabled" false
6+
//@ has "$.target.target_features[?(@.name=='avx2')].implies_features" '["avx"]'
7+
//@ is "$.target.target_features[?(@.name=='avx2')].unstable_feature_gate" null
8+
9+
// If this breaks due to stabilization, check rustc_target::target_features for a replacement
10+
//@ is "$.target.target_features[?(@.name=='amx-tile')].unstable_feature_gate" '"x86_amx_intrinsics"'
11+
//@ is "$.target.target_features[?(@.name=='x87')].unstable_feature_gate" '"x87_target_feature"'
12+
13+
// Ensure we don't look like aarch64
14+
//@ !has "$.target.target_features[?(@.name=='sve2')]"

0 commit comments

Comments
 (0)