Skip to content

Commit

Permalink
Merge pull request #4803 from kinke/abi_default_indirect_nonpod
Browse files Browse the repository at this point in the history
TargetABI: Default to passing non-PODs indirectly by-value
  • Loading branch information
kinke authored Dec 27, 2024
2 parents 6158df2 + ece9e9e commit eef3229
Show file tree
Hide file tree
Showing 20 changed files with 90 additions and 168 deletions.
4 changes: 0 additions & 4 deletions gen/abi/aarch64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,6 @@ struct AArch64TargetABI : TargetABI {
AArch64TargetABI() {}

bool returnInArg(TypeFunction *tf, bool) override {
if (tf->isref()) {
return false;
}

Type *rt = tf->next->toBasetype();
if (rt->ty == TY::Tstruct || rt->ty == TY::Tsarray) {
auto argTypes = getArgTypes(rt);
Expand Down
50 changes: 26 additions & 24 deletions gen/abi/abi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -157,11 +157,23 @@ llvm::CallingConv::ID TargetABI::callingConv(FuncDeclaration *fdecl) {

//////////////////////////////////////////////////////////////////////////////

bool TargetABI::returnInArg(TypeFunction *tf, bool needsThis) {
// default: use sret for non-PODs or if a same-typed argument would be passed
// byval
Type *rt = tf->next->toBasetype();
return !isPOD(rt) || passByVal(tf, rt);
}

bool TargetABI::preferPassByRef(Type *t) {
// simple base heuristic: use a ref for all types > 2 machine words
return size(t) > 2 * target.ptrsize;
}

bool TargetABI::passByVal(TypeFunction *tf, Type *t) {
// default: all POD structs and static arrays
return DtoIsInMemoryOnly(t) && isPOD(t);
}

//////////////////////////////////////////////////////////////////////////////

void TargetABI::rewriteFunctionType(IrFuncTy &fty) {
Expand All @@ -183,6 +195,15 @@ void TargetABI::rewriteVarargs(IrFuncTy &fty,
}
}

void TargetABI::rewriteArgument(IrFuncTy &fty,
IrFuncTyArg &arg) {
// default: pass non-PODs indirectly by-value
if (!isPOD(arg.type)) {
static IndirectByvalRewrite indirectByvalRewrite;
indirectByvalRewrite.applyTo(arg);
}
}

//////////////////////////////////////////////////////////////////////////////

LLValue *TargetABI::prepareVaStart(DLValue *ap) {
Expand Down Expand Up @@ -223,28 +244,6 @@ const char *TargetABI::objcMsgSendFunc(Type *ret, IrFuncTy &fty, bool directcall

//////////////////////////////////////////////////////////////////////////////

// Some reasonable defaults for when we don't know what ABI to use.
struct UnknownTargetABI : TargetABI {
bool returnInArg(TypeFunction *tf, bool) override {
if (tf->isref()) {
return false;
}

// Return structs and static arrays on the stack. The latter is needed
// because otherwise LLVM tries to actually return the array in a number
// of physical registers, which leads, depending on the target, to
// either horrendous codegen or backend crashes.
Type *rt = tf->next->toBasetype();
return passByVal(tf, rt);
}

bool passByVal(TypeFunction *, Type *t) override {
return DtoIsInMemoryOnly(t);
}
};

//////////////////////////////////////////////////////////////////////////////

TargetABI *TargetABI::getTarget() {
switch (global.params.targetTriple->getArch()) {
case llvm::Triple::x86:
Expand Down Expand Up @@ -283,8 +282,10 @@ TargetABI *TargetABI::getTarget() {
case llvm::Triple::wasm64:
return getWasmTargetABI();
default:
Logger::cout() << "WARNING: Unknown ABI, guessing...\n";
return new UnknownTargetABI;
warning(Loc(),
"unknown target ABI, falling back to generic implementation. C/C++ "
"interop will almost certainly NOT work.");
return new TargetABI;
}
}

Expand All @@ -303,6 +304,7 @@ struct IntrinsicABI : TargetABI {
if (ty->ty != TY::Tstruct) {
return;
}
assert(isPOD(arg.type));
// TODO: Check that no unions are passed in or returned.

LLType *abiTy = DtoUnpaddedStructType(arg.type);
Expand Down
6 changes: 3 additions & 3 deletions gen/abi/abi.h
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ struct TargetABI {
/// caller.
/// The address is passed as additional function parameter using the StructRet
/// attribute.
virtual bool returnInArg(TypeFunction *tf, bool needsThis) = 0;
virtual bool returnInArg(TypeFunction *tf, bool needsThis);

/// Returns true if the specified parameter type (a POD) should be passed by
/// ref for `in` params with -preview=in.
Expand All @@ -147,7 +147,7 @@ struct TargetABI {
/// parameter.
/// The LL caller needs to pass a pointer to the original argument (the memcpy
/// source).
virtual bool passByVal(TypeFunction *tf, Type *t) = 0;
virtual bool passByVal(TypeFunction *tf, Type *t);

/// Returns true if the 'this' argument is to be passed before the 'sret'
/// argument.
Expand All @@ -156,7 +156,7 @@ struct TargetABI {
/// Called to give ABI the chance to rewrite the types
virtual void rewriteFunctionType(IrFuncTy &fty);
virtual void rewriteVarargs(IrFuncTy &fty, std::vector<IrFuncTyArg *> &args);
virtual void rewriteArgument(IrFuncTy &fty, IrFuncTyArg &arg) {}
virtual void rewriteArgument(IrFuncTy &fty, IrFuncTyArg &arg);

/// Prepares a va_start intrinsic call by transforming the D argument (of type
/// va_list) to a low-level value (of type i8*) to be passed to LLVM's
Expand Down
9 changes: 5 additions & 4 deletions gen/abi/arm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@ struct ArmTargetABI : TargetABI {
// AAPCS 5.4 wants composites > 4-bytes returned by arg except for
// Homogeneous Aggregates of up-to 4 float types (6.1.2.1) - an HFA.
// TODO: see if Tsarray should be candidate for HFA.
if (tf->isref())
return false;
Type *rt = tf->next->toBasetype();

if (!isPOD(rt))
Expand All @@ -48,8 +46,7 @@ struct ArmTargetABI : TargetABI {
// clang uses byval for types > 64-bytes, then llvm backend
// converts back to non-byval. Without this special handling the
// optimzer generates bad code (e.g. std.random unittest crash).
t = t->toBasetype();
return ((t->ty == TY::Tsarray || t->ty == TY::Tstruct) && size(t) > 64);
return DtoIsInMemoryOnly(t) && isPOD(t) && size(t) > 64;

// Note: byval can have a codegen problem with -O1 and higher.
// What happens is that load instructions are being incorrectly
Expand All @@ -70,6 +67,10 @@ struct ArmTargetABI : TargetABI {
}

void rewriteArgument(IrFuncTy &fty, IrFuncTyArg &arg) override {
TargetABI::rewriteArgument(fty, arg);
if (arg.rewrite)
return;

// structs and arrays need rewrite as i32 arrays. This keeps data layout
// unchanged when passed in registers r0-r3 and is necessary to match C ABI
// for struct passing. Without out this rewrite, each field or array
Expand Down
26 changes: 4 additions & 22 deletions gen/abi/loongarch64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ struct HardfloatRewrite : BaseBitcastABIRewrite {
struct LoongArch64TargetABI : TargetABI {
private:
HardfloatRewrite hardfloatRewrite;
IndirectByvalRewrite indirectByvalRewrite{};
Integer2Rewrite integer2Rewrite;
IntegerRewrite integerRewrite;

Expand All @@ -117,23 +116,8 @@ struct LoongArch64TargetABI : TargetABI {
}

public:
auto returnInArg(TypeFunction *tf, bool) -> bool override {
if (tf->isref()) {
return false;
}
Type *rt = tf->next->toBasetype();
if (!isPOD(rt)) {
return true;
}
// pass by reference when > 2*GRLEN
return size(rt) > 16;
}

auto passByVal(TypeFunction *, Type *t) -> bool override {
if (!isPOD(t)) {
return false;
}
return size(t) > 16;
bool passByVal(TypeFunction *, Type *t) override {
return isPOD(t) && size(t) > 16;
}

void rewriteVarargs(IrFuncTy &fty,
Expand All @@ -149,11 +133,9 @@ struct LoongArch64TargetABI : TargetABI {
}

void rewriteArgument(IrFuncTy &fty, IrFuncTyArg &arg, bool isVararg) {
if (!isPOD(arg.type)) {
// non-PODs should be passed in memory
indirectByvalRewrite.applyTo(arg);
TargetABI::rewriteArgument(fty, arg);
if (arg.rewrite)
return;
}

if (!isVararg && requireHardfloatRewrite(arg.type)) {
hardfloatRewrite.applyTo(arg);
Expand Down
26 changes: 4 additions & 22 deletions gen/abi/mips64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,29 +24,11 @@ struct MIPS64TargetABI : TargetABI {

explicit MIPS64TargetABI(const bool Is64Bit) : Is64Bit(Is64Bit) {}

bool returnInArg(TypeFunction *tf, bool) override {
if (tf->isref()) {
return false;
}

Type *rt = tf->next->toBasetype();

if (!isPOD(rt))
return true;

// Return structs and static arrays on the stack. The latter is needed
// because otherwise LLVM tries to actually return the array in a number
// of physical registers, which leads, depending on the target, to
// either horrendous codegen or backend crashes.
return (rt->ty == TY::Tstruct || rt->ty == TY::Tsarray);
}

bool passByVal(TypeFunction *, Type *t) override {
TY ty = t->toBasetype()->ty;
return ty == TY::Tstruct || ty == TY::Tsarray;
}

void rewriteArgument(IrFuncTy &fty, IrFuncTyArg &arg) override {
TargetABI::rewriteArgument(fty, arg);
if (arg.rewrite)
return;

// FIXME
}
};
Expand Down
9 changes: 6 additions & 3 deletions gen/abi/nvptx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,16 @@ struct NVPTXTargetABI : TargetABI {
: llvm::CallingConv::PTX_Device;
}
bool passByVal(TypeFunction *, Type *t) override {
t = t->toBasetype();
return ((t->ty == TY::Tsarray || t->ty == TY::Tstruct) && size(t) > 64);
return DtoIsInMemoryOnly(t) && isPOD(t) && size(t) > 64;
}
bool returnInArg(TypeFunction *tf, bool) override {
return !tf->isref() && DtoIsInMemoryOnly(tf->next);
return DtoIsInMemoryOnly(tf->next);
}
void rewriteArgument(IrFuncTy &fty, IrFuncTyArg &arg) override {
TargetABI::rewriteArgument(fty, arg);
if (arg.rewrite)
return;

Type *ty = arg.type->toBasetype();
llvm::Optional<DcomputePointer> ptr;
if (ty->ty == TY::Tstruct &&
Expand Down
14 changes: 6 additions & 8 deletions gen/abi/ppc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,29 +37,27 @@ struct PPCTargetABI : TargetABI {
explicit PPCTargetABI(const bool Is64Bit) : Is64Bit(Is64Bit) {}

bool returnInArg(TypeFunction *tf, bool) override {
if (tf->isref()) {
return false;
}

Type *rt = tf->next->toBasetype();

// The ABI specifies that aggregates of size 8 bytes or less are
// returned in r3/r4 (ppc) or in r3 (ppc64). Looking at the IR
// generated by clang this seems not to be implemented. Regardless
// of size, the aggregate is always returned as sret.
return rt->ty == TY::Tsarray || rt->ty == TY::Tstruct;
return DtoIsInMemoryOnly(rt);
}

bool passByVal(TypeFunction *, Type *t) override {
// On ppc, aggregates are always passed as an indirect value.
// On ppc64, they are always passed by value. However, clang
// used byval for type > 64 bytes.
t = t->toBasetype();
return (t->ty == TY::Tsarray || t->ty == TY::Tstruct) &&
(!Is64Bit || size(t) > 64);
return DtoIsInMemoryOnly(t) && isPOD(t) && (!Is64Bit || size(t) > 64);
}

void rewriteArgument(IrFuncTy &fty, IrFuncTyArg &arg) override {
TargetABI::rewriteArgument(fty, arg);
if (arg.rewrite)
return;

Type *ty = arg.type->toBasetype();

if (ty->ty == TY::Tstruct || ty->ty == TY::Tsarray) {
Expand Down
22 changes: 7 additions & 15 deletions gen/abi/ppc64le.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,26 +29,18 @@ struct PPC64LETargetABI : TargetABI {

explicit PPC64LETargetABI() : hfvaToArray(8) {}

bool returnInArg(TypeFunction *tf, bool) override {
if (tf->isref()) {
return false;
}

Type *rt = tf->next->toBasetype();

if (!isPOD(rt))
return true;

return passByVal(tf, rt);
}

bool passByVal(TypeFunction *, Type *t) override {
t = t->toBasetype();
return t->ty == TY::Tsarray || (t->ty == TY::Tstruct && size(t) > 16 &&
!isHFVA(t, hfvaToArray.maxElements));
return isPOD(t) &&
(t->ty == TY::Tsarray || (t->ty == TY::Tstruct && size(t) > 16 &&
!isHFVA(t, hfvaToArray.maxElements)));
}

void rewriteArgument(IrFuncTy &fty, IrFuncTyArg &arg) override {
TargetABI::rewriteArgument(fty, arg);
if (arg.rewrite)
return;

Type *ty = arg.type->toBasetype();
if (ty->ty == TY::Tstruct || ty->ty == TY::Tsarray) {
if (ty->ty == TY::Tstruct &&
Expand Down
28 changes: 9 additions & 19 deletions gen/abi/riscv64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -163,24 +163,16 @@ struct RISCV64TargetABI : TargetABI {
return pointerTo(Type::tvoid);
}
bool returnInArg(TypeFunction *tf, bool) override {
if (tf->isref()) {
return false;
}
Type *rt = tf->next->toBasetype();
if (!size(rt))
return false;
if (!isPOD(rt))
return true;
return size(rt) > 16;
return !isPOD(rt) || size(rt) > 16;
}
bool passByVal(TypeFunction *, Type *t) override {
if (!size(t))
return false;
if (t->toBasetype()->ty == TY::Tcomplex80) {
t = t->toBasetype();
if (t->ty == TY::Tcomplex80) {
// rewrite it later to bypass the RVal problem
return false;
}
return size(t) > 16;
return isPOD(t) && size(t) > 16;
}

void rewriteVarargs(IrFuncTy &fty,
Expand All @@ -196,7 +188,11 @@ struct RISCV64TargetABI : TargetABI {
}

void rewriteArgument(IrFuncTy &fty, IrFuncTyArg &arg, bool isVararg) {
if (!isVararg && isPOD(arg.type) && requireHardfloatRewrite(arg.type)) {
TargetABI::rewriteArgument(fty, arg);
if (arg.rewrite)
return;

if (!isVararg && requireHardfloatRewrite(arg.type)) {
hardfloatRewrite.applyTo(arg);
return;
}
Expand All @@ -208,12 +204,6 @@ struct RISCV64TargetABI : TargetABI {
return;
}

if (!isPOD(arg.type)) {
// non-PODs should be passed in memory
indirectByvalRewrite.applyTo(arg);
return;
}

if (isAggregate(ty) && size(ty) && size(ty) <= 16) {
if (size(ty) > 8 && DtoAlignment(ty) < 16) {
// pass the aggregate as {int64, int64} to avoid wrong alignment
Expand Down
Loading

0 comments on commit eef3229

Please sign in to comment.