Skip to content

Commit

Permalink
[clang][AArch64] Correctly align HFA arguments when passed on the stack
Browse files Browse the repository at this point in the history
When we pass a AArch64 Homogeneous Floating-Point
Aggregate (HFA) argument with increased alignment
requirements, for example

    struct S {
      __attribute__ ((__aligned__(16))) double v[4];
    };

Clang uses `[4 x double]` for the parameter, which is passed
on the stack at alignment 8, whereas it should be at
alignment 16, following Rule C.4 in
AAPCS (https://github.com/ARM-software/abi-aa/blob/master/aapcs64/aapcs64.rst#642parameter-passing-rules)

Currently we don't have a way to express in LLVM IR the
alignment requirements of the function arguments. The align
attribute is applicable to pointers only, and only for some
special ways of passing arguments (e..g byval). When
implementing AAPCS32/AAPCS64, clang resorts to dubious hacks
of coercing to types, which naturally have the needed
alignment. We don't have enough types to cover all the
cases, though.

This patch introduces a new use of the stackalign attribute
to control stack slot alignment, when and if an argument is
passed in memory.

The attribute align is left as an optimizer hint - it still
applies to pointer types only and pertains to the content of
the pointer, whereas the alignment of the pointer itself is
determined by the stackalign attribute.

For byval arguments, the stackalign attribute assumes the
role, previously perfomed by align, falling back to align if
stackalign` is absent.

On the clang side, when passing arguments using the "direct"
style (cf. `ABIArgInfo::Kind`), now we can optionally
specify an alignment, which is emitted as the new
`stackalign` attribute.

Patch by Momchil Velikov and Lucas Prates.

Differential Revision: https://reviews.llvm.org/D98794
  • Loading branch information
momchil-velikov committed Apr 15, 2021
1 parent 4b414b8 commit f9d932e
Show file tree
Hide file tree
Showing 21 changed files with 258 additions and 73 deletions.
41 changes: 29 additions & 12 deletions clang/include/clang/CodeGen/CGFunctionInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,17 @@ class ABIArgInfo {
llvm::Type *UnpaddedCoerceAndExpandType; // isCoerceAndExpand()
};
union {
unsigned DirectOffset; // isDirect() || isExtend()
unsigned IndirectAlign; // isIndirect()
struct {
unsigned Offset;
unsigned Align;
} DirectAttr; // isDirect() || isExtend()
struct {
unsigned Align;
unsigned AddrSpace;
} IndirectAttr; // isIndirect()
unsigned AllocaFieldIndex; // isInAlloca()
};
Kind TheKind;
unsigned IndirectAddrSpace : 24; // isIndirect()
bool PaddingInReg : 1;
bool InAllocaSRet : 1; // isInAlloca()
bool InAllocaIndirect : 1;// isInAlloca()
Expand All @@ -126,19 +131,20 @@ class ABIArgInfo {

public:
ABIArgInfo(Kind K = Direct)
: TypeData(nullptr), PaddingType(nullptr), DirectOffset(0), TheKind(K),
IndirectAddrSpace(0), PaddingInReg(false), InAllocaSRet(false),
: TypeData(nullptr), PaddingType(nullptr), DirectAttr{0, 0}, TheKind(K),
PaddingInReg(false), InAllocaSRet(false),
InAllocaIndirect(false), IndirectByVal(false), IndirectRealign(false),
SRetAfterThis(false), InReg(false), CanBeFlattened(false),
SignExt(false) {}

static ABIArgInfo getDirect(llvm::Type *T = nullptr, unsigned Offset = 0,
llvm::Type *Padding = nullptr,
bool CanBeFlattened = true) {
bool CanBeFlattened = true, unsigned Align = 0) {
auto AI = ABIArgInfo(Direct);
AI.setCoerceToType(T);
AI.setPaddingType(Padding);
AI.setDirectOffset(Offset);
AI.setDirectAlign(Align);
AI.setCanBeFlattened(CanBeFlattened);
return AI;
}
Expand All @@ -154,6 +160,7 @@ class ABIArgInfo {
AI.setCoerceToType(T);
AI.setPaddingType(nullptr);
AI.setDirectOffset(0);
AI.setDirectAlign(0);
AI.setSignExt(true);
return AI;
}
Expand All @@ -164,6 +171,7 @@ class ABIArgInfo {
AI.setCoerceToType(T);
AI.setPaddingType(nullptr);
AI.setDirectOffset(0);
AI.setDirectAlign(0);
AI.setSignExt(false);
return AI;
}
Expand Down Expand Up @@ -299,11 +307,20 @@ class ABIArgInfo {
// Direct/Extend accessors
unsigned getDirectOffset() const {
assert((isDirect() || isExtend()) && "Not a direct or extend kind");
return DirectOffset;
return DirectAttr.Offset;
}
void setDirectOffset(unsigned Offset) {
assert((isDirect() || isExtend()) && "Not a direct or extend kind");
DirectOffset = Offset;
DirectAttr.Offset = Offset;
}

unsigned getDirectAlign() const {
assert((isDirect() || isExtend()) && "Not a direct or extend kind");
return DirectAttr.Align;
}
void setDirectAlign(unsigned Align) {
assert((isDirect() || isExtend()) && "Not a direct or extend kind");
DirectAttr.Align = Align;
}

bool isSignExt() const {
Expand Down Expand Up @@ -369,11 +386,11 @@ class ABIArgInfo {
// Indirect accessors
CharUnits getIndirectAlign() const {
assert((isIndirect() || isIndirectAliased()) && "Invalid kind!");
return CharUnits::fromQuantity(IndirectAlign);
return CharUnits::fromQuantity(IndirectAttr.Align);
}
void setIndirectAlign(CharUnits IA) {
assert((isIndirect() || isIndirectAliased()) && "Invalid kind!");
IndirectAlign = IA.getQuantity();
IndirectAttr.Align = IA.getQuantity();
}

bool getIndirectByVal() const {
Expand All @@ -387,12 +404,12 @@ class ABIArgInfo {

unsigned getIndirectAddrSpace() const {
assert(isIndirectAliased() && "Invalid kind!");
return IndirectAddrSpace;
return IndirectAttr.AddrSpace;
}

void setIndirectAddrSpace(unsigned AddrSpace) {
assert(isIndirectAliased() && "Invalid kind!");
IndirectAddrSpace = AddrSpace;
IndirectAttr.AddrSpace = AddrSpace;
}

bool getIndirectRealign() const {
Expand Down
1 change: 1 addition & 0 deletions clang/lib/CodeGen/CGCall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2363,6 +2363,7 @@ void CodeGenModule::ConstructAttributeList(
Attrs.addAttribute(llvm::Attribute::Nest);
else if (AI.getInReg())
Attrs.addAttribute(llvm::Attribute::InReg);
Attrs.addStackAlignmentAttr(llvm::MaybeAlign(AI.getDirectAlign()));
break;

case ABIArgInfo::Indirect: {
Expand Down
13 changes: 12 additions & 1 deletion clang/lib/CodeGen/TargetInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5690,8 +5690,19 @@ AArch64ABIInfo::classifyArgumentType(QualType Ty, bool IsVariadic,
// In variadic functions on Windows, all composite types are treated alike,
// no special handling of HFAs/HVAs.
if (!IsWinVariadic && isHomogeneousAggregate(Ty, Base, Members)) {
if (Kind != AArch64ABIInfo::AAPCS)
return ABIArgInfo::getDirect(
llvm::ArrayType::get(CGT.ConvertType(QualType(Base, 0)), Members));

// For alignment adjusted HFAs, cap the argument alignment to 16, leave it
// default otherwise.
unsigned Align =
getContext().getTypeUnadjustedAlignInChars(Ty).getQuantity();
unsigned BaseAlign = getContext().getTypeAlignInChars(Base).getQuantity();
Align = (Align > BaseAlign && Align >= 16) ? 16 : 0;
return ABIArgInfo::getDirect(
llvm::ArrayType::get(CGT.ConvertType(QualType(Base, 0)), Members));
llvm::ArrayType::get(CGT.ConvertType(QualType(Base, 0)), Members), 0,
nullptr, true, Align);
}

// Aggregates <= 16 bytes are passed directly in registers or on the stack.
Expand Down
69 changes: 69 additions & 0 deletions clang/test/CodeGen/aarch64-args-hfa.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// RUN: %clang_cc1 -triple aarch64-none-eabi -emit-llvm -o - %s | FileCheck %s --check-prefixes=CHECK,CHECK-AAPCS
// RUN: %clang_cc1 -triple arm64-apple-ios7.0 -target-abi darwinpcs -emit-llvm -o - %s | FileCheck %s --check-prefixes=CHECK,CHECK-DARWIN
// RUN: %clang_cc1 -triple aarch64-linux-gnu -emit-llvm -o - -x c %s | FileCheck %s --check-prefixes=CHECK,CHECK-AAPCS

typedef struct {
float v[2];
} S0;

// CHECK: define{{.*}} float @f0([2 x float] %h.coerce)
float f0(S0 h) {
return h.v[0];
}

// CHECK: define{{.*}} float @f0_call()
// CHECK: %call = call float @f0([2 x float] %1)
float f0_call() {
S0 h = {1.0f, 2.0f};
return f0(h);
}
typedef struct {
double v[2];
} S1;

// CHECK: define{{.*}} double @f1([2 x double] %h.coerce)
double f1(S1 h) {
return h.v[0];
}

// CHECK: define{{.*}} double @f1_call()
// CHECK: %call = call double @f1([2 x double] %1
double f1_call() {
S1 h = {1.0, 2.0};
return f1(h);
}
typedef struct {
__attribute__((__aligned__(16))) double v[2];
} S2;

// CHECK-AAPCS: define{{.*}} double @f2([2 x double] alignstack(16) %h.coerce)
// CHECK-DARWIN: define{{.*}} double @f2([2 x double] %h.coerce)
double f2(S2 h) {
return h.v[0];
}

// CHECK: define{{.*}} double @f2_call()
// CHECK-AAPCS: %call = call double @f2([2 x double] alignstack(16) %1)
// CHECK-DARWIN: %call = call double @f2([2 x double] %1
double f2_call() {
S2 h = {1.0, 2.0};
return f2(h);
}

typedef struct {
__attribute__((__aligned__(32))) double v[4];
} S3;

// CHECK-AAPCS: define{{.*}} double @f3([4 x double] alignstack(16) %h.coerce)
// CHECK-DARWIN: define{{.*}} double @f3([4 x double] %h.coerce)
double f3(S3 h) {
return h.v[0];
}

// CHECK: define{{.*}} double @f3_call()
// CHECK-AAPCS: %call = call double @f3([4 x double] alignstack(16) %1)
// CHECK-DARWIN: %call = call double @f3([4 x double] %1
double f3_call() {
S3 h = {1.0, 2.0};
return f3(h);
}
9 changes: 9 additions & 0 deletions llvm/docs/LangRef.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1300,6 +1300,15 @@ Currently, only the following parameter attributes are defined:
undefined. Note that this does not refer to padding introduced by the
type's storage representation.

``alignstack(<n>)``
This indicates the alignment that should be considered by the backend when
assigning this parameter to a stack slot during calling convention
lowering. The enforcement of the specified alignment is target-dependent,
as target-specific calling convention rules may override this value. This
attribute serves the purpose of carrying language specific alignment
information that is not mapped to base types in the backend (for example,
over-alignment specification through language attributes).

.. _gc:

Garbage Collector Strategy Names
Expand Down
33 changes: 15 additions & 18 deletions llvm/include/llvm/CodeGen/TargetCallingConv.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ namespace ISD {
unsigned IsHva : 1; ///< HVA field for
unsigned IsHvaStart : 1; ///< HVA structure start
unsigned IsSecArgPass : 1; ///< Second argument
unsigned ByValOrByRefAlign : 4; ///< Log 2 of byval/byref alignment
unsigned MemAlign : 4; ///< Log 2 of alignment when arg is passed in memory
///< (including byval/byref)
unsigned OrigAlign : 5; ///< Log 2 of original alignment
unsigned IsInConsecutiveRegsLast : 1;
unsigned IsInConsecutiveRegs : 1;
Expand All @@ -55,18 +56,12 @@ namespace ISD {

unsigned PointerAddrSpace; ///< Address space of pointer argument

/// Set the alignment used by byref or byval parameters.
void setAlignImpl(Align A) {
ByValOrByRefAlign = encode(A);
assert(getNonZeroByValAlign() == A && "bitfield overflow");
}

public:
ArgFlagsTy()
: IsZExt(0), IsSExt(0), IsInReg(0), IsSRet(0), IsByVal(0), IsByRef(0),
IsNest(0), IsReturned(0), IsSplit(0), IsInAlloca(0), IsPreallocated(0),
IsSplitEnd(0), IsSwiftSelf(0), IsSwiftError(0), IsCFGuardTarget(0),
IsHva(0), IsHvaStart(0), IsSecArgPass(0), ByValOrByRefAlign(0),
IsHva(0), IsHvaStart(0), IsSecArgPass(0), MemAlign(0),
OrigAlign(0), IsInConsecutiveRegsLast(0), IsInConsecutiveRegs(0),
IsCopyElisionCandidate(0), IsPointer(0), ByValOrByRefSize(0),
PointerAddrSpace(0) {
Expand Down Expand Up @@ -141,24 +136,26 @@ namespace ISD {
bool isPointer() const { return IsPointer; }
void setPointer() { IsPointer = 1; }

Align getNonZeroByValAlign() const {
MaybeAlign A = decodeMaybeAlign(ByValOrByRefAlign);
assert(A && "ByValAlign must be defined");
return *A;
Align getNonZeroMemAlign() const {
return decodeMaybeAlign(MemAlign).valueOrOne();
}
void setByValAlign(Align A) {
assert(isByVal() && !isByRef());
setAlignImpl(A);

void setMemAlign(Align A) {
MemAlign = encode(A);
assert(getNonZeroMemAlign() == A && "bitfield overflow");
}

void setByRefAlign(Align A) {
assert(!isByVal() && isByRef());
setAlignImpl(A);
Align getNonZeroByValAlign() const {
assert(isByVal());
MaybeAlign A = decodeMaybeAlign(MemAlign);
assert(A && "ByValAlign must be defined");
return *A;
}

Align getNonZeroOrigAlign() const {
return decodeMaybeAlign(OrigAlign).valueOrOne();
}

void setOrigAlign(Align A) {
OrigAlign = encode(A);
assert(getNonZeroOrigAlign() == A && "bitfield overflow");
Expand Down
2 changes: 2 additions & 0 deletions llvm/include/llvm/IR/Argument.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ class Argument final : public Value {
/// If this is a byval or inalloca argument, return its alignment.
MaybeAlign getParamAlign() const;

MaybeAlign getParamStackAlign() const;

/// If this is a byval argument, return its type.
Type *getParamByValType() const;

Expand Down
3 changes: 3 additions & 0 deletions llvm/include/llvm/IR/Attributes.h
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,9 @@ class AttributeList {
/// Return the alignment for the specified function parameter.
MaybeAlign getParamAlignment(unsigned ArgNo) const;

/// Return the stack alignment for the specified function parameter.
MaybeAlign getParamStackAlignment(unsigned ArgNo) const;

/// Return the byval type for the specified function parameter.
Type *getParamByValType(unsigned ArgNo) const;

Expand Down
4 changes: 4 additions & 0 deletions llvm/include/llvm/IR/Function.h
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,10 @@ class Function : public GlobalObject, public ilist_node<Function> {
return AttributeSets.getParamAlignment(ArgNo);
}

MaybeAlign getParamStackAlign(unsigned ArgNo) const {
return AttributeSets.getParamStackAlignment(ArgNo);
}

/// Extract the byval type for a parameter.
Type *getParamByValType(unsigned ArgNo) const {
return AttributeSets.getParamByValType(ArgNo);
Expand Down
4 changes: 4 additions & 0 deletions llvm/include/llvm/IR/InstrTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -1731,6 +1731,10 @@ class CallBase : public Instruction {
return Attrs.getParamAlignment(ArgNo);
}

MaybeAlign getParamStackAlign(unsigned ArgNo) const {
return Attrs.getParamStackAlignment(ArgNo);
}

/// Extract the byval type for a call or parameter.
Type *getParamByValType(unsigned ArgNo) const {
Type *Ty = Attrs.getParamByValType(ArgNo);
Expand Down
8 changes: 7 additions & 1 deletion llvm/lib/AsmParser/LLParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1715,6 +1715,13 @@ bool LLParser::parseOptionalParamAttrs(AttrBuilder &B) {
B.addAlignmentAttr(Alignment);
continue;
}
case lltok::kw_alignstack: {
unsigned Alignment;
if (parseOptionalStackAlignment(Alignment))
return true;
B.addStackAlignmentAttr(Alignment);
continue;
}
case lltok::kw_byval: {
Type *Ty;
if (parseRequiredTypeAttr(Ty, lltok::kw_byval))
Expand Down Expand Up @@ -1783,7 +1790,6 @@ bool LLParser::parseOptionalParamAttrs(AttrBuilder &B) {
case lltok::kw_zeroext: B.addAttribute(Attribute::ZExt); break;
case lltok::kw_immarg: B.addAttribute(Attribute::ImmArg); break;

case lltok::kw_alignstack:
case lltok::kw_alwaysinline:
case lltok::kw_argmemonly:
case lltok::kw_builtin:
Expand Down
16 changes: 11 additions & 5 deletions llvm/lib/CodeGen/GlobalISel/CallLowering.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ void CallLowering::setArgFlags(CallLowering::ArgInfo &Arg, unsigned OpIdx,
const AttributeList &Attrs = FuncInfo.getAttributes();
addArgFlagsFromAttributes(Flags, Attrs, OpIdx);

Align MemAlign;
if (Flags.isByVal() || Flags.isInAlloca() || Flags.isPreallocated()) {
Type *ElementTy = cast<PointerType>(Arg.Ty)->getElementType();

Expand All @@ -162,13 +163,18 @@ void CallLowering::setArgFlags(CallLowering::ArgInfo &Arg, unsigned OpIdx,

// For ByVal, alignment should be passed from FE. BE will guess if
// this info is not there but there are cases it cannot get right.
Align FrameAlign;
if (auto ParamAlign = FuncInfo.getParamAlign(OpIdx - 1))
FrameAlign = *ParamAlign;
if (auto ParamAlign = FuncInfo.getParamStackAlign(OpIdx - 1))
MemAlign = *ParamAlign;
else if ((ParamAlign = FuncInfo.getParamAlign(OpIdx - 1)))
MemAlign = *ParamAlign;
else
FrameAlign = Align(getTLI()->getByValTypeAlignment(ElementTy, DL));
Flags.setByValAlign(FrameAlign);
MemAlign = Align(getTLI()->getByValTypeAlignment(ElementTy, DL));
} else if (auto ParamAlign = FuncInfo.getParamStackAlign(OpIdx - 1)) {
MemAlign = *ParamAlign;
} else {
MemAlign = Align(DL.getABITypeAlign(Arg.Ty));
}
Flags.setMemAlign(MemAlign);
Flags.setOrigAlign(DL.getABITypeAlign(Arg.Ty));

// Don't try to use the returned attribute if the argument is marked as
Expand Down
Loading

0 comments on commit f9d932e

Please sign in to comment.