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

Conversation

pestctrl
Copy link
Contributor

The interrupt attribute currently doesn't save floating-point related registers in the frame setup and destroy.

This patch adds a new interrupt_save_fp attribute that will save floating-point registers as well.

@llvmbot llvmbot added clang Clang issues not falling into any other category backend:ARM clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:codegen IR generation bugs: mangling, exceptions, etc. llvm:ir labels Apr 22, 2024
@llvmbot
Copy link
Member

llvmbot commented Apr 22, 2024

@llvm/pr-subscribers-backend-aarch64
@llvm/pr-subscribers-clang-codegen
@llvm/pr-subscribers-backend-arm

@llvm/pr-subscribers-clang

Author: Benson Chu (pestctrl)

Changes

The interrupt attribute currently doesn't save floating-point related registers in the frame setup and destroy.

This patch adds a new interrupt_save_fp attribute that will save floating-point registers as well.


Patch is 76.18 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/89654.diff

22 Files Affected:

  • (modified) clang/include/clang/Basic/Attr.td (+16)
  • (modified) clang/include/clang/Basic/AttrDocs.td (+14)
  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+8-1)
  • (modified) clang/lib/CodeGen/Targets/ARM.cpp (+6)
  • (modified) clang/lib/Sema/SemaDeclAttr.cpp (+20-4)
  • (modified) clang/lib/Sema/SemaExpr.cpp (+2-1)
  • (added) clang/test/CodeGen/arm-interrupt-save-fp-attr-status-regs.c (+34)
  • (added) clang/test/CodeGen/arm-interrupt-save-fp-attr.c (+39)
  • (modified) clang/test/Sema/arm-interrupt-attr.c (+2-2)
  • (added) clang/test/Sema/arm-interrupt-save-fp-attr.c (+59)
  • (modified) llvm/include/llvm/IR/IntrinsicsARM.td (+1-1)
  • (modified) llvm/lib/Target/ARM/ARMAsmPrinter.cpp (+15)
  • (modified) llvm/lib/Target/ARM/ARMBaseRegisterInfo.cpp (+25-1)
  • (modified) llvm/lib/Target/ARM/ARMCallingConv.td (+32-10)
  • (modified) llvm/lib/Target/ARM/ARMFrameLowering.cpp (+234-111)
  • (modified) llvm/lib/Target/ARM/ARMFrameLowering.h (+9)
  • (modified) llvm/lib/Target/ARM/ARMInstrVFP.td (+3)
  • (modified) llvm/lib/Target/ARM/ARMMachineFunctionInfo.h (+9-3)
  • (modified) llvm/lib/Target/ARM/ARMRegisterInfo.td (+7)
  • (modified) llvm/lib/Target/ARM/Thumb1FrameLowering.cpp (+2-2)
  • (added) llvm/test/CodeGen/ARM/interrupt-save-fp-attr-status-regs.mir (+277)
  • (added) llvm/test/CodeGen/ARM/interrupt-save-fp-attr.ll (+303)
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index dc87a8c6f022dc..71775ce1e12461 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -914,6 +914,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",
+                           ["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 = [ARMInterruptSaveFPDocs];
+}
+
 def AVRInterrupt : InheritableAttr, TargetSpecificAttr<TargetAVR> {
   let Spellings = [GCC<"interrupt">];
   let Subjects = SubjectList<[Function]>;
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index a0bbe5861c5722..1cd642ba90aa9e 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -2239,6 +2239,20 @@ 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. 
+If the FPEXC or FPSCR are needed, that state must be saved manually. 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 = [{
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 63e951daec7477..1f519d0fc1cc35 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -335,7 +335,14 @@ def warn_anyx86_excessive_regsave : Warning<
   " or be compiled with '-mgeneral-regs-only'">,
   InGroup<DiagGroup<"excessive-regsave">>;
 def warn_arm_interrupt_calling_convention : Warning<
-   "call to function without interrupt attribute could clobber interruptee's VFP registers">,
+  "call to function without interrupt attribute could clobber interruptee's "
+  "VFP registers; consider using the `interrupt_save_fp` attribute to prevent "
+  "this behavior">,
+   InGroup<Extra>;
+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<Extra>;
 def warn_interrupt_attribute_invalid : Warning<
    "%select{MIPS|MSP430|RISC-V}0 'interrupt' attribute only applies to "
diff --git a/clang/lib/CodeGen/Targets/ARM.cpp b/clang/lib/CodeGen/Targets/ARM.cpp
index 885d9c77d0e76f..a45858e2e2e586 100644
--- a/clang/lib/CodeGen/Targets/ARM.cpp
+++ b/clang/lib/CodeGen/Targets/ARM.cpp
@@ -185,6 +185,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;
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 363ae93cb62df1..086f0c8daa36c3 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -7524,6 +7524,19 @@ static void handleARMInterruptAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
   D->addAttr(::new (S.Context) ARMInterruptAttr(S.Context, AL, Kind));
 }
 
+static void handleARMInterruptSaveFPAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
+  handleARMInterruptAttr(S, D, AL);
+
+  bool VFP = S.Context.getTargetInfo().hasFeature("vfp");
+
+  if (!VFP) {
+    S.Diag(D->getLocation(), diag::warn_arm_interrupt_save_fp_without_vfp_unit);
+    return;
+  }
+
+  D->addAttr(::new (S.Context) ARMSaveFPAttr(S.Context, AL));
+}
+
 static void handleMSP430InterruptAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
   // MSP430 'interrupt' attribute is applied to
   // a function with no parameters and void return type.
@@ -9134,9 +9147,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
   if (AL.isCXX11Attribute() && !Options.IncludeCXX11Attributes)
     return;
 
-  // Unknown attributes are automatically warned on. Target-specific attributes
-  // which do not apply to the current target architecture are treated as
-  // though they were unknown attributes.
+  // Unknown attributes are automatically warned on. Target-specific
+  // attributes which do not apply to the current target architecture are
+  // treated as though they were unknown attributes.
   if (AL.getKind() == ParsedAttr::UnknownAttribute ||
       !AL.existsInTarget(S.Context.getTargetInfo())) {
     S.Diag(AL.getLoc(),
@@ -9145,7 +9158,7 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
            : AL.isDeclspecAttribute()
                ? (unsigned)diag::warn_unhandled_ms_attribute_ignored
                : (unsigned)diag::warn_unknown_attribute_ignored)
-        << AL << AL.getRange();
+           << AL << AL.getRange();
     return;
   }
 
@@ -9241,6 +9254,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
   case ParsedAttr::AT_Interrupt:
     handleInterruptAttr(S, D, AL);
     break;
+  case ParsedAttr::AT_ARMInterruptSaveFP:
+    handleARMInterruptSaveFPAttr(S, D, AL);
+    break;
   case ParsedAttr::AT_X86ForceAlignArgPointer:
     handleX86ForceAlignArgPointerAttr(S, D, AL);
     break;
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 5c861467bc1023..72be89dbc53cf3 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -6931,7 +6931,8 @@ ExprResult Sema::BuildResolvedCallExpr(Expr *Fn, NamedDecl *NDecl,
   // no_caller_saved_registers since there is no efficient way to
   // save and restore the non-GPR state.
   if (auto *Caller = getCurFunctionDecl()) {
-    if (Caller->hasAttr<ARMInterruptAttr>()) {
+    if (Caller->hasAttr<ARMInterruptAttr>() && 
+        !Caller->hasAttr<ARMSaveFPAttr>()) {
       bool VFP = Context.getTargetInfo().hasFeature("vfp");
       if (VFP && (!FDecl || !FDecl->hasAttr<ARMInterruptAttr>())) {
         Diag(Fn->getExprLoc(), diag::warn_arm_interrupt_calling_convention);
diff --git a/clang/test/CodeGen/arm-interrupt-save-fp-attr-status-regs.c b/clang/test/CodeGen/arm-interrupt-save-fp-attr-status-regs.c
new file mode 100644
index 00000000000000..457f725f8d3af6
--- /dev/null
+++ b/clang/test/CodeGen/arm-interrupt-save-fp-attr-status-regs.c
@@ -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  {fpscr, fpexc}
+    // 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  {fpscr}
+    // CHECK-M-NEXT: push	{r4}
+    // .....
+    // CHECK-M:      pop	{r4}
+    // CHECK-M-NEXT: vmsr	fpscr, r4
+    bar();
+}
diff --git a/clang/test/CodeGen/arm-interrupt-save-fp-attr.c b/clang/test/CodeGen/arm-interrupt-save-fp-attr.c
new file mode 100644
index 00000000000000..5db8b3daa72126
--- /dev/null
+++ b/clang/test/CodeGen/arm-interrupt-save-fp-attr.c
@@ -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"
\ No newline at end of file
diff --git a/clang/test/Sema/arm-interrupt-attr.c b/clang/test/Sema/arm-interrupt-attr.c
index 3537fba8521ad9..937fd929483da0 100644
--- a/clang/test/Sema/arm-interrupt-attr.c
+++ b/clang/test/Sema/arm-interrupt-attr.c
@@ -31,13 +31,13 @@ void caller1(void) {
 
 #ifndef SOFT
 __attribute__((interrupt("IRQ"))) void caller2(void) {
-  callee1(); // expected-warning {{call to function without interrupt attribute could clobber interruptee's VFP registers}}
+  callee1(); // expected-warning {{call to function without interrupt attribute could clobber interruptee's VFP registers; consider using the `interrupt_save_fp` attribute to prevent this behavior}}
   callee2();
 }
 
 void (*callee3)(void);
 __attribute__((interrupt("IRQ"))) void caller3(void) {
-  callee3(); // expected-warning {{call to function without interrupt attribute could clobber interruptee's VFP registers}}
+  callee3(); // expected-warning {{call to function without interrupt attribute could clobber interruptee's VFP registers; consider using the `interrupt_save_fp` attribute to prevent this behavior}}
 }
 #else
 __attribute__((interrupt("IRQ"))) void caller2(void) {
diff --git a/clang/test/Sema/arm-interrupt-save-fp-attr.c b/clang/test/Sema/arm-interrupt-save-fp-attr.c
new file mode 100644
index 00000000000000..e0fd4e2c4d1288
--- /dev/null
+++ b/clang/test/Sema/arm-interrupt-save-fp-attr.c
@@ -0,0 +1,59 @@
+// RUN: %clang_cc1 %s -triple arm-apple-darwin  -target-feature +vfp2 -verify -fsyntax-only
+// RUN: %clang_cc1 %s -triple thumb-apple-darwin  -target-feature +vfp3 -verify -fsyntax-only
+// RUN: %clang_cc1 %s -triple armeb-none-eabi  -target-feature +vfp4 -verify -fsyntax-only
+// RUN: %clang_cc1 %s -triple thumbeb-none-eabi  -target-feature +neon -verify -fsyntax-only
+// RUN: %clang_cc1 %s -triple thumbeb-none-eabi -target-feature +neon -target-feature +soft-float -DSOFT -verify -fsyntax-only
+
+#ifndef SOFT
+__attribute__((interrupt_save_fp(IRQ))) void foo() {} // expected-error {{'interrupt_save_fp' attribute requires a string}}
+__attribute__((interrupt_save_fp("irq"))) void foo1() {} // expected-warning {{'interrupt_save_fp' attribute argument not supported: irq}}
+
+__attribute__((interrupt_save_fp("IRQ", 1))) void foo2() {} // expected-error {{'interrupt_save_fp' attribute takes no more than 1 argument}}
+__attribute__((interrupt_save_fp("IRQ"))) void foo3() {}
+__attribute__((interrupt_save_fp("FIQ"))) void foo4() {}
+__attribute__((interrupt_save_fp("SWI"))) void foo5() {}
+__attribute__((interrupt_save_fp("ABORT"))) void foo6() {}
+__attribute__((interrupt_save_fp("UNDEF"))) void foo7() {}
+__attribute__((interrupt_save_fp)) void foo8() {}
+__attribute__((interrupt_save_fp())) void foo9() {}
+__attribute__((interrupt_save_fp(""))) void foo10() {}
+void callee1();
+__attribute__((interrupt_save_fp("IRQ"))) void callee2();
+void caller1() {
+  callee1();
+  callee2();
+}
+__attribute__((interrupt_save_fp("IRQ"))) void caller2() {
+  callee1();
+  callee2();
+}
+
+void (*callee3)();
+__attribute__((interrupt_save_fp("IRQ"))) void caller3() {
+  callee3();
+}
+#else
+__attribute__((interrupt_save_fp("IRQ"))) void foo3() {} // 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}}
+__attribute__((interrupt_save_fp("FIQ"))) void foo4() {} // 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}}
+__attribute__((interrupt_save_fp("SWI"))) void foo5() {} // 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}}
+__attribute__((interrupt_save_fp("ABORT"))) void foo6() {} // 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}}
+__attribute__((interrupt_save_fp("UNDEF"))) void foo7() {} // 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}}
+__attribute__((interrupt_save_fp)) void foo8() {} // 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}}
+__attribute__((interrupt_save_fp())) void foo9() {} // 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}}
+__attribute__((interrupt_save_fp(""))) void foo10() {} // 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}}
+void callee1();
+__attribute__((interrupt_save_fp("IRQ"))) void callee2(); // 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}}
+void caller1() {
+  callee1();
+  callee2();
+}
+__attribute__((interrupt_save_fp("IRQ"))) void caller2() { // 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}}
+  callee1();
+  callee2();
+}
+
+void (*callee3)();
+__attribute__((interrupt_save_fp("IRQ"))) void caller3() { // 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}}
+  callee3();
+}
+#endif
\ No newline at end of file
diff --git a/llvm/include/llvm/IR/IntrinsicsARM.td b/llvm/include/llvm/IR/IntrinsicsARM.td
index 11b9877091a8ed..91f9d40f2e05f0 100644
--- a/llvm/include/llvm/IR/IntrinsicsARM.td
+++ b/llvm/include/llvm/IR/IntrinsicsARM.td
@@ -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],
diff --git a/llvm/lib/Target/ARM/ARMAsmPrinter.cpp b/llvm/lib/Target/ARM/ARMAsmPrinter.cpp
index 642739a29d6b06..353f5d1a57540d 100644
--- a/llvm/lib/Target/ARM/ARMAsmPrinter.cpp
+++ b/llvm/lib/Target/ARM/ARMAsmPrinter.cpp
@@ -1207,6 +1207,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();
@@ -1369,6 +1377,13 @@ 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. Record the copy so we can emit the
+        // correct ".save" later.
+        AFI->EHPrologueRemappedRegs[DstReg] = SrcReg;
+        break;
       case ARM::tLDRpci: {
         // Grab the constpool index and check, whether it corresponds to
         // original or cloned constpool entry.
diff --git a/llvm/lib/Target/ARM/ARMBaseRegisterInfo.cpp b/llvm/lib/Target/ARM/ARMBaseRegisterInfo.cpp
index 9adf758b46c481..ed774ea185f855 100644
--- a/llvm/lib/Target/ARM/ARMBaseRegisterInfo.cpp
+++ b/llvm/lib/Target/ARM/ARMBaseRegisterInfo.cpp
@@ -79,11 +79,35 @@ ARMBaseRegisterInfo::getCalleeSavedRegs(const MachineFunction *MF) const {
                : (UseSplitPush ? CSR_ATPCS_SplitPush_SwiftTail_SaveList
                                : CSR_AAPCS_SwiftTail_SaveList);
   } else if (F.hasFnAttribute("interrupt")) {
+
+    // Don't bother saving the floating point registers if target is not hard
+    // float. This will prevent the Thumb1FrameLowering (cortex-m0) from
+    // crashing due to an llvm_unreachable being triggered when a D-class
+    // register is in the calling convention.
+    if (STI.isTargetHardFloat() && F.hasFnAttribute("save-fp")) {
+      bool HasNEON = STI.hasNEON();
+
+      if (STI.isMClass()) {
+        assert(!HasNEON && "NEON is only for Cortex-R/A");
+        return UseSplitPush ? 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()) {
     ...
[truncated]

Copy link

github-actions bot commented Apr 22, 2024

⚠️ C/C++ code formatter, clang-format found issues in your code. ⚠️

You can test this locally with the following command:
git-clang-format --diff HEAD~1 HEAD --extensions c,cpp,h -- clang/test/CodeGen/arm-interrupt-save-fp-attr-status-regs.c clang/test/CodeGen/arm-interrupt-save-fp-attr.c clang/test/Sema/arm-interrupt-save-fp-attr.c clang/include/clang/Sema/SemaARM.h clang/lib/CodeGen/Targets/ARM.cpp clang/lib/Sema/SemaARM.cpp clang/lib/Sema/SemaDeclAttr.cpp clang/test/Sema/arm-interrupt-attr.c llvm/lib/Target/ARM/ARMAsmPrinter.cpp llvm/lib/Target/ARM/ARMBaseRegisterInfo.cpp llvm/lib/Target/ARM/ARMFrameLowering.cpp llvm/lib/Target/ARM/ARMFrameLowering.h llvm/lib/Target/ARM/ARMMachineFunctionInfo.h
View the diff from clang-format here.
diff --git a/clang/lib/Sema/SemaARM.cpp b/clang/lib/Sema/SemaARM.cpp
index c1cfea042..dd84ff2f4 100644
--- a/clang/lib/Sema/SemaARM.cpp
+++ b/clang/lib/Sema/SemaARM.cpp
@@ -1343,7 +1343,8 @@ void SemaARM::handleInterruptSaveFPAttr(Decl *D, const ParsedAttr &AL) {
   bool VFP = SemaRef.Context.getTargetInfo().hasFeature("vfp");
 
   if (!VFP) {
-    SemaRef.Diag(D->getLocation(), diag::warn_arm_interrupt_save_fp_without_vfp_unit);
+    SemaRef.Diag(D->getLocation(),
+                 diag::warn_arm_interrupt_save_fp_without_vfp_unit);
     D->dropAttr<ARMSaveFPAttr>();
   }
 }
diff --git a/llvm/lib/Target/ARM/ARMFrameLowering.cpp b/llvm/lib/Target/ARM/ARMFrameLowering.cpp
index 64c067f2b..be29ece61 100644
--- a/llvm/lib/Target/ARM/ARMFrameLowering.cpp
+++ b/llvm/lib/Target/ARM/ARMFrameLowering.cpp
@@ -913,8 +913,8 @@ void ARMFrameLowering::emitPrologue(MachineFunction &MF,
 
   // Determine the sizes of each callee-save spill areas and record which frame
   // belongs to which callee-save spill areas.
-  unsigned GPRCS1Size = 0, GPRCS2Size = 0, FPStatusSize = 0,
-           DPRCS1Size = 0, GPRCS3Size = 0, DPRCS2Size = 0;
+  unsigned GPRCS1Size = 0, GPRCS2Size = 0, FPStatusSize = 0, DPRCS1Size = 0,
+           GPRCS3Size = 0, DPRCS2Size = 0;
   int FramePtrSpillFI = 0;
   int D8SpillFI = 0;
 
@@ -1844,9 +1844,8 @@ void ARMFrameLowering::emitFPStatusSaves(MachineBasicBlock &MBB,
 
   SmallVector<MCRegister> Regs;
   auto RegPresent = [&CSI](MCRegister Reg) {
-    return llvm::any_of(CSI, [Reg](const CalleeSavedInfo &C) {
-      return C.getReg() == Reg;
-    });
+    return llvm::any_of(
+        CSI, [Reg](const CalleeSavedInfo &C) { return C.getReg() == Reg; });
   };
 
   // If we need to save FPSCR, then we must move FPSCR into R4 with the VMRS
@@ -1893,9 +1892,8 @@ void ARMFrameLowering::emitFPStatusRestores(
 
   SmallVector<MCRegister> Regs;
   auto RegPresent = [&CSI](MCRegister Reg) {
-    return llvm::any_of(CSI, [Reg](const CalleeSavedInfo &C) {
-      return C.getReg() == Reg;
-    });
+    return llvm::any_of(
+        CSI, [Reg](const CalleeSavedInfo &C) { return C.getReg() == Reg; });
   };
 
   // Do nothing if we don't need to restore any FP status registers.
diff --git a/llvm/lib/Target/ARM/ARMMachineFunctionInfo.h b/llvm/lib/Target/ARM/ARMMachineFunctionInfo.h
index a9c4b53f9..7692b7cdf 100644
--- a/llvm/lib/Target/ARM/ARMMachineFunctionInfo.h
+++ b/llvm/lib/Target/ARM/ARMMachineFunctionInfo.h
@@ -206,7 +206,7 @@ public:
   unsigned getFrameRecordSavedAreaSize() const { return FRSaveSize; }
   unsigned getGPRCalleeSavedArea1Size() const { return GPRCS1Size; }
   unsigned getGPRCalleeSavedArea2Size() const { return GPRCS2Size; }
-  unsigned getFPStatusSavesSize() const       { return FPStatusSize; }
+  unsigned getFPStatusSavesSize() const { return FPStatusSize; }
   unsigned getDPRCalleeSavedGapSize() const   { return DPRCSAlignGapSize; }
   unsigned getDPRCalleeSavedArea1Size() const { return DPRCS1Size; }
   unsigned getGPRCalleeSavedArea3Size() const { return GPRCS3Size; }
@@ -215,7 +215,7 @@ public:
   void setFrameRecordSavedAreaSize(unsigned s) { FRSaveSize = s; }
   void setGPRCalleeSavedArea1Size(unsigned s) { GPRCS1Size = s; }
   void setGPRCalleeSavedArea2Size(unsigned s) { GPRCS2Size = s; }
-  void setFPStatusSavesSize(unsigned s)       { FPStatusSize = s; }
+  void setFPStatusSavesSize(unsigned s) { FPStatusSize = s; }
   void setDPRCalleeSavedGapSize(unsigned s)   { DPRCSAlignGapSize = s; }
   void setDPRCalleeSavedArea1Size(unsigned s) { DPRCS1Size = s; }
   void setGPRCalleeSavedArea3Size(unsigned s) { GPRCS3Size = s; }

@pestctrl pestctrl force-pushed the save-fp-reg branch 5 times, most recently from 7f036d3 to c3aad46 Compare April 22, 2024 20:00
@efriedma-quic efriedma-quic requested review from ostannard and davemgreen and removed request for efriedma-quic April 22, 2024 21:04
@pestctrl pestctrl force-pushed the save-fp-reg branch 2 times, most recently from 4b99dc8 to 207438b Compare April 23, 2024 15:24
@pestctrl
Copy link
Contributor Author

pestctrl commented May 6, 2024

ping!

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.
If the FPEXC or FPSCR are needed, that state must be saved manually. Note, even
Copy link
Collaborator

Choose a reason for hiding this comment

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

This says FPSCR isn't saved... but some of the tests show FPSCR getting saved. Which is right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The FPSCR is in fact saved, along with the FPEXC.

@pestctrl
Copy link
Contributor Author

ping!

@pestctrl
Copy link
Contributor Author

pestctrl commented Sep 6, 2024

ping

"`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<Extra>;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please don't add diagnostics to Wextra; it's a specific set of diagnostics based on the equivalent gcc flag, not a catchall.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

__attribute__((interrupt_save_fp)) void test_generic_interrupt() {
// CHECK-R: vmrs r4, fpscr
// CHECK-R-NEXT: vmrs r5, fpexc
// CHECK-R-NEXT: .save {fpscr, fpexc}
Copy link
Collaborator

Choose a reason for hiding this comment

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

It looks like this is the only test that checks the new .save syntax? There should be tests in llvm/test/CodeGen/ARM and llvm/test/MC/ARM

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have reverted that syntax change. We had initially rolled it out internally, and later found that our assembler didn't like that syntax change, so we rolled it back. I forgot to roll it back on the upstream branch.

To our understanding, the .save directive is for unwinding, and only accepts GPR's. .vsave isn't the correct directive to use either, since those only accept DPR's. In the end, we decided to just have those .save's refer to the actual GPR's.

Copy link
Collaborator

Choose a reason for hiding this comment

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

At that point, you might as well not emit the .save at all? At best, the debugger will just see the values get overwritten, and ignore the second .save.

const ParsedAttr &AL) {
handleARMInterruptAttr(S, D, AL);

bool VFP = S.Context.getTargetInfo().hasFeature("vfp");
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we care if someone uses a "target" attribute? Maybe not relevant for embedded targets where you'd use this.

// float. This will prevent the Thumb1FrameLowering (cortex-m0) from
// crashing due to an llvm_unreachable being triggered when a D-class
// register is in the calling convention.
if (STI.isTargetHardFloat() && F.hasFnAttribute("save-fp")) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

It's a little weird to check isTargetHardFloat here... that just refers to whether the calling convention passes arguments in integer registers or FP registers. It doesn't have anything to do with whether floating-point registers are used.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

How about we check for if if the subtarget has FP registers?

if (STI.hasFPRegs() && F.hasFnAttribute("save-fp"))

Copy link
Collaborator

@efriedma-quic efriedma-quic left a comment

Choose a reason for hiding this comment

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

LGTM

@efriedma-quic
Copy link
Collaborator

Is this still waiting for something from me?

@pestctrl
Copy link
Contributor Author

@efriedma-quic No, I will push this on Monday. Thank you!

FPSCR and FPEXC will be stored in FPStatusRegs, after GPRCS2 has been
saved.

- GPRCS1
- GPRCS2
- FPStatusRegs (new)
- DPRCS
- GPRCS3
- DPRCS2

FPSCR is present on all targets with a VFP, but the FPEXC register is
not present on Cortex-M devices, so different amounts of bytes are
being pushed onto the stack depending on our target, which would
affect alignment for subsequent saves.

DPRCS1 will sum up all previous bytes that were saved, and will emit
extra instructions to ensure that its alignment is correct. My
assumption is that if DPRCS1 is able to correct its alignment to be
correct, then all subsequent saves will also have correct alignment.

Avoid annotating the saving of FPSCR and FPEXC for functions marked
with the interrupt_save_fp attribute, even though this is done as part
of frame setup.  Since these are status registers, there really is no
viable way of annotating this. Since these aren't GPRs or DPRs, they
can't be used with .save or .vsave directives. Instead, just record
that the intermediate registers r4 and r5 are saved to the stack
again.

Co-authored-by: Jake Vossen <[email protected]>
Co-authored-by: Alan Phipps <[email protected]>
@pestctrl pestctrl merged commit 5032050 into llvm:main Apr 22, 2025
5 of 9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backend:AArch64 backend:ARM clang:codegen IR generation bugs: mangling, exceptions, etc. clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category llvm:ir
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants