Skip to content

Replace ad-hoc ABI "adjustments" with an AbiMap to CanonAbi #141569

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jun 4, 2025
136 changes: 136 additions & 0 deletions compiler/rustc_abi/src/canon_abi.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
use std::fmt;

#[cfg(feature = "nightly")]
use rustc_macros::HashStable_Generic;

use crate::ExternAbi;

/// Calling convention to determine codegen
///
/// CanonAbi erases certain distinctions ExternAbi preserves, but remains target-dependent.
/// There are still both target-specific variants and aliasing variants, though much fewer.
/// The reason for this step is the frontend may wish to show an ExternAbi but implement that ABI
/// using a different ABI than the string per se, or describe irrelevant differences, e.g.
/// - extern "system"
/// - extern "cdecl"
/// - extern "C-unwind"
/// In that sense, this erases mere syntactic distinctions to create a canonical *directive*,
/// rather than picking the "actual" ABI.
#[derive(Copy, Clone, Debug)]
#[derive(PartialOrd, Ord, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "nightly", derive(HashStable_Generic))]
pub enum CanonAbi {
// NOTE: the use of nested variants for some ABIs is for many targets they don't matter,
// and this pushes the complexity of their reasoning to target-specific code,
// allowing a `match` to easily exhaustively ignore these subcategories of variants.
// Otherwise it is very tempting to avoid matching exhaustively!
C,
Rust,
RustCold,

/// ABIs relevant to 32-bit Arm targets
Arm(ArmCall),
/// ABI relevant to GPUs: the entry point for a GPU kernel
GpuKernel,

/// ABIs relevant to bare-metal interrupt targets
// FIXME(workingjubilee): a particular reason for this nesting is we might not need these?
// interrupt ABIs should have the same properties:
// - uncallable by Rust calls, as LLVM rejects it in most cases
// - uses a preserve-all-registers *callee* convention
// - should always return `-> !` (effectively... it can't use normal `ret`)
// what differs between targets is
// - allowed arguments: x86 differs slightly, having 2-3 arguments which are handled magically
// - may need special prologues/epilogues for some interrupts, without affecting "call ABI"
Interrupt(InterruptKind),

/// ABIs relevant to Windows or x86 targets
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "to Windows" part here should disappear when #141435 becomes a hard error, right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably.

X86(X86Call),
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😍

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah so that decision actually does look right to someone who is not inside my head? Great.


impl fmt::Display for CanonAbi {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.to_erased_extern_abi().as_str().fmt(f)
}
}

impl CanonAbi {
/// convert to the ExternAbi that *shares a string* with this CanonAbi
///
/// A target-insensitive mapping of CanonAbi to ExternAbi, convenient for "forwarding" impls.
/// Importantly, the set of CanonAbi values is a logical *subset* of ExternAbi values,
/// so this is injective: if you take an ExternAbi to a CanonAbi and back, you have lost data.
const fn to_erased_extern_abi(self) -> ExternAbi {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this is now the only odd new translation function left here, I think? I'm wondering how we can eradicate this one as well. It's only used for printing. Where do we care about that? Is it only that Miri error message? Ideally that would have a way of recovering the original ExternAbi...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My earlier versions of this had a to_denormalizing_map, yes, which would allow naming all the "{abi}"s which map to the CanonAbi of interest.

Copy link
Member Author

@workingjubilee workingjubilee May 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be happy to pursue eliminating this in a followup if the PR otherwise looks good in its current state except for this function daring to exist (well, and needing a rebase-squash badly). that Miri error message would indeed be a relatively important part of any further changes and I think it would want a little more conversation around it.

match self {
CanonAbi::C => ExternAbi::C { unwind: false },
CanonAbi::Rust => ExternAbi::Rust,
CanonAbi::RustCold => ExternAbi::RustCold,
CanonAbi::Arm(arm_call) => match arm_call {
ArmCall::Aapcs => ExternAbi::Aapcs { unwind: false },
ArmCall::CCmseNonSecureCall => ExternAbi::CCmseNonSecureCall,
ArmCall::CCmseNonSecureEntry => ExternAbi::CCmseNonSecureEntry,
},
CanonAbi::GpuKernel => ExternAbi::GpuKernel,
CanonAbi::Interrupt(interrupt_kind) => match interrupt_kind {
InterruptKind::Avr => ExternAbi::AvrInterrupt,
InterruptKind::AvrNonBlocking => ExternAbi::AvrNonBlockingInterrupt,
InterruptKind::Msp430 => ExternAbi::Msp430Interrupt,
InterruptKind::RiscvMachine => ExternAbi::RiscvInterruptM,
InterruptKind::RiscvSupervisor => ExternAbi::RiscvInterruptS,
InterruptKind::X86 => ExternAbi::X86Interrupt,
},
CanonAbi::X86(x86_call) => match x86_call {
X86Call::Fastcall => ExternAbi::Fastcall { unwind: false },
X86Call::Stdcall => ExternAbi::Stdcall { unwind: false },
X86Call::SysV64 => ExternAbi::SysV64 { unwind: false },
X86Call::Thiscall => ExternAbi::Thiscall { unwind: false },
X86Call::Vectorcall => ExternAbi::Vectorcall { unwind: false },
X86Call::Win64 => ExternAbi::Win64 { unwind: false },
},
}
}
}

