Skip to content

Commit

Permalink
Merge pull request #856 from kinke/win64abi
Browse files Browse the repository at this point in the history
Win64 ABI fix: return non-POD structs via sret.
  • Loading branch information
redstar committed Apr 8, 2015
2 parents b1ff093 + 626875d commit 1dacce4
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 97 deletions.
116 changes: 98 additions & 18 deletions gen/abi-generic.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,64 @@
#include "gen/structs.h"
#include "gen/tollvm.h"

struct LLTypeMemoryLayout
{
// Structs and static arrays are folded recursively to scalars or anonymous structs.
// Pointer types are folded to an integer type.
static LLType* fold(LLType* type)
{
// T* => integer
if (type->isPointerTy())
return LLIntegerType::get(gIR->context(), getTypeBitSize(type));

if (LLStructType* structType = isaStruct(type))
{
unsigned numElements = structType->getNumElements();

// fold each element
std::vector<LLType*> elements;
elements.reserve(numElements);
for (unsigned i = 0; i < numElements; ++i)
elements.push_back(fold(structType->getElementType(i)));

// single element? then discard wrapping struct
if (numElements == 1)
return elements[0];

return LLStructType::get(gIR->context(), elements, structType->isPacked());
}

if (LLArrayType* arrayType = isaArray(type))
{
unsigned numElements = arrayType->getNumElements();
LLType* foldedElementType = fold(arrayType->getElementType());

// single element? then fold to scalar
if (numElements == 1)
return foldedElementType;

// otherwise: convert to struct of N folded elements
std::vector<LLType*> elements(numElements, foldedElementType);
return LLStructType::get(gIR->context(), elements);
}

return type;
}

// Checks two LLVM types for memory-layout equivalency.
static bool typesAreEquivalent(LLType* a, LLType* b)
{
if (a == b)
return true;
if (!a || !b)
return false;

return fold(a) == fold(b);
}
};

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

