ldc/gen/naked.cpp
David Nadlinger 6275c66a89 Fixed segfault on invalid naked functions.
Emitting a frontend error during codegen is somewhat problematic in
any case, but stopping if an error ocurred should work just fine here.
2012-09-07 03:51:33 +02:00

448 lines
14 KiB
C++

#include "gen/llvm.h"
#include "llvm/InlineAsm.h"
#include "expression.h"
#include "statement.h"
#include "declaration.h"
#include "template.h"
#include <cassert>
#include "gen/logger.h"
#include "gen/irstate.h"
#include "gen/llvmhelpers.h"
#include "gen/tollvm.h"
#include "gen/dvalue.h"
//////////////////////////////////////////////////////////////////////////////////////////
void Statement::toNakedIR(IRState *p)
{
error("statement not allowed in naked function");
}
//////////////////////////////////////////////////////////////////////////////////////////
void CompoundStatement::toNakedIR(IRState *p)
{
Logger::println("CompoundStatement::toNakedIR(): %s", loc.toChars());
LOG_SCOPE;
if (statements)
for (unsigned i = 0; i < statements->dim; i++)
{
Statement* s = static_cast<Statement*>(statements->data[i]);
if (s) s->toNakedIR(p);
}
}
//////////////////////////////////////////////////////////////////////////////////////////
void ExpStatement::toNakedIR(IRState *p)
{
Logger::println("ExpStatement::toNakedIR(): %s", loc.toChars());
LOG_SCOPE;
// only expstmt supported in declarations
if (exp->op != TOKdeclaration)
{
Statement::toNakedIR(p);
return;
}
DeclarationExp* d = static_cast<DeclarationExp*>(exp);
VarDeclaration* vd = d->declaration->isVarDeclaration();
FuncDeclaration* fd = d->declaration->isFuncDeclaration();
EnumDeclaration* ed = d->declaration->isEnumDeclaration();
// and only static variable/function declaration
// no locals or nested stuffies!
if (!vd && !fd && !ed)
{
Statement::toNakedIR(p);
return;
}
else if (vd && !vd->isDataseg())
{
error("non-static variable '%s' not allowed in naked function", vd->toChars());
return;
}
else if (fd && !fd->isStatic())
{
error("non-static nested function '%s' not allowed in naked function", fd->toChars());
return;
}
// enum decls should always be safe
// make sure the symbols gets processed
d->declaration->codegen(Type::sir);
}
//////////////////////////////////////////////////////////////////////////////////////////
void LabelStatement::toNakedIR(IRState *p)
{
Logger::println("LabelStatement::toNakedIR(): %s", loc.toChars());
LOG_SCOPE;
printLabelName(p->nakedAsm, p->func()->decl->mangle(), ident->toChars());
p->nakedAsm << ":";
if (statement)
statement->toNakedIR(p);
}
//////////////////////////////////////////////////////////////////////////////////////////
void DtoDefineNakedFunction(FuncDeclaration* fd)
{
Logger::println("DtoDefineNakedFunction(%s)", fd->mangle());
LOG_SCOPE;
assert(fd->ir.irFunc);
gIR->functions.push_back(fd->ir.irFunc);
// we need to do special processing on the body, since we only want
// to allow actual inline asm blocks to reach the final asm output
std::ostringstream& asmstr = gIR->nakedAsm;
// build function header
// FIXME: could we perhaps use llvm asmwriter to give us these details ?
const char* mangle = fd->mangle();
std::ostringstream tmpstr;
// osx is different
// also mangling has an extra underscore prefixed
if (global.params.os == OSMacOSX)
{
std::string section = "text";
bool weak = false;
if (DtoIsTemplateInstance(fd))
{
tmpstr << "section\t__TEXT,__textcoal_nt,coalesced,pure_instructions";
section = tmpstr.str();
weak = true;
}
asmstr << "\t." << section << std::endl;
asmstr << "\t.align\t4,0x90" << std::endl;
asmstr << "\t.globl\t_" << mangle << std::endl;
if (weak)
{
asmstr << "\t.weak_definition\t_" << mangle << std::endl;
}
asmstr << "_" << mangle << ":" << std::endl;
}
// this works on linux x86 32 and 64 bit
// assume it works everywhere else as well for now
// this needed a slight modification for Win
else
{
const char* linkage = "globl";
std::string section = "text";
if (DtoIsTemplateInstance(fd))
{
linkage = "weak";
tmpstr << "section\t.gnu.linkonce.t.";
if (global.params.os != OSWindows)
{
tmpstr << mangle << ",\"ax\",@progbits";
} else
{
tmpstr << "_" << mangle << ",\"ax\"";
}
section = tmpstr.str();
}
asmstr << "\t." << section << std::endl;
asmstr << "\t.align\t16" << std::endl;
if (global.params.os == OSWindows)
{
std::string def = "def";
std::string endef = "endef";
asmstr << "\t." << def << "\t" << mangle << ";";
// hard code these two numbers for now since gas ignores .scl and llvm
// is defaulting to .type 32 for everything I have seen
asmstr << "\t.scl 2; .type 32;\t" << "." << endef << std::endl;
} else
{
asmstr << "\t." << linkage << "\t" << mangle << std::endl;
asmstr << "\t.type\t" << mangle << ",@function" << std::endl;
}
asmstr << mangle << ":" << std::endl;
}
// emit body
fd->fbody->toNakedIR(gIR);
// We could have generated new errors in toNakedIR(), but we are in codegen
// already so we have to abort here.
if (global.errors)
fatal();
// emit size after body
// llvm does this on linux, but not on osx or Win
if (global.params.os != OSMacOSX && global.params.os != OSWindows)
{
asmstr << "\t.size\t" << mangle << ", .-" << mangle << std::endl << std::endl;
}
gIR->module->appendModuleInlineAsm(asmstr.str());
asmstr.str("");
gIR->functions.pop_back();
}
//////////////////////////////////////////////////////////////////////////////////////////
void emitABIReturnAsmStmt(IRAsmBlock* asmblock, Loc loc, FuncDeclaration* fdecl)
{
Logger::println("emitABIReturnAsmStmt(%s)", fdecl->mangle());
LOG_SCOPE;
IRAsmStmt* as = new IRAsmStmt;
LLType* llretTy = DtoType(fdecl->type->nextOf());
asmblock->retty = llretTy;
asmblock->retn = 1;
// FIXME: This should probably be handled by the TargetABI somehow.
// It should be able to do this for a greater variety of types.
// x86
if (global.params.cpu == ARCHx86)
{
LINK l = fdecl->linkage;
assert((l == LINKd || l == LINKc || l == LINKwindows) && "invalid linkage for asm implicit return");
Type* rt = fdecl->type->nextOf()->toBasetype();
if (rt->isintegral() || rt->ty == Tpointer || rt->ty == Tclass || rt->ty == Taarray)
{
if (rt->size() == 8) {
as->out_c = "=A,";
} else {
as->out_c = "={ax},";
}
}
else if (rt->isfloating())
{
if (rt->iscomplex()) {
if (fdecl->linkage == LINKd) {
// extern(D) always returns on the FPU stack
as->out_c = "={st},={st(1)},";
asmblock->retn = 2;
} else if (rt->ty == Tcomplex32) {
// extern(C) cfloat is return as i64
as->out_c = "=A,";
asmblock->retty = LLType::getInt64Ty(gIR->context());
} else {
// cdouble and creal extern(C) are returned in pointer
// don't add anything!
asmblock->retty = LLType::getVoidTy(gIR->context());
asmblock->retn = 0;
return;
}
} else {
as->out_c = "={st},";
}
}
else if (rt->ty == Tarray || rt->ty == Tdelegate)
{
as->out_c = "={ax},={dx},";
asmblock->retn = 2;
#if 0
// this is to show how to allocate a temporary for the return value
// in case the appropriate multi register constraint isn't supported.
// this way abi return from inline asm can still be emulated.
// note that "$<<out0>>" etc in the asm will translate to the correct
// numbered output when the asm block in finalized
// generate asm
as->out_c = "=*m,=*m,";
LLValue* tmp = DtoRawAlloca(llretTy, 0, ".tmp_asm_ret");
as->out.push_back( tmp );
as->out.push_back( DtoGEPi(tmp, 0,1) );
as->code = "movd %eax, $<<out0>>" "\n\t" "mov %edx, $<<out1>>";
// fix asmblock
asmblock->retn = 0;
asmblock->retemu = true;
asmblock->asmBlock->abiret = tmp;
// add "ret" stmt at the end of the block
asmblock->s.push_back(as);
// done, we don't want anything pushed in the front of the block
return;
#endif
}
else
{
error(loc, "unimplemented return type '%s' for implicit abi return", rt->toChars());
fatal();
}
}
// x86_64
else if (global.params.cpu == ARCHx86_64)
{
LINK l = fdecl->linkage;
/* TODO: Check if this works with extern(Windows), completely untested.
* In particular, returning cdouble may not work with
* extern(Windows) since according to X86CallingConv.td it
* doesn't allow XMM1 to be used.
* (So is extern(C), but that should be fine as the calling convention
* is identical to that of extern(D))
*/
assert((l == LINKd || l == LINKc || l == LINKwindows) && "invalid linkage for asm implicit return");
Type* rt = fdecl->type->nextOf()->toBasetype();
if (rt->isintegral() || rt->ty == Tpointer || rt->ty == Tclass || rt->ty == Taarray)
{
as->out_c = "={ax},";
}
else if (rt->isfloating())
{
if (rt == Type::tcomplex80) {
// On x87 stack, re=st, im=st(1)
as->out_c = "={st},={st(1)},";
asmblock->retn = 2;
} else if (rt == Type::tfloat80 || rt == Type::timaginary80) {
// On x87 stack
as->out_c = "={st},";
} else if (l != LINKd && rt == Type::tcomplex32) {
// LLVM and GCC disagree on how to return {float, float}.
// For compatibility, use the GCC/LLVM-GCC way for extern(C/Windows)
// extern(C) cfloat -> %xmm0 (extract two floats)
as->out_c = "={xmm0},";
asmblock->retty = LLType::getDoubleTy(gIR->context());
} else if (rt->iscomplex()) {
// cdouble and extern(D) cfloat -> re=%xmm0, im=%xmm1
as->out_c = "={xmm0},={xmm1},";
asmblock->retn = 2;
} else {
// Plain float/double/ifloat/idouble
as->out_c = "={xmm0},";
}
}
else if (rt->ty == Tarray || rt->ty == Tdelegate)
{
as->out_c = "={ax},={dx},";
asmblock->retn = 2;
}
else
{
error(loc, "unimplemented return type '%s' for implicit abi return", rt->toChars());
fatal();
}
}
// unsupported
else
{
error(loc, "this target (%s) does not implement inline asm falling off the end of the function", global.params.targetTriple);
fatal();
}
// return values always go in the front
asmblock->s.push_front(as);
}
//////////////////////////////////////////////////////////////////////////////////////////
// sort of kinda related to naked ...
DValue * DtoInlineAsmExpr(Loc loc, FuncDeclaration * fd, Expressions * arguments)
{
Logger::println("DtoInlineAsmExpr @ %s", loc.toChars());
LOG_SCOPE;
TemplateInstance* ti = fd->toParent()->isTemplateInstance();
assert(ti && "invalid inline __asm expr");
assert(arguments->dim >= 2 && "invalid __asm call");
// get code param
Expression* e = static_cast<Expression*>(arguments->data[0]);
Logger::println("code exp: %s", e->toChars());
StringExp* se = static_cast<StringExp*>(e);
if (e->op != TOKstring || se->sz != 1)
{
e->error("__asm code argument is not a char[] string literal");
fatal();
}
std::string code(static_cast<char*>(se->string), se->len);
// get constraints param
e = static_cast<Expression*>(arguments->data[1]);
Logger::println("constraint exp: %s", e->toChars());
se = static_cast<StringExp*>(e);
if (e->op != TOKstring || se->sz != 1)
{
e->error("__asm constraints argument is not a char[] string literal");
fatal();
}
std::string constraints(static_cast<char*>(se->string), se->len);
// build runtime arguments
size_t n = arguments->dim;
LLSmallVector<llvm::Value*, 8> args;
args.reserve(n-2);
std::vector<LLType*> argtypes;
argtypes.reserve(n-2);
for (size_t i = 2; i < n; i++)
{
e = static_cast<Expression*>(arguments->data[i]);
args.push_back(e->toElem(gIR)->getRVal());
argtypes.push_back(args.back()->getType());
}
// build asm function type
Type* type = fd->type->nextOf()->toBasetype();
LLType* ret_type = DtoType(type);
llvm::FunctionType* FT = llvm::FunctionType::get(ret_type, argtypes, false);
// build asm call
bool sideeffect = true;
llvm::InlineAsm* ia = llvm::InlineAsm::get(FT, code, constraints, sideeffect);
llvm::Value* rv = gIR->ir->CreateCall(ia, args, "");
// work around missing tuple support for users of the return value
if (type->ty == Tstruct)
{
// make a copy
llvm::Value* mem = DtoAlloca(type, ".__asm_tuple_ret");
TypeStruct* ts = static_cast<TypeStruct*>(type);
size_t n = ts->sym->fields.dim;
for (size_t i = 0; i < n; i++)
{
llvm::Value* v = gIR->ir->CreateExtractValue(rv, i, "");
llvm::Value* gep = DtoGEPi(mem, 0, i);
DtoStore(v, gep);
}
return new DVarValue(fd->type->nextOf(), mem);
}
// return call as im value
return new DImValue(fd->type->nextOf(), rv);
}