[llvm-branch-commits] [clang] 10d4425 - [clang][AVR] Implement standard calling convention for AVR and AVRTiny

Tom Stellard via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Thu Jun 2 13:56:35 PDT 2022


Author: Ben Shi
Date: 2022-06-02T13:55:14-07:00
New Revision: 10d442522b1afa263f4424478ef62d80c2a8b484

URL: https://github.com/llvm/llvm-project/commit/10d442522b1afa263f4424478ef62d80c2a8b484
DIFF: https://github.com/llvm/llvm-project/commit/10d442522b1afa263f4424478ef62d80c2a8b484.diff

LOG: [clang][AVR] Implement standard calling convention for AVR and AVRTiny

This patch implements avr-gcc's calling convention:
https://gcc.gnu.org/wiki/avr-gcc#Calling_Convention

Reviewed By: aykevl

Differential Revision: https://reviews.llvm.org/D120720

(cherry picked from commit 51585aa240de6ef07979345de5406483d7393b7b)

Added: 
    clang/test/CodeGen/avr/argument.c

Modified: 
    clang/lib/Basic/Targets/AVR.cpp
    clang/lib/Basic/Targets/AVR.h
    clang/lib/CodeGen/TargetInfo.cpp
    clang/test/CodeGen/avr/struct.c

Removed: 
    


