From f89cce3acb5106b98f6ada765677dc11e41518a0 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Wed, 17 Dec 2025 14:33:10 +0100 Subject: [PATCH 1/3] `c_variadic`: provide `va_arg` for more targets --- compiler/rustc_codegen_llvm/src/va_arg.rs | 106 +++++++++++++++++----- 1 file changed, 83 insertions(+), 23 deletions(-) diff --git a/compiler/rustc_codegen_llvm/src/va_arg.rs b/compiler/rustc_codegen_llvm/src/va_arg.rs index b23415a732cc7..688f461e7478a 100644 --- a/compiler/rustc_codegen_llvm/src/va_arg.rs +++ b/compiler/rustc_codegen_llvm/src/va_arg.rs @@ -1,12 +1,13 @@ -use rustc_abi::{Align, BackendRepr, Endian, HasDataLayout, Primitive, Size, TyAndLayout}; +use rustc_abi::{Align, BackendRepr, Endian, HasDataLayout, Primitive, Size}; use rustc_codegen_ssa::MemFlags; use rustc_codegen_ssa::common::IntPredicate; use rustc_codegen_ssa::mir::operand::OperandRef; use rustc_codegen_ssa::traits::{ BaseTypeCodegenMethods, BuilderMethods, ConstCodegenMethods, LayoutTypeCodegenMethods, }; +use rustc_middle::bug; use rustc_middle::ty::Ty; -use rustc_middle::ty::layout::{HasTyCtxt, LayoutOf}; +use rustc_middle::ty::layout::{HasTyCtxt, LayoutOf, TyAndLayout}; use rustc_target::spec::{Abi, Arch, Env}; use crate::builder::Builder; @@ -82,6 +83,7 @@ enum PassMode { enum SlotSize { Bytes8 = 8, Bytes4 = 4, + Bytes1 = 1, } enum AllowHigherAlign { @@ -728,7 +730,7 @@ fn emit_x86_64_sysv64_va_arg<'ll, 'tcx>( fn copy_to_temporary_if_more_aligned<'ll, 'tcx>( bx: &mut Builder<'_, 'll, 'tcx>, reg_addr: &'ll Value, - layout: TyAndLayout<'tcx, Ty<'tcx>>, + layout: TyAndLayout<'tcx>, src_align: Align, ) -> &'ll Value { if layout.layout.align.abi > src_align { @@ -751,7 +753,7 @@ fn copy_to_temporary_if_more_aligned<'ll, 'tcx>( fn x86_64_sysv64_va_arg_from_memory<'ll, 'tcx>( bx: &mut Builder<'_, 'll, 'tcx>, va_list_addr: &'ll Value, - layout: TyAndLayout<'tcx, Ty<'tcx>>, + layout: TyAndLayout<'tcx>, ) -> &'ll Value { let dl = bx.cx.data_layout(); let ptr_align_abi = dl.data_layout().pointer_align().abi; @@ -1003,15 +1005,17 @@ fn emit_xtensa_va_arg<'ll, 'tcx>( return bx.load(layout.llvm_type(bx), value_ptr, layout.align.abi); } +/// Determine the va_arg implementation to use. The LLVM va_arg instruction +/// is lacking in some instances, so we should only use it as a fallback. pub(super) fn emit_va_arg<'ll, 'tcx>( bx: &mut Builder<'_, 'll, 'tcx>, addr: OperandRef<'tcx, &'ll Value>, target_ty: Ty<'tcx>, ) -> &'ll Value { - // Determine the va_arg implementation to use. The LLVM va_arg instruction - // is lacking in some instances, so we should only use it as a fallback. - let target = &bx.cx.tcx.sess.target; + let layout = bx.cx.layout_of(target_ty); + let target_ty_size = layout.layout.size().bytes(); + let target = &bx.cx.tcx.sess.target; match target.arch { Arch::X86 => emit_ptr_va_arg( bx, @@ -1069,23 +1073,79 @@ pub(super) fn emit_va_arg<'ll, 'tcx>( AllowHigherAlign::Yes, ForceRightAdjust::No, ), + Arch::LoongArch32 => emit_ptr_va_arg( + bx, + addr, + target_ty, + if target_ty_size > 2 * 4 { PassMode::Indirect } else { PassMode::Direct }, + SlotSize::Bytes4, + AllowHigherAlign::Yes, + ForceRightAdjust::No, + ), + Arch::LoongArch64 => emit_ptr_va_arg( + bx, + addr, + target_ty, + if target_ty_size > 2 * 8 { PassMode::Indirect } else { PassMode::Direct }, + SlotSize::Bytes8, + AllowHigherAlign::Yes, + ForceRightAdjust::No, + ), + Arch::AmdGpu => emit_ptr_va_arg( + bx, + addr, + target_ty, + PassMode::Direct, + SlotSize::Bytes4, + AllowHigherAlign::No, + ForceRightAdjust::No, + ), + Arch::Nvptx64 => emit_ptr_va_arg( + bx, + addr, + target_ty, + PassMode::Direct, + SlotSize::Bytes1, + AllowHigherAlign::Yes, + ForceRightAdjust::No, + ), + Arch::Wasm32 => emit_ptr_va_arg( + bx, + addr, + target_ty, + if layout.is_aggregate() || layout.is_zst() || layout.is_1zst() { + PassMode::Indirect + } else { + PassMode::Direct + }, + SlotSize::Bytes4, + AllowHigherAlign::Yes, + ForceRightAdjust::No, + ), + Arch::Wasm64 => bug!("c-variadic functions are not fully implemented for wasm64"), + Arch::CSky => emit_ptr_va_arg( + bx, + addr, + target_ty, + PassMode::Direct, + SlotSize::Bytes4, + AllowHigherAlign::Yes, + ForceRightAdjust::No, + ), // Windows x86_64 - Arch::X86_64 if target.is_like_windows => { - let target_ty_size = bx.cx.size_of(target_ty).bytes(); - emit_ptr_va_arg( - bx, - addr, - target_ty, - if target_ty_size > 8 || !target_ty_size.is_power_of_two() { - PassMode::Indirect - } else { - PassMode::Direct - }, - SlotSize::Bytes8, - AllowHigherAlign::No, - ForceRightAdjust::No, - ) - } + Arch::X86_64 if target.is_like_windows => emit_ptr_va_arg( + bx, + addr, + target_ty, + if target_ty_size > 8 || !target_ty_size.is_power_of_two() { + PassMode::Indirect + } else { + PassMode::Direct + }, + SlotSize::Bytes8, + AllowHigherAlign::No, + ForceRightAdjust::No, + ), // This includes `target.is_like_darwin`, which on x86_64 targets is like sysv64. Arch::X86_64 => emit_x86_64_sysv64_va_arg(bx, addr, target_ty), Arch::Xtensa => emit_xtensa_va_arg(bx, addr, target_ty), From 1cd87525e6b566b560a540626de9bfac6cc90b4a Mon Sep 17 00:00:00 2001 From: The 8472 Date: Sun, 4 Jan 2026 11:23:35 +0100 Subject: [PATCH 2/3] Unix implementation for stdio set/take/replace --- library/std/src/os/unix/io/mod.rs | 129 ++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) diff --git a/library/std/src/os/unix/io/mod.rs b/library/std/src/os/unix/io/mod.rs index 6d4090ee31cfc..708ebaec362e4 100644 --- a/library/std/src/os/unix/io/mod.rs +++ b/library/std/src/os/unix/io/mod.rs @@ -92,9 +92,138 @@ #![stable(feature = "rust1", since = "1.0.0")] +use crate::io::{self, Stderr, StderrLock, Stdin, StdinLock, Stdout, StdoutLock, Write}; #[stable(feature = "rust1", since = "1.0.0")] pub use crate::os::fd::*; +#[allow(unused_imports)] // not used on all targets +use crate::sys::cvt; // Tests for this module #[cfg(test)] mod tests; + +#[unstable(feature = "stdio_swap", issue = "150667", reason = "recently added")] +pub trait StdioExt: crate::sealed::Sealed { + /// Redirects the stdio file descriptor to point to the file description underpinning `fd`. + /// + /// Rust std::io write buffers (if any) are flushed, but other runtimes + /// (e.g. C stdio) or libraries that acquire a clone of the file descriptor + /// will not be aware of this change. + /// + /// # Platform-specific behavior + /// + /// This is [currently] implemented using + /// + /// - `fd_renumber` on wasip1 + /// - `dup2` on most unixes + /// + /// [currently]: crate::io#platform-specific-behavior + /// + /// ``` + /// #![feature(stdio_swap)] + /// use std::io::{self, Read, Write}; + /// use std::os::unix::io::StdioExt; + /// + /// fn main() -> io::Result<()> { + /// let (reader, mut writer) = io::pipe()?; + /// let mut stdin = io::stdin(); + /// stdin.set_fd(reader)?; + /// writer.write_all(b"Hello, world!")?; + /// let mut buffer = vec![0; 13]; + /// assert_eq!(stdin.read(&mut buffer)?, 13); + /// assert_eq!(&buffer, b"Hello, world!"); + /// Ok(()) + /// } + /// ``` + fn set_fd>(&mut self, fd: T) -> io::Result<()>; + + /// Redirects the stdio file descriptor and returns a new `OwnedFd` + /// backed by the previous file description. + /// + /// See [`set_fd()`] for details. + /// + /// [`set_fd()`]: StdioExt::set_fd + fn replace_fd>(&mut self, replace_with: T) -> io::Result; + + /// Redirects the stdio file descriptor to the null device (`/dev/null`) + /// and returns a new `OwnedFd` backed by the previous file description. + /// + /// Programs that communicate structured data via stdio can use this early in `main()` to + /// extract the fds, treat them as other IO types (`File`, `UnixStream`, etc), + /// apply custom buffering or avoid interference from stdio use later in the program. + /// + /// See [`set_fd()`] for additional details. + /// + /// [`set_fd()`]: StdioExt::set_fd + fn take_fd(&mut self) -> io::Result; +} + +macro io_ext_impl($stdio_ty:ty, $stdio_lock_ty:ty, $writer:literal) { + #[unstable(feature = "stdio_swap", issue = "150667", reason = "recently added")] + impl StdioExt for $stdio_ty { + fn set_fd>(&mut self, fd: T) -> io::Result<()> { + self.lock().set_fd(fd) + } + + fn take_fd(&mut self) -> io::Result { + self.lock().take_fd() + } + + fn replace_fd>(&mut self, replace_with: T) -> io::Result { + self.lock().replace_fd(replace_with) + } + } + + #[unstable(feature = "stdio_swap", issue = "150667", reason = "recently added")] + impl StdioExt for $stdio_lock_ty { + fn set_fd>(&mut self, fd: T) -> io::Result<()> { + #[cfg($writer)] + self.flush()?; + replace_stdio_fd(self.as_fd(), fd.into()) + } + + fn take_fd(&mut self) -> io::Result { + let null = null_fd()?; + let cloned = self.as_fd().try_clone_to_owned()?; + self.set_fd(null)?; + Ok(cloned) + } + + fn replace_fd>(&mut self, replace_with: T) -> io::Result { + let cloned = self.as_fd().try_clone_to_owned()?; + self.set_fd(replace_with)?; + Ok(cloned) + } + } +} + +io_ext_impl!(Stdout, StdoutLock<'_>, true); +io_ext_impl!(Stdin, StdinLock<'_>, false); +io_ext_impl!(Stderr, StderrLock<'_>, true); + +fn null_fd() -> io::Result { + let null_dev = crate::fs::OpenOptions::new().read(true).write(true).open("/dev/null")?; + Ok(null_dev.into()) +} + +/// Replaces the underlying file descriptor with the one from `other`. +/// Does not set CLOEXEC. +fn replace_stdio_fd(this: BorrowedFd<'_>, other: OwnedFd) -> io::Result<()> { + cfg_select! { + all(target_os = "wasi", target_env = "p1") => { + cvt(unsafe { libc::__wasilibc_fd_renumber(other.as_raw_fd(), this.as_raw_fd()) }).map(|_| ()) + } + not(any( + target_arch = "wasm32", + target_os = "hermit", + target_os = "trusty", + target_os = "motor" + )) => { + cvt(unsafe {libc::dup2(other.as_raw_fd(), this.as_raw_fd())}).map(|_| ()) + } + _ => { + let _ = (this, other); + Err(io::Error::UNSUPPORTED_PLATFORM) + } + } +} From 630c7596e959e6ab9b18552e81081e5ff346b1c3 Mon Sep 17 00:00:00 2001 From: kulst Date: Thu, 1 Jan 2026 19:25:29 +0100 Subject: [PATCH 3/3] 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) --- Cargo.lock | 1 + compiler/rustc_monomorphize/Cargo.toml | 1 + compiler/rustc_monomorphize/messages.ftl | 4 + compiler/rustc_monomorphize/src/collector.rs | 3 +- compiler/rustc_monomorphize/src/errors.rs | 12 ++ .../src/graph_checks/mod.rs | 18 +++ .../src/graph_checks/statics.rs | 115 ++++++++++++++++++ compiler/rustc_monomorphize/src/lib.rs | 1 + .../rustc_monomorphize/src/partitioning.rs | 3 + compiler/rustc_target/src/spec/json.rs | 3 + compiler/rustc_target/src/spec/mod.rs | 4 + .../src/spec/targets/nvptx64_nvidia_cuda.rs | 3 + .../platform-support/nvptx64-nvidia-cuda.md | 33 +++++ tests/auxiliary/minicore.rs | 2 +- ...static-initializer-acyclic-issue-146787.rs | 29 +++++ ...ic-initializer-acyclic-issue-146787.stderr | 32 +++++ 16 files changed, 262 insertions(+), 2 deletions(-) create mode 100644 compiler/rustc_monomorphize/src/graph_checks/mod.rs create mode 100644 compiler/rustc_monomorphize/src/graph_checks/statics.rs create mode 100644 tests/ui/static/static-initializer-acyclic-issue-146787.rs create mode 100644 tests/ui/static/static-initializer-acyclic-issue-146787.stderr diff --git a/Cargo.lock b/Cargo.lock index 816bb1a37859f..d0d47f882a89c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4421,6 +4421,7 @@ dependencies = [ "rustc_errors", "rustc_fluent_macro", "rustc_hir", + "rustc_index", "rustc_macros", "rustc_middle", "rustc_session", diff --git a/compiler/rustc_monomorphize/Cargo.toml b/compiler/rustc_monomorphize/Cargo.toml index 09a55f0b5f8da..0829d52283abf 100644 --- a/compiler/rustc_monomorphize/Cargo.toml +++ b/compiler/rustc_monomorphize/Cargo.toml @@ -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" } diff --git a/compiler/rustc_monomorphize/messages.ftl b/compiler/rustc_monomorphize/messages.ftl index edb91d8f2eda1..09500ba73359b 100644 --- a/compiler/rustc_monomorphize/messages.ftl +++ b/compiler/rustc_monomorphize/messages.ftl @@ -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 diff --git a/compiler/rustc_monomorphize/src/collector.rs b/compiler/rustc_monomorphize/src/collector.rs index 4b2f8e03afc13..070db1ae6b5ee 100644 --- a/compiler/rustc_monomorphize/src/collector.rs +++ b/compiler/rustc_monomorphize/src/collector.rs @@ -267,7 +267,8 @@ pub(crate) struct UsageMap<'tcx> { // Maps every mono item to the mono items used by it. pub used_map: UnordMap, Vec>>, - // 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, Vec>>, } diff --git a/compiler/rustc_monomorphize/src/errors.rs b/compiler/rustc_monomorphize/src/errors.rs index 4949d9ae3922d..723649f22117e 100644 --- a/compiler/rustc_monomorphize/src/errors.rs +++ b/compiler/rustc_monomorphize/src/errors.rs @@ -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, + pub head: &'a str, + pub target: &'a str, +} diff --git a/compiler/rustc_monomorphize/src/graph_checks/mod.rs b/compiler/rustc_monomorphize/src/graph_checks/mod.rs new file mode 100644 index 0000000000000..2b9b7cfff0b21 --- /dev/null +++ b/compiler/rustc_monomorphize/src/graph_checks/mod.rs @@ -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); + } +} diff --git a/compiler/rustc_monomorphize/src/graph_checks/statics.rs b/compiler/rustc_monomorphize/src/graph_checks/statics.rs new file mode 100644 index 0000000000000..a764d307b3d4b --- /dev/null +++ b/compiler/rustc_monomorphize/src/graph_checks/statics.rs @@ -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 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, + // contains for each `MonoItem` the `MonoItem`s it uses + used_map: &'b UnordMap, Vec>>, +} + +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 { + 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 = 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 = Sccs::new(&graph); + // Group statics by SCCs + let mut nodes_of_sccs: IndexVec> = + 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, + }); + } +} diff --git a/compiler/rustc_monomorphize/src/lib.rs b/compiler/rustc_monomorphize/src/lib.rs index 8b48cf5a6501d..5b4f74ca6a708 100644 --- a/compiler/rustc_monomorphize/src/lib.rs +++ b/compiler/rustc_monomorphize/src/lib.rs @@ -16,6 +16,7 @@ use rustc_span::ErrorGuaranteed; mod collector; mod errors; +mod graph_checks; mod mono_checks; mod partitioning; mod util; diff --git a/compiler/rustc_monomorphize/src/partitioning.rs b/compiler/rustc_monomorphize/src/partitioning.rs index 1c8d6db08c316..6a1d64bd28bc1 100644 --- a/compiler/rustc_monomorphize/src/partitioning.rs +++ b/compiler/rustc_monomorphize/src/partitioning.rs @@ -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. diff --git a/compiler/rustc_target/src/spec/json.rs b/compiler/rustc_target/src/spec/json.rs index a972299deeac4..20fbb687b3080 100644 --- a/compiler/rustc_target/src/spec/json.rs +++ b/compiler/rustc_target/src/spec/json.rs @@ -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, archive_format: Option>, allow_asm: Option, + static_initializer_must_be_acyclic: Option, main_needs_argc_argv: Option, has_thread_local: Option, obj_is_bitcode: Option, diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs index b06339f594257..89c9fdc935cc5 100644 --- a/compiler/rustc_target/src/spec/mod.rs +++ b/compiler/rustc_target/src/spec/mod.rs @@ -2394,6 +2394,9 @@ pub struct TargetOptions { pub archive_format: StaticCow, /// 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, diff --git a/compiler/rustc_target/src/spec/targets/nvptx64_nvidia_cuda.rs b/compiler/rustc_target/src/spec/targets/nvptx64_nvidia_cuda.rs index be09681b1f35b..87c2693e9877f 100644 --- a/compiler/rustc_target/src/spec/targets/nvptx64_nvidia_cuda.rs +++ b/compiler/rustc_target/src/spec/targets/nvptx64_nvidia_cuda.rs @@ -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() }, } diff --git a/src/doc/rustc/src/platform-support/nvptx64-nvidia-cuda.md b/src/doc/rustc/src/platform-support/nvptx64-nvidia-cuda.md index 0eb7e1d84bd0a..c722a7086967b 100644 --- a/src/doc/rustc/src/platform-support/nvptx64-nvidia-cuda.md +++ b/src/doc/rustc/src/platform-support/nvptx64-nvidia-cuda.md @@ -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 +``` $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 +