Skip to content

Commit 6ab2f24

Browse files
committed
Ensure that static initializers are acyclic for NVPTX
NVPTX does not support cycles in static initializers (see #146787). LLVM produces an error when attempting to codegen such constructs (like self referential structs). To not produce LLVM UB we instead emit a post-monomorphization error on Rust side before reaching codegen. This is achieved by analysing a subgraph of the "mono item graph" that only contains statics: 1. Calculate the strongly connected components (SCCs) of the graph 2. Check for cycles (more than one node in a SCC or exactly one node which references itself)
1 parent b49c7d7 commit 6ab2f24

File tree

16 files changed

+276
-2
lines changed

16 files changed

+276
-2
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4422,6 +4422,7 @@ dependencies = [
44224422
"rustc_errors",
44234423
"rustc_fluent_macro",
44244424
"rustc_hir",
4425+
"rustc_index",
44254426
"rustc_macros",
44264427
"rustc_middle",
44274428
"rustc_session",

compiler/rustc_monomorphize/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ rustc_data_structures = { path = "../rustc_data_structures" }
1010
rustc_errors = { path = "../rustc_errors" }
1111
rustc_fluent_macro = { path = "../rustc_fluent_macro" }
1212
rustc_hir = { path = "../rustc_hir" }
13+
rustc_index = { path = "../rustc_index" }
1314
rustc_macros = { path = "../rustc_macros" }
1415
rustc_middle = { path = "../rustc_middle" }
1516
rustc_session = { path = "../rustc_session" }

compiler/rustc_monomorphize/messages.ftl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,8 @@ monomorphize_recursion_limit =
7575
monomorphize_start_not_found = using `fn main` requires the standard library
7676
.help = use `#![no_main]` to bypass the Rust generated entrypoint and declare a platform specific entrypoint yourself, usually with `#[no_mangle]`
7777
78+
monomorphize_static_initializer_cyclic = static initializer forms a cycle involving `{$head}`
79+
.label = part of this cycle
80+
.note = cyclic static initializers are not supported for target `{$target}`
81+
7882
monomorphize_symbol_already_defined = symbol `{$symbol}` is already defined

compiler/rustc_monomorphize/src/collector.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,10 @@ pub(crate) struct UsageMap<'tcx> {
267267
// Maps every mono item to the mono items used by it.
268268
pub used_map: UnordMap<MonoItem<'tcx>, Vec<MonoItem<'tcx>>>,
269269

270-
// Maps every mono item to the mono items that use it.
270+
// Maps each mono item with users to the mono items that use it.
271+
// Be careful: [`user_map`] does not contain entries for mono items without
272+
// users. In contrast, mono items that do not use other mono items are
273+
// present in [`used_map`], but their value is an empty vector.
271274
user_map: UnordMap<MonoItem<'tcx>, Vec<MonoItem<'tcx>>>,
272275
}
273276

compiler/rustc_monomorphize/src/errors.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,3 +117,15 @@ pub(crate) struct AbiRequiredTargetFeature<'a> {
117117
/// Whether this is a problem at a call site or at a declaration.
118118
pub is_call: bool,
119119
}
120+
121+
#[derive(Diagnostic)]
122+
#[diag(monomorphize_static_initializer_cyclic)]
123+
#[note]
124+
pub(crate) struct StaticInitializerCyclic<'a> {
125+
#[primary_span]
126+
pub span: Span,
127+
#[label]
128+
pub labels: Vec<Span>,
129+
pub head: &'a str,
130+
pub target: &'a str,
131+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//! Checks that need to operate on the entire mono item graph
2+
use rustc_middle::mir::mono::MonoItem;
3+
use rustc_middle::ty::TyCtxt;
4+
5+
use crate::collector::UsageMap;
6+
use crate::graph_checks::statics::check_static_initializers_are_acyclic;
7+
8+
mod statics;
9+
10+
pub(super) fn check_mono_item_graph<'tcx, 'a, 'b>(
11+
tcx: TyCtxt<'tcx>,
12+
mono_items: &'a [MonoItem<'tcx>],
13+
usage_map: &'b UsageMap<'tcx>,
14+
) {
15+
do_target_specific_checks(tcx, mono_items, usage_map);
16+
}
17+
18+
fn do_target_specific_checks<'tcx, 'a, 'b>(
19+
tcx: TyCtxt<'tcx>,
20+
mono_items: &'a [MonoItem<'tcx>],
21+
usage_map: &'b UsageMap<'tcx>,
22+
) {
23+
if tcx.sess.target.options.static_initializer_must_be_acyclic {
24+
check_static_initializers_are_acyclic(tcx, mono_items, usage_map);
25+
}
26+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
use rustc_data_structures::fx::FxIndexSet;
2+
use rustc_data_structures::graph::scc::Sccs;
3+
use rustc_data_structures::graph::{DirectedGraph, Successors};
4+
use rustc_data_structures::unord::UnordMap;
5+
use rustc_hir::def_id::DefId;
6+
use rustc_index::{Idx, IndexVec, newtype_index};
7+
use rustc_middle::mir::mono::MonoItem;
8+
use rustc_middle::ty::TyCtxt;
9+
10+
use crate::collector::UsageMap;
11+
use crate::errors;
12+
13+
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
14+
struct StaticNodeIdx(usize);
15+
16+
impl Idx for StaticNodeIdx {
17+
fn new(idx: usize) -> Self {
18+
Self(idx)
19+
}
20+
21+
fn index(self) -> usize {
22+
self.0
23+
}
24+
}
25+
26+
impl From<usize> for StaticNodeIdx {
27+
fn from(value: usize) -> Self {
28+
StaticNodeIdx(value)
29+
}
30+
}
31+
32+
newtype_index! {
33+
#[derive(Ord, PartialOrd)]
34+
struct StaticSccIdx {}
35+
}
36+
37+
// Adjacency-list graph for statics using [`StaticNodeIdx`] as node type.
38+
// We cannot use [`DefId`] as the note type directly because each node must be
39+
// represented by an index in the range `0..num_nodes`.
40+
struct StaticRefGraph<'a, 'b, 'tcx> {
41+
// maps from [`StaticNodeIdx`] to [`DefId`] and vice versa
42+
statics: &'a FxIndexSet<DefId>,
43+
// contains for each [`MonoItem`] the [`MonoItem`]s it uses
44+
used_map: &'b UnordMap<MonoItem<'tcx>, Vec<MonoItem<'tcx>>>,
45+
}
46+
47+
impl<'a, 'b, 'tcx> DirectedGraph for StaticRefGraph<'a, 'b, 'tcx> {
48+
type Node = StaticNodeIdx;
49+
50+
fn num_nodes(&self) -> usize {
51+
self.statics.len()
52+
}
53+
}
54+
55+
impl<'a, 'b, 'tcx> Successors for StaticRefGraph<'a, 'b, 'tcx> {
56+
fn successors(&self, node_idx: StaticNodeIdx) -> impl Iterator<Item = StaticNodeIdx> {
57+
let def_id = self.statics[node_idx.index()];
58+
self.used_map[&MonoItem::Static(def_id)].iter().filter_map(|&mono_item| match mono_item {
59+
MonoItem::Static(def_id) => self.statics.get_index_of(&def_id).map(|idx| idx.into()),
60+
_ => None,
61+
})
62+
}
63+
}
64+
65+
pub(super) fn check_static_initializers_are_acyclic<'tcx, 'a, 'b>(
66+
tcx: TyCtxt<'tcx>,
67+
mono_items: &'a [MonoItem<'tcx>],
68+
usage_map: &'b UsageMap<'tcx>,
69+
) {
70+
// Collect statics
71+
let statics: FxIndexSet<DefId> = mono_items
72+
.iter()
73+
.filter_map(|&mono_item| match mono_item {
74+
MonoItem::Static(def_id) => Some(def_id),
75+
_ => None,
76+
})
77+
.collect();
78+
79+
// If we don't have any statics the check is not necessary
80+
if statics.is_empty() {
81+
return;
82+
}
83+
// Create a subgraph from the mono item graph, which only contains statics
84+
let graph = StaticRefGraph { statics: &statics, used_map: &usage_map.used_map };
85+
// Calculate its SCCs
86+
let sccs: Sccs<StaticNodeIdx, StaticSccIdx> = Sccs::new(&graph);
87+
// Group statics by SCCs
88+
let mut members: IndexVec<StaticSccIdx, Vec<StaticNodeIdx>> =
89+
IndexVec::from_elem_n(Vec::new(), sccs.num_sccs());
90+
for i in graph.iter_nodes() {
91+
members[sccs.scc(i)].push(i);
92+
}
93+
// Check each SCC for cycles
94+
for scc in sccs.all_sccs() {
95+
let nodes = members[scc].as_mut_slice();
96+
let acyclic = match nodes.len() {
97+
0 => true,
98+
1 => graph.successors(nodes[0]).all(|x| x != nodes[0]),
99+
2.. => false,
100+
};
101+
102+
if acyclic {
103+
continue;
104+
}
105+
// We sort the nodes by their Span to have consistent error line numbers
106+
nodes.sort_by_key(|node| tcx.def_span(statics[node.index()]));
107+
108+
let head_def = statics[nodes[0].index()];
109+
let head_span = tcx.def_span(head_def);
110+
111+
tcx.dcx().emit_err(errors::StaticInitializerCyclic {
112+
span: head_span,
113+
labels: nodes.iter().map(|&n| tcx.def_span(statics[n.index()])).collect(),
114+
head: &tcx.def_path_str(head_def),
115+
target: &tcx.sess.target.llvm_target,
116+
});
117+
}
118+
}