/// Removes padding fields for (non-union-containing!) structs
struct RemoveStructPadding : ABIRewrite {
/// get a rewritten value back to its original form
Expand Down Expand Up @@ -58,6 +116,44 @@ struct RemoveStructPadding : ABIRewrite {
*/
struct IntegerRewrite : ABIRewrite
{
static LLType* getIntegerType(unsigned minSizeInBytes)
{
if (minSizeInBytes > 8)
return NULL;

unsigned size = minSizeInBytes;
switch (minSizeInBytes) {
case 0:
size = 1;
break;
case 3:
size = 4;
break;
case 5:
case 6:
case 7:
size = 8;
break;
default:
break;
}

return LLIntegerType::get(gIR->context(), size * 8);
}

static bool isObsoleteFor(LLType* llType)
{
if (!llType->isSized()) // e.g., opaque types
{
IF_LOG Logger::cout() << "IntegerRewrite: not rewriting non-sized type "
<< *llType << '\n';
return true;
}

LLType* integerType = getIntegerType(getTypeStoreSize(llType));
return LLTypeMemoryLayout::typesAreEquivalent(llType, integerType);
}

LLValue* get(Type* dty, DValue* dv)
{
LLValue* integer = dv->getRVal();
Expand All @@ -77,29 +173,13 @@ struct IntegerRewrite : ABIRewrite
{
assert(dty == dv->getType());
LLValue* address = getAddressOf(dv);
LLType* integerType = type(dty, NULL);
LLType* integerType = getIntegerType(dty->size());
return loadFromMemory(address, integerType, ".IntegerRewrite_putResult");
}

LLType* type(Type* t, LLType*)
{
unsigned size = t->size();
switch (size) {
case 0:
size = 1;
break;
case 3:
size = 4;
break;
case 5:
case 6:
case 7:
size = 8;
break;
default:
break;
}
return LLIntegerType::get(gIR->context(), size * 8);
return getIntegerType(t->size());
}
};

Expand Down
14 changes: 7 additions & 7 deletions gen/abi-ppc64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,12 @@ struct PPC64TargetABI : TargetABI {
void rewriteFunctionType(TypeFunction* tf, IrFuncTy &fty)
{
// EXPLICIT PARAMETERS

for (IrFuncTy::ArgIter I = fty.args.begin(), E = fty.args.end(); I != E; ++I)
{
IrFuncTyArg& arg = **I;

if (arg.byref)
continue;

rewriteArgument(fty, arg);
if (!arg.byref)
rewriteArgument(fty, arg);
}
}

Expand All @@ -86,8 +83,11 @@ struct PPC64TargetABI : TargetABI {
{
if (canRewriteAsInt(ty))
{
arg.rewrite = &integerRewrite;
arg.ltype = integerRewrite.type(arg.type, arg.ltype);
if (!IntegerRewrite::isObsoleteFor(arg.ltype))
{
arg.rewrite = &integerRewrite;
arg.ltype = integerRewrite.type(arg.type, arg.ltype);
}
}
else
{
Expand Down
78 changes: 41 additions & 37 deletions gen/abi-win64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,16 @@ struct Win64TargetABI : TargetABI
void rewriteArgument(IrFuncTy& fty, IrFuncTyArg& arg);

private:
// Returns true if the D type is a composite (struct/static array/complex number).
bool isComposite(Type* t)
// Returns true if the D type is an aggregate:
// * struct
// * static/dynamic array
// * delegate
// * complex number
bool isAggregate(Type* t)
{
return t->ty == Tstruct || t->ty == Tsarray
|| t->iscomplex(); // treat complex numbers as structs too
TY ty = t->ty;
return ty == Tstruct || ty == Tsarray || ty == Tarray || ty == Tdelegate
|| t->iscomplex();
}

// Returns true if the D type can be bit-cast to an integer of the same size.
Expand All @@ -75,10 +80,11 @@ struct Win64TargetABI : TargetABI
bool isPassedWithByvalSemantics(Type* t)
{
return
// * structs/static arrays/complex numbers which can NOT be rewritten as integers
(isComposite(t) && !canRewriteAsInt(t)) ||
// * aggregates which can NOT be rewritten as integers
// (size > 64 bits or not a power of 2)
(isAggregate(t) && !canRewriteAsInt(t)) ||
// * 80-bit real and ireal
((t->ty == Tfloat80 || t->ty == Timaginary80) && realIs80bits());
(realIs80bits() && (t->ty == Tfloat80 || t->ty == Timaginary80));
}
};

Expand Down Expand Up @@ -113,45 +119,36 @@ bool Win64TargetABI::returnInArg(TypeFunction* tf)

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

// * everything <= 64 bits and of a size that is a power of 2
// (incl. 2x32-bit cfloat) is returned in a register (RAX, or
// * let LLVM return 80-bit real/ireal on the x87 stack, for DMD compliance
if (realIs80bits() && (rt->ty == Tfloat80 || rt->ty == Timaginary80))
return false;

// * all POD types <= 64 bits and of a size that is a power of 2
// (incl. 2x32-bit cfloat) are returned in a register (RAX, or
// XMM0 for single float/ifloat/double/idouble)
// * all other structs/static arrays/complex numbers and 80-bit
// real/ireal are returned via struct-return (sret)
return isPassedWithByvalSemantics(rt);
// * all other types are returned via struct-return (sret)
return (rt->ty == Tstruct && !((TypeStruct*)rt)->sym->isPOD())
|| isPassedWithByvalSemantics(rt);
}

bool Win64TargetABI::passByVal(Type* t)
{
t = t->toBasetype();

// FIXME: LLVM doesn't support ByVal on Win64 yet
//return isPassedWithByvalSemantics(t);
return false;
}

void Win64TargetABI::rewriteFunctionType(TypeFunction* tf, IrFuncTy &fty)
{
// RETURN VALUE
if (!tf->isref)
{
Type* rt = fty.ret->type->toBasetype();
if (isComposite(rt) && canRewriteAsInt(rt))
{
fty.ret->rewrite = &integerRewrite;
fty.ret->ltype = integerRewrite.type(fty.ret->type, fty.ret->ltype);
}
}
if (!fty.ret->byref && fty.ret->type->toBasetype()->ty != Tvoid)
rewriteArgument(fty, *fty.ret);

// EXPLICIT PARAMETERS
for (IrFuncTy::ArgIter I = fty.args.begin(), E = fty.args.end(); I != E; ++I)
{
IrFuncTyArg& arg = **I;

if (arg.byref)
continue;

rewriteArgument(fty, arg);
if (!arg.byref)
rewriteArgument(fty, arg);
}

// extern(D): reverse parameter order for non variadics, for DMD-compliance
Expand All @@ -161,15 +158,10 @@ void Win64TargetABI::rewriteFunctionType(TypeFunction* tf, IrFuncTy &fty)

void Win64TargetABI::rewriteArgument(IrFuncTy& fty, IrFuncTyArg& arg)
{
Type* ty = arg.type->toBasetype();
LLType* originalLType = arg.ltype;
Type* t = arg.type->toBasetype();

if (isComposite(ty) && canRewriteAsInt(ty))
{
arg.rewrite = &integerRewrite;
arg.ltype = integerRewrite.type(arg.type, arg.ltype);
}
// FIXME: this should actually be handled by LLVM and the ByVal arg attribute
else if (isPassedWithByvalSemantics(ty))
if (isPassedWithByvalSemantics(t))
{
// these types are passed byval:
// the caller allocates a copy and then passes a pointer to the copy
Expand All @@ -182,4 +174,16 @@ void Win64TargetABI::rewriteArgument(IrFuncTy& fty, IrFuncTyArg& arg)
.add(LDC_ATTRIBUTE(NoAlias))
.add(LDC_ATTRIBUTE(NoCapture));
}
else if (isAggregate(t) && canRewriteAsInt(t) && !IntegerRewrite::isObsoleteFor(originalLType))
{
arg.rewrite = &integerRewrite;
arg.ltype = integerRewrite.type(arg.type, arg.ltype);
}

IF_LOG if (arg.rewrite)
{
Logger::println("Rewriting argument type %s", t->toChars());
LOG_SCOPE;
Logger::cout() << *originalLType << " => " << *arg.ltype << '\n';
}
}
37 changes: 2 additions & 35 deletions gen/abi-x86-64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,39 +94,6 @@ namespace {
}
} // namespace dmd_abi

// Checks two LLVM types for memory-layout equivalency.
// A pointer type is equivalent to any other pointer type.
bool typesAreEquivalent(LLType* a, LLType* b) {
if (a == b)
return true;
if (!a || !b)
return false;

LLStructType* structA;
while ((structA = isaStruct(a)) && structA->getNumElements() == 1)
a = structA->getElementType(0);

LLStructType* structB;
while ((structB = isaStruct(b)) && structB->getNumElements() == 1)
b = structB->getElementType(0);

if (a == b || (a->isPointerTy() && b->isPointerTy()))
return true;

if (!(structA && structB) ||
structA->isPacked() != structB->isPacked() ||
structA->getNumElements() != structB->getNumElements()) {
return false;
}

for (unsigned i = 0; i < structA->getNumElements(); ++i) {
if (!typesAreEquivalent(structA->getElementType(i), structB->getElementType(i)))
return false;
}

return true;
}

LLType* getAbiType(Type* ty) {
return dmd_abi::getAbiType(ty->toBasetype());
}
Expand Down Expand Up @@ -316,7 +283,7 @@ void X86_64TargetABI::rewriteArgument(IrFuncTyArg& arg, RegCount& regCount) {
Type* t = arg.type->toBasetype();

LLType* abiTy = getAbiType(t);
if (abiTy && !typesAreEquivalent(abiTy, originalLType)) {
if (abiTy && !LLTypeMemoryLayout::typesAreEquivalent(abiTy, originalLType)) {
IF_LOG {
Logger::println("Rewriting argument type %s", t->toChars());
LOG_SCOPE;
Expand All @@ -343,7 +310,7 @@ void X86_64TargetABI::rewriteFunctionType(TypeFunction* tf, IrFuncTy &fty) {
regCount = RegCount(); // initialize

// RETURN VALUE
if (!tf->isref && !fty.arg_sret && tf->next->toBasetype()->ty != Tvoid) {
if (!fty.ret->byref && fty.ret->type->toBasetype()->ty != Tvoid) {
Logger::println("x86-64 ABI: Transforming return type");
LOG_SCOPE;
RegCount dummy;
Expand Down

0 comments on commit 1dacce4

Please sign in to comment.