Properly handle DMD-internal "reference variables".

Previously, we just had a hack to make ref foreach statements work.
This commit enables them to work in other cases as well, like the
implicit __result variable for functions with out-contracts (which
is such a magic ref variable for ref-returning functions).

Fixes DMD testcase 'testcontracts'.
This commit is contained in:
David Nadlinger 2012-09-04 01:36:52 +02:00
parent 6b1b84a28d
commit ee4285f934
8 changed files with 173 additions and 172 deletions

View file

@ -16,6 +16,8 @@ DVarValue::DVarValue(Type* t, VarDeclaration* vd, LLValue* llvmValue)
: DValue(t), var(vd), val(llvmValue)
{
assert(isaPointer(llvmValue));
assert(!isSpecialRefVar(vd) ||
isaPointer(isaPointer(llvmValue)->getElementType()));
}
DVarValue::DVarValue(Type* t, LLValue* llvmValue)
@ -27,6 +29,8 @@ DVarValue::DVarValue(Type* t, LLValue* llvmValue)
LLValue* DVarValue::getLVal()
{
assert(val);
if (var && isSpecialRefVar(var))
return DtoLoad(val);
return val;
}
@ -34,9 +38,14 @@ LLValue* DVarValue::getRVal()
{
assert(val);
Type* bt = type->toBasetype();
LLValue* tmp = val;
if (var && isSpecialRefVar(var))
tmp = DtoLoad(tmp);
if (DtoIsPassedByRef(bt))
return val;
return DtoLoad(val);
return tmp;
return DtoLoad(tmp);
}
/////////////////////////////////////////////////////////////////////////////////////////////////

View file

@ -815,8 +815,7 @@ void DtoDefineFunction(FuncDeclaration* fd)
{
DtoNestedInit(fd->vresult);
} else if (fd->vresult) {
fd->vresult->ir.irLocal = new IrLocal(fd->vresult);
fd->vresult->ir.irLocal->value = DtoAlloca(fd->vresult->type, fd->vresult->toChars());
DtoVarDeclaration(fd->vresult);
}
// copy _argptr and _arguments to a memory location

View file

