Re-implement __va_list struct allocation for System V AMD64 varargs ABI.

This commit is contained in:
Martin 2014-11-16 15:21:50 +01:00
parent 5e3785159f
commit e5deefb1dd
5 changed files with 108 additions and 12 deletions

View file

@ -320,3 +320,21 @@ void X86_64TargetABI::rewriteFunctionType(TypeFunction* tf, IrFuncTy &fty) {
if (tf->linkage == LINKd && tf->varargs != 1 && fty.args.size() > 1)
fty.reverseParams = true;
}
bool isSystemVAMD64Target() {
return global.params.targetTriple.getArch() == llvm::Triple::x86_64
&& !global.params.targetTriple.isOSWindows();
}
LLType* getSystemVAMD64NativeValistType() {
LLType* uintType = LLType::getInt32Ty(gIR->context());
LLType* voidPointerType = getVoidPtrType();
std::vector<LLType*> parts; // struct __va_list {
parts.push_back(uintType); // uint gp_offset;
parts.push_back(uintType); // uint fp_offset;
parts.push_back(voidPointerType); // void* overflow_arg_area;
parts.push_back(voidPointerType); // void* reg_save_area; }
return LLStructType::get(gIR->context(), parts, "__va_list");
}

View file

@ -7,7 +7,8 @@
//
//===----------------------------------------------------------------------===//
//
// The ABI implementation used for 64 bit x86 (i.e. x86_64/AMD64/x64) targets.
// The System V AMD64 ABI implementation used on all x86-64 platforms except
// for Windows.
//
//===----------------------------------------------------------------------===//
@ -15,7 +16,12 @@
#define LDC_GEN_ABI_X86_64_H
struct TargetABI;
namespace llvm { class Type; }
TargetABI* getX86_64TargetABI();
bool isSystemVAMD64Target();
llvm::Type* getSystemVAMD64NativeValistType();
#endif

View file

@ -17,6 +17,7 @@
#include "statement.h"
#include "template.h"
#include "gen/abi.h"
#include "gen/abi-x86-64.h"
#include "gen/arrays.h"
#include "gen/classes.h"
#include "gen/dvalue.h"
@ -1182,14 +1183,31 @@ void DtoDefineFunction(FuncDeclaration* fd)
// D varargs: prepare _argptr and _arguments
if (f->linkage == LINKd && f->varargs == 1)
{
// allocate _argptr and initialize it by calling the LLVM va_start intrinsic
LLValue* argptrmem = DtoAlloca(Type::tvoidptr, "_argptr_mem");
llvm::CallInst::Create(GET_INTRINSIC_DECL(vastart), DtoBitCast(argptrmem, getVoidPtrType()), "", gIR->scopebb());
// allocate _argptr (of type core.stdc.stdarg.va_list)
LLValue* argptrmem = DtoAlloca(Type::tvalist, "_argptr_mem"); // _argptr_mem = new va_list [most likely char**]
irFunc->_argptr = argptrmem;
// initialize _argptr
if (isSystemVAMD64Target()) { // System V AMD64 ABI:
LLType* nativeValistType = getSystemVAMD64NativeValistType();
LLValue* valistmem = DtoRawAlloca(nativeValistType,
0, "__va_list_mem"); // __va_list_mem = new __va_list
valistmem = DtoBitCast(valistmem, getVoidPtrType()); // valistmem = (char*)__va_list_mem
DtoStore(valistmem, argptrmem); // *argptrmem = valistmem
// => _argptr = (char*)__va_list_mem
llvm::CallInst::Create(GET_INTRINSIC_DECL(vastart), // llvm.va_start(valistmem)
valistmem, "", gIR->scopebb()); // => llvm.va_start(_argptr)
} else { // all other ABIs:
llvm::CallInst::Create(GET_INTRINSIC_DECL(vastart), // llvm.va_start((char*)&_argptr)
DtoBitCast(argptrmem, getVoidPtrType()), "", gIR->scopebb());
}
// copy _arguments to a memory location
LLValue* argumentsmem = DtoRawAlloca(irFunc->_arguments->getType(), 0, "_arguments_mem");
new llvm::StoreInst(irFunc->_arguments, argumentsmem, gIR->scopebb());
LLType* argumentsType = irFunc->_arguments->getType();
LLValue* argumentsmem = DtoRawAlloca(argumentsType,
0, "_arguments_mem"); // _arguments_mem = new TypeInfo[]
new llvm::StoreInst(irFunc->_arguments, argumentsmem, // *_arguments_mem = <passed _arguments>
gIR->scopebb());
irFunc->_arguments = argumentsmem;
}

View file

