Skip to content

[ARM] Save floating point registers and status registers with save_fp function attribute #89654

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 1 commit into from
Apr 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions clang/include/clang/Basic/Attr.td
Original file line number Diff line number Diff line change
Expand Up @@ -999,6 +999,22 @@ def ARMInterrupt : InheritableAttr, TargetSpecificAttr<TargetARM> {
let Documentation = [ARMInterruptDocs];
}

def ARMInterruptSaveFP : InheritableAttr, TargetSpecificAttr<TargetARM> {
let Spellings = [GNU<"interrupt_save_fp">];
let Args = [EnumArgument<"Interrupt", "InterruptType", /*is_string=*/true,
["IRQ", "FIQ", "SWI", "ABORT", "UNDEF", ""],
["IRQ", "FIQ", "SWI", "ABORT", "UNDEF", "Generic"],
1>];
let HasCustomParsing = 0;
let Documentation = [ARMInterruptSaveFPDocs];
}

def ARMSaveFP : InheritableAttr, TargetSpecificAttr<TargetARM> {
let Spellings = [];
let Subjects = SubjectList<[Function]>;
let Documentation = [InternalOnly];
}

def AVRInterrupt : InheritableAttr, TargetSpecificAttr<TargetAVR> {
let Spellings = [GCC<"interrupt">];
let Subjects = SubjectList<[Function]>;
Expand Down
13 changes: 13 additions & 0 deletions clang/include/clang/Basic/AttrDocs.td
Original file line number Diff line number Diff line change
Expand Up @@ -2686,6 +2686,19 @@ The semantics are as follows:
}];
}

def ARMInterruptSaveFPDocs : Documentation {
let Category = DocCatFunction;
let Heading = "interrupt_save_fp (ARM)";
let Content = [{
Clang supports the GNU style ``__attribute__((interrupt_save_fp("TYPE")))``
on ARM targets. This attribute behaves the same way as the ARM interrupt
attribute, except the general purpose floating point registers are also saved,
along with FPEXC and FPSCR. Note, even on M-class CPUs, where the floating
point context can be automatically saved depending on the FPCCR, the general
purpose floating point registers will be saved.
}];
}

