From 948bed2f0ca9e3a0ae718303149d8082ed513654 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Mon, 3 Nov 2025 21:00:40 +1100 Subject: [PATCH 1/8] Split out a separate `./x test bootstrap-py` step When working on the Rust parts of bootstrap, it's unhelpful for `./x test bootstrap` to also run Python unit tests. --- src/bootstrap/src/core/build_steps/test.rs | 44 +++++++++++++++++----- src/bootstrap/src/core/builder/mod.rs | 1 + 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs index 57dedcccf98c8..b66f965cd555b 100644 --- a/src/bootstrap/src/core/build_steps/test.rs +++ b/src/bootstrap/src/core/build_steps/test.rs @@ -3337,33 +3337,57 @@ fn distcheck_rustc_dev(builder: &Builder<'_>, dir: &Path) { builder.remove_dir(dir); } +/// Runs unit tests in `bootstrap_test.py`, which test the Python parts of bootstrap. #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Bootstrap; +pub(crate) struct BootstrapPy; -impl Step for Bootstrap { +impl Step for BootstrapPy { type Output = (); const DEFAULT: bool = true; const IS_HOST: bool = true; - /// Tests the build system itself. - fn run(self, builder: &Builder<'_>) { - let host = builder.config.host_target; - let build_compiler = builder.compiler(0, host); + fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { + // Bootstrap tests might not be perfectly self-contained and can depend + // on the environment, so only run them by default in CI, not locally. + // See `test::Bootstrap::should_run`. + let is_ci = run.builder.config.is_running_on_ci; + run.alias("bootstrap-py").default_condition(is_ci) + } - // Some tests require cargo submodule to be present. - builder.build.require_submodule("src/tools/cargo", None); + fn make_run(run: RunConfig<'_>) { + run.builder.ensure(BootstrapPy) + } + fn run(self, builder: &Builder<'_>) -> Self::Output { let mut check_bootstrap = command(builder.python()); check_bootstrap .args(["-m", "unittest", "bootstrap_test.py"]) + // Forward command-line args after `--` to unittest, for filtering etc. + .args(builder.config.test_args()) .env("BUILD_DIR", &builder.out) .env("BUILD_PLATFORM", builder.build.host_target.triple) .env("BOOTSTRAP_TEST_RUSTC_BIN", &builder.initial_rustc) .env("BOOTSTRAP_TEST_CARGO_BIN", &builder.initial_cargo) .current_dir(builder.src.join("src/bootstrap/")); - // NOTE: we intentionally don't pass test_args here because the args for unittest and cargo test are mutually incompatible. - // Use `python -m unittest` manually if you want to pass arguments. check_bootstrap.delay_failure().run(builder); + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Bootstrap; + +impl Step for Bootstrap { + type Output = (); + const DEFAULT: bool = true; + const IS_HOST: bool = true; + + /// Tests the build system itself. + fn run(self, builder: &Builder<'_>) { + let host = builder.config.host_target; + let build_compiler = builder.compiler(0, host); + + // Some tests require cargo submodule to be present. + builder.build.require_submodule("src/tools/cargo", None); let mut cargo = tool::prepare_tool_cargo( builder, diff --git a/src/bootstrap/src/core/builder/mod.rs b/src/bootstrap/src/core/builder/mod.rs index c7490c7072bda..43faf92fe6d9b 100644 --- a/src/bootstrap/src/core/builder/mod.rs +++ b/src/bootstrap/src/core/builder/mod.rs @@ -869,6 +869,7 @@ impl<'a> Builder<'a> { Kind::Test => describe!( crate::core::build_steps::toolstate::ToolStateCheck, test::Tidy, + test::BootstrapPy, test::Bootstrap, test::Ui, test::Crashes, From 7354d3d9c2ce2c1660e24f5257f9d6ace4a08387 Mon Sep 17 00:00:00 2001 From: beetrees Date: Fri, 4 Jul 2025 17:43:45 +0100 Subject: [PATCH 2/8] Add `#[rustc_pass_indirectly_in_non_rustic_abis]` --- compiler/rustc_abi/src/layout/ty.rs | 25 ++++++ compiler/rustc_abi/src/lib.rs | 15 ++-- .../src/attributes/codegen_attrs.rs | 9 ++ compiler/rustc_attr_parsing/src/context.rs | 6 +- compiler/rustc_feature/src/builtin_attrs.rs | 6 ++ .../rustc_hir/src/attrs/data_structures.rs | 3 + .../rustc_hir/src/attrs/encode_cross_crate.rs | 1 + compiler/rustc_middle/src/ty/layout.rs | 7 +- compiler/rustc_middle/src/ty/mod.rs | 8 ++ compiler/rustc_passes/src/check_attr.rs | 1 + compiler/rustc_span/src/symbol.rs | 1 + compiler/rustc_target/src/callconv/aarch64.rs | 4 + compiler/rustc_target/src/callconv/amdgpu.rs | 6 +- compiler/rustc_target/src/callconv/arm.rs | 4 + compiler/rustc_target/src/callconv/avr.rs | 18 +++- compiler/rustc_target/src/callconv/bpf.rs | 18 +++- compiler/rustc_target/src/callconv/csky.rs | 18 +++- compiler/rustc_target/src/callconv/hexagon.rs | 18 +++- .../rustc_target/src/callconv/loongarch.rs | 5 ++ compiler/rustc_target/src/callconv/m68k.rs | 18 +++- compiler/rustc_target/src/callconv/mips.rs | 17 +++- compiler/rustc_target/src/callconv/mips64.rs | 4 + compiler/rustc_target/src/callconv/mod.rs | 21 +++-- compiler/rustc_target/src/callconv/msp430.rs | 18 +++- compiler/rustc_target/src/callconv/nvptx64.rs | 16 +++- compiler/rustc_target/src/callconv/powerpc.rs | 14 +++- .../rustc_target/src/callconv/powerpc64.rs | 4 + compiler/rustc_target/src/callconv/riscv.rs | 6 ++ compiler/rustc_target/src/callconv/s390x.rs | 4 + compiler/rustc_target/src/callconv/sparc.rs | 14 +++- compiler/rustc_target/src/callconv/sparc64.rs | 4 + compiler/rustc_target/src/callconv/wasm.rs | 4 + compiler/rustc_target/src/callconv/x86.rs | 5 ++ compiler/rustc_target/src/callconv/x86_64.rs | 6 ++ .../rustc_target/src/callconv/x86_win32.rs | 5 ++ .../rustc_target/src/callconv/x86_win64.rs | 11 ++- compiler/rustc_target/src/callconv/xtensa.rs | 26 ++++-- tests/ui/abi/pass-indirectly-attr.rs | 16 ++++ tests/ui/abi/pass-indirectly-attr.stderr | 84 +++++++++++++++++++ tests/ui/attributes/pass-indirectly.rs | 15 ++++ tests/ui/attributes/pass-indirectly.stderr | 10 +++ 41 files changed, 438 insertions(+), 57 deletions(-) create mode 100644 tests/ui/abi/pass-indirectly-attr.rs create mode 100644 tests/ui/abi/pass-indirectly-attr.stderr create mode 100644 tests/ui/attributes/pass-indirectly.rs create mode 100644 tests/ui/attributes/pass-indirectly.stderr diff --git a/compiler/rustc_abi/src/layout/ty.rs b/compiler/rustc_abi/src/layout/ty.rs index 8d3c10fd770a6..3f80e5d3e9d5a 100644 --- a/compiler/rustc_abi/src/layout/ty.rs +++ b/compiler/rustc_abi/src/layout/ty.rs @@ -172,6 +172,8 @@ pub trait TyAbiInterface<'a, C>: Sized + std::fmt::Debug { fn is_tuple(this: TyAndLayout<'a, Self>) -> bool; fn is_unit(this: TyAndLayout<'a, Self>) -> bool; fn is_transparent(this: TyAndLayout<'a, Self>) -> bool; + /// See [`TyAndLayout::pass_indirectly_in_non_rustic_abis`] for details. + fn is_pass_indirectly_in_non_rustic_abis_flag_set(this: TyAndLayout<'a, Self>) -> bool; } impl<'a, Ty> TyAndLayout<'a, Ty> { @@ -269,6 +271,29 @@ impl<'a, Ty> TyAndLayout<'a, Ty> { Ty::is_transparent(self) } + /// If this method returns `true`, then this type should always have a `PassMode` of + /// `Indirect { on_stack: false, .. }` when being used as the argument type of a function with a + /// non-Rustic ABI (this is true for structs annotated with the + /// `#[rustc_pass_indirectly_in_non_rustic_abis]` attribute). + /// + /// This is used to replicate some of the behaviour of C array-to-pointer decay; however unlike + /// C any changes the caller makes to the passed value will not be reflected in the callee, so + /// the attribute is only useful for types where observing the value in the caller after the + /// function call isn't allowed (a.k.a. `va_list`). + /// + /// This function handles transparent types automatically. + pub fn pass_indirectly_in_non_rustic_abis(mut self, cx: &C) -> bool + where + Ty: TyAbiInterface<'a, C> + Copy, + { + while self.is_transparent() + && let Some((_, field)) = self.non_1zst_field(cx) + { + self = field; + } + Ty::is_pass_indirectly_in_non_rustic_abis_flag_set(self) + } + /// Finds the one field that is not a 1-ZST. /// Returns `None` if there are multiple non-1-ZST fields or only 1-ZST-fields. pub fn non_1zst_field(&self, cx: &C) -> Option<(FieldIdx, Self)> diff --git a/compiler/rustc_abi/src/lib.rs b/compiler/rustc_abi/src/lib.rs index de44c8755a078..05b25da8043ad 100644 --- a/compiler/rustc_abi/src/lib.rs +++ b/compiler/rustc_abi/src/lib.rs @@ -88,14 +88,17 @@ bitflags! { const IS_C = 1 << 0; const IS_SIMD = 1 << 1; const IS_TRANSPARENT = 1 << 2; - // Internal only for now. If true, don't reorder fields. - // On its own it does not prevent ABI optimizations. + /// Internal only for now. If true, don't reorder fields. + /// On its own it does not prevent ABI optimizations. const IS_LINEAR = 1 << 3; - // If true, the type's crate has opted into layout randomization. - // Other flags can still inhibit reordering and thus randomization. - // The seed stored in `ReprOptions.field_shuffle_seed`. + /// If true, the type's crate has opted into layout randomization. + /// Other flags can still inhibit reordering and thus randomization. + /// The seed stored in `ReprOptions.field_shuffle_seed`. const RANDOMIZE_LAYOUT = 1 << 4; - // Any of these flags being set prevent field reordering optimisation. + /// If true, the type is always passed indirectly by non-Rustic ABIs. + /// See [`TyAndLayout::pass_indirectly_in_non_rustic_abis`] for details. + const PASS_INDIRECTLY_IN_NON_RUSTIC_ABIS = 1 << 5; + /// Any of these flags being set prevent field reordering optimisation. const FIELD_ORDER_UNOPTIMIZABLE = ReprFlags::IS_C.bits() | ReprFlags::IS_SIMD.bits() | ReprFlags::IS_LINEAR.bits(); diff --git a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs index f09b02251e4d0..1270f8759d1bd 100644 --- a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs @@ -676,3 +676,12 @@ impl SingleAttributeParser for SanitizeParser { Some(AttributeKind::Sanitize { on_set, off_set, span: cx.attr_span }) } } + +pub(crate) struct RustcPassIndirectlyInNonRusticAbisParser; + +impl NoArgsAttributeParser for RustcPassIndirectlyInNonRusticAbisParser { + const PATH: &[Symbol] = &[sym::rustc_pass_indirectly_in_non_rustic_abis]; + const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Struct)]); + const CREATE: fn(Span) -> AttributeKind = AttributeKind::RustcPassIndirectlyInNonRusticAbis; +} diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index 773527c96a3a9..a3473c675dd59 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -20,8 +20,9 @@ use crate::attributes::allow_unstable::{ use crate::attributes::body::CoroutineParser; use crate::attributes::codegen_attrs::{ ColdParser, CoverageParser, ExportNameParser, ForceTargetFeatureParser, NakedParser, - NoMangleParser, ObjcClassParser, ObjcSelectorParser, OptimizeParser, SanitizeParser, - TargetFeatureParser, TrackCallerParser, UsedParser, + NoMangleParser, ObjcClassParser, ObjcSelectorParser, OptimizeParser, + RustcPassIndirectlyInNonRusticAbisParser, SanitizeParser, TargetFeatureParser, + TrackCallerParser, UsedParser, }; use crate::attributes::confusables::ConfusablesParser; use crate::attributes::crate_level::{ @@ -243,6 +244,7 @@ attribute_parsers!( Single>, Single>, Single>, + Single>, Single>, Single>, Single>, diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index 2c472801aa044..5dcb5df55720c 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -657,6 +657,12 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ template!(Word, "https://doc.rust-lang.org/reference/attributes/codegen.html#the-naked-attribute"), WarnFollowing, EncodeCrossCrate::No ), + // See `TyAndLayout::pass_indirectly_in_non_rustic_abis` for details. + rustc_attr!( + rustc_pass_indirectly_in_non_rustic_abis, Normal, template!(Word), ErrorFollowing, + EncodeCrossCrate::No, + "types marked with `#[rustc_pass_indirectly_in_non_rustic_abis]` are always passed indirectly by non-Rustic abis." + ), // Limits: ungated!( diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index 1bb87673d52d2..a5f7debe1787b 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -679,6 +679,9 @@ pub enum AttributeKind { /// Represents `#[rustc_object_lifetime_default]`. RustcObjectLifetimeDefault, + /// Represents `#[rustc_pass_indirectly_in_non_rustic_abis]` + RustcPassIndirectlyInNonRusticAbis(Span), + /// Represents `#[rustc_simd_monomorphize_lane_limit = "N"]`. RustcSimdMonomorphizeLaneLimit(Limit), diff --git a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs index 11c54b0ac1da0..d59fccb3f1a0b 100644 --- a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs +++ b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs @@ -91,6 +91,7 @@ impl AttributeKind { RustcLayoutScalarValidRangeStart(..) => Yes, RustcMain => No, RustcObjectLifetimeDefault => No, + RustcPassIndirectlyInNonRusticAbis(..) => No, RustcSimdMonomorphizeLaneLimit(..) => Yes, // Affects layout computation, which needs to work cross-crate Sanitize { .. } => No, ShouldPanic { .. } => No, diff --git a/compiler/rustc_middle/src/ty/layout.rs b/compiler/rustc_middle/src/ty/layout.rs index eefb913a33fa4..7763507143f4b 100644 --- a/compiler/rustc_middle/src/ty/layout.rs +++ b/compiler/rustc_middle/src/ty/layout.rs @@ -3,7 +3,7 @@ use std::{cmp, fmt}; use rustc_abi::{ AddressSpace, Align, ExternAbi, FieldIdx, FieldsShape, HasDataLayout, LayoutData, PointeeInfo, - PointerKind, Primitive, ReprOptions, Scalar, Size, TagEncoding, TargetDataLayout, + PointerKind, Primitive, ReprFlags, ReprOptions, Scalar, Size, TagEncoding, TargetDataLayout, TyAbiInterface, VariantIdx, Variants, }; use rustc_error_messages::DiagMessage; @@ -1173,6 +1173,11 @@ where fn is_transparent(this: TyAndLayout<'tcx>) -> bool { matches!(this.ty.kind(), ty::Adt(def, _) if def.repr().transparent()) } + + /// See [`TyAndLayout::pass_indirectly_in_non_rustic_abis`] for details. + fn is_pass_indirectly_in_non_rustic_abis_flag_set(this: TyAndLayout<'tcx>) -> bool { + matches!(this.ty.kind(), ty::Adt(def, _) if def.repr().flags.contains(ReprFlags::PASS_INDIRECTLY_IN_NON_RUSTIC_ABIS)) + } } /// Calculates whether a function's ABI can unwind or not. diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs index 2b9079da1830b..77c5e46e5e4cc 100644 --- a/compiler/rustc_middle/src/ty/mod.rs +++ b/compiler/rustc_middle/src/ty/mod.rs @@ -1573,6 +1573,14 @@ impl<'tcx> TyCtxt<'tcx> { flags.insert(ReprFlags::IS_LINEAR); } + // See `TyAndLayout::pass_indirectly_in_non_rustic_abis` for details. + if find_attr!( + self.get_all_attrs(did), + AttributeKind::RustcPassIndirectlyInNonRusticAbis(..) + ) { + flags.insert(ReprFlags::PASS_INDIRECTLY_IN_NON_RUSTIC_ABIS); + } + ReprOptions { int: size, align: max_align, pack: min_pack, flags, field_shuffle_seed } } diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index c214104d60670..0a266f3eb8b9a 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -284,6 +284,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { | AttributeKind::RustcCoherenceIsCore(..) | AttributeKind::DebuggerVisualizer(..) | AttributeKind::RustcMain + | AttributeKind::RustcPassIndirectlyInNonRusticAbis(..) | AttributeKind::PinV2(..), ) => { /* do nothing */ } Attribute::Unparsed(attr_item) => { diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 02bae6fec785f..86b6bb16835b9 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1944,6 +1944,7 @@ symbols! { rustc_partition_codegened, rustc_partition_reused, rustc_pass_by_value, + rustc_pass_indirectly_in_non_rustic_abis, rustc_peek, rustc_peek_liveness, rustc_peek_maybe_init, diff --git a/compiler/rustc_target/src/callconv/aarch64.rs b/compiler/rustc_target/src/callconv/aarch64.rs index 5c23e7036b043..b86d0baeb942e 100644 --- a/compiler/rustc_target/src/callconv/aarch64.rs +++ b/compiler/rustc_target/src/callconv/aarch64.rs @@ -114,6 +114,10 @@ where // Not touching this... return; } + if arg.layout.pass_indirectly_in_non_rustic_abis(cx) { + arg.make_indirect(); + return; + } if !arg.layout.is_aggregate() { if kind == AbiKind::DarwinPCS { // On Darwin, when passing an i8/i16, it must be sign-extended to 32 bits, diff --git a/compiler/rustc_target/src/callconv/amdgpu.rs b/compiler/rustc_target/src/callconv/amdgpu.rs index 3ec6d0b3da20b..98ab3ce8eb746 100644 --- a/compiler/rustc_target/src/callconv/amdgpu.rs +++ b/compiler/rustc_target/src/callconv/amdgpu.rs @@ -10,11 +10,15 @@ where ret.extend_integer_width_to(32); } -fn classify_arg<'a, Ty, C>(_cx: &C, arg: &mut ArgAbi<'a, Ty>) +fn classify_arg<'a, Ty, C>(cx: &C, arg: &mut ArgAbi<'a, Ty>) where Ty: TyAbiInterface<'a, C> + Copy, C: HasDataLayout, { + if arg.layout.pass_indirectly_in_non_rustic_abis(cx) { + arg.make_indirect(); + return; + } arg.extend_integer_width_to(32); } diff --git a/compiler/rustc_target/src/callconv/arm.rs b/compiler/rustc_target/src/callconv/arm.rs index abc9a404e2ea4..4c1ff27aac509 100644 --- a/compiler/rustc_target/src/callconv/arm.rs +++ b/compiler/rustc_target/src/callconv/arm.rs @@ -65,6 +65,10 @@ where // Not touching this... return; } + if arg.layout.pass_indirectly_in_non_rustic_abis(cx) { + arg.make_indirect(); + return; + } if !arg.layout.is_aggregate() { arg.extend_integer_width_to(32); return; diff --git a/compiler/rustc_target/src/callconv/avr.rs b/compiler/rustc_target/src/callconv/avr.rs index 0d690eff68dcf..0646d3c8ea1a4 100644 --- a/compiler/rustc_target/src/callconv/avr.rs +++ b/compiler/rustc_target/src/callconv/avr.rs @@ -30,6 +30,8 @@ //! compatible with AVR-GCC - Rust and AVR-GCC only differ in the small amount //! of compiler frontend specific calling convention logic implemented here. +use rustc_abi::TyAbiInterface; + use crate::callconv::{ArgAbi, FnAbi}; fn classify_ret_ty(ret: &mut ArgAbi<'_, Ty>) { @@ -38,13 +40,23 @@ fn classify_ret_ty(ret: &mut ArgAbi<'_, Ty>) { } } -fn classify_arg_ty(arg: &mut ArgAbi<'_, Ty>) { +fn classify_arg_ty<'a, Ty, C>(cx: &C, arg: &mut ArgAbi<'a, Ty>) +where + Ty: TyAbiInterface<'a, C> + Copy, +{ + if arg.layout.pass_indirectly_in_non_rustic_abis(cx) { + arg.make_indirect(); + return; + } if arg.layout.is_aggregate() { arg.make_indirect(); } } -pub(crate) fn compute_abi_info(fty: &mut FnAbi<'_, Ty>) { +pub(crate) fn compute_abi_info<'a, Ty, C>(cx: &C, fty: &mut FnAbi<'a, Ty>) +where + Ty: TyAbiInterface<'a, C> + Copy, +{ if !fty.ret.is_ignore() { classify_ret_ty(&mut fty.ret); } @@ -54,6 +66,6 @@ pub(crate) fn compute_abi_info(fty: &mut FnAbi<'_, Ty>) { continue; } - classify_arg_ty(arg); + classify_arg_ty(cx, arg); } } diff --git a/compiler/rustc_target/src/callconv/bpf.rs b/compiler/rustc_target/src/callconv/bpf.rs index ec5c1c2cf6860..3624f406704e9 100644 --- a/compiler/rustc_target/src/callconv/bpf.rs +++ b/compiler/rustc_target/src/callconv/bpf.rs @@ -1,4 +1,6 @@ // see https://github.com/llvm/llvm-project/blob/main/llvm/lib/Target/BPF/BPFCallingConv.td +use rustc_abi::TyAbiInterface; + use crate::callconv::{ArgAbi, FnAbi}; fn classify_ret(ret: &mut ArgAbi<'_, Ty>) { @@ -9,7 +11,14 @@ fn classify_ret(ret: &mut ArgAbi<'_, Ty>) { } } -fn classify_arg(arg: &mut ArgAbi<'_, Ty>) { +fn classify_arg<'a, Ty, C>(cx: &C, arg: &mut ArgAbi<'a, Ty>) +where + Ty: TyAbiInterface<'a, C> + Copy, +{ + if arg.layout.pass_indirectly_in_non_rustic_abis(cx) { + arg.make_indirect(); + return; + } if arg.layout.is_aggregate() || arg.layout.size.bits() > 64 { arg.make_indirect(); } else { @@ -17,7 +26,10 @@ fn classify_arg(arg: &mut ArgAbi<'_, Ty>) { } } -pub(crate) fn compute_abi_info(fn_abi: &mut FnAbi<'_, Ty>) { +pub(crate) fn compute_abi_info<'a, Ty, C>(cx: &C, fn_abi: &mut FnAbi<'a, Ty>) +where + Ty: TyAbiInterface<'a, C> + Copy, +{ if !fn_abi.ret.is_ignore() { classify_ret(&mut fn_abi.ret); } @@ -26,7 +38,7 @@ pub(crate) fn compute_abi_info(fn_abi: &mut FnAbi<'_, Ty>) { if arg.is_ignore() { continue; } - classify_arg(arg); + classify_arg(cx, arg); } } diff --git a/compiler/rustc_target/src/callconv/csky.rs b/compiler/rustc_target/src/callconv/csky.rs index cf289e639eac4..95741f137feab 100644 --- a/compiler/rustc_target/src/callconv/csky.rs +++ b/compiler/rustc_target/src/callconv/csky.rs @@ -4,6 +4,8 @@ // Reference: Clang CSKY lowering code // https://github.com/llvm/llvm-project/blob/4a074f32a6914f2a8d7215d78758c24942dddc3d/clang/lib/CodeGen/Targets/CSKY.cpp#L76-L162 +use rustc_abi::TyAbiInterface; + use crate::callconv::{ArgAbi, FnAbi, Reg, Uniform}; fn classify_ret(arg: &mut ArgAbi<'_, Ty>) { @@ -27,11 +29,18 @@ fn classify_ret(arg: &mut ArgAbi<'_, Ty>) { } } -fn classify_arg(arg: &mut ArgAbi<'_, Ty>) { +fn classify_arg<'a, Ty, C>(cx: &C, arg: &mut ArgAbi<'a, Ty>) +where + Ty: TyAbiInterface<'a, C> + Copy, +{ if !arg.layout.is_sized() { // Not touching this... return; } + if arg.layout.pass_indirectly_in_non_rustic_abis(cx) { + arg.make_indirect(); + return; + } // For argument type, the first 4*XLen parts of aggregate will be passed // in registers, and the rest will be passed in stack. // So we can coerce to integers directly and let backend handle it correctly. @@ -47,7 +56,10 @@ fn classify_arg(arg: &mut ArgAbi<'_, Ty>) { } } -pub(crate) fn compute_abi_info(fn_abi: &mut FnAbi<'_, Ty>) { +pub(crate) fn compute_abi_info<'a, Ty, C>(cx: &C, fn_abi: &mut FnAbi<'a, Ty>) +where + Ty: TyAbiInterface<'a, C> + Copy, +{ if !fn_abi.ret.is_ignore() { classify_ret(&mut fn_abi.ret); } @@ -56,6 +68,6 @@ pub(crate) fn compute_abi_info(fn_abi: &mut FnAbi<'_, Ty>) { if arg.is_ignore() { continue; } - classify_arg(arg); + classify_arg(cx, arg); } } diff --git a/compiler/rustc_target/src/callconv/hexagon.rs b/compiler/rustc_target/src/callconv/hexagon.rs index d4f6dd0a5b4d3..e08e6daa7405a 100644 --- a/compiler/rustc_target/src/callconv/hexagon.rs +++ b/compiler/rustc_target/src/callconv/hexagon.rs @@ -1,3 +1,5 @@ +use rustc_abi::TyAbiInterface; + use crate::callconv::{ArgAbi, FnAbi}; fn classify_ret(ret: &mut ArgAbi<'_, Ty>) { @@ -8,7 +10,14 @@ fn classify_ret(ret: &mut ArgAbi<'_, Ty>) { } } -fn classify_arg(arg: &mut ArgAbi<'_, Ty>) { +fn classify_arg<'a, Ty, C>(cx: &C, arg: &mut ArgAbi<'a, Ty>) +where + Ty: TyAbiInterface<'a, C> + Copy, +{ + if arg.layout.pass_indirectly_in_non_rustic_abis(cx) { + arg.make_indirect(); + return; + } if arg.layout.is_aggregate() && arg.layout.size.bits() > 64 { arg.make_indirect(); } else { @@ -16,7 +25,10 @@ fn classify_arg(arg: &mut ArgAbi<'_, Ty>) { } } -pub(crate) fn compute_abi_info(fn_abi: &mut FnAbi<'_, Ty>) { +pub(crate) fn compute_abi_info<'a, Ty, C>(cx: &C, fn_abi: &mut FnAbi<'a, Ty>) +where + Ty: TyAbiInterface<'a, C> + Copy, +{ if !fn_abi.ret.is_ignore() { classify_ret(&mut fn_abi.ret); } @@ -25,6 +37,6 @@ pub(crate) fn compute_abi_info(fn_abi: &mut FnAbi<'_, Ty>) { if arg.is_ignore() { continue; } - classify_arg(arg); + classify_arg(cx, arg); } } diff --git a/compiler/rustc_target/src/callconv/loongarch.rs b/compiler/rustc_target/src/callconv/loongarch.rs index bc3c9601fa3d3..bbea841b41069 100644 --- a/compiler/rustc_target/src/callconv/loongarch.rs +++ b/compiler/rustc_target/src/callconv/loongarch.rs @@ -287,6 +287,11 @@ fn classify_arg<'a, Ty, C>( // Not touching this... return; } + if arg.layout.pass_indirectly_in_non_rustic_abis(cx) { + arg.make_indirect(); + *avail_gprs = (*avail_gprs).saturating_sub(1); + return; + } if !is_vararg { match should_use_fp_conv(cx, &arg.layout, xlen, flen) { Some(FloatConv::Float(f)) if *avail_fprs >= 1 => { diff --git a/compiler/rustc_target/src/callconv/m68k.rs b/compiler/rustc_target/src/callconv/m68k.rs index 0b637e1e27a5b..f4668b4afbd20 100644 --- a/compiler/rustc_target/src/callconv/m68k.rs +++ b/compiler/rustc_target/src/callconv/m68k.rs @@ -1,3 +1,5 @@ +use rustc_abi::TyAbiInterface; + use crate::callconv::{ArgAbi, FnAbi}; fn classify_ret(ret: &mut ArgAbi<'_, Ty>) { @@ -8,11 +10,18 @@ fn classify_ret(ret: &mut ArgAbi<'_, Ty>) { } } -fn classify_arg(arg: &mut ArgAbi<'_, Ty>) { +fn classify_arg<'a, Ty, C>(cx: &C, arg: &mut ArgAbi<'a, Ty>) +where + Ty: TyAbiInterface<'a, C> + Copy, +{ if !arg.layout.is_sized() { // Not touching this... return; } + if arg.layout.pass_indirectly_in_non_rustic_abis(cx) { + arg.make_indirect(); + return; + } if arg.layout.is_aggregate() { arg.pass_by_stack_offset(None); } else { @@ -20,7 +29,10 @@ fn classify_arg(arg: &mut ArgAbi<'_, Ty>) { } } -pub(crate) fn compute_abi_info(fn_abi: &mut FnAbi<'_, Ty>) { +pub(crate) fn compute_abi_info<'a, Ty, C>(cx: &C, fn_abi: &mut FnAbi<'a, Ty>) +where + Ty: TyAbiInterface<'a, C> + Copy, +{ if !fn_abi.ret.is_ignore() { classify_ret(&mut fn_abi.ret); } @@ -29,6 +41,6 @@ pub(crate) fn compute_abi_info(fn_abi: &mut FnAbi<'_, Ty>) { if arg.is_ignore() { continue; } - classify_arg(arg); + classify_arg(cx, arg); } } diff --git a/compiler/rustc_target/src/callconv/mips.rs b/compiler/rustc_target/src/callconv/mips.rs index 8ffd7bd177849..d2572cc035c1c 100644 --- a/compiler/rustc_target/src/callconv/mips.rs +++ b/compiler/rustc_target/src/callconv/mips.rs @@ -1,4 +1,4 @@ -use rustc_abi::{HasDataLayout, Size}; +use rustc_abi::{HasDataLayout, Size, TyAbiInterface}; use crate::callconv::{ArgAbi, FnAbi, Reg, Uniform}; @@ -14,18 +14,26 @@ where } } -fn classify_arg(cx: &C, arg: &mut ArgAbi<'_, Ty>, offset: &mut Size) +fn classify_arg<'a, Ty, C>(cx: &C, arg: &mut ArgAbi<'a, Ty>, offset: &mut Size) where + Ty: TyAbiInterface<'a, C> + Copy, C: HasDataLayout, { if !arg.layout.is_sized() { + // FIXME: Update offset? // Not touching this... return; } let dl = cx.data_layout(); - let size = arg.layout.size; let align = arg.layout.align.abi.max(dl.i32_align).min(dl.i64_align); + if arg.layout.pass_indirectly_in_non_rustic_abis(cx) { + arg.make_indirect(); + *offset = offset.align_to(align) + dl.pointer_size().align_to(align); + return; + } + + let size = arg.layout.size; if arg.layout.is_aggregate() { let pad_i32 = !offset.is_aligned(align); arg.cast_to_and_pad_i32(Uniform::new(Reg::i32(), size), pad_i32); @@ -36,8 +44,9 @@ where *offset = offset.align_to(align) + size.align_to(align); } -pub(crate) fn compute_abi_info(cx: &C, fn_abi: &mut FnAbi<'_, Ty>) +pub(crate) fn compute_abi_info<'a, Ty, C>(cx: &C, fn_abi: &mut FnAbi<'a, Ty>) where + Ty: TyAbiInterface<'a, C> + Copy, C: HasDataLayout, { let mut offset = Size::ZERO; diff --git a/compiler/rustc_target/src/callconv/mips64.rs b/compiler/rustc_target/src/callconv/mips64.rs index 8386a15933c98..313ad6ddce800 100644 --- a/compiler/rustc_target/src/callconv/mips64.rs +++ b/compiler/rustc_target/src/callconv/mips64.rs @@ -82,6 +82,10 @@ where extend_integer_width_mips(arg, 64); return; } + if arg.layout.pass_indirectly_in_non_rustic_abis(cx) { + arg.make_indirect(); + return; + } let dl = cx.data_layout(); let size = arg.layout.size; diff --git a/compiler/rustc_target/src/callconv/mod.rs b/compiler/rustc_target/src/callconv/mod.rs index a33c246c88c67..a51517338e552 100644 --- a/compiler/rustc_target/src/callconv/mod.rs +++ b/compiler/rustc_target/src/callconv/mod.rs @@ -677,32 +677,39 @@ impl<'a, Ty> FnAbi<'a, Ty> { } "amdgpu" => amdgpu::compute_abi_info(cx, self), "arm" => arm::compute_abi_info(cx, self), - "avr" => avr::compute_abi_info(self), + "avr" => avr::compute_abi_info(cx, self), "loongarch32" | "loongarch64" => loongarch::compute_abi_info(cx, self), - "m68k" => m68k::compute_abi_info(self), - "csky" => csky::compute_abi_info(self), + "m68k" => m68k::compute_abi_info(cx, self), + "csky" => csky::compute_abi_info(cx, self), "mips" | "mips32r6" => mips::compute_abi_info(cx, self), "mips64" | "mips64r6" => mips64::compute_abi_info(cx, self), "powerpc" => powerpc::compute_abi_info(cx, self), "powerpc64" => powerpc64::compute_abi_info(cx, self), "s390x" => s390x::compute_abi_info(cx, self), - "msp430" => msp430::compute_abi_info(self), + "msp430" => msp430::compute_abi_info(cx, self), "sparc" => sparc::compute_abi_info(cx, self), "sparc64" => sparc64::compute_abi_info(cx, self), "nvptx64" => { if abi == ExternAbi::PtxKernel || abi == ExternAbi::GpuKernel { nvptx64::compute_ptx_kernel_abi_info(cx, self) } else { - nvptx64::compute_abi_info(self) + nvptx64::compute_abi_info(cx, self) } } - "hexagon" => hexagon::compute_abi_info(self), + "hexagon" => hexagon::compute_abi_info(cx, self), "xtensa" => xtensa::compute_abi_info(cx, self), "riscv32" | "riscv64" => riscv::compute_abi_info(cx, self), "wasm32" | "wasm64" => wasm::compute_abi_info(cx, self), - "bpf" => bpf::compute_abi_info(self), + "bpf" => bpf::compute_abi_info(cx, self), arch => panic!("no lowering implemented for {arch}"), } + // Double check that any argument types annotated with the + // `#[rustc_pass_indirectly_in_non_rustic_abis]` attribute are passed indirectly. + for arg in &self.args { + if arg.layout.pass_indirectly_in_non_rustic_abis(cx) { + assert!(matches!(arg.mode, PassMode::Indirect { on_stack: false, .. })); + } + } } pub fn adjust_for_rust_abi(&mut self, cx: &C) diff --git a/compiler/rustc_target/src/callconv/msp430.rs b/compiler/rustc_target/src/callconv/msp430.rs index 3b53d183ddcda..7d2336346beb5 100644 --- a/compiler/rustc_target/src/callconv/msp430.rs +++ b/compiler/rustc_target/src/callconv/msp430.rs @@ -1,6 +1,8 @@ // Reference: MSP430 Embedded Application Binary Interface // https://www.ti.com/lit/an/slaa534a/slaa534a.pdf +use rustc_abi::TyAbiInterface; + use crate::callconv::{ArgAbi, FnAbi}; // 3.5 Structures or Unions Passed and Returned by Reference @@ -17,7 +19,14 @@ fn classify_ret(ret: &mut ArgAbi<'_, Ty>) { } } -fn classify_arg(arg: &mut ArgAbi<'_, Ty>) { +fn classify_arg<'a, Ty, C>(cx: &C, arg: &mut ArgAbi<'a, Ty>) +where + Ty: TyAbiInterface<'a, C> + Copy, +{ + if arg.layout.pass_indirectly_in_non_rustic_abis(cx) { + arg.make_indirect(); + return; + } if arg.layout.is_aggregate() && arg.layout.size.bits() > 32 { arg.make_indirect(); } else { @@ -25,7 +34,10 @@ fn classify_arg(arg: &mut ArgAbi<'_, Ty>) { } } -pub(crate) fn compute_abi_info(fn_abi: &mut FnAbi<'_, Ty>) { +pub(crate) fn compute_abi_info<'a, Ty, C>(cx: &C, fn_abi: &mut FnAbi<'a, Ty>) +where + Ty: TyAbiInterface<'a, C> + Copy, +{ if !fn_abi.ret.is_ignore() { classify_ret(&mut fn_abi.ret); } @@ -34,6 +46,6 @@ pub(crate) fn compute_abi_info(fn_abi: &mut FnAbi<'_, Ty>) { if arg.is_ignore() { continue; } - classify_arg(arg); + classify_arg(cx, arg); } } diff --git a/compiler/rustc_target/src/callconv/nvptx64.rs b/compiler/rustc_target/src/callconv/nvptx64.rs index dc32dd87a7e76..a19b89c0132f8 100644 --- a/compiler/rustc_target/src/callconv/nvptx64.rs +++ b/compiler/rustc_target/src/callconv/nvptx64.rs @@ -11,7 +11,14 @@ fn classify_ret(ret: &mut ArgAbi<'_, Ty>) { } } -fn classify_arg(arg: &mut ArgAbi<'_, Ty>) { +fn classify_arg<'a, Ty, C>(cx: &C, arg: &mut ArgAbi<'a, Ty>) +where + Ty: TyAbiInterface<'a, C> + Copy, +{ + if arg.layout.pass_indirectly_in_non_rustic_abis(cx) { + arg.make_indirect(); + return; + } if arg.layout.is_aggregate() && arg.layout.is_sized() { classify_aggregate(arg) } else if arg.layout.size.bits() < 32 && arg.layout.is_sized() { @@ -81,7 +88,10 @@ where } } -pub(crate) fn compute_abi_info(fn_abi: &mut FnAbi<'_, Ty>) { +pub(crate) fn compute_abi_info<'a, Ty, C>(cx: &C, fn_abi: &mut FnAbi<'a, Ty>) +where + Ty: TyAbiInterface<'a, C> + Copy, +{ if !fn_abi.ret.is_ignore() { classify_ret(&mut fn_abi.ret); } @@ -90,7 +100,7 @@ pub(crate) fn compute_abi_info(fn_abi: &mut FnAbi<'_, Ty>) { if arg.is_ignore() { continue; } - classify_arg(arg); + classify_arg(cx, arg); } } diff --git a/compiler/rustc_target/src/callconv/powerpc.rs b/compiler/rustc_target/src/callconv/powerpc.rs index 2f6129626b812..67066672eca3f 100644 --- a/compiler/rustc_target/src/callconv/powerpc.rs +++ b/compiler/rustc_target/src/callconv/powerpc.rs @@ -1,3 +1,5 @@ +use rustc_abi::TyAbiInterface; + use crate::callconv::{ArgAbi, FnAbi}; use crate::spec::HasTargetSpec; @@ -9,7 +11,10 @@ fn classify_ret(ret: &mut ArgAbi<'_, Ty>) { } } -fn classify_arg(cx: &impl HasTargetSpec, arg: &mut ArgAbi<'_, Ty>) { +fn classify_arg<'a, Ty, C: HasTargetSpec>(cx: &C, arg: &mut ArgAbi<'a, Ty>) +where + Ty: TyAbiInterface<'a, C> + Copy, +{ if arg.is_ignore() { // powerpc-unknown-linux-{gnu,musl,uclibc} doesn't ignore ZSTs. if cx.target_spec().os == "linux" @@ -20,14 +25,17 @@ fn classify_arg(cx: &impl HasTargetSpec, arg: &mut ArgAbi<'_, Ty>) { } return; } - if arg.layout.is_aggregate() { + if arg.layout.pass_indirectly_in_non_rustic_abis(cx) || arg.layout.is_aggregate() { arg.make_indirect(); } else { arg.extend_integer_width_to(32); } } -pub(crate) fn compute_abi_info(cx: &impl HasTargetSpec, fn_abi: &mut FnAbi<'_, Ty>) { +pub(crate) fn compute_abi_info<'a, Ty, C: HasTargetSpec>(cx: &C, fn_abi: &mut FnAbi<'a, Ty>) +where + Ty: TyAbiInterface<'a, C> + Copy, +{ if !fn_abi.ret.is_ignore() { classify_ret(&mut fn_abi.ret); } diff --git a/compiler/rustc_target/src/callconv/powerpc64.rs b/compiler/rustc_target/src/callconv/powerpc64.rs index be1d13816eff7..380b280fbc644 100644 --- a/compiler/rustc_target/src/callconv/powerpc64.rs +++ b/compiler/rustc_target/src/callconv/powerpc64.rs @@ -52,6 +52,10 @@ where // Not touching this... return; } + if !is_ret && arg.layout.pass_indirectly_in_non_rustic_abis(cx) { + arg.make_indirect(); + return; + } if !arg.layout.is_aggregate() { arg.extend_integer_width_to(64); return; diff --git a/compiler/rustc_target/src/callconv/riscv.rs b/compiler/rustc_target/src/callconv/riscv.rs index 16de3fe070dd4..c4310bf6ccfb0 100644 --- a/compiler/rustc_target/src/callconv/riscv.rs +++ b/compiler/rustc_target/src/callconv/riscv.rs @@ -290,9 +290,15 @@ fn classify_arg<'a, Ty, C>( Ty: TyAbiInterface<'a, C> + Copy, { if !arg.layout.is_sized() { + // FIXME: Update avail_gprs? // Not touching this... return; } + if arg.layout.pass_indirectly_in_non_rustic_abis(cx) { + arg.make_indirect(); + *avail_gprs = (*avail_gprs).saturating_sub(1); + return; + } if !is_vararg { match should_use_fp_conv(cx, &arg.layout, xlen, flen) { Some(FloatConv::Float(f)) if *avail_fprs >= 1 => { diff --git a/compiler/rustc_target/src/callconv/s390x.rs b/compiler/rustc_target/src/callconv/s390x.rs index d2ae404b23b8b..c2f2b47690cab 100644 --- a/compiler/rustc_target/src/callconv/s390x.rs +++ b/compiler/rustc_target/src/callconv/s390x.rs @@ -37,6 +37,10 @@ where } return; } + if arg.layout.pass_indirectly_in_non_rustic_abis(cx) { + arg.make_indirect(); + return; + } let size = arg.layout.size; if size.bits() <= 128 { diff --git a/compiler/rustc_target/src/callconv/sparc.rs b/compiler/rustc_target/src/callconv/sparc.rs index 8ffd7bd177849..d424214aa497e 100644 --- a/compiler/rustc_target/src/callconv/sparc.rs +++ b/compiler/rustc_target/src/callconv/sparc.rs @@ -1,4 +1,4 @@ -use rustc_abi::{HasDataLayout, Size}; +use rustc_abi::{HasDataLayout, Size, TyAbiInterface}; use crate::callconv::{ArgAbi, FnAbi, Reg, Uniform}; @@ -14,15 +14,22 @@ where } } -fn classify_arg(cx: &C, arg: &mut ArgAbi<'_, Ty>, offset: &mut Size) +fn classify_arg<'a, Ty, C>(cx: &C, arg: &mut ArgAbi<'a, Ty>, offset: &mut Size) where + Ty: TyAbiInterface<'a, C> + Copy, C: HasDataLayout, { if !arg.layout.is_sized() { + // FIXME: Update offset? // Not touching this... return; } let dl = cx.data_layout(); + if arg.layout.pass_indirectly_in_non_rustic_abis(cx) { + arg.make_indirect(); + *offset += dl.pointer_size(); + return; + } let size = arg.layout.size; let align = arg.layout.align.abi.max(dl.i32_align).min(dl.i64_align); @@ -36,8 +43,9 @@ where *offset = offset.align_to(align) + size.align_to(align); } -pub(crate) fn compute_abi_info(cx: &C, fn_abi: &mut FnAbi<'_, Ty>) +pub(crate) fn compute_abi_info<'a, Ty, C>(cx: &C, fn_abi: &mut FnAbi<'a, Ty>) where + Ty: TyAbiInterface<'a, C> + Copy, C: HasDataLayout, { let mut offset = Size::ZERO; diff --git a/compiler/rustc_target/src/callconv/sparc64.rs b/compiler/rustc_target/src/callconv/sparc64.rs index 62c8ed1dc21b1..911eaaf08f825 100644 --- a/compiler/rustc_target/src/callconv/sparc64.rs +++ b/compiler/rustc_target/src/callconv/sparc64.rs @@ -140,6 +140,10 @@ where Ty: TyAbiInterface<'a, C> + Copy, C: HasDataLayout, { + if arg.layout.pass_indirectly_in_non_rustic_abis(cx) { + arg.make_indirect(); + return; + } if !arg.layout.is_aggregate() { arg.extend_integer_width_to(64); return; diff --git a/compiler/rustc_target/src/callconv/wasm.rs b/compiler/rustc_target/src/callconv/wasm.rs index a308f378ee871..5be8cd5d88f26 100644 --- a/compiler/rustc_target/src/callconv/wasm.rs +++ b/compiler/rustc_target/src/callconv/wasm.rs @@ -52,6 +52,10 @@ where // Not touching this... return; } + if arg.layout.pass_indirectly_in_non_rustic_abis(cx) { + arg.make_indirect(); + return; + } arg.extend_integer_width_to(32); if arg.layout.is_aggregate() && !unwrap_trivial_aggregate(cx, arg) { arg.make_indirect(); diff --git a/compiler/rustc_target/src/callconv/x86.rs b/compiler/rustc_target/src/callconv/x86.rs index 918b71c80c4f0..93dc60dc1593e 100644 --- a/compiler/rustc_target/src/callconv/x86.rs +++ b/compiler/rustc_target/src/callconv/x86.rs @@ -64,6 +64,11 @@ where continue; } + if arg.layout.pass_indirectly_in_non_rustic_abis(cx) { + arg.make_indirect(); + continue; + } + let t = cx.target_spec(); let align_4 = Align::from_bytes(4).unwrap(); let align_16 = Align::from_bytes(16).unwrap(); diff --git a/compiler/rustc_target/src/callconv/x86_64.rs b/compiler/rustc_target/src/callconv/x86_64.rs index d8db7ed6e4c0f..04eecbfbc8dd6 100644 --- a/compiler/rustc_target/src/callconv/x86_64.rs +++ b/compiler/rustc_target/src/callconv/x86_64.rs @@ -183,9 +183,15 @@ where let mut x86_64_arg_or_ret = |arg: &mut ArgAbi<'a, Ty>, is_arg: bool| { if !arg.layout.is_sized() { + // FIXME: Update int_regs? // Not touching this... return; } + if is_arg && arg.layout.pass_indirectly_in_non_rustic_abis(cx) { + int_regs = int_regs.saturating_sub(1); + arg.make_indirect(); + return; + } let mut cls_or_mem = classify_arg(cx, arg); if is_arg { diff --git a/compiler/rustc_target/src/callconv/x86_win32.rs b/compiler/rustc_target/src/callconv/x86_win32.rs index 554a7368848b5..824e7cc098a46 100644 --- a/compiler/rustc_target/src/callconv/x86_win32.rs +++ b/compiler/rustc_target/src/callconv/x86_win32.rs @@ -46,6 +46,11 @@ pub(crate) fn compute_abi_info<'a, Ty, C>( continue; } + if arg.layout.pass_indirectly_in_non_rustic_abis(cx) { + arg.make_indirect(); + continue; + } + // FIXME: MSVC 2015+ will pass the first 3 vector arguments in [XYZ]MM0-2 // See https://reviews.llvm.org/D72114 for Clang behavior diff --git a/compiler/rustc_target/src/callconv/x86_win64.rs b/compiler/rustc_target/src/callconv/x86_win64.rs index 8f8597ea662a8..85ee59eabc7c4 100644 --- a/compiler/rustc_target/src/callconv/x86_win64.rs +++ b/compiler/rustc_target/src/callconv/x86_win64.rs @@ -1,11 +1,14 @@ -use rustc_abi::{BackendRepr, Float, Integer, Primitive, RegKind, Size}; +use rustc_abi::{BackendRepr, Float, Integer, Primitive, RegKind, Size, TyAbiInterface}; use crate::callconv::{ArgAbi, FnAbi, Reg}; use crate::spec::{HasTargetSpec, RustcAbi}; // Win64 ABI: https://docs.microsoft.com/en-us/cpp/build/parameter-passing -pub(crate) fn compute_abi_info(cx: &impl HasTargetSpec, fn_abi: &mut FnAbi<'_, Ty>) { +pub(crate) fn compute_abi_info<'a, Ty, C: HasTargetSpec>(cx: &C, fn_abi: &mut FnAbi<'a, Ty>) +where + Ty: TyAbiInterface<'a, C> + Copy, +{ let fixup = |a: &mut ArgAbi<'_, Ty>, is_ret: bool| { match a.layout.backend_repr { BackendRepr::Memory { sized: false } => {} @@ -59,6 +62,10 @@ pub(crate) fn compute_abi_info(cx: &impl HasTargetSpec, fn_abi: &mut FnAbi<' arg.make_indirect_from_ignore(); continue; } + if arg.layout.pass_indirectly_in_non_rustic_abis(cx) { + arg.make_indirect(); + continue; + } fixup(arg, false); } // FIXME: We should likely also do something about ZST return types, similar to above. diff --git a/compiler/rustc_target/src/callconv/xtensa.rs b/compiler/rustc_target/src/callconv/xtensa.rs index 561ee98787dee..4dc9fad650636 100644 --- a/compiler/rustc_target/src/callconv/xtensa.rs +++ b/compiler/rustc_target/src/callconv/xtensa.rs @@ -15,7 +15,7 @@ const NUM_RET_GPRS: u64 = 4; const MAX_ARG_IN_REGS_SIZE: u64 = NUM_ARG_GPRS * 32; const MAX_RET_IN_REGS_SIZE: u64 = NUM_RET_GPRS * 32; -fn classify_ret_ty<'a, Ty, C>(arg: &mut ArgAbi<'_, Ty>) +fn classify_ret_ty<'a, Ty, C>(cx: &C, arg: &mut ArgAbi<'a, Ty>) where Ty: TyAbiInterface<'a, C> + Copy, { @@ -26,7 +26,7 @@ where // The rules for return and argument types are the same, // so defer to `classify_arg_ty`. let mut arg_gprs_left = NUM_RET_GPRS; - classify_arg_ty(arg, &mut arg_gprs_left, MAX_RET_IN_REGS_SIZE); + classify_arg_ty(cx, arg, &mut arg_gprs_left, true); // Ret args cannot be passed via stack, we lower to indirect and let the backend handle the invisible reference match arg.mode { super::PassMode::Indirect { attrs: _, meta_attrs: _, ref mut on_stack } => { @@ -36,12 +36,24 @@ where } } -fn classify_arg_ty<'a, Ty, C>(arg: &mut ArgAbi<'_, Ty>, arg_gprs_left: &mut u64, max_size: u64) -where +fn classify_arg_ty<'a, Ty, C>( + cx: &C, + arg: &mut ArgAbi<'a, Ty>, + arg_gprs_left: &mut u64, + is_ret: bool, +) where Ty: TyAbiInterface<'a, C> + Copy, { assert!(*arg_gprs_left <= NUM_ARG_GPRS, "Arg GPR tracking underflow"); + let max_size = if is_ret { MAX_RET_IN_REGS_SIZE } else { MAX_ARG_IN_REGS_SIZE }; + + if !is_ret && arg.layout.pass_indirectly_in_non_rustic_abis(cx) { + *arg_gprs_left = arg_gprs_left.saturating_sub(1); + arg.make_indirect(); + return; + } + // Ignore empty structs/unions. if arg.layout.is_zst() { return; @@ -95,13 +107,13 @@ where } } -pub(crate) fn compute_abi_info<'a, Ty, C>(_cx: &C, fn_abi: &mut FnAbi<'a, Ty>) +pub(crate) fn compute_abi_info<'a, Ty, C>(cx: &C, fn_abi: &mut FnAbi<'a, Ty>) where Ty: TyAbiInterface<'a, C> + Copy, C: HasDataLayout + HasTargetSpec, { if !fn_abi.ret.is_ignore() { - classify_ret_ty(&mut fn_abi.ret); + classify_ret_ty(cx, &mut fn_abi.ret); } let mut arg_gprs_left = NUM_ARG_GPRS; @@ -110,7 +122,7 @@ where if arg.is_ignore() { continue; } - classify_arg_ty(arg, &mut arg_gprs_left, MAX_ARG_IN_REGS_SIZE); + classify_arg_ty(cx, arg, &mut arg_gprs_left, false); } } diff --git a/tests/ui/abi/pass-indirectly-attr.rs b/tests/ui/abi/pass-indirectly-attr.rs new file mode 100644 index 0000000000000..3460abfe1eda6 --- /dev/null +++ b/tests/ui/abi/pass-indirectly-attr.rs @@ -0,0 +1,16 @@ +//@ check-fail +//@ normalize-stderr: "randomization_seed: \d+" -> "randomization_seed: $$SEED" +//@ compile-flags: -O + +#![feature(rustc_attrs)] +#![crate_type = "lib"] + +#[repr(C)] +#[rustc_pass_indirectly_in_non_rustic_abis] +pub struct Type(u8); + +#[rustc_abi(debug)] +pub extern "C" fn func(_: Type) {} +//~^ ERROR fn_abi_of(func) = FnAbi { +//~^^ ERROR mode: Indirect { +//~^^^ ERROR on_stack: false, diff --git a/tests/ui/abi/pass-indirectly-attr.stderr b/tests/ui/abi/pass-indirectly-attr.stderr new file mode 100644 index 0000000000000..0f979db5d5ac5 --- /dev/null +++ b/tests/ui/abi/pass-indirectly-attr.stderr @@ -0,0 +1,84 @@ +error: fn_abi_of(func) = FnAbi { + args: [ + ArgAbi { + layout: TyAndLayout { + ty: Type, + layout: Layout { + size: Size(1 bytes), + align: AbiAlign { + abi: Align(1 bytes), + }, + backend_repr: Memory { + sized: true, + }, + fields: Arbitrary { + offsets: [ + Size(0 bytes), + ], + memory_index: [ + 0, + ], + }, + largest_niche: None, + uninhabited: false, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: Align(1 bytes), + randomization_seed: $SEED, + }, + }, + mode: Indirect { + attrs: ArgAttributes { + regular: CapturesAddress | NoAlias | NonNull | NoUndef, + arg_ext: None, + pointee_size: Size(1 bytes), + pointee_align: Some( + Align(1 bytes), + ), + }, + meta_attrs: None, + on_stack: false, + }, + }, + ], + ret: ArgAbi { + layout: TyAndLayout { + ty: (), + layout: Layout { + size: Size(0 bytes), + align: AbiAlign { + abi: Align(1 bytes), + }, + backend_repr: Memory { + sized: true, + }, + fields: Arbitrary { + offsets: [], + memory_index: [], + }, + largest_niche: None, + uninhabited: false, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: Align(1 bytes), + randomization_seed: $SEED, + }, + }, + mode: Ignore, + }, + c_variadic: false, + fixed_count: 1, + conv: C, + can_unwind: false, + } + --> $DIR/pass-indirectly-attr.rs:13:1 + | +LL | pub extern "C" fn func(_: Type) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/attributes/pass-indirectly.rs b/tests/ui/attributes/pass-indirectly.rs new file mode 100644 index 0000000000000..0eacffbf01fbe --- /dev/null +++ b/tests/ui/attributes/pass-indirectly.rs @@ -0,0 +1,15 @@ +//@ check-fail + +#![feature(rustc_attrs)] +#![crate_type = "lib"] + +#[rustc_pass_indirectly_in_non_rustic_abis] +//~^ ERROR: `#[rustc_pass_indirectly_in_non_rustic_abis]` attribute cannot be used on functions +fn not_a_struct() {} + +#[repr(C)] +#[rustc_pass_indirectly_in_non_rustic_abis] +struct YesAStruct { + foo: u8, + bar: u16, +} diff --git a/tests/ui/attributes/pass-indirectly.stderr b/tests/ui/attributes/pass-indirectly.stderr new file mode 100644 index 0000000000000..2011f3d928f69 --- /dev/null +++ b/tests/ui/attributes/pass-indirectly.stderr @@ -0,0 +1,10 @@ +error: `#[rustc_pass_indirectly_in_non_rustic_abis]` attribute cannot be used on functions + --> $DIR/pass-indirectly.rs:6:1 + | +LL | #[rustc_pass_indirectly_in_non_rustic_abis] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: `#[rustc_pass_indirectly_in_non_rustic_abis]` can only be applied to structs + +error: aborting due to 1 previous error + From 1866b3a8cf0917aa1f08eee7b6d5123823b2a5bc Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Wed, 24 Sep 2025 14:09:56 +0200 Subject: [PATCH 3/8] assert that `#[rustc_pass_indirectly_in_non_rustic_abis]` is respected --- compiler/rustc_target/src/callconv/mod.rs | 7 ------- compiler/rustc_ty_utils/src/abi.rs | 7 +++++++ library/core/src/ffi/va_list.rs | 12 ++++++++++++ 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_target/src/callconv/mod.rs b/compiler/rustc_target/src/callconv/mod.rs index a51517338e552..5411e8f8176b0 100644 --- a/compiler/rustc_target/src/callconv/mod.rs +++ b/compiler/rustc_target/src/callconv/mod.rs @@ -703,13 +703,6 @@ impl<'a, Ty> FnAbi<'a, Ty> { "bpf" => bpf::compute_abi_info(cx, self), arch => panic!("no lowering implemented for {arch}"), } - // Double check that any argument types annotated with the - // `#[rustc_pass_indirectly_in_non_rustic_abis]` attribute are passed indirectly. - for arg in &self.args { - if arg.layout.pass_indirectly_in_non_rustic_abis(cx) { - assert!(matches!(arg.mode, PassMode::Indirect { on_stack: false, .. })); - } - } } pub fn adjust_for_rust_abi(&mut self, cx: &C) diff --git a/compiler/rustc_ty_utils/src/abi.rs b/compiler/rustc_ty_utils/src/abi.rs index 0f09e548f0e2b..f98f161e80938 100644 --- a/compiler/rustc_ty_utils/src/abi.rs +++ b/compiler/rustc_ty_utils/src/abi.rs @@ -1,3 +1,4 @@ +use std::assert_matches::assert_matches; use std::iter; use rustc_abi::Primitive::Pointer; @@ -388,6 +389,12 @@ fn fn_abi_sanity_check<'tcx>( if let PassMode::Indirect { on_stack, .. } = arg.mode { assert!(!on_stack, "rust abi shouldn't use on_stack"); } + } else if arg.layout.pass_indirectly_in_non_rustic_abis(cx) { + assert_matches!( + arg.mode, + PassMode::Indirect { on_stack: false, .. }, + "the {spec_abi} ABI does not implement `#[rustc_pass_indirectly_in_non_rustic_abis]`" + ); } match &arg.mode { diff --git a/library/core/src/ffi/va_list.rs b/library/core/src/ffi/va_list.rs index 46ccf330d1c22..3c9587d383e30 100644 --- a/library/core/src/ffi/va_list.rs +++ b/library/core/src/ffi/va_list.rs @@ -299,3 +299,15 @@ impl<'f> Drop for VaListImpl<'f> { // This works for now, since `va_end` is a no-op on all current LLVM targets. } } + +// Checks (via an assert in `compiler/rustc_ty_utils/src/abi.rs`) that the C ABI for the current +// target correctly implements `rustc_pass_indirectly_in_non_rustic_abis`. +const _: () = { + #[repr(C)] + #[rustc_pass_indirectly_in_non_rustic_abis] + struct Type(usize); + + const extern "C" fn c(_: Type) {} + + c(Type(0)) +}; From 7be6d6f2e8742cd55a2ce8b6004eb76687c46bee Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Wed, 24 Sep 2025 16:29:59 +0200 Subject: [PATCH 4/8] additional tests for `pass_indirectly_in_non_rustic_abis` Also emit an error when `rustc_pass_indirectly_in_non_rustic_abis` is used in combination with `repr(transparent)`. --- compiler/rustc_abi/src/layout/ty.rs | 1 + compiler/rustc_passes/src/check_attr.rs | 11 +++ tests/ui/abi/pass-indirectly-attr.rs | 32 +++++- tests/ui/abi/pass-indirectly-attr.stderr | 120 ++++++++++++++++++++++- 4 files changed, 154 insertions(+), 10 deletions(-) diff --git a/compiler/rustc_abi/src/layout/ty.rs b/compiler/rustc_abi/src/layout/ty.rs index 3f80e5d3e9d5a..ca2753ea2fd98 100644 --- a/compiler/rustc_abi/src/layout/ty.rs +++ b/compiler/rustc_abi/src/layout/ty.rs @@ -291,6 +291,7 @@ impl<'a, Ty> TyAndLayout<'a, Ty> { { self = field; } + Ty::is_pass_indirectly_in_non_rustic_abis_flag_set(self) } diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 0a266f3eb8b9a..5944a1e8da5d1 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -1771,6 +1771,17 @@ impl<'tcx> CheckAttrVisitor<'tcx> { target: target.to_string(), }); } + // Error on `#[repr(transparent)]` in combination with + // `#[rustc_pass_indirectly_in_non_rustic_abis]` + if is_transparent + && let Some(&pass_indirectly_span) = + find_attr!(attrs, AttributeKind::RustcPassIndirectlyInNonRusticAbis(span) => span) + { + self.dcx().emit_err(errors::TransparentIncompatible { + hint_spans: vec![span, pass_indirectly_span], + target: target.to_string(), + }); + } if is_explicit_rust && (int_reprs > 0 || is_c || is_simd) { let hint_spans = hint_spans.clone().collect(); self.dcx().emit_err(errors::ReprConflicting { hint_spans }); diff --git a/tests/ui/abi/pass-indirectly-attr.rs b/tests/ui/abi/pass-indirectly-attr.rs index 3460abfe1eda6..54aafc716587c 100644 --- a/tests/ui/abi/pass-indirectly-attr.rs +++ b/tests/ui/abi/pass-indirectly-attr.rs @@ -1,16 +1,38 @@ +//@ add-minicore //@ check-fail //@ normalize-stderr: "randomization_seed: \d+" -> "randomization_seed: $$SEED" -//@ compile-flags: -O +//@ ignore-backends: gcc #![feature(rustc_attrs)] #![crate_type = "lib"] +#![feature(no_core)] +#![no_std] +#![no_core] + +extern crate minicore; +use minicore::*; #[repr(C)] #[rustc_pass_indirectly_in_non_rustic_abis] pub struct Type(u8); #[rustc_abi(debug)] -pub extern "C" fn func(_: Type) {} -//~^ ERROR fn_abi_of(func) = FnAbi { -//~^^ ERROR mode: Indirect { -//~^^^ ERROR on_stack: false, +pub extern "C" fn extern_c(_: Type) {} +//~^ ERROR fn_abi_of(extern_c) = FnAbi { +//~| ERROR mode: Indirect +//~| ERROR on_stack: false, +//~| ERROR conv: C, + +#[rustc_abi(debug)] +pub extern "Rust" fn extern_rust(_: Type) {} +//~^ ERROR fn_abi_of(extern_rust) = FnAbi { +//~| ERROR mode: Cast +//~| ERROR conv: Rust + +#[repr(transparent)] +struct Inner(u64); + +#[rustc_pass_indirectly_in_non_rustic_abis] +//~^ ERROR transparent struct cannot have other repr hints +#[repr(transparent)] +struct Wrapper(Inner); diff --git a/tests/ui/abi/pass-indirectly-attr.stderr b/tests/ui/abi/pass-indirectly-attr.stderr index 0f979db5d5ac5..a93982dacfa1b 100644 --- a/tests/ui/abi/pass-indirectly-attr.stderr +++ b/tests/ui/abi/pass-indirectly-attr.stderr @@ -1,4 +1,13 @@ -error: fn_abi_of(func) = FnAbi { +error[E0692]: transparent struct cannot have other repr hints + --> $DIR/pass-indirectly-attr.rs:35:1 + | +LL | #[rustc_pass_indirectly_in_non_rustic_abis] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | struct Wrapper(Inner); + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: fn_abi_of(extern_c) = FnAbi { args: [ ArgAbi { layout: TyAndLayout { @@ -75,10 +84,111 @@ error: fn_abi_of(func) = FnAbi { conv: C, can_unwind: false, } - --> $DIR/pass-indirectly-attr.rs:13:1 + --> $DIR/pass-indirectly-attr.rs:20:1 + | +LL | pub extern "C" fn extern_c(_: Type) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: fn_abi_of(extern_rust) = FnAbi { + args: [ + ArgAbi { + layout: TyAndLayout { + ty: Type, + layout: Layout { + size: Size(1 bytes), + align: AbiAlign { + abi: Align(1 bytes), + }, + backend_repr: Memory { + sized: true, + }, + fields: Arbitrary { + offsets: [ + Size(0 bytes), + ], + memory_index: [ + 0, + ], + }, + largest_niche: None, + uninhabited: false, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: Align(1 bytes), + randomization_seed: $SEED, + }, + }, + mode: Cast { + pad_i32: false, + cast: CastTarget { + prefix: [ + None, + None, + None, + None, + None, + None, + None, + None, + ], + rest_offset: None, + rest: Uniform { + unit: Reg { + kind: Integer, + size: Size(1 bytes), + }, + total: Size(1 bytes), + is_consecutive: false, + }, + attrs: ArgAttributes { + regular: , + arg_ext: None, + pointee_size: Size(0 bytes), + pointee_align: None, + }, + }, + }, + }, + ], + ret: ArgAbi { + layout: TyAndLayout { + ty: (), + layout: Layout { + size: Size(0 bytes), + align: AbiAlign { + abi: Align(1 bytes), + }, + backend_repr: Memory { + sized: true, + }, + fields: Arbitrary { + offsets: [], + memory_index: [], + }, + largest_niche: None, + uninhabited: false, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: Align(1 bytes), + randomization_seed: $SEED, + }, + }, + mode: Ignore, + }, + c_variadic: false, + fixed_count: 1, + conv: Rust, + can_unwind: false, + } + --> $DIR/pass-indirectly-attr.rs:27:1 | -LL | pub extern "C" fn func(_: Type) {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | pub extern "Rust" fn extern_rust(_: Type) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 1 previous error +error: aborting due to 3 previous errors +For more information about this error, try `rustc --explain E0692`. From add37c0c25f271771c6760720885f8520bc69bc8 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 24 Sep 2025 23:08:00 +0200 Subject: [PATCH 5/8] extend some comments and clarify some names around enum tag type computation --- compiler/rustc_abi/src/layout.rs | 2 +- compiler/rustc_abi/src/lib.rs | 5 +++++ compiler/rustc_middle/src/ty/layout.rs | 5 ++++- compiler/rustc_ty_utils/src/layout.rs | 8 ++++---- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_abi/src/layout.rs b/compiler/rustc_abi/src/layout.rs index 14356813b7bb0..b078fc92482fb 100644 --- a/compiler/rustc_abi/src/layout.rs +++ b/compiler/rustc_abi/src/layout.rs @@ -812,7 +812,7 @@ impl LayoutCalculator { let (max, min) = largest_niche // We might have no inhabited variants, so pretend there's at least one. .unwrap_or((0, 0)); - let (min_ity, signed) = discr_range_of_repr(min, max); //Integer::repr_discr(tcx, ty, &repr, min, max); + let (min_ity, signed) = discr_range_of_repr(min, max); //Integer::discr_range_of_repr(tcx, ty, &repr, min, max); let mut align = dl.aggregate_align; let mut max_repr_align = repr.align; diff --git a/compiler/rustc_abi/src/lib.rs b/compiler/rustc_abi/src/lib.rs index de44c8755a078..45c114acf25da 100644 --- a/compiler/rustc_abi/src/lib.rs +++ b/compiler/rustc_abi/src/lib.rs @@ -183,6 +183,11 @@ impl ReprOptions { /// Returns the discriminant type, given these `repr` options. /// This must only be called on enums! + /// + /// This is the "typeck type" of the discriminant, which is effectively the maximum size: + /// discriminant values will be wrapped to fit (with a lint). Layout can later decide to use a + /// smaller type for the tag that stores the discriminant at runtime and that will work just + /// fine, it just induces casts when getting/setting the discriminant. pub fn discr_type(&self) -> IntegerType { self.int.unwrap_or(IntegerType::Pointer(true)) } diff --git a/compiler/rustc_middle/src/ty/layout.rs b/compiler/rustc_middle/src/ty/layout.rs index eefb913a33fa4..3c73e9daa06ed 100644 --- a/compiler/rustc_middle/src/ty/layout.rs +++ b/compiler/rustc_middle/src/ty/layout.rs @@ -72,7 +72,10 @@ impl abi::Integer { /// signed discriminant range and `#[repr]` attribute. /// N.B.: `u128` values above `i128::MAX` will be treated as signed, but /// that shouldn't affect anything, other than maybe debuginfo. - fn repr_discr<'tcx>( + /// + /// This is the basis for computing the type of the *tag* of an enum (which can be smaller than + /// the type of the *discriminant*, which is determined by [`ReprOptions::discr_type`]). + fn discr_range_of_repr<'tcx>( tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, repr: &ReprOptions, diff --git a/compiler/rustc_ty_utils/src/layout.rs b/compiler/rustc_ty_utils/src/layout.rs index b3c0953b8e147..3a935b917ea2d 100644 --- a/compiler/rustc_ty_utils/src/layout.rs +++ b/compiler/rustc_ty_utils/src/layout.rs @@ -639,8 +639,8 @@ fn layout_of_uncached<'tcx>( // UnsafeCell and UnsafePinned both disable niche optimizations let is_special_no_niche = def.is_unsafe_cell() || def.is_unsafe_pinned(); - let get_discriminant_type = - |min, max| abi::Integer::repr_discr(tcx, ty, &def.repr(), min, max); + let discr_range_of_repr = + |min, max| abi::Integer::discr_range_of_repr(tcx, ty, &def.repr(), min, max); let discriminants_iter = || { def.is_enum() @@ -663,7 +663,7 @@ fn layout_of_uncached<'tcx>( def.is_enum(), is_special_no_niche, tcx.layout_scalar_valid_range(def.did()), - get_discriminant_type, + discr_range_of_repr, discriminants_iter(), !maybe_unsized, ) @@ -688,7 +688,7 @@ fn layout_of_uncached<'tcx>( def.is_enum(), is_special_no_niche, tcx.layout_scalar_valid_range(def.did()), - get_discriminant_type, + discr_range_of_repr, discriminants_iter(), !maybe_unsized, ) else { From 8b96fbecb6af782c3ef683eb26c330cc452380f8 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 25 Sep 2025 11:08:08 +0200 Subject: [PATCH 6/8] FCW for repr(C) enums whose discriminant values do not fit into a c_int --- .../rustc_hir_analysis/src/check/check.rs | 2 +- compiler/rustc_hir_analysis/src/collect.rs | 53 +++++++++++++------ compiler/rustc_lint/src/types.rs | 3 +- compiler/rustc_lint_defs/src/builtin.rs | 48 +++++++++++++++++ compiler/rustc_middle/src/ty/layout.rs | 3 +- .../rustc_ty_utils/src/layout/invariant.rs | 2 + tests/auxiliary/minicore.rs | 22 ++++++++ .../repr-c-big-discriminant1.ptr32.stderr | 19 +++++++ .../repr-c-big-discriminant1.ptr64.stderr | 40 ++++++++++++++ .../repr-c-big-discriminant1.rs | 40 ++++++++++++++ .../repr-c-big-discriminant2.ptr32.stderr | 11 ++++ .../repr-c-big-discriminant2.ptr64.stderr | 18 +++++++ .../repr-c-big-discriminant2.rs | 25 +++++++++ 13 files changed, 266 insertions(+), 20 deletions(-) create mode 100644 tests/ui/enum-discriminant/repr-c-big-discriminant1.ptr32.stderr create mode 100644 tests/ui/enum-discriminant/repr-c-big-discriminant1.ptr64.stderr create mode 100644 tests/ui/enum-discriminant/repr-c-big-discriminant1.rs create mode 100644 tests/ui/enum-discriminant/repr-c-big-discriminant2.ptr32.stderr create mode 100644 tests/ui/enum-discriminant/repr-c-big-discriminant2.ptr64.stderr create mode 100644 tests/ui/enum-discriminant/repr-c-big-discriminant2.rs diff --git a/compiler/rustc_hir_analysis/src/check/check.rs b/compiler/rustc_hir_analysis/src/check/check.rs index cc6598916eec6..1e12adbbfc6e0 100644 --- a/compiler/rustc_hir_analysis/src/check/check.rs +++ b/compiler/rustc_hir_analysis/src/check/check.rs @@ -782,7 +782,7 @@ pub(crate) fn check_item_type(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Result<(), tcx.ensure_ok().generics_of(def_id); tcx.ensure_ok().type_of(def_id); tcx.ensure_ok().predicates_of(def_id); - crate::collect::lower_enum_variant_types(tcx, def_id.to_def_id()); + crate::collect::lower_enum_variant_types(tcx, def_id); check_enum(tcx, def_id); check_variances_for_type_defn(tcx, def_id); } diff --git a/compiler/rustc_hir_analysis/src/collect.rs b/compiler/rustc_hir_analysis/src/collect.rs index 1386070e3d9b6..9409fca0a7573 100644 --- a/compiler/rustc_hir_analysis/src/collect.rs +++ b/compiler/rustc_hir_analysis/src/collect.rs @@ -19,7 +19,7 @@ use std::cell::Cell; use std::iter; use std::ops::Bound; -use rustc_abi::ExternAbi; +use rustc_abi::{ExternAbi, Size}; use rustc_ast::Recovered; use rustc_data_structures::fx::{FxHashSet, FxIndexMap}; use rustc_data_structures::unord::UnordMap; @@ -605,7 +605,7 @@ pub(super) fn lower_variant_ctor(tcx: TyCtxt<'_>, def_id: LocalDefId) { tcx.ensure_ok().predicates_of(def_id); } -pub(super) fn lower_enum_variant_types(tcx: TyCtxt<'_>, def_id: DefId) { +pub(super) fn lower_enum_variant_types(tcx: TyCtxt<'_>, def_id: LocalDefId) { let def = tcx.adt_def(def_id); let repr_type = def.repr().discr_type(); let initial = repr_type.initial_discriminant(tcx); @@ -614,23 +614,44 @@ pub(super) fn lower_enum_variant_types(tcx: TyCtxt<'_>, def_id: DefId) { // fill the discriminant values and field types for variant in def.variants() { let wrapped_discr = prev_discr.map_or(initial, |d| d.wrap_incr(tcx)); - prev_discr = Some( - if let ty::VariantDiscr::Explicit(const_def_id) = variant.discr { - def.eval_explicit_discr(tcx, const_def_id).ok() - } else if let Some(discr) = repr_type.disr_incr(tcx, prev_discr) { - Some(discr) - } else { + let cur_discr = if let ty::VariantDiscr::Explicit(const_def_id) = variant.discr { + def.eval_explicit_discr(tcx, const_def_id).ok() + } else if let Some(discr) = repr_type.disr_incr(tcx, prev_discr) { + Some(discr) + } else { + let span = tcx.def_span(variant.def_id); + tcx.dcx().emit_err(errors::EnumDiscriminantOverflowed { + span, + discr: prev_discr.unwrap().to_string(), + item_name: tcx.item_ident(variant.def_id), + wrapped_discr: wrapped_discr.to_string(), + }); + None + } + .unwrap_or(wrapped_discr); + + if def.repr().c() { + // c_int is a signed type, so get a proper signed version of the discriminant + let discr_size = cur_discr.ty.int_size_and_signed(tcx).0; + let discr_val = discr_size.sign_extend(cur_discr.val); + + let c_int = Size::from_bits(tcx.sess.target.c_int_width); + if discr_val < c_int.signed_int_min() || discr_val > c_int.signed_int_max() { let span = tcx.def_span(variant.def_id); - tcx.dcx().emit_err(errors::EnumDiscriminantOverflowed { + tcx.node_span_lint( + rustc_session::lint::builtin::REPR_C_ENUMS_LARGER_THAN_INT, + tcx.local_def_id_to_hir_id(def_id), span, - discr: prev_discr.unwrap().to_string(), - item_name: tcx.item_ident(variant.def_id), - wrapped_discr: wrapped_discr.to_string(), - }); - None + |d| { + d.primary_message("`repr(C)` enum discriminant does not fit into C `int`") + .note("`repr(C)` enums with big discriminants are non-portable, and their size in Rust might not match their size in C") + .help("use `repr($int_ty)` instead to explicitly set the size of this enum"); + } + ); } - .unwrap_or(wrapped_discr), - ); + } + + prev_discr = Some(cur_discr); for f in &variant.fields { tcx.ensure_ok().generics_of(f.did); diff --git a/compiler/rustc_lint/src/types.rs b/compiler/rustc_lint/src/types.rs index 8ce74ff76effc..4895e61069e52 100644 --- a/compiler/rustc_lint/src/types.rs +++ b/compiler/rustc_lint/src/types.rs @@ -10,7 +10,7 @@ use rustc_span::{Span, Symbol, sym}; use tracing::debug; use {rustc_ast as ast, rustc_hir as hir}; -mod improper_ctypes; // these filed do the implementation for ImproperCTypesDefinitions,ImproperCTypesDeclarations +mod improper_ctypes; // these files do the implementation for ImproperCTypesDefinitions,ImproperCTypesDeclarations pub(crate) use improper_ctypes::ImproperCTypesLint; use crate::lints::{ @@ -25,7 +25,6 @@ use crate::lints::{ use crate::{LateContext, LateLintPass, LintContext}; mod literal; - use literal::{int_ty_range, lint_literal, uint_ty_range}; declare_lint! { diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index 8c474ed28240c..0cc4502bb73b8 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -86,6 +86,7 @@ declare_lint_pass! { REFINING_IMPL_TRAIT_INTERNAL, REFINING_IMPL_TRAIT_REACHABLE, RENAMED_AND_REMOVED_LINTS, + REPR_C_ENUMS_LARGER_THAN_INT, REPR_TRANSPARENT_NON_ZST_FIELDS, RUST_2021_INCOMPATIBLE_CLOSURE_CAPTURES, RUST_2021_INCOMPATIBLE_OR_PATTERNS, @@ -5213,3 +5214,50 @@ declare_lint! { Warn, r#"detects when a function annotated with `#[inline(always)]` and `#[target_feature(enable = "..")]` is inlined into a caller without the required target feature"#, } + +declare_lint! { + /// The `repr_c_enums_larger_than_int` lint detects `repr(C)` enums with discriminant + /// values that do not fit into a C `int`. + /// + /// ### Example + /// + /// ```rust,ignore (only errors on 64bit) + /// #[repr(C)] + /// enum E { + /// V = 9223372036854775807, // i64::MAX + /// } + /// ``` + /// + /// This will produce: + /// + /// ```text + /// error: `repr(C)` enum discriminant does not fit into C `int` + /// --> $DIR/repr-c-big-discriminant1.rs:16:5 + /// | + /// LL | A = 9223372036854775807, // i64::MAX + /// | ^ + /// | + /// = note: `repr(C)` enums with big discriminants are non-portable, and their size in Rust might not match their size in C + /// = help: use `repr($int_ty)` instead to explicitly set the size of this enum + /// ``` + /// + /// ### Explanation + /// + /// In C, enums with discriminants that do not fit into an `int` are a portability hazard: such + /// enums are only permitted since C23, and not supported e.g. by MSVC. Furthermore, Rust + /// interprets the discriminant values of `repr(C)` enums as expressions of type `isize`, which + /// cannot be changed in a backwards-compatible way. If the discriminant is given as a literal + /// that does not fit into `isize`, it is wrapped (with a warning). This makes it impossible to + /// implement the C23 behavior of enums where the enum discriminants have no predefined type and + /// instead the enum uses a type large enough to hold all discriminants. + /// + /// Therefore, `repr(C)` enums require all discriminants to fit into a C `int`. + pub REPR_C_ENUMS_LARGER_THAN_INT, + Warn, + "repr(C) enums with discriminant values that do not fit into a C int", + @future_incompatible = FutureIncompatibleInfo { + reason: FutureIncompatibilityReason::FutureReleaseError, + reference: "issue #124403 ", + report_in_deps: false, + }; +} diff --git a/compiler/rustc_middle/src/ty/layout.rs b/compiler/rustc_middle/src/ty/layout.rs index 3c73e9daa06ed..b0eabb3c122bd 100644 --- a/compiler/rustc_middle/src/ty/layout.rs +++ b/compiler/rustc_middle/src/ty/layout.rs @@ -111,7 +111,8 @@ impl abi::Integer { abi::Integer::I8 }; - // Pick the smallest fit. + // Pick the smallest fit. Prefer unsigned; that matches clang in cases where this makes a + // difference (https://godbolt.org/z/h4xEasW1d) so it is crucial for repr(C). if unsigned_fit <= signed_fit { (cmp::max(unsigned_fit, at_least), false) } else { diff --git a/compiler/rustc_ty_utils/src/layout/invariant.rs b/compiler/rustc_ty_utils/src/layout/invariant.rs index b768269215fa2..d1484aed16718 100644 --- a/compiler/rustc_ty_utils/src/layout/invariant.rs +++ b/compiler/rustc_ty_utils/src/layout/invariant.rs @@ -14,6 +14,8 @@ pub(super) fn layout_sanity_check<'tcx>(cx: &LayoutCx<'tcx>, layout: &TyAndLayou if layout.size.bytes() >= tcx.data_layout.obj_size_bound() { bug!("size is too large, in the following layout:\n{layout:#?}"); } + // FIXME(#124403): Once `repr_c_enums_larger_than_int` is a hard error, we could assert + // here that a repr(c) enum discriminant is never larger than a c_int. if !cfg!(debug_assertions) { // Stop here, the rest is kind of expensive. diff --git a/tests/auxiliary/minicore.rs b/tests/auxiliary/minicore.rs index 4f4c653cb46e7..076474caf5580 100644 --- a/tests/auxiliary/minicore.rs +++ b/tests/auxiliary/minicore.rs @@ -177,6 +177,21 @@ impl Add for isize { } } +#[lang = "neg"] +pub trait Neg { + type Output; + + fn neg(self) -> Self::Output; +} + +impl Neg for isize { + type Output = isize; + + fn neg(self) -> isize { + loop {} // Dummy impl, not actually used + } +} + #[lang = "sync"] trait Sync {} impl_marker_trait!( @@ -231,6 +246,13 @@ pub mod mem { #[rustc_nounwind] #[rustc_intrinsic] pub unsafe fn transmute(src: Src) -> Dst; + + #[rustc_nounwind] + #[rustc_intrinsic] + pub const fn size_of() -> usize; + #[rustc_nounwind] + #[rustc_intrinsic] + pub const fn align_of() -> usize; } #[lang = "c_void"] diff --git a/tests/ui/enum-discriminant/repr-c-big-discriminant1.ptr32.stderr b/tests/ui/enum-discriminant/repr-c-big-discriminant1.ptr32.stderr new file mode 100644 index 0000000000000..2973803793854 --- /dev/null +++ b/tests/ui/enum-discriminant/repr-c-big-discriminant1.ptr32.stderr @@ -0,0 +1,19 @@ +error: literal out of range for `isize` + --> $DIR/repr-c-big-discriminant1.rs:16:9 + | +LL | A = 9223372036854775807, // i64::MAX + | ^^^^^^^^^^^^^^^^^^^ + | + = note: the literal `9223372036854775807` does not fit into the type `isize` whose range is `-2147483648..=2147483647` + = note: `#[deny(overflowing_literals)]` on by default + +error: literal out of range for `isize` + --> $DIR/repr-c-big-discriminant1.rs:24:9 + | +LL | A = -2147483649, // i32::MIN-1 + | ^^^^^^^^^^^ + | + = note: the literal `-2147483649` does not fit into the type `isize` whose range is `-2147483648..=2147483647` + +error: aborting due to 2 previous errors + diff --git a/tests/ui/enum-discriminant/repr-c-big-discriminant1.ptr64.stderr b/tests/ui/enum-discriminant/repr-c-big-discriminant1.ptr64.stderr new file mode 100644 index 0000000000000..332f3023cfbe8 --- /dev/null +++ b/tests/ui/enum-discriminant/repr-c-big-discriminant1.ptr64.stderr @@ -0,0 +1,40 @@ +error: `repr(C)` enum discriminant does not fit into C `int` + --> $DIR/repr-c-big-discriminant1.rs:16:5 + | +LL | A = 9223372036854775807, // i64::MAX + | ^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #124403 + = note: `repr(C)` enums with big discriminants are non-portable, and their size in Rust might not match their size in C + = help: use `repr($int_ty)` instead to explicitly set the size of this enum +note: the lint level is defined here + --> $DIR/repr-c-big-discriminant1.rs:6:9 + | +LL | #![deny(repr_c_enums_larger_than_int)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `repr(C)` enum discriminant does not fit into C `int` + --> $DIR/repr-c-big-discriminant1.rs:24:5 + | +LL | A = -2147483649, // i32::MIN-1 + | ^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #124403 + = note: `repr(C)` enums with big discriminants are non-portable, and their size in Rust might not match their size in C + = help: use `repr($int_ty)` instead to explicitly set the size of this enum + +error: `repr(C)` enum discriminant does not fit into C `int` + --> $DIR/repr-c-big-discriminant1.rs:34:5 + | +LL | A = I64_MAX as isize, + | ^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #124403 + = note: `repr(C)` enums with big discriminants are non-portable, and their size in Rust might not match their size in C + = help: use `repr($int_ty)` instead to explicitly set the size of this enum + +error: aborting due to 3 previous errors + diff --git a/tests/ui/enum-discriminant/repr-c-big-discriminant1.rs b/tests/ui/enum-discriminant/repr-c-big-discriminant1.rs new file mode 100644 index 0000000000000..16d007432a02f --- /dev/null +++ b/tests/ui/enum-discriminant/repr-c-big-discriminant1.rs @@ -0,0 +1,40 @@ +//@ revisions: ptr32 ptr64 +//@[ptr32] compile-flags: --target i686-unknown-linux-gnu +//@[ptr32] needs-llvm-components: x86 +//@[ptr64] compile-flags: --target x86_64-unknown-linux-gnu +//@[ptr64] needs-llvm-components: x86 +#![deny(repr_c_enums_larger_than_int)] + +//@ add-minicore +#![feature(no_core)] +#![no_core] +extern crate minicore; +use minicore::*; + +#[repr(C)] +enum OverflowingEnum1 { + A = 9223372036854775807, // i64::MAX + //[ptr32]~^ ERROR: literal out of range + //[ptr64]~^^ ERROR: discriminant does not fit into C `int` + //[ptr64]~^^^ WARN: previously accepted +} + +#[repr(C)] +enum OverflowingEnum2 { + A = -2147483649, // i32::MIN-1 + //[ptr32]~^ ERROR: literal out of range + //[ptr64]~^^ ERROR: discriminant does not fit into C `int` + //[ptr64]~^^^ WARN: previously accepted +} + +const I64_MAX: i64 = 9223372036854775807; + +#[repr(C)] +enum OverflowingEnum3 { + A = I64_MAX as isize, + //[ptr64]~^ ERROR: discriminant does not fit into C `int` + //[ptr64]~^^ WARN: previously accepted + // No warning/error on 32bit targets, but the `as isize` hints that wrapping is occurring. +} + +fn main() {} diff --git a/tests/ui/enum-discriminant/repr-c-big-discriminant2.ptr32.stderr b/tests/ui/enum-discriminant/repr-c-big-discriminant2.ptr32.stderr new file mode 100644 index 0000000000000..9fbadf3bc39e5 --- /dev/null +++ b/tests/ui/enum-discriminant/repr-c-big-discriminant2.ptr32.stderr @@ -0,0 +1,11 @@ +error[E0370]: enum discriminant overflowed + --> $DIR/repr-c-big-discriminant2.rs:19:5 + | +LL | B, // +1 + | ^ overflowed on value after 2147483647 + | + = note: explicitly set `B = -2147483648` if that is desired outcome + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0370`. diff --git a/tests/ui/enum-discriminant/repr-c-big-discriminant2.ptr64.stderr b/tests/ui/enum-discriminant/repr-c-big-discriminant2.ptr64.stderr new file mode 100644 index 0000000000000..39c4f39b5707d --- /dev/null +++ b/tests/ui/enum-discriminant/repr-c-big-discriminant2.ptr64.stderr @@ -0,0 +1,18 @@ +error: `repr(C)` enum discriminant does not fit into C `int` + --> $DIR/repr-c-big-discriminant2.rs:19:5 + | +LL | B, // +1 + | ^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #124403 + = note: `repr(C)` enums with big discriminants are non-portable, and their size in Rust might not match their size in C + = help: use `repr($int_ty)` instead to explicitly set the size of this enum +note: the lint level is defined here + --> $DIR/repr-c-big-discriminant2.rs:6:9 + | +LL | #![deny(repr_c_enums_larger_than_int)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/enum-discriminant/repr-c-big-discriminant2.rs b/tests/ui/enum-discriminant/repr-c-big-discriminant2.rs new file mode 100644 index 0000000000000..87c9f0bea7365 --- /dev/null +++ b/tests/ui/enum-discriminant/repr-c-big-discriminant2.rs @@ -0,0 +1,25 @@ +//@ revisions: ptr32 ptr64 +//@[ptr32] compile-flags: --target i686-unknown-linux-gnu +//@[ptr32] needs-llvm-components: x86 +//@[ptr64] compile-flags: --target x86_64-unknown-linux-gnu +//@[ptr64] needs-llvm-components: x86 +#![deny(repr_c_enums_larger_than_int)] + +//@ add-minicore +#![feature(no_core)] +#![no_core] +extern crate minicore; +use minicore::*; + +// Separate test since it suppresses other errors on ptr32 + +#[repr(C)] +enum OverflowingEnum { + A = 2147483647, // i32::MAX + B, // +1 + //[ptr32]~^ ERROR: enum discriminant overflowed + //[ptr64]~^^ ERROR: discriminant does not fit into C `int` + //[ptr64]~^^^ WARN: previously accepted +} + +fn main() {} From a92bae0b1ca3beb7862639a1ce4e9dea49a5c4d7 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 2 Oct 2025 09:58:34 +0200 Subject: [PATCH 7/8] do not complain about enums where all discriminants fit into a c_uint --- compiler/rustc_hir_analysis/src/collect.rs | 23 +++++++++-- compiler/rustc_lint_defs/src/builtin.rs | 22 ++++++----- .../ui/enum-discriminant/discriminant_size.rs | 23 +++++++++++ .../repr-c-big-discriminant1.ptr32.stderr | 22 +++++++++-- .../repr-c-big-discriminant1.ptr64.stderr | 38 +++++++++++++++---- .../repr-c-big-discriminant1.rs | 35 +++++++++++++++-- .../repr-c-big-discriminant2.ptr32.stderr | 2 +- .../repr-c-big-discriminant2.ptr64.stderr | 6 +-- .../repr-c-big-discriminant2.rs | 7 +++- 9 files changed, 145 insertions(+), 33 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/collect.rs b/compiler/rustc_hir_analysis/src/collect.rs index 9409fca0a7573..6e62e50d3af82 100644 --- a/compiler/rustc_hir_analysis/src/collect.rs +++ b/compiler/rustc_hir_analysis/src/collect.rs @@ -610,6 +610,10 @@ pub(super) fn lower_enum_variant_types(tcx: TyCtxt<'_>, def_id: LocalDefId) { let repr_type = def.repr().discr_type(); let initial = repr_type.initial_discriminant(tcx); let mut prev_discr = None::>; + // Some of the logic below relies on `i128` being able to hold all c_int and c_uint values. + assert!(tcx.sess.target.c_int_width < 128); + let mut min_discr = i128::MAX; + let mut max_discr = i128::MIN; // fill the discriminant values and field types for variant in def.variants() { @@ -631,19 +635,32 @@ pub(super) fn lower_enum_variant_types(tcx: TyCtxt<'_>, def_id: LocalDefId) { .unwrap_or(wrapped_discr); if def.repr().c() { + let c_int = Size::from_bits(tcx.sess.target.c_int_width); + let c_uint_max = i128::try_from(c_int.unsigned_int_max()).unwrap(); // c_int is a signed type, so get a proper signed version of the discriminant let discr_size = cur_discr.ty.int_size_and_signed(tcx).0; let discr_val = discr_size.sign_extend(cur_discr.val); + min_discr = min_discr.min(discr_val); + max_discr = max_discr.max(discr_val); - let c_int = Size::from_bits(tcx.sess.target.c_int_width); - if discr_val < c_int.signed_int_min() || discr_val > c_int.signed_int_max() { + // The discriminant range must either fit into c_int or c_uint. + if !(min_discr >= c_int.signed_int_min() && max_discr <= c_int.signed_int_max()) + && !(min_discr >= 0 && max_discr <= c_uint_max) + { let span = tcx.def_span(variant.def_id); + let msg = if discr_val < c_int.signed_int_min() || discr_val > c_uint_max { + "`repr(C)` enum discriminant does not fit into C `int` nor into C `unsigned int`" + } else if discr_val < 0 { + "`repr(C)` enum discriminant does not fit into C `unsigned int`, and a previous discriminant does not fit into C `int`" + } else { + "`repr(C)` enum discriminant does not fit into C `int`, and a previous discriminant does not fit into C `unsigned int`" + }; tcx.node_span_lint( rustc_session::lint::builtin::REPR_C_ENUMS_LARGER_THAN_INT, tcx.local_def_id_to_hir_id(def_id), span, |d| { - d.primary_message("`repr(C)` enum discriminant does not fit into C `int`") + d.primary_message(msg) .note("`repr(C)` enums with big discriminants are non-portable, and their size in Rust might not match their size in C") .help("use `repr($int_ty)` instead to explicitly set the size of this enum"); } diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index 0cc4502bb73b8..86aa6341aff87 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -5217,7 +5217,7 @@ declare_lint! { declare_lint! { /// The `repr_c_enums_larger_than_int` lint detects `repr(C)` enums with discriminant - /// values that do not fit into a C `int`. + /// values that do not fit into a C `int` or `unsigned int`. /// /// ### Example /// @@ -5231,7 +5231,7 @@ declare_lint! { /// This will produce: /// /// ```text - /// error: `repr(C)` enum discriminant does not fit into C `int` + /// error: `repr(C)` enum discriminant does not fit into C `int` nor into C `unsigned int` /// --> $DIR/repr-c-big-discriminant1.rs:16:5 /// | /// LL | A = 9223372036854775807, // i64::MAX @@ -5243,15 +5243,17 @@ declare_lint! { /// /// ### Explanation /// - /// In C, enums with discriminants that do not fit into an `int` are a portability hazard: such - /// enums are only permitted since C23, and not supported e.g. by MSVC. Furthermore, Rust - /// interprets the discriminant values of `repr(C)` enums as expressions of type `isize`, which - /// cannot be changed in a backwards-compatible way. If the discriminant is given as a literal - /// that does not fit into `isize`, it is wrapped (with a warning). This makes it impossible to - /// implement the C23 behavior of enums where the enum discriminants have no predefined type and - /// instead the enum uses a type large enough to hold all discriminants. + /// In C, enums with discriminants that do not all fit into an `int` or all fit into an + /// `unsigned int` are a portability hazard: such enums are only permitted since C23, and not + /// supported e.g. by MSVC. /// - /// Therefore, `repr(C)` enums require all discriminants to fit into a C `int`. + /// Furthermore, Rust interprets the discriminant values of `repr(C)` enums as expressions of + /// type `isize`. This makes it impossible to implement the C23 behavior of enums where the enum + /// discriminants have no predefined type and instead the enum uses a type large enough to hold + /// all discriminants. + /// + /// Therefore, `repr(C)` enums in Rust require that either all discriminants to fit into a C + /// `int` or they all fit into an `unsigned int`. pub REPR_C_ENUMS_LARGER_THAN_INT, Warn, "repr(C) enums with discriminant values that do not fit into a C int", diff --git a/tests/ui/enum-discriminant/discriminant_size.rs b/tests/ui/enum-discriminant/discriminant_size.rs index b1feff3c59e1d..537940cfb7ee3 100644 --- a/tests/ui/enum-discriminant/discriminant_size.rs +++ b/tests/ui/enum-discriminant/discriminant_size.rs @@ -2,6 +2,7 @@ #![feature(core_intrinsics)] use std::intrinsics::discriminant_value; +use std::mem::size_of; enum E1 { A, @@ -20,6 +21,14 @@ enum E3 { B = 100, } +// Enums like this are found in the ecosystem, let's make sure they get the right size. +#[repr(C)] +#[allow(overflowing_literals)] +enum UnsignedIntEnum { + A = 0, + O = 0xffffffff, // doesn't fit into `int`, but fits into `unsigned int` +} + #[repr(i128)] enum E4 { A = 0x1223_3445_5667_7889, @@ -27,24 +36,38 @@ enum E4 { } fn main() { + assert_eq!(size_of::(), 1); let mut target: [isize; 3] = [0, 0, 0]; target[1] = discriminant_value(&E1::A); assert_eq!(target, [0, 0, 0]); target[1] = discriminant_value(&E1::B); assert_eq!(target, [0, 1, 0]); + assert_eq!(size_of::(), 1); let mut target: [i8; 3] = [0, 0, 0]; target[1] = discriminant_value(&E2::A); assert_eq!(target, [0, 7, 0]); target[1] = discriminant_value(&E2::B); assert_eq!(target, [0, -2, 0]); + // E3's size is target-dependent let mut target: [isize; 3] = [0, 0, 0]; target[1] = discriminant_value(&E3::A); assert_eq!(target, [0, 42, 0]); target[1] = discriminant_value(&E3::B); assert_eq!(target, [0, 100, 0]); + #[allow(overflowing_literals)] + { + assert_eq!(size_of::(), 4); + let mut target: [isize; 3] = [0, -1, 0]; + target[1] = discriminant_value(&UnsignedIntEnum::A); + assert_eq!(target, [0, 0, 0]); + target[1] = discriminant_value(&UnsignedIntEnum::O); + assert_eq!(target, [0, 0xffffffff as isize, 0]); + } + + assert_eq!(size_of::(), 16); let mut target: [i128; 3] = [0, 0, 0]; target[1] = discriminant_value(&E4::A); assert_eq!(target, [0, 0x1223_3445_5667_7889, 0]); diff --git a/tests/ui/enum-discriminant/repr-c-big-discriminant1.ptr32.stderr b/tests/ui/enum-discriminant/repr-c-big-discriminant1.ptr32.stderr index 2973803793854..db60fd1f7a3cb 100644 --- a/tests/ui/enum-discriminant/repr-c-big-discriminant1.ptr32.stderr +++ b/tests/ui/enum-discriminant/repr-c-big-discriminant1.ptr32.stderr @@ -1,5 +1,5 @@ error: literal out of range for `isize` - --> $DIR/repr-c-big-discriminant1.rs:16:9 + --> $DIR/repr-c-big-discriminant1.rs:18:9 | LL | A = 9223372036854775807, // i64::MAX | ^^^^^^^^^^^^^^^^^^^ @@ -8,12 +8,28 @@ LL | A = 9223372036854775807, // i64::MAX = note: `#[deny(overflowing_literals)]` on by default error: literal out of range for `isize` - --> $DIR/repr-c-big-discriminant1.rs:24:9 + --> $DIR/repr-c-big-discriminant1.rs:26:9 | LL | A = -2147483649, // i32::MIN-1 | ^^^^^^^^^^^ | = note: the literal `-2147483649` does not fit into the type `isize` whose range is `-2147483648..=2147483647` -error: aborting due to 2 previous errors +error: literal out of range for `isize` + --> $DIR/repr-c-big-discriminant1.rs:34:9 + | +LL | A = 2147483648, // i32::MAX+1 + | ^^^^^^^^^^ + | + = note: the literal `2147483648` does not fit into the type `isize` whose range is `-2147483648..=2147483647` + +error: literal out of range for `isize` + --> $DIR/repr-c-big-discriminant1.rs:43:9 + | +LL | A = 2147483648, // i32::MAX+1 + | ^^^^^^^^^^ + | + = note: the literal `2147483648` does not fit into the type `isize` whose range is `-2147483648..=2147483647` + +error: aborting due to 4 previous errors diff --git a/tests/ui/enum-discriminant/repr-c-big-discriminant1.ptr64.stderr b/tests/ui/enum-discriminant/repr-c-big-discriminant1.ptr64.stderr index 332f3023cfbe8..e2517ab342f45 100644 --- a/tests/ui/enum-discriminant/repr-c-big-discriminant1.ptr64.stderr +++ b/tests/ui/enum-discriminant/repr-c-big-discriminant1.ptr64.stderr @@ -1,5 +1,5 @@ -error: `repr(C)` enum discriminant does not fit into C `int` - --> $DIR/repr-c-big-discriminant1.rs:16:5 +error: `repr(C)` enum discriminant does not fit into C `int` nor into C `unsigned int` + --> $DIR/repr-c-big-discriminant1.rs:18:5 | LL | A = 9223372036854775807, // i64::MAX | ^ @@ -9,13 +9,13 @@ LL | A = 9223372036854775807, // i64::MAX = note: `repr(C)` enums with big discriminants are non-portable, and their size in Rust might not match their size in C = help: use `repr($int_ty)` instead to explicitly set the size of this enum note: the lint level is defined here - --> $DIR/repr-c-big-discriminant1.rs:6:9 + --> $DIR/repr-c-big-discriminant1.rs:8:9 | LL | #![deny(repr_c_enums_larger_than_int)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: `repr(C)` enum discriminant does not fit into C `int` - --> $DIR/repr-c-big-discriminant1.rs:24:5 +error: `repr(C)` enum discriminant does not fit into C `int` nor into C `unsigned int` + --> $DIR/repr-c-big-discriminant1.rs:26:5 | LL | A = -2147483649, // i32::MIN-1 | ^ @@ -25,8 +25,30 @@ LL | A = -2147483649, // i32::MIN-1 = note: `repr(C)` enums with big discriminants are non-portable, and their size in Rust might not match their size in C = help: use `repr($int_ty)` instead to explicitly set the size of this enum -error: `repr(C)` enum discriminant does not fit into C `int` - --> $DIR/repr-c-big-discriminant1.rs:34:5 +error: `repr(C)` enum discriminant does not fit into C `unsigned int`, and a previous discriminant does not fit into C `int` + --> $DIR/repr-c-big-discriminant1.rs:36:5 + | +LL | B = -1, + | ^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #124403 + = note: `repr(C)` enums with big discriminants are non-portable, and their size in Rust might not match their size in C + = help: use `repr($int_ty)` instead to explicitly set the size of this enum + +error: `repr(C)` enum discriminant does not fit into C `int`, and a previous discriminant does not fit into C `unsigned int` + --> $DIR/repr-c-big-discriminant1.rs:43:5 + | +LL | A = 2147483648, // i32::MAX+1 + | ^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #124403 + = note: `repr(C)` enums with big discriminants are non-portable, and their size in Rust might not match their size in C + = help: use `repr($int_ty)` instead to explicitly set the size of this enum + +error: `repr(C)` enum discriminant does not fit into C `int` nor into C `unsigned int` + --> $DIR/repr-c-big-discriminant1.rs:53:5 | LL | A = I64_MAX as isize, | ^ @@ -36,5 +58,5 @@ LL | A = I64_MAX as isize, = note: `repr(C)` enums with big discriminants are non-portable, and their size in Rust might not match their size in C = help: use `repr($int_ty)` instead to explicitly set the size of this enum -error: aborting due to 3 previous errors +error: aborting due to 5 previous errors diff --git a/tests/ui/enum-discriminant/repr-c-big-discriminant1.rs b/tests/ui/enum-discriminant/repr-c-big-discriminant1.rs index 16d007432a02f..739f7cbed14b3 100644 --- a/tests/ui/enum-discriminant/repr-c-big-discriminant1.rs +++ b/tests/ui/enum-discriminant/repr-c-big-discriminant1.rs @@ -3,6 +3,8 @@ //@[ptr32] needs-llvm-components: x86 //@[ptr64] compile-flags: --target x86_64-unknown-linux-gnu //@[ptr64] needs-llvm-components: x86 +// GCC doesn't like cross-compilation +//@ ignore-backends: gcc #![deny(repr_c_enums_larger_than_int)] //@ add-minicore @@ -15,7 +17,7 @@ use minicore::*; enum OverflowingEnum1 { A = 9223372036854775807, // i64::MAX //[ptr32]~^ ERROR: literal out of range - //[ptr64]~^^ ERROR: discriminant does not fit into C `int` + //[ptr64]~^^ ERROR: discriminant does not fit into C `int` nor into C `unsigned int` //[ptr64]~^^^ WARN: previously accepted } @@ -23,18 +25,43 @@ enum OverflowingEnum1 { enum OverflowingEnum2 { A = -2147483649, // i32::MIN-1 //[ptr32]~^ ERROR: literal out of range - //[ptr64]~^^ ERROR: discriminant does not fit into C `int` + //[ptr64]~^^ ERROR: discriminant does not fit into C `int` nor into C `unsigned int` + //[ptr64]~^^^ WARN: previously accepted +} + +#[repr(C)] +enum OverflowingEnum3a { + A = 2147483648, // i32::MAX+1 + //[ptr32]~^ ERROR: literal out of range + B = -1, + //[ptr64]~^ ERROR: discriminant does not fit into C `unsigned int`, and a previous + //[ptr64]~^^ WARN: previously accepted +} +#[repr(C)] +enum OverflowingEnum3b { + B = -1, + A = 2147483648, // i32::MAX+1 + //[ptr32]~^ ERROR: literal out of range + //[ptr64]~^^ ERROR: discriminant does not fit into C `int`, and a previous //[ptr64]~^^^ WARN: previously accepted } const I64_MAX: i64 = 9223372036854775807; #[repr(C)] -enum OverflowingEnum3 { +enum OverflowingEnum4 { A = I64_MAX as isize, - //[ptr64]~^ ERROR: discriminant does not fit into C `int` + //[ptr64]~^ ERROR: discriminant does not fit into C `int` nor into C `unsigned int` //[ptr64]~^^ WARN: previously accepted // No warning/error on 32bit targets, but the `as isize` hints that wrapping is occurring. } +// Enums like this are found in the ecosystem, let's make sure they get accepted. +#[repr(C)] +#[allow(overflowing_literals)] +enum OkayEnum { + A = 0, + O = 0xffffffff, +} + fn main() {} diff --git a/tests/ui/enum-discriminant/repr-c-big-discriminant2.ptr32.stderr b/tests/ui/enum-discriminant/repr-c-big-discriminant2.ptr32.stderr index 9fbadf3bc39e5..85aaff46a6892 100644 --- a/tests/ui/enum-discriminant/repr-c-big-discriminant2.ptr32.stderr +++ b/tests/ui/enum-discriminant/repr-c-big-discriminant2.ptr32.stderr @@ -1,5 +1,5 @@ error[E0370]: enum discriminant overflowed - --> $DIR/repr-c-big-discriminant2.rs:19:5 + --> $DIR/repr-c-big-discriminant2.rs:24:5 | LL | B, // +1 | ^ overflowed on value after 2147483647 diff --git a/tests/ui/enum-discriminant/repr-c-big-discriminant2.ptr64.stderr b/tests/ui/enum-discriminant/repr-c-big-discriminant2.ptr64.stderr index 39c4f39b5707d..8cd978ccb2fb1 100644 --- a/tests/ui/enum-discriminant/repr-c-big-discriminant2.ptr64.stderr +++ b/tests/ui/enum-discriminant/repr-c-big-discriminant2.ptr64.stderr @@ -1,5 +1,5 @@ -error: `repr(C)` enum discriminant does not fit into C `int` - --> $DIR/repr-c-big-discriminant2.rs:19:5 +error: `repr(C)` enum discriminant does not fit into C `int`, and a previous discriminant does not fit into C `unsigned int` + --> $DIR/repr-c-big-discriminant2.rs:24:5 | LL | B, // +1 | ^ @@ -9,7 +9,7 @@ LL | B, // +1 = note: `repr(C)` enums with big discriminants are non-portable, and their size in Rust might not match their size in C = help: use `repr($int_ty)` instead to explicitly set the size of this enum note: the lint level is defined here - --> $DIR/repr-c-big-discriminant2.rs:6:9 + --> $DIR/repr-c-big-discriminant2.rs:8:9 | LL | #![deny(repr_c_enums_larger_than_int)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/enum-discriminant/repr-c-big-discriminant2.rs b/tests/ui/enum-discriminant/repr-c-big-discriminant2.rs index 87c9f0bea7365..8e333b7598929 100644 --- a/tests/ui/enum-discriminant/repr-c-big-discriminant2.rs +++ b/tests/ui/enum-discriminant/repr-c-big-discriminant2.rs @@ -3,6 +3,8 @@ //@[ptr32] needs-llvm-components: x86 //@[ptr64] compile-flags: --target x86_64-unknown-linux-gnu //@[ptr64] needs-llvm-components: x86 +// GCC doesn't like cross-compilation +//@ ignore-backends: gcc #![deny(repr_c_enums_larger_than_int)] //@ add-minicore @@ -11,10 +13,13 @@ extern crate minicore; use minicore::*; -// Separate test since it suppresses other errors on ptr32 +// Separate test since it suppresses other errors on ptr32: +// ensure we find the bad discriminant when it is implicitly computed by incrementing +// the previous discriminant. #[repr(C)] enum OverflowingEnum { + NEG = -1, A = 2147483647, // i32::MAX B, // +1 //[ptr32]~^ ERROR: enum discriminant overflowed From 5f312079f2782999e07ff25869e1353082bbe240 Mon Sep 17 00:00:00 2001 From: lcnr Date: Tue, 4 Nov 2025 12:02:58 +0100 Subject: [PATCH 8/8] add logging to `fudge_inference_if_ok` --- compiler/rustc_infer/src/infer/snapshot/fudge.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_infer/src/infer/snapshot/fudge.rs b/compiler/rustc_infer/src/infer/snapshot/fudge.rs index e210479581ba6..3730d215a901e 100644 --- a/compiler/rustc_infer/src/infer/snapshot/fudge.rs +++ b/compiler/rustc_infer/src/infer/snapshot/fudge.rs @@ -1,3 +1,4 @@ +use std::fmt::Debug; use std::ops::Range; use rustc_data_structures::{snapshot_vec as sv, unify as ut}; @@ -84,11 +85,12 @@ impl<'tcx> InferCtxt<'tcx> { /// the actual types (`?T`, `Option`) -- and remember that /// after the snapshot is popped, the variable `?T` is no longer /// unified. - #[instrument(skip(self, f), level = "debug")] + #[instrument(skip(self, f), level = "debug", ret)] pub fn fudge_inference_if_ok(&self, f: F) -> Result where F: FnOnce() -> Result, T: TypeFoldable>, + E: Debug, { let variable_lengths = self.variable_lengths(); let (snapshot_vars, value) = self.probe(|_| {