@ -86,6 +86,7 @@ DValue* DtoVaArg(Loc& loc, Type* type, Expression* valistArg)
llt = getPtrToType(llt);
// issue a warning for broken va_arg instruction.
if (global.params.targetTriple.getArch() != llvm::Triple::x86
&& global.params.targetTriple.getArch() != llvm::Triple::x86_64
&& global.params.targetTriple.getArch() != llvm::Triple::ppc64
#if LDC_LLVM_VER >= 305
&& global.params.targetTriple.getArch() != llvm::Triple::ppc64le

View file

@ -19,6 +19,7 @@
#include "template.h"
#include "gen/aa.h"
#include "gen/abi.h"
#include "gen/abi-x86-64.h"
#include "gen/arrays.h"
#include "gen/classes.h"
#include "gen/complex.h"
@ -827,6 +828,16 @@ public:
{
FuncDeclaration* fndecl = dfnval->func;
// The System V AMD64 ABI used for all x86-64 targets except for Windows
// uses a special native va_list type - a 24-bytes struct passed by reference.
// In druntime, the struct is defined as core.stdc.stdarg.__va_list;
// the actually used core.stdc.stdarg.va_list type is a raw char* pointer
// though to achieve byref semantics.
// This requires a little bit of compiler magic here in the following
// implementations of LDC's va_start and va_copy intrinsics.
const bool isSystemVTarget = isSystemVAMD64Target();
LLType* systemVValistType = getSystemVAMD64NativeValistType();
// as requested by bearophile, see if it's a C printf call and that it's valid.
if (global.params.warnings && checkPrintf)
{
@ -843,15 +854,57 @@ public:
fatal();
}
Expression* exp = (*e->arguments)[0];
LLValue* arg = toElem(exp)->getLVal();
if (LLValue *argptr = p->func()->_argptr) {
DtoStore(DtoLoad(argptr), DtoBitCast(arg, getPtrToType(getVoidPtrType())));
LLValue* arg = toElem(exp)->getLVal(); // arg = &<passed ap> [va_list*]
if (LLValue* argptr = p->func()->_argptr) { // variadic extern(D) function with hidden _argptr:
DtoStore(DtoLoad(argptr), arg); // *arg = *_argptr_mem
// => <passed ap> = _argptr
result = new DImValue(e->type, arg);
} else {
arg = DtoBitCast(arg, getVoidPtrType());
result = new DImValue(e->type, gIR->ir->CreateCall(GET_INTRINSIC_DECL(vastart), arg, ""));
} else if (isSystemVTarget) { // System V AMD64 ABI:
// Since the user only created a char* pointer on the stack before invoking va_start, we first
// need to allocate the actual __va_list struct and set the passed pointer to its address
// before invoking va_start.
LLValue* valistmem = DtoRawAlloca(systemVValistType,
0, "__va_list_mem"); // __va_list_mem = new __va_list
valistmem = DtoBitCast(valistmem, getVoidPtrType()); // valistmem = (char*)__va_list_mem
DtoStore(valistmem, arg); // *arg = valistmem
// => <passed ap> = (char*)__va_list_mem
result = new DImValue(e->type, gIR->ir->CreateCall( // llvm.va_start(valistmem)
GET_INTRINSIC_DECL(vastart), valistmem, "")); // => llvm.va_start(<passed ap>)
} else { // all other ABIs:
arg = DtoBitCast(arg, getVoidPtrType()); // arg = (char*)arg
result = new DImValue(e->type, gIR->ir->CreateCall( // llvm.va_start(arg)
GET_INTRINSIC_DECL(vastart), arg, "")); // => llvm.va_start((char*)&<passed ap>)
}
}
// va_copy instruction
else if (fndecl->llvmInternal == LLVMva_copy) {
if (e->arguments->dim != 2) {
e->error("va_copy instruction expects 2 arguments");
fatal();
}
LLValue* arg1 = toElem((*e->arguments)[0])->getLVal(); // arg1 = &<passed dest> [va_list*]
LLValue* arg2 = toElem((*e->arguments)[1])->getRVal(); // arg2 = <passed src> [va_list]
if (isSystemVTarget) {
// Similar to va_start, we need to allocate a new __va_list struct on the stack first
// and set the passed destination char* pointer to its address.
LLValue* valistmem = DtoRawAlloca(systemVValistType,
0, "__va_list_mem"); // __va_list_mem = new __va_list
DtoStore(DtoBitCast(valistmem, getVoidPtrType()), // *arg1 = (char*)__va_list_mem
arg1); // => <passed dest> = (char*)__va_list_mem
// Now simply bitcopy the source struct over the destination struct.
arg2 = DtoBitCast(arg2, valistmem->getType()); // arg2 = (__va_list*)arg2
DtoStore(DtoLoad(arg2), valistmem); // *__va_list_mem = *arg2
// => *(__va_list*)<passed dest> = *(__va_list*)<passed src>
} else { // all other ABIs:
DtoStore(arg2, arg1); // *arg1 = arg2
// => <passed dest> = <passed src>
}
result = new DVarValue(e->type, arg1);
}
// va_arg instruction
else if (fndecl->llvmInternal == LLVMva_arg) {
if (e->arguments->dim != 1) {