From c978c0007873c4310174388b6e70da7a6940cdb0 Mon Sep 17 00:00:00 2001 From: AkshayK Date: Tue, 18 Aug 2026 12:08:13 -0400 Subject: [PATCH 1/2] [ARM] Identify the general-purpose and argument registers Declare the GeneralPurposeRegisters and FixedRegisters categories and implement isArgumentRegister. The generic defaults are all false, so the shared call-used-register computation selected nothing on ARM for any of the "-gpr" or "-arg" modes. isArgumentRegister asks the lowering which CCAssignFn the function was assigned rather than re-deriving the convention from the ABI, the FPU, Thumb-1 and varargs, and unions a delegating convention's register list with the delegated-to one's. No observable change: prologue and epilogue insertion is the only generic consumer, and ARM still refuses the request until the emission lands. This is trailofbits/vspells-ct-internal-notes#52, under the umbrella trailofbits/vspells-ct-internal-notes#30. --- llvm/lib/Target/ARM/ARMBaseRegisterInfo.cpp | 52 +++++++++++++++++++++ llvm/lib/Target/ARM/ARMBaseRegisterInfo.h | 2 + llvm/lib/Target/ARM/ARMRegisterInfo.td | 18 +++++++ 3 files changed, 72 insertions(+) diff --git a/llvm/lib/Target/ARM/ARMBaseRegisterInfo.cpp b/llvm/lib/Target/ARM/ARMBaseRegisterInfo.cpp index c581df8cf892b..1e87d8ddf9e68 100644 --- a/llvm/lib/Target/ARM/ARMBaseRegisterInfo.cpp +++ b/llvm/lib/Target/ARM/ARMBaseRegisterInfo.cpp @@ -13,7 +13,9 @@ #include "ARMBaseRegisterInfo.h" #include "ARM.h" #include "ARMBaseInstrInfo.h" +#include "ARMCallingConv.h" #include "ARMFrameLowering.h" +#include "ARMISelLowering.h" #include "ARMMachineFunctionInfo.h" #include "ARMSubtarget.h" #include "MCTargetDesc/ARMAddressingModes.h" @@ -54,6 +56,9 @@ using namespace llvm; +#define GET_CC_REGISTER_LISTS +#include "ARMGenCallingConv.inc" + ARMBaseRegisterInfo::ARMBaseRegisterInfo() : ARMGenRegisterInfo(ARM::LR, 0, 0, ARM::PC) { ARM_MC::initLLVMToCVRegMapping(this); @@ -279,6 +284,53 @@ bool ARMBaseRegisterInfo::isInlineAsmReadOnlyReg(const MachineFunction &MF, return Reserved.test(PhysReg.id()); } +bool ARMBaseRegisterInfo::isArgumentRegister(const MachineFunction &MF, + MCRegister PhysReg) const { + // Ask the lowering which convention this function was assigned rather than + // deriving it again here. Which one a calling convention resolves to depends + // on the ABI, on whether the subtarget has floating-point registers, on + // Thumb-1, and on varargs, and a second copy of that reasoning would be one + // that can disagree with the one the arguments were actually assigned by. + const Function &F = MF.getFunction(); + const ARMTargetLowering *TLI = MF.getSubtarget().getTargetLowering(); + CCAssignFn *Fn = TLI->CCAssignFnForCall(F.getCallingConv(), F.isVarArg()); + + auto HasReg = [PhysReg](ArrayRef RegList) { + return llvm::is_contained(RegList, PhysReg); + }; + + // Registers a convention uses only for a swift-self or swift-error argument + // are emitted as a list of their own, and belong to the convention only when + // the function is one that can carry those arguments. + CallingConv::ID CC = F.getCallingConv(); + const bool IsSwift = + CC == CallingConv::Swift || CC == CallingConv::SwiftTail; + + // A convention that delegates has a list per definition, and the delegated-to + // definition's list is as much a part of it as its own. + if (Fn == CC_ARM_APCS) + return HasReg(CC_ARM_APCS_ArgRegs) || + (IsSwift && HasReg(CC_ARM_APCS_Swift_ArgRegs)); + if (Fn == FastCC_ARM_APCS) + return HasReg(FastCC_ARM_APCS_ArgRegs) || HasReg(CC_ARM_APCS_ArgRegs); + if (Fn == CC_ARM_APCS_GHC) + return HasReg(CC_ARM_APCS_GHC_ArgRegs); + if (Fn == CC_ARM_AAPCS) + return HasReg(CC_ARM_AAPCS_ArgRegs) || + HasReg(CC_ARM_AAPCS_Common_ArgRegs) || + (IsSwift && HasReg(CC_ARM_AAPCS_Swift_ArgRegs)); + if (Fn == CC_ARM_AAPCS_VFP) + return HasReg(CC_ARM_AAPCS_VFP_ArgRegs) || + HasReg(CC_ARM_AAPCS_Common_ArgRegs) || + (IsSwift && HasReg(CC_ARM_AAPCS_VFP_Swift_ArgRegs)); + if (Fn == CC_ARM_Win32_CFGuard_Check) + return HasReg(CC_ARM_Win32_CFGuard_Check_ArgRegs); + + // CCAssignFnForCall answers with one of the above or reports the convention + // as unsupported before returning, so there is no fourth possibility. + llvm_unreachable("unhandled ARM calling convention"); +} + const TargetRegisterClass * ARMBaseRegisterInfo::getLargestLegalSuperClass(const TargetRegisterClass *RC, const MachineFunction &MF) const { diff --git a/llvm/lib/Target/ARM/ARMBaseRegisterInfo.h b/llvm/lib/Target/ARM/ARMBaseRegisterInfo.h index 1f0681bf2ee3e..1434514e52ad5 100644 --- a/llvm/lib/Target/ARM/ARMBaseRegisterInfo.h +++ b/llvm/lib/Target/ARM/ARMBaseRegisterInfo.h @@ -89,6 +89,8 @@ class ARMBaseRegisterInfo : public ARMGenRegisterInfo { MCRegister PhysReg) const override; bool isInlineAsmReadOnlyReg(const MachineFunction &MF, MCRegister PhysReg) const override; + bool isArgumentRegister(const MachineFunction &MF, + MCRegister PhysReg) const override; const TargetRegisterClass * getPointerRegClass(unsigned Kind = 0) const override; diff --git a/llvm/lib/Target/ARM/ARMRegisterInfo.td b/llvm/lib/Target/ARM/ARMRegisterInfo.td index e8c2b3b822e65..571bfe3be7ed5 100644 --- a/llvm/lib/Target/ARM/ARMRegisterInfo.td +++ b/llvm/lib/Target/ARM/ARMRegisterInfo.td @@ -652,3 +652,21 @@ def DQuadSpc : RegisterClass<"ARM", [v4i64], 64, (add Tuples3DSpc)>; // FP context payload def FPCXTRegs : RegisterClass<"ARM", [i32], 32, (add FPCXTNS)>; + +//===----------------------------------------------------------------------===// +// Register categories. +// + +// GPR spans R0-R12, SP, LR and PC. SP and PC are reserved and so never reach +// the consumers of this category, which all start from the allocatable set. +def GeneralPurposeRegisters : RegisterCategory<[GPR]>; + +// Registers that hold status rather than data, and that a request to clear the +// call-used registers therefore does not name. CPSR and the FPSCR halves are +// condition flags, which are cleared by a step of their own and not by this +// one. FPCXTNS holds the floating-point context the security extension saves +// and restores across a non-secure call; overwriting it would destroy state +// the caller is entitled to get back, which is not what clearing a call-used +// register means. VPR is deliberately absent: an MVE predicate is data. +def FixedRegisters : RegisterCategory<[CCR, cl_FPSCR_NZCV, FP_STATUS_REGS, + FPCXTRegs]>; From 03dec1151157f5041db386cd4a13d9b2ee4e6329 Mon Sep 17 00:00:00 2001 From: AkshayK Date: Thu, 17 Sep 2026 16:51:41 -0400 Subject: [PATCH 2/2] [ARM] Recognize Swift argument attributes across calling conventions --- llvm/lib/CodeGen/PrologEpilogInserter.cpp | 4 + llvm/lib/Target/ARM/ARMBaseRegisterInfo.cpp | 37 +++++---- llvm/lib/Target/ARM/ARMRegisterInfo.td | 11 ++- .../ARM/zero-call-used-regs-unsupported.ll | 15 ++++ .../Target/ARM/ARMRegisterInfoTest.cpp | 75 +++++++++++++++++++ llvm/unittests/Target/ARM/CMakeLists.txt | 1 + 6 files changed, 122 insertions(+), 21 deletions(-) create mode 100644 llvm/unittests/Target/ARM/ARMRegisterInfoTest.cpp diff --git a/llvm/lib/CodeGen/PrologEpilogInserter.cpp b/llvm/lib/CodeGen/PrologEpilogInserter.cpp index ce828db933cb3..0a951fdc72bee 100644 --- a/llvm/lib/CodeGen/PrologEpilogInserter.cpp +++ b/llvm/lib/CodeGen/PrologEpilogInserter.cpp @@ -1783,6 +1783,10 @@ PEIImpl::planClearRegisters(MachineFunction &MF, continue; MCRegister Reg = MO.getReg(); + // TODO: Mark allocatable subregisters used as well. ARM pair operands + // such as R0_R1 must mark R0 and R1 so used-gpr can select the scalar + // components without classifying GPRPair as a general-purpose class. + // Add coverage for pair-only uses before enabling ARM register clearing. if (AllocatableSet[Reg.id()]) UsedRegs.set(Reg.id()); } diff --git a/llvm/lib/Target/ARM/ARMBaseRegisterInfo.cpp b/llvm/lib/Target/ARM/ARMBaseRegisterInfo.cpp index 1e87d8ddf9e68..d4806e803f713 100644 --- a/llvm/lib/Target/ARM/ARMBaseRegisterInfo.cpp +++ b/llvm/lib/Target/ARM/ARMBaseRegisterInfo.cpp @@ -292,42 +292,49 @@ bool ARMBaseRegisterInfo::isArgumentRegister(const MachineFunction &MF, // Thumb-1, and on varargs, and a second copy of that reasoning would be one // that can disagree with the one the arguments were actually assigned by. const Function &F = MF.getFunction(); - const ARMTargetLowering *TLI = MF.getSubtarget().getTargetLowering(); + const ARMTargetLowering *TLI = + MF.getSubtarget().getTargetLowering(); CCAssignFn *Fn = TLI->CCAssignFnForCall(F.getCallingConv(), F.isVarArg()); auto HasReg = [PhysReg](ArrayRef RegList) { return llvm::is_contained(RegList, PhysReg); }; - // Registers a convention uses only for a swift-self or swift-error argument - // are emitted as a list of their own, and belong to the convention only when - // the function is one that can carry those arguments. + // Swift argument registers are emitted as a separate list. The attributes + // can also select these registers under non-Swift calling conventions. CallingConv::ID CC = F.getCallingConv(); - const bool IsSwift = - CC == CallingConv::Swift || CC == CallingConv::SwiftTail; - - // A convention that delegates has a list per definition, and the delegated-to - // definition's list is as much a part of it as its own. + const bool IsSwiftArgument = + CC == CallingConv::Swift || CC == CallingConv::SwiftTail || + (PhysReg == ARM::R10 && + F.getAttributes().hasAttrSomewhere(Attribute::SwiftSelf)) || + (PhysReg == ARM::R8 && + F.getAttributes().hasAttrSomewhere(Attribute::SwiftError)); + + // Generated argument lists currently omit registers from delegated + // conventions, so explicitly include those lists here. For example, + // CC_ARM_AAPCS_ArgRegs contains only R12; CC_ARM_AAPCS_Common_ArgRegs + // supplies R0-R3. Removing the union would lose ordinary AAPCS arguments. if (Fn == CC_ARM_APCS) return HasReg(CC_ARM_APCS_ArgRegs) || - (IsSwift && HasReg(CC_ARM_APCS_Swift_ArgRegs)); + (IsSwiftArgument && HasReg(CC_ARM_APCS_Swift_ArgRegs)); if (Fn == FastCC_ARM_APCS) - return HasReg(FastCC_ARM_APCS_ArgRegs) || HasReg(CC_ARM_APCS_ArgRegs); + return HasReg(FastCC_ARM_APCS_ArgRegs) || HasReg(CC_ARM_APCS_ArgRegs) || + (IsSwiftArgument && HasReg(CC_ARM_APCS_Swift_ArgRegs)); if (Fn == CC_ARM_APCS_GHC) return HasReg(CC_ARM_APCS_GHC_ArgRegs); if (Fn == CC_ARM_AAPCS) return HasReg(CC_ARM_AAPCS_ArgRegs) || HasReg(CC_ARM_AAPCS_Common_ArgRegs) || - (IsSwift && HasReg(CC_ARM_AAPCS_Swift_ArgRegs)); + (IsSwiftArgument && HasReg(CC_ARM_AAPCS_Swift_ArgRegs)); if (Fn == CC_ARM_AAPCS_VFP) return HasReg(CC_ARM_AAPCS_VFP_ArgRegs) || HasReg(CC_ARM_AAPCS_Common_ArgRegs) || - (IsSwift && HasReg(CC_ARM_AAPCS_VFP_Swift_ArgRegs)); + (IsSwiftArgument && HasReg(CC_ARM_AAPCS_VFP_Swift_ArgRegs)); if (Fn == CC_ARM_Win32_CFGuard_Check) return HasReg(CC_ARM_Win32_CFGuard_Check_ArgRegs); - // CCAssignFnForCall answers with one of the above or reports the convention - // as unsupported before returning, so there is no fourth possibility. + // The selector returns one of the handled functions or terminates with an + // unsupported-convention error. llvm_unreachable("unhandled ARM calling convention"); } diff --git a/llvm/lib/Target/ARM/ARMRegisterInfo.td b/llvm/lib/Target/ARM/ARMRegisterInfo.td index 571bfe3be7ed5..469a7a0a2c642 100644 --- a/llvm/lib/Target/ARM/ARMRegisterInfo.td +++ b/llvm/lib/Target/ARM/ARMRegisterInfo.td @@ -657,14 +657,13 @@ def FPCXTRegs : RegisterClass<"ARM", [i32], 32, (add FPCXTNS)>; // Register categories. // -// GPR spans R0-R12, SP, LR and PC. SP and PC are reserved and so never reach -// the consumers of this category, which all start from the allocatable set. +// GPR spans R0-R12, SP, LR and PC. PEI selects register-clearing candidates +// from the allocatable set, which excludes the reserved SP and PC registers. def GeneralPurposeRegisters : RegisterCategory<[GPR]>; -// Registers that hold status rather than data, and that a request to clear the -// call-used registers therefore does not name. CPSR and the FPSCR halves are -// condition flags, which are cleared by a step of their own and not by this -// one. FPCXTNS holds the floating-point context the security extension saves +// Status and control registers are excluded from call-used register clearing. +// Clearing condition flags requires separate handling. +// FPCXTNS holds the floating-point context the security extension saves // and restores across a non-secure call; overwriting it would destroy state // the caller is entitled to get back, which is not what clearing a call-used // register means. VPR is deliberately absent: an MVE predicate is data. diff --git a/llvm/test/CodeGen/ARM/zero-call-used-regs-unsupported.ll b/llvm/test/CodeGen/ARM/zero-call-used-regs-unsupported.ll index 31037fcad2bc3..c783386c745e5 100644 --- a/llvm/test/CodeGen/ARM/zero-call-used-regs-unsupported.ll +++ b/llvm/test/CodeGen/ARM/zero-call-used-regs-unsupported.ll @@ -1,4 +1,7 @@ ; RUN: not llc -mtriple=armv7-unknown-linux-gnueabi < %s -o /dev/null 2>&1 | FileCheck %s +; RUN: not llc -mtriple=thumbv6m-none-eabi < %s -o /dev/null 2>&1 | FileCheck %s +; RUN: not llc -mtriple=thumbv7m-none-eabi < %s -o /dev/null 2>&1 | FileCheck %s +; RUN: not llc -mtriple=thumbv8.1m.main-none-eabi -mattr=+mve < %s -o /dev/null 2>&1 | FileCheck %s ; ARM does not implement emitZeroCallUsedRegs, so supportsZeroCallUsedRegs is ; false and the request is reported. Before the query it was dropped silently. @@ -8,6 +11,18 @@ define i32 @used_gpr(i32 %x) "zero-call-used-regs"="used-gpr" { ret i32 %x } +; Register classification must not enable clearing on a target that has no +; emitter, even when it can identify the requested GPR or argument registers. +; CHECK: error: {{.*}}in function all_gpr i32 (i32): "zero-call-used-regs" is not supported by this target +define i32 @all_gpr(i32 %x) "zero-call-used-regs"="all-gpr" { + ret i32 %x +} + +; CHECK: error: {{.*}}in function all_arg i32 (i32): "zero-call-used-regs" is not supported by this target +define i32 @all_arg(i32 %x) "zero-call-used-regs"="all-arg" { + ret i32 %x +} + ; CHECK: error: {{.*}}in function all i32 (i32): "zero-call-used-regs" is not supported by this target define i32 @all(i32 %x) "zero-call-used-regs"="all" { ret i32 %x diff --git a/llvm/unittests/Target/ARM/ARMRegisterInfoTest.cpp b/llvm/unittests/Target/ARM/ARMRegisterInfoTest.cpp new file mode 100644 index 0000000000000..9b44b4fc5673b --- /dev/null +++ b/llvm/unittests/Target/ARM/ARMRegisterInfoTest.cpp @@ -0,0 +1,75 @@ +//===----------------------------------------------------------------------===// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "MCTargetDesc/ARMMCTargetDesc.h" +#include "llvm/CodeGen/MachineFunction.h" +#include "llvm/CodeGen/MachineModuleInfo.h" +#include "llvm/CodeGen/TargetRegisterInfo.h" +#include "llvm/CodeGen/TargetSubtargetInfo.h" +#include "llvm/IR/Function.h" +#include "llvm/IR/Module.h" +#include "llvm/MC/TargetRegistry.h" +#include "llvm/Support/TargetSelect.h" +#include "llvm/Target/TargetMachine.h" +#include "gtest/gtest.h" + +using namespace llvm; + +TEST(ARMRegisterInfoTest, SwiftArgumentAttributes) { + LLVMInitializeARMTargetInfo(); + LLVMInitializeARMTarget(); + LLVMInitializeARMTargetMC(); + + // Exercise APCS, AAPCS and AAPCS-VFP, including FastCC's APCS delegation. + for (const char *TripleName : + {"armv7-unknown-linux-gnueabi", "armv7-unknown-linux-gnueabihf", + "armv7-apple-ios"}) { + Triple TT(TripleName); + std::string Error; + const Target *T = TargetRegistry::lookupTarget(TT, Error); + ASSERT_NE(T, nullptr) << Error; + TargetOptions Options; + std::unique_ptr TM(T->createTargetMachine( + TT, "cortex-a9", "+neon", Options, std::nullopt)); + ASSERT_NE(TM, nullptr); + + for (CallingConv::ID CC : {CallingConv::C, CallingConv::Fast, + CallingConv::Swift, CallingConv::SwiftTail}) { + for (bool HasSelf : {false, true}) { + for (bool HasError : {false, true}) { + SCOPED_TRACE(testing::Message() + << TripleName << " CC=" << CC << " self=" << HasSelf + << " error=" << HasError); + LLVMContext Context; + Module M("test", Context); + M.setTargetTriple(TT); + M.setDataLayout(TM->createDataLayout()); + Type *PtrTy = PointerType::getUnqual(Context); + Function *F = + Function::Create(FunctionType::get(Type::getVoidTy(Context), + {PtrTy, PtrTy}, false), + GlobalValue::ExternalLinkage, "f", M); + F->setCallingConv(CC); + if (HasSelf) + F->addParamAttr(0, Attribute::SwiftSelf); + if (HasError) + F->addParamAttr(1, Attribute::SwiftError); + + MachineModuleInfo MMI(TM.get()); + MachineFunction &MF = MMI.getOrCreateMachineFunction(*F); + const TargetRegisterInfo &TRI = *MF.getSubtarget().getRegisterInfo(); + bool IsSwift = + CC == CallingConv::Swift || CC == CallingConv::SwiftTail; + EXPECT_EQ(TRI.isArgumentRegister(MF, ARM::R10), IsSwift || HasSelf); + EXPECT_EQ(TRI.isArgumentRegister(MF, ARM::R8), IsSwift || HasError); + EXPECT_TRUE(TRI.isArgumentRegister(MF, ARM::R0)); + EXPECT_FALSE(TRI.isArgumentRegister(MF, ARM::R9)); + } + } + } + } +} diff --git a/llvm/unittests/Target/ARM/CMakeLists.txt b/llvm/unittests/Target/ARM/CMakeLists.txt index fd9bcc69a870b..8842546e12536 100644 --- a/llvm/unittests/Target/ARM/CMakeLists.txt +++ b/llvm/unittests/Target/ARM/CMakeLists.txt @@ -22,6 +22,7 @@ set(LLVM_LINK_COMPONENTS ) add_llvm_target_unittest(ARMTests + ARMRegisterInfoTest.cpp MachineInstrTest.cpp InstSizes.cpp ARMSelectionDAGTest.cpp