Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4421,6 +4421,7 @@ dependencies = [
"rustc_errors",
"rustc_fluent_macro",
"rustc_hir",
"rustc_index",
"rustc_macros",
"rustc_middle",
"rustc_session",
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_monomorphize/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_monomorphize/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 2 additions & 1 deletion compiler/rustc_monomorphize/src/collector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>>>,
}

Expand Down
12 changes: 12 additions & 0 deletions compiler/rustc_monomorphize/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 18 additions & 0 deletions compiler/rustc_monomorphize/src/graph_checks/mod.rs
Original file line number Diff line number Diff line change
@@ -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>(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a way this is sort of a misnomer because target feature checks are also target-specific but they're also kinda not?

I'm not gonna stress over it.

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 changes: 115 additions & 0 deletions compiler/rustc_monomorphize/src/graph_checks/statics.rs
Original file line number Diff line number Diff line change
@@ -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)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor adjustment:
I now iterate directly over the SCC-grouped nodes and check for cyclicality instead of acyclicality, which makes it possible to filter.

// 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,
});
}
}
1 change: 1 addition & 0 deletions compiler/rustc_monomorphize/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use rustc_span::ErrorGuaranteed;

mod collector;
mod errors;
mod graph_checks;
mod mono_checks;
mod partitioning;
mod util;
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_monomorphize/src/partitioning.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
Expand Down Expand Up @@ -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.
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_target/src/spec/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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>,
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_target/src/spec/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_target/src/spec/targets/nvptx64_nvidia_cuda.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
},
}
Expand Down
33 changes: 33 additions & 0 deletions src/doc/rustc/src/platform-support/nvptx64-nvidia-cuda.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion tests/auxiliary/minicore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ impl Neg for isize {
}

#[lang = "sync"]
trait Sync {}
pub trait Sync {}
impl_marker_trait!(
Sync => [
char, bool,
Expand Down
29 changes: 29 additions & 0 deletions tests/ui/static/static-initializer-acyclic-issue-146787.rs
Original file line number Diff line number Diff line change
@@ -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;
32 changes: 32 additions & 0 deletions tests/ui/static/static-initializer-acyclic-issue-146787.stderr
Original file line number Diff line number Diff line change
@@ -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

Loading