################################################################################
diff  --git a/clang/lib/Basic/Targets/AVR.cpp b/clang/lib/Basic/Targets/AVR.cpp
index 6266ed72cd5c0..93ed0671119fe 100644
--- a/clang/lib/Basic/Targets/AVR.cpp
+++ b/clang/lib/Basic/Targets/AVR.cpp
@@ -24,7 +24,8 @@ namespace targets {
 struct LLVM_LIBRARY_VISIBILITY MCUInfo {
   const char *Name;
   const char *DefineName;
-  const int NumFlashBanks; // -1 means the device does not support LPM/ELPM.
+  const int NumFlashBanks; // Set to 0 for the devices do not support LPM/ELPM.
+  bool IsTiny; // Set to true for the devices belong to the avrtiny family.
 };
 
 // This list should be kept up-to-date with AVRDevices.td in LLVM.
@@ -267,14 +268,14 @@ static MCUInfo AVRMcus[] = {
     {"atxmega128a1", "__AVR_ATxmega128A1__", 2},
     {"atxmega128a1u", "__AVR_ATxmega128A1U__", 2},
     {"atxmega128a4u", "__AVR_ATxmega128A4U__", 2},
-    {"attiny4", "__AVR_ATtiny4__", 0},
-    {"attiny5", "__AVR_ATtiny5__", 0},
-    {"attiny9", "__AVR_ATtiny9__", 0},
-    {"attiny10", "__AVR_ATtiny10__", 0},
-    {"attiny20", "__AVR_ATtiny20__", 0},
-    {"attiny40", "__AVR_ATtiny40__", 0},
-    {"attiny102", "__AVR_ATtiny102__", 0},
-    {"attiny104", "__AVR_ATtiny104__", 0},
+    {"attiny4", "__AVR_ATtiny4__", 0, true},
+    {"attiny5", "__AVR_ATtiny5__", 0, true},
+    {"attiny9", "__AVR_ATtiny9__", 0, true},
+    {"attiny10", "__AVR_ATtiny10__", 0, true},
+    {"attiny20", "__AVR_ATtiny20__", 0, true},
+    {"attiny40", "__AVR_ATtiny40__", 0, true},
+    {"attiny102", "__AVR_ATtiny102__", 0, true},
+    {"attiny104", "__AVR_ATtiny104__", 0, true},
     {"attiny202", "__AVR_ATtiny202__", 1},
     {"attiny402", "__AVR_ATtiny402__", 1},
     {"attiny204", "__AVR_ATtiny204__", 1},
@@ -325,6 +326,27 @@ void AVRTargetInfo::fillValidCPUList(SmallVectorImpl<StringRef> &Values) const {
     Values.push_back(Info.Name);
 }
 
+bool AVRTargetInfo::setCPU(const std::string &Name) {
+  // Set the ABI and CPU fields if parameter Name is a family name.
+  if (llvm::is_contained(ValidFamilyNames, Name)) {
+    CPU = Name;
+    ABI = Name == "avrtiny" ? "avrtiny" : "avr";
+    return true;
+  }
+
+  // Set the ABI field if parameter Name is a device name.
+  auto It = llvm::find_if(
+      AVRMcus, [&](const MCUInfo &Info) { return Info.Name == Name; });
+  if (It != std::end(AVRMcus)) {
+    CPU = Name;
+    ABI = It->IsTiny ? "avrtiny" : "avr";
+    return true;
+  }
+
+  // Parameter Name is neither valid family name nor valid device name.
+  return false;
+}
+
 void AVRTargetInfo::getTargetDefines(const LangOptions &Opts,
                                      MacroBuilder &Builder) const {
   Builder.defineMacro("AVR");

diff  --git a/clang/lib/Basic/Targets/AVR.h b/clang/lib/Basic/Targets/AVR.h
index a281e2c2cd744..74b012a0923b7 100644
--- a/clang/lib/Basic/Targets/AVR.h
+++ b/clang/lib/Basic/Targets/AVR.h
@@ -74,8 +74,7 @@ class LLVM_LIBRARY_VISIBILITY AVRTargetInfo : public TargetInfo {
     static const char *const GCCRegNames[] = {
         "r0",  "r1",  "r2",  "r3",  "r4",  "r5",  "r6",  "r7",  "r8",  "r9",
         "r10", "r11", "r12", "r13", "r14", "r15", "r16", "r17", "r18", "r19",
-        "r20", "r21", "r22", "r23", "r24", "r25", "X",   "Y",   "Z",   "SP"
-    };
+        "r20", "r21", "r22", "r23", "r24", "r25", "X",   "Y",   "Z",   "SP"};
     return llvm::makeArrayRef(GCCRegNames);
   }
 
@@ -169,15 +168,12 @@ class LLVM_LIBRARY_VISIBILITY AVRTargetInfo : public TargetInfo {
 
   bool isValidCPUName(StringRef Name) const override;
   void fillValidCPUList(SmallVectorImpl<StringRef> &Values) const override;
-  bool setCPU(const std::string &Name) override {
-    bool isValid = isValidCPUName(Name);
-    if (isValid)
-      CPU = Name;
-    return isValid;
-  }
+  bool setCPU(const std::string &Name) override;
+  StringRef getABI() const override { return ABI; }
 
 protected:
   std::string CPU;
+  StringRef ABI;
 };
 
 } // namespace targets

diff  --git a/clang/lib/CodeGen/TargetInfo.cpp b/clang/lib/CodeGen/TargetInfo.cpp
index 9af3004ebcc5b..d83bc9e529a6d 100644
--- a/clang/lib/CodeGen/TargetInfo.cpp
+++ b/clang/lib/CodeGen/TargetInfo.cpp
@@ -19,9 +19,9 @@
 #include "CodeGenFunction.h"
 #include "clang/AST/Attr.h"
 #include "clang/AST/RecordLayout.h"
+#include "clang/Basic/Builtins.h"
 #include "clang/Basic/CodeGenOptions.h"
 #include "clang/Basic/DiagnosticFrontend.h"
-#include "clang/Basic/Builtins.h"
 #include "clang/CodeGen/CGFunctionInfo.h"
 #include "clang/CodeGen/SwiftCallingConv.h"
 #include "llvm/ADT/SmallBitVector.h"
@@ -33,6 +33,7 @@
 #include "llvm/IR/IntrinsicsNVPTX.h"
 #include "llvm/IR/IntrinsicsS390.h"
 #include "llvm/IR/Type.h"
+#include "llvm/Support/MathExtras.h"
 #include "llvm/Support/raw_ostream.h"
 #include <algorithm> // std::sort
 
@@ -8272,32 +8273,93 @@ void M68kTargetCodeGenInfo::setTargetAttributes(
 
 namespace {
 class AVRABIInfo : public DefaultABIInfo {
+private:
+  // The total amount of registers can be used to pass parameters. It is 18 on
+  // AVR, or 6 on AVRTiny.
+  const unsigned ParamRegs;
+  // The total amount of registers can be used to pass return value. It is 8 on
+  // AVR, or 4 on AVRTiny.
+  const unsigned RetRegs;
+
 public:
-  AVRABIInfo(CodeGenTypes &CGT) : DefaultABIInfo(CGT) {}
+  AVRABIInfo(CodeGenTypes &CGT, unsigned NPR, unsigned NRR)
+      : DefaultABIInfo(CGT), ParamRegs(NPR), RetRegs(NRR) {}
+
+  ABIArgInfo classifyReturnType(QualType Ty, bool &LargeRet) const {
+    if (isAggregateTypeForABI(Ty)) {
+      // On AVR, a return struct with size less than or equals to 8 bytes is
+      // returned directly via registers R18-R25. On AVRTiny, a return struct
+      // with size less than or equals to 4 bytes is returned directly via
+      // registers R22-R25.
+      if (getContext().getTypeSize(Ty) <= RetRegs * 8)
+        return ABIArgInfo::getDirect();
+      // A return struct with larger size is returned via a stack
+      // slot, along with a pointer to it as the function's implicit argument.
+      LargeRet = true;
+      return getNaturalAlignIndirect(Ty);
+    }
+    // Otherwise we follow the default way which is compatible.
+    return DefaultABIInfo::classifyReturnType(Ty);
+  }
 
-  ABIArgInfo classifyReturnType(QualType Ty) const {
-    // A return struct with size less than or equal to 8 bytes is returned
-    // directly via registers R18-R25.
-    if (isAggregateTypeForABI(Ty) && getContext().getTypeSize(Ty) <= 64)
+  ABIArgInfo classifyArgumentType(QualType Ty, unsigned &NumRegs) const {
+    unsigned TySize = getContext().getTypeSize(Ty);
+
+    // An int8 type argument always costs two registers like an int16.
+    if (TySize == 8 && NumRegs >= 2) {
+      NumRegs -= 2;
+      return ABIArgInfo::getExtend(Ty);
+    }
+
+    // If the argument size is an odd number of bytes, round up the size
+    // to the next even number.
+    TySize = llvm::alignTo(TySize, 16);
+
+    // Any type including an array/struct type can be passed in rgisters,
+    // if there are enough registers left.
+    if (TySize <= NumRegs * 8) {
+      NumRegs -= TySize / 8;
       return ABIArgInfo::getDirect();
-    else
-      return DefaultABIInfo::classifyReturnType(Ty);
+    }
+
+    // An argument is passed either completely in registers or completely in
+    // memory. Since there are not enough registers left, current argument
+    // and all other unprocessed arguments should be passed in memory.
+    // However we still need to return `ABIArgInfo::getDirect()` other than
+    // `ABIInfo::getNaturalAlignIndirect(Ty)`, otherwise an extra stack slot
+    // will be allocated, so the stack frame layout will be incompatible with
+    // avr-gcc.
+    NumRegs = 0;
+    return ABIArgInfo::getDirect();
   }
 
-  // Just copy the original implementation of DefaultABIInfo::computeInfo(),
-  // since DefaultABIInfo::classify{Return,Argument}Type() are not virtual.
   void computeInfo(CGFunctionInfo &FI) const override {
+    // Decide the return type.
+    bool LargeRet = false;
     if (!getCXXABI().classifyReturnType(FI))
-      FI.getReturnInfo() = classifyReturnType(FI.getReturnType());
+      FI.getReturnInfo() = classifyReturnType(FI.getReturnType(), LargeRet);
+
+    // Decide each argument type. The total number of registers can be used for
+    // arguments depends on several factors:
+    // 1. Arguments of varargs functions are passed on the stack. This applies
+    //    even to the named arguments. So no register can be used.
+    // 2. Total 18 registers can be used on avr and 6 ones on avrtiny.
+    // 3. If the return type is a struct with too large size, two registers
+    //    (out of 18/6) will be cost as an implicit pointer argument.
+    unsigned NumRegs = ParamRegs;
+    if (FI.isVariadic())
+      NumRegs = 0;
+    else if (LargeRet)
+      NumRegs -= 2;
     for (auto &I : FI.arguments())
-      I.info = classifyArgumentType(I.type);
+      I.info = classifyArgumentType(I.type, NumRegs);
   }
 };
 
 class AVRTargetCodeGenInfo : public TargetCodeGenInfo {
 public:
-  AVRTargetCodeGenInfo(CodeGenTypes &CGT)
-      : TargetCodeGenInfo(std::make_unique<AVRABIInfo>(CGT)) {}
+  AVRTargetCodeGenInfo(CodeGenTypes &CGT, unsigned NPR, unsigned NRR)
+      : TargetCodeGenInfo(std::make_unique<AVRABIInfo>(CGT, NPR, NRR)) {}
 
   LangAS getGlobalVarAddressSpace(CodeGenModule &CGM,
                                   const VarDecl *D) const override {
@@ -11270,8 +11332,14 @@ const TargetCodeGenInfo &CodeGenModule::getTargetCodeGenInfo() {
   case llvm::Triple::mips64el:
     return SetCGInfo(new MIPSTargetCodeGenInfo(Types, false));
 
-  case llvm::Triple::avr:
-    return SetCGInfo(new AVRTargetCodeGenInfo(Types));
+  case llvm::Triple::avr: {
+    // For passing parameters, R8~R25 are used on avr, and R18~R25 are used
+    // on avrtiny. For passing return value, R18~R25 are used on avr, and
+    // R22~R25 are used on avrtiny.
+    unsigned NPR = getTarget().getABI() == "avrtiny" ? 6 : 18;
+    unsigned NRR = getTarget().getABI() == "avrtiny" ? 4 : 8;
+    return SetCGInfo(new AVRTargetCodeGenInfo(Types, NPR, NRR));
+  }
 
   case llvm::Triple::aarch64:
   case llvm::Triple::aarch64_32:

diff  --git a/clang/test/CodeGen/avr/argument.c b/clang/test/CodeGen/avr/argument.c
new file mode 100644
index 0000000000000..31bf678c05a54
--- /dev/null
+++ b/clang/test/CodeGen/avr/argument.c
@@ -0,0 +1,116 @@
+// RUN: %clang_cc1 -triple avr -target-cpu atmega328 -emit-llvm %s -o - \
+// RUN:     | FileCheck %s --check-prefix AVR
+// RUN: %clang_cc1 -triple avr -target-cpu attiny40 -emit-llvm %s -o - \
+// RUN:     | FileCheck %s --check-prefix TINY
+
+// NOTE: All arguments are passed via the stack for functions with variable arguments.
+// AVR:      define {{.*}} i8 @foo0(i8 {{.*}}, i8 {{.*}}, ...)
+// TINY:     define {{.*}} i8 @foo0(i8 {{.*}}, i8 {{.*}}, ...)
+// AVR-NOT:  define {{.*}} i8 @foo0(i8 {{.*}} signext {{.*}}, i8 {{.*}} signext {{.*}}, ...)
+// TINY-NOT: define {{.*}} i8 @foo0(i8 {{.*}} signext {{.*}}, i8 {{.*}} signext {{.*}}, ...)
+char foo0(char a, char b, ...) {
+  return a + b;
+}
+
+// NOTE: All arguments are passed via registers on both avr and avrtiny.
+// AVR:  define {{.*}} i8 @foo1(i32 {{.*}}, i8 {{.*}} signext {{.*}})
+// TINY: define {{.*}} i8 @foo1(i32 {{.*}}, i8 {{.*}} signext {{.*}})
+char foo1(long a, char b) {
+  return a + b;
+}
+
+// NOTE: The argument `char c` is passed via registers on avr, while via the stack on avrtiny.
+//       The argument `char b` costs 2 registers, so there is no vacant register left for
+//       `char c` on avrtiny.
+// AVR:      define {{.*}} i8 @foo2(i32 {{.*}}, i8 {{.*}} signext {{.*}}, i8 {{.*}} signext {{.*}})
+// TINY:     define {{.*}} i8 @foo2(i32 {{.*}}, i8 {{.*}} signext {{.*}}, i8 {{.*}})
+// TINY-NOT: define {{.*}} i8 @foo2(i32 {{.*}}, i8 {{.*}} signext {{.*}}, i8 {{.*}} signext {{.*}})
+char foo2(long a, char b, char c) {
+  return a + b + c;
+}
+
+// NOTE: On avr, the argument `a` costs 16 registers and `b` costs 2 registers, so
+//       `c` has to be passed via the stack.
+// AVR:      define {{.*}} i8 @foo3({{.*}}, i8 {{.*}} signext {{.*}}, i8 {{.*}})
+// AVR-NOT:  define {{.*}} i8 @foo3({{.*}}, i8 {{.*}} signext {{.*}}, i8 {{.*}} signext {{.*}})
+// TINY:     define {{.*}} i8 @foo3({{.*}}, i8 {{.*}}, i8 {{.*}})
+// TINY-NOT: define {{.*}} i8 @foo3({{.*}}, i8 {{.*}} signext {{.*}}, i8 {{.*}} signext {{.*}})
+struct s15 {
+  char arr[15];
+};
+char foo3(struct s15 a, char b, char c) {
+  return a.arr[b] + a.arr[c];
+}
+
+// NOTE: On avr, `a` only costs 16 registers, though there are 2 vacant registers,
+//       both `b` and `c` have to be passed via the stack.
+// AVR:      define {{.*}} i8 @foo4({{.*}}, i32 {{.*}}, i8 {{.*}})
+// AVR-NOT:  define {{.*}} i8 @foo4({{.*}}, i32 {{.*}}, i8 {{.*}} signext {{.*}})
+// TINY:     define {{.*}} i8 @foo4({{.*}}, i32 {{.*}}, i8 {{.*}})
+// TINY-NOT: define {{.*}} i8 @foo4({{.*}}, i32 {{.*}}, i8 {{.*}} signext {{.*}})
+char foo4(struct s15 a, long b, char c) {
+  return a.arr[c];
+}
+
+// NOTE: On avrtiny, `a` only costs 4 registers, though there are 2 vacant
+//       registers, both `b` and `c` are passed via the stack.
+// AVR:      define {{.*}} i8 @foo5(i32 {{.*}}, i32 {{.*}}, i8 {{.*}} signext {{.*}})
+// TINY:     define {{.*}} i8 @foo5(i32 {{.*}}, i32 {{.*}}, i8 {{.*}})
+// TINY-NOT: define {{.*}} i8 @foo5(i32 {{.*}}, i32 {{.*}}, i8 {{.*}} signext {{.*}})
+char foo5(long a, long b, char c) {
+  return c + 1;
+}
+
+// NOTE: All arguments are passed via the stack, though all registers are vacant.
+// AVR:      define {{.*}} i8 @foo6({{.*}}, i8 {{.*}})
+// AVR-NOT:  define {{.*}} i8 @foo6({{.*}}, i8 {{.*}} signext {{.*}})
+// TINY:     define {{.*}} i8 @foo6({{.*}}, i8 {{.*}})
+// TINY-NOT: define {{.*}} i8 @foo6({{.*}}, i8 {{.*}} signext {{.*}})
+struct s32 {
+  char arr[32];
+};
+char foo6(struct s32 a, char b) {
+  return a.arr[b];
+}
+
+// NOTE: All arguments are passed via registers on avr. While all arguments are passed
+//       via the stack on avrtiny, though all registers are vacant.
+// AVR:      define {{.*}} i8 @foo7({{.*}}, i8 {{.*}} signext {{.*}})
+// TINY:     define {{.*}} i8 @foo7({{.*}}, i8 {{.*}})
+// TINY-NOT: define {{.*}} i8 @foo7({{.*}}, i8 {{.*}} signext {{.*}})
+char foo7(struct s15 a, char b) {
+  return a.arr[b];
+}
+
+// NOTE: On avr, though `a` only cost 16 registers, `b` has to be passed via the
+//       stack, since there is an implicit pointer argument costs 2 registers.
+// AVR:      define {{.*}} @foo8({{.*}}, {{.*}}, i8 {{.*}})
+// AVR-NOT:  define {{.*}} @foo8({{.*}}, {{.*}}, i8 {{.*}} signext {{.*}})
+// TINY:     define {{.*}} @foo8({{.*}}, {{.*}}, i8 {{.*}})
+// TINY-NOT: define {{.*}} @foo8({{.*}}, {{.*}}, i8 {{.*}} signext {{.*}})
+struct s15 foo8(struct s15 a, char b) {
+  a.arr[0] = b;
+  return a;
+}
+
+// NOTE: On avrtiny, `b` has to be passed via the stack, since there is an
+//       implicit pointer argument costs 2 registers.
+// AVR:      define {{.*}} @foo9({{.*}}, i32 {{.*}}, i8 {{.*}} signext {{.*}})
+// TINY:     define {{.*}} @foo9({{.*}}, i32 {{.*}}, i8 {{.*}})
+// TINY-NOT: define {{.*}} @foo9({{.*}}, i32 {{.*}}, i8 {{.*}} signext {{.*}})
+struct s15 foo9(long a, char b) {
+  struct s15 x;
+  x.arr[0] = b;
+  return x;
+}
+
+// NOTE: All arguments are passed via registers, though there is an implicit
+//       pointer argument costs 2 registers.
+// AVR:  define {{.*}} @fooa({{.*}}, i8 {{.*}} signext {{.*}}, i8 {{.*}} signext {{.*}})
+// TINY: define {{.*}} @fooa({{.*}}, i8 {{.*}} signext {{.*}}, i8 {{.*}} signext {{.*}})
+struct s15 fooa(char a, char b) {
+  struct s15 x;
+  x.arr[0] = a;
+  x.arr[1] = b;
+  return x;
+}

diff  --git a/clang/test/CodeGen/avr/struct.c b/clang/test/CodeGen/avr/struct.c
index 54fb59e4292e8..ecc193acacd27 100644
--- a/clang/test/CodeGen/avr/struct.c
+++ b/clang/test/CodeGen/avr/struct.c
@@ -1,15 +1,23 @@
-// RUN: %clang_cc1 -triple avr -emit-llvm %s -o - | FileCheck %s
+// RUN: %clang_cc1 -triple avr -target-cpu atmega328 -emit-llvm %s -o - \
+// RUN:     | FileCheck %s --check-prefix=AVR
+// RUN: %clang_cc1 -triple avr -target-cpu attiny40 -emit-llvm %s -o - \
+// RUN:     | FileCheck %s --check-prefix=TINY
 
 // Structure that is more than 8 bytes.
 struct s10 {
   int a, b, c, d, e;
 };
 
-// Structure that is less than 8 bytes.
+// Structure that is less than 8 bytes but more than 4 bytes.
 struct s06 {
   int a, b, c;
 };
 
+// Structure that is less than 4 bytes.
+struct s04 {
+  int a, b;
+};
+
 struct s10 foo10(int a, int b, int c) {
   struct s10 a0;
   return a0;
@@ -20,7 +28,21 @@ struct s06 foo06(int a, int b, int c) {
   return a0;
 }
 
-// CHECK: %struct.s10 = type { i16, i16, i16, i16, i16 }
-// CHECK: %struct.s06 = type { i16, i16, i16 }
-// CHECK: define{{.*}} void @foo10(%struct.s10* {{.*}}, i16 noundef %a, i16 noundef %b, i16 noundef %c)
-// CHECK: define{{.*}} %struct.s06 @foo06(i16 noundef %a, i16 noundef %b, i16 noundef %c)
+struct s04 foo04(int a, int b) {
+  struct s04 a0;
+  return a0;
+}
+
+// AVR: %struct.s10 = type { i16, i16, i16, i16, i16 }
+// AVR: %struct.s06 = type { i16, i16, i16 }
+// AVR: %struct.s04 = type { i16, i16 }
+// AVR: define{{.*}} void @foo10(%struct.s10* {{.*}}, i16 noundef %a, i16 noundef %b, i16 noundef %c)
+// AVR: define{{.*}} %struct.s06 @foo06(i16 noundef %a, i16 noundef %b, i16 noundef %c)
+// AVR: define{{.*}} %struct.s04 @foo04(i16 noundef %a, i16 noundef %b)
+
+// TINY: %struct.s10 = type { i16, i16, i16, i16, i16 }
+// TINY: %struct.s06 = type { i16, i16, i16 }
+// TINY: %struct.s04 = type { i16, i16 }
+// TINY: define{{.*}} void @foo10(%struct.s10* {{.*}}, i16 noundef %a, i16 noundef %b, i16 noundef %c)
+// TINY: define{{.*}} void @foo06(%struct.s06* {{.*}}, i16 noundef %a, i16 noundef %b, i16 noundef %c)
+// TINY: define{{.*}} %struct.s04 @foo04(i16 noundef %a, i16 noundef %b)


        


More information about the llvm-branch-commits mailing list