This commit is contained in:
Moritz Warning
2011-04-07 01:12:27 +02:00
11 changed files with 182 additions and 271 deletions

View File

@@ -53,4 +53,45 @@ struct X87_complex_swap : ABIRewrite
}
};
//////////////////////////////////////////////////////////////////////////////
// FIXME: try into eliminating the alloca or if at least check
// if it gets optimized away
// convert byval struct
// when
struct X86_struct_to_register : ABIRewrite
{
// int -> struct
LLValue* get(Type* dty, DValue* dv)
{
Logger::println("rewriting int -> struct");
LLValue* mem = DtoAlloca(dty, ".int_to_struct");
LLValue* v = dv->getRVal();
DtoStore(v, DtoBitCast(mem, getPtrToType(v->getType())));
return DtoLoad(mem);
}
// int -> struct (with dst lvalue given)
void getL(Type* dty, DValue* dv, llvm::Value* lval)
{
Logger::println("rewriting int -> struct");
LLValue* v = dv->getRVal();
DtoStore(v, DtoBitCast(lval, getPtrToType(v->getType())));
}
// struct -> int
LLValue* put(Type* dty, DValue* dv)
{
Logger::println("rewriting struct -> int");
assert(dv->isLVal());
LLValue* mem = dv->getLVal();
const LLType* t = LLIntegerType::get(gIR->context(), dty->size()*8);
return DtoLoad(DtoBitCast(mem, getPtrToType(t)));
}
const LLType* type(Type* t, const LLType*)
{
size_t sz = t->size()*8;
return LLIntegerType::get(gIR->context(), sz);
}
};
#endif

View File

