Skip to content

Commit f4c3911

Browse files
committed
Ensure that static initializers are acyclic for NVPTX
NVPTX does not support cycles in static initializers. 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 bd33b83 commit f4c3911

File tree

16 files changed

+275
-2
lines changed

16 files changed

+275
-2
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4421,6 +4421,7 @@ dependencies = [
44214421
"rustc_errors",
44224422
"rustc_fluent_macro",
44234423
"rustc_hir",
4424+
"rustc_index",
44244425
"rustc_macros",
44254426
"rustc_middle",
44264427
"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)