From 9e6ba08b282cd3ce0fc2d63aff3119676f1d05b8 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 13 Sep 2025 13:30:00 +0200 Subject: [PATCH 1/3] repr(C) enums: fix enum size mismatch on MSVC targets --- compiler/rustc_abi/src/layout.rs | 4 +- compiler/rustc_abi/src/lib.rs | 23 ++++++++++-- compiler/rustc_hir_analysis/src/collect.rs | 2 +- .../rustc_hir_analysis/src/collect/type_of.rs | 2 +- compiler/rustc_middle/src/ty/adt.rs | 6 +-- compiler/rustc_middle/src/ty/sty.rs | 2 +- .../src/builder/expr/as_rvalue.rs | 2 +- .../src/builder/matches/test.rs | 2 +- compiler/rustc_mir_build/src/thir/cx/expr.rs | 2 +- .../rustc_mir_transform/src/elaborate_drop.rs | 2 +- .../rustc_mir_transform/src/large_enums.rs | 2 +- compiler/rustc_target/src/spec/base/msvc.rs | 5 +++ compiler/rustc_target/src/spec/mod.rs | 10 +++++ .../clippy/clippy_lints/src/enum_clike.rs | 2 +- .../crates/hir-ty/src/chalk_db.rs | 3 +- tests/auxiliary/minicore.rs | 35 +++++++++++++++++- .../repr-c-size.linux32.stderr | 15 ++++++++ .../repr-c-size.msvc32.stderr | 16 ++++++++ .../repr-c-size.msvc64.stderr | 16 ++++++++ tests/ui/enum-discriminant/repr-c-size.rs | 37 +++++++++++++++++++ 20 files changed, 168 insertions(+), 20 deletions(-) create mode 100644 tests/ui/enum-discriminant/repr-c-size.linux32.stderr create mode 100644 tests/ui/enum-discriminant/repr-c-size.msvc32.stderr create mode 100644 tests/ui/enum-discriminant/repr-c-size.msvc64.stderr create mode 100644 tests/ui/enum-discriminant/repr-c-size.rs diff --git a/compiler/rustc_abi/src/layout.rs b/compiler/rustc_abi/src/layout.rs index 5004d0c80220f..0d4a89d99bc7c 100644 --- a/compiler/rustc_abi/src/layout.rs +++ b/compiler/rustc_abi/src/layout.rs @@ -757,7 +757,7 @@ impl LayoutCalculator { let niche_filling_layout = calculate_niche_filling_layout(); - let discr_type = repr.discr_type(); + let discr_type = repr.discr_type(dl); let discr_int = Integer::from_attr(dl, discr_type); // Because we can only represent one range of valid values, we'll look for the // largest range of invalid values and pick everything else as the range of valid @@ -875,7 +875,7 @@ impl LayoutCalculator { return Err(LayoutCalculatorError::SizeOverflow); } - let typeck_ity = Integer::from_attr(dl, repr.discr_type()); + let typeck_ity = Integer::from_attr(dl, repr.discr_type(dl)); if typeck_ity < min_ity { // It is a bug if Layout decided on a greater discriminant size than typeck for // some reason at this point (based on values discriminant can take on). Mostly diff --git a/compiler/rustc_abi/src/lib.rs b/compiler/rustc_abi/src/lib.rs index 369874521e57e..b89958405facd 100644 --- a/compiler/rustc_abi/src/lib.rs +++ b/compiler/rustc_abi/src/lib.rs @@ -183,8 +183,22 @@ impl ReprOptions { /// Returns the discriminant type, given these `repr` options. /// This must only be called on enums! - pub fn discr_type(&self) -> IntegerType { - self.int.unwrap_or(IntegerType::Pointer(true)) + /// + /// 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 (which it will do depending on the actual discriminant values, also enforcing + /// `c_enum_min_size` along the way) and that will work just fine, it just induces casts when + /// getting/setting the discriminant. + pub fn discr_type(&self, cx: &impl HasDataLayout) -> IntegerType { + self.int.unwrap_or( + if self.c() + && let Some(max_size) = cx.data_layout().c_enum_max_size + { + IntegerType::Fixed(max_size, true) + } else { + IntegerType::Pointer(true) + }, + ) } /// Returns `true` if this `#[repr()]` should inhabit "smart enum @@ -274,6 +288,8 @@ pub struct TargetDataLayout { /// Note: This isn't in LLVM's data layout string, it is `short_enum` /// so the only valid spec for LLVM is c_int::BITS or 8 pub c_enum_min_size: Integer, + /// Maximum size of #[repr(C)] enums (defaults to pointer size). + pub c_enum_max_size: Option, } impl Default for TargetDataLayout { @@ -307,6 +323,7 @@ impl Default for TargetDataLayout { address_space_info: vec![], instruction_address_space: AddressSpace::ZERO, c_enum_min_size: Integer::I32, + c_enum_max_size: None, } } } @@ -327,7 +344,7 @@ impl TargetDataLayout { /// [llvm data layout string](https://llvm.org/docs/LangRef.html#data-layout) /// /// This function doesn't fill `c_enum_min_size` and it will always be `I32` since it can not be - /// determined from llvm string. + /// determined from llvm string. Likewise, it does not fill in `c_enum_max_size`. pub fn parse_from_llvm_datalayout_string<'a>( input: &'a str, default_address_space: AddressSpace, diff --git a/compiler/rustc_hir_analysis/src/collect.rs b/compiler/rustc_hir_analysis/src/collect.rs index b72e743f95b0f..ad0782b965426 100644 --- a/compiler/rustc_hir_analysis/src/collect.rs +++ b/compiler/rustc_hir_analysis/src/collect.rs @@ -607,7 +607,7 @@ pub(super) fn lower_variant_ctor(tcx: TyCtxt<'_>, def_id: LocalDefId) { pub(super) fn lower_enum_variant_types(tcx: TyCtxt<'_>, def_id: DefId) { let def = tcx.adt_def(def_id); - let repr_type = def.repr().discr_type(); + let repr_type = def.repr().discr_type(&tcx); let initial = repr_type.initial_discriminant(tcx); let mut prev_discr = None::>; diff --git a/compiler/rustc_hir_analysis/src/collect/type_of.rs b/compiler/rustc_hir_analysis/src/collect/type_of.rs index 62125c99d8021..9f9a52ff9f050 100644 --- a/compiler/rustc_hir_analysis/src/collect/type_of.rs +++ b/compiler/rustc_hir_analysis/src/collect/type_of.rs @@ -46,7 +46,7 @@ fn anon_const_type_of<'tcx>(icx: &ItemCtxt<'tcx>, def_id: LocalDefId) -> Ty<'tcx }) if anon_hir_id == hir_id => const_arg_anon_type_of(icx, arg_hir_id, span), Node::Variant(Variant { disr_expr: Some(e), .. }) if e.hir_id == hir_id => { - tcx.adt_def(tcx.hir_get_parent_item(hir_id)).repr().discr_type().to_ty(tcx) + tcx.adt_def(tcx.hir_get_parent_item(hir_id)).repr().discr_type(&tcx).to_ty(tcx) } // Sort of affects the type system, but only for the purpose of diagnostics // so no need for ConstArg. diff --git a/compiler/rustc_middle/src/ty/adt.rs b/compiler/rustc_middle/src/ty/adt.rs index df82c7a826be9..1eac113c2c491 100644 --- a/compiler/rustc_middle/src/ty/adt.rs +++ b/compiler/rustc_middle/src/ty/adt.rs @@ -526,7 +526,7 @@ impl<'tcx> AdtDef<'tcx> { ) -> Result, ErrorGuaranteed> { assert!(self.is_enum()); - let repr_type = self.repr().discr_type(); + let repr_type = self.repr().discr_type(&tcx); match tcx.const_eval_poly(expr_did) { Ok(val) => { let typing_env = ty::TypingEnv::post_analysis(tcx, expr_did); @@ -561,7 +561,7 @@ impl<'tcx> AdtDef<'tcx> { tcx: TyCtxt<'tcx>, ) -> impl Iterator)> { assert!(self.is_enum()); - let repr_type = self.repr().discr_type(); + let repr_type = self.repr().discr_type(&tcx); let initial = repr_type.initial_discriminant(tcx); let mut prev_discr = None::>; self.variants().iter_enumerated().map(move |(i, v)| { @@ -600,7 +600,7 @@ impl<'tcx> AdtDef<'tcx> { { val } else { - self.repr().discr_type().initial_discriminant(tcx) + self.repr().discr_type(&tcx).initial_discriminant(tcx) }; explicit_value.checked_add(tcx, offset as u128).0 } diff --git a/compiler/rustc_middle/src/ty/sty.rs b/compiler/rustc_middle/src/ty/sty.rs index 755fc68d86f34..bb2639075161a 100644 --- a/compiler/rustc_middle/src/ty/sty.rs +++ b/compiler/rustc_middle/src/ty/sty.rs @@ -1589,7 +1589,7 @@ impl<'tcx> Ty<'tcx> { /// Returns the type of the discriminant of this type. pub fn discriminant_ty(self, tcx: TyCtxt<'tcx>) -> Ty<'tcx> { match self.kind() { - ty::Adt(adt, _) if adt.is_enum() => adt.repr().discr_type().to_ty(tcx), + ty::Adt(adt, _) if adt.is_enum() => adt.repr().discr_type(&tcx).to_ty(tcx), ty::Coroutine(_, args) => args.as_coroutine().discr_ty(tcx), ty::Param(_) | ty::Alias(..) | ty::Infer(ty::TyVar(_)) => { diff --git a/compiler/rustc_mir_build/src/builder/expr/as_rvalue.rs b/compiler/rustc_mir_build/src/builder/expr/as_rvalue.rs index a4ef6e9273921..61e862695764a 100644 --- a/compiler/rustc_mir_build/src/builder/expr/as_rvalue.rs +++ b/compiler/rustc_mir_build/src/builder/expr/as_rvalue.rs @@ -197,7 +197,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { let (source, ty) = if let ty::Adt(adt_def, ..) = source_expr.ty.kind() && adt_def.is_enum() { - let discr_ty = adt_def.repr().discr_type().to_ty(this.tcx); + let discr_ty = adt_def.repr().discr_type(&this.tcx).to_ty(this.tcx); let temp = unpack!(block = this.as_temp(block, scope, source, Mutability::Not)); let discr = this.temp(discr_ty, source_expr.span); this.cfg.push_assign( diff --git a/compiler/rustc_mir_build/src/builder/matches/test.rs b/compiler/rustc_mir_build/src/builder/matches/test.rs index d03794fe2d584..794999e930c9d 100644 --- a/compiler/rustc_mir_build/src/builder/matches/test.rs +++ b/compiler/rustc_mir_build/src/builder/matches/test.rs @@ -89,7 +89,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { otherwise_block, ); debug!("num_enum_variants: {}", adt_def.variants().len()); - let discr_ty = adt_def.repr().discr_type().to_ty(self.tcx); + let discr_ty = adt_def.repr().discr_type(&self.tcx).to_ty(self.tcx); let discr = self.temp(discr_ty, test.span); self.cfg.push_assign( block, diff --git a/compiler/rustc_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs index 0b9bc018a09b3..3e6e58f5a4ef3 100644 --- a/compiler/rustc_mir_build/src/thir/cx/expr.rs +++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs @@ -292,7 +292,7 @@ impl<'tcx> ThirBuildCx<'tcx> { let (discr_did, discr_offset) = adt_def.discriminant_def_for_variant(idx); use rustc_middle::ty::util::IntTypeExt; - let ty = adt_def.repr().discr_type(); + let ty = adt_def.repr().discr_type(&tcx); let discr_ty = ty.to_ty(tcx); let size = tcx diff --git a/compiler/rustc_mir_transform/src/elaborate_drop.rs b/compiler/rustc_mir_transform/src/elaborate_drop.rs index 4f3c53d761f10..86714182d9625 100644 --- a/compiler/rustc_mir_transform/src/elaborate_drop.rs +++ b/compiler/rustc_mir_transform/src/elaborate_drop.rs @@ -937,7 +937,7 @@ where // Additionally, we do not want to switch on the // discriminant after it is free-ed, because that // way lies only trouble. - let discr_ty = adt.repr().discr_type().to_ty(self.tcx()); + let discr_ty = adt.repr().discr_type(&self.tcx()).to_ty(self.tcx()); let discr = Place::from(self.new_temp(discr_ty)); let discr_rv = Rvalue::Discriminant(self.place); let switch_block = BasicBlockData::new_stmts( diff --git a/compiler/rustc_mir_transform/src/large_enums.rs b/compiler/rustc_mir_transform/src/large_enums.rs index 1a91d6bd7da98..b5024ad12ea19 100644 --- a/compiler/rustc_mir_transform/src/large_enums.rs +++ b/compiler/rustc_mir_transform/src/large_enums.rs @@ -85,7 +85,7 @@ impl<'tcx> crate::MirPass<'tcx> for EnumSizeOpt { let const_assign = StatementKind::Assign(Box::new((place, rval))); let discr_place = - Place::from(patch.new_temp(adt_def.repr().discr_type().to_ty(tcx), span)); + Place::from(patch.new_temp(adt_def.repr().discr_type(&tcx).to_ty(tcx), span)); let store_discr = StatementKind::Assign(Box::new((discr_place, Rvalue::Discriminant(*rhs)))); diff --git a/compiler/rustc_target/src/spec/base/msvc.rs b/compiler/rustc_target/src/spec/base/msvc.rs index 486d7158723f8..cac1cfac3c36e 100644 --- a/compiler/rustc_target/src/spec/base/msvc.rs +++ b/compiler/rustc_target/src/spec/base/msvc.rs @@ -18,6 +18,11 @@ pub(crate) fn opts() -> TargetOptions { emit_debug_gdb_scripts: false, archive_format: "coff".into(), + // MSVC does not seem to ever automatically increase enums beyond their default size (see + // , ). + c_enum_min_bits: Some(32), + c_enum_max_bits: Some(32), + // Currently this is the only supported method of debuginfo on MSVC // where `*.pdb` files show up next to the final artifact. split_debuginfo: SplitDebuginfo::Packed, diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs index 4d9f06c568bd3..ea33e88c50142 100644 --- a/compiler/rustc_target/src/spec/mod.rs +++ b/compiler/rustc_target/src/spec/mod.rs @@ -2386,6 +2386,13 @@ impl Target { self.c_enum_min_bits.unwrap_or(self.c_int_width as _), )) .map_err(|err| TargetDataLayoutErrors::InvalidBitsSize { err })?; + dl.c_enum_max_size = match self.c_enum_max_bits { + None => None, + Some(max_bits) => Some( + Integer::from_size(Size::from_bits(max_bits)) + .map_err(|err| TargetDataLayoutErrors::InvalidBitsSize { err })?, + ), + }; Ok(dl) } @@ -2797,6 +2804,8 @@ pub struct TargetOptions { /// Minimum number of bits in #[repr(C)] enum. Defaults to the size of c_int pub c_enum_min_bits: Option, + /// Maximum number of bits in #[repr(C)] enum. Defaults to the pointer size. + pub c_enum_max_bits: Option, /// Whether or not the DWARF `.debug_aranges` section should be generated. pub generate_arange_section: bool, @@ -3044,6 +3053,7 @@ impl Default for TargetOptions { supported_split_debuginfo: Cow::Borrowed(&[SplitDebuginfo::Off]), supported_sanitizers: SanitizerSet::empty(), c_enum_min_bits: None, + c_enum_max_bits: None, generate_arange_section: true, supports_stack_protector: true, entry_name: "main".into(), diff --git a/src/tools/clippy/clippy_lints/src/enum_clike.rs b/src/tools/clippy/clippy_lints/src/enum_clike.rs index c828fc57f7601..7312d38a35c8f 100644 --- a/src/tools/clippy/clippy_lints/src/enum_clike.rs +++ b/src/tools/clippy/clippy_lints/src/enum_clike.rs @@ -52,7 +52,7 @@ impl<'tcx> LateLintPass<'tcx> for UnportableVariant { if let ty::Adt(adt, _) = ty.kind() && adt.is_enum() { - ty = adt.repr().discr_type().to_ty(cx.tcx); + ty = adt.repr().discr_type(&cx.tcx).to_ty(cx.tcx); } match ty.kind() { ty::Int(IntTy::Isize) => { diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/chalk_db.rs b/src/tools/rust-analyzer/crates/hir-ty/src/chalk_db.rs index 3ba7c93d4fb76..6e1b910124bad 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/chalk_db.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/chalk_db.rs @@ -87,7 +87,8 @@ impl chalk_solve::RustIrDatabase for ChalkContext<'_> { && let hir_def::AdtId::EnumId(e) = id.0 { let enum_data = self.db.enum_signature(e); - let ty = enum_data.repr.unwrap_or_default().discr_type(); + let dl = self.db.target_data_layout(self.krate).expect("FIXME"); + let ty = enum_data.repr.unwrap_or_default().discr_type(&*dl); return chalk_ir::TyKind::Scalar(match ty { hir_def::layout::IntegerType::Pointer(is_signed) => match is_signed { true => chalk_ir::Scalar::Int(chalk_ir::IntTy::Isize), diff --git a/tests/auxiliary/minicore.rs b/tests/auxiliary/minicore.rs index 4f4c653cb46e7..f3fe134a22ae8 100644 --- a/tests/auxiliary/minicore.rs +++ b/tests/auxiliary/minicore.rs @@ -169,11 +169,29 @@ pub trait Add { fn add(self, _: Rhs) -> Self::Output; } -impl Add for isize { +impl Add for isize { type Output = isize; fn add(self, other: isize) -> isize { - 7 // avoid needing to add all of the overflow handling and panic language items + loop {} // Dummy impl, not actually used + } +} + +#[lang = "eq"] +pub trait PartialEq { + fn eq(&self, other: &Rhs) -> bool; + + fn ne(&self, other: &Rhs) -> bool { + match self.eq(other) { + true => false, + false => true, + } + } +} + +impl PartialEq for usize { + fn eq(&self, other: &Self) -> bool { + loop {} // Dummy impl, not actually used } } @@ -231,6 +249,19 @@ 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; +} + +pub mod hint { + #[rustc_nounwind] + #[rustc_intrinsic] + pub const unsafe fn unreachable() -> !; } #[lang = "c_void"] diff --git a/tests/ui/enum-discriminant/repr-c-size.linux32.stderr b/tests/ui/enum-discriminant/repr-c-size.linux32.stderr new file mode 100644 index 0000000000000..c3aedca896650 --- /dev/null +++ b/tests/ui/enum-discriminant/repr-c-size.linux32.stderr @@ -0,0 +1,15 @@ +warning: literal out of range for `isize` + --> $DIR/repr-c-size.rs:24:9 + | +LL | A = 9223372036854775807, // i64::MAX + | ^^^^^^^^^^^^^^^^^^^ + | + = note: the literal `9223372036854775807` does not fit into the type `isize` whose range is `-2147483648..=2147483647` +note: the lint level is defined here + --> $DIR/repr-c-size.rs:22:8 + | +LL | #[warn(overflowing_literals)] + | ^^^^^^^^^^^^^^^^^^^^ + +warning: 1 warning emitted + diff --git a/tests/ui/enum-discriminant/repr-c-size.msvc32.stderr b/tests/ui/enum-discriminant/repr-c-size.msvc32.stderr new file mode 100644 index 0000000000000..4780ecea01699 --- /dev/null +++ b/tests/ui/enum-discriminant/repr-c-size.msvc32.stderr @@ -0,0 +1,16 @@ +warning: literal out of range for `i32` + --> $DIR/repr-c-size.rs:24:9 + | +LL | A = 9223372036854775807, // i64::MAX + | ^^^^^^^^^^^^^^^^^^^ + | + = note: the literal `9223372036854775807` does not fit into the type `i32` whose range is `-2147483648..=2147483647` + = help: consider using the type `i64` instead +note: the lint level is defined here + --> $DIR/repr-c-size.rs:22:8 + | +LL | #[warn(overflowing_literals)] + | ^^^^^^^^^^^^^^^^^^^^ + +warning: 1 warning emitted + diff --git a/tests/ui/enum-discriminant/repr-c-size.msvc64.stderr b/tests/ui/enum-discriminant/repr-c-size.msvc64.stderr new file mode 100644 index 0000000000000..4780ecea01699 --- /dev/null +++ b/tests/ui/enum-discriminant/repr-c-size.msvc64.stderr @@ -0,0 +1,16 @@ +warning: literal out of range for `i32` + --> $DIR/repr-c-size.rs:24:9 + | +LL | A = 9223372036854775807, // i64::MAX + | ^^^^^^^^^^^^^^^^^^^ + | + = note: the literal `9223372036854775807` does not fit into the type `i32` whose range is `-2147483648..=2147483647` + = help: consider using the type `i64` instead +note: the lint level is defined here + --> $DIR/repr-c-size.rs:22:8 + | +LL | #[warn(overflowing_literals)] + | ^^^^^^^^^^^^^^^^^^^^ + +warning: 1 warning emitted + diff --git a/tests/ui/enum-discriminant/repr-c-size.rs b/tests/ui/enum-discriminant/repr-c-size.rs new file mode 100644 index 0000000000000..a775055808f11 --- /dev/null +++ b/tests/ui/enum-discriminant/repr-c-size.rs @@ -0,0 +1,37 @@ +//@ check-pass +//@ revisions: linux32 linux64 msvc32 msvc64 +//@[linux32] compile-flags: --target i686-unknown-linux-gnu +//@[linux32] needs-llvm-components: x86 +//@[linux64] compile-flags: --target x86_64-unknown-linux-gnu +//@[linux64] needs-llvm-components: x86 +//@[msvc32] compile-flags: --target i686-pc-windows-msvc +//@[msvc32] needs-llvm-components: x86 +//@[msvc64] compile-flags: --target x86_64-pc-windows-msvc +//@[msvc64] needs-llvm-components: x86 + +//@ add-core-stubs +#![feature(no_core)] +#![no_core] +extern crate minicore; +use minicore::*; + +// Fits in i64 but not i32. +// C compiler demo: +// FIXME: This seems to be wrong for linux32? +#[repr(C)] +#[warn(overflowing_literals)] +enum OverflowingEnum { + A = 9223372036854775807, // i64::MAX + //[linux32,msvc32,msvc64]~^ WARN: literal out of range +} + +#[cfg(not(linux64))] +const _: () = if mem::size_of::() != 4 { + unsafe { hint::unreachable() } +}; +#[cfg(linux64)] +const _: () = if mem::size_of::() != 8 { + unsafe { hint::unreachable() } +}; + +fn main() {} From 63d04dee4a7a99741aa07d9debb1785165f1894e Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 13 Sep 2025 13:59:04 +0200 Subject: [PATCH 2/3] add some more repr(C) enum size tests --- tests/auxiliary/minicore.rs | 23 ++++++++++ .../repr-c-size.linux32.stderr | 15 ++++++- .../repr-c-size.msvc32.stderr | 16 ++++++- .../repr-c-size.msvc64.stderr | 16 ++++++- tests/ui/enum-discriminant/repr-c-size.rs | 42 +++++++++++++++++++ 5 files changed, 109 insertions(+), 3 deletions(-) diff --git a/tests/auxiliary/minicore.rs b/tests/auxiliary/minicore.rs index f3fe134a22ae8..01ad172fde537 100644 --- a/tests/auxiliary/minicore.rs +++ b/tests/auxiliary/minicore.rs @@ -162,6 +162,29 @@ macro_rules! stringify { }; } +#[lang = "neg"] +pub trait Neg { + type Output; + + fn neg(self) -> Self::Output; +} + +impl Neg for i32 { + type Output = i32; + + fn neg(self) -> i32 { + loop {} // Dummy impl, not actually used + } +} + +impl Neg for isize { + type Output = isize; + + fn neg(self) -> isize { + loop {} // Dummy impl, not actually used + } +} + #[lang = "add"] pub trait Add { type Output; diff --git a/tests/ui/enum-discriminant/repr-c-size.linux32.stderr b/tests/ui/enum-discriminant/repr-c-size.linux32.stderr index c3aedca896650..7c0c24a821aa6 100644 --- a/tests/ui/enum-discriminant/repr-c-size.linux32.stderr +++ b/tests/ui/enum-discriminant/repr-c-size.linux32.stderr @@ -11,5 +11,18 @@ note: the lint level is defined here LL | #[warn(overflowing_literals)] | ^^^^^^^^^^^^^^^^^^^^ -warning: 1 warning emitted +warning: literal out of range for `isize` + --> $DIR/repr-c-size.rs:43:9 + | +LL | A = 4294967294, // u32::MAX - 1 + | ^^^^^^^^^^ + | + = note: the literal `4294967294` does not fit into the type `isize` whose range is `-2147483648..=2147483647` +note: the lint level is defined here + --> $DIR/repr-c-size.rs:41:8 + | +LL | #[warn(overflowing_literals)] + | ^^^^^^^^^^^^^^^^^^^^ + +warning: 2 warnings emitted diff --git a/tests/ui/enum-discriminant/repr-c-size.msvc32.stderr b/tests/ui/enum-discriminant/repr-c-size.msvc32.stderr index 4780ecea01699..97c53eb8ee45b 100644 --- a/tests/ui/enum-discriminant/repr-c-size.msvc32.stderr +++ b/tests/ui/enum-discriminant/repr-c-size.msvc32.stderr @@ -12,5 +12,19 @@ note: the lint level is defined here LL | #[warn(overflowing_literals)] | ^^^^^^^^^^^^^^^^^^^^ -warning: 1 warning emitted +warning: literal out of range for `i32` + --> $DIR/repr-c-size.rs:43:9 + | +LL | A = 4294967294, // u32::MAX - 1 + | ^^^^^^^^^^ + | + = note: the literal `4294967294` does not fit into the type `i32` whose range is `-2147483648..=2147483647` + = help: consider using the type `u32` instead +note: the lint level is defined here + --> $DIR/repr-c-size.rs:41:8 + | +LL | #[warn(overflowing_literals)] + | ^^^^^^^^^^^^^^^^^^^^ + +warning: 2 warnings emitted diff --git a/tests/ui/enum-discriminant/repr-c-size.msvc64.stderr b/tests/ui/enum-discriminant/repr-c-size.msvc64.stderr index 4780ecea01699..97c53eb8ee45b 100644 --- a/tests/ui/enum-discriminant/repr-c-size.msvc64.stderr +++ b/tests/ui/enum-discriminant/repr-c-size.msvc64.stderr @@ -12,5 +12,19 @@ note: the lint level is defined here LL | #[warn(overflowing_literals)] | ^^^^^^^^^^^^^^^^^^^^ -warning: 1 warning emitted +warning: literal out of range for `i32` + --> $DIR/repr-c-size.rs:43:9 + | +LL | A = 4294967294, // u32::MAX - 1 + | ^^^^^^^^^^ + | + = note: the literal `4294967294` does not fit into the type `i32` whose range is `-2147483648..=2147483647` + = help: consider using the type `u32` instead +note: the lint level is defined here + --> $DIR/repr-c-size.rs:41:8 + | +LL | #[warn(overflowing_literals)] + | ^^^^^^^^^^^^^^^^^^^^ + +warning: 2 warnings emitted diff --git a/tests/ui/enum-discriminant/repr-c-size.rs b/tests/ui/enum-discriminant/repr-c-size.rs index a775055808f11..b63869a6ed382 100644 --- a/tests/ui/enum-discriminant/repr-c-size.rs +++ b/tests/ui/enum-discriminant/repr-c-size.rs @@ -34,4 +34,46 @@ const _: () = if mem::size_of::() != 8 { unsafe { hint::unreachable() } }; +// Each value fits in i32 or u32, but not all values fit into the same type. +// C compiler demo: +// FIXME: This seems to do the wrong thing for 32bit Linux? +#[repr(C)] +#[warn(overflowing_literals)] +enum OverflowingEnum2 { + A = 4294967294, // u32::MAX - 1 + //[linux32,msvc32,msvc64]~^ WARN: literal out of range + B = -1, +} + +#[cfg(not(linux64))] +const _: () = if mem::size_of::() != 4 { + unsafe { hint::unreachable() } +}; +#[cfg(linux64)] +const _: () = if mem::size_of::() != 8 { + unsafe { hint::unreachable() } +}; + +// Force i32 or u32, respectively. +// C compiler demo: +#[repr(C)] +enum I32Enum { + A = 2147483647, // i32::MAX + B = -2147483647, +} +const _: () = if mem::size_of::() != 4 { + unsafe { hint::unreachable() } +}; + +// C compiler demo: +#[repr(C)] +#[allow(overflowing_literals)] +enum U32Enum { + A = 4294967295, // u32::MAX + B = 0, +} +const _: () = if mem::size_of::() != 4 { + unsafe { hint::unreachable() } +}; + fn main() {} From d44c01c4515d19672eac31f7bd4295e7c4811a78 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 14 Sep 2025 12:23:47 +0200 Subject: [PATCH 3/3] allow repr(C) enums to be up to 64bit in size --- compiler/rustc_abi/src/lib.rs | 23 ++++++++------- compiler/rustc_target/src/spec/mod.rs | 14 +++++----- tests/auxiliary/minicore.rs | 8 ++++++ .../repr-c-size.linux32.stderr | 28 ------------------- .../repr-c-size.msvc32.stderr | 8 +++--- .../repr-c-size.msvc64.stderr | 8 +++--- tests/ui/enum-discriminant/repr-c-size.rs | 18 ++++++------ 7 files changed, 42 insertions(+), 65 deletions(-) delete mode 100644 tests/ui/enum-discriminant/repr-c-size.linux32.stderr diff --git a/compiler/rustc_abi/src/lib.rs b/compiler/rustc_abi/src/lib.rs index b89958405facd..d531e57b0f790 100644 --- a/compiler/rustc_abi/src/lib.rs +++ b/compiler/rustc_abi/src/lib.rs @@ -190,15 +190,11 @@ impl ReprOptions { /// `c_enum_min_size` along the way) and that will work just fine, it just induces casts when /// getting/setting the discriminant. pub fn discr_type(&self, cx: &impl HasDataLayout) -> IntegerType { - self.int.unwrap_or( - if self.c() - && let Some(max_size) = cx.data_layout().c_enum_max_size - { - IntegerType::Fixed(max_size, true) - } else { - IntegerType::Pointer(true) - }, - ) + self.int.unwrap_or(if self.c() { + IntegerType::Fixed(cx.data_layout().c_enum_max_size, true) + } else { + IntegerType::Pointer(true) + }) } /// Returns `true` if this `#[repr()]` should inhabit "smart enum @@ -288,8 +284,8 @@ pub struct TargetDataLayout { /// Note: This isn't in LLVM's data layout string, it is `short_enum` /// so the only valid spec for LLVM is c_int::BITS or 8 pub c_enum_min_size: Integer, - /// Maximum size of #[repr(C)] enums (defaults to pointer size). - pub c_enum_max_size: Option, + /// Maximum size of #[repr(C)] enums (defaults to c_longlong::BITS, which is always 64). + pub c_enum_max_size: Integer, } impl Default for TargetDataLayout { @@ -323,7 +319,10 @@ impl Default for TargetDataLayout { address_space_info: vec![], instruction_address_space: AddressSpace::ZERO, c_enum_min_size: Integer::I32, - c_enum_max_size: None, + // C23 allows enums to have any integer type. The largest integer type in the standard + // is `long long`, which is always 64bits (judging from our own definition in + // `library/core/src/ffi/primitives.rs`). + c_enum_max_size: Integer::I64, } } } diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs index ea33e88c50142..4418c57e296a0 100644 --- a/compiler/rustc_target/src/spec/mod.rs +++ b/compiler/rustc_target/src/spec/mod.rs @@ -2386,13 +2386,13 @@ impl Target { self.c_enum_min_bits.unwrap_or(self.c_int_width as _), )) .map_err(|err| TargetDataLayoutErrors::InvalidBitsSize { err })?; - dl.c_enum_max_size = match self.c_enum_max_bits { - None => None, - Some(max_bits) => Some( - Integer::from_size(Size::from_bits(max_bits)) - .map_err(|err| TargetDataLayoutErrors::InvalidBitsSize { err })?, - ), - }; + dl.c_enum_max_size = Integer::from_size(Size::from_bits( + // C23 allows enums to have any integer type. The largest integer type in the standard + // is `long long`, which is always 64bits (judging from our own definition in + // `library/core/src/ffi/primitives.rs`). Hence we default to 64. + self.c_enum_max_bits.unwrap_or(64), + )) + .map_err(|err| TargetDataLayoutErrors::InvalidBitsSize { err })?; Ok(dl) } diff --git a/tests/auxiliary/minicore.rs b/tests/auxiliary/minicore.rs index 01ad172fde537..ea44709ec7b9f 100644 --- a/tests/auxiliary/minicore.rs +++ b/tests/auxiliary/minicore.rs @@ -177,6 +177,14 @@ impl Neg for i32 { } } +impl Neg for i64 { + type Output = i64; + + fn neg(self) -> i64 { + loop {} // Dummy impl, not actually used + } +} + impl Neg for isize { type Output = isize; diff --git a/tests/ui/enum-discriminant/repr-c-size.linux32.stderr b/tests/ui/enum-discriminant/repr-c-size.linux32.stderr deleted file mode 100644 index 7c0c24a821aa6..0000000000000 --- a/tests/ui/enum-discriminant/repr-c-size.linux32.stderr +++ /dev/null @@ -1,28 +0,0 @@ -warning: literal out of range for `isize` - --> $DIR/repr-c-size.rs:24:9 - | -LL | A = 9223372036854775807, // i64::MAX - | ^^^^^^^^^^^^^^^^^^^ - | - = note: the literal `9223372036854775807` does not fit into the type `isize` whose range is `-2147483648..=2147483647` -note: the lint level is defined here - --> $DIR/repr-c-size.rs:22:8 - | -LL | #[warn(overflowing_literals)] - | ^^^^^^^^^^^^^^^^^^^^ - -warning: literal out of range for `isize` - --> $DIR/repr-c-size.rs:43:9 - | -LL | A = 4294967294, // u32::MAX - 1 - | ^^^^^^^^^^ - | - = note: the literal `4294967294` does not fit into the type `isize` whose range is `-2147483648..=2147483647` -note: the lint level is defined here - --> $DIR/repr-c-size.rs:41:8 - | -LL | #[warn(overflowing_literals)] - | ^^^^^^^^^^^^^^^^^^^^ - -warning: 2 warnings emitted - diff --git a/tests/ui/enum-discriminant/repr-c-size.msvc32.stderr b/tests/ui/enum-discriminant/repr-c-size.msvc32.stderr index 97c53eb8ee45b..6082d77224247 100644 --- a/tests/ui/enum-discriminant/repr-c-size.msvc32.stderr +++ b/tests/ui/enum-discriminant/repr-c-size.msvc32.stderr @@ -1,5 +1,5 @@ warning: literal out of range for `i32` - --> $DIR/repr-c-size.rs:24:9 + --> $DIR/repr-c-size.rs:23:9 | LL | A = 9223372036854775807, // i64::MAX | ^^^^^^^^^^^^^^^^^^^ @@ -7,13 +7,13 @@ LL | A = 9223372036854775807, // i64::MAX = note: the literal `9223372036854775807` does not fit into the type `i32` whose range is `-2147483648..=2147483647` = help: consider using the type `i64` instead note: the lint level is defined here - --> $DIR/repr-c-size.rs:22:8 + --> $DIR/repr-c-size.rs:21:8 | LL | #[warn(overflowing_literals)] | ^^^^^^^^^^^^^^^^^^^^ warning: literal out of range for `i32` - --> $DIR/repr-c-size.rs:43:9 + --> $DIR/repr-c-size.rs:41:9 | LL | A = 4294967294, // u32::MAX - 1 | ^^^^^^^^^^ @@ -21,7 +21,7 @@ LL | A = 4294967294, // u32::MAX - 1 = note: the literal `4294967294` does not fit into the type `i32` whose range is `-2147483648..=2147483647` = help: consider using the type `u32` instead note: the lint level is defined here - --> $DIR/repr-c-size.rs:41:8 + --> $DIR/repr-c-size.rs:39:8 | LL | #[warn(overflowing_literals)] | ^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/enum-discriminant/repr-c-size.msvc64.stderr b/tests/ui/enum-discriminant/repr-c-size.msvc64.stderr index 97c53eb8ee45b..6082d77224247 100644 --- a/tests/ui/enum-discriminant/repr-c-size.msvc64.stderr +++ b/tests/ui/enum-discriminant/repr-c-size.msvc64.stderr @@ -1,5 +1,5 @@ warning: literal out of range for `i32` - --> $DIR/repr-c-size.rs:24:9 + --> $DIR/repr-c-size.rs:23:9 | LL | A = 9223372036854775807, // i64::MAX | ^^^^^^^^^^^^^^^^^^^ @@ -7,13 +7,13 @@ LL | A = 9223372036854775807, // i64::MAX = note: the literal `9223372036854775807` does not fit into the type `i32` whose range is `-2147483648..=2147483647` = help: consider using the type `i64` instead note: the lint level is defined here - --> $DIR/repr-c-size.rs:22:8 + --> $DIR/repr-c-size.rs:21:8 | LL | #[warn(overflowing_literals)] | ^^^^^^^^^^^^^^^^^^^^ warning: literal out of range for `i32` - --> $DIR/repr-c-size.rs:43:9 + --> $DIR/repr-c-size.rs:41:9 | LL | A = 4294967294, // u32::MAX - 1 | ^^^^^^^^^^ @@ -21,7 +21,7 @@ LL | A = 4294967294, // u32::MAX - 1 = note: the literal `4294967294` does not fit into the type `i32` whose range is `-2147483648..=2147483647` = help: consider using the type `u32` instead note: the lint level is defined here - --> $DIR/repr-c-size.rs:41:8 + --> $DIR/repr-c-size.rs:39:8 | LL | #[warn(overflowing_literals)] | ^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/enum-discriminant/repr-c-size.rs b/tests/ui/enum-discriminant/repr-c-size.rs index b63869a6ed382..48dde1688f60b 100644 --- a/tests/ui/enum-discriminant/repr-c-size.rs +++ b/tests/ui/enum-discriminant/repr-c-size.rs @@ -17,40 +17,38 @@ use minicore::*; // Fits in i64 but not i32. // C compiler demo: -// FIXME: This seems to be wrong for linux32? #[repr(C)] #[warn(overflowing_literals)] enum OverflowingEnum { A = 9223372036854775807, // i64::MAX - //[linux32,msvc32,msvc64]~^ WARN: literal out of range + //[msvc32,msvc64]~^ WARN: literal out of range } -#[cfg(not(linux64))] +#[cfg(any(msvc32,msvc64))] const _: () = if mem::size_of::() != 4 { unsafe { hint::unreachable() } }; -#[cfg(linux64)] +#[cfg(any(linux32,linux64))] const _: () = if mem::size_of::() != 8 { unsafe { hint::unreachable() } }; // Each value fits in i32 or u32, but not all values fit into the same type. // C compiler demo: -// FIXME: This seems to do the wrong thing for 32bit Linux? #[repr(C)] #[warn(overflowing_literals)] enum OverflowingEnum2 { A = 4294967294, // u32::MAX - 1 - //[linux32,msvc32,msvc64]~^ WARN: literal out of range + //[msvc32,msvc64]~^ WARN: literal out of range B = -1, } -#[cfg(not(linux64))] -const _: () = if mem::size_of::() != 4 { +#[cfg(any(msvc32,msvc64))] +const _: () = if mem::size_of::() != 4 { unsafe { hint::unreachable() } }; -#[cfg(linux64)] -const _: () = if mem::size_of::() != 8 { +#[cfg(any(linux32,linux64))] +const _: () = if mem::size_of::() != 8 { unsafe { hint::unreachable() } };