/// Callee codegen for interrupts
///
/// This is named differently from the "Call" enums because it is different:
/// these "ABI" differences are not relevant to callers, since there is "no caller".
/// These only affect callee codegen. making their categorization as distinct ABIs a bit peculiar.
#[derive(Copy, Clone, Debug)]
#[derive(PartialOrd, Ord, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "nightly", derive(HashStable_Generic))]
pub enum InterruptKind {
Avr,
AvrNonBlocking,
Msp430,
RiscvMachine,
RiscvSupervisor,
X86,
}

/// ABIs defined for x86-{32,64}
///
/// One of SysV64 or Win64 may alias the C ABI, and arguably Win64 is cross-platform now?
#[derive(Clone, Copy, Debug)]
#[derive(PartialOrd, Ord, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "nightly", derive(HashStable_Generic))]
pub enum X86Call {
/// "fastcall" has both GNU and Windows variants
Fastcall,
/// "stdcall" has both GNU and Windows variants
Stdcall,
SysV64,
Thiscall,
Vectorcall,
Win64,
}

/// ABIs defined for 32-bit Arm
#[derive(Copy, Clone, Debug)]
#[derive(PartialOrd, Ord, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "nightly", derive(HashStable_Generic))]
pub enum ArmCall {
Aapcs,
CCmseNonSecureCall,
CCmseNonSecureEntry,
}
7 changes: 2 additions & 5 deletions compiler/rustc_abi/src/extern_abi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use rustc_data_structures::stable_hasher::{HashStable, StableHasher, StableOrd};
#[cfg(feature = "nightly")]
use rustc_macros::{Decodable, Encodable};

use crate::AbiFromStrErr;

#[cfg(test)]
mod tests;

Expand Down Expand Up @@ -99,11 +101,6 @@ macro_rules! abi_impls {
}
}

#[derive(Debug)]
pub enum AbiFromStrErr {
Unknown,
}