compiler/rustc_monomorphize/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use rustc_span::ErrorGuaranteed;
1616

1717
mod collector;
1818
mod errors;
19+
mod graph_checks;
1920
mod mono_checks;
2021
mod partitioning;
2122
mod util;

compiler/rustc_monomorphize/src/partitioning.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ use tracing::debug;
124124

125125
use crate::collector::{self, MonoItemCollectionStrategy, UsageMap};
126126
use crate::errors::{CouldntDumpMonoStats, SymbolAlreadyDefined};
127+
use crate::graph_checks::check_mono_item_graph;
127128

128129
struct PartitioningCx<'a, 'tcx> {
129130
tcx: TyCtxt<'tcx>,
@@ -1125,6 +1126,8 @@ fn collect_and_partition_mono_items(tcx: TyCtxt<'_>, (): ()) -> MonoItemPartitio
11251126
};
11261127

11271128
let (items, usage_map) = collector::collect_crate_mono_items(tcx, collection_strategy);
1129+
// Perform checks that need to operate on the entire mono item graph
1130+
check_mono_item_graph(tcx, &items, &usage_map);
11281131

11291132
// If there was an error during collection (e.g. from one of the constants we evaluated),
11301133
// then we stop here. This way codegen does not have to worry about failing constants.

compiler/rustc_target/src/spec/json.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ impl Target {
163163
forward!(relro_level);
164164
forward!(archive_format);
165165
forward!(allow_asm);
166+
forward!(static_initializer_must_be_acyclic);
166167
forward!(main_needs_argc_argv);
167168
forward!(has_thread_local);
168169
forward!(obj_is_bitcode);
@@ -360,6 +361,7 @@ impl ToJson for Target {
360361
target_option_val!(relro_level);
361362
target_option_val!(archive_format);
362363
target_option_val!(allow_asm);
364+
target_option_val!(static_initializer_must_be_acyclic);
363365
target_option_val!(main_needs_argc_argv);
364366
target_option_val!(has_thread_local);
365367
target_option_val!(obj_is_bitcode);
@@ -581,6 +583,7 @@ struct TargetSpecJson {
581583
relro_level: Option<RelroLevel>,
582584
archive_format: Option<StaticCow<str>>,
583585
allow_asm: Option<bool>,
586+
static_initializer_must_be_acyclic: Option<bool>,
584587
main_needs_argc_argv: Option<bool>,
585588
has_thread_local: Option<bool>,
586589
obj_is_bitcode: Option<bool>,

0 commit comments

Comments
 (0)