Skip to content

Commit

Permalink
[clang][AVR] Implement standard calling convention for AVR and AVRTiny
Browse files Browse the repository at this point in the history
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
  • Loading branch information
benshi001 committed Mar 24, 2022
1 parent cf198e9 commit 51585aa
Show file tree
Hide file tree
Showing 5 changed files with 263 additions and 39 deletions.
40 changes: 31 additions & 9 deletions clang/lib/Basic/Targets/AVR.cpp
Expand Up @@ -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.
};

// NOTE: This list has been synchronized with gcc-avr 5.4.0 and avr-libc 2.0.0.
Expand Down Expand Up @@ -282,14 +283,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},
Expand Down Expand Up @@ -340,6 +341,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");
Expand Down
12 changes: 4 additions & 8 deletions clang/lib/Basic/Targets/AVR.h
Expand Up @@ -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);
}

Expand Down Expand Up @@ -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
Expand Down
100 changes: 84 additions & 16 deletions clang/lib/CodeGen/TargetInfo.cpp
Expand Up @@ -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"
Expand All @@ -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

Expand Down Expand Up @@ -8278,32 +8279,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 {
Expand Down Expand Up @@ -11280,8 +11342,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:
Expand Down
116 changes: 116 additions & 0 deletions 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;
}

0 comments on commit 51585aa

Please sign in to comment.