@ -1013,6 +1013,110 @@ void DtoConstInitGlobal(VarDeclaration* vd)
/*////////////////////////////////////////////////////////////////////////////////////////
// DECLARATION EXP HELPER
////////////////////////////////////////////////////////////////////////////////////////*/
// TODO: Merge with DtoRawVarDeclaration!
void DtoVarDeclaration(VarDeclaration* vd)
{
assert(!vd->isDataseg() && "Statics/globals are handled in DtoDeclarationExp.");
assert(!vd->aliassym && "Aliases are handled in DtoDeclarationExp.");
Logger::println("vdtype = %s", vd->type->toChars());
#if DMDV2
if (vd->nestedrefs.dim)
#else
if (vd->nestedref)
#endif
{
Logger::println("has nestedref set (referenced by nested function/delegate)");
assert(vd->ir.irLocal);
DtoNestedInit(vd);
}
else if(vd->ir.irLocal)
{
// Nothing to do if it has already been allocated.
}
#if DMDV2
/* Named Return Value Optimization (NRVO):
T f(){
T ret; // &ret == hidden pointer
ret = ...
return ret; // NRVO.
}
*/
else if (gIR->func()->retArg && gIR->func()->decl->nrvo_can && gIR->func()->decl->nrvo_var == vd) {
assert(!isSpecialRefVar(vd) && "Can this happen?");
vd->ir.irLocal = new IrLocal(vd);
vd->ir.irLocal->value = gIR->func()->retArg;
}
#endif
// normal stack variable, allocate storage on the stack if it has not already been done
else {
vd->ir.irLocal = new IrLocal(vd);
#if DMDV2
/* NRVO again:
T t = f(); // t's memory address is taken hidden pointer
*/
ExpInitializer *ei = 0;
if (vd->type->toBasetype()->ty == Tstruct && vd->init &&
!!(ei = vd->init->isExpInitializer()))
{
if (ei->exp->op == TOKconstruct) {
AssignExp *ae = static_cast<AssignExp*>(ei->exp);
if (ae->e2->op == TOKcall) {
CallExp *ce = static_cast<CallExp *>(ae->e2);
TypeFunction *tf = static_cast<TypeFunction *>(ce->e1->type->toBasetype());
if (tf->ty == Tfunction && tf->fty.arg_sret) {
LLValue* const val = ce->toElem(gIR)->getLVal();
if (isSpecialRefVar(vd))
{
vd->ir.irLocal->value = DtoAlloca(
vd->type->pointerTo(), vd->toChars());
DtoStore(val, vd->ir.irLocal->value);
}
else
{
vd->ir.irLocal->value = val;
}
goto Lexit;
}
}
}
}
#endif
Type* type = isSpecialRefVar(vd) ? vd->type->pointerTo() : vd->type;
LLType* lltype = DtoType(type);
llvm::Value* allocainst;
if(gTargetData->getTypeSizeInBits(lltype) == 0)
allocainst = llvm::ConstantPointerNull::get(getPtrToType(lltype));
else
allocainst = DtoAlloca(type, vd->toChars());
vd->ir.irLocal->value = allocainst;
DtoDwarfLocalVariable(allocainst, vd);
}
if (Logger::enabled())
Logger::cout() << "llvm value for decl: " << *vd->ir.irLocal->value << '\n';
DtoInitializer(vd->ir.irLocal->value, vd->init); // TODO: Remove altogether?
#if DMDV2
Lexit:
/* Mark the point of construction of a variable that needs to be destructed.
*/
if (vd->edtor && !vd->noscope)
{
// Put vd on list of things needing destruction
gIR->varsInScope().push_back(vd);
}
#endif
}
DValue* DtoDeclarationExp(Dsymbol* declaration)
{
Logger::print("DtoDeclarationExp: %s\n", declaration->toChars());
@ -1036,123 +1140,8 @@ DValue* DtoDeclarationExp(Dsymbol* declaration)
}
else
{
if (global.params.llvmAnnotate)
DtoAnnotation(declaration->toChars());
Logger::println("vdtype = %s", vd->type->toChars());
// ref vardecls are generated when DMD lowers foreach to a for statement,
// and this is a hack to support them for this case only
if(vd->isRef())
{
if (!vd->ir.irLocal)
vd->ir.irLocal = new IrLocal(vd);
ExpInitializer* ex = vd->init->isExpInitializer();
assert(ex && "ref vars must have expression initializer");
assert(ex->exp);
AssignExp* as = ex->exp->isAssignExp();
assert(as && "ref vars must be initialized by an assign exp");
DValue *val = as->e2->toElem(gIR);
if (val->isLVal())
{
vd->ir.irLocal->value = val->getLVal();
DtoVarDeclaration(vd);
}
else
{
LLValue *newVal = DtoAlloca(val->type);
DtoStore(val->getRVal(), newVal);
vd->ir.irLocal->value = newVal;
}
}
// referenced by nested delegate?
#if DMDV2
if (vd->nestedrefs.dim) {
#else
if (vd->nestedref) {
#endif
Logger::println("has nestedref set");
assert(vd->ir.irLocal);
DtoNestedInit(vd);
// is it already allocated?
} else if(vd->ir.irLocal) {
// nothing to do...
}
#if DMDV2
/* Named Return Value Optimization (NRVO):
T f(){
T ret; // &ret == hidden pointer
ret = ...
return ret; // NRVO.
}
*/
else if (gIR->func()->retArg && gIR->func()->decl->nrvo_can && gIR->func()->decl->nrvo_var == vd) {
vd->ir.irLocal = new IrLocal(vd);
vd->ir.irLocal->value = gIR->func()->retArg;
}
#endif
// normal stack variable, allocate storage on the stack if it has not already been done
else if(!vd->isRef()) {
vd->ir.irLocal = new IrLocal(vd);
#if DMDV2
/* NRVO again:
T t = f(); // t's memory address is taken hidden pointer
*/
ExpInitializer *ei = 0;
if (vd->type->toBasetype()->ty == Tstruct && vd->init &&
!!(ei = vd->init->isExpInitializer()))
{
if (ei->exp->op == TOKconstruct) {
AssignExp *ae = static_cast<AssignExp*>(ei->exp);
if (ae->e2->op == TOKcall) {
CallExp *ce = static_cast<CallExp *>(ae->e2);
TypeFunction *tf = static_cast<TypeFunction *>(ce->e1->type->toBasetype());
if (tf->ty == Tfunction && tf->fty.arg_sret) {
vd->ir.irLocal->value = ce->toElem(gIR)->getLVal();
goto Lexit;
}
}
}
}
#endif
LLType* lltype = DtoType(vd->type);
llvm::Value* allocainst;
if(gTargetData->getTypeSizeInBits(lltype) == 0)
allocainst = llvm::ConstantPointerNull::get(getPtrToType(lltype));
else
allocainst = DtoAlloca(vd->type, vd->toChars());
//allocainst->setAlignment(vd->type->alignsize()); // TODO
vd->ir.irLocal->value = allocainst;
DtoDwarfLocalVariable(allocainst, vd);
}
else
{
assert(vd->ir.irLocal->value);
}
if (Logger::enabled())
Logger::cout() << "llvm value for decl: " << *vd->ir.irLocal->value << '\n';
if (!vd->isRef())
DtoInitializer(vd->ir.irLocal->value, vd->init); // TODO: Remove altogether?
#if DMDV2
Lexit:
/* Mark the point of construction of a variable that needs to be destructed.
*/
if (vd->edtor && !vd->noscope)
{
// Put vd on list of things needing destruction
gIR->varsInScope().push_back(vd);
}
#endif
}
return new DVarValue(vd->type, vd, vd->ir.getIrValue());
}
// struct declaration
@ -1921,6 +1910,13 @@ void callPostblit(Loc &loc, Expression *exp, LLValue *val)
//////////////////////////////////////////////////////////////////////////////////////////
bool isSpecialRefVar(VarDeclaration* vd)
{
return (vd->storage_class & STCref) && (vd->storage_class & STCforeach);
}
//////////////////////////////////////////////////////////////////////////////////////////
void printLabelName(std::ostream& target, const char* func_mangle, const char* label_name)
{
target << gTargetMachine->getMCAsmInfo()->getPrivateGlobalPrefix() <<

View file

@ -98,6 +98,7 @@ void DtoResolveDsymbol(Dsymbol* dsym);
void DtoConstInitGlobal(VarDeclaration* vd);
// declaration inside a declarationexp
void DtoVarDeclaration(VarDeclaration* var);
DValue* DtoDeclarationExp(Dsymbol* declaration);
LLValue* DtoRawVarDeclaration(VarDeclaration* var, LLValue* addr = 0);
@ -158,6 +159,14 @@ LLValue* makeLValue(Loc& loc, DValue* value);
void callPostblit(Loc &loc, Expression *exp, LLValue *val);
#endif
/// Returns whether the given variable is a DMD-internal "ref variable".
///
/// D doesn't have reference variables (the ref keyword is only usable in
/// function signatures and foreach headers), but the DMD frontend internally
/// creates them in cases like lowering a ref foreach to a for loop or the
/// implicit __result variable for ref-return functions with out contracts.
bool isSpecialRefVar(VarDeclaration* vd);
////////////////////////////////////////////
// gen/tocall.cpp stuff below
////////////////////////////////////////////

View file

@ -211,7 +211,7 @@ DValue* DtoNestedVariable(Loc loc, Type* astype, VarDeclaration* vd, bool byref)
val = DtoGEPi(val, 0, vd->ir.irLocal->nestedIndex, vd->toChars());
Logger::cout() << "Addr: " << *val << '\n';
Logger::cout() << "of type: " << *val->getType() << '\n';
if (vd->ir.irLocal->byref || byref) {
if (byref || (vd->isParameter() && vd->ir.irParam->arg->byref)) {
val = DtoAlignedLoad(val);
//dwarfOpDeref(dwarfAddr);
Logger::cout() << "Was byref, now: " << *val << '\n';
@ -251,27 +251,7 @@ void DtoNestedInit(VarDeclaration* vd)
DtoAlignedStore(val, gep);
}
else if (nestedCtx == NCHybrid) {
assert(vd->ir.irLocal->value && "Nested variable without storage?");
if (!vd->isParameter() && (vd->isRef() || vd->isOut())) {
unsigned vardepth = vd->ir.irLocal->nestedDepth;
LLValue* val = NULL;
// Retrieve frame pointer
if (vardepth == irfunc->depth) {
val = nestedVar;
} else {
FuncDeclaration *parentfunc = getParentFunc(vd, true);
assert(parentfunc && "No parent function for nested variable?");
val = DtoGEPi(nestedVar, 0, vardepth);
val = DtoAlignedLoad(val, (std::string(".frame.") + parentfunc->toChars()).c_str());
}
val = DtoGEPi(val, 0, vd->ir.irLocal->nestedIndex, vd->toChars());
storeVariable(vd, val);
} else {
// Already initialized in DtoCreateNestedContext
}
// Already initialized in DtoCreateNestedContext.
}
else {
assert(0 && "Not implemented yet");
@ -492,18 +472,13 @@ static void DtoCreateNestedContextType(FuncDeclaration* fd) {
type = type->getContainedType(0);
else
type = DtoType(vd->type);
vd->ir.irParam->byref = false;
} else {
vd->ir.irParam->byref = true;
}
types.push_back(type);
} else if (vd->isRef() || vd->isOut()) {
// Foreach variables can also be by reference, for instance.
} else if (isSpecialRefVar(vd)) {
types.push_back(DtoType(vd->type->pointerTo()));
vd->ir.irLocal->byref = true;
} else {
types.push_back(DtoType(vd->type));
vd->ir.irLocal->byref = false;
}
if (Logger::enabled()) {
Logger::println("Nested var: %s", vd->toChars());
@ -692,36 +667,26 @@ void DtoCreateNestedContext(FuncDeclaration* fd) {
if (vd->isParameter()) {
Logger::println("nested param: %s", vd->toChars());
LOG_SCOPE
LLValue* value = vd->ir.irLocal->value;
if (llvm::isa<llvm::AllocaInst>(llvm::GetUnderlyingObject(value))) {
IrParameter* parm = vd->ir.irParam;
if (parm->arg->byref)
{
storeVariable(vd, gep);
}
else
{
Logger::println("Copying to nested frame");
// The parameter value is an alloca'd stack slot.
// Copy to the nesting frame and leave the alloca for
// the optimizers to clean up.
assert(!vd->ir.irLocal->byref);
DtoStore(DtoLoad(value), gep);
gep->takeName(value);
vd->ir.irLocal->value = gep;
} else {
Logger::println("Adding pointer to nested frame");
// The parameter value is something else, such as a
// passed-in pointer (for 'ref' or 'out' parameters) or
// a pointer arg with byval attribute.
// Store the address into the frame.
assert(vd->ir.irLocal->byref);
storeVariable(vd, gep);
DtoStore(DtoLoad(parm->value), gep);
gep->takeName(parm->value);
parm->value = gep;
}
} else if (vd->isRef() || vd->isOut()) {
// This slot is initialized in DtoNestedInit, to handle things like byref foreach variables
// which move around in memory.
assert(vd->ir.irLocal->byref);
} else {
Logger::println("nested var: %s", vd->toChars());
if (vd->ir.irLocal->value)
Logger::cout() << "Pre-existing value: " << *vd->ir.irLocal->value << '\n';
assert(!vd->ir.irLocal->value);
vd->ir.irLocal->value = gep;
assert(!vd->ir.irLocal->byref);
}
if (global.params.symdebug) {

View file

@ -583,6 +583,30 @@ DValue* AssignExp::toElem(IRState* p)
return newlen;
}
// Can't just override ConstructExp::toElem because not all TOKconstruct
// operations are actually instances of ConstructExp... Long live the DMD
// coding style!
if (op == TOKconstruct)
{
if (e1->op == TOKvar)
{
VarExp* ve = (VarExp*)e1;
if (ve->var->storage_class & STCref)
{
// Note that the variable value is accessed directly (instead
// of via getLValue(), which would perform a load from the
// uninitialized location), and that rhs is stored as an l-value!
IrLocal* const local = ve->var->ir.irLocal;
assert(local && "ref var must be local and already initialized");
DValue* rhs = e2->toElem(p);
DtoStore(rhs->getLVal(), local->value);
return rhs;
}
}
}
Logger::println("performing normal assignment");
DValue* l = e1->toElem(p);

View file

@ -31,7 +31,6 @@ IrGlobal::IrGlobal(VarDeclaration* v): IrVar(v)
IrLocal::IrLocal(VarDeclaration* v) : IrVar(v)
{
nestedIndex = -1;
byref = false;
}
//////////////////////////////////////////////////////////////////////////////

View file

@ -28,8 +28,8 @@ struct IrLocal : IrVar
{
IrLocal(VarDeclaration* v);
bool byref; // Not used for -nested-ctx=array
int nestedDepth; // ditto
// Used for hybrid nested context creation.
int nestedDepth;
int nestedIndex;
};