From 83012a43ba79e4ba5493626380a6fe6d24278933 Mon Sep 17 00:00:00 2001 From: Makai Date: Thu, 6 Aug 2026 21:17:21 +0800 Subject: [PATCH 01/31] rustc_public: split `def`s out of `ty` --- compiler/rustc_public/src/crate_def.rs | 52 +- compiler/rustc_public/src/ty.rs | 1760 +----------------------- compiler/rustc_public/src/ty/def.rs | 316 +++++ compiler/rustc_public/src/ty/tys.rs | 1409 +++++++++++++++++++ 4 files changed, 1761 insertions(+), 1776 deletions(-) create mode 100644 compiler/rustc_public/src/ty/def.rs create mode 100644 compiler/rustc_public/src/ty/tys.rs diff --git a/compiler/rustc_public/src/crate_def.rs b/compiler/rustc_public/src/crate_def.rs index 04ab2a1908e53..09fd5fdbae1d9 100644 --- a/compiler/rustc_public/src/crate_def.rs +++ b/compiler/rustc_public/src/crate_def.rs @@ -134,24 +134,30 @@ impl Attribute { } macro_rules! crate_def { - ( $(#[$attr:meta])* - $vis:vis $name:ident $(;)? - ) => { - $(#[$attr])* - #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] - $vis struct $name(pub DefId); - - impl CrateDef for $name { - fn def_id(&self) -> DefId { - self.0 + ($( + $(#[$attr:meta])* + $vis:vis $name:ident; + )*) => { + $( + $(#[$attr])* + #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] + $vis struct $name(pub DefId); + + impl CrateDef for $name { + fn def_id(&self) -> DefId { + self.0 + } } - } + )* }; } macro_rules! crate_def_with_ty { - ( $(#[$attr:meta])* - $vis:vis $name:ident $(;)? + () => {}; + ( + $(#[$attr:meta])* + $vis:vis $name:ident; + $($rest:tt)* ) => { $(#[$attr])* #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] @@ -164,14 +170,18 @@ macro_rules! crate_def_with_ty { } impl CrateDefType for $name {} + + crate_def_with_ty!($($rest)*); }; - ( $(#[$attr:meta])* - $vis:vis $name:ident { - $( - $(#[$f_attr:meta])* - $f_vis:vis $f_name:ident: $f_ty:ty, - )* - } + ( + $(#[$attr:meta])* + $vis:vis $name:ident { + $( + $(#[$f_attr:meta])* + $f_vis:vis $f_name:ident: $f_ty:ty, + )* + } + $($rest:tt)* ) => { $(#[$attr])* #[derive(Clone, PartialEq, Eq, Debug)] @@ -190,5 +200,7 @@ macro_rules! crate_def_with_ty { } impl CrateDefType for $name {} + + crate_def_with_ty!($($rest)*); }; } diff --git a/compiler/rustc_public/src/ty.rs b/compiler/rustc_public/src/ty.rs index 504b6f03fcc7d..8ef8447b09757 100644 --- a/compiler/rustc_public/src/ty.rs +++ b/compiler/rustc_public/src/ty.rs @@ -1,1756 +1,4 @@ -use std::fmt::{self, Debug, Display, Formatter}; -use std::ops::Range; - -use serde::Serialize; - -use super::abi::ReprOptions; -use super::mir::{Body, Mutability, Safety}; -use super::{DefId, Error, Symbol, with}; -use crate::abi::{FnAbi, Layout}; -use crate::crate_def::{CrateDef, CrateDefType}; -use crate::mir::alloc::{AllocId, read_target_int, read_target_uint}; -use crate::mir::mono::{Instance, StaticDef}; -use crate::target::MachineInfo; -use crate::{AssocItems, Filename, IndexedVal, Opaque, ThreadLocalIndex}; - -#[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub struct Ty(usize, ThreadLocalIndex); - -impl Debug for Ty { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_struct("Ty").field("id", &self.0).field("kind", &self.kind()).finish() - } -} - -/// Constructors for `Ty`. -impl Ty { - /// Create a new type from a given kind. - pub fn from_rigid_kind(kind: RigidTy) -> Ty { - with(|cx| cx.new_rigid_ty(kind)) - } - - /// Create a new array type. - pub fn try_new_array(elem_ty: Ty, size: u64) -> Result { - Ok(Ty::from_rigid_kind(RigidTy::Array(elem_ty, TyConst::try_from_target_usize(size)?))) - } - - /// Create a new array type from Const length. - pub fn new_array_with_const_len(elem_ty: Ty, len: TyConst) -> Ty { - Ty::from_rigid_kind(RigidTy::Array(elem_ty, len)) - } - - /// Create a new pointer type. - pub fn new_ptr(pointee_ty: Ty, mutability: Mutability) -> Ty { - Ty::from_rigid_kind(RigidTy::RawPtr(pointee_ty, mutability)) - } - - /// Create a new reference type. - pub fn new_ref(reg: Region, pointee_ty: Ty, mutability: Mutability) -> Ty { - Ty::from_rigid_kind(RigidTy::Ref(reg, pointee_ty, mutability)) - } - - /// Create a new pointer type. - pub fn new_tuple(tys: &[Ty]) -> Ty { - Ty::from_rigid_kind(RigidTy::Tuple(Vec::from(tys))) - } - - /// Create a new closure type. - pub fn new_closure(def: ClosureDef, args: GenericArgs) -> Ty { - Ty::from_rigid_kind(RigidTy::Closure(def, args)) - } - - /// Create a new coroutine type. - pub fn new_coroutine(def: CoroutineDef, args: GenericArgs) -> Ty { - Ty::from_rigid_kind(RigidTy::Coroutine(def, args)) - } - - /// Create a new closure type. - pub fn new_coroutine_closure(def: CoroutineClosureDef, args: GenericArgs) -> Ty { - Ty::from_rigid_kind(RigidTy::CoroutineClosure(def, args)) - } - - /// Create a new box type that represents `Box`, for the given inner type `T`. - pub fn new_box(inner_ty: Ty) -> Ty { - with(|cx| cx.new_box_ty(inner_ty)) - } - - /// Create a type representing `usize`. - pub fn usize_ty() -> Ty { - Ty::from_rigid_kind(RigidTy::Uint(UintTy::Usize)) - } - - /// Create a type representing `bool`. - pub fn bool_ty() -> Ty { - Ty::from_rigid_kind(RigidTy::Bool) - } - - /// Create a type representing a signed integer. - pub fn signed_ty(inner: IntTy) -> Ty { - Ty::from_rigid_kind(RigidTy::Int(inner)) - } - - /// Create a type representing an unsigned integer. - pub fn unsigned_ty(inner: UintTy) -> Ty { - Ty::from_rigid_kind(RigidTy::Uint(inner)) - } - - /// Get a type layout. - pub fn layout(self) -> Result { - with(|cx| cx.ty_layout(self)) - } -} - -impl Ty { - pub fn kind(&self) -> TyKind { - with(|context| context.ty_kind(*self)) - } -} - -/// Represents a pattern in the type system -#[derive(Clone, Debug, Eq, PartialEq, Serialize)] -pub enum Pattern { - Range { start: TyConst, end: TyConst, include_end: bool }, - NotNull, - Or(Vec), -} - -/// Represents a constant in the type system -#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize)] -pub struct TyConst { - pub(crate) kind: TyConstKind, - pub id: TyConstId, -} - -impl TyConst { - pub fn new(kind: TyConstKind, id: TyConstId) -> TyConst { - Self { kind, id } - } - - /// Retrieve the constant kind. - pub fn kind(&self) -> &TyConstKind { - &self.kind - } - - /// Creates an interned usize constant. - pub fn try_from_target_usize(val: u64) -> Result { - with(|cx| cx.try_new_ty_const_uint(val.into(), UintTy::Usize)) - } - - /// Try to evaluate to a target `usize`. - pub fn eval_target_usize(&self) -> Result { - with(|cx| cx.eval_target_usize_ty(self)) - } -} - -#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize)] -pub enum TyConstKind { - Param(ParamConst), - Bound(DebruijnIndex, BoundVar), - Unevaluated(ConstDef, GenericArgs), - - // FIXME: These should be a valtree - Value(Ty, Allocation), - ZSTValue(Ty), -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] -pub struct TyConstId(usize, ThreadLocalIndex); - -/// Represents a constant in MIR -#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize)] -pub struct MirConst { - /// The constant kind. - pub(crate) kind: ConstantKind, - /// The constant type. - pub(crate) ty: Ty, - /// Used for internal tracking of the internal constant. - pub id: MirConstId, -} - -impl MirConst { - /// Build a constant. Note that this should only be used by the compiler. - pub fn new(kind: ConstantKind, ty: Ty, id: MirConstId) -> MirConst { - MirConst { kind, ty, id } - } - - /// Retrieve the constant kind. - pub fn kind(&self) -> &ConstantKind { - &self.kind - } - - /// Get the constant type. - pub fn ty(&self) -> Ty { - self.ty - } - - /// Try to evaluate to a target `usize`. - pub fn eval_target_usize(&self) -> Result { - with(|cx| cx.eval_target_usize(self)) - } - - /// Create a constant that represents a new zero-sized constant of type T. - /// Fails if the type is not a ZST or if it doesn't have a known size. - pub fn try_new_zero_sized(ty: Ty) -> Result { - with(|cx| cx.try_new_const_zst(ty)) - } - - /// Build a new constant that represents the given string. - /// - /// Note that there is no guarantee today about duplication of the same constant. - /// I.e.: Calling this function multiple times with the same argument may or may not return - /// the same allocation. - pub fn from_str(value: &str) -> MirConst { - with(|cx| cx.new_const_str(value)) - } - - /// Build a new constant that represents the given boolean value. - pub fn from_bool(value: bool) -> MirConst { - with(|cx| cx.new_const_bool(value)) - } - - /// Build a new constant that represents the given unsigned integer. - pub fn try_from_uint(value: u128, uint_ty: UintTy) -> Result { - with(|cx| cx.try_new_const_uint(value, uint_ty)) - } - - /// Build a new constant that represents the given floating point number. - /// The value is the binary representation of the float constant. - /// Example: `try_from_float(2.5_f32.to_bits() as u128, FloatTy::F32)`. - pub fn try_from_float(value: u128, float_ty: FloatTy) -> Result { - with(|cx| cx.try_new_const_float(value, float_ty)) - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub struct MirConstId(usize, ThreadLocalIndex); - -type Ident = Opaque; - -#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize)] -pub struct Region { - pub kind: RegionKind, -} - -#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize)] -pub enum RegionKind { - ReEarlyParam(EarlyParamRegion), - ReBound(DebruijnIndex, BoundRegion), - ReStatic, - RePlaceholder(Placeholder), - ReErased, -} - -pub(crate) type DebruijnIndex = u32; - -#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize)] -pub struct EarlyParamRegion { - pub index: u32, - pub name: Symbol, -} - -pub(crate) type BoundVar = u32; - -#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize)] -pub struct BoundRegion { - pub var: BoundVar, - pub kind: BoundRegionKind, -} - -pub(crate) type UniverseIndex = u32; - -#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize)] -pub struct Placeholder { - pub universe: UniverseIndex, - pub bound: T, -} - -#[derive(Clone, Copy, PartialEq, Eq, Hash)] -pub struct Span(usize, ThreadLocalIndex); - -impl Debug for Span { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_struct("Span") - .field("id", &self.0) - .field("repr", &with(|cx| cx.span_to_string(*self))) - .finish() - } -} - -impl Span { - /// Return filename for diagnostic purposes - pub fn get_filename(&self) -> Filename { - with(|c| c.get_filename(self)) - } - - /// Return lines that correspond to this `Span` - pub fn get_lines(&self) -> LineInfo { - with(|c| c.get_lines(self)) - } - - /// Return the span location to be printed in diagnostic messages. - /// - /// This may leak local file paths and should not be used to build artifacts that may be - /// distributed. - pub fn diagnostic(&self) -> String { - with(|c| c.span_to_string(*self)) - } - - /// Create a `&'static core::panic::Location<'static>` constant from this span. - pub(crate) fn as_caller_location(&self) -> MirConst { - with(|c| c.span_as_caller_location(*self)) - } -} - -#[derive(Clone, Copy, Debug, Serialize)] -/// Information you get from `Span` in a struct form. -/// Line and col start from 1. -pub struct LineInfo { - pub start_line: usize, - pub start_col: usize, - pub end_line: usize, - pub end_col: usize, -} - -impl LineInfo { - pub fn from(lines: (usize, usize, usize, usize)) -> Self { - LineInfo { start_line: lines.0, start_col: lines.1, end_line: lines.2, end_col: lines.3 } - } -} - -#[derive(Clone, Debug, Eq, PartialEq, Serialize)] -pub enum TyKind { - RigidTy(RigidTy), - Alias(AliasKind, AliasTy), - Param(ParamTy), - Bound(usize, BoundTy), -} - -impl TyKind { - pub fn rigid(&self) -> Option<&RigidTy> { - if let TyKind::RigidTy(inner) = self { Some(inner) } else { None } - } - - #[inline] - pub fn is_unit(&self) -> bool { - matches!(self, TyKind::RigidTy(RigidTy::Tuple(data)) if data.is_empty()) - } - - #[inline] - pub fn is_bool(&self) -> bool { - matches!(self, TyKind::RigidTy(RigidTy::Bool)) - } - - #[inline] - pub fn is_char(&self) -> bool { - matches!(self, TyKind::RigidTy(RigidTy::Char)) - } - - #[inline] - pub fn is_trait(&self) -> bool { - matches!(self, TyKind::RigidTy(RigidTy::Dynamic(_, _))) - } - - #[inline] - pub fn is_enum(&self) -> bool { - matches!(self, TyKind::RigidTy(RigidTy::Adt(def, _)) if def.kind() == AdtKind::Enum) - } - - #[inline] - pub fn is_struct(&self) -> bool { - matches!(self, TyKind::RigidTy(RigidTy::Adt(def, _)) if def.kind() == AdtKind::Struct) - } - - #[inline] - pub fn is_union(&self) -> bool { - matches!(self, TyKind::RigidTy(RigidTy::Adt(def, _)) if def.kind() == AdtKind::Union) - } - - #[inline] - pub fn is_adt(&self) -> bool { - matches!(self, TyKind::RigidTy(RigidTy::Adt(..))) - } - - #[inline] - pub fn is_ref(&self) -> bool { - matches!(self, TyKind::RigidTy(RigidTy::Ref(..))) - } - - #[inline] - pub fn is_fn(&self) -> bool { - matches!(self, TyKind::RigidTy(RigidTy::FnDef(..))) - } - - #[inline] - pub fn is_fn_ptr(&self) -> bool { - matches!(self, TyKind::RigidTy(RigidTy::FnPtr(..))) - } - - #[inline] - pub fn is_primitive(&self) -> bool { - matches!( - self, - TyKind::RigidTy( - RigidTy::Bool - | RigidTy::Char - | RigidTy::Int(_) - | RigidTy::Uint(_) - | RigidTy::Float(_) - ) - ) - } - - #[inline] - pub fn is_float(&self) -> bool { - matches!(self, TyKind::RigidTy(RigidTy::Float(_))) - } - - #[inline] - pub fn is_integral(&self) -> bool { - matches!(self, TyKind::RigidTy(RigidTy::Int(_) | RigidTy::Uint(_))) - } - - #[inline] - pub fn is_numeric(&self) -> bool { - self.is_integral() || self.is_float() - } - - #[inline] - pub fn is_signed(&self) -> bool { - matches!(self, TyKind::RigidTy(RigidTy::Int(_))) - } - - #[inline] - pub fn is_str(&self) -> bool { - *self == TyKind::RigidTy(RigidTy::Str) - } - - #[inline] - pub fn is_cstr(&self) -> bool { - let TyKind::RigidTy(RigidTy::Adt(def, _)) = self else { - return false; - }; - with(|cx| cx.adt_is_cstr(*def)) - } - - #[inline] - pub fn is_slice(&self) -> bool { - matches!(self, TyKind::RigidTy(RigidTy::Slice(_))) - } - - #[inline] - pub fn is_array(&self) -> bool { - matches!(self, TyKind::RigidTy(RigidTy::Array(..))) - } - - #[inline] - pub fn is_mutable_ptr(&self) -> bool { - matches!( - self, - TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) - | TyKind::RigidTy(RigidTy::Ref(_, _, Mutability::Mut)) - ) - } - - #[inline] - pub fn is_raw_ptr(&self) -> bool { - matches!(self, TyKind::RigidTy(RigidTy::RawPtr(..))) - } - - /// Tests if this is any kind of primitive pointer type (reference, raw pointer, fn pointer). - #[inline] - pub fn is_any_ptr(&self) -> bool { - self.is_ref() || self.is_raw_ptr() || self.is_fn_ptr() - } - - #[inline] - pub fn is_coroutine(&self) -> bool { - matches!(self, TyKind::RigidTy(RigidTy::Coroutine(..))) - } - - #[inline] - pub fn is_closure(&self) -> bool { - matches!(self, TyKind::RigidTy(RigidTy::Closure(..))) - } - - #[inline] - pub fn is_box(&self) -> bool { - match self { - TyKind::RigidTy(RigidTy::Adt(def, _)) => def.is_box(), - _ => false, - } - } - - #[inline] - pub fn is_simd(&self) -> bool { - matches!(self, TyKind::RigidTy(RigidTy::Adt(def, _)) if def.is_simd()) - } - - pub fn trait_principal(&self) -> Option> { - if let TyKind::RigidTy(RigidTy::Dynamic(predicates, _)) = self { - if let Some(Binder { value: ExistentialPredicate::Trait(trait_ref), bound_vars }) = - predicates.first() - { - Some(Binder { value: trait_ref.clone(), bound_vars: bound_vars.clone() }) - } else { - None - } - } else { - None - } - } - - /// Returns the type of `ty[i]` for builtin types. - pub fn builtin_index(&self) -> Option { - match self.rigid()? { - RigidTy::Array(ty, _) | RigidTy::Slice(ty) => Some(*ty), - _ => None, - } - } - - /// Returns the type and mutability of `*ty` for builtin types. - /// - /// The parameter `explicit` indicates if this is an *explicit* dereference. - /// Some types -- notably raw ptrs -- can only be dereferenced explicitly. - pub fn builtin_deref(&self, explicit: bool) -> Option { - match self.rigid()? { - RigidTy::Adt(def, args) if def.is_box() => { - Some(TypeAndMut { ty: *args.0.first()?.ty()?, mutability: Mutability::Not }) - } - RigidTy::Ref(_, ty, mutability) => { - Some(TypeAndMut { ty: *ty, mutability: *mutability }) - } - RigidTy::RawPtr(ty, mutability) if explicit => { - Some(TypeAndMut { ty: *ty, mutability: *mutability }) - } - _ => None, - } - } - - /// Get the function signature for function like types (Fn, FnPtr, and Closure) - pub fn fn_sig(&self) -> Option { - match self { - TyKind::RigidTy(RigidTy::FnDef(def, args)) => Some(with(|cx| cx.fn_sig(*def, args))), - TyKind::RigidTy(RigidTy::FnPtr(sig)) => Some(sig.clone()), - TyKind::RigidTy(RigidTy::Closure(_def, args)) => Some(with(|cx| cx.closure_sig(args))), - _ => None, - } - } - - /// Get the discriminant type for this type. - pub fn discriminant_ty(&self) -> Option { - self.rigid().map(|ty| with(|cx| cx.rigid_ty_discriminant_ty(ty))) - } - - /// Deconstruct a function type if this is one. - pub fn fn_def(&self) -> Option<(FnDef, &GenericArgs)> { - if let TyKind::RigidTy(RigidTy::FnDef(def, args)) = self { - Some((*def, args)) - } else { - None - } - } -} - -pub struct TypeAndMut { - pub ty: Ty, - pub mutability: Mutability, -} - -#[derive(Clone, Debug, Eq, PartialEq, Serialize)] -pub enum RigidTy { - Bool, - Char, - Int(IntTy), - Uint(UintTy), - Float(FloatTy), - Adt(AdtDef, GenericArgs), - Foreign(ForeignDef), - Str, - Array(Ty, TyConst), - Pat(Ty, Pattern), - Slice(Ty), - RawPtr(Ty, Mutability), - Ref(Region, Ty, Mutability), - FnDef(FnDef, GenericArgs), - FnPtr(PolyFnSig), - Closure(ClosureDef, GenericArgs), - Coroutine(CoroutineDef, GenericArgs), - CoroutineClosure(CoroutineClosureDef, GenericArgs), - Dynamic(Vec>, Region), - Never, - Tuple(Vec), - CoroutineWitness(CoroutineWitnessDef, GenericArgs), -} - -impl RigidTy { - /// Get the discriminant type for this type. - pub fn discriminant_ty(&self) -> Ty { - with(|cx| cx.rigid_ty_discriminant_ty(self)) - } -} - -impl From for TyKind { - fn from(value: RigidTy) -> Self { - TyKind::RigidTy(value) - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)] -pub enum IntTy { - Isize, - I8, - I16, - I32, - I64, - I128, -} - -impl IntTy { - pub fn num_bytes(self) -> usize { - match self { - IntTy::Isize => MachineInfo::target_pointer_width().bytes(), - IntTy::I8 => 1, - IntTy::I16 => 2, - IntTy::I32 => 4, - IntTy::I64 => 8, - IntTy::I128 => 16, - } - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)] -pub enum UintTy { - Usize, - U8, - U16, - U32, - U64, - U128, -} - -impl UintTy { - pub fn num_bytes(self) -> usize { - match self { - UintTy::Usize => MachineInfo::target_pointer_width().bytes(), - UintTy::U8 => 1, - UintTy::U16 => 2, - UintTy::U32 => 4, - UintTy::U64 => 8, - UintTy::U128 => 16, - } - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)] -pub enum FloatTy { - F16, - F32, - F64, - F128, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)] -pub enum Movability { - Static, - Movable, -} - -crate_def! { - #[derive(Serialize)] - pub ForeignModuleDef; -} - -impl ForeignModuleDef { - pub fn module(&self) -> ForeignModule { - with(|cx| cx.foreign_module(*self)) - } -} - -pub struct ForeignModule { - pub def_id: ForeignModuleDef, - pub abi: Abi, -} - -impl ForeignModule { - pub fn items(&self) -> Vec { - with(|cx| cx.foreign_items(self.def_id)) - } -} - -crate_def_with_ty! { - /// Hold information about a ForeignItem in a crate. - #[derive(Serialize)] - pub ForeignDef; -} - -impl ForeignDef { - pub fn kind(&self) -> ForeignItemKind { - with(|cx| cx.foreign_item_kind(*self)) - } -} - -#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, Serialize)] -pub enum ForeignItemKind { - Fn(FnDef), - Static(StaticDef), - Type(Ty), -} - -crate_def_with_ty! { - /// Hold information about a function definition in a crate. - #[derive(Serialize)] - pub FnDef; -} - -impl FnDef { - // Get the function body if available. - pub fn body(&self) -> Option { - with(|ctx| ctx.has_body(self.0).then(|| ctx.mir_body(self.0))) - } - - // Check if the function body is available. - pub fn has_body(&self) -> bool { - with(|ctx| ctx.has_body(self.0)) - } - - /// Get the information of the intrinsic if this function is a definition of one. - pub fn as_intrinsic(&self) -> Option { - with(|cx| cx.intrinsic(self.def_id())) - } - - /// Check if the function is an intrinsic. - #[inline] - pub fn is_intrinsic(&self) -> bool { - self.as_intrinsic().is_some() - } - - /// Get the constness of this function definition. - pub fn constness(&self) -> Constness { - with(|cx| cx.constness(*self)) - } - - /// Get the asyncness of this function definition. - pub fn asyncness(&self) -> Asyncness { - with(|cx| cx.asyncness(*self)) - } - - /// Get the function signature for this function definition. - pub fn fn_sig(&self) -> PolyFnSig { - let kind = self.ty().kind(); - kind.fn_sig().unwrap() - } - - /// Get the generics of this function definition. - pub fn generics_of(&self) -> Generics { - with(|cx| cx.generics_of(self.0)) - } - - /// Get the associated item information if this function is one. - pub fn associated_item(&self) -> Option { - with(|cx| cx.associated_item(self.0)) - } -} - -crate_def_with_ty! { - #[derive(Serialize)] - pub IntrinsicDef; -} - -impl IntrinsicDef { - /// Returns the plain name of the intrinsic. - /// e.g., `transmute` for `core::intrinsics::transmute`. - pub fn fn_name(&self) -> Symbol { - with(|cx| cx.intrinsic_name(*self)) - } - - /// Returns whether the intrinsic has no meaningful body and all backends - /// need to shim all calls to it. - pub fn must_be_overridden(&self) -> bool { - with(|cx| !cx.has_body(self.0)) - } -} - -impl From for FnDef { - fn from(def: IntrinsicDef) -> Self { - FnDef(def.0) - } -} - -crate_def! { - #[derive(Serialize)] - pub ClosureDef; -} - -impl ClosureDef { - /// Retrieves the body of the closure definition. Returns None if the body - /// isn't available. - pub fn body(&self) -> Option { - with(|ctx| ctx.has_body(self.0).then(|| ctx.mir_body(self.0))) - } -} - -crate_def! { - #[derive(Serialize)] - pub CoroutineDef; -} - -impl CoroutineDef { - /// Retrieves the body of the coroutine definition. Returns None if the body - /// isn't available. - pub fn body(&self) -> Option { - with(|cx| cx.has_body(self.0).then(|| cx.mir_body(self.0))) - } - - pub fn discriminant_for_variant(&self, args: &GenericArgs, idx: VariantIdx) -> Discr { - with(|cx| cx.coroutine_discr_for_variant(*self, args, idx)) - } -} - -crate_def! { - #[derive(Serialize)] - pub CoroutineClosureDef; -} - -crate_def! { - #[derive(Serialize)] - pub ParamDef; -} - -crate_def! { - #[derive(Serialize)] - pub BrNamedDef; -} - -crate_def! { - #[derive(Serialize)] - pub AdtDef; -} - -#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, Serialize)] -pub enum AdtKind { - Enum, - Union, - Struct, -} - -impl AdtDef { - pub fn kind(&self) -> AdtKind { - with(|cx| cx.adt_kind(*self)) - } - - /// Retrieve the type of this Adt. - pub fn ty(&self) -> Ty { - with(|cx| cx.def_ty(self.0)) - } - - /// Retrieve the type of this Adt by instantiating and normalizing it with the given arguments. - /// - /// This will assume the type can be instantiated with these arguments. - pub fn ty_with_args(&self, args: &GenericArgs) -> Ty { - with(|cx| cx.def_ty_with_args(self.0, args)) - } - - pub fn is_box(&self) -> bool { - with(|cx| cx.adt_is_box(*self)) - } - - pub fn is_simd(&self) -> bool { - with(|cx| cx.adt_is_simd(*self)) - } - - /// The number of variants in this ADT. - pub fn num_variants(&self) -> usize { - with(|cx| cx.adt_variants_len(*self)) - } - - /// Retrieve the variants in this ADT. - pub fn variants(&self) -> Vec { - self.variants_iter().collect() - } - - /// Iterate over the variants in this ADT. - pub fn variants_iter(&self) -> impl Iterator { - (0..self.num_variants()) - .map(|idx| VariantDef { idx: VariantIdx::to_val(idx), adt_def: *self }) - } - - pub fn variant(&self, idx: VariantIdx) -> Option { - (idx.to_index() < self.num_variants()).then_some(VariantDef { idx, adt_def: *self }) - } - - pub fn repr(&self) -> ReprOptions { - with(|cx| cx.adt_repr(*self)) - } - - pub fn discriminant_for_variant(&self, idx: VariantIdx) -> Discr { - with(|cx| cx.adt_discr_for_variant(*self, idx)) - } - - /// Get the generics of this ADT definition. - pub fn generics_of(&self) -> Generics { - with(|cx| cx.generics_of(self.0)) - } - - /// Retrieve the inherent implementations for this ADT. - pub fn inherent_impls(&self) -> Vec { - with(|cx| cx.inherent_impls(*self)) - } -} - -pub struct Discr { - pub val: u128, - pub ty: Ty, -} - -/// Definition of a variant, which can be either a struct / union field or an enum variant. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)] -pub struct VariantDef { - /// The variant index. - pub(crate) idx: VariantIdx, - /// The data type where this variant comes from. - /// For now, we use this to retrieve information about the variant itself so we don't need to - /// cache more information. - pub(crate) adt_def: AdtDef, -} - -impl VariantDef { - /// The name of the variant, struct or union. - /// - /// This will not include the name of the enum or qualified path. - pub fn name(&self) -> Symbol { - with(|cx| cx.variant_name(*self)) - } - - /// Retrieve all the fields in this variant. - // We expect user to cache this and use it directly since today it is expensive to generate all - // fields name. - pub fn fields(&self) -> Vec { - with(|cx| cx.variant_fields(*self)) - } - - /// Returns the variant index. - pub fn idx(&self) -> VariantIdx { - self.idx - } - - /// Returns the `AdtDef` which this variant comes from. - pub fn adt_def(&self) -> AdtDef { - self.adt_def - } -} - -crate_def_with_ty! { - #[derive(Serialize)] - pub FieldDef { - /// The field name. - pub name: Symbol, - } -} - -impl Display for AdtKind { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.write_str(match self { - AdtKind::Enum => "enum", - AdtKind::Union => "union", - AdtKind::Struct => "struct", - }) - } -} - -impl AdtKind { - pub fn is_enum(&self) -> bool { - matches!(self, AdtKind::Enum) - } - - pub fn is_struct(&self) -> bool { - matches!(self, AdtKind::Struct) - } - - pub fn is_union(&self) -> bool { - matches!(self, AdtKind::Union) - } -} - -crate_def! { - #[derive(Serialize)] - pub AliasDef; -} - -crate_def! { - /// A trait's definition. - #[derive(Serialize)] - pub TraitDef; -} - -impl TraitDef { - pub fn declaration(trait_def: &TraitDef) -> TraitDecl { - with(|cx| cx.trait_decl(trait_def)) - } - - pub fn associated_items(&self) -> AssocItems { - with(|cx| cx.associated_items(self.def_id())) - } -} - -crate_def! { - #[derive(Serialize)] - pub GenericDef; -} - -crate_def_with_ty! { - #[derive(Serialize)] - pub ConstDef; -} - -crate_def_with_ty! { - /// A trait impl definition. - #[derive(Serialize)] - pub ImplDef; -} - -impl ImplDef { - /// Retrieve information about this implementation. - pub fn trait_impl(&self) -> ImplTrait { - with(|cx| cx.trait_impl(self)) - } - - pub fn associated_items(&self) -> AssocItems { - with(|cx| cx.associated_items(self.def_id())) - } - - /// Get the generics of this implementation. - pub fn generics_of(&self) -> Generics { - with(|cx| cx.generics_of(self.0)) - } -} - -crate_def! { - #[derive(Serialize)] - pub RegionDef; -} - -crate_def! { - #[derive(Serialize)] - pub CoroutineWitnessDef; -} - -/// A list of generic arguments. -#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize)] -pub struct GenericArgs(pub Vec); - -impl std::ops::Index for GenericArgs { - type Output = Ty; - - fn index(&self, index: ParamTy) -> &Self::Output { - self.0[index.index as usize].expect_ty() - } -} - -impl std::ops::Index for GenericArgs { - type Output = TyConst; - - fn index(&self, index: ParamConst) -> &Self::Output { - self.0[index.index as usize].expect_const() - } -} - -#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize)] -pub enum GenericArgKind { - Lifetime(Region), - Type(Ty), - Const(TyConst), -} - -impl GenericArgKind { - /// Panic if this generic argument is not a type, otherwise - /// return the type. - #[track_caller] - pub fn expect_ty(&self) -> &Ty { - match self { - GenericArgKind::Type(ty) => ty, - _ => panic!("{self:?}"), - } - } - - /// Panic if this generic argument is not a const, otherwise - /// return the const. - #[track_caller] - pub fn expect_const(&self) -> &TyConst { - match self { - GenericArgKind::Const(c) => c, - _ => panic!("{self:?}"), - } - } - - /// Return the generic argument type if applicable, otherwise return `None`. - pub fn ty(&self) -> Option<&Ty> { - match self { - GenericArgKind::Type(ty) => Some(ty), - _ => None, - } - } -} - -#[derive(Clone, Debug, Eq, PartialEq, Serialize)] -pub enum TermKind { - Type(Ty), - Const(TyConst), -} - -#[derive(Clone, Debug, Eq, PartialEq, Serialize)] -pub enum AliasKind { - Projection, - Inherent, - Opaque, - Free, -} - -#[derive(Clone, Debug, Eq, PartialEq, Serialize)] -pub struct AliasTy { - pub def_id: AliasDef, - pub args: GenericArgs, -} - -#[derive(Clone, Debug, Eq, PartialEq, Serialize)] -pub struct AliasTerm { - pub def_id: AliasDef, - pub args: GenericArgs, -} - -pub type PolyFnSig = Binder; - -impl PolyFnSig { - /// Compute a `FnAbi` suitable for indirect calls, i.e. to `fn` pointers. - /// - /// NB: this doesn't handle virtual calls - those should use `Instance::fn_abi` - /// instead, where the instance is an `InstanceKind::Virtual`. - pub fn fn_ptr_abi(self) -> Result { - with(|cx| cx.fn_ptr_abi(self)) - } -} - -#[derive(Clone, Debug, Eq, PartialEq, Serialize)] -pub struct FnSig { - pub inputs_and_output: Vec, - pub c_variadic: bool, - pub safety: Safety, - pub abi: Abi, -} - -impl FnSig { - pub fn output(&self) -> Ty { - self.inputs_and_output[self.inputs_and_output.len() - 1] - } - - pub fn inputs(&self) -> &[Ty] { - &self.inputs_and_output[..self.inputs_and_output.len() - 1] - } -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize)] -pub enum Constness { - Const { always: bool }, - NotConst, -} - -impl Constness { - pub fn is_const(self) -> bool { - matches!(self, Constness::Const { always: false }) - } -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize)] -pub enum Asyncness { - Async, - NotAsync, -} - -impl Asyncness { - pub fn is_async(self) -> bool { - matches!(self, Asyncness::Async) - } -} - -#[derive(Clone, PartialEq, Eq, Debug, Serialize)] -pub enum Abi { - Rust, - C { unwind: bool }, - Cdecl { unwind: bool }, - Stdcall { unwind: bool }, - Fastcall { unwind: bool }, - Vectorcall { unwind: bool }, - Thiscall { unwind: bool }, - Aapcs { unwind: bool }, - Win64 { unwind: bool }, - SysV64 { unwind: bool }, - PtxKernel, - Msp430Interrupt, - X86Interrupt, - GpuKernel, - EfiApi, - AvrInterrupt, - AvrNonBlockingInterrupt, - CCmseNonSecureCall, - CCmseNonSecureEntry, - System { unwind: bool }, - RustCall, - Unadjusted, - RustCold, - RiscvInterruptM, - RiscvInterruptS, - RustPreserveNone, - RustTail, - RustInvalid, - Custom, - Swift, -} - -/// A binder represents a possibly generic type and its bound vars. -#[derive(Clone, Debug, Eq, PartialEq, Serialize)] -pub struct Binder { - pub value: T, - pub bound_vars: Vec, -} - -impl Binder { - /// Create a new binder with the given bound vars. - pub fn bind_with_vars(value: T, bound_vars: Vec) -> Self { - Binder { value, bound_vars } - } - - /// Create a new binder with no bounded variable. - pub fn dummy(value: T) -> Self { - Binder { value, bound_vars: vec![] } - } - - pub fn skip_binder(self) -> T { - self.value - } - - pub fn map_bound_ref(&self, f: F) -> Binder - where - F: FnOnce(&T) -> U, - { - let Binder { value, bound_vars } = self; - let new_value = f(value); - Binder { value: new_value, bound_vars: bound_vars.clone() } - } - - pub fn map_bound(self, f: F) -> Binder - where - F: FnOnce(T) -> U, - { - let Binder { value, bound_vars } = self; - let new_value = f(value); - Binder { value: new_value, bound_vars } - } -} - -#[derive(Clone, Debug, Eq, PartialEq, Serialize)] -pub struct EarlyBinder { - pub value: T, -} - -#[derive(Clone, Debug, Eq, PartialEq, Serialize)] -pub enum BoundVariableKind { - Ty(BoundTyKind), - Region(BoundRegionKind), - Const, -} - -#[derive(Clone, PartialEq, Eq, Debug, Serialize)] -pub enum BoundTyKind { - Anon, - Param(ParamDef, String), -} - -#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize)] -pub enum BoundRegionKind { - BrAnon, - BrNamed(BrNamedDef, String), - BrEnv, -} - -#[derive(Clone, Debug, Eq, PartialEq, Serialize)] -pub enum ExistentialPredicate { - Trait(ExistentialTraitRef), - Projection(ExistentialProjection), - AutoTrait(TraitDef), -} - -/// An existential reference to a trait where `Self` is not included. -/// -/// The `generic_args` will include any other known argument. -#[derive(Clone, Debug, Eq, PartialEq, Serialize)] -pub struct ExistentialTraitRef { - pub def_id: TraitDef, - pub generic_args: GenericArgs, -} - -impl Binder { - pub fn with_self_ty(&self, self_ty: Ty) -> Binder { - self.map_bound_ref(|trait_ref| trait_ref.with_self_ty(self_ty)) - } -} - -impl ExistentialTraitRef { - pub fn with_self_ty(&self, self_ty: Ty) -> TraitRef { - TraitRef::new(self.def_id, self_ty, &self.generic_args) - } -} - -#[derive(Clone, Debug, Eq, PartialEq, Serialize)] -pub struct ExistentialProjection { - pub def_id: TraitDef, - pub generic_args: GenericArgs, - pub term: TermKind, -} - -#[derive(Clone, Debug, Eq, PartialEq, Serialize)] -pub struct ParamTy { - pub index: u32, - pub name: String, -} - -#[derive(Clone, Debug, Eq, PartialEq, Serialize)] -pub struct BoundTy { - pub var: usize, - pub kind: BoundTyKind, -} - -pub type Bytes = Vec>; - -/// Size in bytes. -pub type Size = usize; - -#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, Serialize)] -pub struct Prov(pub AllocId); - -pub type Align = u64; -pub type Promoted = u32; -pub type InitMaskMaterialized = Vec; - -/// Stores the provenance information of pointers stored in memory. -#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize)] -pub struct ProvenanceMap { - /// Provenance in this map applies from the given offset for an entire pointer-size worth of - /// bytes. Two entries in this map are always at least a pointer size apart. - pub ptrs: Vec<(Size, Prov)>, -} - -#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize)] -pub struct Allocation { - pub bytes: Bytes, - pub provenance: ProvenanceMap, - pub align: Align, - pub mutability: Mutability, -} - -impl Allocation { - /// Get a vector of bytes for an Allocation that has been fully initialized - pub fn raw_bytes(&self) -> Result, Error> { - self.bytes - .iter() - .copied() - .collect::>>() - .ok_or_else(|| error!("Found uninitialized bytes: `{:?}`", self.bytes)) - } - - /// Read a uint value from the specified range. - pub fn read_partial_uint(&self, range: Range) -> Result { - if range.end - range.start > 16 { - return Err(error!("Allocation is bigger than largest integer")); - } - if range.end > self.bytes.len() { - return Err(error!( - "Range is out of bounds. Allocation length is `{}`, but requested range `{:?}`", - self.bytes.len(), - range - )); - } - let raw = self.bytes[range] - .iter() - .copied() - .collect::>>() - .ok_or_else(|| error!("Found uninitialized bytes: `{:?}`", self.bytes))?; - read_target_uint(&raw) - } - - /// Read this allocation and try to convert it to an unassigned integer. - pub fn read_uint(&self) -> Result { - if self.bytes.len() > 16 { - return Err(error!("Allocation is bigger than largest integer")); - } - let raw = self.raw_bytes()?; - read_target_uint(&raw) - } - - /// Read this allocation and try to convert it to a signed integer. - pub fn read_int(&self) -> Result { - if self.bytes.len() > 16 { - return Err(error!("Allocation is bigger than largest integer")); - } - let raw = self.raw_bytes()?; - read_target_int(&raw) - } - - /// Read this allocation and try to convert it to a boolean. - pub fn read_bool(&self) -> Result { - match self.read_int()? { - 0 => Ok(false), - 1 => Ok(true), - val => Err(error!("Unexpected value for bool: `{val}`")), - } - } - - /// Read this allocation as a pointer and return whether it represents a `null` pointer. - pub fn is_null(&self) -> Result { - let len = self.bytes.len(); - let ptr_len = MachineInfo::target_pointer_width().bytes(); - if len != ptr_len { - return Err(error!("Expected width of pointer (`{ptr_len}`), but found: `{len}`")); - } - Ok(self.read_uint()? == 0 && self.provenance.ptrs.is_empty()) - } -} - -#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize)] -pub enum ConstantKind { - Ty(TyConst), - Allocated(Allocation), - Unevaluated(UnevaluatedConst), - Param(ParamConst), - /// Store ZST constants. - /// We have to special handle these constants since its type might be generic. - ZeroSized, -} - -#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize)] -pub struct ParamConst { - pub index: u32, - pub name: String, -} - -#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize)] -pub struct UnevaluatedConst { - pub def: ConstDef, - pub args: GenericArgs, - pub promoted: Option, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)] -pub enum TraitSpecializationKind { - None, - Marker, - AlwaysApplicable, -} - -#[derive(Clone, Debug, Eq, PartialEq, Serialize)] -pub struct TraitDecl { - pub def_id: TraitDef, - pub safety: Safety, - pub paren_sugar: bool, - pub has_auto_impl: bool, - pub is_marker: bool, - pub is_coinductive: bool, - pub skip_array_during_method_dispatch: bool, - pub skip_boxed_slice_during_method_dispatch: bool, - pub specialization_kind: TraitSpecializationKind, - pub must_implement_one_of: Option>, - pub force_dyn_incompatible: Option, - pub deny_explicit_impl: bool, -} - -impl TraitDecl { - pub fn generics_of(&self) -> Generics { - with(|cx| cx.generics_of(self.def_id.0)) - } - - pub fn clauses_of(&self) -> GenericClauses { - with(|cx| cx.clauses_of(self.def_id.0)) - } - - pub fn explicit_clauses_of(&self) -> GenericClauses { - with(|cx| cx.explicit_clauses_of(self.def_id.0)) - } -} - -pub type ImplTrait = EarlyBinder; - -/// A complete reference to a trait, i.e., one where `Self` is known. -#[derive(Clone, Debug, Eq, PartialEq, Serialize)] -pub struct TraitRef { - pub def_id: TraitDef, - /// The generic arguments for this definition. - /// The first element must always be type, and it represents `Self`. - args: GenericArgs, -} - -impl TraitRef { - pub fn new(def_id: TraitDef, self_ty: Ty, gen_args: &GenericArgs) -> TraitRef { - let mut args = vec![GenericArgKind::Type(self_ty)]; - args.extend_from_slice(&gen_args.0); - TraitRef { def_id, args: GenericArgs(args) } - } - - pub fn try_new(def_id: TraitDef, args: GenericArgs) -> Result { - match &args.0[..] { - [GenericArgKind::Type(_), ..] => Ok(TraitRef { def_id, args }), - _ => Err(()), - } - } - - pub fn args(&self) -> &GenericArgs { - &self.args - } - - pub fn self_ty(&self) -> Ty { - let GenericArgKind::Type(self_ty) = self.args.0[0] else { - panic!("Self must be a type, but found: {:?}", self.args.0[0]) - }; - self_ty - } - - /// Retrieve all vtable entries. - pub fn vtable_entries(&self) -> Vec { - with(|cx| cx.vtable_entries(self)) - } - - /// Returns the vtable entry at the given index. - /// - /// Returns `None` if the index is out of bounds. - pub fn vtable_entry(&self, idx: usize) -> Option { - with(|cx| cx.vtable_entry(self, idx)) - } -} - -#[derive(Clone, Debug, Eq, PartialEq, Serialize)] -pub struct Generics { - pub parent: Option, - pub parent_count: usize, - pub params: Vec, - pub param_def_id_to_index: Vec<(GenericDef, u32)>, - pub has_self: bool, - pub has_late_bound_regions: Option, -} - -#[derive(Clone, Debug, Eq, PartialEq, Serialize)] -pub enum GenericParamDefKind { - Lifetime, - Type { has_default: bool, synthetic: bool }, - Const { has_default: bool }, -} - -#[derive(Clone, Debug, Eq, PartialEq, Serialize)] -pub struct GenericParamDef { - pub name: super::Symbol, - pub def_id: GenericDef, - pub index: u32, - pub pure_wrt_drop: bool, - pub kind: GenericParamDefKind, -} - -pub struct GenericClauses { - pub parent: Option, - pub clauses: Vec<(ClauseKind, Span)>, -} - -#[derive(Clone, Debug, Eq, PartialEq, Serialize)] -pub enum PredicateKind { - Clause(ClauseKind), - DynCompatible(TraitDef), - SubType(SubtypePredicate), - Coerce(CoercePredicate), - ConstEquate(TyConst, TyConst), - Ambiguous, -} - -#[derive(Clone, Debug, Eq, PartialEq, Serialize)] -pub enum ClauseKind { - Trait(TraitPredicate), - RegionOutlives(RegionOutlivesClause), - TypeOutlives(TypeOutlivesClause), - Projection(ProjectionPredicate), - ConstArgHasType(TyConst, Ty), - WellFormed(TermKind), - ConstEvaluatable(TyConst), -} - -#[derive(Clone, Debug, Eq, PartialEq, Serialize)] -pub enum ClosureKind { - Fn, - FnMut, - FnOnce, -} - -#[derive(Clone, Debug, Eq, PartialEq, Serialize)] -pub struct SubtypePredicate { - pub a: Ty, - pub b: Ty, -} - -#[derive(Clone, Debug, Eq, PartialEq, Serialize)] -pub struct CoercePredicate { - pub a: Ty, - pub b: Ty, -} - -#[derive(Clone, Debug, Eq, PartialEq, Serialize)] -pub struct TraitPredicate { - pub trait_ref: TraitRef, - pub polarity: PredicatePolarity, -} - -#[derive(Clone, Debug, Eq, PartialEq, Serialize)] -pub struct OutlivesClause(pub A, pub B); - -pub type RegionOutlivesClause = OutlivesClause; -pub type TypeOutlivesClause = OutlivesClause; - -#[deprecated = "renamed to [`OutlivesClause`]"] -pub type OutlivesPredicate = OutlivesClause; -#[deprecated = "renamed to [`RegionOutlivesClause`]"] -pub type RegionOutlivesPredicate = RegionOutlivesClause; -#[deprecated = "renamed to [`TypeOutlivesClause`]"] -pub type TypeOutlivesPredicate = TypeOutlivesClause; - -#[derive(Clone, Debug, Eq, PartialEq, Serialize)] -pub struct ProjectionPredicate { - pub projection_term: AliasTerm, - pub term: TermKind, -} - -#[derive(Clone, Debug, Eq, PartialEq, Serialize)] -pub enum ImplPolarity { - Positive, - Negative, - Reservation, -} - -#[derive(Clone, Debug, Eq, PartialEq, Serialize)] -pub enum PredicatePolarity { - Positive, - Negative, -} - -macro_rules! index_impl { - ($name:ident) => { - impl crate::IndexedVal for $name { - fn to_val(index: usize) -> Self { - $name(index, $crate::ThreadLocalIndex) - } - fn to_index(&self) -> usize { - self.0 - } - } - $crate::ty::serialize_index_impl!($name); - }; -} -macro_rules! serialize_index_impl { - ($name:ident) => { - impl ::serde::Serialize for $name { - fn serialize(&self, serializer: S) -> Result - where - S: ::serde::Serializer, - { - let n: usize = self.0; // Make sure we're serializing an int. - ::serde::Serialize::serialize(&n, serializer) - } - } - }; -} -pub(crate) use index_impl; -pub(crate) use serialize_index_impl; - -index_impl!(TyConstId); -index_impl!(MirConstId); -index_impl!(Ty); -index_impl!(Span); - -/// The source-order index of a variant in a type. -/// -/// For example, in the following types, -/// ```ignore(illustrative) -/// enum Demo1 { -/// Variant0 { a: bool, b: i32 }, -/// Variant1 { c: u8, d: u64 }, -/// } -/// struct Demo2 { e: u8, f: u16, g: u8 } -/// ``` -/// `a` is in the variant with the `VariantIdx` of `0`, -/// `c` is in the variant with the `VariantIdx` of `1`, and -/// `g` is in the variant with the `VariantIdx` of `0`. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub struct VariantIdx(usize, ThreadLocalIndex); - -index_impl!(VariantIdx); - -crate_def! { - /// Hold information about an Opaque definition, particularly useful in `RPITIT`. - #[derive(Serialize)] - pub OpaqueDef; -} - -crate_def! { - #[derive(Serialize)] - pub AssocDef; -} - -#[derive(Clone, Debug, Eq, PartialEq, Serialize)] -pub struct AssocItem { - pub def_id: AssocDef, - pub kind: AssocKind, - pub container: AssocContainer, -} - -#[derive(Clone, PartialEq, Debug, Eq, Serialize)] -pub enum AssocTypeData { - Normal(Symbol), - /// The associated type comes from an RPITIT. It has no name, and the - /// `ImplTraitInTraitData` provides additional information about its - /// source. - Rpitit(ImplTraitInTraitData), -} - -#[derive(Clone, Debug, Eq, PartialEq, Serialize)] -pub enum AssocKind { - Const { name: Symbol }, - Fn { name: Symbol, has_self: bool }, - Type { data: AssocTypeData }, -} - -#[derive(Clone, Debug, Eq, PartialEq, Serialize)] -pub enum AssocContainer { - InherentImpl, - /// The `AssocDef` points to the trait item being implemented. - TraitImpl(AssocDef), - Trait, -} - -#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, Serialize)] -pub enum ImplTraitInTraitData { - Trait { fn_def_id: FnDef, opaque_def_id: OpaqueDef }, - Impl { fn_def_id: FnDef }, -} - -impl AssocItem { - pub fn is_impl_trait_in_trait(&self) -> bool { - matches!(self.kind, AssocKind::Type { data: AssocTypeData::Rpitit(_) }) - } -} - -#[derive(Clone, Debug, Eq, PartialEq, Serialize)] -pub enum VtblEntry { - /// destructor of this type (used in vtable header) - MetadataDropInPlace, - /// layout size of this type (used in vtable header) - MetadataSize, - /// layout align of this type (used in vtable header) - MetadataAlign, - /// non-dispatchable associated function that is excluded from trait object - Vacant, - /// dispatchable associated function - Method(Instance), - /// pointer to a separate supertrait vtable, can be used by trait upcasting coercion - TraitVPtr(TraitRef), -} +mod def; +mod tys; +pub use def::*; +pub use tys::*; diff --git a/compiler/rustc_public/src/ty/def.rs b/compiler/rustc_public/src/ty/def.rs new file mode 100644 index 0000000000000..4e34e3bbb2e33 --- /dev/null +++ b/compiler/rustc_public/src/ty/def.rs @@ -0,0 +1,316 @@ +use serde::Serialize; + +use crate::abi::ReprOptions; +use crate::crate_def::{CrateDef, CrateDefType}; +use crate::mir::Body; +use crate::ty::tys::*; +use crate::{AssocItems, DefId, IndexedVal, Symbol, with}; + +crate_def! { + #[derive(Serialize)] + pub ForeignModuleDef; + + #[derive(Serialize)] + pub ClosureDef; + + #[derive(Serialize)] + pub CoroutineDef; + + #[derive(Serialize)] + pub CoroutineClosureDef; + + #[derive(Serialize)] + pub ParamDef; + + #[derive(Serialize)] + pub BrNamedDef; + + #[derive(Serialize)] + pub AdtDef; + + #[derive(Serialize)] + pub AliasDef; + + /// A trait's definition. + #[derive(Serialize)] + pub TraitDef; + + #[derive(Serialize)] + pub GenericDef; + + #[derive(Serialize)] + pub RegionDef; + + #[derive(Serialize)] + pub CoroutineWitnessDef; + + /// Hold information about an Opaque definition, particularly useful in `RPITIT`. + #[derive(Serialize)] + pub OpaqueDef; + + #[derive(Serialize)] + pub AssocDef; +} + +crate_def_with_ty! { + /// Hold information about a ForeignItem in a crate. + #[derive(Serialize)] + pub ForeignDef; + + /// Hold information about a function definition in a crate. + #[derive(Serialize)] + pub FnDef; + + #[derive(Serialize)] + pub IntrinsicDef; + + #[derive(Serialize)] + pub FieldDef { + /// The field name. + pub name: Symbol, + } + + #[derive(Serialize)] + pub ConstDef; + + /// A trait impl definition. + #[derive(Serialize)] + pub ImplDef; +} + +impl ForeignModuleDef { + pub fn module(&self) -> ForeignModule { + with(|cx| cx.foreign_module(*self)) + } +} + +impl ForeignDef { + pub fn kind(&self) -> ForeignItemKind { + with(|cx| cx.foreign_item_kind(*self)) + } +} + +impl FnDef { + // Get the function body if available. + pub fn body(&self) -> Option { + with(|ctx| ctx.has_body(self.0).then(|| ctx.mir_body(self.0))) + } + + // Check if the function body is available. + pub fn has_body(&self) -> bool { + with(|ctx| ctx.has_body(self.0)) + } + + /// Get the information of the intrinsic if this function is a definition of one. + pub fn as_intrinsic(&self) -> Option { + with(|cx| cx.intrinsic(self.def_id())) + } + + /// Check if the function is an intrinsic. + #[inline] + pub fn is_intrinsic(&self) -> bool { + self.as_intrinsic().is_some() + } + + /// Get the constness of this function definition. + pub fn constness(&self) -> Constness { + with(|cx| cx.constness(*self)) + } + + /// Get the asyncness of this function definition. + pub fn asyncness(&self) -> Asyncness { + with(|cx| cx.asyncness(*self)) + } + + /// Get the function signature for this function definition. + pub fn fn_sig(&self) -> PolyFnSig { + let kind = self.ty().kind(); + kind.fn_sig().unwrap() + } + + /// Get the generics of this function definition. + pub fn generics_of(&self) -> Generics { + with(|cx| cx.generics_of(self.0)) + } + + /// Get the associated item information if this function is one. + pub fn associated_item(&self) -> Option { + with(|cx| cx.associated_item(self.0)) + } +} + +impl IntrinsicDef { + /// Returns the plain name of the intrinsic. + /// e.g., `transmute` for `core::intrinsics::transmute`. + pub fn fn_name(&self) -> Symbol { + with(|cx| cx.intrinsic_name(*self)) + } + + /// Returns whether the intrinsic has no meaningful body and all backends + /// need to shim all calls to it. + pub fn must_be_overridden(&self) -> bool { + with(|cx| !cx.has_body(self.0)) + } +} + +impl From for FnDef { + fn from(def: IntrinsicDef) -> Self { + FnDef(def.0) + } +} + +impl ClosureDef { + /// Retrieves the body of the closure definition. Returns None if the body + /// isn't available. + pub fn body(&self) -> Option { + with(|ctx| ctx.has_body(self.0).then(|| ctx.mir_body(self.0))) + } +} + +impl CoroutineDef { + /// Retrieves the body of the coroutine definition. Returns None if the body + /// isn't available. + pub fn body(&self) -> Option { + with(|cx| cx.has_body(self.0).then(|| cx.mir_body(self.0))) + } + + pub fn discriminant_for_variant(&self, args: &GenericArgs, idx: VariantIdx) -> Discr { + with(|cx| cx.coroutine_discr_for_variant(*self, args, idx)) + } +} + +impl AdtDef { + pub fn kind(&self) -> AdtKind { + with(|cx| cx.adt_kind(*self)) + } + + /// Retrieve the type of this Adt. + pub fn ty(&self) -> Ty { + with(|cx| cx.def_ty(self.0)) + } + + /// Retrieve the type of this Adt by instantiating and normalizing it with the given arguments. + /// + /// This will assume the type can be instantiated with these arguments. + pub fn ty_with_args(&self, args: &GenericArgs) -> Ty { + with(|cx| cx.def_ty_with_args(self.0, args)) + } + + pub fn is_box(&self) -> bool { + with(|cx| cx.adt_is_box(*self)) + } + + pub fn is_simd(&self) -> bool { + with(|cx| cx.adt_is_simd(*self)) + } + + /// The number of variants in this ADT. + pub fn num_variants(&self) -> usize { + with(|cx| cx.adt_variants_len(*self)) + } + + /// Retrieve the variants in this ADT. + pub fn variants(&self) -> Vec { + self.variants_iter().collect() + } + + /// Iterate over the variants in this ADT. + pub fn variants_iter(&self) -> impl Iterator { + (0..self.num_variants()) + .map(|idx| VariantDef { idx: VariantIdx::to_val(idx), adt_def: *self }) + } + + pub fn variant(&self, idx: VariantIdx) -> Option { + (idx.to_index() < self.num_variants()).then_some(VariantDef { idx, adt_def: *self }) + } + + pub fn repr(&self) -> ReprOptions { + with(|cx| cx.adt_repr(*self)) + } + + pub fn discriminant_for_variant(&self, idx: VariantIdx) -> Discr { + with(|cx| cx.adt_discr_for_variant(*self, idx)) + } + + /// Get the generics of this ADT definition. + pub fn generics_of(&self) -> Generics { + with(|cx| cx.generics_of(self.0)) + } + + /// Retrieve the inherent implementations for this ADT. + pub fn inherent_impls(&self) -> Vec { + with(|cx| cx.inherent_impls(*self)) + } +} + +/// Definition of a variant, which can be either a struct / union field or an enum variant. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)] +pub struct VariantDef { + /// The variant index. + pub(crate) idx: VariantIdx, + /// The data type where this variant comes from. + /// For now, we use this to retrieve information about the variant itself so we don't need to + /// cache more information. + pub(crate) adt_def: AdtDef, +} + +impl VariantDef { + /// The name of the variant, struct or union. + /// + /// This will not include the name of the enum or qualified path. + pub fn name(&self) -> Symbol { + with(|cx| cx.variant_name(*self)) + } + + /// Retrieve all the fields in this variant. + // We expect user to cache this and use it directly since today it is expensive to generate all + // fields name. + pub fn fields(&self) -> Vec { + with(|cx| cx.variant_fields(*self)) + } + + /// Returns the variant index. + pub fn idx(&self) -> VariantIdx { + self.idx + } + + /// Returns the `AdtDef` which this variant comes from. + pub fn adt_def(&self) -> AdtDef { + self.adt_def + } +} + +impl TraitDef { + pub fn declaration(trait_def: &TraitDef) -> TraitDecl { + with(|cx| cx.trait_decl(trait_def)) + } + + pub fn associated_items(&self) -> AssocItems { + with(|cx| cx.associated_items(self.def_id())) + } +} + +impl ImplDef { + /// Retrieve information about this implementation. + pub fn trait_impl(&self) -> ImplTrait { + with(|cx| cx.trait_impl(self)) + } + + pub fn associated_items(&self) -> AssocItems { + with(|cx| cx.associated_items(self.def_id())) + } + + /// Get the generics of this implementation. + pub fn generics_of(&self) -> Generics { + with(|cx| cx.generics_of(self.0)) + } +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub struct GenericParamDef { + pub name: Symbol, + pub def_id: GenericDef, + pub index: u32, + pub pure_wrt_drop: bool, + pub kind: GenericParamDefKind, +} diff --git a/compiler/rustc_public/src/ty/tys.rs b/compiler/rustc_public/src/ty/tys.rs new file mode 100644 index 0000000000000..35792ce2fcc23 --- /dev/null +++ b/compiler/rustc_public/src/ty/tys.rs @@ -0,0 +1,1409 @@ +use std::fmt::{self, Debug, Display, Formatter}; +use std::ops::Range; + +use serde::Serialize; + +use crate::abi::{FnAbi, Layout}; +use crate::mir::alloc::{AllocId, read_target_int, read_target_uint}; +use crate::mir::mono::{Instance, StaticDef}; +use crate::mir::{Mutability, Safety}; +use crate::target::MachineInfo; +use crate::ty::def::*; +use crate::{Error, Filename, Opaque, Symbol, ThreadLocalIndex, with}; + +#[derive(Copy, Clone, Eq, PartialEq, Hash)] +pub struct Ty(usize, ThreadLocalIndex); + +impl Debug for Ty { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("Ty").field("id", &self.0).field("kind", &self.kind()).finish() + } +} + +/// Constructors for `Ty`. +impl Ty { + /// Create a new type from a given kind. + pub fn from_rigid_kind(kind: RigidTy) -> Ty { + with(|cx| cx.new_rigid_ty(kind)) + } + + /// Create a new array type. + pub fn try_new_array(elem_ty: Ty, size: u64) -> Result { + Ok(Ty::from_rigid_kind(RigidTy::Array(elem_ty, TyConst::try_from_target_usize(size)?))) + } + + /// Create a new array type from Const length. + pub fn new_array_with_const_len(elem_ty: Ty, len: TyConst) -> Ty { + Ty::from_rigid_kind(RigidTy::Array(elem_ty, len)) + } + + /// Create a new pointer type. + pub fn new_ptr(pointee_ty: Ty, mutability: Mutability) -> Ty { + Ty::from_rigid_kind(RigidTy::RawPtr(pointee_ty, mutability)) + } + + /// Create a new reference type. + pub fn new_ref(reg: Region, pointee_ty: Ty, mutability: Mutability) -> Ty { + Ty::from_rigid_kind(RigidTy::Ref(reg, pointee_ty, mutability)) + } + + /// Create a new pointer type. + pub fn new_tuple(tys: &[Ty]) -> Ty { + Ty::from_rigid_kind(RigidTy::Tuple(Vec::from(tys))) + } + + /// Create a new closure type. + pub fn new_closure(def: ClosureDef, args: GenericArgs) -> Ty { + Ty::from_rigid_kind(RigidTy::Closure(def, args)) + } + + /// Create a new coroutine type. + pub fn new_coroutine(def: CoroutineDef, args: GenericArgs) -> Ty { + Ty::from_rigid_kind(RigidTy::Coroutine(def, args)) + } + + /// Create a new closure type. + pub fn new_coroutine_closure(def: CoroutineClosureDef, args: GenericArgs) -> Ty { + Ty::from_rigid_kind(RigidTy::CoroutineClosure(def, args)) + } + + /// Create a new box type that represents `Box`, for the given inner type `T`. + pub fn new_box(inner_ty: Ty) -> Ty { + with(|cx| cx.new_box_ty(inner_ty)) + } + + /// Create a type representing `usize`. + pub fn usize_ty() -> Ty { + Ty::from_rigid_kind(RigidTy::Uint(UintTy::Usize)) + } + + /// Create a type representing `bool`. + pub fn bool_ty() -> Ty { + Ty::from_rigid_kind(RigidTy::Bool) + } + + /// Create a type representing a signed integer. + pub fn signed_ty(inner: IntTy) -> Ty { + Ty::from_rigid_kind(RigidTy::Int(inner)) + } + + /// Create a type representing an unsigned integer. + pub fn unsigned_ty(inner: UintTy) -> Ty { + Ty::from_rigid_kind(RigidTy::Uint(inner)) + } + + /// Get a type layout. + pub fn layout(self) -> Result { + with(|cx| cx.ty_layout(self)) + } +} + +impl Ty { + pub fn kind(&self) -> TyKind { + with(|context| context.ty_kind(*self)) + } +} + +/// Represents a pattern in the type system +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub enum Pattern { + Range { start: TyConst, end: TyConst, include_end: bool }, + NotNull, + Or(Vec), +} + +/// Represents a constant in the type system +#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize)] +pub struct TyConst { + pub(crate) kind: TyConstKind, + pub id: TyConstId, +} + +impl TyConst { + pub fn new(kind: TyConstKind, id: TyConstId) -> TyConst { + Self { kind, id } + } + + /// Retrieve the constant kind. + pub fn kind(&self) -> &TyConstKind { + &self.kind + } + + /// Creates an interned usize constant. + pub fn try_from_target_usize(val: u64) -> Result { + with(|cx| cx.try_new_ty_const_uint(val.into(), UintTy::Usize)) + } + + /// Try to evaluate to a target `usize`. + pub fn eval_target_usize(&self) -> Result { + with(|cx| cx.eval_target_usize_ty(self)) + } +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize)] +pub enum TyConstKind { + Param(ParamConst), + Bound(DebruijnIndex, BoundVar), + Unevaluated(ConstDef, GenericArgs), + + // FIXME: These should be a valtree + Value(Ty, Allocation), + ZSTValue(Ty), +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub struct TyConstId(usize, ThreadLocalIndex); + +/// Represents a constant in MIR +#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize)] +pub struct MirConst { + /// The constant kind. + pub(crate) kind: ConstantKind, + /// The constant type. + pub(crate) ty: Ty, + /// Used for internal tracking of the internal constant. + pub id: MirConstId, +} + +impl MirConst { + /// Build a constant. Note that this should only be used by the compiler. + pub fn new(kind: ConstantKind, ty: Ty, id: MirConstId) -> MirConst { + MirConst { kind, ty, id } + } + + /// Retrieve the constant kind. + pub fn kind(&self) -> &ConstantKind { + &self.kind + } + + /// Get the constant type. + pub fn ty(&self) -> Ty { + self.ty + } + + /// Try to evaluate to a target `usize`. + pub fn eval_target_usize(&self) -> Result { + with(|cx| cx.eval_target_usize(self)) + } + + /// Create a constant that represents a new zero-sized constant of type T. + /// Fails if the type is not a ZST or if it doesn't have a known size. + pub fn try_new_zero_sized(ty: Ty) -> Result { + with(|cx| cx.try_new_const_zst(ty)) + } + + /// Build a new constant that represents the given string. + /// + /// Note that there is no guarantee today about duplication of the same constant. + /// I.e.: Calling this function multiple times with the same argument may or may not return + /// the same allocation. + pub fn from_str(value: &str) -> MirConst { + with(|cx| cx.new_const_str(value)) + } + + /// Build a new constant that represents the given boolean value. + pub fn from_bool(value: bool) -> MirConst { + with(|cx| cx.new_const_bool(value)) + } + + /// Build a new constant that represents the given unsigned integer. + pub fn try_from_uint(value: u128, uint_ty: UintTy) -> Result { + with(|cx| cx.try_new_const_uint(value, uint_ty)) + } + + /// Build a new constant that represents the given floating point number. + /// The value is the binary representation of the float constant. + /// Example: `try_from_float(2.5_f32.to_bits() as u128, FloatTy::F32)`. + pub fn try_from_float(value: u128, float_ty: FloatTy) -> Result { + with(|cx| cx.try_new_const_float(value, float_ty)) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct MirConstId(usize, ThreadLocalIndex); + +type Ident = Opaque; + +#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize)] +pub struct Region { + pub kind: RegionKind, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize)] +pub enum RegionKind { + ReEarlyParam(EarlyParamRegion), + ReBound(DebruijnIndex, BoundRegion), + ReStatic, + RePlaceholder(Placeholder), + ReErased, +} + +pub(crate) type DebruijnIndex = u32; + +#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize)] +pub struct EarlyParamRegion { + pub index: u32, + pub name: Symbol, +} + +pub(crate) type BoundVar = u32; + +#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize)] +pub struct BoundRegion { + pub var: BoundVar, + pub kind: BoundRegionKind, +} + +pub(crate) type UniverseIndex = u32; + +#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize)] +pub struct Placeholder { + pub universe: UniverseIndex, + pub bound: T, +} + +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +pub struct Span(usize, ThreadLocalIndex); + +impl Debug for Span { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("Span") + .field("id", &self.0) + .field("repr", &with(|cx| cx.span_to_string(*self))) + .finish() + } +} + +impl Span { + /// Return filename for diagnostic purposes + pub fn get_filename(&self) -> Filename { + with(|c| c.get_filename(self)) + } + + /// Return lines that correspond to this `Span` + pub fn get_lines(&self) -> LineInfo { + with(|c| c.get_lines(self)) + } + + /// Return the span location to be printed in diagnostic messages. + /// + /// This may leak local file paths and should not be used to build artifacts that may be + /// distributed. + pub fn diagnostic(&self) -> String { + with(|c| c.span_to_string(*self)) + } + + /// Create a `&'static core::panic::Location<'static>` constant from this span. + pub(crate) fn as_caller_location(&self) -> MirConst { + with(|c| c.span_as_caller_location(*self)) + } +} + +#[derive(Clone, Copy, Debug, Serialize)] +/// Information you get from `Span` in a struct form. +/// Line and col start from 1. +pub struct LineInfo { + pub start_line: usize, + pub start_col: usize, + pub end_line: usize, + pub end_col: usize, +} + +impl LineInfo { + pub fn from(lines: (usize, usize, usize, usize)) -> Self { + LineInfo { start_line: lines.0, start_col: lines.1, end_line: lines.2, end_col: lines.3 } + } +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub enum TyKind { + RigidTy(RigidTy), + Alias(AliasKind, AliasTy), + Param(ParamTy), + Bound(usize, BoundTy), +} + +impl TyKind { + pub fn rigid(&self) -> Option<&RigidTy> { + if let TyKind::RigidTy(inner) = self { Some(inner) } else { None } + } + + #[inline] + pub fn is_unit(&self) -> bool { + matches!(self, TyKind::RigidTy(RigidTy::Tuple(data)) if data.is_empty()) + } + + #[inline] + pub fn is_bool(&self) -> bool { + matches!(self, TyKind::RigidTy(RigidTy::Bool)) + } + + #[inline] + pub fn is_char(&self) -> bool { + matches!(self, TyKind::RigidTy(RigidTy::Char)) + } + + #[inline] + pub fn is_trait(&self) -> bool { + matches!(self, TyKind::RigidTy(RigidTy::Dynamic(_, _))) + } + + #[inline] + pub fn is_enum(&self) -> bool { + matches!(self, TyKind::RigidTy(RigidTy::Adt(def, _)) if def.kind() == AdtKind::Enum) + } + + #[inline] + pub fn is_struct(&self) -> bool { + matches!(self, TyKind::RigidTy(RigidTy::Adt(def, _)) if def.kind() == AdtKind::Struct) + } + + #[inline] + pub fn is_union(&self) -> bool { + matches!(self, TyKind::RigidTy(RigidTy::Adt(def, _)) if def.kind() == AdtKind::Union) + } + + #[inline] + pub fn is_adt(&self) -> bool { + matches!(self, TyKind::RigidTy(RigidTy::Adt(..))) + } + + #[inline] + pub fn is_ref(&self) -> bool { + matches!(self, TyKind::RigidTy(RigidTy::Ref(..))) + } + + #[inline] + pub fn is_fn(&self) -> bool { + matches!(self, TyKind::RigidTy(RigidTy::FnDef(..))) + } + + #[inline] + pub fn is_fn_ptr(&self) -> bool { + matches!(self, TyKind::RigidTy(RigidTy::FnPtr(..))) + } + + #[inline] + pub fn is_primitive(&self) -> bool { + matches!( + self, + TyKind::RigidTy( + RigidTy::Bool + | RigidTy::Char + | RigidTy::Int(_) + | RigidTy::Uint(_) + | RigidTy::Float(_) + ) + ) + } + + #[inline] + pub fn is_float(&self) -> bool { + matches!(self, TyKind::RigidTy(RigidTy::Float(_))) + } + + #[inline] + pub fn is_integral(&self) -> bool { + matches!(self, TyKind::RigidTy(RigidTy::Int(_) | RigidTy::Uint(_))) + } + + #[inline] + pub fn is_numeric(&self) -> bool { + self.is_integral() || self.is_float() + } + + #[inline] + pub fn is_signed(&self) -> bool { + matches!(self, TyKind::RigidTy(RigidTy::Int(_))) + } + + #[inline] + pub fn is_str(&self) -> bool { + *self == TyKind::RigidTy(RigidTy::Str) + } + + #[inline] + pub fn is_cstr(&self) -> bool { + let TyKind::RigidTy(RigidTy::Adt(def, _)) = self else { + return false; + }; + with(|cx| cx.adt_is_cstr(*def)) + } + + #[inline] + pub fn is_slice(&self) -> bool { + matches!(self, TyKind::RigidTy(RigidTy::Slice(_))) + } + + #[inline] + pub fn is_array(&self) -> bool { + matches!(self, TyKind::RigidTy(RigidTy::Array(..))) + } + + #[inline] + pub fn is_mutable_ptr(&self) -> bool { + matches!( + self, + TyKind::RigidTy(RigidTy::RawPtr(_, Mutability::Mut)) + | TyKind::RigidTy(RigidTy::Ref(_, _, Mutability::Mut)) + ) + } + + #[inline] + pub fn is_raw_ptr(&self) -> bool { + matches!(self, TyKind::RigidTy(RigidTy::RawPtr(..))) + } + + /// Tests if this is any kind of primitive pointer type (reference, raw pointer, fn pointer). + #[inline] + pub fn is_any_ptr(&self) -> bool { + self.is_ref() || self.is_raw_ptr() || self.is_fn_ptr() + } + + #[inline] + pub fn is_coroutine(&self) -> bool { + matches!(self, TyKind::RigidTy(RigidTy::Coroutine(..))) + } + + #[inline] + pub fn is_closure(&self) -> bool { + matches!(self, TyKind::RigidTy(RigidTy::Closure(..))) + } + + #[inline] + pub fn is_box(&self) -> bool { + match self { + TyKind::RigidTy(RigidTy::Adt(def, _)) => def.is_box(), + _ => false, + } + } + + #[inline] + pub fn is_simd(&self) -> bool { + matches!(self, TyKind::RigidTy(RigidTy::Adt(def, _)) if def.is_simd()) + } + + pub fn trait_principal(&self) -> Option> { + if let TyKind::RigidTy(RigidTy::Dynamic(predicates, _)) = self { + if let Some(Binder { value: ExistentialPredicate::Trait(trait_ref), bound_vars }) = + predicates.first() + { + Some(Binder { value: trait_ref.clone(), bound_vars: bound_vars.clone() }) + } else { + None + } + } else { + None + } + } + + /// Returns the type of `ty[i]` for builtin types. + pub fn builtin_index(&self) -> Option { + match self.rigid()? { + RigidTy::Array(ty, _) | RigidTy::Slice(ty) => Some(*ty), + _ => None, + } + } + + /// Returns the type and mutability of `*ty` for builtin types. + /// + /// The parameter `explicit` indicates if this is an *explicit* dereference. + /// Some types -- notably raw ptrs -- can only be dereferenced explicitly. + pub fn builtin_deref(&self, explicit: bool) -> Option { + match self.rigid()? { + RigidTy::Adt(def, args) if def.is_box() => { + Some(TypeAndMut { ty: *args.0.first()?.ty()?, mutability: Mutability::Not }) + } + RigidTy::Ref(_, ty, mutability) => { + Some(TypeAndMut { ty: *ty, mutability: *mutability }) + } + RigidTy::RawPtr(ty, mutability) if explicit => { + Some(TypeAndMut { ty: *ty, mutability: *mutability }) + } + _ => None, + } + } + + /// Get the function signature for function like types (Fn, FnPtr, and Closure) + pub fn fn_sig(&self) -> Option { + match self { + TyKind::RigidTy(RigidTy::FnDef(def, args)) => Some(with(|cx| cx.fn_sig(*def, args))), + TyKind::RigidTy(RigidTy::FnPtr(sig)) => Some(sig.clone()), + TyKind::RigidTy(RigidTy::Closure(_def, args)) => Some(with(|cx| cx.closure_sig(args))), + _ => None, + } + } + + /// Get the discriminant type for this type. + pub fn discriminant_ty(&self) -> Option { + self.rigid().map(|ty| with(|cx| cx.rigid_ty_discriminant_ty(ty))) + } + + /// Deconstruct a function type if this is one. + pub fn fn_def(&self) -> Option<(FnDef, &GenericArgs)> { + if let TyKind::RigidTy(RigidTy::FnDef(def, args)) = self { + Some((*def, args)) + } else { + None + } + } +} + +pub struct TypeAndMut { + pub ty: Ty, + pub mutability: Mutability, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub enum RigidTy { + Bool, + Char, + Int(IntTy), + Uint(UintTy), + Float(FloatTy), + Adt(AdtDef, GenericArgs), + Foreign(ForeignDef), + Str, + Array(Ty, TyConst), + Pat(Ty, Pattern), + Slice(Ty), + RawPtr(Ty, Mutability), + Ref(Region, Ty, Mutability), + FnDef(FnDef, GenericArgs), + FnPtr(PolyFnSig), + Closure(ClosureDef, GenericArgs), + Coroutine(CoroutineDef, GenericArgs), + CoroutineClosure(CoroutineClosureDef, GenericArgs), + Dynamic(Vec>, Region), + Never, + Tuple(Vec), + CoroutineWitness(CoroutineWitnessDef, GenericArgs), +} + +impl RigidTy { + /// Get the discriminant type for this type. + pub fn discriminant_ty(&self) -> Ty { + with(|cx| cx.rigid_ty_discriminant_ty(self)) + } +} + +impl From for TyKind { + fn from(value: RigidTy) -> Self { + TyKind::RigidTy(value) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)] +pub enum IntTy { + Isize, + I8, + I16, + I32, + I64, + I128, +} + +impl IntTy { + pub fn num_bytes(self) -> usize { + match self { + IntTy::Isize => MachineInfo::target_pointer_width().bytes(), + IntTy::I8 => 1, + IntTy::I16 => 2, + IntTy::I32 => 4, + IntTy::I64 => 8, + IntTy::I128 => 16, + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)] +pub enum UintTy { + Usize, + U8, + U16, + U32, + U64, + U128, +} + +impl UintTy { + pub fn num_bytes(self) -> usize { + match self { + UintTy::Usize => MachineInfo::target_pointer_width().bytes(), + UintTy::U8 => 1, + UintTy::U16 => 2, + UintTy::U32 => 4, + UintTy::U64 => 8, + UintTy::U128 => 16, + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)] +pub enum FloatTy { + F16, + F32, + F64, + F128, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)] +pub enum Movability { + Static, + Movable, +} + +pub struct ForeignModule { + pub def_id: ForeignModuleDef, + pub abi: Abi, +} + +impl ForeignModule { + pub fn items(&self) -> Vec { + with(|cx| cx.foreign_items(self.def_id)) + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, Serialize)] +pub enum ForeignItemKind { + Fn(FnDef), + Static(StaticDef), + Type(Ty), +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, Serialize)] +pub enum AdtKind { + Enum, + Union, + Struct, +} + +pub struct Discr { + pub val: u128, + pub ty: Ty, +} + +impl Display for AdtKind { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str(match self { + AdtKind::Enum => "enum", + AdtKind::Union => "union", + AdtKind::Struct => "struct", + }) + } +} + +impl AdtKind { + pub fn is_enum(&self) -> bool { + matches!(self, AdtKind::Enum) + } + + pub fn is_struct(&self) -> bool { + matches!(self, AdtKind::Struct) + } + + pub fn is_union(&self) -> bool { + matches!(self, AdtKind::Union) + } +} + +/// A list of generic arguments. +#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize)] +pub struct GenericArgs(pub Vec); + +impl std::ops::Index for GenericArgs { + type Output = Ty; + + fn index(&self, index: ParamTy) -> &Self::Output { + self.0[index.index as usize].expect_ty() + } +} + +impl std::ops::Index for GenericArgs { + type Output = TyConst; + + fn index(&self, index: ParamConst) -> &Self::Output { + self.0[index.index as usize].expect_const() + } +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize)] +pub enum GenericArgKind { + Lifetime(Region), + Type(Ty), + Const(TyConst), +} + +impl GenericArgKind { + /// Panic if this generic argument is not a type, otherwise + /// return the type. + #[track_caller] + pub fn expect_ty(&self) -> &Ty { + match self { + GenericArgKind::Type(ty) => ty, + _ => panic!("{self:?}"), + } + } + + /// Panic if this generic argument is not a const, otherwise + /// return the const. + #[track_caller] + pub fn expect_const(&self) -> &TyConst { + match self { + GenericArgKind::Const(c) => c, + _ => panic!("{self:?}"), + } + } + + /// Return the generic argument type if applicable, otherwise return `None`. + pub fn ty(&self) -> Option<&Ty> { + match self { + GenericArgKind::Type(ty) => Some(ty), + _ => None, + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub enum TermKind { + Type(Ty), + Const(TyConst), +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub enum AliasKind { + Projection, + Inherent, + Opaque, + Free, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub struct AliasTy { + pub def_id: AliasDef, + pub args: GenericArgs, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub struct AliasTerm { + pub def_id: AliasDef, + pub args: GenericArgs, +} + +pub type PolyFnSig = Binder; + +impl PolyFnSig { + /// Compute a `FnAbi` suitable for indirect calls, i.e. to `fn` pointers. + /// + /// NB: this doesn't handle virtual calls - those should use `Instance::fn_abi` + /// instead, where the instance is an `InstanceKind::Virtual`. + pub fn fn_ptr_abi(self) -> Result { + with(|cx| cx.fn_ptr_abi(self)) + } +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub struct FnSig { + pub inputs_and_output: Vec, + pub c_variadic: bool, + pub safety: Safety, + pub abi: Abi, +} + +impl FnSig { + pub fn output(&self) -> Ty { + self.inputs_and_output[self.inputs_and_output.len() - 1] + } + + pub fn inputs(&self) -> &[Ty] { + &self.inputs_and_output[..self.inputs_and_output.len() - 1] + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize)] +pub enum Constness { + Const { always: bool }, + NotConst, +} + +impl Constness { + pub fn is_const(self) -> bool { + matches!(self, Constness::Const { always: false }) + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize)] +pub enum Asyncness { + Async, + NotAsync, +} + +impl Asyncness { + pub fn is_async(self) -> bool { + matches!(self, Asyncness::Async) + } +} + +#[derive(Clone, PartialEq, Eq, Debug, Serialize)] +pub enum Abi { + Rust, + C { unwind: bool }, + Cdecl { unwind: bool }, + Stdcall { unwind: bool }, + Fastcall { unwind: bool }, + Vectorcall { unwind: bool }, + Thiscall { unwind: bool }, + Aapcs { unwind: bool }, + Win64 { unwind: bool }, + SysV64 { unwind: bool }, + PtxKernel, + Msp430Interrupt, + X86Interrupt, + GpuKernel, + EfiApi, + AvrInterrupt, + AvrNonBlockingInterrupt, + CCmseNonSecureCall, + CCmseNonSecureEntry, + System { unwind: bool }, + RustCall, + Unadjusted, + RustCold, + RiscvInterruptM, + RiscvInterruptS, + RustPreserveNone, + RustTail, + RustInvalid, + Custom, + Swift, +} + +/// A binder represents a possibly generic type and its bound vars. +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub struct Binder { + pub value: T, + pub bound_vars: Vec, +} + +impl Binder { + /// Create a new binder with the given bound vars. + pub fn bind_with_vars(value: T, bound_vars: Vec) -> Self { + Binder { value, bound_vars } + } + + /// Create a new binder with no bounded variable. + pub fn dummy(value: T) -> Self { + Binder { value, bound_vars: vec![] } + } + + pub fn skip_binder(self) -> T { + self.value + } + + pub fn map_bound_ref(&self, f: F) -> Binder + where + F: FnOnce(&T) -> U, + { + let Binder { value, bound_vars } = self; + let new_value = f(value); + Binder { value: new_value, bound_vars: bound_vars.clone() } + } + + pub fn map_bound(self, f: F) -> Binder + where + F: FnOnce(T) -> U, + { + let Binder { value, bound_vars } = self; + let new_value = f(value); + Binder { value: new_value, bound_vars } + } +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub struct EarlyBinder { + pub value: T, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub enum BoundVariableKind { + Ty(BoundTyKind), + Region(BoundRegionKind), + Const, +} + +#[derive(Clone, PartialEq, Eq, Debug, Serialize)] +pub enum BoundTyKind { + Anon, + Param(ParamDef, String), +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize)] +pub enum BoundRegionKind { + BrAnon, + BrNamed(BrNamedDef, String), + BrEnv, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub enum ExistentialPredicate { + Trait(ExistentialTraitRef), + Projection(ExistentialProjection), + AutoTrait(TraitDef), +} + +/// An existential reference to a trait where `Self` is not included. +/// +/// The `generic_args` will include any other known argument. +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub struct ExistentialTraitRef { + pub def_id: TraitDef, + pub generic_args: GenericArgs, +} + +impl Binder { + pub fn with_self_ty(&self, self_ty: Ty) -> Binder { + self.map_bound_ref(|trait_ref| trait_ref.with_self_ty(self_ty)) + } +} + +impl ExistentialTraitRef { + pub fn with_self_ty(&self, self_ty: Ty) -> TraitRef { + TraitRef::new(self.def_id, self_ty, &self.generic_args) + } +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub struct ExistentialProjection { + pub def_id: TraitDef, + pub generic_args: GenericArgs, + pub term: TermKind, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub struct ParamTy { + pub index: u32, + pub name: String, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub struct BoundTy { + pub var: usize, + pub kind: BoundTyKind, +} + +pub type Bytes = Vec>; + +/// Size in bytes. +pub type Size = usize; + +#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, Serialize)] +pub struct Prov(pub AllocId); + +pub type Align = u64; +pub type Promoted = u32; +pub type InitMaskMaterialized = Vec; + +/// Stores the provenance information of pointers stored in memory. +#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize)] +pub struct ProvenanceMap { + /// Provenance in this map applies from the given offset for an entire pointer-size worth of + /// bytes. Two entries in this map are always at least a pointer size apart. + pub ptrs: Vec<(Size, Prov)>, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize)] +pub struct Allocation { + pub bytes: Bytes, + pub provenance: ProvenanceMap, + pub align: Align, + pub mutability: Mutability, +} + +impl Allocation { + /// Get a vector of bytes for an Allocation that has been fully initialized + pub fn raw_bytes(&self) -> Result, Error> { + self.bytes + .iter() + .copied() + .collect::>>() + .ok_or_else(|| error!("Found uninitialized bytes: `{:?}`", self.bytes)) + } + + /// Read a uint value from the specified range. + pub fn read_partial_uint(&self, range: Range) -> Result { + if range.end - range.start > 16 { + return Err(error!("Allocation is bigger than largest integer")); + } + if range.end > self.bytes.len() { + return Err(error!( + "Range is out of bounds. Allocation length is `{}`, but requested range `{:?}`", + self.bytes.len(), + range + )); + } + let raw = self.bytes[range] + .iter() + .copied() + .collect::>>() + .ok_or_else(|| error!("Found uninitialized bytes: `{:?}`", self.bytes))?; + read_target_uint(&raw) + } + + /// Read this allocation and try to convert it to an unassigned integer. + pub fn read_uint(&self) -> Result { + if self.bytes.len() > 16 { + return Err(error!("Allocation is bigger than largest integer")); + } + let raw = self.raw_bytes()?; + read_target_uint(&raw) + } + + /// Read this allocation and try to convert it to a signed integer. + pub fn read_int(&self) -> Result { + if self.bytes.len() > 16 { + return Err(error!("Allocation is bigger than largest integer")); + } + let raw = self.raw_bytes()?; + read_target_int(&raw) + } + + /// Read this allocation and try to convert it to a boolean. + pub fn read_bool(&self) -> Result { + match self.read_int()? { + 0 => Ok(false), + 1 => Ok(true), + val => Err(error!("Unexpected value for bool: `{val}`")), + } + } + + /// Read this allocation as a pointer and return whether it represents a `null` pointer. + pub fn is_null(&self) -> Result { + let len = self.bytes.len(); + let ptr_len = MachineInfo::target_pointer_width().bytes(); + if len != ptr_len { + return Err(error!("Expected width of pointer (`{ptr_len}`), but found: `{len}`")); + } + Ok(self.read_uint()? == 0 && self.provenance.ptrs.is_empty()) + } +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize)] +pub enum ConstantKind { + Ty(TyConst), + Allocated(Allocation), + Unevaluated(UnevaluatedConst), + Param(ParamConst), + /// Store ZST constants. + /// We have to special handle these constants since its type might be generic. + ZeroSized, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize)] +pub struct ParamConst { + pub index: u32, + pub name: String, +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize)] +pub struct UnevaluatedConst { + pub def: ConstDef, + pub args: GenericArgs, + pub promoted: Option, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)] +pub enum TraitSpecializationKind { + None, + Marker, + AlwaysApplicable, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub struct TraitDecl { + pub def_id: TraitDef, + pub safety: Safety, + pub paren_sugar: bool, + pub has_auto_impl: bool, + pub is_marker: bool, + pub is_coinductive: bool, + pub skip_array_during_method_dispatch: bool, + pub skip_boxed_slice_during_method_dispatch: bool, + pub specialization_kind: TraitSpecializationKind, + pub must_implement_one_of: Option>, + pub force_dyn_incompatible: Option, + pub deny_explicit_impl: bool, +} + +impl TraitDecl { + pub fn generics_of(&self) -> Generics { + with(|cx| cx.generics_of(self.def_id.0)) + } + + pub fn clauses_of(&self) -> GenericClauses { + with(|cx| cx.clauses_of(self.def_id.0)) + } + + pub fn explicit_clauses_of(&self) -> GenericClauses { + with(|cx| cx.explicit_clauses_of(self.def_id.0)) + } +} + +pub type ImplTrait = EarlyBinder; + +/// A complete reference to a trait, i.e., one where `Self` is known. +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub struct TraitRef { + pub def_id: TraitDef, + /// The generic arguments for this definition. + /// The first element must always be type, and it represents `Self`. + args: GenericArgs, +} + +impl TraitRef { + pub fn new(def_id: TraitDef, self_ty: Ty, gen_args: &GenericArgs) -> TraitRef { + let mut args = vec![GenericArgKind::Type(self_ty)]; + args.extend_from_slice(&gen_args.0); + TraitRef { def_id, args: GenericArgs(args) } + } + + pub fn try_new(def_id: TraitDef, args: GenericArgs) -> Result { + match &args.0[..] { + [GenericArgKind::Type(_), ..] => Ok(TraitRef { def_id, args }), + _ => Err(()), + } + } + + pub fn args(&self) -> &GenericArgs { + &self.args + } + + pub fn self_ty(&self) -> Ty { + let GenericArgKind::Type(self_ty) = self.args.0[0] else { + panic!("Self must be a type, but found: {:?}", self.args.0[0]) + }; + self_ty + } + + /// Retrieve all vtable entries. + pub fn vtable_entries(&self) -> Vec { + with(|cx| cx.vtable_entries(self)) + } + + /// Returns the vtable entry at the given index. + /// + /// Returns `None` if the index is out of bounds. + pub fn vtable_entry(&self, idx: usize) -> Option { + with(|cx| cx.vtable_entry(self, idx)) + } +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub struct Generics { + pub parent: Option, + pub parent_count: usize, + pub params: Vec, + pub param_def_id_to_index: Vec<(GenericDef, u32)>, + pub has_self: bool, + pub has_late_bound_regions: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub enum GenericParamDefKind { + Lifetime, + Type { has_default: bool, synthetic: bool }, + Const { has_default: bool }, +} + +pub struct GenericClauses { + pub parent: Option, + pub clauses: Vec<(ClauseKind, Span)>, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub enum PredicateKind { + Clause(ClauseKind), + DynCompatible(TraitDef), + SubType(SubtypePredicate), + Coerce(CoercePredicate), + ConstEquate(TyConst, TyConst), + Ambiguous, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub enum ClauseKind { + Trait(TraitPredicate), + RegionOutlives(RegionOutlivesClause), + TypeOutlives(TypeOutlivesClause), + Projection(ProjectionPredicate), + ConstArgHasType(TyConst, Ty), + WellFormed(TermKind), + ConstEvaluatable(TyConst), +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub enum ClosureKind { + Fn, + FnMut, + FnOnce, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub struct SubtypePredicate { + pub a: Ty, + pub b: Ty, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub struct CoercePredicate { + pub a: Ty, + pub b: Ty, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub struct TraitPredicate { + pub trait_ref: TraitRef, + pub polarity: PredicatePolarity, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub struct OutlivesClause(pub A, pub B); + +pub type RegionOutlivesClause = OutlivesClause; +pub type TypeOutlivesClause = OutlivesClause; + +#[deprecated = "renamed to [`OutlivesClause`]"] +pub type OutlivesPredicate = OutlivesClause; +#[deprecated = "renamed to [`RegionOutlivesClause`]"] +pub type RegionOutlivesPredicate = RegionOutlivesClause; +#[deprecated = "renamed to [`TypeOutlivesClause`]"] +pub type TypeOutlivesPredicate = TypeOutlivesClause; + +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub struct ProjectionPredicate { + pub projection_term: AliasTerm, + pub term: TermKind, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub enum ImplPolarity { + Positive, + Negative, + Reservation, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub enum PredicatePolarity { + Positive, + Negative, +} + +macro_rules! index_impl { + ($name:ident) => { + impl crate::IndexedVal for $name { + fn to_val(index: usize) -> Self { + $name(index, $crate::ThreadLocalIndex) + } + fn to_index(&self) -> usize { + self.0 + } + } + $crate::ty::serialize_index_impl!($name); + }; +} +macro_rules! serialize_index_impl { + ($name:ident) => { + impl ::serde::Serialize for $name { + fn serialize(&self, serializer: S) -> Result + where + S: ::serde::Serializer, + { + let n: usize = self.0; // Make sure we're serializing an int. + ::serde::Serialize::serialize(&n, serializer) + } + } + }; +} +pub(crate) use index_impl; +pub(crate) use serialize_index_impl; + +index_impl!(TyConstId); +index_impl!(MirConstId); +index_impl!(Ty); +index_impl!(Span); + +/// The source-order index of a variant in a type. +/// +/// For example, in the following types, +/// ```ignore(illustrative) +/// enum Demo1 { +/// Variant0 { a: bool, b: i32 }, +/// Variant1 { c: u8, d: u64 }, +/// } +/// struct Demo2 { e: u8, f: u16, g: u8 } +/// ``` +/// `a` is in the variant with the `VariantIdx` of `0`, +/// `c` is in the variant with the `VariantIdx` of `1`, and +/// `g` is in the variant with the `VariantIdx` of `0`. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct VariantIdx(usize, ThreadLocalIndex); + +index_impl!(VariantIdx); + +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub struct AssocItem { + pub def_id: AssocDef, + pub kind: AssocKind, + pub container: AssocContainer, +} + +#[derive(Clone, PartialEq, Debug, Eq, Serialize)] +pub enum AssocTypeData { + Normal(Symbol), + /// The associated type comes from an RPITIT. It has no name, and the + /// `ImplTraitInTraitData` provides additional information about its + /// source. + Rpitit(ImplTraitInTraitData), +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub enum AssocKind { + Const { name: Symbol }, + Fn { name: Symbol, has_self: bool }, + Type { data: AssocTypeData }, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub enum AssocContainer { + InherentImpl, + /// The `AssocDef` points to the trait item being implemented. + TraitImpl(AssocDef), + Trait, +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, Serialize)] +pub enum ImplTraitInTraitData { + Trait { fn_def_id: FnDef, opaque_def_id: OpaqueDef }, + Impl { fn_def_id: FnDef }, +} + +impl AssocItem { + pub fn is_impl_trait_in_trait(&self) -> bool { + matches!(self.kind, AssocKind::Type { data: AssocTypeData::Rpitit(_) }) + } +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub enum VtblEntry { + /// destructor of this type (used in vtable header) + MetadataDropInPlace, + /// layout size of this type (used in vtable header) + MetadataSize, + /// layout align of this type (used in vtable header) + MetadataAlign, + /// non-dispatchable associated function that is excluded from trait object + Vacant, + /// dispatchable associated function + Method(Instance), + /// pointer to a separate supertrait vtable, can be used by trait upcasting coercion + TraitVPtr(TraitRef), +} From a184a6dd42695cef3bd594fe33f6e0230e2a5dd3 Mon Sep 17 00:00:00 2001 From: Roland Xu Date: Tue, 11 Aug 2026 23:19:42 +0800 Subject: [PATCH 02/31] Remove unused #[non_exhaustive] in library --- library/core/src/escape.rs | 2 -- library/core/src/mem/type_info.rs | 1 - library/proc_macro/src/lib.rs | 1 - library/std/src/sys/process/unsupported.rs | 1 - 4 files changed, 5 deletions(-) diff --git a/library/core/src/escape.rs b/library/core/src/escape.rs index f459c58270818..940daeee7184c 100644 --- a/library/core/src/escape.rs +++ b/library/core/src/escape.rs @@ -167,13 +167,11 @@ union MaybeEscapedCharacter { /// Marker type to indicate that the character is always escaped, /// used to optimize the iterator implementation. #[derive(Clone, Copy)] -#[non_exhaustive] pub(crate) struct AlwaysEscaped; /// Marker type to indicate that the character may be escaped, /// used to optimize the iterator implementation. #[derive(Clone, Copy)] -#[non_exhaustive] pub(crate) struct MaybeEscaped; /// An iterator over a possibly escaped character. diff --git a/library/core/src/mem/type_info.rs b/library/core/src/mem/type_info.rs index 66f85dab7b6c9..51ad9bfed050e 100644 --- a/library/core/src/mem/type_info.rs +++ b/library/core/src/mem/type_info.rs @@ -20,7 +20,6 @@ pub struct Type { /// Info of a trait implementation, you can retrieve the vtable with [Self::get_vtable] #[derive(Debug, PartialEq, Eq)] #[unstable(feature = "type_info", issue = "146922")] -#[non_exhaustive] pub struct TraitImpl { pub(crate) vtable: DynMetadata, } diff --git a/library/proc_macro/src/lib.rs b/library/proc_macro/src/lib.rs index 2cba4b52fc276..2f026fb81ee1b 100644 --- a/library/proc_macro/src/lib.rs +++ b/library/proc_macro/src/lib.rs @@ -245,7 +245,6 @@ impl !Sync for TokenStream {} /// The contained error message is explicitly not guaranteed to be stable in any way, /// and may change between Rust versions or across compilations. #[stable(feature = "proc_macro_lib", since = "1.15.0")] -#[non_exhaustive] #[derive(Debug)] pub struct LexError(String); diff --git a/library/std/src/sys/process/unsupported.rs b/library/std/src/sys/process/unsupported.rs index 114f0001b7faf..f7d7f489ec079 100644 --- a/library/std/src/sys/process/unsupported.rs +++ b/library/std/src/sys/process/unsupported.rs @@ -199,7 +199,6 @@ impl fmt::Debug for Command { } #[derive(PartialEq, Eq, Clone, Copy, Debug, Default)] -#[non_exhaustive] pub struct ExitStatus(); impl ExitStatus { From 31e436d3a51a41d2fba5e997dee590fc66212318 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 11 Mar 2025 14:54:18 +0100 Subject: [PATCH 03/31] Add new rustdoc `broken_footnote` lint --- src/librustdoc/lint.rs | 8 +++ src/librustdoc/passes/lint.rs | 2 + src/librustdoc/passes/lint/footnotes.rs | 71 +++++++++++++++++++++++++ 3 files changed, 81 insertions(+) create mode 100644 src/librustdoc/passes/lint/footnotes.rs diff --git a/src/librustdoc/lint.rs b/src/librustdoc/lint.rs index 91f92b799889b..a5ea989d7b8e9 100644 --- a/src/librustdoc/lint.rs +++ b/src/librustdoc/lint.rs @@ -196,6 +196,13 @@ declare_rustdoc_lint! { "detects redundant explicit links in doc comments" } +declare_rustdoc_lint! { + /// This lint checks for uses of footnote references without definition. + BROKEN_FOOTNOTE, + Warn, + "footnote reference with no associated definition" +} + pub(crate) static RUSTDOC_LINTS: Lazy> = Lazy::new(|| { vec![ BROKEN_INTRA_DOC_LINKS, @@ -209,6 +216,7 @@ pub(crate) static RUSTDOC_LINTS: Lazy> = Lazy::new(|| { MISSING_CRATE_LEVEL_DOCS, UNESCAPED_BACKTICKS, REDUNDANT_EXPLICIT_LINKS, + BROKEN_FOOTNOTE, ] }); diff --git a/src/librustdoc/passes/lint.rs b/src/librustdoc/passes/lint.rs index 7740d14148bf0..bb952b32393cf 100644 --- a/src/librustdoc/passes/lint.rs +++ b/src/librustdoc/passes/lint.rs @@ -3,6 +3,7 @@ mod bare_urls; mod check_code_block_syntax; +mod footnotes; mod html_tags; mod redundant_explicit_links; mod unescaped_backticks; @@ -41,6 +42,7 @@ impl DocVisitor<'_> for Linter<'_, '_> { if may_have_link { bare_urls::visit_item(self.cx, item, hir_id, &dox); redundant_explicit_links::visit_item(self.cx, item, hir_id); + footnotes::visit_item(self.cx, item, hir_id, &dox); } if may_have_code { check_code_block_syntax::visit_item(self.cx, item, &dox); diff --git a/src/librustdoc/passes/lint/footnotes.rs b/src/librustdoc/passes/lint/footnotes.rs new file mode 100644 index 0000000000000..2c1b42170cba7 --- /dev/null +++ b/src/librustdoc/passes/lint/footnotes.rs @@ -0,0 +1,71 @@ +//! Detects specific markdown syntax that's different between pulldown-cmark +//! 0.9 and 0.11. +//! +//! This is a mitigation for old parser bugs that affected some +//! real crates' docs. The old parser claimed to comply with CommonMark, +//! but it did not. These warnings will eventually be removed, +//! though some of them may become Clippy lints. +//! +//! +//! +//! + +use std::ops::Range; + +use rustc_data_structures::fx::FxHashSet; +use rustc_hir::HirId; +use rustc_lint_defs::Applicability; +use rustc_resolve::rustdoc::pulldown_cmark::{Event, Options, Parser}; +use rustc_resolve::rustdoc::source_span_for_markdown_range; + +use crate::clean::Item; +use crate::core::DocContext; + +pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item, hir_id: HirId, dox: &str) { + let tcx = cx.tcx; + + let mut missing_footnote_references = FxHashSet::default(); + + let options = Options::ENABLE_FOOTNOTES; + let mut parser = Parser::new_ext(dox, options).into_offset_iter().peekable(); + while let Some((event, span)) = parser.next() { + match event { + Event::Text(text) + if &*text == "[" + && let Some((Event::Text(text), _)) = parser.peek() + && text.trim_start().starts_with('^') + && parser.next().is_some() + && let Some((Event::Text(text), end_span)) = parser.peek() + && &**text == "]" => + { + missing_footnote_references.insert(Range { start: span.start, end: end_span.end }); + } + _ => {} + } + } + + #[allow(rustc::potential_query_instability)] + for span in missing_footnote_references { + let (ref_span, precise) = + source_span_for_markdown_range(tcx, dox, &span, &item.attrs.doc_strings) + .map(|(span, _)| (span, true)) + .unwrap_or_else(|| (item.attr_span(tcx), false)); + + if precise { + tcx.emit_node_span_lint( + crate::lint::BROKEN_FOOTNOTE, + hir_id, + ref_span, + rustc_errors::DiagDecorator(|lint| { + lint.primary_message("no footnote definition matching this footnote"); + lint.span_suggestion( + ref_span.shrink_to_lo(), + "if it should not be a footnote, escape it", + "\\", + Applicability::MaybeIncorrect, + ); + }), + ); + } + } +} From 37b8c5389e6a826e901b6dd2714d29d233ba99c7 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 11 Mar 2025 14:58:06 +0100 Subject: [PATCH 04/31] Add ui test for rustdoc `broken_footnote` lint --- tests/rustdoc-ui/lints/broken-footnote.rs | 7 ++++++ tests/rustdoc-ui/lints/broken-footnote.stderr | 24 +++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 tests/rustdoc-ui/lints/broken-footnote.rs create mode 100644 tests/rustdoc-ui/lints/broken-footnote.stderr diff --git a/tests/rustdoc-ui/lints/broken-footnote.rs b/tests/rustdoc-ui/lints/broken-footnote.rs new file mode 100644 index 0000000000000..ef030d0e14999 --- /dev/null +++ b/tests/rustdoc-ui/lints/broken-footnote.rs @@ -0,0 +1,7 @@ +#![deny(rustdoc::broken_footnote)] + +//! Footnote referenced [^1]. And [^2]. And [^bla]. +//! +//! [^1]: footnote defined +//~^^^ ERROR: no footnote definition matching this footnote +//~| ERROR: no footnote definition matching this footnote diff --git a/tests/rustdoc-ui/lints/broken-footnote.stderr b/tests/rustdoc-ui/lints/broken-footnote.stderr new file mode 100644 index 0000000000000..0d63ab8f01513 --- /dev/null +++ b/tests/rustdoc-ui/lints/broken-footnote.stderr @@ -0,0 +1,24 @@ +error: no footnote definition matching this footnote + --> $DIR/broken-footnote.rs:3:45 + | +LL | //! Footnote referenced [^1]. And [^2]. And [^bla]. + | -^^^^^ + | | + | help: if it should not be a footnote, escape it: `\` + | +note: the lint level is defined here + --> $DIR/broken-footnote.rs:1:9 + | +LL | #![deny(rustdoc::broken_footnote)] + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: no footnote definition matching this footnote + --> $DIR/broken-footnote.rs:3:35 + | +LL | //! Footnote referenced [^1]. And [^2]. And [^bla]. + | -^^^ + | | + | help: if it should not be a footnote, escape it: `\` + +error: aborting due to 2 previous errors + From c74e84a5667ff7d61743131487b896f411904568 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 11 Mar 2025 15:10:40 +0100 Subject: [PATCH 05/31] Add new `unused_footnote_definition` rustdoc lint --- src/librustdoc/lint.rs | 8 ++++++ src/librustdoc/passes/lint/footnotes.rs | 37 +++++++++++++++++++++++-- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/src/librustdoc/lint.rs b/src/librustdoc/lint.rs index a5ea989d7b8e9..0a50056f52799 100644 --- a/src/librustdoc/lint.rs +++ b/src/librustdoc/lint.rs @@ -203,6 +203,13 @@ declare_rustdoc_lint! { "footnote reference with no associated definition" } +declare_rustdoc_lint! { + /// This lint checks if all footnote definitions are used. + UNUSED_FOOTNOTE_DEFINITION, + Warn, + "unused footnote definition" +} + pub(crate) static RUSTDOC_LINTS: Lazy> = Lazy::new(|| { vec![ BROKEN_INTRA_DOC_LINKS, @@ -217,6 +224,7 @@ pub(crate) static RUSTDOC_LINTS: Lazy> = Lazy::new(|| { UNESCAPED_BACKTICKS, REDUNDANT_EXPLICIT_LINKS, BROKEN_FOOTNOTE, + UNUSED_FOOTNOTE_DEFINITION, ] }); diff --git a/src/librustdoc/passes/lint/footnotes.rs b/src/librustdoc/passes/lint/footnotes.rs index 2c1b42170cba7..42842574034fd 100644 --- a/src/librustdoc/passes/lint/footnotes.rs +++ b/src/librustdoc/passes/lint/footnotes.rs @@ -12,10 +12,11 @@ use std::ops::Range; -use rustc_data_structures::fx::FxHashSet; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_errors::DiagDecorator; use rustc_hir::HirId; use rustc_lint_defs::Applicability; -use rustc_resolve::rustdoc::pulldown_cmark::{Event, Options, Parser}; +use rustc_resolve::rustdoc::pulldown_cmark::{Event, Options, Parser, Tag}; use rustc_resolve::rustdoc::source_span_for_markdown_range; use crate::clean::Item; @@ -25,6 +26,8 @@ pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item, hir_id: HirId, dox: & let tcx = cx.tcx; let mut missing_footnote_references = FxHashSet::default(); + let mut footnote_references = FxHashSet::default(); + let mut footnote_definitions = FxHashMap::default(); let options = Options::ENABLE_FOOTNOTES; let mut parser = Parser::new_ext(dox, options).into_offset_iter().peekable(); @@ -40,10 +43,38 @@ pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item, hir_id: HirId, dox: & { missing_footnote_references.insert(Range { start: span.start, end: end_span.end }); } + Event::FootnoteReference(label) => { + footnote_references.insert(label); + } + Event::Start(Tag::FootnoteDefinition(label)) => { + footnote_definitions.insert(label, span.start + 1); + } _ => {} } } + #[allow(rustc::potential_query_instability)] + for (footnote, span) in footnote_definitions { + if !footnote_references.contains(&footnote) { + let (span, _) = source_span_for_markdown_range( + tcx, + dox, + &(span..span + 1), + &item.attrs.doc_strings, + ) + .unwrap_or_else(|| (item.attr_span(tcx), false)); + + tcx.emit_node_span_lint( + crate::lint::UNUSED_FOOTNOTE_DEFINITION, + hir_id, + span, + DiagDecorator(|lint| { + lint.primary_message("unused footnote definition"); + }), + ); + } + } + #[allow(rustc::potential_query_instability)] for span in missing_footnote_references { let (ref_span, precise) = @@ -56,7 +87,7 @@ pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item, hir_id: HirId, dox: & crate::lint::BROKEN_FOOTNOTE, hir_id, ref_span, - rustc_errors::DiagDecorator(|lint| { + DiagDecorator(|lint| { lint.primary_message("no footnote definition matching this footnote"); lint.span_suggestion( ref_span.shrink_to_lo(), From 9f30b845d053a8466796c0c817f4399b7bd70dbc Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 11 Mar 2025 15:10:54 +0100 Subject: [PATCH 06/31] Add ui test for new `unused_footnote_definition` rustdoc lint --- tests/rustdoc-ui/lints/unused-footnote.rs | 9 +++++++++ tests/rustdoc-ui/lints/unused-footnote.stderr | 14 ++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 tests/rustdoc-ui/lints/unused-footnote.rs create mode 100644 tests/rustdoc-ui/lints/unused-footnote.stderr diff --git a/tests/rustdoc-ui/lints/unused-footnote.rs b/tests/rustdoc-ui/lints/unused-footnote.rs new file mode 100644 index 0000000000000..d144b42d30fb2 --- /dev/null +++ b/tests/rustdoc-ui/lints/unused-footnote.rs @@ -0,0 +1,9 @@ +// This test ensures that the rustdoc `unused_footnote` is working as expected. + +#![deny(rustdoc::unused_footnote_definition)] + +//! Footnote referenced. [^2] +//! +//! [^1]: footnote defined +//! [^2]: footnote defined +//~^^ ERROR: unused_footnote_definition diff --git a/tests/rustdoc-ui/lints/unused-footnote.stderr b/tests/rustdoc-ui/lints/unused-footnote.stderr new file mode 100644 index 0000000000000..d227cef181df3 --- /dev/null +++ b/tests/rustdoc-ui/lints/unused-footnote.stderr @@ -0,0 +1,14 @@ +error: unused footnote definition + --> $DIR/unused-footnote.rs:7:6 + | +LL | //! [^1]: footnote defined + | ^ + | +note: the lint level is defined here + --> $DIR/unused-footnote.rs:3:9 + | +LL | #![deny(rustdoc::unused_footnote_definition)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + From 261a21353e95dfa7991cc70ba40b20efdf848b48 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 26 Jan 2026 12:05:56 +0100 Subject: [PATCH 07/31] Improve description of new rustdoc lints --- src/librustdoc/lint.rs | 4 +-- src/librustdoc/passes/lint/footnotes.rs | 37 +++++++++++------------ tests/rustdoc-ui/lints/unused-footnote.rs | 2 +- 3 files changed, 20 insertions(+), 23 deletions(-) diff --git a/src/librustdoc/lint.rs b/src/librustdoc/lint.rs index 0a50056f52799..f3052dd9ead82 100644 --- a/src/librustdoc/lint.rs +++ b/src/librustdoc/lint.rs @@ -200,14 +200,14 @@ declare_rustdoc_lint! { /// This lint checks for uses of footnote references without definition. BROKEN_FOOTNOTE, Warn, - "footnote reference with no associated definition" + "detects footnote references with no associated definition" } declare_rustdoc_lint! { /// This lint checks if all footnote definitions are used. UNUSED_FOOTNOTE_DEFINITION, Warn, - "unused footnote definition" + "detects unused footnote definitions" } pub(crate) static RUSTDOC_LINTS: Lazy> = Lazy::new(|| { diff --git a/src/librustdoc/passes/lint/footnotes.rs b/src/librustdoc/passes/lint/footnotes.rs index 42842574034fd..3b4ca28b24487 100644 --- a/src/librustdoc/passes/lint/footnotes.rs +++ b/src/librustdoc/passes/lint/footnotes.rs @@ -77,26 +77,23 @@ pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item, hir_id: HirId, dox: & #[allow(rustc::potential_query_instability)] for span in missing_footnote_references { - let (ref_span, precise) = - source_span_for_markdown_range(tcx, dox, &span, &item.attrs.doc_strings) - .map(|(span, _)| (span, true)) - .unwrap_or_else(|| (item.attr_span(tcx), false)); + let ref_span = source_span_for_markdown_range(tcx, dox, &span, &item.attrs.doc_strings) + .map(|(span, _)| span) + .unwrap_or_else(|| item.attr_span(tcx)); - if precise { - tcx.emit_node_span_lint( - crate::lint::BROKEN_FOOTNOTE, - hir_id, - ref_span, - DiagDecorator(|lint| { - lint.primary_message("no footnote definition matching this footnote"); - lint.span_suggestion( - ref_span.shrink_to_lo(), - "if it should not be a footnote, escape it", - "\\", - Applicability::MaybeIncorrect, - ); - }), - ); - } + tcx.emit_node_span_lint( + crate::lint::BROKEN_FOOTNOTE, + hir_id, + ref_span, + DiagDecorator(|lint| { + lint.primary_message("no footnote definition matching this footnote"); + lint.span_suggestion( + ref_span.shrink_to_lo(), + "if it should not be a footnote, escape it", + "\\", + Applicability::MaybeIncorrect, + ); + }), + ); } } diff --git a/tests/rustdoc-ui/lints/unused-footnote.rs b/tests/rustdoc-ui/lints/unused-footnote.rs index d144b42d30fb2..a71e20ff6d500 100644 --- a/tests/rustdoc-ui/lints/unused-footnote.rs +++ b/tests/rustdoc-ui/lints/unused-footnote.rs @@ -1,4 +1,4 @@ -// This test ensures that the rustdoc `unused_footnote` is working as expected. +// This test ensures that the `rustdoc::unused_footnote` lint is working as expected. #![deny(rustdoc::unused_footnote_definition)] From 4420714ad170293091fa6fbe1c718884752a8508 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 25 Feb 2026 16:45:41 +0100 Subject: [PATCH 08/31] Remove outdated comment --- src/librustdoc/passes/lint/footnotes.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/librustdoc/passes/lint/footnotes.rs b/src/librustdoc/passes/lint/footnotes.rs index 3b4ca28b24487..7c975cf4fed08 100644 --- a/src/librustdoc/passes/lint/footnotes.rs +++ b/src/librustdoc/passes/lint/footnotes.rs @@ -1,15 +1,3 @@ -//! Detects specific markdown syntax that's different between pulldown-cmark -//! 0.9 and 0.11. -//! -//! This is a mitigation for old parser bugs that affected some -//! real crates' docs. The old parser claimed to comply with CommonMark, -//! but it did not. These warnings will eventually be removed, -//! though some of them may become Clippy lints. -//! -//! -//! -//! - use std::ops::Range; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; From 92dcc5b490eb155abf3a700e33c4e5034bc73e2c Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 12 Mar 2026 21:04:55 +0100 Subject: [PATCH 09/31] Add extra "broken_footnote" lint ui test --- tests/rustdoc-ui/lints/broken-footnote.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/rustdoc-ui/lints/broken-footnote.rs b/tests/rustdoc-ui/lints/broken-footnote.rs index ef030d0e14999..83b492148ca84 100644 --- a/tests/rustdoc-ui/lints/broken-footnote.rs +++ b/tests/rustdoc-ui/lints/broken-footnote.rs @@ -5,3 +5,12 @@ //! [^1]: footnote defined //~^^^ ERROR: no footnote definition matching this footnote //~| ERROR: no footnote definition matching this footnote + +// Should not lint. +//! foo[^1] +//! +//! ``` +//! +//! [^1]: bar +//! +//! ``` From 7253b43d6d4f30b2ad8f011799c763f80a6d40e6 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Fri, 26 Jun 2026 23:26:51 -0700 Subject: [PATCH 10/31] Fix backslashes and line breaks in footnote lint --- src/librustdoc/passes/lint/footnotes.rs | 42 ++++++++++++++++--- tests/rustdoc-ui/lints/broken-footnote.rs | 28 +++++++++++++ tests/rustdoc-ui/lints/broken-footnote.stderr | 26 +++++++++++- 3 files changed, 89 insertions(+), 7 deletions(-) diff --git a/src/librustdoc/passes/lint/footnotes.rs b/src/librustdoc/passes/lint/footnotes.rs index 7c975cf4fed08..e9b78b534d9c6 100644 --- a/src/librustdoc/passes/lint/footnotes.rs +++ b/src/librustdoc/passes/lint/footnotes.rs @@ -23,13 +23,11 @@ pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item, hir_id: HirId, dox: & match event { Event::Text(text) if &*text == "[" - && let Some((Event::Text(text), _)) = parser.peek() - && text.trim_start().starts_with('^') - && parser.next().is_some() - && let Some((Event::Text(text), end_span)) = parser.peek() - && &**text == "]" => + && (span.start == 0 || dox.as_bytes().get(span.start - 1) != Some(&b'\\')) + && let Some(len) = scan_footnote_ref(&dox[span.start..]) => { - missing_footnote_references.insert(Range { start: span.start, end: end_span.end }); + missing_footnote_references + .insert(Range { start: span.start, end: span.start + len }); } Event::FootnoteReference(label) => { footnote_references.insert(label); @@ -85,3 +83,35 @@ pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item, hir_id: HirId, dox: & ); } } + +fn scan_footnote_ref(dox: &str) -> Option { + let dox = dox.as_bytes(); + let mut i = 0; + if dox.get(i) != Some(&b'[') { + return None; + } + i += 1; + if dox.get(i) != Some(&b'^') { + return None; + } + i += 1; + while let Some(&c) = dox.get(i) { + if c == b']' { + i += 1; + return Some(i); + } + if c == b'\r' || c == b'\n' || c == b'[' { + // Can't nest things like this. + break; + } + if c == b'\\' { + i += 1; + } + if dox.get(i) == Some(&b'\r') || dox.get(i) == Some(&b'\n') { + // Can't have line breaks in footnote refs + break; + } + i += 1; + } + None +} diff --git a/tests/rustdoc-ui/lints/broken-footnote.rs b/tests/rustdoc-ui/lints/broken-footnote.rs index 83b492148ca84..63314fd98f075 100644 --- a/tests/rustdoc-ui/lints/broken-footnote.rs +++ b/tests/rustdoc-ui/lints/broken-footnote.rs @@ -14,3 +14,31 @@ //! [^1]: bar //! //! ``` + +// Edge cases from https://pulldown-cmark.github.io/pulldown-cmark/specs/footnotes.html +/// The following are not footnote references: +/// +/// \[^a] +/// +/// [\^b] +/// +/// [^c\] +/// +/// [^d +/// e] +/// +/// [^f\ +/// g] +pub struct NotReferences; + +/// The following are not footnote references: +/// +/// [^a b] +//~^ ERROR: no footnote definition matching this footnote +/// +/// [^1\.2] +//~^ ERROR: no footnote definition matching this footnote +/// +/// [^*] +//~^ ERROR: no footnote definition matching this footnote +pub struct EdgeCases; diff --git a/tests/rustdoc-ui/lints/broken-footnote.stderr b/tests/rustdoc-ui/lints/broken-footnote.stderr index 0d63ab8f01513..4bf1ce59d3f83 100644 --- a/tests/rustdoc-ui/lints/broken-footnote.stderr +++ b/tests/rustdoc-ui/lints/broken-footnote.stderr @@ -20,5 +20,29 @@ LL | //! Footnote referenced [^1]. And [^2]. And [^bla]. | | | help: if it should not be a footnote, escape it: `\` -error: aborting due to 2 previous errors +error: no footnote definition matching this footnote + --> $DIR/broken-footnote.rs:39:5 + | +LL | /// [^1\.2] + | -^^^^^^ + | | + | help: if it should not be a footnote, escape it: `\` + +error: no footnote definition matching this footnote + --> $DIR/broken-footnote.rs:42:5 + | +LL | /// [^*] + | -^^^ + | | + | help: if it should not be a footnote, escape it: `\` + +error: no footnote definition matching this footnote + --> $DIR/broken-footnote.rs:36:5 + | +LL | /// [^a b] + | -^^^^^ + | | + | help: if it should not be a footnote, escape it: `\` + +error: aborting due to 5 previous errors From b9e9138428b84660c34ddd04b4dfbe90f2c7c8bd Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Wed, 12 Aug 2026 12:59:52 +0200 Subject: [PATCH 11/31] move resolve_path to Session inherent method --- Cargo.lock | 1 - .../rustc_builtin_macros/src/source_util.rs | 6 +-- compiler/rustc_expand/src/base.rs | 35 +---------------- compiler/rustc_expand/src/diagnostics.rs | 8 ---- compiler/rustc_passes/Cargo.toml | 1 - .../rustc_passes/src/debugger_visualizer.rs | 3 +- compiler/rustc_session/src/diagnostics.rs | 8 ++++ compiler/rustc_session/src/session.rs | 38 ++++++++++++++++++- 8 files changed, 50 insertions(+), 50 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ce3fd04faf3bb..11770eb8d0cd5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4599,7 +4599,6 @@ dependencies = [ "rustc_crate_store", "rustc_data_structures", "rustc_errors", - "rustc_expand", "rustc_feature", "rustc_hir", "rustc_index", diff --git a/compiler/rustc_builtin_macros/src/source_util.rs b/compiler/rustc_builtin_macros/src/source_util.rs index fe2b5e1a45920..d327439ec6c83 100644 --- a/compiler/rustc_builtin_macros/src/source_util.rs +++ b/compiler/rustc_builtin_macros/src/source_util.rs @@ -9,7 +9,7 @@ use rustc_ast::tokenstream::TokenStream; use rustc_ast::{join_path_idents, token}; use rustc_ast_pretty::pprust; use rustc_expand::base::{ - DummyResult, ExpandResult, ExtCtxt, MacEager, MacResult, MacroExpanderResult, resolve_path, + DummyResult, ExpandResult, ExtCtxt, MacEager, MacResult, MacroExpanderResult, }; use rustc_expand::module::DirOwnership; use rustc_parse::lexer::StripTokens; @@ -117,7 +117,7 @@ pub(crate) fn expand_include<'cx>( Err(guar) => return ExpandResult::Ready(DummyResult::any(sp, guar)), }; // The file will be added to the code map by the parser - let path = match resolve_path(&cx.sess, path.as_str(), sp) { + let path = match cx.sess.resolve_path(path.as_str(), sp) { Ok(path) => path, Err(err) => { let guar = err.emit(); @@ -267,7 +267,7 @@ fn load_binary_file( macro_span: Span, path_span: Span, ) -> Result<(Arc<[u8]>, Span), Box> { - let resolved_path = match resolve_path(&cx.sess, original_path, macro_span) { + let resolved_path = match cx.sess.resolve_path(original_path, macro_span) { Ok(path) => path, Err(err) => { let guar = err.emit(); diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs index 2579be47deb61..64b6e4b8ef498 100644 --- a/compiler/rustc_expand/src/base.rs +++ b/compiler/rustc_expand/src/base.rs @@ -1,7 +1,6 @@ use std::any::Any; use std::default::Default; use std::iter; -use std::path::Component::Prefix; use std::path::PathBuf; use std::rc::Rc; use std::sync::Arc; @@ -15,7 +14,7 @@ use rustc_attr_ir::{ }; use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; use rustc_data_structures::{Limit, sync}; -use rustc_errors::{BufferedEarlyLint, DiagCtxtHandle, ErrorGuaranteed, PResult}; +use rustc_errors::{BufferedEarlyLint, DiagCtxtHandle, ErrorGuaranteed}; use rustc_feature::Features; use rustc_hir::def::MacroKinds; use rustc_lint_defs::RegisteredTools; @@ -1341,35 +1340,3 @@ impl<'a> ExtCtxt<'a> { self.resolver.check_unused_macros(); } } - -/// Resolves a `path` mentioned inside Rust code, returning an absolute path. -/// -/// This unifies the logic used for resolving `include_X!`. -pub fn resolve_path(sess: &Session, path: impl Into, span: Span) -> PResult<'_, PathBuf> { - let path = path.into(); - - // Relative paths are resolved relative to the file in which they are found - // after macro expansion (that is, they are unhygienic). - if !path.is_absolute() { - let callsite = span.source_callsite(); - let source_map = sess.source_map(); - let Some(mut base_path) = source_map.span_to_filename(callsite).into_local_path() else { - return Err(sess.dcx().create_err(diagnostics::ResolveRelativePath { - span, - path: source_map - .filename_for_diagnostics(&source_map.span_to_filename(callsite)) - .to_string(), - })); - }; - base_path.pop(); - base_path.push(path); - Ok(base_path) - } else { - // This ensures that Windows verbatim paths are fixed if mixed path separators are used, - // which can happen when `concat!` is used to join paths. - match path.components().next() { - Some(Prefix(prefix)) if prefix.kind().is_verbatim() => Ok(path.components().collect()), - _ => Ok(path), - } - } -} diff --git a/compiler/rustc_expand/src/diagnostics.rs b/compiler/rustc_expand/src/diagnostics.rs index 5d2066ef88773..7fd3cf7c43fa9 100644 --- a/compiler/rustc_expand/src/diagnostics.rs +++ b/compiler/rustc_expand/src/diagnostics.rs @@ -124,14 +124,6 @@ pub(crate) struct UnknownMacroVariable { pub name: MacroRulesNormalizedIdent, } -#[derive(Diagnostic)] -#[diag("cannot resolve relative path in non-file source `{$path}`")] -pub(crate) struct ResolveRelativePath { - #[primary_span] - pub span: Span, - pub path: String, -} - #[derive(Diagnostic)] #[diag("macros cannot have body stability attributes")] pub(crate) struct MacroBodyStability { diff --git a/compiler/rustc_passes/Cargo.toml b/compiler/rustc_passes/Cargo.toml index 7319aadf29ae2..759ad330d5700 100644 --- a/compiler/rustc_passes/Cargo.toml +++ b/compiler/rustc_passes/Cargo.toml @@ -12,7 +12,6 @@ rustc_attr_parsing = { path = "../rustc_attr_parsing" } rustc_crate_store = { path = "../rustc_crate_store" } rustc_data_structures = { path = "../rustc_data_structures" } rustc_errors = { path = "../rustc_errors" } -rustc_expand = { path = "../rustc_expand" } rustc_feature = { path = "../rustc_feature" } rustc_hir = { path = "../rustc_hir" } rustc_index = { path = "../rustc_index" } diff --git a/compiler/rustc_passes/src/debugger_visualizer.rs b/compiler/rustc_passes/src/debugger_visualizer.rs index 459e6da7d961b..91e7c69c2efdb 100644 --- a/compiler/rustc_passes/src/debugger_visualizer.rs +++ b/compiler/rustc_passes/src/debugger_visualizer.rs @@ -2,7 +2,6 @@ use rustc_ast::{ItemKind, ast}; use rustc_attr_parsing::AttributeParser; -use rustc_expand::base::resolve_path; use rustc_hir::Attribute; use rustc_hir::attrs::{AttributeKind, DebugVisualizer}; use rustc_middle::middle::debugger_visualizer::DebuggerVisualizerFile; @@ -19,7 +18,7 @@ impl DebuggerVisualizerCollector<'_> { AttributeParser::parse_limited_sym(&self.sess, attrs, &[sym::debugger_visualizer]) { for DebugVisualizer { span, visualizer_type, path } in visualizers { - let file = match resolve_path(&self.sess, path.as_str(), span) { + let file = match self.sess.resolve_path(path.as_str(), span) { Ok(file) => file, Err(err) => { err.emit(); diff --git a/compiler/rustc_session/src/diagnostics.rs b/compiler/rustc_session/src/diagnostics.rs index c229adf5aef4d..e8f29d8a9ee77 100644 --- a/compiler/rustc_session/src/diagnostics.rs +++ b/compiler/rustc_session/src/diagnostics.rs @@ -721,3 +721,11 @@ pub(crate) struct NativeTargetCpuNotAllowed<'a> { pub(crate) target_triple: &'a TargetTuple, pub(crate) need_explicit_cpu: bool, } + +#[derive(Diagnostic)] +#[diag("cannot resolve relative path in non-file source `{$path}`")] +pub(crate) struct ResolveRelativePath { + #[primary_span] + pub span: Span, + pub path: String, +} diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index f30d825b470ac..584c2b1c161e0 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -1,4 +1,5 @@ use std::any::Any; +use std::path::Component::Prefix; use std::path::PathBuf; use std::str::FromStr; use std::sync::Arc; @@ -15,7 +16,7 @@ use rustc_errors::emitter::{DynEmitter, HumanReadableErrorType, OutputTheme, std use rustc_errors::json::JsonEmitter; use rustc_errors::timings::TimingSectionHandler; use rustc_errors::{ - Diag, DiagCtxt, DiagCtxtHandle, DiagMessage, Diagnostic, ErrorGuaranteed, FatalAbort, + Diag, DiagCtxt, DiagCtxtHandle, DiagMessage, Diagnostic, ErrorGuaranteed, FatalAbort, PResult, TerminalUrl, }; use rustc_feature::UnstableFeatures; @@ -772,6 +773,41 @@ impl Session { None => Box::new(std::iter::empty()), } } + + /// Resolves a `path` mentioned inside Rust code, returning an absolute path. + /// + /// This unifies the logic used for resolving `include_X!`. + pub fn resolve_path(&self, path: impl Into, span: Span) -> PResult<'_, PathBuf> { + let path = path.into(); + + // Relative paths are resolved relative to the file in which they are found + // after macro expansion (that is, they are unhygienic). + if !path.is_absolute() { + let callsite = span.source_callsite(); + let source_map = self.source_map(); + let Some(mut base_path) = source_map.span_to_filename(callsite).into_local_path() + else { + return Err(self.dcx().create_err(diagnostics::ResolveRelativePath { + span, + path: source_map + .filename_for_diagnostics(&source_map.span_to_filename(callsite)) + .to_string(), + })); + }; + base_path.pop(); + base_path.push(path); + Ok(base_path) + } else { + // This ensures that Windows verbatim paths are fixed if mixed path separators are used, + // which can happen when `concat!` is used to join paths. + match path.components().next() { + Some(Prefix(prefix)) if prefix.kind().is_verbatim() => { + Ok(path.components().collect()) + } + _ => Ok(path), + } + } + } } // JUSTIFICATION: defn of the suggested wrapper fns From 9c162cbbff75c30c0736913dedbf44b3f8f2d462 Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Wed, 12 Aug 2026 13:03:59 +0200 Subject: [PATCH 12/31] update comment --- compiler/rustc_session/src/session.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index 584c2b1c161e0..7db30b38f9be4 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -776,7 +776,7 @@ impl Session { /// Resolves a `path` mentioned inside Rust code, returning an absolute path. /// - /// This unifies the logic used for resolving `include_X!`. + /// This unifies the logic used for resolving `include_*!` and debugger visualizers. pub fn resolve_path(&self, path: impl Into, span: Span) -> PResult<'_, PathBuf> { let path = path.into(); From e73699643d6ee64854cad8e48d1d7678da509b5b Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 12 Aug 2026 14:43:00 +0200 Subject: [PATCH 13/31] Split doc comment so lints are always emitted in the right order --- tests/rustdoc-ui/lints/broken-footnote.rs | 8 +++++++- tests/rustdoc-ui/lints/broken-footnote.stderr | 16 ++++++++-------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/tests/rustdoc-ui/lints/broken-footnote.rs b/tests/rustdoc-ui/lints/broken-footnote.rs index 63314fd98f075..ab386314aec3e 100644 --- a/tests/rustdoc-ui/lints/broken-footnote.rs +++ b/tests/rustdoc-ui/lints/broken-footnote.rs @@ -35,10 +35,16 @@ pub struct NotReferences; /// /// [^a b] //~^ ERROR: no footnote definition matching this footnote +pub struct EdgeCases1; + +/// Another: /// /// [^1\.2] //~^ ERROR: no footnote definition matching this footnote +pub struct EdgeCases2; + +/// Last: /// /// [^*] //~^ ERROR: no footnote definition matching this footnote -pub struct EdgeCases; +pub struct EdgeCases3; diff --git a/tests/rustdoc-ui/lints/broken-footnote.stderr b/tests/rustdoc-ui/lints/broken-footnote.stderr index 4bf1ce59d3f83..26f4c164862ec 100644 --- a/tests/rustdoc-ui/lints/broken-footnote.stderr +++ b/tests/rustdoc-ui/lints/broken-footnote.stderr @@ -21,26 +21,26 @@ LL | //! Footnote referenced [^1]. And [^2]. And [^bla]. | help: if it should not be a footnote, escape it: `\` error: no footnote definition matching this footnote - --> $DIR/broken-footnote.rs:39:5 + --> $DIR/broken-footnote.rs:36:5 | -LL | /// [^1\.2] - | -^^^^^^ +LL | /// [^a b] + | -^^^^^ | | | help: if it should not be a footnote, escape it: `\` error: no footnote definition matching this footnote --> $DIR/broken-footnote.rs:42:5 | -LL | /// [^*] - | -^^^ +LL | /// [^1\.2] + | -^^^^^^ | | | help: if it should not be a footnote, escape it: `\` error: no footnote definition matching this footnote - --> $DIR/broken-footnote.rs:36:5 + --> $DIR/broken-footnote.rs:48:5 | -LL | /// [^a b] - | -^^^^^ +LL | /// [^*] + | -^^^ | | | help: if it should not be a footnote, escape it: `\` From 116fdd183af5d8240b24a14eee0b02c805b68720 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 12 Aug 2026 14:59:31 +0200 Subject: [PATCH 14/31] Fix tidy error --- tests/rustdoc-ui/lints/broken-footnote.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/rustdoc-ui/lints/broken-footnote.rs b/tests/rustdoc-ui/lints/broken-footnote.rs index ab386314aec3e..8a9a78f9cff45 100644 --- a/tests/rustdoc-ui/lints/broken-footnote.rs +++ b/tests/rustdoc-ui/lints/broken-footnote.rs @@ -47,4 +47,4 @@ pub struct EdgeCases2; /// /// [^*] //~^ ERROR: no footnote definition matching this footnote -pub struct EdgeCases3; +pub struct EdgeCases3; From 8447565b9ca47fda5c6b335696863c470c01e4e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Rakic?= Date: Wed, 12 Aug 2026 15:23:12 +0200 Subject: [PATCH 15/31] self-profile more of borrowck --- compiler/rustc_borrowck/src/lib.rs | 35 +++++++++++-------- compiler/rustc_borrowck/src/nll.rs | 1 + .../src/type_check/liveness/mod.rs | 1 + .../src/type_check/liveness/trace.rs | 3 ++ 4 files changed, 25 insertions(+), 15 deletions(-) diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs index c32e3457d73a1..cc61ba92da280 100644 --- a/compiler/rustc_borrowck/src/lib.rs +++ b/compiler/rustc_borrowck/src/lib.rs @@ -612,21 +612,26 @@ fn get_flow_results<'a, 'tcx>( ) -> Results<'tcx, Borrowck<'a, 'tcx>> { // We compute these three analyses individually, but them combine them into // a single results so that `mbcx` can visit them all together. - let borrows = Borrows::new(tcx, body, regioncx, borrow_set).iterate_to_fixpoint( - tcx, - body, - Some("borrowck"), - ); - let uninits = MaybeUninitializedPlaces::new(tcx, body, move_data).iterate_to_fixpoint( - tcx, - body, - Some("borrowck"), - ); - let ever_inits = EverInitializedPlaces::new(body, move_data).iterate_to_fixpoint( - tcx, - body, - Some("borrowck"), - ); + let borrows = { + let _timer = tcx.prof.generic_activity("borrowck_dataflow_borrows"); + Borrows::new(tcx, body, regioncx, borrow_set).iterate_to_fixpoint( + tcx, + body, + Some("borrowck"), + ) + }; + let uninits = { + let _timer = tcx.prof.generic_activity("borrowck_dataflow_maybe_uninits"); + MaybeUninitializedPlaces::new(tcx, body, move_data).iterate_to_fixpoint( + tcx, + body, + Some("borrowck"), + ) + }; + let ever_inits = { + let _timer = tcx.prof.generic_activity("borrowck_dataflow_ever_inits"); + EverInitializedPlaces::new(body, move_data).iterate_to_fixpoint(tcx, body, Some("borrowck")) + }; let analysis = Borrowck { borrows: borrows.analysis, diff --git a/compiler/rustc_borrowck/src/nll.rs b/compiler/rustc_borrowck/src/nll.rs index 9cc4fd56eebd1..672b58fcbfbea 100644 --- a/compiler/rustc_borrowck/src/nll.rs +++ b/compiler/rustc_borrowck/src/nll.rs @@ -154,6 +154,7 @@ pub(crate) fn compute_regions<'tcx>( // If requested for `-Zpolonius=next`, convert NLL constraints to localized outlives constraints // and use them to compute loan liveness. if let Some(polonius_context) = polonius_context.as_mut() { + let _timer = infcx.tcx.prof.generic_activity("borrowck_polonius_loan_liveness"); polonius_context.compute_loan_liveness(&mut regioncx, body, borrow_set) } diff --git a/compiler/rustc_borrowck/src/type_check/liveness/mod.rs b/compiler/rustc_borrowck/src/type_check/liveness/mod.rs index 442c37e26ec18..3520131232b62 100644 --- a/compiler/rustc_borrowck/src/type_check/liveness/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/liveness/mod.rs @@ -32,6 +32,7 @@ pub(super) fn generate<'tcx>( move_data: &MoveData<'tcx>, ) { debug!("liveness::generate"); + let _timer = typeck.tcx().prof.generic_activity("borrowck_liveness"); let mut free_regions = regions_that_outlive_free_regions( typeck.infcx.num_region_vars(), diff --git a/compiler/rustc_borrowck/src/type_check/liveness/trace.rs b/compiler/rustc_borrowck/src/type_check/liveness/trace.rs index 6f1f977823c8e..fe20bb6c28c0c 100644 --- a/compiler/rustc_borrowck/src/type_check/liveness/trace.rs +++ b/compiler/rustc_borrowck/src/type_check/liveness/trace.rs @@ -45,6 +45,8 @@ pub(super) fn trace<'tcx>( relevant_live_locals: Vec, boring_locals: Vec, ) { + let _timer = typeck.tcx().prof.generic_activity("borrowck_liveness_trace"); + let local_use_map = &LocalUseMap::build(&relevant_live_locals, location_map, typeck.body); let cx = LivenessContext { typeck, @@ -485,6 +487,7 @@ impl<'a, 'typeck, 'tcx> LivenessContext<'a, 'typeck, 'tcx> { // a much, much smaller domain: in our benchmarks, when it's not zero (the most likely // case), there are a few dozens compared to e.g. thousands or tens of thousands of // locals and move paths. + let _timer = tcx.prof.generic_activity("borrowck_dataflow_maybe_inits"); let flow_inits = MaybeInitializedPlaces::new(tcx, body, self.move_data) .iterate_to_fixpoint(tcx, body, Some("borrowck")) .into_results_cursor(body); From 349eac6d9284359c8186f0d6814b6ccbdf46c868 Mon Sep 17 00:00:00 2001 From: ravlyn Date: Mon, 10 Aug 2026 21:52:05 +0700 Subject: [PATCH 16/31] rustc_parse: suggest removing semicolon before `if` block --- compiler/rustc_parse/src/parser/expr.rs | 11 +++ tests/ui/parser/if-semi-before-block.fixed | 47 +++++++++++ tests/ui/parser/if-semi-before-block.rs | 47 +++++++++++ tests/ui/parser/if-semi-before-block.stderr | 87 +++++++++++++++++++++ tests/ui/parser/semi-in-let-chain.stderr | 5 ++ 5 files changed, 197 insertions(+) create mode 100644 tests/ui/parser/if-semi-before-block.fixed create mode 100644 tests/ui/parser/if-semi-before-block.rs create mode 100644 tests/ui/parser/if-semi-before-block.stderr diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index f81727eda4fb6..3e03730ab632b 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -2777,6 +2777,17 @@ impl<'a> Parser<'a> { "you likely meant to continue parsing the let-chain starting here", ); } else { + if self.prev_token == token::Semi + && (self.token == token::OpenBrace || AssocOp::from_token(&self.token).is_some()) + { + err.span_suggestion_verbose( + self.prev_token.span, + "remove this semicolon", + "", + Applicability::MaybeIncorrect, + ); + } + // Look for usages of '=>' where '>=' might be intended if maybe_fatarrow == token::FatArrow { err.span_suggestion_verbose( diff --git a/tests/ui/parser/if-semi-before-block.fixed b/tests/ui/parser/if-semi-before-block.fixed new file mode 100644 index 0000000000000..f7b7d63a9670c --- /dev/null +++ b/tests/ui/parser/if-semi-before-block.fixed @@ -0,0 +1,47 @@ +//@ edition:2024 +//@ run-rustfix + +#![allow(dead_code)] + +fn block() { + if let Some(_) = Some(2) {} + //~^ ERROR expected `{`, found `;` + //~| NOTE expected `{` + //~| HELP remove this semicolon + //~| NOTE the `if` expression is missing a block after this condition +} + +fn and() { + if let Some(x) = Some(2) && x != 1 {} + //~^ ERROR expected `{`, found `;` + //~| NOTE expected `{` + //~| HELP remove this semicolon + //~| NOTE the `if` expression is missing a block after this condition +} + +fn and_paren_cond() { + if let Some(x) = Some(2) && (x > 0 && x != 1) {} + //~^ ERROR expected `{`, found `;` + //~| NOTE expected `{` + //~| HELP remove this semicolon + //~| NOTE the `if` expression is missing a block after this condition +} + +fn and_some() { + if let Some(x) = Some(2) && Some(1) == Some(x) {} + //~^ ERROR expected `{`, found `;` + //~| NOTE expected `{` + //~| HELP remove this semicolon + //~| NOTE the `if` expression is missing a block after this condition +} + +fn or() { + if true || false { + //~^ ERROR expected `{`, found `;` + //~| NOTE expected `{` + //~| HELP remove this semicolon + //~| NOTE the `if` expression is missing a block after this condition + } +} + +fn main() {} diff --git a/tests/ui/parser/if-semi-before-block.rs b/tests/ui/parser/if-semi-before-block.rs new file mode 100644 index 0000000000000..84d132fd08cec --- /dev/null +++ b/tests/ui/parser/if-semi-before-block.rs @@ -0,0 +1,47 @@ +//@ edition:2024 +//@ run-rustfix + +#![allow(dead_code)] + +fn block() { + if let Some(_) = Some(2); {} + //~^ ERROR expected `{`, found `;` + //~| NOTE expected `{` + //~| HELP remove this semicolon + //~| NOTE the `if` expression is missing a block after this condition +} + +fn and() { + if let Some(x) = Some(2); && x != 1 {} + //~^ ERROR expected `{`, found `;` + //~| NOTE expected `{` + //~| HELP remove this semicolon + //~| NOTE the `if` expression is missing a block after this condition +} + +fn and_paren_cond() { + if let Some(x) = Some(2); && (x > 0 && x != 1) {} + //~^ ERROR expected `{`, found `;` + //~| NOTE expected `{` + //~| HELP remove this semicolon + //~| NOTE the `if` expression is missing a block after this condition +} + +fn and_some() { + if let Some(x) = Some(2); && Some(1) == Some(x) {} + //~^ ERROR expected `{`, found `;` + //~| NOTE expected `{` + //~| HELP remove this semicolon + //~| NOTE the `if` expression is missing a block after this condition +} + +fn or() { + if true; || false { + //~^ ERROR expected `{`, found `;` + //~| NOTE expected `{` + //~| HELP remove this semicolon + //~| NOTE the `if` expression is missing a block after this condition + } +} + +fn main() {} diff --git a/tests/ui/parser/if-semi-before-block.stderr b/tests/ui/parser/if-semi-before-block.stderr new file mode 100644 index 0000000000000..d2454f278b17a --- /dev/null +++ b/tests/ui/parser/if-semi-before-block.stderr @@ -0,0 +1,87 @@ +error: expected `{`, found `;` + --> $DIR/if-semi-before-block.rs:7:29 + | +LL | if let Some(_) = Some(2); {} + | ^ expected `{` + | +note: the `if` expression is missing a block after this condition + --> $DIR/if-semi-before-block.rs:7:8 + | +LL | if let Some(_) = Some(2); {} + | ^^^^^^^^^^^^^^^^^^^^^ +help: remove this semicolon + | +LL - if let Some(_) = Some(2); {} +LL + if let Some(_) = Some(2) {} + | + +error: expected `{`, found `;` + --> $DIR/if-semi-before-block.rs:15:29 + | +LL | if let Some(x) = Some(2); && x != 1 {} + | ^ expected `{` + | +note: the `if` expression is missing a block after this condition + --> $DIR/if-semi-before-block.rs:15:8 + | +LL | if let Some(x) = Some(2); && x != 1 {} + | ^^^^^^^^^^^^^^^^^^^^^ +help: remove this semicolon + | +LL - if let Some(x) = Some(2); && x != 1 {} +LL + if let Some(x) = Some(2) && x != 1 {} + | + +error: expected `{`, found `;` + --> $DIR/if-semi-before-block.rs:23:29 + | +LL | if let Some(x) = Some(2); && (x > 0 && x != 1) {} + | ^ expected `{` + | +note: the `if` expression is missing a block after this condition + --> $DIR/if-semi-before-block.rs:23:8 + | +LL | if let Some(x) = Some(2); && (x > 0 && x != 1) {} + | ^^^^^^^^^^^^^^^^^^^^^ +help: remove this semicolon + | +LL - if let Some(x) = Some(2); && (x > 0 && x != 1) {} +LL + if let Some(x) = Some(2) && (x > 0 && x != 1) {} + | + +error: expected `{`, found `;` + --> $DIR/if-semi-before-block.rs:31:29 + | +LL | if let Some(x) = Some(2); && Some(1) == Some(x) {} + | ^ expected `{` + | +note: the `if` expression is missing a block after this condition + --> $DIR/if-semi-before-block.rs:31:8 + | +LL | if let Some(x) = Some(2); && Some(1) == Some(x) {} + | ^^^^^^^^^^^^^^^^^^^^^ +help: remove this semicolon + | +LL - if let Some(x) = Some(2); && Some(1) == Some(x) {} +LL + if let Some(x) = Some(2) && Some(1) == Some(x) {} + | + +error: expected `{`, found `;` + --> $DIR/if-semi-before-block.rs:39:12 + | +LL | if true; || false { + | ^ expected `{` + | +note: the `if` expression is missing a block after this condition + --> $DIR/if-semi-before-block.rs:39:8 + | +LL | if true; || false { + | ^^^^ +help: remove this semicolon + | +LL - if true; || false { +LL + if true || false { + | + +error: aborting due to 5 previous errors + diff --git a/tests/ui/parser/semi-in-let-chain.stderr b/tests/ui/parser/semi-in-let-chain.stderr index f36d5e041e5d5..b742979d033df 100644 --- a/tests/ui/parser/semi-in-let-chain.stderr +++ b/tests/ui/parser/semi-in-let-chain.stderr @@ -28,6 +28,11 @@ LL | if let () = () | ________^ LL | | && () == (); | |___________________^ +help: remove this semicolon + | +LL - && () == (); +LL + && () == () + | error: expected `{`, found `;` --> $DIR/semi-in-let-chain.rs:22:20 From 298250703731261e26104ab157908a2552ea32a4 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 12 Aug 2026 16:30:57 +0200 Subject: [PATCH 17/31] Remove old cfg parser which is now dead code --- compiler/rustc_expand/src/config.rs | 33 ++--------------------- compiler/rustc_expand/src/diagnostics.rs | 34 ------------------------ 2 files changed, 2 insertions(+), 65 deletions(-) diff --git a/compiler/rustc_expand/src/config.rs b/compiler/rustc_expand/src/config.rs index 93dfab10077ee..7e80f6e4cb897 100644 --- a/compiler/rustc_expand/src/config.rs +++ b/compiler/rustc_expand/src/config.rs @@ -7,10 +7,7 @@ use rustc_ast::token::{Delimiter, Token, TokenKind}; use rustc_ast::tokenstream::{ AttrTokenStream, AttrTokenTree, LazyAttrTokenStream, Spacing, TokenTree, WithTokens, }; -use rustc_ast::{ - self as ast, AttrStyle, Attribute, HasAttrs, HasTokens, MetaItem, MetaItemInner, NodeId, - SyntheticAttr, -}; +use rustc_ast::{self as ast, AttrStyle, Attribute, HasAttrs, HasTokens, NodeId, SyntheticAttr}; use rustc_attr_ir::target::Target; use rustc_attr_ir::{self as attrs, AttributeKind}; use rustc_attr_parsing::parser::AllowExprMetavar; @@ -32,7 +29,7 @@ use tracing::instrument; use crate::diagnostics::{ CrateNameInCfgAttr, CrateTypeInCfgAttr, FeatureNotAllowed, FeatureRemoved, - FeatureRemovedReason, InvalidCfg, RemoveExprNotSupported, + FeatureRemovedReason, RemoveExprNotSupported, }; /// A folder that strips out items that do not belong in the current configuration. @@ -442,32 +439,6 @@ impl<'a> StripUnconfigured<'a> { } } -/// FIXME: Still used by Rustdoc, should be removed after -pub fn parse_cfg_old<'a>(meta_item: &'a MetaItem, sess: &Session) -> Option<&'a MetaItemInner> { - let span = meta_item.span; - match meta_item.meta_item_list() { - None => { - sess.dcx().emit_err(InvalidCfg::NotFollowedByParens { span }); - None - } - Some([]) => { - sess.dcx().emit_err(InvalidCfg::NoPredicate { span }); - None - } - Some([_, .., l]) => { - sess.dcx().emit_err(InvalidCfg::MultiplePredicates { span: l.span() }); - None - } - Some([single]) => match single.meta_item_or_bool() { - Some(meta_item) => Some(meta_item), - None => { - sess.dcx().emit_err(InvalidCfg::PredicateLiteral { span: single.span() }); - None - } - }, - } -} - fn is_cfg(attr: &Attribute) -> bool { attr.has_name(sym::cfg) } diff --git a/compiler/rustc_expand/src/diagnostics.rs b/compiler/rustc_expand/src/diagnostics.rs index 5d2066ef88773..bc8222feb14bf 100644 --- a/compiler/rustc_expand/src/diagnostics.rs +++ b/compiler/rustc_expand/src/diagnostics.rs @@ -189,40 +189,6 @@ pub(crate) struct RemoveExprNotSupported { pub span: Span, } -#[derive(Diagnostic)] -pub(crate) enum InvalidCfg { - #[diag("`cfg` is not followed by parentheses")] - NotFollowedByParens { - #[primary_span] - #[suggestion( - "expected syntax is", - code = "cfg(/* predicate */)", - applicability = "has-placeholders" - )] - span: Span, - }, - #[diag("`cfg` predicate is not specified")] - NoPredicate { - #[primary_span] - #[suggestion( - "expected syntax is", - code = "cfg(/* predicate */)", - applicability = "has-placeholders" - )] - span: Span, - }, - #[diag("multiple `cfg` predicates are specified")] - MultiplePredicates { - #[primary_span] - span: Span, - }, - #[diag("`cfg` predicate key cannot be a literal")] - PredicateLiteral { - #[primary_span] - span: Span, - }, -} - #[derive(Diagnostic)] #[diag("non-{$kind} macro in {$kind} position: {$name}")] pub(crate) struct WrongFragmentKind<'a> { From 18320e3e670a5ecbcfdd2708d43991a19f3e0ae9 Mon Sep 17 00:00:00 2001 From: sgasho Date: Wed, 12 Aug 2026 14:55:44 +0000 Subject: [PATCH 18/31] Add offload component on nightly --- src/tools/build-manifest/src/main.rs | 6 ++++-- src/tools/build-manifest/src/versions.rs | 3 +++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/tools/build-manifest/src/main.rs b/src/tools/build-manifest/src/main.rs index f5fdb662662d0..0d2e8cb5e74ab 100644 --- a/src/tools/build-manifest/src/main.rs +++ b/src/tools/build-manifest/src/main.rs @@ -39,7 +39,8 @@ fn is_nightly_only(pkg: &PkgType) -> bool { | PkgType::RustcCodegenCranelift | PkgType::RustcCodegenGcc | PkgType::Gcc { .. } - | PkgType::Enzyme => true, + | PkgType::Enzyme + | PkgType::Offload => true, PkgType::Rust | PkgType::RustSrc | PkgType::Rustc @@ -331,7 +332,8 @@ impl Builder { | PkgType::RustcCodegenGcc | PkgType::Gcc { .. } | PkgType::LlvmBitcodeLinker - | PkgType::Enzyme => { + | PkgType::Enzyme + | PkgType::Offload => { extensions.push(host_component(pkg)); } PkgType::RustcDev => { diff --git a/src/tools/build-manifest/src/versions.rs b/src/tools/build-manifest/src/versions.rs index 56da7f7af7eaa..ec333cf4691ad 100644 --- a/src/tools/build-manifest/src/versions.rs +++ b/src/tools/build-manifest/src/versions.rs @@ -89,6 +89,7 @@ pkg_type! { "x86_64-unknown-linux-gnu" ], Enzyme = "enzyme"; preview = true, + Offload = "offload"; preview = true, } impl PkgType { @@ -128,6 +129,7 @@ impl PkgType { PkgType::RustAnalysis => true, PkgType::LlvmBitcodeLinker => true, PkgType::Enzyme => true, + PkgType::Offload => true, } } @@ -165,6 +167,7 @@ impl PkgType { LlvmTools => TARGETS, LlvmBitcodeLinker => HOSTS, Enzyme => HOSTS, + Offload => HOSTS, } } From 4d52fe93a207098976f78bcd95bf8477f8a10131 Mon Sep 17 00:00:00 2001 From: Marcelo Dominguez Date: Wed, 15 Jul 2026 18:13:56 +0300 Subject: [PATCH 19/31] Add 3-pass compilation to support generics and remove `no_mangle` attr --- Cargo.lock | 2 + compiler/rustc_builtin_macros/src/offload.rs | 40 +- compiler/rustc_codegen_llvm/src/back/write.rs | 14 +- compiler/rustc_codegen_llvm/src/intrinsic.rs | 6 +- .../src/back/symbol_export.rs | 94 ++++- compiler/rustc_codegen_ssa/src/back/write.rs | 33 +- compiler/rustc_codegen_ssa/src/base.rs | 20 +- compiler/rustc_middle/src/mono.rs | 6 + compiler/rustc_monomorphize/Cargo.toml | 2 + compiler/rustc_monomorphize/src/collector.rs | 102 ++++- .../rustc_monomorphize/src/diagnostics.rs | 14 + compiler/rustc_monomorphize/src/lib.rs | 1 + .../src/offload_manifest.rs | 396 ++++++++++++++++++ .../rustc_monomorphize/src/partitioning.rs | 10 + compiler/rustc_session/src/config.rs | 6 + compiler/rustc_session/src/options.rs | 17 +- compiler/rustc_symbol_mangling/src/lib.rs | 104 +++-- .../codegen-llvm/gpu_offload/control_flow.rs | 8 +- tests/codegen-llvm/gpu_offload/slice_host.rs | 4 +- tests/pretty/offload/offload_kernel.device.pp | 1 - tests/pretty/offload/offload_kernel.host.pp | 2 +- 21 files changed, 792 insertions(+), 90 deletions(-) create mode 100644 compiler/rustc_monomorphize/src/offload_manifest.rs diff --git a/Cargo.lock b/Cargo.lock index ce3fd04faf3bb..3ddb0c6981318 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4528,12 +4528,14 @@ name = "rustc_monomorphize" version = "0.0.0" dependencies = [ "rustc_abi", + "rustc_ast", "rustc_data_structures", "rustc_errors", "rustc_hir", "rustc_index", "rustc_macros", "rustc_middle", + "rustc_serialize", "rustc_session", "rustc_span", "rustc_symbol_mangling", diff --git a/compiler/rustc_builtin_macros/src/offload.rs b/compiler/rustc_builtin_macros/src/offload.rs index e47ccc0d85f7d..96cb9372d3e09 100644 --- a/compiler/rustc_builtin_macros/src/offload.rs +++ b/compiler/rustc_builtin_macros/src/offload.rs @@ -1,6 +1,6 @@ +use rustc_ast::ast; use rustc_ast::token::{Delimiter, Token, TokenKind}; use rustc_ast::tokenstream::{DelimSpan, Spacing, TokenStream, TokenTree}; -use rustc_ast::{AttrItem, ast}; use rustc_expand::base::{Annotatable, ExtCtxt}; use rustc_session::config::Offload; use rustc_span::{DUMMY_SP, Ident, Span, sym}; @@ -9,7 +9,12 @@ use thin_vec::thin_vec; use crate::diagnostics; fn compile_for_device(ecx: &mut ExtCtxt<'_>) -> bool { - ecx.sess.opts.unstable_opts.offload.contains(&Offload::Device) + ecx.sess + .opts + .unstable_opts + .offload + .iter() + .any(|o| matches!(o, Offload::Device | Offload::DeviceWithManifest(_))) } fn outer_normal_attr(normal: &Box, id: ast::AttrId, span: Span) -> ast::Attribute { @@ -45,7 +50,6 @@ fn extract_fn( /// This expands to the host-side function: /// /// ``` -/// #[unsafe(no_mangle)] /// #[inline(never)] /// fn foo(_: &[f32], _: &[f32], _: *mut f32) { /// ::core::panicking::panic("not implemented") @@ -56,7 +60,6 @@ fn extract_fn( /// /// ``` /// #[rustc_offload_kernel] -/// #[unsafe(no_mangle)] /// unsafe extern "gpu-kernel" fn foo(a: &[f32], b: &[f32], c: *mut f32) { /// *c = a[0] + b[0]; /// } @@ -110,24 +113,9 @@ pub(crate) fn expand_kernel( span, ); - // unsafe(no_mangle) attr - let unsafe_item = AttrItem { - unsafety: ast::Safety::Unsafe(span), - path: ast::Path::from_ident(Ident::new(sym::no_mangle, span)), - args: ast::AttrArgs::Empty, - span, - }; - - let no_mangle_attr = Box::new(ast::NormalAttr { item: unsafe_item, tokens: None }); - let new_id = ecx.sess.psess.attr_id_generator.mk_attr_id(); - let unsafe_no_mangle = outer_normal_attr(&no_mangle_attr, new_id, span); - let device_item = { - let mut item = ecx.item( - span, - thin_vec![rustc_offload_kernel, unsafe_no_mangle], - ast::ItemKind::Fn(device_fn), - ); + let mut item = + ecx.item(span, thin_vec![rustc_offload_kernel.clone()], ast::ItemKind::Fn(device_fn)); item.vis = vis.clone(); Annotatable::Item(item) }; @@ -187,12 +175,12 @@ pub(crate) fn expand_kernel( let new_id = ecx.sess.psess.attr_id_generator.mk_attr_id(); let inline_never = outer_normal_attr(&inline_never_attr, new_id, span); - let new_id = ecx.sess.psess.attr_id_generator.mk_attr_id(); - let unsafe_no_mangle = outer_normal_attr(&no_mangle_attr, new_id, span); - let host_item = { - let mut item = - ecx.item(span, thin_vec![unsafe_no_mangle, inline_never], ast::ItemKind::Fn(host_fn)); + let mut item = ecx.item( + span, + thin_vec![rustc_offload_kernel, inline_never], + ast::ItemKind::Fn(host_fn), + ); item.vis = vis.clone(); Annotatable::Item(item) }; diff --git a/compiler/rustc_codegen_llvm/src/back/write.rs b/compiler/rustc_codegen_llvm/src/back/write.rs index 400a9ce89df8b..80d5ff31c730e 100644 --- a/compiler/rustc_codegen_llvm/src/back/write.rs +++ b/compiler/rustc_codegen_llvm/src/back/write.rs @@ -742,7 +742,12 @@ pub(crate) unsafe fn llvm_optimize( llvm::set_value_name(new_fn, &name); } - if cgcx.target_is_like_gpu && config.offload.contains(&config::Offload::Device) { + if cgcx.target_is_like_gpu + && config + .offload + .iter() + .any(|o| matches!(o, config::Offload::Device | config::Offload::DeviceWithManifest(_))) + { let cx = SimpleCx::new(module.module_llvm.llmod(), module.module_llvm.llcx, cgcx.pointer_size); for func in cx.get_functions() { @@ -813,7 +818,12 @@ pub(crate) unsafe fn llvm_optimize( ) }; - if cgcx.target_is_like_gpu && config.offload.contains(&config::Offload::Device) { + if cgcx.target_is_like_gpu + && config + .offload + .iter() + .any(|o| matches!(o, config::Offload::Device | config::Offload::DeviceWithManifest(_))) + { let device_path = cgcx.output_filenames.path(OutputType::Object); let device_dir = device_path.parent().unwrap(); let device_out = device_dir.join("device.bin"); diff --git a/compiler/rustc_codegen_llvm/src/intrinsic.rs b/compiler/rustc_codegen_llvm/src/intrinsic.rs index 4ae85af897527..4c54b60c6d48a 100644 --- a/compiler/rustc_codegen_llvm/src/intrinsic.rs +++ b/compiler/rustc_codegen_llvm/src/intrinsic.rs @@ -26,7 +26,9 @@ use rustc_session::config::CrateType; use rustc_session::diagnostics::feature_err; use rustc_session::lint::builtin::DEPRECATED_LLVM_INTRINSIC; use rustc_span::{ErrorGuaranteed, Span, Symbol, sym}; -use rustc_symbol_mangling::{mangle_internal_symbol, symbol_name_for_instance_in_crate}; +use rustc_symbol_mangling::{ + mangle_internal_symbol, mangle_offload_export, symbol_name_for_instance_in_crate, +}; use rustc_target::callconv::PassMode; use rustc_target::spec::Arch; use tracing::debug; @@ -1850,7 +1852,7 @@ fn codegen_offload<'ll, 'tcx>( _ => panic!("unparsable"), }; let args = get_args_from_tuple(bx, args[4], fn_target); - let target_symbol = symbol_name_for_instance_in_crate(tcx, fn_target, LOCAL_CRATE); + let target_symbol = mangle_offload_export(tcx, fn_target); let sig = tcx.fn_sig(fn_target.def_id()).skip_binder(); let sig = tcx.instantiate_bound_regions_with_erased(sig); diff --git a/compiler/rustc_codegen_ssa/src/back/symbol_export.rs b/compiler/rustc_codegen_ssa/src/back/symbol_export.rs index 941e9d28fc7e1..bdff48ced55b6 100644 --- a/compiler/rustc_codegen_ssa/src/back/symbol_export.rs +++ b/compiler/rustc_codegen_ssa/src/back/symbol_export.rs @@ -3,6 +3,7 @@ use std::collections::hash_map::Entry::*; use rustc_abi::{CanonAbi, X86Call}; use rustc_ast::expand::allocator::{AllocatorKind, NO_ALLOC_SHIM_IS_UNSTABLE, global_fn_name}; use rustc_crate_store::CrateDepKind; +use rustc_data_structures::fx::FxHashSet; use rustc_data_structures::unord::UnordMap; use rustc_hir as hir; use rustc_hir::def::DefKind; @@ -19,7 +20,7 @@ use rustc_middle::ty::{ use rustc_middle::util::Providers; use rustc_session::config::CrateType; use rustc_span::Span; -use rustc_symbol_mangling::mangle_internal_symbol; +use rustc_symbol_mangling::{is_offload_kernel, mangle_internal_symbol}; use rustc_target::spec::{Arch, Os, TlsModel}; use tracing::debug; @@ -244,6 +245,51 @@ pub fn exported_non_generic_symbols_helper<'tcx>( )); } + let is_device_offload = tcx.sess.opts.unstable_opts.offload.iter().any(|o| { + matches!( + o, + rustc_session::config::Offload::DeviceWithManifest(_) + | rustc_session::config::Offload::Device + ) + }); + if is_device_offload { + let crate_items = tcx.hir_crate_items(()); + let mut seen: rustc_data_structures::fx::FxHashSet = symbols + .iter() + .filter_map(|(s, _)| match s { + ExportedSymbol::NonGeneric(d) => Some(*d), + _ => None, + }) + .collect(); + + let mut try_emit_offload_kernel = |def_id: DefId, seen: &mut FxHashSet| { + if !matches!(tcx.def_kind(def_id), DefKind::Fn | DefKind::AssocFn) { + return; + } + if !tcx.generics_of(def_id).requires_monomorphization(tcx) + && is_offload_kernel(tcx.codegen_fn_attrs(def_id)) + && seen.insert(def_id) + { + symbols.push(( + ExportedSymbol::NonGeneric(def_id), + SymbolExportInfo { + level: SymbolExportLevel::C, + kind: SymbolExportKind::Text, + used: false, + rustc_std_internal_symbol: false, + }, + )); + } + }; + + for id in crate_items.free_items() { + try_emit_offload_kernel(id.owner_id.to_def_id(), &mut seen); + } + for id in crate_items.impl_items() { + try_emit_offload_kernel(id.owner_id.to_def_id(), &mut seen); + } + } + // Sort so we get a stable incr. comp. hash. symbols.sort_by_cached_key(|s| s.0.symbol_name_for_local_instance(tcx)); @@ -260,7 +306,16 @@ fn exported_generic_symbols_provider_local<'tcx>( let mut symbols: Vec<_> = vec![]; - if tcx.local_crate_exports_generics() { + let export_generics = tcx.local_crate_exports_generics(); + let is_device_offload = tcx.sess.opts.unstable_opts.offload.iter().any(|o| { + matches!( + o, + rustc_session::config::Offload::DeviceWithManifest(_) + | rustc_session::config::Offload::Device + ) + }); + + if export_generics || is_device_offload { use rustc_hir::attrs::Linkage; use rustc_middle::mono::{MonoItem, Visibility}; use rustc_middle::ty::InstanceKind; @@ -306,6 +361,14 @@ fn exported_generic_symbols_provider_local<'tcx>( }) }; + let is_offload_instance = |mono_item: &MonoItem<'tcx>| { + if let MonoItem::Fn(instance) = mono_item { + is_offload_kernel(tcx.codegen_fn_attrs(instance.def_id())) + } else { + false + } + }; + // The symbols created in this loop are sorted below it #[allow(rustc::potential_query_instability)] for (mono_item, data) in cgus.iter().flat_map(|cgu| cgu.items().iter()) { @@ -321,7 +384,9 @@ fn exported_generic_symbols_provider_local<'tcx>( continue; } - if !tcx.sess.opts.share_generics() { + let item_is_offload = is_offload_instance(mono_item); + + if !item_is_offload && !tcx.sess.opts.share_generics() { if tcx.codegen_fn_attrs(mono_item.def_id()).inline == rustc_hir::attrs::InlineAttr::Never { @@ -338,15 +403,22 @@ fn exported_generic_symbols_provider_local<'tcx>( MonoItem::Fn(Instance { def: InstanceKind::Item(def), args }) => { let has_generics = args.non_erasable_generics().next().is_some(); - let should_export = - has_generics && is_instantiable_downstream(Some(def), &args); + let should_export = if item_is_offload { + has_generics + } else { + has_generics && is_instantiable_downstream(Some(def), &args) + }; if should_export { let symbol = ExportedSymbol::Generic(def, args); symbols.push(( symbol, SymbolExportInfo { - level: SymbolExportLevel::Rust, + level: if item_is_offload { + SymbolExportLevel::C + } else { + SymbolExportLevel::Rust + }, kind: SymbolExportKind::Text, used: false, rustc_std_internal_symbol: false, @@ -561,11 +633,19 @@ fn symbol_export_level(tcx: TyCtxt<'_>, sym_def_id: DefId) -> SymbolExportLevel // are not considered for export let codegen_fn_attrs = tcx.codegen_fn_attrs(sym_def_id); let is_extern = codegen_fn_attrs.contains_extern_indicator(); + let is_device_offload = tcx.sess.opts.unstable_opts.offload.iter().any(|o| { + matches!( + o, + rustc_session::config::Offload::DeviceWithManifest(_) + | rustc_session::config::Offload::Device + ) + }); + let is_offload = is_device_offload && is_offload_kernel(codegen_fn_attrs); let std_internal = codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::RUSTC_STD_INTERNAL_SYMBOL); let eii = codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::EXTERNALLY_IMPLEMENTABLE_ITEM); - if is_extern && !std_internal && !eii { + if (is_extern && !std_internal && !eii) || is_offload { let target = &tcx.sess.target.llvm_target; // WebAssembly cannot export data symbols, so reduce their export level // FIXME(jdonszelmann) don't do a substring match here. diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs index 2eaceb68a67a2..46ac35cb7dbc5 100644 --- a/compiler/rustc_codegen_ssa/src/back/write.rs +++ b/compiler/rustc_codegen_ssa/src/back/write.rs @@ -464,6 +464,29 @@ pub(crate) fn start_async_codegen( } } +/// Create an `OngoingCodegen` that has no coordinator thread and will finish +/// immediately when joined. This is used for the offload host-metadata pass, +/// that only need to run the monomorphization collector. +pub(crate) fn empty_ongoing_codegen( + backend: B, + tcx: TyCtxt<'_>, +) -> OngoingCodegen { + let (coordinator_send, _) = channel::>(); + let (codegen_worker_send, codegen_worker_receive) = channel(); + drop(codegen_worker_send); + + let (shared_emitter, shared_emitter_main) = SharedEmitter::new(); + drop(shared_emitter); + + OngoingCodegen { + backend, + codegen_worker_receive, + shared_emitter_main, + coordinator: Coordinator { sender: coordinator_send, future: None, phantom: PhantomData }, + output_filenames: Arc::clone(tcx.output_filenames(())), + } +} + fn copy_all_cgu_workproducts_to_incr_comp_cache_dir( sess: &Session, incr_comp_session: Option<&IncrCompSession>, @@ -2112,7 +2135,15 @@ pub struct Coordinator { impl Coordinator { fn join(mut self) -> std::thread::Result, ()>> { - self.future.take().unwrap().join() + if let Some(future) = self.future.take() { + future.join() + } else { + // Used for passes that do not codegen anything (e.g. the offload host-metadata pass). + Ok(Ok(MaybeLtoModules::NoLto(CompiledModules { + modules: vec![], + allocator_module: None, + }))) + } } } diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs index 0468e3de18d8b..d615c8daab550 100644 --- a/compiler/rustc_codegen_ssa/src/base.rs +++ b/compiler/rustc_codegen_ssa/src/base.rs @@ -41,8 +41,9 @@ use tracing::{debug, info}; use crate::assert_module_sources::CguReuse; use crate::back::link::are_upstream_rust_objects_already_included; use crate::back::write::{ - ComputedLtoType, OngoingCodegen, compute_per_cgu_lto_type, start_async_codegen, - submit_codegened_module_to_llvm, submit_post_lto_module_to_llvm, submit_pre_lto_module_to_llvm, + ComputedLtoType, OngoingCodegen, compute_per_cgu_lto_type, empty_ongoing_codegen, + start_async_codegen, submit_codegened_module_to_llvm, submit_post_lto_module_to_llvm, + submit_pre_lto_module_to_llvm, }; use crate::common::{self, IntPredicate, RealPredicate, TypeKind}; use crate::meth::load_vtable; @@ -723,6 +724,21 @@ pub fn codegen_crate< tcx.dcx().emit_fatal(diagnostics::CpuRequired); } + // A `HostMetadata` pass only exists to collect the set of generic kernel instantiations + // required by the host and write the offload manifest. + let is_host_metadata = tcx + .sess + .opts + .unstable_opts + .offload + .iter() + .any(|o| matches!(o, rustc_session::config::Offload::HostMetadata(_))); + + if is_host_metadata { + let _ = tcx.collect_and_partition_mono_items(()); + return empty_ongoing_codegen(backend, tcx); + } + if let Some(target_cpu) = &tcx.sess.opts.cg.target_cpu && tcx.sess.target.unsupported_cpus.contains(&target_cpu.into()) { diff --git a/compiler/rustc_middle/src/mono.rs b/compiler/rustc_middle/src/mono.rs index dc9a94f79aa0f..1d15844f4ca96 100644 --- a/compiler/rustc_middle/src/mono.rs +++ b/compiler/rustc_middle/src/mono.rs @@ -160,6 +160,12 @@ impl<'tcx> MonoItem<'tcx> { return InstantiationMode::GloballyShared { may_conflict: false }; } + // Offload kernels are looked up by symbol name at runtime by the host. + // They must be emitted exactly once with external linkage. + if codegen_fn_attrs.flags.intersects(CodegenFnAttrFlags::OFFLOAD_KERNEL) { + return InstantiationMode::GloballyShared { may_conflict: false }; + } + // This is technically a heuristic even though it's in the "not a heuristic" part of // instantiation mode selection. // It is surely possible to untangle this; the root problem is that the way we instantiate diff --git a/compiler/rustc_monomorphize/Cargo.toml b/compiler/rustc_monomorphize/Cargo.toml index 58ccf77903bab..c45232b2565a7 100644 --- a/compiler/rustc_monomorphize/Cargo.toml +++ b/compiler/rustc_monomorphize/Cargo.toml @@ -6,12 +6,14 @@ edition = "2024" [dependencies] # tidy-alphabetical-start rustc_abi = { path = "../rustc_abi" } +rustc_ast = { path = "../rustc_ast" } rustc_data_structures = { path = "../rustc_data_structures" } rustc_errors = { path = "../rustc_errors" } rustc_hir = { path = "../rustc_hir" } rustc_index = { path = "../rustc_index" } rustc_macros = { path = "../rustc_macros" } rustc_middle = { path = "../rustc_middle" } +rustc_serialize = { path = "../rustc_serialize" } rustc_session = { path = "../rustc_session" } rustc_span = { path = "../rustc_span" } rustc_symbol_mangling = { path = "../rustc_symbol_mangling" } diff --git a/compiler/rustc_monomorphize/src/collector.rs b/compiler/rustc_monomorphize/src/collector.rs index 1ee6e0506dcdd..eb5741f68746f 100644 --- a/compiler/rustc_monomorphize/src/collector.rs +++ b/compiler/rustc_monomorphize/src/collector.rs @@ -831,8 +831,8 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MirUsedCollector<'a, 'tcx> { }; match terminator.kind { - mir::TerminatorKind::Call { ref func, .. } - | mir::TerminatorKind::TailCall { ref func, .. } => { + mir::TerminatorKind::Call { ref func, ref args, .. } + | mir::TerminatorKind::TailCall { ref func, ref args, .. } => { let callee_ty = func.ty(self.body, tcx); // *Before* monomorphizing, record that we already handled this mention. self.used_mentioned_items.insert(MentionedItem::Fn(callee_ty)); @@ -865,7 +865,17 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MirUsedCollector<'a, 'tcx> { !force_indirect_call, source, &mut self.used_items, - ) + ); + + // TODO(Sa4dUs): check why it only collects non generic fns + if let ty::FnDef(def_id, _) = *callee_ty.kind() + && self.tcx.is_intrinsic(def_id, rustc_span::sym::offload) + && let Some(kernel) = args.first() + { + let kernel_ty = kernel.node.ty(self.body, self.tcx); + let kernel_ty = self.monomorphize(kernel_ty); + visit_fn_use(self.tcx, kernel_ty, false, source, &mut self.used_items); + } } mir::TerminatorKind::Drop { ref place, .. } => { let ty = place.ty(self.body, self.tcx).ty; @@ -1475,6 +1485,27 @@ fn collect_roots(tcx: TyCtxt<'_>, mode: MonoItemCollectionStrategy) -> Vec { + for instance in instances { + if instance.def_id().is_local() { + roots.push(dummy_spanned(MonoItem::Fn(instance))); + } + } + } + Err(e) => { + tcx.dcx().emit_err(crate::diagnostics::OffloadManifestReadError { + path: manifest_path.clone(), + err: e.to_string(), + }); + } + } + } + { let entry_fn = tcx.entry_fn(()); @@ -1499,6 +1530,39 @@ fn collect_roots(tcx: TyCtxt<'_>, mode: MonoItemCollectionStrategy) -> Vec( state.visited.into_inner().into_sorted(&mut hcx, true) }); + // Write out the offload manifest of required generic kernel instantiations. + if let Some(path) = tcx.sess.opts.unstable_opts.offload.iter().find_map(|o| { + if let rustc_session::config::Offload::HostMetadata(p) = o { Some(p) } else { None } + }) { + let instances: Vec> = mono_items + .iter() + .filter_map(|item| { + if let MonoItem::Fn(instance) = item { + if tcx + .codegen_fn_attrs(instance.def_id()) + .flags + .contains(CodegenFnAttrFlags::OFFLOAD_KERNEL) + { + Some(*instance) + } else { + None + } + } else { + None + } + }) + .collect(); + if let Err(e) = + crate::offload_manifest::write_manifest(std::path::Path::new(path), tcx, &instances) + { + tcx.dcx().emit_fatal(crate::diagnostics::OffloadManifestWriteError { + path: path.clone(), + err: e.to_string(), + }); + } + } + (mono_items, state.usage_map.into_inner()) } diff --git a/compiler/rustc_monomorphize/src/diagnostics.rs b/compiler/rustc_monomorphize/src/diagnostics.rs index 27705a9837ad3..df4b54abc2258 100644 --- a/compiler/rustc_monomorphize/src/diagnostics.rs +++ b/compiler/rustc_monomorphize/src/diagnostics.rs @@ -50,6 +50,20 @@ pub(crate) struct CouldntDumpMonoStats { pub error: String, } +#[derive(Diagnostic)] +#[diag("could not write offload monomorphization manifest to `{$path}`: {$err}")] +pub(crate) struct OffloadManifestWriteError { + pub path: String, + pub err: String, +} + +#[derive(Diagnostic)] +#[diag("could not read offload monomorphization manifest from `{$path}`: {$err}")] +pub(crate) struct OffloadManifestReadError { + pub path: String, + pub err: String, +} + #[derive(Diagnostic)] #[diag("the above error was encountered while instantiating `{$kind} {$instance}`")] pub(crate) struct EncounteredErrorWhileInstantiating<'tcx> { diff --git a/compiler/rustc_monomorphize/src/lib.rs b/compiler/rustc_monomorphize/src/lib.rs index c72ee9dd23393..a15ebe33651c6 100644 --- a/compiler/rustc_monomorphize/src/lib.rs +++ b/compiler/rustc_monomorphize/src/lib.rs @@ -16,6 +16,7 @@ mod collector; mod diagnostics; mod graph_checks; mod mono_checks; +mod offload_manifest; mod partitioning; mod util; diff --git a/compiler/rustc_monomorphize/src/offload_manifest.rs b/compiler/rustc_monomorphize/src/offload_manifest.rs new file mode 100644 index 0000000000000..adfbebc435568 --- /dev/null +++ b/compiler/rustc_monomorphize/src/offload_manifest.rs @@ -0,0 +1,396 @@ +//! Offload manifest: communicates required generic kernel instantiations +//! between host-metadata and device compilation passes. +//! +//! Uses `TyEncoder`/`TyDecoder` to serialize `ty::Instance`. DefIds are +//! encoded as (crate name, DefPath) pairs for stability. + +use std::fs; + +use rustc_data_structures::fx::FxHashMap; +use rustc_data_structures::sync::Lock; +use rustc_hir::def_id::{DefId, DefIndex, LOCAL_CRATE}; +use rustc_middle::ty::codec::{TyDecoder, TyEncoder}; +use rustc_middle::ty::{self, Ty, TyCtxt}; +use rustc_serialize::opaque::{FileEncoder, MemDecoder}; +use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; +use rustc_span::{ + BlobDecoder, BytePos, ByteSymbol, Pos, Span, SpanDecoder, SpanEncoder, Symbol, SyntaxContext, +}; + +pub(crate) struct OffloadManifestEncoder<'a, 'tcx> { + encoder: FileEncoder<'a>, + type_shorthands: FxHashMap, usize>, + predicate_shorthands: FxHashMap, usize>, + tcx: TyCtxt<'tcx>, +} + +impl<'a, 'tcx> OffloadManifestEncoder<'a, 'tcx> { + pub(crate) fn new(path: &'a std::path::Path, tcx: TyCtxt<'tcx>) -> std::io::Result { + let encoder = FileEncoder::new(path)?; + Ok(OffloadManifestEncoder { + encoder, + type_shorthands: FxHashMap::default(), + predicate_shorthands: FxHashMap::default(), + tcx, + }) + } + + pub(crate) fn finish(mut self) -> std::io::Result<()> { + self.encoder.finish().map(|_| ()).map_err(|(_, e)| e) + } +} + +impl<'a, 'tcx> Encoder for OffloadManifestEncoder<'a, 'tcx> { + fn emit_usize(&mut self, v: usize) { + self.encoder.emit_usize(v); + } + fn emit_u128(&mut self, v: u128) { + self.encoder.emit_u128(v); + } + fn emit_u64(&mut self, v: u64) { + self.encoder.emit_u64(v); + } + fn emit_u32(&mut self, v: u32) { + self.encoder.emit_u32(v); + } + fn emit_u16(&mut self, v: u16) { + self.encoder.emit_u16(v); + } + fn emit_u8(&mut self, v: u8) { + self.encoder.emit_u8(v); + } + fn emit_isize(&mut self, v: isize) { + self.encoder.emit_isize(v); + } + fn emit_i128(&mut self, v: i128) { + self.encoder.emit_i128(v); + } + fn emit_i64(&mut self, v: i64) { + self.encoder.emit_i64(v); + } + fn emit_i32(&mut self, v: i32) { + self.encoder.emit_i32(v); + } + fn emit_i16(&mut self, v: i16) { + self.encoder.emit_i16(v); + } + fn emit_i8(&mut self, v: i8) { + self.encoder.emit_i8(v); + } + fn emit_raw_bytes(&mut self, v: &[u8]) { + self.encoder.emit_raw_bytes(v); + } +} + +impl<'a, 'tcx> SpanEncoder for OffloadManifestEncoder<'a, 'tcx> { + fn encode_span(&mut self, _span: Span) { + // Spans are not needed in the manifest, encode a dummy span. + self.emit_usize(0); + self.emit_usize(0); + self.emit_u32(0); + } + + fn encode_symbol(&mut self, sym: rustc_span::Symbol) { + sym.as_str().encode(self); + } + + fn encode_byte_symbol(&mut self, byte_sym: ByteSymbol) { + let bytes = byte_sym.as_byte_str(); + debug_assert!( + bytes.is_empty(), + "ByteSymbols with content are not expected in offload manifests" + ); + self.emit_usize(bytes.len()); + self.emit_raw_bytes(bytes.as_ref()) + } + + fn encode_expn_id(&mut self, _expn_id: rustc_span::ExpnId) { + self.emit_u32(0); + } + + fn encode_syntax_context(&mut self, _syntax_context: SyntaxContext) { + self.emit_u32(0); + } + + fn encode_crate_num(&mut self, crate_num: rustc_span::def_id::CrateNum) { + crate_num.as_u32().encode(self); + } + + fn encode_def_index(&mut self, def_index: rustc_span::def_id::DefIndex) { + def_index.as_u32().encode(self); + } + + fn encode_def_id(&mut self, def_id: rustc_span::def_id::DefId) { + let crate_name = self.tcx.crate_name(def_id.krate); + let def_path = self.tcx.def_path(def_id); + crate_name.encode(self); + def_path.to_string_no_crate_verbose().encode(self); + } +} + +impl<'a, 'tcx> TyEncoder<'tcx> for OffloadManifestEncoder<'a, 'tcx> { + const CLEAR_CROSS_CRATE: bool = true; + + fn position(&self) -> usize { + self.encoder.position() + } + + fn type_shorthands(&mut self) -> &mut FxHashMap, usize> { + &mut self.type_shorthands + } + + fn predicate_shorthands(&mut self) -> &mut FxHashMap, usize> { + &mut self.predicate_shorthands + } + + fn encode_alloc_id(&mut self, _alloc_id: &rustc_middle::mir::interpret::AllocId) { + // AllocIds are not expected in the manifest. + } +} + +const UNRESOLVED_DEF_ID: DefId = DefId { + krate: rustc_span::def_id::CrateNum::MAX, + index: rustc_span::def_id::DefIndex::from_u32(0), +}; + +/// Decoder used to read the offload monomorphization manifest. +pub(crate) struct OffloadManifestDecoder<'a, 'tcx> { + decoder: MemDecoder<'a>, + type_shorthands: Lock>>, + #[allow(dead_code)] + predicate_shorthands: Lock>>, + tcx: TyCtxt<'tcx>, + /// Map from (crate name, DefPath string) to DefId, used to resolve DefIds + /// across compilation sessions where StableCrateId differs. + def_path_map: Lock>>, +} + +impl<'a, 'tcx> OffloadManifestDecoder<'a, 'tcx> { + pub(crate) fn new(data: &'a [u8], tcx: TyCtxt<'tcx>) -> Result { + let decoder = MemDecoder::new(data, 0)?; + Ok(OffloadManifestDecoder { + decoder, + type_shorthands: Lock::new(FxHashMap::default()), + predicate_shorthands: Lock::new(FxHashMap::default()), + tcx, + def_path_map: Lock::new(None), + }) + } + + /// (crate name, DefPath) -> DefId map for resolving cross-session DefIds. + fn get_or_build_def_path_map(&self) -> FxHashMap<(Symbol, String), DefId> { + let mut guard = self.def_path_map.lock(); + if let Some(map) = guard.as_ref() { + return map.clone(); + } + let map = Self::build_def_path_map(self.tcx); + *guard = Some(map.clone()); + map + } + + /// Build a (crate name, DefPath) -> DefId map. Owns the format details. + fn build_def_path_map(tcx: TyCtxt<'tcx>) -> FxHashMap<(Symbol, String), DefId> { + let mut map: FxHashMap<(Symbol, String), DefId> = FxHashMap::default(); + + let local_crate_name = tcx.crate_name(LOCAL_CRATE); + let krate_items = tcx.hir_crate_items(()); + let local_def_ids = krate_items + .free_items() + .map(|id| id.owner_id.to_def_id()) + .chain(krate_items.trait_items().map(|id| id.owner_id.to_def_id())) + .chain(krate_items.impl_items().map(|id| id.owner_id.to_def_id())) + .chain(krate_items.foreign_items().map(|id| id.owner_id.to_def_id())); + for item_id in local_def_ids { + let def_id = item_id; + let def_path = tcx.def_path(def_id); + map.insert((local_crate_name, def_path.to_string_no_crate_verbose()), def_id); + } + + for &cnum in tcx.crates(()) { + if cnum == LOCAL_CRATE { + continue; + } + let crate_name = tcx.crate_name(cnum); + let num_defs = tcx.num_extern_def_ids(cnum); + for i in 0..num_defs { + let def_id = DefId { krate: cnum, index: DefIndex::from_usize(i) }; + let def_path = tcx.def_path(def_id); + map.entry((crate_name, def_path.to_string_no_crate_verbose())).or_insert(def_id); + } + } + + map + } +} + +impl<'a, 'tcx> Decoder for OffloadManifestDecoder<'a, 'tcx> { + fn read_usize(&mut self) -> usize { + self.decoder.read_usize() + } + fn read_u128(&mut self) -> u128 { + self.decoder.read_u128() + } + fn read_u64(&mut self) -> u64 { + self.decoder.read_u64() + } + fn read_u32(&mut self) -> u32 { + self.decoder.read_u32() + } + fn read_u16(&mut self) -> u16 { + self.decoder.read_u16() + } + fn read_u8(&mut self) -> u8 { + self.decoder.read_u8() + } + fn read_isize(&mut self) -> isize { + self.decoder.read_isize() + } + fn read_i128(&mut self) -> i128 { + self.decoder.read_i128() + } + fn read_i64(&mut self) -> i64 { + self.decoder.read_i64() + } + fn read_i32(&mut self) -> i32 { + self.decoder.read_i32() + } + fn read_i16(&mut self) -> i16 { + self.decoder.read_i16() + } + fn read_i8(&mut self) -> i8 { + self.decoder.read_i8() + } + fn read_raw_bytes(&mut self, len: usize) -> &[u8] { + self.decoder.read_raw_bytes(len) + } + fn peek_byte(&self) -> u8 { + self.decoder.peek_byte() + } + fn position(&self) -> usize { + self.decoder.position() + } +} + +impl<'a, 'tcx> BlobDecoder for OffloadManifestDecoder<'a, 'tcx> { + fn decode_symbol(&mut self) -> rustc_span::Symbol { + let s: String = Decodable::decode(self); + rustc_span::Symbol::intern(&s) + } + + fn decode_byte_symbol(&mut self) -> ByteSymbol { + let len = self.read_usize(); + let bytes = self.read_raw_bytes(len); + ByteSymbol::intern(bytes) + } + + fn decode_def_index(&mut self) -> rustc_span::def_id::DefIndex { + let v = self.read_u32(); + rustc_span::def_id::DefIndex::from_u32(v) + } +} + +impl<'a, 'tcx> SpanDecoder for OffloadManifestDecoder<'a, 'tcx> { + fn decode_span(&mut self) -> Span { + let lo = self.read_usize(); + let hi = self.read_usize(); + let _ctxt = self.read_u32(); + Span::new(BytePos::from_usize(lo), BytePos::from_usize(hi), SyntaxContext::root(), None) + } + + fn decode_expn_id(&mut self) -> rustc_span::ExpnId { + let _ = self.read_u32(); + rustc_span::ExpnId::root() + } + + fn decode_syntax_context(&mut self) -> SyntaxContext { + let _ = self.read_u32(); + SyntaxContext::root() + } + + fn decode_crate_num(&mut self) -> rustc_span::def_id::CrateNum { + let v = self.read_u32(); + rustc_span::def_id::CrateNum::from_u32(v) + } + + fn decode_def_id(&mut self) -> rustc_span::def_id::DefId { + let crate_name: String = Decodable::decode(self); + let crate_name = Symbol::intern(&crate_name); + let def_path_str: String = Decodable::decode(self); + let map = self.get_or_build_def_path_map(); + map.get(&(crate_name, def_path_str)).copied().unwrap_or(UNRESOLVED_DEF_ID) + } + + fn decode_attr_id(&mut self) -> rustc_ast::AttrId { + self.tcx.dcx().fatal("AttrIds are not expected in offload manifests"); + } +} + +impl<'a, 'tcx> TyDecoder<'tcx> for OffloadManifestDecoder<'a, 'tcx> { + const CLEAR_CROSS_CRATE: bool = true; + + fn interner(&self) -> TyCtxt<'tcx> { + self.tcx + } + + fn cached_ty_for_shorthand(&mut self, shorthand: usize, or_insert_with: F) -> Ty<'tcx> + where + F: FnOnce(&mut Self) -> Ty<'tcx>, + { + if let Some(ty) = self.type_shorthands.lock().get(&shorthand) { + return *ty; + } + let ty = or_insert_with(self); + self.type_shorthands.lock().insert(shorthand, ty); + ty + } + + fn with_position(&mut self, pos: usize, f: F) -> R + where + F: FnOnce(&mut Self) -> R, + { + let new_decoder = self.decoder.split_at(pos); + let old_decoder = std::mem::replace(&mut self.decoder, new_decoder); + let result = f(self); + self.decoder = old_decoder; + result + } + + fn decode_alloc_id(&mut self) -> rustc_middle::mir::interpret::AllocId { + self.tcx.dcx().fatal("AllocIds are not expected in offload manifests"); + } +} + +/// Write a list of offload kernel instances to the manifest file. +pub(crate) fn write_manifest<'tcx>( + path: &std::path::Path, + tcx: TyCtxt<'tcx>, + instances: &[ty::Instance<'tcx>], +) -> std::io::Result<()> { + let mut encoder = OffloadManifestEncoder::new(path, tcx)?; + instances.encode(&mut encoder); + encoder.finish() +} + +/// Read a list of offload kernel instances from the manifest file. +pub(crate) fn read_manifest<'tcx>( + path: &std::path::Path, + tcx: TyCtxt<'tcx>, +) -> std::io::Result>> { + let data = fs::read(path)?; + let mut decoder = OffloadManifestDecoder::new(&data, tcx) + .map_err(|_| std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid manifest"))?; + + let payload_len = decoder.decoder.len() - decoder.position(); + if payload_len == 0 { + return Ok(Vec::new()); + } + + let instances: Vec> = Decodable::decode(&mut decoder); + + let instances: Vec<_> = instances + .into_iter() + .filter(|instance| instance.def_id().krate != rustc_span::def_id::CrateNum::MAX) + .collect(); + + Ok(instances) +} diff --git a/compiler/rustc_monomorphize/src/partitioning.rs b/compiler/rustc_monomorphize/src/partitioning.rs index cdd18654f0930..1b3e411312ca6 100644 --- a/compiler/rustc_monomorphize/src/partitioning.rs +++ b/compiler/rustc_monomorphize/src/partitioning.rs @@ -844,6 +844,16 @@ fn mono_item_visibility<'tcx>( | InstanceKind::Shim(ShimKind::FnPtrAddr(..)) => return Visibility::Hidden, }; + let attrs = tcx.codegen_fn_attrs(def_id); + if attrs.flags.intersects(CodegenFnAttrFlags::OFFLOAD_KERNEL) { + *can_be_internalized = false; + return default_visibility( + tcx, + def_id, + instance.args.non_erasable_generics().next().is_some(), + ); + } + // Both the `start_fn` lang item and `main` itself should not be exported, // so we give them with `Hidden` visibility but these symbols are // only referenced from the actual `main` symbol which we unfortunately diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index 0303081e2c627..d424991f2921f 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -198,10 +198,16 @@ pub enum CoverageLevel { pub enum Offload { /// Entry point for `std::offload`, enables kernel compilation for a gpu device Device, + /// Like `Device`, but reads a manifest of required generic kernel instantiations + /// produced by a previous `HostMetadata` pass. + DeviceWithManifest(String), /// Second step in the offload pipeline, generates the host code to call kernels. Host(String), /// Test is similar to Host, but allows testing without a device artifact. Test, + /// First step in the offload pipeline: compile for the host but only emit a manifest of + /// kernel instantiations required by the host code. + HostMetadata(String), } /// The different settings that the `-Z codegen-emit-retag` flag can have. diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index 20d1ff55eab6e..65a94bc9e314c 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -819,8 +819,7 @@ mod desc { "a comma-separated list of strings, with elements beginning with + or -"; pub(crate) const parse_pointer_authentication_list_with_polarity: &str = "a comma-separated list of options, each of the form `+` or `-`, where `` is one of: `aarch64-jump-table-hardening`, `auth-traps`, `calls`, `elf-got`, `function-pointer-type-discrimination`, `indirect-gotos`, `init-fini`, `init-fini-address-discrimination`, `intrinsics`, `return-addresses`, `typeinfo-vt-ptr-discrimination`, `vt-ptr-addr-discrimination` or `vt-ptr-type-discrimination`"; pub(crate) const parse_autodiff: &str = "a comma separated list of settings: `Enable`, `PrintSteps`, `PrintTA`, `PrintTAFn`, `PrintAA`, `PrintPerf`, `PrintModBefore`, `PrintModAfter`, `PrintModFinal`, `PrintPasses`, `NoPostopt`, `LooseTypes`, `Inline`, `NoTT`"; - pub(crate) const parse_offload: &str = - "a comma separated list of settings: `Host=`, `Device`, `Test`"; + pub(crate) const parse_offload: &str = "a comma separated list of settings: `Host=`, `HostMetadata=`, `Device`, `DeviceWithManifest=`, `Test`"; pub(crate) const parse_comma_list: &str = "a comma-separated list of strings"; pub(crate) const parse_opt_comma_list: &str = parse_comma_list; pub(crate) const parse_number: &str = "a number"; @@ -1514,6 +1513,13 @@ pub mod parse { return false; } } + "HostMetadata" => { + if let Some(p) = arg { + Offload::HostMetadata(p.to_string()) + } else { + return false; + } + } "Device" => { if let Some(_) = arg { // Device does not accept a value @@ -1521,6 +1527,13 @@ pub mod parse { } Offload::Device } + "DeviceWithManifest" => { + if let Some(p) = arg { + Offload::DeviceWithManifest(p.to_string()) + } else { + return false; + } + } "Test" => { if let Some(_) = arg { // Test does not accept a value diff --git a/compiler/rustc_symbol_mangling/src/lib.rs b/compiler/rustc_symbol_mangling/src/lib.rs index 482848578a81b..a6b7485223f11 100644 --- a/compiler/rustc_symbol_mangling/src/lib.rs +++ b/compiler/rustc_symbol_mangling/src/lib.rs @@ -106,6 +106,13 @@ pub mod test; pub use v0::{mangle_cgu, mangle_internal_symbol}; +/// Offload kernels need custom v0 symbol treatment because the host +/// and device compilation passes run with different `stable_crate_id`s +/// so they cannot rely on the regular export-hash path. +pub fn is_offload_kernel(attrs: &CodegenFnAttrs) -> bool { + attrs.flags.intersects(CodegenFnAttrFlags::OFFLOAD_KERNEL) +} + /// This function computes the symbol name for the given `instance` and the /// given instantiating crate. That is, if you know that instance X is /// instantiated in crate Y, this is the symbol name this instance would have. @@ -121,6 +128,18 @@ pub fn provide(providers: &mut Providers) { *providers = Providers { symbol_name: symbol_name_provider, ..*providers }; } +/// Compute the v0 symbol name for an offload kernel instance. Forces +/// `is_exportable: true` to omit the `stable_crate_id` disambiguator +/// (which differs between host and device passes). +pub fn mangle_offload_export<'tcx>(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) -> String { + let instantiating_crate = if is_generic(instance) { + Some(instance.upstream_monomorphization(tcx).unwrap_or(LOCAL_CRATE)) + } else { + None + }; + v0::mangle(tcx, instance, instantiating_crate, true) +} + // The `symbol_name` query provides the symbol name for calling a given // instance from the local crate. In particular, it will also look up the // correct symbol name of instances from upstream crates. @@ -293,45 +312,54 @@ fn compute_symbol_name<'tcx>( tcx.symbol_mangling_version(mangling_version_crate) }; - let symbol = match tcx.is_exportable(def_id) { - true => format!( - "{}.{}", - v0::mangle(tcx, instance, instantiating_crate, true), - export::compute_hash_of_export_fn(tcx, instance) - ), - false => match mangling_version { - SymbolManglingVersion::Legacy => { - let mangled_name = legacy::mangle(tcx, instance, instantiating_crate); - - let mangled_name_too_long = { - // The PDB debug info format cannot store mangled symbol names for which its - // internal record exceeds u16::MAX bytes, a limit multiple Rust projects have been - // hitting due to the verbosity of legacy name mangling. Depending on the linker version - // in use, such symbol names can lead to linker crashes or incomprehensible linker error - // about a limit being hit. - // Mangle those symbols with v0 mangling instead, which gives us more room to breathe - // as v0 mangling is more compact. - // Empirical testing has shown the limit for the symbol name to be 65521 bytes; use - // 65000 bytes to leave some room for prefixes / suffixes as well as unknown scenarios - // with a different limit. - const MAX_SYMBOL_LENGTH: usize = 65000; - - tcx.sess.target.uses_pdb_debuginfo() && mangled_name.len() > MAX_SYMBOL_LENGTH - }; - - if mangled_name_too_long { - v0::mangle(tcx, instance, instantiating_crate, false) - } else { - mangled_name + // Offload kernels must omit the stable_crate_id disambiguator because + // host and device passes have different stable_crate_ids. + let is_offload_kernel = + tcx.codegen_fn_attrs(def_id).flags.contains(CodegenFnAttrFlags::OFFLOAD_KERNEL); + let symbol = if is_offload_kernel { + v0::mangle(tcx, instance, instantiating_crate, true) + } else { + match tcx.is_exportable(def_id) { + true => format!( + "{}.{}", + v0::mangle(tcx, instance, instantiating_crate, true), + export::compute_hash_of_export_fn(tcx, instance) + ), + false => match mangling_version { + SymbolManglingVersion::Legacy => { + let mangled_name = legacy::mangle(tcx, instance, instantiating_crate); + + let mangled_name_too_long = { + // The PDB debug info format cannot store mangled symbol names for which its + // internal record exceeds u16::MAX bytes, a limit multiple Rust projects have been + // hitting due to the verbosity of legacy name mangling. Depending on the linker version + // in use, such symbol names can lead to linker crashes or incomprehensible linker error + // about a limit being hit. + // Mangle those symbols with v0 mangling instead, which gives us more room to breathe + // as v0 mangling is more compact. + // Empirical testing has shown the limit for the symbol name to be 65521 bytes; use + // 65000 bytes to leave some room for prefixes / suffixes as well as unknown scenarios + // with a different limit. + const MAX_SYMBOL_LENGTH: usize = 65000; + + tcx.sess.target.uses_pdb_debuginfo() + && mangled_name.len() > MAX_SYMBOL_LENGTH + }; + + if mangled_name_too_long { + v0::mangle(tcx, instance, instantiating_crate, false) + } else { + mangled_name + } } - } - SymbolManglingVersion::V0 => v0::mangle(tcx, instance, instantiating_crate, false), - SymbolManglingVersion::Hashed => { - hashed::mangle(tcx, instance, instantiating_crate, || { - v0::mangle(tcx, instance, instantiating_crate, false) - }) - } - }, + SymbolManglingVersion::V0 => v0::mangle(tcx, instance, instantiating_crate, false), + SymbolManglingVersion::Hashed => { + hashed::mangle(tcx, instance, instantiating_crate, || { + v0::mangle(tcx, instance, instantiating_crate, false) + }) + } + }, + } }; debug_assert!( diff --git a/tests/codegen-llvm/gpu_offload/control_flow.rs b/tests/codegen-llvm/gpu_offload/control_flow.rs index 605a6f08843c3..da997de53a428 100644 --- a/tests/codegen-llvm/gpu_offload/control_flow.rs +++ b/tests/codegen-llvm/gpu_offload/control_flow.rs @@ -10,6 +10,8 @@ #![feature(core_intrinsics)] #![no_main] +// CHECK: @.offload_sizes.[[K:[^ ]*foo]] = private unnamed_addr constant + // CHECK: define{{( dso_local)?}} void @main() // CHECK-NOT: define // CHECK: %.offload_baseptrs = alloca [1 x ptr], align 8 @@ -18,9 +20,9 @@ // CHECK: br label %bb3 // CHECK-NOT define // CHECK: bb3 -// CHECK: call void @__tgt_target_data_begin_mapper(ptr nonnull @anon.{{.*}}.1, i64 -1, i32 1, ptr nonnull %.offload_baseptrs, ptr nonnull %.offload_ptrs, ptr nonnull @.offload_sizes.foo, ptr nonnull @.offload_maptypes.foo.begin, ptr null, ptr null) -// CHECK: = call i32 @__tgt_target_kernel(ptr nonnull @anon.{{.*}}.1, i64 -1, i32 256, i32 32, ptr nonnull @.foo.region_id, ptr nonnull %kernel_args) -// CHECK-NEXT: call void @__tgt_target_data_end_mapper(ptr nonnull @anon.{{.*}}.1, i64 -1, i32 1, ptr nonnull %.offload_baseptrs, ptr nonnull %.offload_ptrs, ptr nonnull @.offload_sizes.foo, ptr nonnull @.offload_maptypes.foo.end, ptr null, ptr null) +// CHECK: call void @__tgt_target_data_begin_mapper(ptr nonnull @anon.{{.*}}.1, i64 -1, i32 1, ptr nonnull %.offload_baseptrs, ptr nonnull %.offload_ptrs, ptr nonnull @.offload_sizes.[[K]], ptr nonnull @.offload_maptypes.[[K]].begin, ptr null, ptr null) +// CHECK: = call i32 @__tgt_target_kernel(ptr nonnull @anon.{{.*}}.1, i64 -1, i32 256, i32 32, ptr nonnull @.[[K]].region_id, ptr nonnull %kernel_args) +// CHECK-NEXT: call void @__tgt_target_data_end_mapper(ptr nonnull @anon.{{.*}}.1, i64 -1, i32 1, ptr nonnull %.offload_baseptrs, ptr nonnull %.offload_ptrs, ptr nonnull @.offload_sizes.[[K]], ptr nonnull @.offload_maptypes.[[K]].end, ptr null, ptr null) #[unsafe(no_mangle)] unsafe fn main() { let A = [1.0, 1.0, 1.0, 1.0, 1.0, 1.0]; diff --git a/tests/codegen-llvm/gpu_offload/slice_host.rs b/tests/codegen-llvm/gpu_offload/slice_host.rs index 0f27821ef765c..dfc7ec545630c 100644 --- a/tests/codegen-llvm/gpu_offload/slice_host.rs +++ b/tests/codegen-llvm/gpu_offload/slice_host.rs @@ -18,10 +18,10 @@ // CHECK: define{{( dso_local)?}} void @main() // CHECK: %.offload_sizes = alloca [2 x i64], align 8 -// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr {{.*}} %.offload_sizes, ptr {{.*}} @.offload_sizes.foo, i64 16, i1 false) +// CHECK: call void @llvm.memcpy.p0.p0.i64(ptr {{.*}} %.offload_sizes, ptr {{.*}} @.offload_sizes.[[K]], i64 16, i1 false) // CHECK: store i64 16, ptr %.offload_sizes, align 8 // CHECK: call void @__tgt_target_data_begin_mapper(ptr nonnull @anon.[[ID]].1, i64 -1, i32 2, ptr nonnull %.offload_baseptrs, ptr nonnull %.offload_ptrs, ptr nonnull %.offload_sizes, ptr nonnull @.offload_maptypes.[[K]].begin, ptr null, ptr null) -// CHECK: call i32 @__tgt_target_kernel(ptr nonnull @anon.[[ID]].1, i64 -1, i32 1, i32 1, ptr nonnull @.foo.region_id, ptr nonnull %kernel_args) +// CHECK: call i32 @__tgt_target_kernel(ptr nonnull @anon.[[ID]].1, i64 -1, i32 1, i32 1, ptr nonnull @.[[K]].region_id, ptr nonnull %kernel_args) // CHECK-NEXT: call void @__tgt_target_data_end_mapper(ptr nonnull @anon.[[ID]].1, i64 -1, i32 2, ptr nonnull %.offload_baseptrs, ptr nonnull %.offload_ptrs, ptr nonnull %.offload_sizes, ptr nonnull @.offload_maptypes.[[K]].end, ptr null, ptr null) #[unsafe(no_mangle)] diff --git a/tests/pretty/offload/offload_kernel.device.pp b/tests/pretty/offload/offload_kernel.device.pp index 9c8e6edaf2ed3..6f6c4e9693ddf 100644 --- a/tests/pretty/offload/offload_kernel.device.pp +++ b/tests/pretty/offload/offload_kernel.device.pp @@ -18,7 +18,6 @@ use std::offload::offload_kernel; #[rustc_offload_kernel] -#[unsafe(no_mangle)] unsafe extern "gpu-kernel" fn foo(a: &[f32], b: &[f32], c: *mut f32) { *c = a[0] + b[0]; } diff --git a/tests/pretty/offload/offload_kernel.host.pp b/tests/pretty/offload/offload_kernel.host.pp index cf60ee9f8138b..c35cf9474f709 100644 --- a/tests/pretty/offload/offload_kernel.host.pp +++ b/tests/pretty/offload/offload_kernel.host.pp @@ -17,7 +17,7 @@ use std::offload::offload_kernel; -#[unsafe(no_mangle)] +#[rustc_offload_kernel] #[inline(never)] fn foo(_: &[f32], _: &[f32], _: *mut f32) { From a5a9f8cf4f8923efc976c1561e408bb30eb57183 Mon Sep 17 00:00:00 2001 From: Marcelo Dominguez Date: Tue, 21 Jul 2026 20:12:30 +0300 Subject: [PATCH 20/31] Remove codegen and move manifest out of query --- .../src/back/symbol_export.rs | 10 +--- compiler/rustc_codegen_ssa/src/back/write.rs | 23 ---------- compiler/rustc_codegen_ssa/src/base.rs | 20 +------- compiler/rustc_interface/src/passes.rs | 18 +++++++- .../src/middle/codegen_fn_attrs.rs | 2 + compiler/rustc_middle/src/mono.rs | 6 --- compiler/rustc_monomorphize/src/collector.rs | 33 ------------- compiler/rustc_monomorphize/src/lib.rs | 4 ++ .../src/offload_manifest.rs | 46 +++++++++++++++++++ 9 files changed, 71 insertions(+), 91 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/back/symbol_export.rs b/compiler/rustc_codegen_ssa/src/back/symbol_export.rs index bdff48ced55b6..6109231edb5f9 100644 --- a/compiler/rustc_codegen_ssa/src/back/symbol_export.rs +++ b/compiler/rustc_codegen_ssa/src/back/symbol_export.rs @@ -633,19 +633,11 @@ fn symbol_export_level(tcx: TyCtxt<'_>, sym_def_id: DefId) -> SymbolExportLevel // are not considered for export let codegen_fn_attrs = tcx.codegen_fn_attrs(sym_def_id); let is_extern = codegen_fn_attrs.contains_extern_indicator(); - let is_device_offload = tcx.sess.opts.unstable_opts.offload.iter().any(|o| { - matches!( - o, - rustc_session::config::Offload::DeviceWithManifest(_) - | rustc_session::config::Offload::Device - ) - }); - let is_offload = is_device_offload && is_offload_kernel(codegen_fn_attrs); let std_internal = codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::RUSTC_STD_INTERNAL_SYMBOL); let eii = codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::EXTERNALLY_IMPLEMENTABLE_ITEM); - if (is_extern && !std_internal && !eii) || is_offload { + if is_extern && !std_internal && !eii { let target = &tcx.sess.target.llvm_target; // WebAssembly cannot export data symbols, so reduce their export level // FIXME(jdonszelmann) don't do a substring match here. diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs index 46ac35cb7dbc5..bd1683b3acc34 100644 --- a/compiler/rustc_codegen_ssa/src/back/write.rs +++ b/compiler/rustc_codegen_ssa/src/back/write.rs @@ -464,29 +464,6 @@ pub(crate) fn start_async_codegen( } } -/// Create an `OngoingCodegen` that has no coordinator thread and will finish -/// immediately when joined. This is used for the offload host-metadata pass, -/// that only need to run the monomorphization collector. -pub(crate) fn empty_ongoing_codegen( - backend: B, - tcx: TyCtxt<'_>, -) -> OngoingCodegen { - let (coordinator_send, _) = channel::>(); - let (codegen_worker_send, codegen_worker_receive) = channel(); - drop(codegen_worker_send); - - let (shared_emitter, shared_emitter_main) = SharedEmitter::new(); - drop(shared_emitter); - - OngoingCodegen { - backend, - codegen_worker_receive, - shared_emitter_main, - coordinator: Coordinator { sender: coordinator_send, future: None, phantom: PhantomData }, - output_filenames: Arc::clone(tcx.output_filenames(())), - } -} - fn copy_all_cgu_workproducts_to_incr_comp_cache_dir( sess: &Session, incr_comp_session: Option<&IncrCompSession>, diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs index d615c8daab550..0468e3de18d8b 100644 --- a/compiler/rustc_codegen_ssa/src/base.rs +++ b/compiler/rustc_codegen_ssa/src/base.rs @@ -41,9 +41,8 @@ use tracing::{debug, info}; use crate::assert_module_sources::CguReuse; use crate::back::link::are_upstream_rust_objects_already_included; use crate::back::write::{ - ComputedLtoType, OngoingCodegen, compute_per_cgu_lto_type, empty_ongoing_codegen, - start_async_codegen, submit_codegened_module_to_llvm, submit_post_lto_module_to_llvm, - submit_pre_lto_module_to_llvm, + ComputedLtoType, OngoingCodegen, compute_per_cgu_lto_type, start_async_codegen, + submit_codegened_module_to_llvm, submit_post_lto_module_to_llvm, submit_pre_lto_module_to_llvm, }; use crate::common::{self, IntPredicate, RealPredicate, TypeKind}; use crate::meth::load_vtable; @@ -724,21 +723,6 @@ pub fn codegen_crate< tcx.dcx().emit_fatal(diagnostics::CpuRequired); } - // A `HostMetadata` pass only exists to collect the set of generic kernel instantiations - // required by the host and write the offload manifest. - let is_host_metadata = tcx - .sess - .opts - .unstable_opts - .offload - .iter() - .any(|o| matches!(o, rustc_session::config::Offload::HostMetadata(_))); - - if is_host_metadata { - let _ = tcx.collect_and_partition_mono_items(()); - return empty_ongoing_codegen(backend, tcx); - } - if let Some(target_cpu) = &tcx.sess.opts.cg.target_cpu && tcx.sess.target.unsupported_cpus.contains(&target_cpu.into()) { diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs index 0e5bf519fab18..d8e5243148847 100644 --- a/compiler/rustc_interface/src/passes.rs +++ b/compiler/rustc_interface/src/passes.rs @@ -1313,11 +1313,25 @@ pub(crate) fn start_codegen<'tcx>( let metadata = rustc_metadata::fs::encode_and_write_metadata(tcx); + let is_host_metadata = tcx + .sess + .opts + .unstable_opts + .offload + .iter() + .any(|o| matches!(o, rustc_session::config::Offload::HostMetadata(_))); + let codegen = tcx.sess.time("codegen_crate", || { - if tcx.sess.opts.unstable_opts.no_codegen || !tcx.sess.opts.output_types.should_codegen() { - // Skip crate items and just output metadata in -Z no-codegen mode. + if tcx.sess.opts.unstable_opts.no_codegen + || !tcx.sess.opts.output_types.should_codegen() + || is_host_metadata + { tcx.sess.dcx().abort_if_errors(); + if is_host_metadata { + rustc_monomorphize::write_host_metadata_offload_manifest(tcx); + } + // Linker::link will skip join_codegen in case of a CodegenResults Any value. Box::new(CompiledModules { modules: vec![], allocator_module: None }) } else { diff --git a/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs b/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs index b6ae4a98a34e3..ef9043101dbbb 100644 --- a/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs +++ b/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs @@ -295,6 +295,8 @@ impl CodegenFnAttrs { // note: for these we do also set a symbol name so technically also handled by the // condition below. However, I think that regardless these should be treated as extern. || self.flags.contains(CodegenFnAttrFlags::EXTERNALLY_IMPLEMENTABLE_ITEM) + // `#[rustc_offload_kernel]`: this item is an externally-launched kernel entry point. + || self.flags.contains(CodegenFnAttrFlags::OFFLOAD_KERNEL) || self.symbol_name.is_some() || match self.linkage { // These are private, so make sure we don't try to consider diff --git a/compiler/rustc_middle/src/mono.rs b/compiler/rustc_middle/src/mono.rs index 1d15844f4ca96..dc9a94f79aa0f 100644 --- a/compiler/rustc_middle/src/mono.rs +++ b/compiler/rustc_middle/src/mono.rs @@ -160,12 +160,6 @@ impl<'tcx> MonoItem<'tcx> { return InstantiationMode::GloballyShared { may_conflict: false }; } - // Offload kernels are looked up by symbol name at runtime by the host. - // They must be emitted exactly once with external linkage. - if codegen_fn_attrs.flags.intersects(CodegenFnAttrFlags::OFFLOAD_KERNEL) { - return InstantiationMode::GloballyShared { may_conflict: false }; - } - // This is technically a heuristic even though it's in the "not a heuristic" part of // instantiation mode selection. // It is surely possible to untangle this; the root problem is that the way we instantiate diff --git a/compiler/rustc_monomorphize/src/collector.rs b/compiler/rustc_monomorphize/src/collector.rs index eb5741f68746f..69e532fd1e2df 100644 --- a/compiler/rustc_monomorphize/src/collector.rs +++ b/compiler/rustc_monomorphize/src/collector.rs @@ -867,7 +867,6 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MirUsedCollector<'a, 'tcx> { &mut self.used_items, ); - // TODO(Sa4dUs): check why it only collects non generic fns if let ty::FnDef(def_id, _) = *callee_ty.kind() && self.tcx.is_intrinsic(def_id, rustc_span::sym::offload) && let Some(kernel) = args.first() @@ -1925,38 +1924,6 @@ pub(crate) fn collect_crate_mono_items<'tcx>( state.visited.into_inner().into_sorted(&mut hcx, true) }); - // Write out the offload manifest of required generic kernel instantiations. - if let Some(path) = tcx.sess.opts.unstable_opts.offload.iter().find_map(|o| { - if let rustc_session::config::Offload::HostMetadata(p) = o { Some(p) } else { None } - }) { - let instances: Vec> = mono_items - .iter() - .filter_map(|item| { - if let MonoItem::Fn(instance) = item { - if tcx - .codegen_fn_attrs(instance.def_id()) - .flags - .contains(CodegenFnAttrFlags::OFFLOAD_KERNEL) - { - Some(*instance) - } else { - None - } - } else { - None - } - }) - .collect(); - if let Err(e) = - crate::offload_manifest::write_manifest(std::path::Path::new(path), tcx, &instances) - { - tcx.dcx().emit_fatal(crate::diagnostics::OffloadManifestWriteError { - path: path.clone(), - err: e.to_string(), - }); - } - } - (mono_items, state.usage_map.into_inner()) } diff --git a/compiler/rustc_monomorphize/src/lib.rs b/compiler/rustc_monomorphize/src/lib.rs index a15ebe33651c6..a7d1119dd064c 100644 --- a/compiler/rustc_monomorphize/src/lib.rs +++ b/compiler/rustc_monomorphize/src/lib.rs @@ -20,6 +20,10 @@ mod offload_manifest; mod partitioning; mod util; +// Exposed so `rustc_codegen_ssa::base::codegen_crate` can trigger the +// host-metadata manifest write. +pub use offload_manifest::write_host_metadata_offload_manifest; + fn custom_coerce_unsize_info<'tcx>( tcx: TyCtxtAt<'tcx>, source_ty: Ty<'tcx>, diff --git a/compiler/rustc_monomorphize/src/offload_manifest.rs b/compiler/rustc_monomorphize/src/offload_manifest.rs index adfbebc435568..60bf8373f8445 100644 --- a/compiler/rustc_monomorphize/src/offload_manifest.rs +++ b/compiler/rustc_monomorphize/src/offload_manifest.rs @@ -9,6 +9,8 @@ use std::fs; use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::sync::Lock; use rustc_hir::def_id::{DefId, DefIndex, LOCAL_CRATE}; +use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; +use rustc_middle::mono::MonoItem; use rustc_middle::ty::codec::{TyDecoder, TyEncoder}; use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_serialize::opaque::{FileEncoder, MemDecoder}; @@ -371,6 +373,50 @@ pub(crate) fn write_manifest<'tcx>( encoder.finish() } +/// Write out the offload host-metadata manifest for `mono_items`. No-op unless +/// the session was invoked with `-Zoffload=HostMetadata=`. +pub fn write_host_metadata_offload_manifest<'tcx>(tcx: TyCtxt<'tcx>) { + let Some(path) = tcx.sess.opts.unstable_opts.offload.iter().find_map(|o| { + if let rustc_session::config::Offload::HostMetadata(p) = o { Some(p) } else { None } + }) else { + return; + }; + + let partitions = tcx.collect_and_partition_mono_items(()); + let mono_items: Vec> = partitions + .codegen_units + .iter() + .flat_map(|cgu| cgu.items().iter()) + .map(|(item, _)| *item) + .collect(); + + let instances: Vec> = mono_items + .iter() + .filter_map(|item| { + if let MonoItem::Fn(instance) = item { + if tcx + .codegen_fn_attrs(instance.def_id()) + .flags + .contains(CodegenFnAttrFlags::OFFLOAD_KERNEL) + { + Some(*instance) + } else { + None + } + } else { + None + } + }) + .collect(); + + if let Err(e) = write_manifest(std::path::Path::new(path), tcx, &instances) { + tcx.dcx().emit_fatal(crate::diagnostics::OffloadManifestWriteError { + path: path.clone(), + err: e.to_string(), + }); + } +} + /// Read a list of offload kernel instances from the manifest file. pub(crate) fn read_manifest<'tcx>( path: &std::path::Path, From 3daa8b3c7076275e41e3ccd52e7c1aa199ff36f5 Mon Sep 17 00:00:00 2001 From: Marcelo Dominguez Date: Thu, 23 Jul 2026 19:29:00 +0300 Subject: [PATCH 21/31] fix --- compiler/rustc_codegen_ssa/src/back/write.rs | 10 +--------- compiler/rustc_monomorphize/src/offload_manifest.rs | 13 +++++++++---- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs index bd1683b3acc34..2eaceb68a67a2 100644 --- a/compiler/rustc_codegen_ssa/src/back/write.rs +++ b/compiler/rustc_codegen_ssa/src/back/write.rs @@ -2112,15 +2112,7 @@ pub struct Coordinator { impl Coordinator { fn join(mut self) -> std::thread::Result, ()>> { - if let Some(future) = self.future.take() { - future.join() - } else { - // Used for passes that do not codegen anything (e.g. the offload host-metadata pass). - Ok(Ok(MaybeLtoModules::NoLto(CompiledModules { - modules: vec![], - allocator_module: None, - }))) - } + self.future.take().unwrap().join() } } diff --git a/compiler/rustc_monomorphize/src/offload_manifest.rs b/compiler/rustc_monomorphize/src/offload_manifest.rs index 60bf8373f8445..f3848832e1755 100644 --- a/compiler/rustc_monomorphize/src/offload_manifest.rs +++ b/compiler/rustc_monomorphize/src/offload_manifest.rs @@ -330,10 +330,6 @@ impl<'a, 'tcx> SpanDecoder for OffloadManifestDecoder<'a, 'tcx> { impl<'a, 'tcx> TyDecoder<'tcx> for OffloadManifestDecoder<'a, 'tcx> { const CLEAR_CROSS_CRATE: bool = true; - fn interner(&self) -> TyCtxt<'tcx> { - self.tcx - } - fn cached_ty_for_shorthand(&mut self, shorthand: usize, or_insert_with: F) -> Ty<'tcx> where F: FnOnce(&mut Self) -> Ty<'tcx>, @@ -362,6 +358,15 @@ impl<'a, 'tcx> TyDecoder<'tcx> for OffloadManifestDecoder<'a, 'tcx> { } } +impl<'a, 'tcx> rustc_middle::ty::InternerDecoder for OffloadManifestDecoder<'a, 'tcx> { + type Interner = TyCtxt<'tcx>; + + #[inline] + fn interner(&self) -> Self::Interner { + self.tcx + } +} + /// Write a list of offload kernel instances to the manifest file. pub(crate) fn write_manifest<'tcx>( path: &std::path::Path, From 1920a16080af8e3b8bac09006625c4d3a2488c3e Mon Sep 17 00:00:00 2001 From: Marcelo Dominguez Date: Fri, 24 Jul 2026 16:44:41 +0300 Subject: [PATCH 22/31] ci fix --- compiler/rustc_symbol_mangling/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_symbol_mangling/src/lib.rs b/compiler/rustc_symbol_mangling/src/lib.rs index a6b7485223f11..93db093f630bd 100644 --- a/compiler/rustc_symbol_mangling/src/lib.rs +++ b/compiler/rustc_symbol_mangling/src/lib.rs @@ -314,8 +314,8 @@ fn compute_symbol_name<'tcx>( // Offload kernels must omit the stable_crate_id disambiguator because // host and device passes have different stable_crate_ids. - let is_offload_kernel = - tcx.codegen_fn_attrs(def_id).flags.contains(CodegenFnAttrFlags::OFFLOAD_KERNEL); + let is_offload_kernel = tcx.def_kind(def_id).has_codegen_attrs() + && tcx.codegen_fn_attrs(def_id).flags.contains(CodegenFnAttrFlags::OFFLOAD_KERNEL); let symbol = if is_offload_kernel { v0::mangle(tcx, instance, instantiating_crate, true) } else { From 30dd3d499b566c4bb24cbbd197f0016e586c2a14 Mon Sep 17 00:00:00 2001 From: Marcelo Dominguez Date: Fri, 24 Jul 2026 19:25:39 +0300 Subject: [PATCH 23/31] minor fixes --- compiler/rustc_monomorphize/src/collector.rs | 3 ++- .../rustc_monomorphize/src/offload_manifest.rs | 18 ++++-------------- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/compiler/rustc_monomorphize/src/collector.rs b/compiler/rustc_monomorphize/src/collector.rs index 69e532fd1e2df..401a441a939ea 100644 --- a/compiler/rustc_monomorphize/src/collector.rs +++ b/compiler/rustc_monomorphize/src/collector.rs @@ -232,7 +232,7 @@ use rustc_middle::ty::{ use rustc_middle::util::Providers; use rustc_middle::{bug, span_bug}; use rustc_session::config::{DebugInfo, EntryFnType}; -use rustc_span::{DUMMY_SP, Span, Spanned, dummy_spanned, respan}; +use rustc_span::{DUMMY_SP, Span, Spanned, Symbol, dummy_spanned, respan}; use tracing::{debug, instrument, trace}; use crate::diagnostics::{ @@ -1488,6 +1488,7 @@ fn collect_roots(tcx: TyCtxt<'_>, mode: MonoItemCollectionStrategy) -> Vec { for instance in instances { diff --git a/compiler/rustc_monomorphize/src/offload_manifest.rs b/compiler/rustc_monomorphize/src/offload_manifest.rs index f3848832e1755..d307282a3f9b1 100644 --- a/compiler/rustc_monomorphize/src/offload_manifest.rs +++ b/compiler/rustc_monomorphize/src/offload_manifest.rs @@ -8,7 +8,7 @@ use std::fs; use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::sync::Lock; -use rustc_hir::def_id::{DefId, DefIndex, LOCAL_CRATE}; +use rustc_hir::def_id::{DefId, DefIndex, LOCAL_CRATE, StableCrateId}; use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; use rustc_middle::mono::MonoItem; use rustc_middle::ty::codec::{TyDecoder, TyEncoder}; @@ -115,7 +115,7 @@ impl<'a, 'tcx> SpanEncoder for OffloadManifestEncoder<'a, 'tcx> { } fn encode_crate_num(&mut self, crate_num: rustc_span::def_id::CrateNum) { - crate_num.as_u32().encode(self); + self.tcx.stable_crate_id(crate_num).encode(self); } fn encode_def_index(&mut self, def_index: rustc_span::def_id::DefIndex) { @@ -310,8 +310,8 @@ impl<'a, 'tcx> SpanDecoder for OffloadManifestDecoder<'a, 'tcx> { } fn decode_crate_num(&mut self) -> rustc_span::def_id::CrateNum { - let v = self.read_u32(); - rustc_span::def_id::CrateNum::from_u32(v) + let stable_id: StableCrateId = Decodable::decode(self); + self.tcx.stable_crate_id_to_crate_num(stable_id) } fn decode_def_id(&mut self) -> rustc_span::def_id::DefId { @@ -431,17 +431,7 @@ pub(crate) fn read_manifest<'tcx>( let mut decoder = OffloadManifestDecoder::new(&data, tcx) .map_err(|_| std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid manifest"))?; - let payload_len = decoder.decoder.len() - decoder.position(); - if payload_len == 0 { - return Ok(Vec::new()); - } - let instances: Vec> = Decodable::decode(&mut decoder); - let instances: Vec<_> = instances - .into_iter() - .filter(|instance| instance.def_id().krate != rustc_span::def_id::CrateNum::MAX) - .collect(); - Ok(instances) } From 55c63da34578d02154d887842310fae685406159 Mon Sep 17 00:00:00 2001 From: Marcelo Dominguez Date: Wed, 5 Aug 2026 18:48:25 +0300 Subject: [PATCH 24/31] Add tests remove extra mode and other fixes --- compiler/rustc_builtin_macros/src/offload.rs | 7 +--- compiler/rustc_codegen_llvm/src/back/write.rs | 10 +---- compiler/rustc_codegen_llvm/src/intrinsic.rs | 2 +- .../src/back/symbol_export.rs | 28 +++++++------- compiler/rustc_interface/src/tests.rs | 2 +- compiler/rustc_monomorphize/src/collector.rs | 19 +++++++++- .../src/offload_manifest.rs | 3 +- compiler/rustc_session/src/config.rs | 8 ++-- compiler/rustc_session/src/options.rs | 17 ++------- .../offload-generic-manifest/generic.rs | 12 ++++++ .../offload-generic-manifest/rmake.rs | 37 +++++++++++++++++++ tests/ui/offload/duplicate_kernel.rs | 20 ++++++++++ tests/ui/offload/duplicate_kernel.stderr | 8 ++++ 13 files changed, 124 insertions(+), 49 deletions(-) create mode 100644 tests/run-make/offload-generic-manifest/generic.rs create mode 100644 tests/run-make/offload-generic-manifest/rmake.rs create mode 100644 tests/ui/offload/duplicate_kernel.rs create mode 100644 tests/ui/offload/duplicate_kernel.stderr diff --git a/compiler/rustc_builtin_macros/src/offload.rs b/compiler/rustc_builtin_macros/src/offload.rs index 96cb9372d3e09..006332b8c40c9 100644 --- a/compiler/rustc_builtin_macros/src/offload.rs +++ b/compiler/rustc_builtin_macros/src/offload.rs @@ -9,12 +9,7 @@ use thin_vec::thin_vec; use crate::diagnostics; fn compile_for_device(ecx: &mut ExtCtxt<'_>) -> bool { - ecx.sess - .opts - .unstable_opts - .offload - .iter() - .any(|o| matches!(o, Offload::Device | Offload::DeviceWithManifest(_))) + ecx.sess.opts.unstable_opts.offload.iter().any(|o| matches!(o, Offload::Device(_))) } fn outer_normal_attr(normal: &Box, id: ast::AttrId, span: Span) -> ast::Attribute { diff --git a/compiler/rustc_codegen_llvm/src/back/write.rs b/compiler/rustc_codegen_llvm/src/back/write.rs index 80d5ff31c730e..66b51d9184a79 100644 --- a/compiler/rustc_codegen_llvm/src/back/write.rs +++ b/compiler/rustc_codegen_llvm/src/back/write.rs @@ -743,10 +743,7 @@ pub(crate) unsafe fn llvm_optimize( } if cgcx.target_is_like_gpu - && config - .offload - .iter() - .any(|o| matches!(o, config::Offload::Device | config::Offload::DeviceWithManifest(_))) + && config.offload.iter().any(|o| matches!(o, config::Offload::Device(_))) { let cx = SimpleCx::new(module.module_llvm.llmod(), module.module_llvm.llcx, cgcx.pointer_size); @@ -819,10 +816,7 @@ pub(crate) unsafe fn llvm_optimize( }; if cgcx.target_is_like_gpu - && config - .offload - .iter() - .any(|o| matches!(o, config::Offload::Device | config::Offload::DeviceWithManifest(_))) + && config.offload.iter().any(|o| matches!(o, config::Offload::Device(_))) { let device_path = cgcx.output_filenames.path(OutputType::Object); let device_dir = device_path.parent().unwrap(); diff --git a/compiler/rustc_codegen_llvm/src/intrinsic.rs b/compiler/rustc_codegen_llvm/src/intrinsic.rs index 4c54b60c6d48a..ba11ef29fb536 100644 --- a/compiler/rustc_codegen_llvm/src/intrinsic.rs +++ b/compiler/rustc_codegen_llvm/src/intrinsic.rs @@ -1854,7 +1854,7 @@ fn codegen_offload<'ll, 'tcx>( let args = get_args_from_tuple(bx, args[4], fn_target); let target_symbol = mangle_offload_export(tcx, fn_target); - let sig = tcx.fn_sig(fn_target.def_id()).skip_binder(); + let sig = tcx.fn_sig(fn_target.def_id()).instantiate(tcx, fn_target.args).skip_norm_wip(); let sig = tcx.instantiate_bound_regions_with_erased(sig); let inputs = sig.inputs(); diff --git a/compiler/rustc_codegen_ssa/src/back/symbol_export.rs b/compiler/rustc_codegen_ssa/src/back/symbol_export.rs index 6109231edb5f9..cc102c636f4ae 100644 --- a/compiler/rustc_codegen_ssa/src/back/symbol_export.rs +++ b/compiler/rustc_codegen_ssa/src/back/symbol_export.rs @@ -245,13 +245,13 @@ pub fn exported_non_generic_symbols_helper<'tcx>( )); } - let is_device_offload = tcx.sess.opts.unstable_opts.offload.iter().any(|o| { - matches!( - o, - rustc_session::config::Offload::DeviceWithManifest(_) - | rustc_session::config::Offload::Device - ) - }); + let is_device_offload = tcx + .sess + .opts + .unstable_opts + .offload + .iter() + .any(|o| matches!(o, rustc_session::config::Offload::Device(_))); if is_device_offload { let crate_items = tcx.hir_crate_items(()); let mut seen: rustc_data_structures::fx::FxHashSet = symbols @@ -307,13 +307,13 @@ fn exported_generic_symbols_provider_local<'tcx>( let mut symbols: Vec<_> = vec![]; let export_generics = tcx.local_crate_exports_generics(); - let is_device_offload = tcx.sess.opts.unstable_opts.offload.iter().any(|o| { - matches!( - o, - rustc_session::config::Offload::DeviceWithManifest(_) - | rustc_session::config::Offload::Device - ) - }); + let is_device_offload = tcx + .sess + .opts + .unstable_opts + .offload + .iter() + .any(|o| matches!(o, rustc_session::config::Offload::Device(_))); if export_generics || is_device_offload { use rustc_hir::attrs::Linkage; diff --git a/compiler/rustc_interface/src/tests.rs b/compiler/rustc_interface/src/tests.rs index 548ee3f4b8e7b..24c7ff8484a5e 100644 --- a/compiler/rustc_interface/src/tests.rs +++ b/compiler/rustc_interface/src/tests.rs @@ -857,7 +857,7 @@ fn test_unstable_options_tracking_hash() { tracked!(no_profiler_runtime, true); tracked!(no_trait_vptr, true); tracked!(no_unique_section_names, true); - tracked!(offload, vec![Offload::Device]); + tracked!(offload, vec![Offload::Device(String::new())]); tracked!(on_broken_pipe, OnBrokenPipe::Kill); tracked!(osx_rpath_install_name, true); tracked!(packed_bundled_libs, true); diff --git a/compiler/rustc_monomorphize/src/collector.rs b/compiler/rustc_monomorphize/src/collector.rs index 401a441a939ea..44c602dafc9eb 100644 --- a/compiler/rustc_monomorphize/src/collector.rs +++ b/compiler/rustc_monomorphize/src/collector.rs @@ -1486,7 +1486,13 @@ fn collect_roots(tcx: TyCtxt<'_>, mode: MonoItemCollectionStrategy) -> Vec, mode: MonoItemCollectionStrategy) -> Vec(tcx: TyCtxt<'tcx>) { let Some(path) = tcx.sess.opts.unstable_opts.offload.iter().find_map(|o| { if let rustc_session::config::Offload::HostMetadata(p) = o { Some(p) } else { None } }) else { - return; + bug!("HostMetadata path not found; caller should have checked"); }; let partitions = tcx.collect_and_partition_mono_items(()); diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index d424991f2921f..1abc122ba8a11 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -197,10 +197,10 @@ pub enum CoverageLevel { #[derive(Clone, PartialEq, Hash, Debug, Encodable, Decodable)] pub enum Offload { /// Entry point for `std::offload`, enables kernel compilation for a gpu device - Device, - /// Like `Device`, but reads a manifest of required generic kernel instantiations - /// produced by a previous `HostMetadata` pass. - DeviceWithManifest(String), + /// Reads a manifest of required generic kernel instantiations + /// produced by a previous `HostMetadata` pass. An empty manifest + /// means all kernel instantiations are discovered via monomorphization. + Device(String), /// Second step in the offload pipeline, generates the host code to call kernels. Host(String), /// Test is similar to Host, but allows testing without a device artifact. diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index 65a94bc9e314c..3c3f1d69fe2c1 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -819,7 +819,7 @@ mod desc { "a comma-separated list of strings, with elements beginning with + or -"; pub(crate) const parse_pointer_authentication_list_with_polarity: &str = "a comma-separated list of options, each of the form `+` or `-`, where `` is one of: `aarch64-jump-table-hardening`, `auth-traps`, `calls`, `elf-got`, `function-pointer-type-discrimination`, `indirect-gotos`, `init-fini`, `init-fini-address-discrimination`, `intrinsics`, `return-addresses`, `typeinfo-vt-ptr-discrimination`, `vt-ptr-addr-discrimination` or `vt-ptr-type-discrimination`"; pub(crate) const parse_autodiff: &str = "a comma separated list of settings: `Enable`, `PrintSteps`, `PrintTA`, `PrintTAFn`, `PrintAA`, `PrintPerf`, `PrintModBefore`, `PrintModAfter`, `PrintModFinal`, `PrintPasses`, `NoPostopt`, `LooseTypes`, `Inline`, `NoTT`"; - pub(crate) const parse_offload: &str = "a comma separated list of settings: `Host=`, `HostMetadata=`, `Device`, `DeviceWithManifest=`, `Test`"; + pub(crate) const parse_offload: &str = "a comma separated list of settings: `Host=`, `HostMetadata=`, `Device` (empty manifest) or `Device=`, `Test`"; pub(crate) const parse_comma_list: &str = "a comma-separated list of strings"; pub(crate) const parse_opt_comma_list: &str = parse_comma_list; pub(crate) const parse_number: &str = "a number"; @@ -1521,18 +1521,9 @@ pub mod parse { } } "Device" => { - if let Some(_) = arg { - // Device does not accept a value - return false; - } - Offload::Device - } - "DeviceWithManifest" => { - if let Some(p) = arg { - Offload::DeviceWithManifest(p.to_string()) - } else { - return false; - } + // Without an argument, `Device` uses an empty manifest and all kernel + // instantiations are discovered via monomorphization. + Offload::Device(arg.unwrap_or_default().to_string()) } "Test" => { if let Some(_) = arg { diff --git a/tests/run-make/offload-generic-manifest/generic.rs b/tests/run-make/offload-generic-manifest/generic.rs new file mode 100644 index 0000000000000..eb356ad05c574 --- /dev/null +++ b/tests/run-make/offload-generic-manifest/generic.rs @@ -0,0 +1,12 @@ +#![feature(core_intrinsics, rustc_attrs)] +#![allow(internal_features)] +#![cfg_attr(device, no_main)] + +#[rustc_offload_kernel] +fn kernel(x: T) {} + +#[cfg(not(device))] +fn main() { + core::intrinsics::offload::<_, _, ()>(kernel::, [1, 1, 1], [1, 1, 1], 0, (0.0f32,)); + core::intrinsics::offload::<_, _, ()>(kernel::, [1, 1, 1], [1, 1, 1], 0, (0i32,)); +} diff --git a/tests/run-make/offload-generic-manifest/rmake.rs b/tests/run-make/offload-generic-manifest/rmake.rs new file mode 100644 index 0000000000000..29603196d35c7 --- /dev/null +++ b/tests/run-make/offload-generic-manifest/rmake.rs @@ -0,0 +1,37 @@ +// Tests the offload manifest pipeline for generic kernels + +use run_make_support::rustc; +use run_make_support::symbols::object_contains_any_symbol_substring; + +fn main() { + rustc() + .input("generic.rs") + .arg("-Zunstable-options") + .arg("-Zoffload=HostMetadata=generic.manifest") + .arg("-Clto=fat") + .emit("metadata") + .run(); + + rustc() + .input("generic.rs") + .cfg("device") + .arg("-Zunstable-options") + .arg("-Zoffload=Device=generic.manifest") + .arg("-Clto=fat") + .emit("obj") + .run(); + + assert!(object_contains_any_symbol_substring("generic.o", &["6kernelfEB2_"])); + assert!(object_contains_any_symbol_substring("generic.o", &["6kernellEB2_"])); + + rustc() + .input("generic.rs") + .cfg("device") + .arg("-Zunstable-options") + .arg("-Zoffload=Device") + .arg("-Clto=fat") + .emit("obj") + .run(); + + assert!(!object_contains_any_symbol_substring("generic.o", &["6kernel"])); +} diff --git a/tests/ui/offload/duplicate_kernel.rs b/tests/ui/offload/duplicate_kernel.rs new file mode 100644 index 0000000000000..66f8b4c7a5445 --- /dev/null +++ b/tests/ui/offload/duplicate_kernel.rs @@ -0,0 +1,20 @@ +//@ compile-flags: -Zunstable-options -Zoffload=Device -Clto=fat --crate-name collision_kernels_a +//@ build-fail + +// An offload kernel whose mangled symbol collides with another item in the +// same crate must be rejected, just like any other symbol collision. + +#![feature(core_intrinsics, rustc_attrs)] +#![allow(internal_features)] + +#[allow(non_snake_case)] +#[no_mangle] +pub fn _RNvC19collision_kernels_a6kernel(_x: f32) {} + +#[rustc_offload_kernel] +fn kernel(_x: f32) {} //~ ERROR symbol `_RNvC19collision_kernels_a6kernel` is already defined + +fn main() { + _RNvC19collision_kernels_a6kernel(0.0); + core::intrinsics::offload::<_, _, ()>(kernel, [1, 1, 1], [1, 1, 1], 0, (0.0f32,)); +} diff --git a/tests/ui/offload/duplicate_kernel.stderr b/tests/ui/offload/duplicate_kernel.stderr new file mode 100644 index 0000000000000..28b0aca940246 --- /dev/null +++ b/tests/ui/offload/duplicate_kernel.stderr @@ -0,0 +1,8 @@ +error: symbol `_RNvC19collision_kernels_a6kernel` is already defined + --> $DIR/duplicate_kernel.rs:15:1 + | +LL | fn kernel(_x: f32) {} + | ^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + From 4227e93fd2b094171eefa59d374c97e1ecfa1bbd Mon Sep 17 00:00:00 2001 From: Marcelo Dominguez Date: Wed, 12 Aug 2026 18:01:32 +0300 Subject: [PATCH 25/31] Some fixes and error when missing manifest --- compiler/rustc_monomorphize/Cargo.toml | 5 ++ compiler/rustc_monomorphize/src/collector.rs | 15 +++++- .../rustc_monomorphize/src/diagnostics.rs | 13 ++++++ compiler/rustc_monomorphize/src/lib.rs | 4 +- .../manifest.rs} | 0 .../rustc_monomorphize/src/offload/mod.rs | 46 +++++++++++++++++++ compiler/rustc_session/src/config.rs | 8 ++-- .../offload-generic-manifest/rmake.rs | 11 +++-- tests/ui/offload/duplicate_kernel.rs | 2 +- .../generic_kernel_not_instantiated.rs | 16 +++++++ .../generic_kernel_not_instantiated.stderr | 10 ++++ 11 files changed, 118 insertions(+), 12 deletions(-) rename compiler/rustc_monomorphize/src/{offload_manifest.rs => offload/manifest.rs} (100%) create mode 100644 compiler/rustc_monomorphize/src/offload/mod.rs create mode 100644 tests/ui/offload/generic_kernel_not_instantiated.rs create mode 100644 tests/ui/offload/generic_kernel_not_instantiated.stderr diff --git a/compiler/rustc_monomorphize/Cargo.toml b/compiler/rustc_monomorphize/Cargo.toml index c45232b2565a7..8846de7a9bf81 100644 --- a/compiler/rustc_monomorphize/Cargo.toml +++ b/compiler/rustc_monomorphize/Cargo.toml @@ -22,3 +22,8 @@ serde = "1" serde_json = "1" tracing = "0.1" # tidy-alphabetical-end + +[features] +# tidy-alphabetical-start +llvm_offload = [] +# tidy-alphabetical-end diff --git a/compiler/rustc_monomorphize/src/collector.rs b/compiler/rustc_monomorphize/src/collector.rs index 44c602dafc9eb..5d7820df622b7 100644 --- a/compiler/rustc_monomorphize/src/collector.rs +++ b/compiler/rustc_monomorphize/src/collector.rs @@ -231,7 +231,7 @@ use rustc_middle::ty::{ }; use rustc_middle::util::Providers; use rustc_middle::{bug, span_bug}; -use rustc_session::config::{DebugInfo, EntryFnType}; +use rustc_session::config::{DebugInfo, EntryFnType, Offload}; use rustc_span::{DUMMY_SP, Span, Spanned, Symbol, dummy_spanned, respan}; use tracing::{debug, instrument, trace}; @@ -1495,7 +1495,7 @@ fn collect_roots(tcx: TyCtxt<'_>, mode: MonoItemCollectionStrategy) -> Vec { for instance in instances { if instance.def_id().is_local() { @@ -1942,6 +1942,17 @@ pub(crate) fn collect_crate_mono_items<'tcx>( state.visited.into_inner().into_sorted(&mut hcx, true) }); + if tcx + .sess + .opts + .unstable_opts + .offload + .iter() + .any(|o| matches!(o, Offload::Device(p) if p.is_empty())) + { + crate::offload::check_offload_kernels_instantiated(tcx, &mono_items); + } + (mono_items, state.usage_map.into_inner()) } diff --git a/compiler/rustc_monomorphize/src/diagnostics.rs b/compiler/rustc_monomorphize/src/diagnostics.rs index df4b54abc2258..51ce633f55a54 100644 --- a/compiler/rustc_monomorphize/src/diagnostics.rs +++ b/compiler/rustc_monomorphize/src/diagnostics.rs @@ -64,6 +64,19 @@ pub(crate) struct OffloadManifestReadError { pub err: String, } +#[derive(Diagnostic)] +#[diag("generic offload kernel `{$def_path}` is not instantiated")] +#[help( + "with `-Zoffload=Device` (without a manifest), generic kernels are only discovered via \ + monomorphization; if this kernel is called from host code, pass \ + `-Zoffload=Device=`, using the manifest written by `-Zoffload=HostMetadata=`" +)] +pub(crate) struct GenericKernelNotInstantiated { + #[primary_span] + pub span: Span, + pub def_path: String, +} + #[derive(Diagnostic)] #[diag("the above error was encountered while instantiating `{$kind} {$instance}`")] pub(crate) struct EncounteredErrorWhileInstantiating<'tcx> { diff --git a/compiler/rustc_monomorphize/src/lib.rs b/compiler/rustc_monomorphize/src/lib.rs index a7d1119dd064c..79abdee53eda4 100644 --- a/compiler/rustc_monomorphize/src/lib.rs +++ b/compiler/rustc_monomorphize/src/lib.rs @@ -16,13 +16,13 @@ mod collector; mod diagnostics; mod graph_checks; mod mono_checks; -mod offload_manifest; +mod offload; mod partitioning; mod util; // Exposed so `rustc_codegen_ssa::base::codegen_crate` can trigger the // host-metadata manifest write. -pub use offload_manifest::write_host_metadata_offload_manifest; +pub use offload::manifest::write_host_metadata_offload_manifest; fn custom_coerce_unsize_info<'tcx>( tcx: TyCtxtAt<'tcx>, diff --git a/compiler/rustc_monomorphize/src/offload_manifest.rs b/compiler/rustc_monomorphize/src/offload/manifest.rs similarity index 100% rename from compiler/rustc_monomorphize/src/offload_manifest.rs rename to compiler/rustc_monomorphize/src/offload/manifest.rs diff --git a/compiler/rustc_monomorphize/src/offload/mod.rs b/compiler/rustc_monomorphize/src/offload/mod.rs new file mode 100644 index 0000000000000..a4dca281e0122 --- /dev/null +++ b/compiler/rustc_monomorphize/src/offload/mod.rs @@ -0,0 +1,46 @@ +pub(crate) mod manifest; + +use rustc_data_structures::fx::FxHashSet; +use rustc_hir::def::DefKind; +use rustc_hir::def_id::DefId; +use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; +use rustc_middle::mono::MonoItem; +use rustc_middle::ty::TyCtxt; + +pub(crate) fn check_offload_kernels_instantiated<'tcx>( + tcx: TyCtxt<'tcx>, + mono_items: &[MonoItem<'tcx>], +) { + let instantiated: FxHashSet = mono_items + .iter() + .filter_map(|item| match item { + MonoItem::Fn(instance) => Some(instance.def_id()), + MonoItem::Static(def_id) => Some(*def_id), + _ => None, + }) + .collect(); + + let crate_items = tcx.hir_crate_items(()); + let check = |def_id: DefId| { + if !matches!(tcx.def_kind(def_id), DefKind::Fn | DefKind::AssocFn) + || !tcx.generics_of(def_id).requires_monomorphization(tcx) + || !tcx.codegen_fn_attrs(def_id).flags.intersects(CodegenFnAttrFlags::OFFLOAD_KERNEL) + || instantiated.contains(&def_id) + { + return; + } + tcx.dcx().emit_err(crate::diagnostics::GenericKernelNotInstantiated { + span: tcx.def_span(def_id), + def_path: tcx.def_path_str(def_id), + }); + }; + for id in crate_items.free_items() { + check(id.owner_id.to_def_id()); + } + for id in crate_items.impl_items() { + check(id.owner_id.to_def_id()); + } + for id in crate_items.trait_items() { + check(id.owner_id.to_def_id()); + } +} diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index 1abc122ba8a11..efbffa8c0e486 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -196,12 +196,14 @@ pub enum CoverageLevel { // The different settings that the `-Z offload` flag can have. #[derive(Clone, PartialEq, Hash, Debug, Encodable, Decodable)] pub enum Offload { - /// Entry point for `std::offload`, enables kernel compilation for a gpu device + /// Second step in the offload pipeline, enables kernel compilation for a gpu device /// Reads a manifest of required generic kernel instantiations /// produced by a previous `HostMetadata` pass. An empty manifest - /// means all kernel instantiations are discovered via monomorphization. + /// means there are no generic kernels at all, or that generic kernels are only + /// called from non-generic device entry points and never from the host, so we + /// don't need to track their instantiations. Device(String), - /// Second step in the offload pipeline, generates the host code to call kernels. + /// Third step in the offload pipeline, generates the host code to call kernels. Host(String), /// Test is similar to Host, but allows testing without a device artifact. Test, diff --git a/tests/run-make/offload-generic-manifest/rmake.rs b/tests/run-make/offload-generic-manifest/rmake.rs index 29603196d35c7..54907fabf8ee4 100644 --- a/tests/run-make/offload-generic-manifest/rmake.rs +++ b/tests/run-make/offload-generic-manifest/rmake.rs @@ -8,6 +8,7 @@ fn main() { .input("generic.rs") .arg("-Zunstable-options") .arg("-Zoffload=HostMetadata=generic.manifest") + .arg("-Csymbol-mangling-version=v0") .arg("-Clto=fat") .emit("metadata") .run(); @@ -17,6 +18,7 @@ fn main() { .cfg("device") .arg("-Zunstable-options") .arg("-Zoffload=Device=generic.manifest") + .arg("-Csymbol-mangling-version=v0") .arg("-Clto=fat") .emit("obj") .run(); @@ -24,14 +26,15 @@ fn main() { assert!(object_contains_any_symbol_substring("generic.o", &["6kernelfEB2_"])); assert!(object_contains_any_symbol_substring("generic.o", &["6kernellEB2_"])); - rustc() + let p = rustc() .input("generic.rs") .cfg("device") .arg("-Zunstable-options") .arg("-Zoffload=Device") + .arg("-Csymbol-mangling-version=v0") .arg("-Clto=fat") .emit("obj") - .run(); - - assert!(!object_contains_any_symbol_substring("generic.o", &["6kernel"])); + .run_fail(); + assert!(p.stderr_utf8().contains("generic offload kernel")); + assert!(p.stderr_utf8().contains("is not instantiated")); } diff --git a/tests/ui/offload/duplicate_kernel.rs b/tests/ui/offload/duplicate_kernel.rs index 66f8b4c7a5445..0410fe0690d47 100644 --- a/tests/ui/offload/duplicate_kernel.rs +++ b/tests/ui/offload/duplicate_kernel.rs @@ -1,4 +1,4 @@ -//@ compile-flags: -Zunstable-options -Zoffload=Device -Clto=fat --crate-name collision_kernels_a +//@ compile-flags: -Zunstable-options -Zoffload=Device -Clto=fat -Csymbol-mangling-version=v0 --crate-name collision_kernels_a //@ build-fail // An offload kernel whose mangled symbol collides with another item in the diff --git a/tests/ui/offload/generic_kernel_not_instantiated.rs b/tests/ui/offload/generic_kernel_not_instantiated.rs new file mode 100644 index 0000000000000..2df058758024b --- /dev/null +++ b/tests/ui/offload/generic_kernel_not_instantiated.rs @@ -0,0 +1,16 @@ +//@ compile-flags: -Zunstable-options -Zoffload=Device -Clto=fat -Csymbol-mangling-version=v0 +//@ build-fail + +// A generic offload kernel that is never called from host code (and hence +// never monomorphized) cannot be discovered without a manifest: with +// `-Zoffload=Device` (no manifest path), the compiler relies on +// monomorphization to find kernels, so it must reject the kernel rather than +// silently emit no device code for it. + +#![feature(rustc_attrs)] +#![allow(internal_features)] + +#[rustc_offload_kernel] +fn kernel(x: T) {} //~ ERROR generic offload kernel `kernel` is not instantiated + +fn main() {} diff --git a/tests/ui/offload/generic_kernel_not_instantiated.stderr b/tests/ui/offload/generic_kernel_not_instantiated.stderr new file mode 100644 index 0000000000000..ab7c066d09b82 --- /dev/null +++ b/tests/ui/offload/generic_kernel_not_instantiated.stderr @@ -0,0 +1,10 @@ +error: generic offload kernel `kernel` is not instantiated + --> $DIR/generic_kernel_not_instantiated.rs:14:1 + | +LL | fn kernel(x: T) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: with `-Zoffload=Device` (without a manifest), generic kernels are only discovered via monomorphization; if this kernel is called from host code, pass `-Zoffload=Device=`, using the manifest written by `-Zoffload=HostMetadata=` + +error: aborting due to 1 previous error + From b1b0f5e7b4fcedfdab14de37a2c8f06118868bd6 Mon Sep 17 00:00:00 2001 From: Marcelo Dominguez Date: Wed, 12 Aug 2026 18:48:12 +0300 Subject: [PATCH 26/31] fix --- compiler/rustc_codegen_llvm/src/lib.rs | 2 +- tests/run-make/offload-generic-manifest/rmake.rs | 2 ++ tests/ui/offload/check_config.rs | 2 +- tests/ui/offload/duplicate_kernel.rs | 4 +++- tests/ui/offload/duplicate_kernel.stderr | 2 +- tests/ui/offload/generic_kernel_not_instantiated.rs | 4 +++- tests/ui/offload/generic_kernel_not_instantiated.stderr | 2 +- 7 files changed, 12 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_codegen_llvm/src/lib.rs b/compiler/rustc_codegen_llvm/src/lib.rs index 1f0e583709592..552a91ffee071 100644 --- a/compiler/rustc_codegen_llvm/src/lib.rs +++ b/compiler/rustc_codegen_llvm/src/lib.rs @@ -375,7 +375,7 @@ impl CodegenBackend for LlvmCodegenBackend { fn codegen_crate<'tcx>(&self, tcx: TyCtxt<'tcx>) -> Box { use rustc_session::config::Offload; - if tcx.sess.opts.unstable_opts.offload.contains(&Offload::Device) + if tcx.sess.opts.unstable_opts.offload.iter().any(|o| matches!(o, Offload::Device(_))) || tcx.sess.opts.unstable_opts.offload.iter().any(|o| matches!(o, Offload::Host(_))) { match llvm::RustOffloadWrapper::get_or_init(&tcx.sess.opts.sysroot) { diff --git a/tests/run-make/offload-generic-manifest/rmake.rs b/tests/run-make/offload-generic-manifest/rmake.rs index 54907fabf8ee4..01b09ff87d2fa 100644 --- a/tests/run-make/offload-generic-manifest/rmake.rs +++ b/tests/run-make/offload-generic-manifest/rmake.rs @@ -1,3 +1,5 @@ +//@ needs-offload + // Tests the offload manifest pipeline for generic kernels use run_make_support::rustc; diff --git a/tests/ui/offload/check_config.rs b/tests/ui/offload/check_config.rs index 69afe65a308b4..ff145f420e482 100644 --- a/tests/ui/offload/check_config.rs +++ b/tests/ui/offload/check_config.rs @@ -1,6 +1,6 @@ //@ revisions: pass fail //@ no-prefer-dynamic -//@ needs-enzyme +//@ needs-offload //@[pass] build-pass //@[fail] build-fail //@[pass] compile-flags: -Zunstable-options -Zoffload=Device -Clto=fat --emit=metadata diff --git a/tests/ui/offload/duplicate_kernel.rs b/tests/ui/offload/duplicate_kernel.rs index 0410fe0690d47..abde76137a37c 100644 --- a/tests/ui/offload/duplicate_kernel.rs +++ b/tests/ui/offload/duplicate_kernel.rs @@ -1,5 +1,6 @@ //@ compile-flags: -Zunstable-options -Zoffload=Device -Clto=fat -Csymbol-mangling-version=v0 --crate-name collision_kernels_a //@ build-fail +//@ needs-offload // An offload kernel whose mangled symbol collides with another item in the // same crate must be rejected, just like any other symbol collision. @@ -12,7 +13,8 @@ pub fn _RNvC19collision_kernels_a6kernel(_x: f32) {} #[rustc_offload_kernel] -fn kernel(_x: f32) {} //~ ERROR symbol `_RNvC19collision_kernels_a6kernel` is already defined +fn kernel(_x: f32) {} +//~^ ERROR symbol `_RNvC19collision_kernels_a6kernel` is already defined fn main() { _RNvC19collision_kernels_a6kernel(0.0); diff --git a/tests/ui/offload/duplicate_kernel.stderr b/tests/ui/offload/duplicate_kernel.stderr index 28b0aca940246..bb0f21b1f1cc3 100644 --- a/tests/ui/offload/duplicate_kernel.stderr +++ b/tests/ui/offload/duplicate_kernel.stderr @@ -1,5 +1,5 @@ error: symbol `_RNvC19collision_kernels_a6kernel` is already defined - --> $DIR/duplicate_kernel.rs:15:1 + --> $DIR/duplicate_kernel.rs:16:1 | LL | fn kernel(_x: f32) {} | ^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/offload/generic_kernel_not_instantiated.rs b/tests/ui/offload/generic_kernel_not_instantiated.rs index 2df058758024b..36f4f8c667a54 100644 --- a/tests/ui/offload/generic_kernel_not_instantiated.rs +++ b/tests/ui/offload/generic_kernel_not_instantiated.rs @@ -1,5 +1,6 @@ //@ compile-flags: -Zunstable-options -Zoffload=Device -Clto=fat -Csymbol-mangling-version=v0 //@ build-fail +//@ needs-offload // A generic offload kernel that is never called from host code (and hence // never monomorphized) cannot be discovered without a manifest: with @@ -11,6 +12,7 @@ #![allow(internal_features)] #[rustc_offload_kernel] -fn kernel(x: T) {} //~ ERROR generic offload kernel `kernel` is not instantiated +fn kernel(x: T) {} +//~^ ERROR generic offload kernel `kernel` is not instantiated fn main() {} diff --git a/tests/ui/offload/generic_kernel_not_instantiated.stderr b/tests/ui/offload/generic_kernel_not_instantiated.stderr index ab7c066d09b82..60af2e9b3b92c 100644 --- a/tests/ui/offload/generic_kernel_not_instantiated.stderr +++ b/tests/ui/offload/generic_kernel_not_instantiated.stderr @@ -1,5 +1,5 @@ error: generic offload kernel `kernel` is not instantiated - --> $DIR/generic_kernel_not_instantiated.rs:14:1 + --> $DIR/generic_kernel_not_instantiated.rs:15:1 | LL | fn kernel(x: T) {} | ^^^^^^^^^^^^^^^^^^^^^^^^ From 5b7132497c6f6828246c06e47d2d14b29a33eb03 Mon Sep 17 00:00:00 2001 From: Max Dexheimer Date: Wed, 12 Aug 2026 12:29:50 +0200 Subject: [PATCH 27/31] Ensure TLS accesses don't call the global allocator through panic part 2 This is torture. --- .../src/sys/thread_local/destructors/list.rs | 5 +++ library/std/src/sys/thread_local/key/tests.rs | 10 +++++ library/std/src/sys/thread_local/key/unix.rs | 15 ++++--- .../std/src/sys/thread_local/key/windows.rs | 44 +++++++++++-------- library/std/src/sys/thread_local/key/xous.rs | 13 +++--- library/std/src/sys/thread_local/mod.rs | 10 +++++ .../std/src/sys/thread_local/native/lazy.rs | 2 +- .../std/src/sys/thread_local/no_threads.rs | 4 +- library/std/src/sys/thread_local/os.rs | 9 ++-- 9 files changed, 74 insertions(+), 38 deletions(-) diff --git a/library/std/src/sys/thread_local/destructors/list.rs b/library/std/src/sys/thread_local/destructors/list.rs index 44e00c8a5ae59..dc767639797fb 100644 --- a/library/std/src/sys/thread_local/destructors/list.rs +++ b/library/std/src/sys/thread_local/destructors/list.rs @@ -11,6 +11,11 @@ pub unsafe fn register(t: *mut u8, dtor: unsafe extern "C" fn(*mut u8)) { rtabort!("the System allocator may not use TLS with destructors") }; guard::enable(); + + // Avoid calling the alloc error hook + if dtors.capacity() == dtors.len() { + dtors.try_reserve(1).unwrap_or_else(|_| rtabort!("Failed to grow TLS destructor list")) + } dtors.push((t, dtor)); } diff --git a/library/std/src/sys/thread_local/key/tests.rs b/library/std/src/sys/thread_local/key/tests.rs index 5e5243d9835ed..33c7f2dbd8b31 100644 --- a/library/std/src/sys/thread_local/key/tests.rs +++ b/library/std/src/sys/thread_local/key/tests.rs @@ -1,3 +1,13 @@ +#![allow( + clippy::arithmetic_side_effects, + clippy::expect_used, + clippy::unwrap_used, + clippy::indexing_slicing, + clippy::panic, + clippy::unreachable, + clippy::unimplemented +)] + use super::{LazyKey, get, set}; use crate::ptr; diff --git a/library/std/src/sys/thread_local/key/unix.rs b/library/std/src/sys/thread_local/key/unix.rs index 27c12c343aafb..a43eaf6dfaae5 100644 --- a/library/std/src/sys/thread_local/key/unix.rs +++ b/library/std/src/sys/thread_local/key/unix.rs @@ -31,13 +31,13 @@ pub fn create(dtor: Option) -> Key { key } +#[cold] +fn fail() -> ! { + rtabort!("Unexpected TLS failure") +} + #[inline] pub unsafe fn set(key: Key, value: *mut u8) { - #[cold] - fn fail() -> ! { - rtabort!("Failed to set value of thread local") - } - let r = unsafe { libc::pthread_setspecific(key, value as *mut _) }; // May happen on memory exhaustion if r != 0 { @@ -54,5 +54,8 @@ pub unsafe fn get(key: Key) -> *mut u8 { #[inline] pub unsafe fn destroy(key: Key) { let r = unsafe { libc::pthread_key_delete(key) }; - debug_assert_eq!(r, 0); + // only documented error is for invalid keys + if r != 0 { + fail() + } } diff --git a/library/std/src/sys/thread_local/key/windows.rs b/library/std/src/sys/thread_local/key/windows.rs index fdedcc97110c1..36c744d92d9f2 100644 --- a/library/std/src/sys/thread_local/key/windows.rs +++ b/library/std/src/sys/thread_local/key/windows.rs @@ -47,6 +47,11 @@ pub struct LazyKey { once: UnsafeCell, } +#[cold] +fn fail() -> ! { + rtabort!("Unexpected TLS failure") +} + impl LazyKey { #[inline] pub const fn new(dtor: Option) -> LazyKey { @@ -66,9 +71,9 @@ impl LazyKey { guard::enable(); } - match self.key.load(Acquire) { - 0 => unsafe { self.init() }, - key => key - 1, + match self.key.load(Acquire).checked_sub(1) { + None => unsafe { self.init() }, + Some(dec) => dec, } } @@ -79,11 +84,13 @@ impl LazyKey { let r = unsafe { c::InitOnceBeginInitialize(self.once.get(), 0, &mut pending, ptr::null_mut()) }; - assert_eq!(r, c::TRUE); + if r != c::TRUE { + fail() + } if pending == c::FALSE { // Some other thread initialized the key, load it. - self.key.load(Relaxed) - 1 + self.key.load(Relaxed).wrapping_sub(1) } else { let key = unsafe { c::TlsAlloc() }; if key == c::TLS_OUT_OF_INDEXES { @@ -104,10 +111,12 @@ impl LazyKey { // and if that sees this write then it will entirely bypass the `InitOnce`. We thus // need to establish synchronization through `key`. In particular that acquire load // must happen-after the register_dtor above, to ensure the dtor actually runs! - self.key.store(key + 1, Release); + self.key.store(key.wrapping_add(1), Release); let r = unsafe { c::InitOnceComplete(self.once.get(), 0, ptr::null_mut()) }; - debug_assert_eq!(r, c::TRUE); + if r != c::TRUE { + fail() + } key } @@ -119,14 +128,16 @@ impl LazyKey { rtabort!("out of TLS indexes"); } - match self.key.compare_exchange(0, key + 1, AcqRel, Acquire) { + match self.key.compare_exchange(0, key.wrapping_add(1), AcqRel, Acquire) { Ok(_) => key, Err(new) => unsafe { // Some other thread completed initialization first, so destroy // our key and use theirs. let r = c::TlsFree(key); - debug_assert_eq!(r, c::TRUE); - new - 1 + if r != c::TRUE { + fail() + } + new.wrapping_sub(1) }, } } @@ -138,10 +149,6 @@ unsafe impl Sync for LazyKey {} #[inline] pub unsafe fn set(key: Key, val: *mut u8) { - #[cold] - fn fail() -> ! { - rtabort!("Failed to set value of thread local") - } let r = unsafe { c::TlsSetValue(key, val.cast()) }; // According to MS documentation, `TlsSetValue` returns zero "if it fails" if r != c::TRUE { @@ -181,22 +188,21 @@ pub unsafe fn run_dtors() { let mut cur = DTORS.load(Acquire); while !cur.is_null() { let pre_key = unsafe { (*cur).key.load(Acquire) }; - let dtor = unsafe { (*cur).dtor.unwrap() }; + let dtor = unsafe { rtunwrap!(Some, (*cur).dtor) }; cur = unsafe { (*cur).next.load(Relaxed) }; // In LazyKey::init, we register the dtor before setting `key`. // So if one thread's `run_dtors` races with another thread executing `init` on the same // `LazyKey`, we can encounter a key of 0 here. That means this key was never // initialized in this thread so we can safely skip it. - if pre_key == 0 { + let Some(key) = pre_key.checked_sub(1) else { continue; - } + }; + // If this is non-zero, then via the `Acquire` load above we synchronized with // everything relevant for this key. (It's not clear that this is needed, since the // release-acquire pair on DTORS also establishes synchronization, but better safe than // sorry.) - let key = pre_key - 1; - let ptr = unsafe { c::TlsGetValue(key) }; if !ptr.is_null() { unsafe { diff --git a/library/std/src/sys/thread_local/key/xous.rs b/library/std/src/sys/thread_local/key/xous.rs index 6da7162fff01f..02b68df38ab68 100644 --- a/library/std/src/sys/thread_local/key/xous.rs +++ b/library/std/src/sys/thread_local/key/xous.rs @@ -104,11 +104,11 @@ fn tls_table_slow() -> &'static mut [*mut u8] { TLS_MEMORY_SIZE / size_of::<*mut u8>(), MemoryFlags::R | MemoryFlags::W, ) - .expect("Unable to allocate memory for thread local storage") + .unwrap_or_else(|_| rtabort!("Unable to allocate memory for thread local storage")) }; for val in tp.iter() { - assert!((*val).is_null()); + rtassert!((*val).is_null()); } unsafe { @@ -136,13 +136,14 @@ pub fn create(dtor: Option) -> Key { pub unsafe fn set(key: Key, value: *mut u8) { rtassert!((key < 1022) && (key >= 1)); let table = tls_table(); - table[key] = value; + *rtunwrap!(Some, table.get_mut(key)) = value; } #[inline] pub unsafe fn get(key: Key) -> *mut u8 { rtassert!((key < 1022) && (key >= 1)); - tls_table()[key] + let table = tls_table(); + *rtunwrap!(Some, table.get(key)) } #[inline] @@ -186,10 +187,10 @@ pub unsafe fn destroy_tls() { unsafe { run_dtors() }; // Finally, free the TLS array - unsafe { + let result = unsafe { unmap_memory(core::slice::from_raw_parts_mut(tp, TLS_MEMORY_SIZE / size_of::())) - .unwrap() }; + rtunwrap!(Ok, result); } // This is marked inline(never) to prevent dealloc calls from being reordered diff --git a/library/std/src/sys/thread_local/mod.rs b/library/std/src/sys/thread_local/mod.rs index 3ccfdd27ee833..2158bb3e275f9 100644 --- a/library/std/src/sys/thread_local/mod.rs +++ b/library/std/src/sys/thread_local/mod.rs @@ -22,6 +22,16 @@ reason = "internal details of the thread_local macro", issue = "none" )] +#![deny( + clippy::arithmetic_side_effects, + clippy::expect_used, + clippy::unwrap_used, + clippy::indexing_slicing, + clippy::panic, + clippy::unreachable, + clippy::unimplemented, + reason = "TLS accesses must not call the global allocator, including via panic (#160930)" +)] cfg_select! { any( diff --git a/library/std/src/sys/thread_local/native/lazy.rs b/library/std/src/sys/thread_local/native/lazy.rs index 02939a74fc089..c9bf5ea74c92f 100644 --- a/library/std/src/sys/thread_local/native/lazy.rs +++ b/library/std/src/sys/thread_local/native/lazy.rs @@ -95,7 +95,7 @@ where // as we've already registered the destructor. State::Alive => unsafe { old_value.assume_init_drop() }, - State::Destroyed(_) => unreachable!(), + State::Destroyed(_) => rtabort!("unreachable"), } self.value.get().cast() diff --git a/library/std/src/sys/thread_local/no_threads.rs b/library/std/src/sys/thread_local/no_threads.rs index f9d0ed384fe24..9f4e7710dffb7 100644 --- a/library/std/src/sys/thread_local/no_threads.rs +++ b/library/std/src/sys/thread_local/no_threads.rs @@ -95,7 +95,7 @@ impl LazyStorage { let value = i.and_then(Option::take).unwrap_or_else(f); // Destroy the old value if it is initialized - // FIXME(#110897): maybe panic on recursive initialization. + // FIXME(#110897): maybe abort on recursive initialization. if self.state.get() == State::Alive { self.state.set(State::Destroying); // Safety: we check for no initialization during drop below @@ -107,7 +107,7 @@ impl LazyStorage { // Guard against initialization during drop if self.state.get() == State::Destroying { - panic!("Attempted to initialize thread-local while it is being dropped"); + rtabort!("Attempted to initialize thread-local while it is being dropped"); } unsafe { diff --git a/library/std/src/sys/thread_local/os.rs b/library/std/src/sys/thread_local/os.rs index bc044aafa983c..428339a16f4ea 100644 --- a/library/std/src/sys/thread_local/os.rs +++ b/library/std/src/sys/thread_local/os.rs @@ -1,6 +1,6 @@ use super::key::{Key, LazyKey, get, set}; use super::{abort_on_dtor_unwind, guard}; -use crate::alloc::{self, GlobalAlloc, Layout, System}; +use crate::alloc::{GlobalAlloc, Layout, System}; use crate::cell::Cell; use crate::marker::PhantomData; use crate::mem::ManuallyDrop; @@ -103,13 +103,14 @@ struct AlignedSystemBox { impl AlignedSystemBox { #[inline] fn new(v: Value) -> Self { - let layout = Layout::new::>().align_to(ALIGN).unwrap(); + let layout = rtunwrap!(Ok, Layout::new::>().align_to(ALIGN)); // We use the System allocator here to avoid interfering with a potential // Global allocator using thread-local storage. let ptr: *mut Value = (unsafe { System.alloc(layout) }).cast(); let Some(ptr) = NonNull::new(ptr) else { - alloc::handle_alloc_error(layout); + // Do not call the alloc error hook here. It may allocate! + rtabort!("Allocation failure"); }; unsafe { ptr.write(v) }; Self { ptr } @@ -139,7 +140,7 @@ impl Deref for AlignedSystemBox { impl Drop for AlignedSystemBox { #[inline] fn drop(&mut self) { - let layout = Layout::new::>().align_to(ALIGN).unwrap(); + let layout = rtunwrap!(Ok, Layout::new::>().align_to(ALIGN)); unsafe { let unwind_result = catch_unwind(AssertUnwindSafe(|| self.ptr.drop_in_place())); From a09d72a3bfd4113f9c3e05ecbb2dbba78baa78be Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 12 Aug 2026 21:37:37 +0200 Subject: [PATCH 28/31] Also warn if an invalid `doc` attribute is used on a macro invocation --- compiler/rustc_expand/src/expand.rs | 6 +++++- .../lint/unused/unused-doc-comments-for-macros.rs | 9 +++++++++ .../unused/unused-doc-comments-for-macros.stderr | 15 ++++++++++++++- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index 6328115f4b37c..fa7a2dce717b7 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -2241,7 +2241,11 @@ impl<'a, 'b> InvocationCollector<'a, 'b> { continue; } - if attr.doc_str_and_fragment_kind().is_some() { + if match &attr.kind { + AttrKind::Normal(normal) => normal.item.name() == Some(sym::doc), + AttrKind::DocComment(..) => true, + _ => false, + } { self.cx.sess.psess.buffer_lint( UNUSED_DOC_COMMENTS, current_span, diff --git a/tests/ui/lint/unused/unused-doc-comments-for-macros.rs b/tests/ui/lint/unused/unused-doc-comments-for-macros.rs index 05828ebb2c353..0a95b79988894 100644 --- a/tests/ui/lint/unused/unused-doc-comments-for-macros.rs +++ b/tests/ui/lint/unused/unused-doc-comments-for-macros.rs @@ -14,4 +14,13 @@ fn main() { /// line2 /// line3 foo!(); + + // Even invalid doc attributes should emit the warning. + #[doc = { //~ ERROR: unused doc comment + let a = 1; + let b = 1; + let sum = a + b; + assert_eq!(sum, 2); + }] + foo!(); } diff --git a/tests/ui/lint/unused/unused-doc-comments-for-macros.stderr b/tests/ui/lint/unused/unused-doc-comments-for-macros.stderr index 26b1c2b058c1f..4634e0704c536 100644 --- a/tests/ui/lint/unused/unused-doc-comments-for-macros.stderr +++ b/tests/ui/lint/unused/unused-doc-comments-for-macros.stderr @@ -27,5 +27,18 @@ LL | | /// line3 | = help: to document an item produced by a macro, the macro must produce the documentation as part of its expansion -error: aborting due to 2 previous errors +error: unused doc comment + --> $DIR/unused-doc-comments-for-macros.rs:19:5 + | +LL | / #[doc = { +LL | | let a = 1; +LL | | let b = 1; +LL | | let sum = a + b; +LL | | assert_eq!(sum, 2); +LL | | }] + | |______^ rustdoc does not generate documentation for macro invocations + | + = help: to document an item produced by a macro, the macro must produce the documentation as part of its expansion + +error: aborting due to 3 previous errors From e647d65fafa7ef64dc3e6243df87b51c13ee8242 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 3 Aug 2026 15:39:46 +0200 Subject: [PATCH 29/31] Change table odd table rows background color to not make it the same as inline code --- src/librustdoc/html/static/css/noscript.css | 4 ++-- src/librustdoc/html/static/css/rustdoc.css | 6 +++--- tests/rustdoc-gui/docblock-table.goml | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/librustdoc/html/static/css/noscript.css b/src/librustdoc/html/static/css/noscript.css index b769238d91a8b..085880b215d14 100644 --- a/src/librustdoc/html/static/css/noscript.css +++ b/src/librustdoc/html/static/css/noscript.css @@ -48,7 +48,7 @@ nav.sub { --sidebar-background-color: #f5f5f5; --sidebar-background-color-hover: #e0e0e0; --sidebar-border-color: #ddd; - --code-block-background-color: #f5f5f5; + --code-block-background-color: #eaeaea; --scrollbar-track-background-color: #dcdcdc; --scrollbar-thumb-background-color: rgba(36, 37, 39, 0.6); --scrollbar-color: rgba(36, 37, 39, 0.6) #d9d9d9; @@ -244,7 +244,7 @@ nav.sub { --crate-search-hover-border: #2196f3; --src-sidebar-background-selected: #333; --src-sidebar-background-hover: #444; - --table-alt-row-background-color: #2a2a2a; + --table-alt-row-background-color: #1d1d1d; --codeblock-link-background: #333; --scrape-example-toggle-line-background: #999; --scrape-example-toggle-line-hover-background: #c5c5c5; diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index 83b36ffbe7618..70a4bc425f75d 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -3217,7 +3217,7 @@ by default. --sidebar-background-color: #f5f5f5; --sidebar-background-color-hover: #e0e0e0; --sidebar-border-color: #ddd; - --code-block-background-color: #f5f5f5; + --code-block-background-color: #eaeaea; --scrollbar-track-background-color: #dcdcdc; --scrollbar-thumb-background-color: rgba(36, 37, 39, 0.6); --scrollbar-color: rgba(36, 37, 39, 0.6) #d9d9d9; @@ -3412,7 +3412,7 @@ by default. --crate-search-hover-border: #2196f3; --src-sidebar-background-selected: #333; --src-sidebar-background-hover: #444; - --table-alt-row-background-color: #2a2a2a; + --table-alt-row-background-color: #1d1d1d; --codeblock-link-background: #333; --scrape-example-toggle-line-background: #999; --scrape-example-toggle-line-hover-background: #c5c5c5; @@ -3532,7 +3532,7 @@ Original by Dempfi (https://github.com/dempfi/ayu) --crate-search-hover-border: #e0e0e0; --src-sidebar-background-selected: #14191f; --src-sidebar-background-hover: #14191f; - --table-alt-row-background-color: #191f26; + --table-alt-row-background-color: #000; --codeblock-link-background: #333; --scrape-example-toggle-line-background: #999; --scrape-example-toggle-line-hover-background: #c5c5c5; diff --git a/tests/rustdoc-gui/docblock-table.goml b/tests/rustdoc-gui/docblock-table.goml index a73f4aaa7677c..305aeb352ae3e 100644 --- a/tests/rustdoc-gui/docblock-table.goml +++ b/tests/rustdoc-gui/docblock-table.goml @@ -38,12 +38,12 @@ define-function: ( call-function: ("check-colors", { "theme": "ayu", "border_color": "#5c6773", - "zebra_stripe_color": "#191f26", + "zebra_stripe_color": "#000", }) call-function: ("check-colors", { "theme": "dark", "border_color": "#e0e0e0", - "zebra_stripe_color": "#2a2a2a", + "zebra_stripe_color": "#1d1d1d", }) call-function: ("check-colors", { "theme": "light", From e7e5245db151fc771151f75b52c6c2cdcfd69936 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 12 Aug 2026 23:09:20 +0200 Subject: [PATCH 30/31] Invert table row background --- src/librustdoc/html/static/css/rustdoc.css | 2 +- tests/rustdoc-gui/docblock-table.goml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index 70a4bc425f75d..06f0a3ece502f 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -1145,7 +1145,7 @@ pre, .rustdoc.src .example-wrap, .example-wrap .src-line-numbers { border: 1px solid var(--border-color); } -.docblock table tbody tr:nth-child(2n) { +.docblock table tbody tr:nth-child(odd) { background: var(--table-alt-row-background-color); } diff --git a/tests/rustdoc-gui/docblock-table.goml b/tests/rustdoc-gui/docblock-table.goml index 305aeb352ae3e..ec61581e1f75d 100644 --- a/tests/rustdoc-gui/docblock-table.goml +++ b/tests/rustdoc-gui/docblock-table.goml @@ -11,16 +11,16 @@ define-function: ( block { call-function: ("switch-theme", {"theme": |theme|}) assert-css: (".top-doc .docblock table tbody tr:nth-child(1)", { - "background-color": "rgba(0, 0, 0, 0)", + "background-color": |zebra_stripe_color|, }) assert-css: (".top-doc .docblock table tbody tr:nth-child(2)", { - "background-color": |zebra_stripe_color|, + "background-color": "rgba(0, 0, 0, 0)", }) assert-css: (".top-doc .docblock table tbody tr:nth-child(3)", { - "background-color": "rgba(0, 0, 0, 0)", + "background-color": |zebra_stripe_color|, }) assert-css: (".top-doc .docblock table tbody tr:nth-child(4)", { - "background-color": |zebra_stripe_color|, + "background-color": "rgba(0, 0, 0, 0)", }) assert-css: (".top-doc .docblock table td", { "border-style": "solid", From 795555e77821e916355131009277fdf19af390ee Mon Sep 17 00:00:00 2001 From: Pranav Dronavalli Date: Wed, 12 Aug 2026 22:51:13 +0000 Subject: [PATCH 31/31] doc changes to expect messages in process.rs * doc changes to expect messages in process.rs * req changes for expect message --- library/std/src/process.rs | 106 ++++++++++++++++++------------------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/library/std/src/process.rs b/library/std/src/process.rs index a398363cf4bf9..9f0bf72f755f4 100644 --- a/library/std/src/process.rs +++ b/library/std/src/process.rs @@ -14,7 +14,7 @@ //! let output = Command::new("echo") //! .arg("Hello world") //! .output() -//! .expect("Failed to execute command"); +//! .expect("echo command should execute successfully"); //! //! assert_eq!(b"Hello world\n", output.stdout.as_slice()); //! ``` @@ -41,20 +41,20 @@ //! .arg("Oh no, a tpyo!") //! .stdout(Stdio::piped()) //! .spawn() -//! .expect("Failed to start echo process"); +//! .expect("echo command should start"); //! //! // Note that `echo_child` is moved here, but we won't be needing //! // `echo_child` anymore -//! let echo_out = echo_child.stdout.expect("Failed to open echo stdout"); +//! let echo_out = echo_child.stdout.expect("child stdout should open"); //! //! let mut sed_child = Command::new("sed") //! .arg("s/tpyo/typo/") //! .stdin(Stdio::from(echo_out)) //! .stdout(Stdio::piped()) //! .spawn() -//! .expect("Failed to start sed process"); +//! .expect("sed command should start"); //! -//! let output = sed_child.wait_with_output().expect("Failed to wait on sed"); +//! let output = sed_child.wait_with_output().expect("wait_with_output on sed should succeed"); //! assert_eq!(b"Oh no, a typo!\n", output.stdout.as_slice()); //! ``` //! @@ -69,21 +69,21 @@ //! .stdin(Stdio::piped()) //! .stdout(Stdio::piped()) //! .spawn() -//! .expect("failed to execute child"); +//! .expect("child should start"); //! //! // If the child process fills its stdout buffer, it may end up //! // waiting until the parent reads the stdout, and not be able to //! // read stdin in the meantime, causing a deadlock. //! // Writing from another thread ensures that stdout is being read //! // at the same time, avoiding the problem. -//! let mut stdin = child.stdin.take().expect("failed to get stdin"); +//! let mut stdin = child.stdin.take().expect("stdin should be able to be retrieved"); //! std::thread::spawn(move || { -//! stdin.write_all(b"test").expect("failed to write to stdin"); +//! stdin.write_all(b"test").expect("writing to stdin should succeed"); //! }); //! //! let output = child //! .wait_with_output() -//! .expect("failed to wait on child"); +//! .expect("wait_with_output on child should succeed"); //! //! assert_eq!(b"test", output.stdout.as_slice()); //! ``` @@ -207,9 +207,9 @@ use crate::{fmt, format_args_nl, fs, str}; /// let mut child = Command::new("/bin/cat") /// .arg("file.txt") /// .spawn() -/// .expect("failed to execute child"); +/// .expect("child should spawn"); /// -/// let ecode = child.wait().expect("failed to wait on child"); +/// let ecode = child.wait().expect("child should be running"); /// /// assert!(ecode.success()); /// ``` @@ -224,7 +224,7 @@ pub struct Child { /// has been captured. You might find it helpful to do /// /// ```ignore (incomplete) - /// let stdin = child.stdin.take().expect("handle present"); + /// let stdin = child.stdin.take().expect("handle should be present"); /// ``` /// /// to avoid partially moving the `child` and thus blocking yourself from calling @@ -236,7 +236,7 @@ pub struct Child { /// has been captured. You might find it helpful to do /// /// ```ignore (incomplete) - /// let stdout = child.stdout.take().expect("handle present"); + /// let stdout = child.stdout.take().expect("handle should be present"); /// ``` /// /// to avoid partially moving the `child` and thus blocking yourself from calling @@ -248,7 +248,7 @@ pub struct Child { /// has been captured. You might find it helpful to do /// /// ```ignore (incomplete) - /// let stderr = child.stderr.take().expect("handle present"); + /// let stderr = child.stderr.take().expect("handle should be present"); /// ``` /// /// to avoid partially moving the `child` and thus blocking yourself from calling @@ -544,13 +544,13 @@ impl fmt::Debug for ChildStderr { /// Command::new("cmd") /// .args(["/C", "echo hello"]) /// .output() -/// .expect("failed to execute process") +/// .expect("process should execute successfully") /// } else { /// Command::new("sh") /// .arg("-c") /// .arg("echo hello") /// .output() -/// .expect("failed to execute process") +/// .expect("process should execute successfully") /// }; /// /// let hello = output.stdout; @@ -565,8 +565,8 @@ impl fmt::Debug for ChildStderr { /// /// let mut echo_hello = Command::new("sh"); /// echo_hello.arg("-c").arg("echo hello"); -/// let hello_1 = echo_hello.output().expect("failed to execute process"); -/// let hello_2 = echo_hello.output().expect("failed to execute process"); +/// let hello_1 = echo_hello.output().expect("process should execute successfully"); +/// let hello_2 = echo_hello.output().expect("process should execute successfully"); /// ``` /// /// Similarly, you can call builder methods after spawning a process and then @@ -578,7 +578,7 @@ impl fmt::Debug for ChildStderr { /// let mut list_dir = Command::new("ls"); /// /// // Execute `ls` in the current directory of the program. -/// list_dir.status().expect("process failed to execute"); +/// list_dir.status().expect("process should execute successfully"); /// /// println!(); /// @@ -586,7 +586,7 @@ impl fmt::Debug for ChildStderr { /// list_dir.current_dir("/"); /// /// // And then execute `ls` again but in the root directory. -/// list_dir.status().expect("process failed to execute"); +/// list_dir.status().expect("process should execute successfully"); /// ``` #[stable(feature = "process", since = "1.0.0")] #[cfg_attr(not(test), rustc_diagnostic_item = "Command")] @@ -662,7 +662,7 @@ impl Command { /// /// Command::new("sh") /// .spawn() - /// .expect("sh command failed to start"); + /// .expect("sh command should start"); /// ``` /// /// # Caveats @@ -678,7 +678,7 @@ impl Command { /// Command::new("ls") /// .arg("-l") // arg passed separately /// .spawn() - /// .expect("ls command failed to start"); + /// .expect("ls command should start"); /// ``` /// /// [`arg`]: Self::arg @@ -744,7 +744,7 @@ impl Command { /// .arg("-l") /// .arg("-a") /// .spawn() - /// .expect("ls command failed to start"); + /// .expect("ls command should start"); /// ``` #[stable(feature = "process", since = "1.0.0")] pub fn arg>(&mut self, arg: S) -> &mut Command { @@ -790,7 +790,7 @@ impl Command { /// Command::new("ls") /// .args(["-l", "-a"]) /// .spawn() - /// .expect("ls command failed to start"); + /// .expect("ls command should start"); /// ``` #[stable(feature = "process", since = "1.0.0")] pub fn args(&mut self, args: I) -> &mut Command @@ -826,7 +826,7 @@ impl Command { /// Command::new("ls") /// .env("PATH", "/bin") /// .spawn() - /// .expect("ls command failed to start"); + /// .expect("ls command should start"); /// ``` #[stable(feature = "process", since = "1.0.0")] pub fn env(&mut self, key: K, val: V) -> &mut Command @@ -870,7 +870,7 @@ impl Command { /// .env_clear() /// .envs(&filtered_env) /// .spawn() - /// .expect("printenv failed to start"); + /// .expect("printenv command should start"); /// ``` #[stable(feature = "command_envs", since = "1.19.0")] pub fn envs(&mut self, vars: I) -> &mut Command @@ -968,7 +968,7 @@ impl Command { /// Command::new("ls") /// .current_dir("/bin") /// .spawn() - /// .expect("ls command failed to start"); + /// .expect("ls command should start"); /// ``` /// /// [`canonicalize`]: crate::fs::canonicalize @@ -997,7 +997,7 @@ impl Command { /// Command::new("ls") /// .stdin(Stdio::null()) /// .spawn() - /// .expect("ls command failed to start"); + /// .expect("ls command should start"); /// ``` #[stable(feature = "process", since = "1.0.0")] pub fn stdin>(&mut self, cfg: T) -> &mut Command { @@ -1024,7 +1024,7 @@ impl Command { /// Command::new("ls") /// .stdout(Stdio::null()) /// .spawn() - /// .expect("ls command failed to start"); + /// .expect("ls command should start"); /// ``` #[stable(feature = "process", since = "1.0.0")] pub fn stdout>(&mut self, cfg: T) -> &mut Command { @@ -1051,7 +1051,7 @@ impl Command { /// Command::new("ls") /// .stderr(Stdio::null()) /// .spawn() - /// .expect("ls command failed to start"); + /// .expect("ls command should start"); /// ``` #[stable(feature = "process", since = "1.0.0")] pub fn stderr>(&mut self, cfg: T) -> &mut Command { @@ -1090,7 +1090,7 @@ impl Command { /// /// Command::new("ls") /// .spawn() - /// .expect("ls command failed to start"); + /// .expect("ls command should start"); /// ``` #[stable(feature = "process", since = "1.0.0")] pub fn spawn(&mut self) -> io::Result { @@ -1167,7 +1167,7 @@ impl Command { /// let status = Command::new("/bin/cat") /// .arg("file.txt") /// .status() - /// .expect("failed to execute process"); + /// .expect("process should execute successfully"); /// /// println!("process finished with: {status}"); /// @@ -1558,7 +1558,7 @@ impl Stdio { /// .arg("Hello, world!") /// .stdout(Stdio::piped()) /// .output() - /// .expect("Failed to execute command"); + /// .expect("process should execute successfully"); /// /// assert_eq!(String::from_utf8_lossy(&output.stdout), "Hello, world!\n"); /// // Nothing echoed to console @@ -1574,14 +1574,14 @@ impl Stdio { /// .stdin(Stdio::piped()) /// .stdout(Stdio::piped()) /// .spawn() - /// .expect("Failed to spawn child process"); + /// .expect("rev command should start"); /// - /// let mut stdin = child.stdin.take().expect("Failed to open stdin"); + /// let mut stdin = child.stdin.take().expect("child stdin should be retrievable"); /// std::thread::spawn(move || { - /// stdin.write_all("Hello, world!".as_bytes()).expect("Failed to write to stdin"); + /// stdin.write_all("Hello, world!".as_bytes()).expect("writing to child stdin should succeed"); /// }); /// - /// let output = child.wait_with_output().expect("Failed to read stdout"); + /// let output = child.wait_with_output().expect("child stdout should be able to be read"); /// assert_eq!(String::from_utf8_lossy(&output.stdout), "!dlrow ,olleH"); /// ``` /// @@ -1610,7 +1610,7 @@ impl Stdio { /// .arg("Hello, world!") /// .stdout(Stdio::inherit()) /// .output() - /// .expect("Failed to execute command"); + /// .expect("process should execute successfully"); /// /// assert_eq!(String::from_utf8_lossy(&output.stdout), ""); /// // "Hello, world!" echoed to console @@ -1651,7 +1651,7 @@ impl Stdio { /// .arg("Hello, world!") /// .stdout(Stdio::null()) /// .output() - /// .expect("Failed to execute command"); + /// .expect("process should execute successfully"); /// /// assert_eq!(String::from_utf8_lossy(&output.stdout), ""); /// // Nothing echoed to console @@ -1666,7 +1666,7 @@ impl Stdio { /// .stdin(Stdio::null()) /// .stdout(Stdio::piped()) /// .output() - /// .expect("Failed to execute command"); + /// .expect("process should execute successfully"); /// /// assert_eq!(String::from_utf8_lossy(&output.stdout), ""); /// // Ignores any piped-in input @@ -1721,13 +1721,13 @@ impl From for Stdio { /// let reverse = Command::new("rev") /// .stdin(Stdio::piped()) /// .spawn() - /// .expect("failed reverse command"); + /// .expect("rev command should start"); /// /// let _echo = Command::new("echo") /// .arg("Hello, world!") /// .stdout(reverse.stdin.unwrap()) // Converted into a Stdio here /// .output() - /// .expect("failed echo command"); + /// .expect("echo command should execute successfully"); /// /// // "!dlrow ,olleH" echoed to console /// ``` @@ -1751,12 +1751,12 @@ impl From for Stdio { /// .arg("Hello, world!") /// .stdout(Stdio::piped()) /// .spawn() - /// .expect("failed echo command"); + /// .expect("echo command should start"); /// /// let reverse = Command::new("rev") /// .stdin(hello.stdout.unwrap()) // Converted into a Stdio here /// .output() - /// .expect("failed reverse command"); + /// .expect("rev command should execute successfully"); /// /// assert_eq!(reverse.stdout, b"!dlrow ,olleH\n"); /// ``` @@ -1778,13 +1778,13 @@ impl From for Stdio { /// .arg("non_existing_file.txt") /// .stderr(Stdio::piped()) /// .spawn() - /// .expect("failed reverse command"); + /// .expect("rev command should start"); /// /// let cat = Command::new("cat") /// .arg("-") /// .stdin(reverse.stderr.unwrap()) // Converted into a Stdio here /// .output() - /// .expect("failed echo command"); + /// .expect("cat command should execute successfully"); /// /// assert_eq!( /// String::from_utf8_lossy(&cat.stdout), @@ -1952,7 +1952,7 @@ impl ExitStatus { /// let status = Command::new("ls") /// .arg("/dev/nonexistent") /// .status() - /// .expect("ls could not be executed"); + /// .expect("ls command should execute successfully"); /// /// println!("ls: {status}"); /// status.exit_ok().expect_err("/dev/nonexistent could be listed!"); @@ -1974,7 +1974,7 @@ impl ExitStatus { /// let status = Command::new("mkdir") /// .arg("projects") /// .status() - /// .expect("failed to execute mkdir"); + /// .expect("mkdir command should execute successfully"); /// /// if status.success() { /// println!("'projects/' directory created"); @@ -2007,7 +2007,7 @@ impl ExitStatus { /// let status = Command::new("mkdir") /// .arg("projects") /// .status() - /// .expect("failed to execute mkdir"); + /// .expect("mkdir command should execute successfully"); /// /// match status.code() { /// Some(code) => println!("Exited with status code: {code}"), @@ -2335,7 +2335,7 @@ impl Child { /// /// let mut command = Command::new("yes"); /// if let Ok(mut child) = command.spawn() { - /// child.kill().expect("command couldn't be killed"); + /// child.kill().expect("process should be killed"); /// } else { /// println!("yes command didn't start"); /// } @@ -2386,7 +2386,7 @@ impl Child { /// /// let mut command = Command::new("ls"); /// if let Ok(mut child) = command.spawn() { - /// child.wait().expect("command wasn't running"); + /// child.wait().expect("child should be running"); /// println!("Child has finished its execution!"); /// } else { /// println!("ls command didn't start"); @@ -2459,11 +2459,11 @@ impl Child { /// .arg("file.txt") /// .stdout(Stdio::piped()) /// .spawn() - /// .expect("failed to execute child"); + /// .expect("child should spawn"); /// /// let output = child /// .wait_with_output() - /// .expect("failed to wait on child"); + /// .expect("wait_with_output on child should succeed"); /// /// assert!(output.status.success()); /// ```