abi_impls! {
ExternAbi = {
C { unwind: false } =><= "C",
Expand Down
13 changes: 11 additions & 2 deletions compiler/rustc_abi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,14 @@ use rustc_index::{Idx, IndexSlice, IndexVec};
use rustc_macros::{Decodable_NoContext, Encodable_NoContext, HashStable_Generic};

mod callconv;
mod canon_abi;
mod extern_abi;
mod layout;
#[cfg(test)]
mod tests;

mod extern_abi;

pub use callconv::{Heterogeneous, HomogeneousAggregate, Reg, RegKind};
pub use canon_abi::{ArmCall, CanonAbi, InterruptKind, X86Call};
pub use extern_abi::{ExternAbi, all_names};
#[cfg(feature = "nightly")]
pub use layout::{FIRST_VARIANT, FieldIdx, Layout, TyAbiInterface, TyAndLayout, VariantIdx};
Expand Down Expand Up @@ -1895,3 +1896,11 @@ pub enum StructKind {
/// A univariant, but with a prefix of an arbitrary size & alignment (e.g., enum tag).
Prefixed(Size, Align),
}

#[derive(Clone, Debug)]
pub enum AbiFromStrErr {
/// not a known ABI
Unknown,
/// no "-unwind" variant can be used here
NoExplicitUnwind,
}
49 changes: 22 additions & 27 deletions compiler/rustc_codegen_cranelift/src/abi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use std::mem;
use cranelift_codegen::ir::{ArgumentPurpose, SigRef};
use cranelift_codegen::isa::CallConv;
use cranelift_module::ModuleError;
use rustc_abi::ExternAbi;
use rustc_abi::{CanonAbi, ExternAbi, X86Call};
use rustc_codegen_ssa::base::is_call_from_compiler_builtins_to_upstream_monomorphization;
use rustc_codegen_ssa::errors::CompilerBuiltinsCannotCall;
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
Expand All @@ -19,7 +19,7 @@ use rustc_middle::ty::layout::FnAbiOf;
use rustc_middle::ty::print::with_no_trimmed_paths;
use rustc_session::Session;
use rustc_span::source_map::Spanned;
use rustc_target::callconv::{Conv, FnAbi, PassMode};
use rustc_target::callconv::{FnAbi, PassMode};
use smallvec::SmallVec;

use self::pass_mode::*;
Expand All @@ -42,32 +42,27 @@ fn clif_sig_from_fn_abi<'tcx>(
Signature { params, returns, call_conv }
}

pub(crate) fn conv_to_call_conv(sess: &Session, c: Conv, default_call_conv: CallConv) -> CallConv {
pub(crate) fn conv_to_call_conv(
sess: &Session,
c: CanonAbi,
default_call_conv: CallConv,
) -> CallConv {
match c {
Conv::Rust | Conv::C => default_call_conv,
Conv::Cold | Conv::PreserveMost | Conv::PreserveAll => CallConv::Cold,
Conv::X86_64SysV => CallConv::SystemV,
Conv::X86_64Win64 => CallConv::WindowsFastcall,

// Should already get a back compat warning
Conv::X86Fastcall | Conv::X86Stdcall | Conv::X86ThisCall | Conv::X86VectorCall => {
default_call_conv
}

Conv::X86Intr | Conv::RiscvInterrupt { .. } => {
sess.dcx().fatal(format!("interrupt call conv {c:?} not yet implemented"))
CanonAbi::Rust | CanonAbi::C => default_call_conv,
CanonAbi::RustCold => CallConv::Cold,

CanonAbi::X86(x86_call) => match x86_call {
X86Call::SysV64 => CallConv::SystemV,
X86Call::Win64 => CallConv::WindowsFastcall,
// Should already get a back compat warning
_ => default_call_conv,
},

CanonAbi::Interrupt(_) | CanonAbi::Arm(_) => {
sess.dcx().fatal("call conv {c:?} is not yet implemented")
}

Conv::ArmAapcs => sess.dcx().fatal("aapcs call conv not yet implemented"),
Conv::CCmseNonSecureCall => {
sess.dcx().fatal("C-cmse-nonsecure-call call conv is not yet implemented");
}
Conv::CCmseNonSecureEntry => {
sess.dcx().fatal("C-cmse-nonsecure-entry call conv is not yet implemented");
}

Conv::Msp430Intr | Conv::GpuKernel | Conv::AvrInterrupt | Conv::AvrNonBlockingInterrupt => {
unreachable!("tried to use {c:?} call conv which only exists on an unsupported target");
CanonAbi::GpuKernel => {
unreachable!("tried to use {c:?} call conv which only exists on an unsupported target")
}
}
}
Expand Down Expand Up @@ -610,7 +605,7 @@ pub(crate) fn codegen_terminator_call<'tcx>(
target: CallTarget,
call_args: &mut Vec<Value>,
) {
if fn_abi.conv != Conv::C {
if fn_abi.conv != CanonAbi::C {
fx.tcx.dcx().span_fatal(
source_info.span,
format!("Variadic call for non-C abi {:?}", fn_abi.conv),
Expand Down
73 changes: 28 additions & 45 deletions compiler/rustc_codegen_gcc/src/abi.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#[cfg(feature = "master")]
use gccjit::FnAttribute;
use gccjit::{ToLValue, ToRValue, Type};
use rustc_abi::{Reg, RegKind};
use rustc_abi::{ArmCall, CanonAbi, InterruptKind, Reg, RegKind, X86Call};
use rustc_codegen_ssa::traits::{AbiBuilderMethods, BaseTypeCodegenMethods};
use rustc_data_structures::fx::FxHashSet;
use rustc_middle::bug;
Expand All @@ -10,8 +10,6 @@ use rustc_middle::ty::layout::LayoutOf;
#[cfg(feature = "master")]
use rustc_session::config;
use rustc_target::callconv::{ArgAttributes, CastTarget, FnAbi, PassMode};
#[cfg(feature = "master")]
use rustc_target::callconv::{Conv, RiscvInterruptKind};

use crate::builder::Builder;
use crate::context::CodegenCx;
Expand Down Expand Up @@ -239,29 +237,16 @@ impl<'gcc, 'tcx> FnAbiGccExt<'gcc, 'tcx> for FnAbi<'tcx, Ty<'tcx>> {
}

#[cfg(feature = "master")]
pub fn conv_to_fn_attribute<'gcc>(conv: Conv, arch: &str) -> Option<FnAttribute<'gcc>> {
pub fn conv_to_fn_attribute<'gcc>(conv: CanonAbi, arch: &str) -> Option<FnAttribute<'gcc>> {
let attribute = match conv {
Conv::C | Conv::Rust => return None,
Conv::CCmseNonSecureCall => {
if arch == "arm" {
FnAttribute::ArmCmseNonsecureCall
} else {
return None;
}
}
Conv::CCmseNonSecureEntry => {
if arch == "arm" {
FnAttribute::ArmCmseNonsecureEntry
} else {
return None;
}
}
Conv::Cold => FnAttribute::Cold,
// NOTE: the preserve attributes are not yet implemented in GCC:
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=110899
Conv::PreserveMost => return None,
Conv::PreserveAll => return None,
Conv::GpuKernel => {
CanonAbi::C | CanonAbi::Rust => return None,
CanonAbi::Arm(arm_call) => match arm_call {
ArmCall::CCmseNonSecureCall => FnAttribute::ArmCmseNonsecureCall,
ArmCall::CCmseNonSecureEntry => FnAttribute::ArmCmseNonsecureEntry,
ArmCall::Aapcs => FnAttribute::ArmPcs("aapcs"),
},
CanonAbi::RustCold => FnAttribute::Cold,
CanonAbi::GpuKernel => {
if arch == "amdgpu" {
FnAttribute::GcnAmdGpuHsaKernel
} else if arch == "nvptx64" {
Expand All @@ -271,26 +256,24 @@ pub fn conv_to_fn_attribute<'gcc>(conv: Conv, arch: &str) -> Option<FnAttribute<
}
}
// TODO(antoyo): check if those AVR attributes are mapped correctly.
Conv::AvrInterrupt => FnAttribute::AvrSignal,
Conv::AvrNonBlockingInterrupt => FnAttribute::AvrInterrupt,
Conv::ArmAapcs => FnAttribute::ArmPcs("aapcs"),
Conv::Msp430Intr => FnAttribute::Msp430Interrupt,
Conv::RiscvInterrupt { kind } => {
let kind = match kind {
RiscvInterruptKind::Machine => "machine",
RiscvInterruptKind::Supervisor => "supervisor",
};
FnAttribute::RiscvInterrupt(kind)
}
Conv::X86Fastcall => FnAttribute::X86FastCall,
Conv::X86Intr => FnAttribute::X86Interrupt,
Conv::X86Stdcall => FnAttribute::X86Stdcall,
Conv::X86ThisCall => FnAttribute::X86ThisCall,
// NOTE: the vectorcall calling convention is not yet implemented in GCC:
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=89485
Conv::X86VectorCall => return None,
Conv::X86_64SysV => FnAttribute::X86SysvAbi,
Conv::X86_64Win64 => FnAttribute::X86MsAbi,
CanonAbi::Interrupt(interrupt_kind) => match interrupt_kind {
InterruptKind::Avr => FnAttribute::AvrSignal,
InterruptKind::AvrNonBlocking => FnAttribute::AvrInterrupt,
InterruptKind::Msp430 => FnAttribute::Msp430Interrupt,
InterruptKind::RiscvMachine => FnAttribute::RiscvInterrupt("machine"),
InterruptKind::RiscvSupervisor => FnAttribute::RiscvInterrupt("supervisor"),
InterruptKind::X86 => FnAttribute::X86Interrupt,
},
CanonAbi::X86(x86_call) => match x86_call {
X86Call::Fastcall => FnAttribute::X86FastCall,
X86Call::Stdcall => FnAttribute::X86Stdcall,
X86Call::Thiscall => FnAttribute::X86ThisCall,
// // NOTE: the vectorcall calling convention is not yet implemented in GCC:
// // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=89485
X86Call::Vectorcall => return None,
X86Call::SysV64 => FnAttribute::X86SysvAbi,
X86Call::Win64 => FnAttribute::X86MsAbi,
},
};
Some(attribute)
}
6 changes: 3 additions & 3 deletions compiler/rustc_codegen_gcc/src/int.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
//! 128-bit integers on 32-bit platforms and thus require to be handled manually.

use gccjit::{BinaryOp, ComparisonOp, FunctionType, Location, RValue, ToRValue, Type, UnaryOp};
use rustc_abi::{Endian, ExternAbi};
use rustc_abi::{CanonAbi, Endian, ExternAbi};
use rustc_codegen_ssa::common::{IntPredicate, TypeKind};
use rustc_codegen_ssa::traits::{BackendTypes, BaseTypeCodegenMethods, BuilderMethods, OverflowOp};
use rustc_middle::ty::{self, Ty};
use rustc_target::callconv::{ArgAbi, ArgAttributes, Conv, FnAbi, PassMode};
use rustc_target::callconv::{ArgAbi, ArgAttributes, FnAbi, PassMode};

use crate::builder::{Builder, ToGccComp};
use crate::common::{SignType, TypeReflection};
Expand Down Expand Up @@ -397,7 +397,7 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> {
ret: arg_abi,
c_variadic: false,
fixed_count: 3,
conv: Conv::C,
conv: CanonAbi::C,
can_unwind: false,
};
fn_abi.adjust_for_foreign_abi(self.cx, ExternAbi::C { unwind: false });
Expand Down
Loading
Loading