mirror of
https://github.com/xomboverlord/ldc.git
synced 2026-01-14 20:03:14 +01:00
- Implement x86-64 extern(C), hopefully correctly.
- Tried to be a bit smarter about extern(D) while I was there.
Interestingly, this code seems to be generating more efficient code than
gcc and llvm-gcc in some edge cases, like returning a `{ [7 x i8] }` loaded from
a stack slot from an extern(C) function. (gcc generates 7 1-byte loads, while
this code generates a 4-byte, a 2-byte and a 1-byte load)
I also added some changes to make sure structs being returned from functions or
passed in as parameters are stored in memory where the rest of the backend seems
to expect them to be. These should be removed when support for first-class
aggregates improves.
556 lines
17 KiB
C++
556 lines
17 KiB
C++
#include "gen/llvm.h"
|
|
|
|
#include "mtype.h"
|
|
#include "declaration.h"
|
|
|
|
#include "gen/tollvm.h"
|
|
#include "gen/llvmhelpers.h"
|
|
#include "gen/irstate.h"
|
|
#include "gen/dvalue.h"
|
|
#include "gen/functions.h"
|
|
#include "gen/abi.h"
|
|
|
|
#include "gen/logger.h"
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
TypeFunction* DtoTypeFunction(DValue* fnval)
|
|
{
|
|
Type* type = fnval->getType()->toBasetype();
|
|
if (type->ty == Tfunction)
|
|
{
|
|
return (TypeFunction*)type;
|
|
}
|
|
else if (type->ty == Tdelegate)
|
|
{
|
|
Type* next = type->nextOf();
|
|
assert(next->ty == Tfunction);
|
|
return (TypeFunction*)next;
|
|
}
|
|
|
|
assert(0 && "cant get TypeFunction* from non lazy/function/delegate");
|
|
return 0;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
unsigned DtoCallingConv(Loc loc, LINK l)
|
|
{
|
|
if (l == LINKc || l == LINKcpp || l == LINKintrinsic)
|
|
return llvm::CallingConv::C;
|
|
else if (l == LINKd || l == LINKdefault)
|
|
{
|
|
//TODO: StdCall is not a good base on Windows due to extra name mangling
|
|
// applied there
|
|
if (global.params.cpu == ARCHx86)
|
|
return (global.params.os != OSWindows) ? llvm::CallingConv::X86_StdCall : llvm::CallingConv::C;
|
|
else
|
|
return llvm::CallingConv::Fast;
|
|
}
|
|
// on the other hand, here, it's exactly what we want!!! TODO: right?
|
|
else if (l == LINKwindows)
|
|
return llvm::CallingConv::X86_StdCall;
|
|
else
|
|
{
|
|
error(loc, "unsupported calling convention");
|
|
fatal();
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
DValue* DtoVaArg(Loc& loc, Type* type, Expression* valistArg)
|
|
{
|
|
DValue* expelem = valistArg->toElem(gIR);
|
|
const LLType* llt = DtoType(type);
|
|
if (DtoIsPassedByRef(type))
|
|
llt = getPtrToType(llt);
|
|
// issue a warning for broken va_arg instruction.
|
|
if (global.params.cpu != ARCHx86)
|
|
warning("%s: va_arg for C variadic functions is probably broken for anything but x86", loc.toChars());
|
|
// done
|
|
return new DImValue(type, gIR->ir->CreateVAArg(expelem->getLVal(), llt, "tmp"));
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
LLValue* DtoCallableValue(DValue* fn)
|
|
{
|
|
Type* type = fn->getType()->toBasetype();
|
|
if (type->ty == Tfunction)
|
|
{
|
|
return fn->getRVal();
|
|
}
|
|
else if (type->ty == Tdelegate)
|
|
{
|
|
if (fn->isLVal())
|
|
{
|
|
LLValue* dg = fn->getLVal();
|
|
LLValue* funcptr = DtoGEPi(dg, 0, 1);
|
|
return DtoLoad(funcptr);
|
|
}
|
|
else
|
|
{
|
|
LLValue* dg = fn->getRVal();
|
|
assert(isaStruct(dg));
|
|
return gIR->ir->CreateExtractValue(dg, 1, ".funcptr");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
assert(0 && "not a callable type");
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
const LLFunctionType* DtoExtractFunctionType(const LLType* type)
|
|
{
|
|
if (const LLFunctionType* fty = isaFunction(type))
|
|
return fty;
|
|
else if (const LLPointerType* pty = isaPointer(type))
|
|
{
|
|
if (const LLFunctionType* fty = isaFunction(pty->getElementType()))
|
|
return fty;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void DtoBuildDVarArgList(std::vector<LLValue*>& args, std::vector<llvm::AttributeWithIndex>& attrs, TypeFunction* tf, Expressions* arguments, size_t argidx)
|
|
{
|
|
Logger::println("doing d-style variadic arguments");
|
|
|
|
std::vector<const LLType*> vtypes;
|
|
|
|
// number of non variadic args
|
|
int begin = tf->parameters->dim;
|
|
Logger::println("num non vararg params = %d", begin);
|
|
|
|
// get n args in arguments list
|
|
size_t n_arguments = arguments ? arguments->dim : 0;
|
|
|
|
// build struct with argument types (non variadic args)
|
|
for (int i=begin; i<n_arguments; i++)
|
|
{
|
|
Expression* argexp = (Expression*)arguments->data[i];
|
|
vtypes.push_back(DtoType(argexp->type));
|
|
size_t sz = getTypePaddedSize(vtypes.back());
|
|
if (sz < PTRSIZE)
|
|
vtypes.back() = DtoSize_t();
|
|
}
|
|
const LLStructType* vtype = LLStructType::get(vtypes);
|
|
|
|
if (Logger::enabled())
|
|
Logger::cout() << "d-variadic argument struct type:\n" << *vtype << '\n';
|
|
|
|
LLValue* mem = DtoAlloca(vtype,"_argptr_storage");
|
|
|
|
// store arguments in the struct
|
|
for (int i=begin,k=0; i<n_arguments; i++,k++)
|
|
{
|
|
Expression* argexp = (Expression*)arguments->data[i];
|
|
if (global.params.llvmAnnotate)
|
|
DtoAnnotation(argexp->toChars());
|
|
LLValue* argdst = DtoGEPi(mem,0,k);
|
|
argdst = DtoBitCast(argdst, getPtrToType(DtoType(argexp->type)));
|
|
DtoVariadicArgument(argexp, argdst);
|
|
}
|
|
|
|
// build type info array
|
|
const LLType* typeinfotype = DtoType(Type::typeinfo->type);
|
|
const LLArrayType* typeinfoarraytype = LLArrayType::get(typeinfotype,vtype->getNumElements());
|
|
|
|
llvm::GlobalVariable* typeinfomem =
|
|
new llvm::GlobalVariable(typeinfoarraytype, true, llvm::GlobalValue::InternalLinkage, NULL, "._arguments.storage", gIR->module);
|
|
if (Logger::enabled())
|
|
Logger::cout() << "_arguments storage: " << *typeinfomem << '\n';
|
|
|
|
std::vector<LLConstant*> vtypeinfos;
|
|
for (int i=begin,k=0; i<n_arguments; i++,k++)
|
|
{
|
|
Expression* argexp = (Expression*)arguments->data[i];
|
|
vtypeinfos.push_back(DtoTypeInfoOf(argexp->type));
|
|
}
|
|
|
|
// apply initializer
|
|
LLConstant* tiinits = llvm::ConstantArray::get(typeinfoarraytype, vtypeinfos);
|
|
typeinfomem->setInitializer(tiinits);
|
|
|
|
// put data in d-array
|
|
std::vector<LLConstant*> pinits;
|
|
pinits.push_back(DtoConstSize_t(vtype->getNumElements()));
|
|
pinits.push_back(llvm::ConstantExpr::getBitCast(typeinfomem, getPtrToType(typeinfotype)));
|
|
const LLType* tiarrty = DtoType(Type::typeinfo->type->arrayOf());
|
|
tiinits = llvm::ConstantStruct::get(pinits);
|
|
LLValue* typeinfoarrayparam = new llvm::GlobalVariable(tiarrty,
|
|
true, llvm::GlobalValue::InternalLinkage, tiinits, "._arguments.array", gIR->module);
|
|
|
|
// specify arguments
|
|
args.push_back(DtoLoad(typeinfoarrayparam));
|
|
++argidx;
|
|
args.push_back(gIR->ir->CreateBitCast(mem, getPtrToType(LLType::Int8Ty), "tmp"));
|
|
++argidx;
|
|
|
|
// pass non variadic args
|
|
for (int i=0; i<begin; i++)
|
|
{
|
|
Argument* fnarg = Argument::getNth(tf->parameters, i);
|
|
DValue* argval = DtoArgument(fnarg, (Expression*)arguments->data[i]);
|
|
args.push_back(argval->getRVal());
|
|
|
|
if (tf->fty->args[i]->attrs)
|
|
{
|
|
llvm::AttributeWithIndex Attr;
|
|
Attr.Index = argidx;
|
|
Attr.Attrs = tf->fty->args[i]->attrs;
|
|
attrs.push_back(Attr);
|
|
}
|
|
|
|
++argidx;
|
|
}
|
|
}
|
|
|
|
// FIXME: this function is a mess !
|
|
|
|
DValue* DtoCallFunction(Loc& loc, Type* resulttype, DValue* fnval, Expressions* arguments)
|
|
{
|
|
if (Logger::enabled()) {
|
|
Logger::println("DtoCallFunction()");
|
|
}
|
|
LOG_SCOPE
|
|
|
|
// the callee D type
|
|
Type* calleeType = fnval->getType();
|
|
|
|
// if the type has not yet been processed, do so now
|
|
if (calleeType->ir.type == NULL)
|
|
DtoType(calleeType);
|
|
|
|
// get func value if any
|
|
DFuncValue* dfnval = fnval->isFunc();
|
|
|
|
// handle special vararg intrinsics
|
|
bool va_intrinsic = (dfnval && dfnval->func && dfnval->func->isVaIntrinsic());
|
|
|
|
// get function type info
|
|
TypeFunction* tf = DtoTypeFunction(fnval);
|
|
|
|
// misc
|
|
bool retinptr = tf->fty->arg_sret;
|
|
bool thiscall = tf->fty->arg_this;
|
|
bool delegatecall = (calleeType->toBasetype()->ty == Tdelegate);
|
|
bool nestedcall = tf->fty->arg_nest;
|
|
bool dvarargs = (tf->linkage == LINKd && tf->varargs == 1);
|
|
|
|
unsigned callconv = DtoCallingConv(loc, tf->linkage);
|
|
|
|
// get callee llvm value
|
|
LLValue* callable = DtoCallableValue(fnval);
|
|
const LLFunctionType* callableTy = DtoExtractFunctionType(callable->getType());
|
|
assert(callableTy);
|
|
|
|
// if (Logger::enabled())
|
|
// Logger::cout() << "callable: " << *callable << '\n';
|
|
|
|
// get n arguments
|
|
size_t n_arguments = arguments ? arguments->dim : 0;
|
|
|
|
// get llvm argument iterator, for types
|
|
LLFunctionType::param_iterator argbegin = callableTy->param_begin();
|
|
LLFunctionType::param_iterator argiter = argbegin;
|
|
|
|
// parameter attributes
|
|
std::vector<llvm::AttributeWithIndex> attrs;
|
|
llvm::AttributeWithIndex Attr;
|
|
|
|
// return attrs
|
|
if (tf->fty->ret->attrs)
|
|
{
|
|
Attr.Index = 0;
|
|
Attr.Attrs = tf->fty->ret->attrs;
|
|
attrs.push_back(Attr);
|
|
}
|
|
|
|
// handle implicit arguments
|
|
std::vector<LLValue*> args;
|
|
args.reserve(tf->fty->args.size());
|
|
|
|
// return in hidden ptr is first
|
|
if (retinptr)
|
|
{
|
|
LLValue* retvar = DtoAlloca(argiter->get()->getContainedType(0), ".rettmp");
|
|
++argiter;
|
|
args.push_back(retvar);
|
|
|
|
// add attrs for hidden ptr
|
|
Attr.Index = 1;
|
|
Attr.Attrs = llvm::Attribute::StructRet;
|
|
attrs.push_back(Attr);
|
|
}
|
|
|
|
// then comes a context argument...
|
|
if(thiscall || delegatecall || nestedcall)
|
|
{
|
|
// ... which can be a 'this' argument
|
|
if (thiscall && dfnval && dfnval->vthis)
|
|
{
|
|
LLValue* thisarg = DtoBitCast(dfnval->vthis, argiter->get());
|
|
++argiter;
|
|
args.push_back(thisarg);
|
|
}
|
|
// ... or a delegate context arg
|
|
else if (delegatecall)
|
|
{
|
|
LLValue* ctxarg;
|
|
if (fnval->isLVal())
|
|
{
|
|
ctxarg = DtoLoad(DtoGEPi(fnval->getLVal(), 0,0));
|
|
}
|
|
else
|
|
{
|
|
ctxarg = gIR->ir->CreateExtractValue(fnval->getRVal(), 0, ".ptr");
|
|
}
|
|
assert(ctxarg->getType() == argiter->get());
|
|
++argiter;
|
|
args.push_back(ctxarg);
|
|
}
|
|
// ... or a nested function context arg
|
|
else if (nestedcall)
|
|
{
|
|
LLValue* contextptr = DtoNestedContext(loc, dfnval->func);
|
|
contextptr = DtoBitCast(contextptr, getVoidPtrType());
|
|
++argiter;
|
|
args.push_back(contextptr);
|
|
}
|
|
else
|
|
{
|
|
error(loc, "Context argument required but none given");
|
|
fatal();
|
|
}
|
|
|
|
// add attributes for context argument
|
|
if (tf->fty->arg_this && tf->fty->arg_this->attrs)
|
|
{
|
|
Attr.Index = retinptr ? 2 : 1;
|
|
Attr.Attrs = tf->fty->arg_this->attrs;
|
|
attrs.push_back(Attr);
|
|
}
|
|
else if (tf->fty->arg_nest && tf->fty->arg_nest->attrs)
|
|
{
|
|
Attr.Index = retinptr ? 2 : 1;
|
|
Attr.Attrs = tf->fty->arg_nest->attrs;
|
|
attrs.push_back(Attr);
|
|
}
|
|
}
|
|
|
|
// handle the rest of the arguments based on param passing style
|
|
|
|
// variadic instrinsics need some custom casts
|
|
if (va_intrinsic)
|
|
{
|
|
for (int i=0; i<n_arguments; i++)
|
|
{
|
|
Expression* exp = (Expression*)arguments->data[i];
|
|
DValue* expelem = exp->toElem(gIR);
|
|
// cast to va_list*
|
|
LLValue* val = DtoBitCast(expelem->getLVal(), getVoidPtrType());
|
|
++argiter;
|
|
args.push_back(val);
|
|
}
|
|
}
|
|
|
|
// d style varargs needs a few more hidden arguments as well as special passing
|
|
else if (dvarargs)
|
|
{
|
|
DtoBuildDVarArgList(args, attrs, tf, arguments, argiter-argbegin+1);
|
|
}
|
|
|
|
// otherwise we're looking at a normal function call
|
|
// or a C style vararg call
|
|
else
|
|
{
|
|
Logger::println("doing normal arguments");
|
|
|
|
size_t n = Argument::dim(tf->parameters);
|
|
|
|
LLSmallVector<unsigned, 10> attrptr(n, 0);
|
|
|
|
// do formal params
|
|
int beg = argiter-argbegin;
|
|
for (int i=0; i<n; i++)
|
|
{
|
|
Argument* fnarg = Argument::getNth(tf->parameters, i);
|
|
assert(fnarg);
|
|
DValue* argval = DtoArgument(fnarg, (Expression*)arguments->data[i]);
|
|
|
|
// give the ABI a say
|
|
LLValue* arg = tf->fty->putParam(argval->getType(), i, argval);
|
|
|
|
int j = tf->fty->reverseParams ? beg + n - i - 1 : beg + i;
|
|
|
|
// Hack around LDC assuming structs are in memory:
|
|
// If the function wants a struct, and the argument value is a
|
|
// pointer to a struct, load from it before passing it in.
|
|
if (argval->getType()->ty == Tstruct
|
|
&& isaPointer(arg) && !isaPointer(callableTy->getParamType(j))) {
|
|
Logger::println("Loading struct type for function argument");
|
|
arg = DtoLoad(arg);
|
|
}
|
|
|
|
// parameter type mismatch, this is hard to get rid of
|
|
if (arg->getType() != callableTy->getParamType(j))
|
|
{
|
|
#if 1
|
|
if (Logger::enabled())
|
|
{
|
|
Logger::cout() << "arg: " << *arg << '\n';
|
|
Logger::cout() << "expects: " << *callableTy->getParamType(j) << '\n';
|
|
}
|
|
#endif
|
|
arg = DtoBitCast(arg, callableTy->getParamType(j));
|
|
}
|
|
|
|
// param attrs
|
|
attrptr[i] = tf->fty->args[i]->attrs;
|
|
|
|
++argiter;
|
|
args.push_back(arg);
|
|
}
|
|
|
|
// reverse the relevant params as well as the param attrs
|
|
if (tf->fty->reverseParams)
|
|
{
|
|
std::reverse(args.begin() + beg, args.end());
|
|
std::reverse(attrptr.begin(), attrptr.end());
|
|
}
|
|
|
|
// add attributes
|
|
for (int i = 0; i < n; i++)
|
|
{
|
|
if (attrptr[i])
|
|
{
|
|
Attr.Index = beg + i + 1;
|
|
Attr.Attrs = attrptr[i];
|
|
attrs.push_back(Attr);
|
|
}
|
|
}
|
|
|
|
// do C varargs
|
|
if (n_arguments > n)
|
|
{
|
|
for (int i=n; i<n_arguments; i++)
|
|
{
|
|
Argument* fnarg = Argument::getNth(tf->parameters, i);
|
|
DValue* argval = DtoArgument(fnarg, (Expression*)arguments->data[i]);
|
|
LLValue* arg = argval->getRVal();
|
|
|
|
// FIXME: do we need any param attrs here ?
|
|
|
|
++argiter;
|
|
args.push_back(arg);
|
|
}
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
if (Logger::enabled())
|
|
{
|
|
Logger::println("%lu params passed", args.size());
|
|
for (int i=0; i<args.size(); ++i) {
|
|
assert(args[i]);
|
|
Logger::cout() << "arg["<<i<<"] = " << *args[i] << '\n';
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// void returns cannot not be named
|
|
const char* varname = "";
|
|
if (callableTy->getReturnType() != LLType::VoidTy)
|
|
varname = "tmp";
|
|
|
|
#if 0
|
|
if (Logger::enabled())
|
|
Logger::cout() << "Calling: " << *callable << '\n';
|
|
#endif
|
|
|
|
// call the function
|
|
LLCallSite call = gIR->CreateCallOrInvoke(callable, args.begin(), args.end(), varname);
|
|
|
|
// get return value
|
|
LLValue* retllval = (retinptr) ? args[0] : call.getInstruction();
|
|
|
|
// Ignore ABI for intrinsics
|
|
if (tf->linkage != LINKintrinsic && !retinptr)
|
|
{
|
|
// do abi specific return value fixups
|
|
DImValue dretval(tf->next, retllval);
|
|
retllval = tf->fty->getRet(tf->next, &dretval);
|
|
}
|
|
|
|
// Hack around LDC assuming structs are in memory:
|
|
// If the function returns a struct, and the return value is not a
|
|
// pointer to a struct, store it to a stack slot before continuing.
|
|
if (tf->next->ty == Tstruct && !isaPointer(retllval)) {
|
|
Logger::println("Storing return value to stack slot");
|
|
LLValue* mem = DtoAlloca(retllval->getType());
|
|
DtoStore(retllval, mem);
|
|
retllval = mem;
|
|
}
|
|
|
|
// repaint the type if necessary
|
|
if (resulttype)
|
|
{
|
|
Type* rbase = resulttype->toBasetype();
|
|
Type* nextbase = tf->nextOf()->toBasetype();
|
|
#if DMDV2
|
|
rbase = rbase->mutableOf();
|
|
nextbase = nextbase->mutableOf();
|
|
#endif
|
|
if (!rbase->equals(nextbase))
|
|
{
|
|
Logger::println("repainting return value from '%s' to '%s'", tf->nextOf()->toChars(), rbase->toChars());
|
|
switch(rbase->ty)
|
|
{
|
|
case Tarray:
|
|
retllval = DtoAggrPaint(retllval, DtoType(rbase));
|
|
break;
|
|
|
|
case Tclass:
|
|
case Taarray:
|
|
case Tpointer:
|
|
retllval = DtoBitCast(retllval, DtoType(rbase));
|
|
break;
|
|
|
|
default:
|
|
assert(0 && "unhandled repainting of return value");
|
|
}
|
|
if (Logger::enabled())
|
|
Logger::cout() << "final return value: " << *retllval << '\n';
|
|
}
|
|
}
|
|
|
|
// set calling convention and parameter attributes
|
|
llvm::AttrListPtr attrlist = llvm::AttrListPtr::get(attrs.begin(), attrs.end());
|
|
if (dfnval && dfnval->func)
|
|
{
|
|
LLFunction* llfunc = llvm::dyn_cast<LLFunction>(dfnval->val);
|
|
if (llfunc && llfunc->isIntrinsic()) // override intrinsic attrs
|
|
attrlist = llvm::Intrinsic::getAttributes((llvm::Intrinsic::ID)llfunc->getIntrinsicID());
|
|
else
|
|
call.setCallingConv(callconv);
|
|
}
|
|
else
|
|
call.setCallingConv(callconv);
|
|
call.setAttributes(attrlist);
|
|
|
|
// if we are returning through a pointer arg
|
|
// make sure we provide a lvalue back!
|
|
if (retinptr)
|
|
return new DVarValue(resulttype, retllval);
|
|
|
|
return new DImValue(resulttype, retllval);
|
|
}
|