@@ -1,12 +1,6 @@
/* TargetABI implementation for x86-64.
* Written for LDC by Frits van Bommel in 2009.
*
* extern(D) follows no particular external ABI, but tries to be smart about
* passing structs and returning them. It should probably be reviewed if the
* way LLVM implements fastcc on this platform ever changes.
* (Specifically, the number of return registers of various types is hardcoded)
*
*
* extern(C) implements the C calling convention for x86-64, as found in
* http://www.x86-64.org/documentation/abi-0.99.pdf
*
@@ -286,150 +280,6 @@ namespace {
}
}
// Implementation details for extern(D)
namespace x86_64_D_cc {
struct DRegCount {
unsigned ints;
unsigned sse;
unsigned x87;
DRegCount(unsigned ints_, unsigned sse_, unsigned x87_)
: ints(ints_), sse(sse_), x87(x87_) {}
};
// Count the number of registers needed for a simple type.
// (Not a struct or static array)
DRegCount regsNeededForSimpleType(Type* t) {
DRegCount r(0, 0, 0);
switch(t->ty) {
case Tstruct:
case Tsarray:
assert(0 && "Not a simple type!");
// Return huge numbers if assertions are disabled, so it'll always get
// bumped to memory.
r.ints = r.sse = r.x87 = (unsigned)-1;
break;
// Floats, doubles and such are passed in SSE registers
case Tfloat32:
case Tfloat64:
case Timaginary32:
case Timaginary64:
r.sse = 1;
break;
case Tcomplex32:
case Tcomplex64:
r.sse = 2;
break;
// Reals, ireals and creals are passed in x87 registers
case Tfloat80:
case Timaginary80:
r.x87 = 1;
break;
case Tcomplex80:
r.x87 = 2;
break;
// Anything else is passed in one or two integer registers,
// depending on its size.
default: {
int needed = (t->size() + 7) / 8;
assert(needed <= 2);
r.ints = needed;
break;
}
}
return r;
}
// Returns true if it's possible (and a good idea) to pass the struct in the
// specified number of registers.
// (May return false if it's a bad idea to pass the type in registers for
// reasons other than it not fitting)
// Note that if true is returned, 'left' is also modified to contain the
// number of registers left. This property is used in the recursive case.
// If false is returned, 'left' is garbage.
bool shouldPassStructInRegs(TypeStruct* t, DRegCount& left) {
// If it has unaligned fields, there's probably a reason for it,
// so keep it in memory.
if (hasUnalignedFields(t))
return false;
Array* fields = &t->sym->fields;
if (fields->dim == 0)
return false;
d_uns64 nextbyte = 0;
for (d_uns64 i = 0; i < fields->dim; i++) {
VarDeclaration* field = (VarDeclaration*) fields->data[i];
// This depends on ascending order of field offsets in structs
// without overlapping fields.
if (field->offset < nextbyte) {
// Don't return unions (or structs containing them) in registers.
return false;
}
nextbyte = field->offset + field->type->size();
switch (field->type->ty) {
case Tstruct:
if (!shouldPassStructInRegs((TypeStruct*) field->type, left))
return false;
break;
case Tsarray:
// Don't return static arrays in registers
// (indexing registers doesn't work well)
return false;
default: {
DRegCount needed = regsNeededForSimpleType(field->type);
if (needed.ints > left.ints || needed.sse > left.sse || needed.x87 > left.x87)
return false;
left.ints -= needed.ints;
left.sse -= needed.sse;
left.x87 -= needed.x87;
break;
}
}
}
return true;
}
// Returns true if the struct fits in return registers in the x86-64 fastcc
// calling convention.
bool retStructInRegs(TypeStruct* st) {
// 'fastcc' allows returns in up to two registers of each kind:
DRegCount state(2, 2, 2);
return shouldPassStructInRegs(st, state);
}
// Heuristic for determining whether to pass a struct type directly or
// bump it to memory.
bool passStructTypeDirectly(TypeStruct* st) {
// If the type fits in a reasonable number of registers,
// pass it directly.
// This does not necessarily mean it will actually be passed in
// registers. For example, x87 registers are never actually used for
// parameters.
DRegCount state(2, 2, 2);
return shouldPassStructInRegs(st, state);
// This doesn't work well: Since the register count can differ depending
// on backend options, there's no way to be exact anyway.
/*
// Regular fastcc: 6 int, 8 sse, 0 x87
// fastcc + tailcall: 5 int, 8 sse, 0 x87
RegCount state(5, 8, 0);
*/
}
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
@@ -498,7 +348,7 @@ struct RegCount {
struct X86_64TargetABI : TargetABI {
X86_64_C_struct_rewrite struct_rewrite;
X87_complex_swap swapComplex;
RemoveStructPadding remove_padding;
X86_struct_to_register structToReg;
void newFunctionType(TypeFunction* tf) {
funcTypeStack.push_back(FuncTypeData(tf->linkage));
@@ -538,7 +388,6 @@ private:
return funcTypeStack.back().state;
}
void fixup_D(IrFuncTyArg& arg);
void fixup(IrFuncTyArg& arg);
};
@@ -558,15 +407,8 @@ bool X86_64TargetABI::returnInArg(TypeFunction* tf) {
if (tf->isref)
return false;
#endif
// All non-structs can be returned in registers.
// FIXME: Update calling convention for static array returns
if (rt->ty != Tstruct)
return false;
// Try to figure out whether the struct fits in return registers
// and whether it's a good idea to put it there.
return !x86_64_D_cc::retStructInRegs((TypeStruct*) rt);
return (rt->ty == Tstruct);
} else {
if (rt == Type::tvoid || keepUnchanged(rt))
return false;
@@ -586,11 +428,7 @@ bool X86_64TargetABI::returnInArg(TypeFunction* tf) {
bool X86_64TargetABI::passByVal(Type* t) {
t = t->toBasetype();
if (linkage() == LINKd) {
if (t->ty != Tstruct)
return false;
// Try to be smart about which structs are passed in memory.
return !x86_64_D_cc::passStructTypeDirectly((TypeStruct*) t);
return t->toBasetype()->ty == Tstruct;
} else {
// This implements the C calling convention for x86-64.
// It might not be correct for other calling conventions.
@@ -649,20 +487,6 @@ bool X86_64TargetABI::passByVal(Type* t) {
}
}
// Helper function for rewriteFunctionType.
// Structs passed or returned in registers are passed here
// to get their padding removed (if necessary).
void X86_64TargetABI::fixup_D(IrFuncTyArg& arg) {
TypeStruct *type = (TypeStruct*)arg.type->toBasetype();
assert(type->ty == Tstruct);
if (type->alignsize() != 1) {
// TODO: don't do this transformation if there's no padding
LLType* abiTy = DtoUnpaddedStructType(type);
assert(abiTy);
arg.ltype = abiTy;
arg.rewrite = &remove_padding;
}
}
// Helper function for rewriteFunctionType.
// Return type and parameters are passed here (unless they're already in memory)
@@ -682,12 +506,8 @@ void X86_64TargetABI::rewriteFunctionType(TypeFunction* tf) {
Type* rt = fty.ret->type->toBasetype();
if (tf->linkage == LINKd) {
if (!fty.arg_sret) {
if (rt->ty == Tstruct && !fty.ret->byref) {
Logger::println("x86-64 D ABI: Transforming return type");
fixup_D(*fty.ret);
}
}
// RETURN VALUE
// complex {re,im} -> {im,re}
if (rt->iscomplex())
@@ -695,35 +515,102 @@ void X86_64TargetABI::rewriteFunctionType(TypeFunction* tf) {
Logger::println("Rewriting complex return value");
fty.ret->rewrite = &swapComplex;
}
#if DMDV1
if (fty.arg_this) {
fty.arg_this->attrs |= llvm::Attribute::Nest;
// IMPLICIT PARAMETERS
int regcount = 6; // RDI,RSI,RDX,RCX,R8,R9
int xmmcount = 8; // XMM0..XMM7
// mark this/nested params inreg
if (fty.arg_this)
{
Logger::println("Putting 'this' in register");
fty.arg_this->attrs = llvm::Attribute::InReg;
--regcount;
}
if (fty.arg_nest) {
fty.arg_nest->attrs |= llvm::Attribute::Nest;
else if (fty.arg_nest)
{
Logger::println("Putting context ptr in register");
fty.arg_nest->attrs = llvm::Attribute::InReg;
--regcount;
}
else if (IrFuncTyArg* sret = fty.arg_sret)
{
Logger::println("Putting sret ptr in register");
// sret and inreg are incompatible, but the ABI requires the
// sret parameter to be in RDI in this situation...
sret->attrs = (sret->attrs | llvm::Attribute::InReg)
& ~llvm::Attribute::StructRet;
--regcount;
}
#endif
Logger::println("x86-64 D ABI: Transforming arguments");
LOG_SCOPE;
for (IrFuncTy::ArgIter I = fty.args.begin(), E = fty.args.end(); I != E; ++I) {
for (IrFuncTy::ArgRIter I = fty.args.rbegin(), E = fty.args.rend(); I != E; ++I) {
IrFuncTyArg& arg = **I;
if (Logger::enabled())
Logger::cout() << "Arg: " << arg.type->toChars() << '\n';
// Arguments that are in memory are of no interest to us.
if (arg.byref)
continue;
Type* ty = arg.type->toBasetype();
if (ty->ty == Tstruct)
fixup_D(arg);
if (Logger::enabled())
Logger::cout() << "New arg type: " << *arg.ltype << '\n';
unsigned sz = ty->size();
if (ty->isfloating() && sz <= 8)
{
if (xmmcount > 0) {
Logger::println("Putting float parameter in register");
arg.attrs |= llvm::Attribute::InReg;
--xmmcount;
}
}
else if (regcount == 0)
{
continue;
}
else if (arg.byref && !arg.isByVal())
{
Logger::println("Putting byref parameter in register");
arg.attrs |= llvm::Attribute::InReg;
--regcount;
}
else if (ty->ty == Tpointer)
{
Logger::println("Putting pointer parameter in register");
arg.attrs |= llvm::Attribute::InReg;
--regcount;
}
else if (ty->isintegral() && sz <= 8)
{
Logger::println("Putting integral parameter in register");
arg.attrs |= llvm::Attribute::InReg;
--regcount;
}
else if ((ty->ty == Tstruct || ty->ty == Tsarray) &&
(sz == 1 || sz == 2 || sz == 4 || sz == 8))
{
if (ty->ty == Tstruct)
{
Logger::println("Putting struct in register");
arg.rewrite = &structToReg;
arg.ltype = structToReg.type(arg.type, arg.ltype);
arg.byref = false;
// erase previous attributes
arg.attrs = 0;
}
else
{
Logger::println("Putting static array in register");
}
arg.attrs |= llvm::Attribute::InReg;
--regcount;
}
}
// EXPLICIT PARAMETERS
// reverse parameter order
// for non variadics
if (!fty.args.empty() && tf->varargs != 1)
{
fty.reverseParams = true;
}
} else {
// TODO: See if this is correct for more than just extern(C).

View File

@@ -13,6 +13,7 @@
#include "gen/abi-generic.h"
#include "ir/irfunction.h"
#include "ir/irfuncty.h"
//////////////////////////////////////////////////////////////////////////////
@@ -83,47 +84,6 @@ struct X86_cfloat_rewrite : ABIRewrite
//////////////////////////////////////////////////////////////////////////////
// FIXME: try into eliminating the alloca or if at least check
// if it gets optimized away
// convert byval struct
// when
struct X86_struct_to_register : ABIRewrite
{
// int -> struct
LLValue* get(Type* dty, DValue* dv)
{
Logger::println("rewriting int -> struct");
LLValue* mem = DtoAlloca(dty, ".int_to_struct");
LLValue* v = dv->getRVal();
DtoStore(v, DtoBitCast(mem, getPtrToType(v->getType())));
return DtoLoad(mem);
}
// int -> struct (with dst lvalue given)
void getL(Type* dty, DValue* dv, llvm::Value* lval)
{
Logger::println("rewriting int -> struct");
LLValue* v = dv->getRVal();
DtoStore(v, DtoBitCast(lval, getPtrToType(v->getType())));
}
// struct -> int
LLValue* put(Type* dty, DValue* dv)
{
Logger::println("rewriting struct -> int");
assert(dv->isLVal());
LLValue* mem = dv->getLVal();
const LLType* t = LLIntegerType::get(gIR->context(), dty->size()*8);
return DtoLoad(DtoBitCast(mem, getPtrToType(t)));
}
const LLType* type(Type* t, const LLType*)
{
size_t sz = t->size()*8;
return LLIntegerType::get(gIR->context(), sz);
}
};
//////////////////////////////////////////////////////////////////////////////
struct X86TargetABI : TargetABI
{
X87_complex_swap swapComplex;

View File

@@ -1117,7 +1117,7 @@ LLValue* DtoArrayCompare(Loc& loc, TOK op, DValue* l, DValue* r)
if (t->ty == Tchar)
res = DtoArrayEqCmp_impl(loc, "_adCmpChar", l, r, false);
else
res = DtoArrayEqCmp_impl(loc, "_adCmp", l, r, true);
res = DtoArrayEqCmp_impl(loc, _adCmp, l, r, true);
res = gIR->ir->CreateICmp(cmpop, res, DtoConstInt(0), "tmp");
}

View File

@@ -789,7 +789,7 @@ static void LLVM_D_BuildRuntimeModule()
// int _adCmp(void[] a1, void[] a2, TypeInfo ti)
{
llvm::StringRef fname(_adEq);
llvm::StringRef fname2("_adCmp");
llvm::StringRef fname2(_adCmp);
std::vector<const LLType*> types;
types.push_back(rt_array(byteTy));
types.push_back(rt_array(byteTy));

View File

@@ -13,9 +13,11 @@ llvm::GlobalVariable* LLVM_D_GetRuntimeGlobal(llvm::Module* target, const char*
#if DMDV1
#define _d_allocclass "_d_allocclass"
#define _adEq "_adEq"
#define _adCmp "_adCmp"
#else
#define _d_allocclass "_d_newclass"
#define _adEq "_adEq2"
#define _adCmp "_adCmp2"
#endif
#endif // LDC_GEN_RUNTIME_H_

View File

@@ -522,9 +522,11 @@ static LLFunction* build_module_reference_and_ctor(LLConstant* moduleinfo)
LLGlobalVariable* thismref = new LLGlobalVariable(*gIR->module, modulerefTy, false, LLGlobalValue::InternalLinkage, thismrefinit, thismrefname);
// make sure _Dmodule_ref is declared
LLGlobalVariable* mref = gIR->module->getNamedGlobal("_Dmodule_ref");
LLConstant* mref = gIR->module->getNamedGlobal("_Dmodule_ref");
const LLType *modulerefPtrTy = getPtrToType(modulerefTy);
if (!mref)
mref = new LLGlobalVariable(*gIR->module, getPtrToType(modulerefTy), false, LLGlobalValue::ExternalLinkage, NULL, "_Dmodule_ref");
mref = new LLGlobalVariable(*gIR->module, modulerefPtrTy, false, LLGlobalValue::ExternalLinkage, NULL, "_Dmodule_ref");
mref = DtoBitCast(mref, getPtrToType(modulerefPtrTy));
// make the function insert this moduleinfo as the beginning of the _Dmodule_ref linked list
llvm::BasicBlock* bb = llvm::BasicBlock::Create(gIR->context(), "moduleinfoCtorEntry", ctor);

View File

@@ -70,6 +70,7 @@ struct IrFuncTy : IrBase
// typedef llvm::SmallVector<IrFuncTyArg*, 4> ArgList;
typedef std::vector<IrFuncTyArg*> ArgList;
typedef ArgList::iterator ArgIter;
typedef ArgList::reverse_iterator ArgRIter;
ArgList args;
// C varargs

View File

@@ -7,6 +7,7 @@ default:
// 'switches' holds array of string that are appends to the command line
// arguments before they are parsed.
switches = [
"-I@PROJECT_BINARY_DIR@/../import",
"-I@RUNTIME_DIR@/import",
"-I@RUNTIME_DIR@/src",
"-L-L@PROJECT_BINARY_DIR@/../lib",

View File

@@ -7,11 +7,12 @@ default:
// 'switches' holds array of string that are appends to the command line
// arguments before they are parsed.
switches = [
"-I@PROJECT_BINARY_DIR@/../import",
"-I@RUNTIME_DIR@/import",
"-I@RUNTIME_DIR@/src",
"-I@PHOBOS2_DIR@/",
"-L-L@PROJECT_BINARY_DIR@/../lib",
"-defaultlib=phobos2",
"-debuglib=phobos2"
"-defaultlib=lphobos2",
"-debuglib=lphobos2"
];
};

View File

@@ -9,6 +9,10 @@ set(D_FLAGS -g -w -d CACHE STRING "runtime build flags, separated by ;")
if(BUILD_SHARED_LIBS)
list(APPEND D_FLAGS -relocation-model=pic)
set(D_LIBRARY_TYPE SHARED)
else(BUILD_SHARED_LIBS)
set(D_LIBRARY_TYPE STATIC)
set(CXX_COMPILE_FLAGS " ")
endif(BUILD_SHARED_LIBS)
# build tango for D1, druntime for D2
@@ -81,12 +85,15 @@ elseif(D_VERSION EQUAL 2)
${RUNTIME_DC_DIR}/arrayfloat.d
${RUNTIME_DC_DIR}/arrayreal.d
${RUNTIME_DC_DIR}/arrayshort.d
${RUNTIME_DC_DIR}/critical_.d
${RUNTIME_DC_DIR}/deh.d
${RUNTIME_DC_DIR}/deh2.d
${RUNTIME_DC_DIR}/llmath.d
${RUNTIME_DC_DIR}/qsort2.d
${RUNTIME_DC_DIR}/trace.d
)
file(GLOB DCRT_C ${RUNTIME_DC_DIR}/*.c)
list(REMOVE_ITEM DCRT_C ${RUNTIME_DC_DIR}/deh.c ${RUNTIME_DC_DIR}/memory_osx.c)
list(REMOVE_ITEM DCRT_C ${RUNTIME_DC_DIR}/deh.c ${RUNTIME_DC_DIR}/memory_osx.c ${RUNTIME_DC_DIR}/dylib_fixes.c)
if(UNIX)
file(GLOB_RECURSE CORE_D_SYS ${RUNTIME_DIR}/src/core/sys/posix/*.d)
elseif(WIN32)
@@ -94,15 +101,11 @@ elseif(D_VERSION EQUAL 2)
elseif(APPLE)
file(GLOB_RECURSE CORE_D_SYS ${RUNTIME_DIR}/src/core/sys/osx/*.d)
endif(UNIX)
list(APPEND CORE_D ${CORE_D_SYNC} ${CORE_D_SYS} ${CORE_D_STDC} ${LDC_D}
${RUNTIME_DIR}/src/object_.d
)
list(APPEND CORE_D ${CORE_D_SYNC} ${CORE_D_SYS} ${CORE_D_STDC} )
set(GENERATE_DI ${CORE_D})
list(APPEND CORE_D ${LDC_D} ${RUNTIME_DIR}/src/object_.d)
file(GLOB CORE_C ${RUNTIME_DIR}/src/core/stdc/*.c)
set(IGNORE_DI
${RUNTIME_DIR}/src/object_.d
)
if(PHOBOS2_DIR)
file(GLOB PHOBOS2_D ${PHOBOS2_DIR}/std/*.d)
file(GLOB PHOBOS2_D_MATH ${PHOBOS2_DIR}/std/internal/math/*.d)
@@ -131,6 +134,9 @@ elseif(D_VERSION EQUAL 2)
${PHOBOS2_DIR}/etc/c/zlib.d
${PHOBOS2_DIR}/crc32.d
)
list(REMOVE_ITEM PHOBOS2_D
${PHOBOS2_DIR}/std/intrinsic.d
)
set(CONFIG_NAME ${LDC_EXE}_phobos)
else(PHOBOS2_DIR)
set(CONFIG_NAME ${LDC_EXE})
@@ -195,12 +201,13 @@ macro(dc INPUT_D OUTLIST_O OUTLIST_BC INCDIR MOREFLAGS PATH)
list(APPEND ${OUTLIST_O} ${OUTPUT_O})
list(APPEND ${OUTLIST_BC} ${OUTPUT_BC})
list(FIND IGNORE_DI "${INPUT_D}" INDEX)
list(FIND GENERATE_DI "${INPUT_D}" INDEX)
if (INDEX EQUAL -1)
string(REGEX REPLACE "^src/" "druntime/" di_output ${output})
set(DI_CMD -Hf=${CMAKE_BINARY_DIR}/import/${di_output}.di)
set(DI_CMD "")
else (INDEX EQUAL -1)
list(REMOVE_AT IGNORE_DI ${INDEX})
string(REGEX REPLACE "^src/" "" di_output ${output})
set(DI_CMD -Hf=${CMAKE_BINARY_DIR}/import/${di_output}.di)
list(REMOVE_AT GENERATE_DI ${INDEX})
endif (INDEX EQUAL -1)
# Compile
@@ -233,12 +240,12 @@ foreach(f ${DCRT_D})
endforeach(f)
if(BUILD_SINGLE_LIB)
add_library(${RUNTIME_AIO} ${CORE_O} ${CORE_C} ${GC_O} ${DCRT_O} ${DCRT_C})
add_library(${RUNTIME_AIO} ${D_LIBRARY_TYPE} ${CORE_O} ${CORE_C} ${GC_O} ${DCRT_O} ${DCRT_C})
set(LIBS ${RUNTIME_AIO})
else(BUILD_SINGLE_LIB)
add_library(${RUNTIME_CC} ${CORE_O} ${CORE_C})
add_library(${RUNTIME_GC} ${GC_O})
add_library(${RUNTIME_DC} ${DCRT_O} ${DCRT_C})
add_library(${RUNTIME_CC} ${D_LIBRARY_TYPE} ${CORE_O} ${CORE_C})
add_library(${RUNTIME_GC} ${D_LIBRARY_TYPE} ${GC_O})
add_library(${RUNTIME_DC} ${D_LIBRARY_TYPE} ${DCRT_O} ${DCRT_C})
set(LIBS
${RUNTIME_CC}
${RUNTIME_GC}
@@ -289,12 +296,21 @@ if(PHOBOS2_DIR)
dc(${f} PHOBOS2_O PHOBOS2_BC ${RUNTIME_DIR}/src/ "-I${PHOBOS2_DIR}" ${PHOBOS2_DIR})
endforeach(f)
add_library(phobos2 ${ZLIB_C} ${PHOBOS2_O} ${CORE_O} ${CORE_C} ${GC_O} ${DCRT_O} ${DCRT_C})
add_dependencies(phobos2 runtime)
add_library(lphobos2 ${D_LIBRARY_TYPE}
${ZLIB_C}
${PHOBOS2_O}
${CORE_O}
${CORE_C}
${GC_O}
${DCRT_O}
${DCRT_C}
)
add_dependencies(lphobos2 runtime)
set_target_properties(
phobos2 PROPERTIES
lphobos2 PROPERTIES
LINKER_LANGUAGE C
ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/../lib
LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/../lib
)
add_custom_target(phobos2 DEPENDS lphobos2)
endif(PHOBOS2_DIR)