diff --git a/compiler/rustc_public/src/abi.rs b/compiler/rustc_public/src/abi.rs index 02674e4107c77..ce4408a5cfbdf 100644 --- a/compiler/rustc_public/src/abi.rs +++ b/compiler/rustc_public/src/abi.rs @@ -8,7 +8,7 @@ use crate::compiler_interface::with; use crate::mir::FieldIdx; use crate::target::{MachineInfo, MachineSize as Size}; use crate::ty::{Align, Ty, VariantIdx, index_impl}; -use crate::{Error, Opaque, ThreadLocalIndex, error}; +use crate::{Error, ThreadLocalIndex, error}; /// A function ABI definition. #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)] @@ -19,9 +19,11 @@ pub struct FnAbi { /// The expected return type. pub ret: ArgAbi, - /// The count of non-variadic arguments. + /// The count of declared arguments (excluding variadic and implicit arguments). /// - /// Should only be different from `args.len()` when a function is a C variadic function. + /// This may be less than `args.len()` for C variadic functions (which have + /// additional variadic arguments) or `#[track_caller]` functions (which have + /// an implicit caller location argument). pub fixed_count: u32, /// The ABI convention. @@ -40,24 +42,166 @@ pub struct ArgAbi { } /// How a function argument should be passed in to the target function. +/// +/// The pass mode is determined by the platform's calling convention and the +/// argument's type layout. The same Rust type may use different pass modes +/// on different targets or when register availability changes. +/// +/// Note: for the Rust ABI, pass modes may not correspond to any valid C +/// calling convention (e.g., using more return registers than the platform +/// C ABI allows). Further processing may be needed depending on the target. #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)] pub enum PassMode { /// Ignore the argument. /// - /// The argument is either uninhabited or a ZST. + /// The argument is either uninhabited or a ZST (zero-sized type). Ignore, - /// Pass the argument directly. + /// Pass the argument directly in a single register. + /// + /// Used for primitive types and small values that fit in one register. + Direct(ArgAttributes), + /// Pass the argument directly in two registers. /// - /// The argument has a layout abi of `Scalar` or `Vector`. - Direct(Opaque), - /// Pass a pair's elements directly in two arguments. + /// Used for types represented as a pair of values (e.g., a fat pointer + /// consisting of a data pointer and a length/vtable pointer). + Pair(ArgAttributes, ArgAttributes), + /// Pass the argument after reinterpreting it as a different register layout. /// - /// The argument has a layout abi of `ScalarPair`. - Pair(Opaque, Opaque), - /// Pass the argument after casting it. - Cast { pad_i32: bool, cast: Opaque }, - /// Pass the argument indirectly via a hidden pointer. - Indirect { attrs: Opaque, meta_attrs: Opaque, on_stack: bool }, + /// Used for aggregates (structs, tuples) that the platform ABI passes in + /// registers. The argument's bytes are reinterpreted as the register + /// sequence described by [`CastTarget`]. See its documentation for details. + Cast { pad_i32: bool, cast: CastTarget }, + /// Pass the argument indirectly via a pointer. + /// + /// The caller places the value in memory and passes a pointer to it. + /// When `on_stack` is true, the value is placed at a fixed stack offset + /// rather than passed as a regular pointer argument. + Indirect { + attrs: ArgAttributes, + /// Attributes for the metadata pointer (vtable or length) of unsized arguments. + /// Only present for unsized types (e.g., `dyn Trait`, `[T]`). + meta_attrs: Option, + on_stack: bool, + }, +} + +/// Attributes of a function argument that affect its ABI. +/// +/// Not all internal compiler attributes are exposed here, as some are +/// LLVM-specific optimization hints. The internal representation is kept +/// private so it can be expanded in the future. +#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)] +pub struct ArgAttributes { + pub(crate) arg_ext: ArgExtension, + pub(crate) pointee_size: Size, + pub(crate) pointee_align: Option, +} + +impl ArgAttributes { + /// Return how this argument should be extended when passed in a register. + /// + /// Relevant for integer arguments smaller than the register width. + pub fn arg_extension(&self) -> ArgExtension { + self.arg_ext + } + + /// Return the minimum alignment of the pointee, if applicable. + /// + /// This is relevant for `PassMode::Indirect` arguments where the pointer + /// must satisfy a particular alignment. + pub fn pointee_align(&self) -> Option { + self.pointee_align + } + + /// Return the minimum dereferenceable size of the pointee, if known. + pub fn pointee_size(&self) -> Size { + self.pointee_size + } +} + +/// How a small integer argument should be extended to fill a register. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize)] +pub enum ArgExtension { + /// No extension required. + None, + /// Zero-extend to the register width. + Zext, + /// Sign-extend to the register width. + Sext, +} + +/// Describes the ABI type that an argument is transmuted to for `PassMode::Cast`. +/// +/// When an argument is "cast," its raw bytes are reinterpreted as a sequence of +/// register-sized values for passing. This struct describes that target layout: +/// +/// 1. The `prefix` registers are laid out first, like fields of a `repr(C)` struct +/// (i.e., with alignment padding between them). +/// 2. After the prefix, `rest.unit` is repeated enough times to cover `rest.total`, +/// starting at `rest_offset` (or immediately after the prefix if `None`). +/// +/// For example, on x86_64 a `struct { i32, f64 }` might be cast to a prefix of +/// `[Reg::i64()]` followed by a rest of `Reg::f64()` — placing the first 8 bytes +/// in an integer register and the second 8 bytes in a floating-point register. +#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)] +pub struct CastTarget { + /// Leading registers of potentially different types, laid out with `repr(C)` padding. + pub prefix: Vec, + /// The byte offset where `rest` begins, if explicitly set. + /// When `None`, `rest` starts immediately after the prefix. + pub rest_offset: Option, + /// The repeated trailing register type filling the remainder of the value. + pub rest: Uniform, +} + +impl CastTarget { + /// Return the total size of the ABI type this argument is cast to. + pub fn size(&self) -> Size { + let prefix_size: usize = self.prefix.iter().map(|r| r.size.bits()).sum(); + Size::from_bits(prefix_size + self.rest.total.bits()) + } +} + +/// A sequence of registers of the same kind used to pass an argument. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)] +pub struct Uniform { + /// The type of register used. + pub unit: Reg, + /// The total size of the argument, which can be: + /// * equal to `unit.size` (one scalar/vector), + /// * a multiple of `unit.size` (an array of scalar/vectors), + /// * if `unit.kind` is `Integer`, the last element can be shorter, i.e., `{ i64, i64, i32 }` + /// for 64-bit integers with a total size of 20 bytes. When the argument is actually passed, + /// this size will be rounded up to the nearest multiple of `unit.size`. + pub total: Size, + /// Whether the argument is consecutive: either all values are passed in registers, or all on + /// the stack with no additional padding between elements. + pub is_consecutive: bool, +} + +impl Uniform { + /// Return the number of registers needed to cover `total`. + pub fn reg_count(&self) -> usize { + if self.unit.size.bits() == 0 { + return 0; + } + (self.total.bits() + self.unit.size.bits() - 1) / self.unit.size.bits() + } +} + +/// A register type used in ABI calling conventions. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)] +pub struct Reg { + pub kind: RegKind, + pub size: Size, +} + +/// The kind of a register used in calling conventions. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)] +pub enum RegKind { + Integer, + Float, + Vector, } /// The layout of a type, alongside the type itself. @@ -67,7 +211,7 @@ pub struct TyAndLayout { pub layout: Layout, } -/// The layout of a type in memory. +/// The layout of a type, including its size, alignment, field offsets, and backend representation. #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)] pub struct LayoutShape { /// The fields location within the layout @@ -81,8 +225,8 @@ pub struct LayoutShape { /// must be taken into account. pub variants: VariantsShape, - /// The `abi` defines how this data is passed between functions. - pub abi: ValueAbi, + /// A hint for how backends should represent this type: as a scalar, vector, or aggregate. + pub value_repr: ValueRepr, /// The ABI mandated alignment in bytes. pub abi_align: Align, @@ -95,12 +239,12 @@ impl LayoutShape { /// Returns `true` if the layout corresponds to an unsized type. #[inline] pub fn is_unsized(&self) -> bool { - self.abi.is_unsized() + self.value_repr.is_unsized() } #[inline] pub fn is_sized(&self) -> bool { - !self.abi.is_unsized() + !self.value_repr.is_unsized() } /// Returns `true` if the type is sized and a 1-ZST (meaning it has size 0 and alignment 1). @@ -119,7 +263,7 @@ impl Layout { } } -/// Describes how the fields of a type are shaped in memory. +/// Describes the number and position of fields within a type's layout. #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)] pub enum FieldsShape { /// Scalar primitives and `!`, which never have fields. @@ -232,49 +376,54 @@ pub enum TagEncoding { }, } -/// How many scalable vectors are in a `ValueAbi::ScalableVector`? +/// The number of scalable vectors in a [`ValueRepr::ScalableVector`]. #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)] pub struct NumScalableVectors(pub(crate) u8); -/// Describes how values of the type are passed by target ABIs, -/// in terms of categories of C types there are ABI rules for. +/// A hint for how backends should represent values of this type. +/// +/// Distinguishes between types representable as scalars, pairs of scalars, +/// SIMD vectors, or aggregates. #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)] -pub enum ValueAbi { +pub enum ValueRepr { Scalar(Scalar), ScalarPair { a: Scalar, b: Scalar, b_offset: Size, }, + /// A fixed-length SIMD vector. Vector { element: Scalar, count: u64, }, + /// A scalable SIMD vector (e.g., ARM SVE). ScalableVector { element: Scalar, count: u64, number_of_vectors: NumScalableVectors, }, + /// The type is not representable as a scalar or vector (e.g., aggregates, unsized types). Aggregate { /// If true, the size is exact, otherwise it's only a lower bound. sized: bool, }, } -impl ValueAbi { +impl ValueRepr { /// Returns `true` if the layout corresponds to an unsized type. pub fn is_unsized(&self) -> bool { match *self { - ValueAbi::Scalar(_) - | ValueAbi::ScalarPair { .. } - | ValueAbi::Vector { .. } + ValueRepr::Scalar(_) + | ValueRepr::ScalarPair { .. } + | ValueRepr::Vector { .. } // FIXME(rustc_scalable_vector): Scalable vectors are `Sized` while the // `sized_hierarchy` feature is not yet fully implemented. After `sized_hierarchy` is // fully implemented, scalable vectors will remain `Sized`, they just won't be // `const Sized` - whether `is_unsized` continues to return `false` at that point will // need to be revisited and will depend on what `is_unsized` is used for. - | ValueAbi::ScalableVector { .. } => false, - ValueAbi::Aggregate { sized } => !sized, + | ValueRepr::ScalableVector { .. } => false, + ValueRepr::Aggregate { sized } => !sized, } } } @@ -291,9 +440,8 @@ pub enum Scalar { }, Union { /// Unions never have niches, so there is no `valid_range`. - /// Even for unions, we need to use the correct registers for the kind of - /// values inside the union, so we keep the `Primitive` type around. - /// It is also used to compute the size of the scalar. + /// The `Primitive` type is kept to inform the backend representation + /// and to compute the size of the scalar. value: Primitive, }, } @@ -309,23 +457,18 @@ impl Scalar { } } -/// Fundamental unit of memory access and layout. +/// A primitive scalar type: integer, float, or pointer. #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Serialize)] pub enum Primitive { - /// The `bool` is the signedness of the `Integer` type. + /// An integer type with a given length and signedness. /// - /// One would think we would not care about such details this low down, - /// but some ABIs are described in terms of C types and ISAs where the - /// integer arithmetic is done on {sign,zero}-extended registers, e.g. - /// a negative integer passed by zero-extension will appear positive in - /// the callee, and most operations on it will produce the wrong values. - Int { - length: IntegerLength, - signed: bool, - }, - Float { - length: FloatLength, - }, + /// Signedness matters because some calling conventions require small integers + /// to be sign-extended or zero-extended when passed, and using the wrong + /// extension produces incorrect values in the callee. + Int { length: IntegerLength, signed: bool }, + /// A floating-point type with a given length. + Float { length: FloatLength }, + /// A pointer in the given address space. Pointer(AddressSpace), } diff --git a/compiler/rustc_public/src/unstable/convert/stable/abi.rs b/compiler/rustc_public/src/unstable/convert/stable/abi.rs index bbc7435a6c596..8d7bccf78a267 100644 --- a/compiler/rustc_public/src/unstable/convert/stable/abi.rs +++ b/compiler/rustc_public/src/unstable/convert/stable/abi.rs @@ -8,17 +8,17 @@ use rustc_public_bridge::Tables; use rustc_public_bridge::context::CompilerCtxt; use rustc_target::callconv; +use crate::IndexedVal; use crate::abi::{ - AddressSpace, ArgAbi, CallConvention, FieldsShape, FloatLength, FnAbi, IntegerLength, - IntegerType, Layout, LayoutShape, NumScalableVectors, PassMode, Primitive, ReprFlags, - ReprOptions, Scalar, TagEncoding, TyAndLayout, ValueAbi, VariantFields, VariantsShape, - WrappingRange, + AddressSpace, ArgAbi, ArgAttributes, ArgExtension, CallConvention, CastTarget, FieldsShape, + FloatLength, FnAbi, IntegerLength, IntegerType, Layout, LayoutShape, NumScalableVectors, + PassMode, Primitive, Reg, RegKind, ReprFlags, ReprOptions, Scalar, TagEncoding, TyAndLayout, + Uniform, ValueRepr, VariantFields, VariantsShape, WrappingRange, }; use crate::compiler_interface::BridgeTys; use crate::target::MachineSize as Size; use crate::ty::{Align, VariantIdx}; use crate::unstable::Stable; -use crate::{IndexedVal, opaque}; impl<'tcx> Stable<'tcx> for rustc_abi::VariantIdx { type T = VariantIdx; @@ -73,7 +73,7 @@ impl<'tcx> Stable<'tcx> for rustc_abi::LayoutData Stable<'tcx> for CanonAbi { impl<'tcx> Stable<'tcx> for callconv::PassMode { type T = PassMode; - fn stable(&self, _: &mut Tables<'_, BridgeTys>, _: &CompilerCtxt<'_, BridgeTys>) -> Self::T { + fn stable<'cx>( + &self, + tables: &mut Tables<'cx, BridgeTys>, + cx: &CompilerCtxt<'cx, BridgeTys>, + ) -> Self::T { match self { callconv::PassMode::Ignore => PassMode::Ignore, - callconv::PassMode::Direct(attr) => PassMode::Direct(opaque(attr)), + callconv::PassMode::Direct(attr) => PassMode::Direct(attr.stable(tables, cx)), callconv::PassMode::Pair(first, second) => { - PassMode::Pair(opaque(first), opaque(second)) + PassMode::Pair(first.stable(tables, cx), second.stable(tables, cx)) } callconv::PassMode::Cast { pad_i32, cast } => { - PassMode::Cast { pad_i32: *pad_i32, cast: opaque(cast) } + PassMode::Cast { pad_i32: *pad_i32, cast: cast.stable(tables, cx) } } callconv::PassMode::Indirect { attrs, meta_attrs, on_stack } => PassMode::Indirect { - attrs: opaque(attrs), - meta_attrs: opaque(meta_attrs), + attrs: attrs.stable(tables, cx), + meta_attrs: meta_attrs.map(|a| a.stable(tables, cx)), on_stack: *on_stack, }, } } } +impl<'tcx> Stable<'tcx> for callconv::CastTarget { + type T = CastTarget; + + fn stable<'cx>( + &self, + tables: &mut Tables<'cx, BridgeTys>, + cx: &CompilerCtxt<'cx, BridgeTys>, + ) -> Self::T { + CastTarget { + prefix: self.prefix.iter().map(|reg| reg.stable(tables, cx)).collect(), + rest_offset: self.rest_offset.map(|offset| Size::from_bits(offset.bits_usize())), + rest: self.rest.stable(tables, cx), + } + } +} + +impl<'tcx> Stable<'tcx> for callconv::Uniform { + type T = Uniform; + + fn stable<'cx>( + &self, + tables: &mut Tables<'cx, BridgeTys>, + cx: &CompilerCtxt<'cx, BridgeTys>, + ) -> Self::T { + Uniform { + unit: self.unit.stable(tables, cx), + total: Size::from_bits(self.total.bits_usize()), + is_consecutive: self.is_consecutive, + } + } +} + +impl<'tcx> Stable<'tcx> for rustc_abi::Reg { + type T = Reg; + + fn stable<'cx>( + &self, + _: &mut Tables<'cx, BridgeTys>, + _: &CompilerCtxt<'cx, BridgeTys>, + ) -> Self::T { + Reg { + kind: match self.kind { + rustc_abi::RegKind::Integer => RegKind::Integer, + rustc_abi::RegKind::Float => RegKind::Float, + rustc_abi::RegKind::Vector { .. } => RegKind::Vector, + }, + size: Size::from_bits(self.size.bits_usize()), + } + } +} + +impl<'tcx> Stable<'tcx> for callconv::ArgAttributes { + type T = ArgAttributes; + + fn stable<'cx>( + &self, + _: &mut Tables<'cx, BridgeTys>, + _: &CompilerCtxt<'cx, BridgeTys>, + ) -> Self::T { + ArgAttributes { + arg_ext: match self.arg_ext { + callconv::ArgExtension::None => ArgExtension::None, + callconv::ArgExtension::Zext => ArgExtension::Zext, + callconv::ArgExtension::Sext => ArgExtension::Sext, + }, + pointee_size: Size::from_bits(self.pointee_size.bits_usize()), + pointee_align: self.pointee_align.map(|a| a.bytes()), + } + } +} + impl<'tcx> Stable<'tcx> for rustc_abi::FieldsShape { type T = FieldsShape; @@ -262,7 +337,7 @@ impl<'tcx> Stable<'tcx> for rustc_abi::NumScalableVectors { } impl<'tcx> Stable<'tcx> for rustc_abi::BackendRepr { - type T = ValueAbi; + type T = ValueRepr; fn stable<'cx>( &self, @@ -270,25 +345,25 @@ impl<'tcx> Stable<'tcx> for rustc_abi::BackendRepr { cx: &CompilerCtxt<'cx, BridgeTys>, ) -> Self::T { match *self { - rustc_abi::BackendRepr::Scalar(scalar) => ValueAbi::Scalar(scalar.stable(tables, cx)), + rustc_abi::BackendRepr::Scalar(scalar) => ValueRepr::Scalar(scalar.stable(tables, cx)), rustc_abi::BackendRepr::ScalarPair { a: first, b: second, b_offset: second_offset } => { - ValueAbi::ScalarPair { + ValueRepr::ScalarPair { a: first.stable(tables, cx), b: second.stable(tables, cx), b_offset: second_offset.stable(tables, cx), } } rustc_abi::BackendRepr::SimdVector { element, count } => { - ValueAbi::Vector { element: element.stable(tables, cx), count } + ValueRepr::Vector { element: element.stable(tables, cx), count } } rustc_abi::BackendRepr::SimdScalableVector { element, count, number_of_vectors } => { - ValueAbi::ScalableVector { + ValueRepr::ScalableVector { element: element.stable(tables, cx), count, number_of_vectors: number_of_vectors.stable(tables, cx), } } - rustc_abi::BackendRepr::Memory { sized } => ValueAbi::Aggregate { sized }, + rustc_abi::BackendRepr::Memory { sized } => ValueRepr::Aggregate { sized }, } } } diff --git a/tests/ui-fulldeps/rustc_public/check_abi.rs b/tests/ui-fulldeps/rustc_public/check_abi.rs index d823e76b93cd0..5dfc2fce72560 100644 --- a/tests/ui-fulldeps/rustc_public/check_abi.rs +++ b/tests/ui-fulldeps/rustc_public/check_abi.rs @@ -15,8 +15,8 @@ extern crate rustc_middle; extern crate rustc_public; use rustc_public::abi::{ - ArgAbi, CallConvention, FieldsShape, IntegerLength, PassMode, Primitive, Scalar, ValueAbi, - VariantsShape, + ArgAbi, ArgExtension, CallConvention, FieldsShape, IntegerLength, PassMode, Primitive, Scalar, + ValueRepr, VariantsShape, }; use rustc_public::mir::MirVisitor; use rustc_public::mir::mono::Instance; @@ -106,7 +106,13 @@ fn check_ignore(abi: &ArgAbi) { /// Check the primitive argument: `primitive: char`. fn check_primitive(abi: &ArgAbi) { assert!(abi.ty.kind().is_char()); - assert_matches!(abi.mode, PassMode::Direct(_)); + let PassMode::Direct(ref attrs) = abi.mode else { + panic!("Expected PassMode::Direct for char, got: {:?}", abi.mode); + }; + // A char (32-bit) doesn't need sign/zero extension on most platforms. + assert_eq!(attrs.arg_extension(), ArgExtension::None); + // Direct arguments are not pointers, so no pointee alignment. + assert_eq!(attrs.pointee_align(), None); let layout = abi.layout.shape(); assert!(layout.is_sized()); assert!(!layout.is_1zst()); @@ -116,7 +122,14 @@ fn check_primitive(abi: &ArgAbi) { /// Check the return value: `Result`. fn check_result(abi: &ArgAbi) { assert!(abi.ty.kind().is_enum()); - assert_matches!(abi.mode, PassMode::Indirect { .. }); + let PassMode::Indirect { ref attrs, ref meta_attrs, on_stack } = abi.mode else { + panic!("Expected PassMode::Indirect for Result, got: {:?}", abi.mode); + }; + // Indirect arguments have a pointee alignment (the pointer must be aligned). + assert!(attrs.pointee_align().is_some()); + // Result is a sized type, so no metadata pointer. + assert!(meta_attrs.is_none()); + assert!(!on_stack); let layout = abi.layout.shape(); assert!(layout.is_sized()); assert_matches!(layout.fields, FieldsShape::Arbitrary { .. }); @@ -131,7 +144,7 @@ fn check_niche(abi: &ArgAbi) { assert!(layout.is_sized()); assert_eq!(layout.size.bytes(), 1); - let ValueAbi::Scalar(scalar) = layout.abi else { unreachable!() }; + let ValueRepr::Scalar(scalar) = layout.value_repr else { unreachable!() }; assert!(scalar.has_niche(&MachineInfo::target()), "Opps: {:?}", scalar); let Scalar::Initialized { value, valid_range } = scalar else { unreachable!() }; diff --git a/tests/ui-fulldeps/rustc_public/check_abi_cast.rs b/tests/ui-fulldeps/rustc_public/check_abi_cast.rs new file mode 100644 index 0000000000000..78a7bcccb94eb --- /dev/null +++ b/tests/ui-fulldeps/rustc_public/check_abi_cast.rs @@ -0,0 +1,218 @@ +//@ run-pass +//! Test that `PassMode::Cast` exposes the `CastTarget` structure for arguments and returns. +//! +//! When a platform ABI requires an aggregate to be passed in registers, rustc represents +//! this as `PassMode::Cast` with a `CastTarget` describing the register layout. This test +//! verifies that the public API exposes the register kinds, sizes, and that register +//! exhaustion correctly transitions arguments from `Cast` to `Indirect { on_stack: true }`. + +//@ ignore-stage1 +//@ ignore-cross-compile +//@ ignore-remote +//@ only-x86_64-unknown-linux-gnu + +#![feature(rustc_private)] + +extern crate rustc_driver; +extern crate rustc_interface; +extern crate rustc_middle; +#[macro_use] +extern crate rustc_public; + +use std::convert::TryFrom; +use std::io::Write; +use std::ops::ControlFlow; + +use rustc_public::abi::{CallConvention, PassMode, RegKind}; +use rustc_public::mir::mono::Instance; +use rustc_public::{CrateDef, ItemKind}; + +const CRATE_NAME: &str = "input"; + +fn test_abi_cast() -> ControlFlow<()> { + let items = rustc_public::all_local_items(); + + // Test Cast on argument: a small struct passed in registers. + let cast_arg_fn = items + .iter() + .find(|item| item.kind() == ItemKind::Fn && item.name() == "input::cast_arg") + .expect("missing cast_arg"); + + let instance = Instance::try_from(*cast_arg_fn).unwrap(); + let abi = instance.fn_abi().unwrap(); + assert_eq!(abi.conv, CallConvention::C); + match &abi.args[0].mode { + PassMode::Cast { pad_i32, cast } => { + assert!(!pad_i32); + assert_eq!(cast.rest.unit.kind, RegKind::Integer); + assert!(cast.rest.total.bits() > 0); + } + other => panic!("Expected PassMode::Cast for struct arg, got: {:?}", other), + } + + // Test Cast on return: a small struct returned via registers. + let cast_ret_fn = items + .iter() + .find(|item| item.kind() == ItemKind::Fn && item.name() == "input::cast_ret") + .expect("missing cast_ret"); + + let instance = Instance::try_from(*cast_ret_fn).unwrap(); + let abi = instance.fn_abi().unwrap(); + match &abi.ret.mode { + PassMode::Cast { pad_i32, cast } => { + assert!(!pad_i32); + // A 16-byte struct returned via integer registers. + assert!( + cast.rest.unit.kind == RegKind::Integer + || cast.prefix.iter().any(|r| r.kind == RegKind::Integer), + "Expected integer registers for return, got: {:?}", + cast + ); + } + other => panic!("Expected PassMode::Cast for struct return, got: {:?}", other), + } + + // Test Cast with mixed register kinds: struct with int + float fields. + let cast_mixed_fn = items + .iter() + .find(|item| item.kind() == ItemKind::Fn && item.name() == "input::cast_mixed") + .expect("missing cast_mixed"); + + let instance = Instance::try_from(*cast_mixed_fn).unwrap(); + let abi = instance.fn_abi().unwrap(); + match &abi.args[0].mode { + PassMode::Cast { pad_i32, cast } => { + assert!(!pad_i32); + // On x86_64 SysV, a struct { i64, f64 } uses prefix [Int] + rest Sse, + // or similar split. Just verify we have register info exposed. + let has_int = cast.prefix.iter().any(|r| r.kind == RegKind::Integer) + || cast.rest.unit.kind == RegKind::Integer; + let has_float = cast.prefix.iter().any(|r| r.kind == RegKind::Float) + || cast.rest.unit.kind == RegKind::Float; + assert!( + has_int && has_float, + "Expected both integer and float registers, got: {:?}", + cast + ); + } + other => panic!("Expected PassMode::Cast for mixed struct arg, got: {:?}", other), + } + + // Test multiple cast arguments in one function. + let cast_multi_fn = items + .iter() + .find(|item| item.kind() == ItemKind::Fn && item.name() == "input::cast_multi") + .expect("missing cast_multi"); + + let instance = Instance::try_from(*cast_multi_fn).unwrap(); + let abi = instance.fn_abi().unwrap(); + assert_eq!(abi.conv, CallConvention::C); + assert_eq!(abi.args.len(), 3); + // First arg: SmallStruct → Cast + assert!(matches!(&abi.args[0].mode, PassMode::Cast { .. })); + // Second arg: u64 → Direct (scalar) + assert!(matches!(&abi.args[1].mode, PassMode::Direct(_))); + // Third arg: MixedStruct → Cast with both int and float registers + match &abi.args[2].mode { + PassMode::Cast { cast, .. } => { + let has_int = cast.prefix.iter().any(|r| r.kind == RegKind::Integer) + || cast.rest.unit.kind == RegKind::Integer; + let has_float = cast.prefix.iter().any(|r| r.kind == RegKind::Float) + || cast.rest.unit.kind == RegKind::Float; + assert!(has_int && has_float, "Expected mixed registers, got: {:?}", cast); + } + other => panic!("Expected PassMode::Cast for third arg, got: {:?}", other), + } + + // Test stack spill: same type can have different PassModes when registers are exhausted. + // On x86_64 SysV, integer args use up to 6 registers (rdi, rsi, rdx, rcx, r8, r9). + // TwoWords uses 2 registers each, so the 4th one spills to the stack. + let cast_spill_fn = items + .iter() + .find(|item| item.kind() == ItemKind::Fn && item.name() == "input::cast_spill") + .expect("missing cast_spill"); + + let instance = Instance::try_from(*cast_spill_fn).unwrap(); + let abi = instance.fn_abi().unwrap(); + assert_eq!(abi.conv, CallConvention::C); + assert_eq!(abi.args.len(), 4); + // First three TwoWords fit in registers (2 regs each = 6 total) → Cast + for i in 0..3 { + assert!( + matches!(&abi.args[i].mode, PassMode::Cast { .. }), + "Expected arg {} to be Cast, got: {:?}", + i, + abi.args[i].mode + ); + } + // Fourth TwoWords has no registers left → Indirect (on stack) + assert!( + matches!(&abi.args[3].mode, PassMode::Indirect { on_stack: true, .. }), + "Expected arg 3 to be Indirect on stack, got: {:?}", + abi.args[3].mode + ); + + ControlFlow::Continue(()) +} + +fn main() { + let path = "pass_mode_input.rs"; + generate_input(&path).unwrap(); + let args = &[ + "rustc".to_string(), + "-Cpanic=abort".to_string(), + "--crate-type=lib".to_string(), + "--crate-name".to_string(), + CRATE_NAME.to_string(), + path.to_string(), + ]; + run!(args, test_abi_cast).unwrap(); +} + +fn generate_input(path: &str) -> std::io::Result<()> { + let mut file = std::fs::File::create(path)?; + write!( + file, + r#" + #[repr(C)] + pub struct SmallStruct {{ + pub a: u8, + pub b: u16, + pub c: u32, + }} + + #[repr(C)] + pub struct TwoWords {{ + pub a: u64, + pub b: u64, + }} + + #[repr(C)] + pub struct MixedStruct {{ + pub i: i64, + pub f: f64, + }} + + pub extern "C" fn cast_arg(s: SmallStruct) -> u64 {{ + (s.a as u64) + (s.b as u64) + (s.c as u64) + }} + + pub extern "C" fn cast_ret(x: u64) -> TwoWords {{ + TwoWords {{ a: x, b: x + 1 }} + }} + + pub extern "C" fn cast_mixed(s: MixedStruct) -> f64 {{ + (s.i as f64) + s.f + }} + + pub extern "C" fn cast_multi(s: SmallStruct, x: u64, m: MixedStruct) -> u64 {{ + (s.a as u64) + x + (m.i as u64) + }} + + pub extern "C" fn cast_spill(a: TwoWords, b: TwoWords, c: TwoWords, d: TwoWords) -> u64 {{ + a.a + b.a + c.a + d.a + }} + "# + )?; + Ok(()) +}