def BPFPreserveAccessIndexDocs : Documentation {
let Category = DocCatFunction;
let Content = [{
Expand Down
8 changes: 7 additions & 1 deletion clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -402,8 +402,14 @@ def warn_anyx86_excessive_regsave : Warning<
InGroup<DiagGroup<"excessive-regsave">>;
def warn_arm_interrupt_vfp_clobber : Warning<
"interrupt service routine with vfp enabled may clobber the "
"interruptee's vfp state">,
"interruptee's vfp state; "
"consider using the `interrupt_save_fp` attribute to prevent this behavior">,
InGroup<DiagGroup<"arm-interrupt-vfp-clobber">>;
def warn_arm_interrupt_save_fp_without_vfp_unit : Warning<
"`interrupt_save_fp` only applies to targets that have a VFP unit enabled "
"for this compilation; this will be treated as a regular `interrupt` "
"attribute">,
InGroup<DiagGroup<"arm-interrupt-save-fp-no-vfp-unit">>;
def err_arm_interrupt_called : Error<
"interrupt service routine cannot be called directly">;
def warn_interrupt_signal_attribute_invalid : Warning<
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/Sema/SemaARM.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ class SemaARM : public SemaBase {
void handleNewAttr(Decl *D, const ParsedAttr &AL);
void handleCmseNSEntryAttr(Decl *D, const ParsedAttr &AL);
void handleInterruptAttr(Decl *D, const ParsedAttr &AL);
void handleInterruptSaveFPAttr(Decl *D, const ParsedAttr &AL);

void CheckSMEFunctionDefAttributes(const FunctionDecl *FD);
};
Expand Down
6 changes: 6 additions & 0 deletions clang/lib/CodeGen/Targets/ARM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,12 @@ class ARMTargetCodeGenInfo : public TargetCodeGenInfo {

Fn->addFnAttr("interrupt", Kind);

// Note: the ARMSaveFPAttr can only exist if we also have an interrupt
// attribute
const ARMSaveFPAttr *SaveFPAttr = FD->getAttr<ARMSaveFPAttr>();
if (SaveFPAttr)
Fn->addFnAttr("save-fp");

ARMABIKind ABI = getABIInfo<ARMABIInfo>().getABIKind();
if (ABI == ARMABIKind::APCS)
return;
Expand Down
30 changes: 27 additions & 3 deletions clang/lib/Sema/SemaARM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1316,14 +1316,38 @@ void SemaARM::handleInterruptAttr(Decl *D, const ParsedAttr &AL) {
return;
}

const TargetInfo &TI = getASTContext().getTargetInfo();
if (TI.hasFeature("vfp"))
Diag(D->getLocation(), diag::warn_arm_interrupt_vfp_clobber);
if (!D->hasAttr<ARMSaveFPAttr>()) {
const TargetInfo &TI = getASTContext().getTargetInfo();
if (TI.hasFeature("vfp"))
Diag(D->getLocation(), diag::warn_arm_interrupt_vfp_clobber);
}

D->addAttr(::new (getASTContext())
ARMInterruptAttr(getASTContext(), AL, Kind));
}

void SemaARM::handleInterruptSaveFPAttr(Decl *D, const ParsedAttr &AL) {
// Go ahead and add ARMSaveFPAttr because handleInterruptAttr() checks for
// it when deciding to issue a diagnostic about clobbering floating point
// registers, which ARMSaveFPAttr prevents.
D->addAttr(::new (SemaRef.Context) ARMSaveFPAttr(SemaRef.Context, AL));
SemaRef.ARM().handleInterruptAttr(D, AL);

// If ARM().handleInterruptAttr() failed, remove ARMSaveFPAttr.
if (!D->hasAttr<ARMInterruptAttr>()) {
D->dropAttr<ARMSaveFPAttr>();
return;
}

// If VFP not enabled, remove ARMSaveFPAttr but leave ARMInterruptAttr.
bool VFP = SemaRef.Context.getTargetInfo().hasFeature("vfp");

if (!VFP) {
SemaRef.Diag(D->getLocation(), diag::warn_arm_interrupt_save_fp_without_vfp_unit);
D->dropAttr<ARMSaveFPAttr>();
}
}

// Check if the function definition uses any AArch64 SME features without
// having the '+sme' feature enabled and warn user if sme locally streaming
// function returns or uses arguments with VL-based types.
Expand Down
3 changes: 3 additions & 0 deletions clang/lib/Sema/SemaDeclAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6950,6 +6950,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
case ParsedAttr::AT_Interrupt:
handleInterruptAttr(S, D, AL);
break;
case ParsedAttr::AT_ARMInterruptSaveFP:
S.ARM().handleInterruptSaveFPAttr(D, AL);
break;
case ParsedAttr::AT_X86ForceAlignArgPointer:
S.X86().handleForceAlignArgPointerAttr(D, AL);
break;
Expand Down
34 changes: 34 additions & 0 deletions clang/test/CodeGen/arm-interrupt-save-fp-attr-status-regs.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// REQUIRES: arm-registered-target
// RUN: %clang -target arm-none-none-eabihf -mcpu=cortex-r5 -mfpu=vfpv3-d16 -marm -S -o - %s \
// RUN: | FileCheck %s --check-prefix=CHECK-R
// RUN: %clang -target arm-none-none-eabihf -mcpu=cortex-r5 -mfpu=vfpv3-d16 -mthumb -S -o - %s \
// RUN: | FileCheck %s --check-prefix=CHECK-R
// RUN: %clang -target arm-none-none-eabihf -mcpu=cortex-r4 -mfpu=vfpv3-d16 -marm -S -o - %s \
// RUN: | FileCheck %s --check-prefix=CHECK-R
// RUN: %clang -target arm-none-none-eabihf -mcpu=cortex-r4 -mfpu=vfpv3-d16 -mthumb -S -o - %s \
// RUN: | FileCheck %s --check-prefix=CHECK-R
// RUN: %clang -target arm-none-none-eabihf -mcpu=cortex-m4 -mfpu=fpv4-sp-d16 -S -o - %s \
// RUN: | FileCheck %s --check-prefix=CHECK-M
// RUN: %clang -target arm-none-none-eabihf -mcpu=cortex-m33 -mfpu=fpv5-sp-d16 -S -o - %s \
// RUN: | FileCheck %s --check-prefix=CHECK-M

void bar();

__attribute__((interrupt_save_fp)) void test_generic_interrupt() {
// CHECK-R: vmrs r4, fpscr
// CHECK-R-NEXT: vmrs r5, fpexc
// CHECK-R-NEXT: .save {r4, r5}
// CHECK-R-NEXT: push {r4, r5}
// .....
// CHECK-R: pop {r4, r5}
// CHECK-R-NEXT: vmsr fpscr, r4
// CHECK-R-NEXT: vmsr fpexc, r5

// CHECK-M: vmrs r4, fpscr
// CHECK-M-NEXT: .save {r4}
// CHECK-M-NEXT: push {r4}
// .....
// CHECK-M: pop {r4}
// CHECK-M-NEXT: vmsr fpscr, r4
bar();
}
39 changes: 39 additions & 0 deletions clang/test/CodeGen/arm-interrupt-save-fp-attr.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// RUN: %clang_cc1 -triple thumb-apple-darwin -target-abi aapcs -target-feature +vfp4 -target-cpu cortex-m3 -emit-llvm -o - %s | FileCheck %s
// RUN: %clang_cc1 -triple arm-apple-darwin -target-abi apcs-gnu -target-feature +vfp4 -emit-llvm -o - %s | FileCheck %s --check-prefix=CHECK-APCS

__attribute__((interrupt_save_fp)) void test_generic_interrupt() {
// CHECK: define{{.*}} arm_aapcscc void @test_generic_interrupt() [[GENERIC_ATTR:#[0-9]+]]

// CHECK-APCS: define{{.*}} void @test_generic_interrupt() [[GENERIC_ATTR:#[0-9]+]]
}

__attribute__((interrupt_save_fp("IRQ"))) void test_irq_interrupt() {
// CHECK: define{{.*}} arm_aapcscc void @test_irq_interrupt() [[IRQ_ATTR:#[0-9]+]]
}

__attribute__((interrupt_save_fp("FIQ"))) void test_fiq_interrupt() {
// CHECK: define{{.*}} arm_aapcscc void @test_fiq_interrupt() [[FIQ_ATTR:#[0-9]+]]
}

__attribute__((interrupt_save_fp("SWI"))) void test_swi_interrupt() {
// CHECK: define{{.*}} arm_aapcscc void @test_swi_interrupt() [[SWI_ATTR:#[0-9]+]]
}

__attribute__((interrupt_save_fp("ABORT"))) void test_abort_interrupt() {
// CHECK: define{{.*}} arm_aapcscc void @test_abort_interrupt() [[ABORT_ATTR:#[0-9]+]]
}


__attribute__((interrupt_save_fp("UNDEF"))) void test_undef_interrupt() {
// CHECK: define{{.*}} arm_aapcscc void @test_undef_interrupt() [[UNDEF_ATTR:#[0-9]+]]
}


// CHECK: attributes [[GENERIC_ATTR]] = { {{.*}} {{"interrupt"[^=]}}{{.*}} "save-fp"
// CHECK: attributes [[IRQ_ATTR]] = { {{.*}} "interrupt"="IRQ" {{.*}} "save-fp"
// CHECK: attributes [[FIQ_ATTR]] = { {{.*}} "interrupt"="FIQ" {{.*}} "save-fp"
// CHECK: attributes [[SWI_ATTR]] = { {{.*}} "interrupt"="SWI" {{.*}} "save-fp"
// CHECK: attributes [[ABORT_ATTR]] = { {{.*}} "interrupt"="ABORT" {{.*}} "save-fp"
// CHECK: attributes [[UNDEF_ATTR]] = { {{.*}} "interrupt"="UNDEF" {{.*}} "save-fp"

// CHECK-APCS: attributes [[GENERIC_ATTR]] = { {{.*}} "interrupt" {{.*}} "save-fp"
2 changes: 1 addition & 1 deletion clang/test/Sema/arm-interrupt-attr.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@


#ifdef __ARM_FP
__attribute__((interrupt("IRQ"))) void float_irq(void); // expected-warning {{interrupt service routine with vfp enabled may clobber the interruptee's vfp state}}
__attribute__((interrupt("IRQ"))) void float_irq(void); // expected-warning {{interrupt service routine with vfp enabled may clobber the interruptee's vfp state; consider using the `interrupt_save_fp` attribute to prevent this behavior}}
#else // !defined(__ARM_FP)
__attribute__((interrupt("irq"))) void foo1(void) {} // expected-warning {{'interrupt' attribute argument not supported: irq}}
__attribute__((interrupt(IRQ))) void foo(void) {} // expected-error {{'interrupt' attribute requires a string}}
Expand Down
26 changes: 26 additions & 0 deletions clang/test/Sema/arm-interrupt-save-fp-attr.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// RUN: %clang_cc1 %s -triple arm-none-eabi -verify -fsyntax-only
// RUN: %clang_cc1 %s -triple arm-none-eabi -target-feature +vfp2 -verify -fsyntax-only


#if !defined(__ARM_FP)
__attribute__((interrupt_save_fp("IRQ"))) void float_irq(void); // expected-warning {{`interrupt_save_fp` only applies to targets that have a VFP unit enabled for this compilation; this will be treated as a regular `interrupt` attribute}}
#else // defined(__ARM_FP)
__attribute__((interrupt_save_fp("irq"))) void foo1(void) {} // expected-warning {{'interrupt_save_fp' attribute argument not supported: irq}}
__attribute__((interrupt_save_fp(IRQ))) void foo(void) {} // expected-error {{'interrupt_save_fp' attribute requires a string}}
__attribute__((interrupt_save_fp("IRQ", 1))) void foo2(void) {} // expected-error {{'interrupt_save_fp' attribute takes no more than 1 argument}}
__attribute__((interrupt_save_fp("IRQ"))) void foo3(void) {}
__attribute__((interrupt_save_fp("FIQ"))) void foo4(void) {}
__attribute__((interrupt_save_fp("SWI"))) void foo5(void) {}
__attribute__((interrupt_save_fp("ABORT"))) void foo6(void) {}
__attribute__((interrupt_save_fp("UNDEF"))) void foo7(void) {}
__attribute__((interrupt_save_fp)) void foo8(void) {}
__attribute__((interrupt_save_fp())) void foo9(void) {}
__attribute__((interrupt_save_fp(""))) void foo10(void) {}

__attribute__((interrupt_save_fp("IRQ"))) void callee(void) {}

void caller(void)
{
callee(); // expected-error {{interrupt service routine cannot be called directly}}
}
#endif // __ARM_FP
2 changes: 1 addition & 1 deletion llvm/include/llvm/IR/IntrinsicsARM.td
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ def int_arm_isb : ClangBuiltin<"__builtin_arm_isb">, MSBuiltin<"__isb">,
// VFP

def int_arm_get_fpscr : ClangBuiltin<"__builtin_arm_get_fpscr">,
DefaultAttrsIntrinsic<[llvm_i32_ty], [], []>;
DefaultAttrsIntrinsic<[llvm_i32_ty], [], [IntrReadMem]>;
def int_arm_set_fpscr : ClangBuiltin<"__builtin_arm_set_fpscr">,
DefaultAttrsIntrinsic<[], [llvm_i32_ty], []>;
def int_arm_vcvtr : DefaultAttrsIntrinsic<[llvm_float_ty],
Expand Down
16 changes: 16 additions & 0 deletions llvm/lib/Target/ARM/ARMAsmPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1202,6 +1202,14 @@ void ARMAsmPrinter::EmitUnwindingInstruction(const MachineInstr *MI) {
SrcReg = ~0U;
DstReg = MI->getOperand(0).getReg();
break;
case ARM::VMRS:
SrcReg = ARM::FPSCR;
DstReg = MI->getOperand(0).getReg();
break;
case ARM::VMRS_FPEXC:
SrcReg = ARM::FPEXC;
DstReg = MI->getOperand(0).getReg();
break;
default:
SrcReg = MI->getOperand(1).getReg();
DstReg = MI->getOperand(0).getReg();
Expand Down Expand Up @@ -1368,6 +1376,14 @@ void ARMAsmPrinter::EmitUnwindingInstruction(const MachineInstr *MI) {
// correct ".save" later.
AFI->EHPrologueRemappedRegs[DstReg] = SrcReg;
break;
case ARM::VMRS:
case ARM::VMRS_FPEXC:
// If a function spills FPSCR or FPEXC, we copy the values to low
// registers before pushing them. However, we can't issue annotations
// for FP status registers because ".save" requires GPR registers, and
// ".vsave" requires DPR registers, so don't record the copy and simply
// emit annotations for the source registers used for the store.
break;
case ARM::tLDRpci: {
// Grab the constpool index and check, whether it corresponds to
// original or cloned constpool entry.
Expand Down
19 changes: 19 additions & 0 deletions llvm/lib/Target/ARM/ARMBaseRegisterInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,25 @@ ARMBaseRegisterInfo::getCalleeSavedRegs(const MachineFunction *MF) const {
? CSR_ATPCS_SplitPush_SwiftTail_SaveList
: CSR_AAPCS_SwiftTail_SaveList);
} else if (F.hasFnAttribute("interrupt")) {

// Don't save the floating point registers if target does not have floating
// point registers.
if (STI.hasFPRegs() && F.hasFnAttribute("save-fp")) {
bool HasNEON = STI.hasNEON();

if (STI.isMClass()) {
assert(!HasNEON && "NEON is only for Cortex-R/A");
return PushPopSplit == ARMSubtarget::SplitR7
? CSR_ATPCS_SplitPush_FP_SaveList
: CSR_AAPCS_FP_SaveList;
}
if (F.getFnAttribute("interrupt").getValueAsString() == "FIQ") {
return HasNEON ? CSR_FIQ_FP_NEON_SaveList : CSR_FIQ_FP_SaveList;
}
return HasNEON ? CSR_GenericInt_FP_NEON_SaveList
: CSR_GenericInt_FP_SaveList;
}

if (STI.isMClass()) {
// M-class CPUs have hardware which saves the registers needed to allow a
// function conforming to the AAPCS to function as a handler.
Expand Down
42 changes: 32 additions & 10 deletions llvm/lib/Target/ARM/ARMCallingConv.td
Original file line number Diff line number Diff line change
Expand Up @@ -268,19 +268,14 @@ def CC_ARM_Win32_CFGuard_Check : CallingConv<[
def CSR_NoRegs : CalleeSavedRegs<(add)>;
def CSR_FPRegs : CalleeSavedRegs<(add (sequence "D%u", 0, 31))>;

def CSR_FP_Interrupt_Regs : CalleeSavedRegs<(add FPSCR, FPEXC, (sequence "D%u", 15, 0))>;
def CSR_FP_NEON_Interrupt_Regs : CalleeSavedRegs<(add CSR_FP_Interrupt_Regs,
(sequence "D%u", 31, 16))>;

def CSR_AAPCS : CalleeSavedRegs<(add LR, R11, R10, R9, R8, R7, R6, R5, R4,
(sequence "D%u", 15, 8))>;

// The Windows Control Flow Guard Check function preserves the same registers as
// AAPCS, and also preserves all floating point registers.
def CSR_Win_AAPCS_CFGuard_Check : CalleeSavedRegs<(add LR, R11, R10, R9, R8, R7,
R6, R5, R4, (sequence "D%u", 15, 0))>;

// R8 is used to pass swifterror, remove it from CSR.
def CSR_AAPCS_SwiftError : CalleeSavedRegs<(sub CSR_AAPCS, R8)>;

// R10 is used to pass swiftself, remove it from CSR.
def CSR_AAPCS_SwiftTail : CalleeSavedRegs<(sub CSR_AAPCS, R10)>;
def CSR_AAPCS_FP : CalleeSavedRegs<(add CSR_AAPCS, CSR_FP_Interrupt_Regs)>;

// The order of callee-saved registers needs to match the order we actually push
// them in FrameLowering, because this order is what's used by
Expand All @@ -294,6 +289,21 @@ def CSR_Win_SplitFP : CalleeSavedRegs<(add R10, R9, R8, R7, R6, R5, R4,
(sequence "D%u", 15, 8),
LR, R11)>;

def CSR_ATPCS_SplitPush_FP : CalleeSavedRegs<(add CSR_ATPCS_SplitPush,
CSR_FP_Interrupt_Regs)>;

// The Windows Control Flow Guard Check function preserves the same registers as
// AAPCS, and also preserves all floating point registers.
def CSR_Win_AAPCS_CFGuard_Check : CalleeSavedRegs<(add LR, R11, R10, R9, R8, R7,
R6, R5, R4, (sequence "D%u", 15, 0))>;

// R8 is used to pass swifterror, remove it from CSR.
def CSR_AAPCS_SwiftError : CalleeSavedRegs<(sub CSR_AAPCS, R8)>;

// R10 is used to pass swiftself, remove it from CSR.
def CSR_AAPCS_SwiftTail : CalleeSavedRegs<(sub CSR_AAPCS, R10)>;


// R8 is used to pass swifterror, remove it from CSR.
def CSR_ATPCS_SplitPush_SwiftError : CalleeSavedRegs<(sub CSR_ATPCS_SplitPush,
R8)>;
Expand Down Expand Up @@ -361,6 +371,13 @@ def CSR_iOS_CXX_TLS_ViaCopy : CalleeSavedRegs<(sub CSR_iOS_CXX_TLS,
// generally does rather than tracking its liveness as a normal register.
def CSR_GenericInt : CalleeSavedRegs<(add LR, (sequence "R%u", 12, 0))>;

def CSR_GenericInt_FP : CalleeSavedRegs<(add CSR_GenericInt,
CSR_FP_Interrupt_Regs)>;

def CSR_GenericInt_FP_NEON : CalleeSavedRegs<(add CSR_GenericInt_FP,
CSR_FP_NEON_Interrupt_Regs)>;


// The fast interrupt handlers have more private state and get their own copies
// of R8-R12, in addition to SP and LR. As before, mark LR for saving too.

Expand All @@ -369,4 +386,9 @@ def CSR_GenericInt : CalleeSavedRegs<(add LR, (sequence "R%u", 12, 0))>;
// registers.
def CSR_FIQ : CalleeSavedRegs<(add LR, R11, (sequence "R%u", 7, 0))>;

def CSR_FIQ_FP : CalleeSavedRegs<(add CSR_FIQ, CSR_FP_Interrupt_Regs)>;

def CSR_FIQ_FP_NEON : CalleeSavedRegs<(add CSR_FIQ_FP,
CSR_FP_NEON_Interrupt_Regs)>;


Loading
Loading