mirror of
https://github.com/rust-lang/rust.git
synced 2026-01-25 07:48:44 +00:00
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)
This commit is contained in:
@@ -4421,6 +4421,7 @@ dependencies = [
|
||||
"rustc_errors",
|
||||
"rustc_fluent_macro",
|
||||
"rustc_hir",
|
||||
"rustc_index",
|
||||
"rustc_macros",
|
||||
"rustc_middle",
|
||||
"rustc_session",
|
||||
|
||||
@@ -10,6 +10,7 @@ rustc_data_structures = { path = "../rustc_data_structures" }
|
||||
rustc_errors = { path = "../rustc_errors" }
|
||||
rustc_fluent_macro = { path = "../rustc_fluent_macro" }
|
||||
rustc_hir = { path = "../rustc_hir" }
|
||||
rustc_index = { path = "../rustc_index" }
|
||||
rustc_macros = { path = "../rustc_macros" }
|
||||
rustc_middle = { path = "../rustc_middle" }
|
||||
rustc_session = { path = "../rustc_session" }
|
||||
|
||||
@@ -75,4 +75,8 @@ monomorphize_recursion_limit =
|
||||
monomorphize_start_not_found = using `fn main` requires the standard library
|
||||
.help = use `#![no_main]` to bypass the Rust generated entrypoint and declare a platform specific entrypoint yourself, usually with `#[no_mangle]`
|
||||
|
||||
monomorphize_static_initializer_cyclic = static initializer forms a cycle involving `{$head}`
|
||||
.label = part of this cycle
|
||||
.note = cyclic static initializers are not supported for target `{$target}`
|
||||
|
||||
monomorphize_symbol_already_defined = symbol `{$symbol}` is already defined
|
||||
|
||||
@@ -267,7 +267,8 @@ pub(crate) struct UsageMap<'tcx> {
|
||||
// Maps every mono item to the mono items used by it.
|
||||
pub used_map: UnordMap<MonoItem<'tcx>, Vec<MonoItem<'tcx>>>,
|
||||
|
||||
// Maps every mono item to the mono items that use it.
|
||||
// Maps each mono item with users to the mono items that use it.
|
||||
// Be careful: subsets `used_map`, so unused items are vacant.
|
||||
user_map: UnordMap<MonoItem<'tcx>, Vec<MonoItem<'tcx>>>,
|
||||
}
|
||||
|
||||
|
||||
@@ -117,3 +117,15 @@ pub(crate) struct AbiRequiredTargetFeature<'a> {
|
||||
/// Whether this is a problem at a call site or at a declaration.
|
||||
pub is_call: bool,
|
||||
}
|
||||
|
||||
#[derive(Diagnostic)]
|
||||
#[diag(monomorphize_static_initializer_cyclic)]
|
||||
#[note]
|
||||
pub(crate) struct StaticInitializerCyclic<'a> {
|
||||
#[primary_span]
|
||||
pub span: Span,
|
||||
#[label]
|
||||
pub labels: Vec<Span>,
|
||||
pub head: &'a str,
|
||||
pub target: &'a str,
|
||||
}
|
||||
|
||||
18
compiler/rustc_monomorphize/src/graph_checks/mod.rs
Normal file
18
compiler/rustc_monomorphize/src/graph_checks/mod.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
//! Checks that need to operate on the entire mono item graph
|
||||
use rustc_middle::mir::mono::MonoItem;
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
|
||||
use crate::collector::UsageMap;
|
||||
use crate::graph_checks::statics::check_static_initializers_are_acyclic;
|
||||
|
||||
mod statics;
|
||||
|
||||
pub(super) fn target_specific_checks<'tcx, 'a, 'b>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
mono_items: &'a [MonoItem<'tcx>],
|
||||
usage_map: &'b UsageMap<'tcx>,
|
||||
) {
|
||||
if tcx.sess.target.options.static_initializer_must_be_acyclic {
|
||||
check_static_initializers_are_acyclic(tcx, mono_items, usage_map);
|
||||
}
|
||||
}
|
||||
115
compiler/rustc_monomorphize/src/graph_checks/statics.rs
Normal file
115
compiler/rustc_monomorphize/src/graph_checks/statics.rs
Normal file
@@ -0,0 +1,115 @@
|
||||
use rustc_data_structures::fx::FxIndexSet;
|
||||
use rustc_data_structures::graph::scc::Sccs;
|
||||
use rustc_data_structures::graph::{DirectedGraph, Successors};
|
||||
use rustc_data_structures::unord::UnordMap;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_index::{Idx, IndexVec, newtype_index};
|
||||
use rustc_middle::mir::mono::MonoItem;
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
|
||||
use crate::collector::UsageMap;
|
||||
use crate::errors;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
struct StaticNodeIdx(usize);
|
||||
|
||||
impl Idx for StaticNodeIdx {
|
||||
fn new(idx: usize) -> Self {
|
||||
Self(idx)
|
||||
}
|
||||
|
||||
fn index(self) -> usize {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<usize> for StaticNodeIdx {
|
||||
fn from(value: usize) -> Self {
|
||||
StaticNodeIdx(value)
|
||||
}
|
||||
}
|
||||
|
||||
newtype_index! {
|
||||
#[derive(Ord, PartialOrd)]
|
||||
struct StaticSccIdx {}
|
||||
}
|
||||
|
||||
// Adjacency-list graph for statics using `StaticNodeIdx` as node type.
|
||||
// We cannot use `DefId` as the node type directly because each node must be
|
||||
// represented by an index in the range `0..num_nodes`.
|
||||
struct StaticRefGraph<'a, 'b, 'tcx> {
|
||||
// maps from `StaticNodeIdx` to `DefId` and vice versa
|
||||
statics: &'a FxIndexSet<DefId>,
|
||||
// contains for each `MonoItem` the `MonoItem`s it uses
|
||||
used_map: &'b UnordMap<MonoItem<'tcx>, Vec<MonoItem<'tcx>>>,
|
||||
}
|
||||
|
||||
impl<'a, 'b, 'tcx> DirectedGraph for StaticRefGraph<'a, 'b, 'tcx> {
|
||||
type Node = StaticNodeIdx;
|
||||
|
||||
fn num_nodes(&self) -> usize {
|
||||
self.statics.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b, 'tcx> Successors for StaticRefGraph<'a, 'b, 'tcx> {
|
||||
fn successors(&self, node_idx: StaticNodeIdx) -> impl Iterator<Item = StaticNodeIdx> {
|
||||
let def_id = self.statics[node_idx.index()];
|
||||
self.used_map[&MonoItem::Static(def_id)].iter().filter_map(|&mono_item| match mono_item {
|
||||
MonoItem::Static(def_id) => self.statics.get_index_of(&def_id).map(|idx| idx.into()),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn check_static_initializers_are_acyclic<'tcx, 'a, 'b>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
mono_items: &'a [MonoItem<'tcx>],
|
||||
usage_map: &'b UsageMap<'tcx>,
|
||||
) {
|
||||
// Collect statics
|
||||
let statics: FxIndexSet<DefId> = mono_items
|
||||
.iter()
|
||||
.filter_map(|&mono_item| match mono_item {
|
||||
MonoItem::Static(def_id) => Some(def_id),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
|
||||
// If we don't have any statics the check is not necessary
|
||||
if statics.is_empty() {
|
||||
return;
|
||||
}
|
||||
// Create a subgraph from the mono item graph, which only contains statics
|
||||
let graph = StaticRefGraph { statics: &statics, used_map: &usage_map.used_map };
|
||||
// Calculate its SCCs
|
||||
let sccs: Sccs<StaticNodeIdx, StaticSccIdx> = Sccs::new(&graph);
|
||||
// Group statics by SCCs
|
||||
let mut nodes_of_sccs: IndexVec<StaticSccIdx, Vec<StaticNodeIdx>> =
|
||||
IndexVec::from_elem_n(Vec::new(), sccs.num_sccs());
|
||||
for i in graph.iter_nodes() {
|
||||
nodes_of_sccs[sccs.scc(i)].push(i);
|
||||
}
|
||||
let is_cyclic = |nodes_of_scc: &[StaticNodeIdx]| -> bool {
|
||||
match nodes_of_scc.len() {
|
||||
0 => false,
|
||||
1 => graph.successors(nodes_of_scc[0]).any(|x| x == nodes_of_scc[0]),
|
||||
2.. => true,
|
||||
}
|
||||
};
|
||||
// Emit errors for all cycles
|
||||
for nodes in nodes_of_sccs.iter_mut().filter(|nodes| is_cyclic(nodes)) {
|
||||
// We sort the nodes by their Span to have consistent error line numbers
|
||||
nodes.sort_by_key(|node| tcx.def_span(statics[node.index()]));
|
||||
|
||||
let head_def = statics[nodes[0].index()];
|
||||
let head_span = tcx.def_span(head_def);
|
||||
|
||||
tcx.dcx().emit_err(errors::StaticInitializerCyclic {
|
||||
span: head_span,
|
||||
labels: nodes.iter().map(|&n| tcx.def_span(statics[n.index()])).collect(),
|
||||
head: &tcx.def_path_str(head_def),
|
||||
target: &tcx.sess.target.llvm_target,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ use rustc_span::ErrorGuaranteed;
|
||||
|
||||
mod collector;
|
||||
mod errors;
|
||||
mod graph_checks;
|
||||
mod mono_checks;
|
||||
mod partitioning;
|
||||
mod util;
|
||||
|
||||
@@ -124,6 +124,7 @@ use tracing::debug;
|
||||
|
||||
use crate::collector::{self, MonoItemCollectionStrategy, UsageMap};
|
||||
use crate::errors::{CouldntDumpMonoStats, SymbolAlreadyDefined};
|
||||
use crate::graph_checks::target_specific_checks;
|
||||
|
||||
struct PartitioningCx<'a, 'tcx> {
|
||||
tcx: TyCtxt<'tcx>,
|
||||
@@ -1125,6 +1126,8 @@ fn collect_and_partition_mono_items(tcx: TyCtxt<'_>, (): ()) -> MonoItemPartitio
|
||||
};
|
||||
|
||||
let (items, usage_map) = collector::collect_crate_mono_items(tcx, collection_strategy);
|
||||
// Perform checks that need to operate on the entire mono item graph
|
||||
target_specific_checks(tcx, &items, &usage_map);
|
||||
|
||||
// If there was an error during collection (e.g. from one of the constants we evaluated),
|
||||
// then we stop here. This way codegen does not have to worry about failing constants.
|
||||
|
||||
@@ -163,6 +163,7 @@ impl Target {
|
||||
forward!(relro_level);
|
||||
forward!(archive_format);
|
||||
forward!(allow_asm);
|
||||
forward!(static_initializer_must_be_acyclic);
|
||||
forward!(main_needs_argc_argv);
|
||||
forward!(has_thread_local);
|
||||
forward!(obj_is_bitcode);
|
||||
@@ -360,6 +361,7 @@ impl ToJson for Target {
|
||||
target_option_val!(relro_level);
|
||||
target_option_val!(archive_format);
|
||||
target_option_val!(allow_asm);
|
||||
target_option_val!(static_initializer_must_be_acyclic);
|
||||
target_option_val!(main_needs_argc_argv);
|
||||
target_option_val!(has_thread_local);
|
||||
target_option_val!(obj_is_bitcode);
|
||||
@@ -581,6 +583,7 @@ struct TargetSpecJson {
|
||||
relro_level: Option<RelroLevel>,
|
||||
archive_format: Option<StaticCow<str>>,
|
||||
allow_asm: Option<bool>,
|
||||
static_initializer_must_be_acyclic: Option<bool>,
|
||||
main_needs_argc_argv: Option<bool>,
|
||||
has_thread_local: Option<bool>,
|
||||
obj_is_bitcode: Option<bool>,
|
||||
|
||||
@@ -2394,6 +2394,9 @@ pub struct TargetOptions {
|
||||
pub archive_format: StaticCow<str>,
|
||||
/// Is asm!() allowed? Defaults to true.
|
||||
pub allow_asm: bool,
|
||||
/// Static initializers must be acyclic.
|
||||
/// Defaults to false
|
||||
pub static_initializer_must_be_acyclic: bool,
|
||||
/// Whether the runtime startup code requires the `main` function be passed
|
||||
/// `argc` and `argv` values.
|
||||
pub main_needs_argc_argv: bool,
|
||||
@@ -2777,6 +2780,7 @@ impl Default for TargetOptions {
|
||||
archive_format: "gnu".into(),
|
||||
main_needs_argc_argv: true,
|
||||
allow_asm: true,
|
||||
static_initializer_must_be_acyclic: false,
|
||||
has_thread_local: false,
|
||||
obj_is_bitcode: false,
|
||||
min_atomic_width: None,
|
||||
|
||||
@@ -59,6 +59,9 @@ pub(crate) fn target() -> Target {
|
||||
// Support using `self-contained` linkers like the llvm-bitcode-linker
|
||||
link_self_contained: LinkSelfContainedDefault::True,
|
||||
|
||||
// Static initializers must not have cycles on this target
|
||||
static_initializer_must_be_acyclic: true,
|
||||
|
||||
..Default::default()
|
||||
},
|
||||
}
|
||||
|
||||
@@ -49,6 +49,39 @@ $ rustup component add llvm-tools --toolchain nightly
|
||||
$ rustup component add llvm-bitcode-linker --toolchain nightly
|
||||
```
|
||||
|
||||
## Target specific restrictions
|
||||
|
||||
The PTX instruction set architecture has special requirements regarding what is
|
||||
and isn't allowed. In order to avoid producing invalid PTX or generating undefined
|
||||
behavior by LLVM, some Rust language features are disallowed when compiling for this target.
|
||||
|
||||
### Static initializers must be acyclic
|
||||
|
||||
A static's initializer must not form a cycle with itself or another static's
|
||||
initializer. Therefore, the compiler will reject not only the self-referencing static `A`,
|
||||
but all of the following statics.
|
||||
|
||||
```Rust
|
||||
struct Foo(&'static Foo);
|
||||
|
||||
static A: Foo = Foo(&A); //~ ERROR static initializer forms a cycle involving `A`
|
||||
|
||||
static B0: Foo = Foo(&B1); //~ ERROR static initializer forms a cycle involving `B0`
|
||||
static B1: Foo = Foo(&B0);
|
||||
|
||||
static C0: Foo = Foo(&C1); //~ ERROR static initializer forms a cycle involving `C0`
|
||||
static C1: Foo = Foo(&C2);
|
||||
static C2: Foo = Foo(&C0);
|
||||
```
|
||||
|
||||
Initializers that are acyclic are allowed:
|
||||
|
||||
```Rust
|
||||
struct Bar(&'static u32);
|
||||
|
||||
static BAR: Bar = Bar(&INT); // is allowed
|
||||
static INT: u32 = 42u32; // also allowed
|
||||
```
|
||||
|
||||
<!-- FIXME: fill this out
|
||||
|
||||
|
||||
@@ -211,7 +211,7 @@ impl Neg for isize {
|
||||
}
|
||||
|
||||
#[lang = "sync"]
|
||||
trait Sync {}
|
||||
pub trait Sync {}
|
||||
impl_marker_trait!(
|
||||
Sync => [
|
||||
char, bool,
|
||||
|
||||
29
tests/ui/static/static-initializer-acyclic-issue-146787.rs
Normal file
29
tests/ui/static/static-initializer-acyclic-issue-146787.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
//@ add-minicore
|
||||
//@ needs-llvm-components: nvptx
|
||||
//@ compile-flags: --target nvptx64-nvidia-cuda --emit link
|
||||
//@ ignore-backends: gcc
|
||||
#![crate_type = "rlib"]
|
||||
#![feature(no_core)]
|
||||
#![no_std]
|
||||
#![no_core]
|
||||
|
||||
extern crate minicore;
|
||||
use minicore::*;
|
||||
|
||||
struct Foo(&'static Foo);
|
||||
impl Sync for Foo {}
|
||||
|
||||
static A: Foo = Foo(&A); //~ ERROR static initializer forms a cycle involving `A`
|
||||
|
||||
static B0: Foo = Foo(&B1); //~ ERROR static initializer forms a cycle involving `B0`
|
||||
static B1: Foo = Foo(&B0);
|
||||
|
||||
static C0: Foo = Foo(&C1); //~ ERROR static initializer forms a cycle involving `C0`
|
||||
static C1: Foo = Foo(&C2);
|
||||
static C2: Foo = Foo(&C0);
|
||||
|
||||
struct Bar(&'static u32);
|
||||
impl Sync for Bar {}
|
||||
|
||||
static BAR: Bar = Bar(&INT);
|
||||
static INT: u32 = 42u32;
|
||||
@@ -0,0 +1,32 @@
|
||||
error: static initializer forms a cycle involving `C0`
|
||||
--> $DIR/static-initializer-acyclic-issue-146787.rs:21:1
|
||||
|
|
||||
LL | static C0: Foo = Foo(&C1);
|
||||
| ^^^^^^^^^^^^^^ part of this cycle
|
||||
LL | static C1: Foo = Foo(&C2);
|
||||
| -------------- part of this cycle
|
||||
LL | static C2: Foo = Foo(&C0);
|
||||
| -------------- part of this cycle
|
||||
|
|
||||
= note: cyclic static initializers are not supported for target `nvptx64-nvidia-cuda`
|
||||
|
||||
error: static initializer forms a cycle involving `B0`
|
||||
--> $DIR/static-initializer-acyclic-issue-146787.rs:18:1
|
||||
|
|
||||
LL | static B0: Foo = Foo(&B1);
|
||||
| ^^^^^^^^^^^^^^ part of this cycle
|
||||
LL | static B1: Foo = Foo(&B0);
|
||||
| -------------- part of this cycle
|
||||
|
|
||||
= note: cyclic static initializers are not supported for target `nvptx64-nvidia-cuda`
|
||||
|
||||
error: static initializer forms a cycle involving `A`
|
||||
--> $DIR/static-initializer-acyclic-issue-146787.rs:16:1
|
||||
|
|
||||
LL | static A: Foo = Foo(&A);
|
||||
| ^^^^^^^^^^^^^ part of this cycle
|
||||
|
|
||||
= note: cyclic static initializers are not supported for target `nvptx64-nvidia-cuda`
|
||||
|
||||
error: aborting due to 3 previous errors
|
||||
|
||||
Reference in New Issue
Block a user