Removed the 'needsstorage' thing from Dsymbol. Arguments are not always given storage when applicable. This is not longer treat specially

in this regard. Code for accessing nested variables and contexts rewritten. Probably more. Fairly well tested.
This commit is contained in:
Tomas Lindquist Olsen
2008-08-04 02:59:34 +02:00
parent b0a5f554d6
commit daad516579
17 changed files with 232 additions and 259 deletions

View File

@@ -621,9 +621,6 @@ VarDeclaration::VarDeclaration(Loc loc, Type *type, Identifier *id, Initializer
onstack = 0;
canassign = 0;
value = NULL;
// LLVMDC
needsStorage = false;
}
Dsymbol *VarDeclaration::syntaxCopy(Dsymbol *s)
@@ -645,8 +642,6 @@ Dsymbol *VarDeclaration::syntaxCopy(Dsymbol *s)
sv = new VarDeclaration(loc, type ? type->syntaxCopy() : NULL, ident, init);
sv->storage_class = storage_class;
// LLVMDC
sv->needsStorage = needsStorage;
}
#ifdef _DH
// Syntax copy for header file

View File

@@ -266,9 +266,6 @@ struct VarDeclaration : Declaration
// Eliminate need for dynamic_cast
VarDeclaration *isVarDeclaration() { return (VarDeclaration *)this; }
// LLVMDC
bool needsStorage;
};
/**************************************************************/

View File

@@ -3628,7 +3628,6 @@ Expression *SymOffExp::semantic(Scope *sc)
if (v)
{
v->checkNestedReference(sc, loc);
v->needsStorage = true;
}
return this;
}
@@ -3777,7 +3776,6 @@ Expression *VarExp::modifiableLvalue(Scope *sc, Expression *e)
if (v && v->canassign == 0 &&
(var->isConst() || (global.params.Dversion > 1 && var->isFinal())))
error("cannot modify final variable '%s'", var->toChars());
v->needsStorage = true;
if (var->isCtorinit())
{ // It's only modifiable if inside the right constructor
@@ -5991,10 +5989,6 @@ Expression *AddrExp::semantic(Scope *sc)
e = e->semantic(sc);
return e;
}
else if (v)
{
v->needsStorage = true;
}
}
else if (e1->op == TOKarray)
{

View File

@@ -796,11 +796,6 @@ void FuncDeclaration::semantic3(Scope *sc)
if (v->storage_class & STClazy)
v->storage_class |= STCin;
v->semantic(sc2);
#if IN_LLVM
// LLVMDC: the argument needs an addres if we want to attach debug info to it.
if (global.params.symdebug)
v->needsStorage = true;
#endif
if (!sc2->insert(v))
error("parameter %s.%s is already defined", toChars(), v->toChars());
else

View File

@@ -22,10 +22,6 @@ static LLValue* to_pkey(Loc& loc, DValue* key)
if (key->isIm()) {
pkey = key->getRVal();
}
else if (key->isThis()) {
pkey = key->getRVal();
needmem = true;
}
else if (DVarValue* var = key->isVar()) {
if (var->lval) {
pkey = key->getLVal();

View File

@@ -2037,9 +2037,6 @@ struct AsmProcessor
}
} else if (exp->op == TOKvar) {
VarDeclaration * v = ((VarExp *) exp)->var->isVarDeclaration();
if (v) {
v->needsStorage = true;
}
if (v && v->storage_class & STCfield) {
operand->constDisplacement += v->offset;

View File

@@ -53,7 +53,7 @@ LLValue* DVarValue::getRVal()
else {
if (rval) return rval;
//Logger::cout() << "val: " << *val << '\n';
if (!isThis() && !isField() && DtoCanLoad(val)) {
if (!isField() && DtoCanLoad(val)) {
return DtoLoad(val);
}
return val;

View File

@@ -27,7 +27,6 @@ struct DConstValue;
struct DNullValue;
struct DVarValue;
struct DFieldValue;
struct DThisValue;
struct DFuncValue;
struct DSliceValue;
struct DArrayLenValue;
@@ -49,7 +48,6 @@ struct DValue : Object
virtual DNullValue* isNull() { return NULL; }
virtual DVarValue* isVar() { return NULL; }
virtual DFieldValue* isField() { return NULL; }
virtual DThisValue* isThis() { return NULL; }
virtual DSliceValue* isSlice() { return NULL; }
virtual DFuncValue* isFunc() { return NULL; }
virtual DArrayLenValue* isArrayLen() { return NULL; }
@@ -130,13 +128,6 @@ struct DFieldValue : DVarValue
virtual DFieldValue* isField() { return this; }
};
// this d-value
struct DThisValue : DVarValue
{
DThisValue(Type* t, VarDeclaration* vd, LLValue* llvmValue) : DVarValue(t, vd, llvmValue, true) {}
virtual DThisValue* isThis() { return this; }
};
// slice d-value
struct DSliceValue : DValue
{

View File

@@ -546,7 +546,8 @@ void DtoDefineFunc(FuncDeclaration* fd)
Logger::println("Doing function body for: %s", fd->toChars());
assert(fd->ir.irFunc);
gIR->functions.push_back(fd->ir.irFunc);
IrFunction* irfunction = fd->ir.irFunc;
gIR->functions.push_back(irfunction);
if (fd->isMain())
gIR->emitMain = true;
@@ -562,7 +563,7 @@ void DtoDefineFunc(FuncDeclaration* fd)
// create alloca point
llvm::Instruction* allocaPoint = new llvm::AllocaInst(LLType::Int32Ty, "alloca point", beginbb);
gIR->func()->allocapoint = allocaPoint;
irfunction->allocapoint = allocaPoint;
// debug info - after all allocas, but before any llvm.dbg.declare etc
if (global.params.symdebug) DtoDwarfFuncStart(fd);
@@ -574,18 +575,23 @@ void DtoDefineFunc(FuncDeclaration* fd)
fd->vresult->ir.irLocal->value = new llvm::AllocaInst(DtoType(fd->vresult->type),"function_vresult",allocaPoint);
}
// give 'this' argument debug info (and storage)
if (fd->needThis() && global.params.symdebug)
// give the 'this' argument storage and debug info
// only if not referenced by nested functions
if (fd->needThis() && !fd->vthis->nestedref)
{
LLValue** thisvar = &fd->ir.irFunc->thisVar;
assert(*thisvar);
LLValue* thismem = new llvm::AllocaInst((*thisvar)->getType(), "newthis", allocaPoint);
DtoDwarfLocalVariable(thismem, fd->vthis);
gIR->ir->CreateStore(*thisvar, thismem);
*thisvar = thismem;
LLValue* thisvar = irfunction->thisVar;
assert(thisvar);
LLValue* thismem = new llvm::AllocaInst(thisvar->getType(), ".newthis", allocaPoint);
DtoStore(thisvar, thismem);
irfunction->thisVar = thismem;
if (global.params.symdebug)
DtoDwarfLocalVariable(thismem, fd->vthis);
}
// give arguments storage
// and debug info
if (fd->parameters)
{
size_t n = fd->parameters->dim;
@@ -595,44 +601,27 @@ void DtoDefineFunc(FuncDeclaration* fd)
VarDeclaration* vd = argsym->isVarDeclaration();
assert(vd);
IrLocal* irloc = vd->ir.irLocal;
assert(irloc);
bool refoutlazy = vd->storage_class & (STCref | STCout | STClazy);
// FIXME: llvm seems to want an alloca/byval for debug info
if (!vd->needsStorage || vd->nestedref || refoutlazy)
if (vd->nestedref || refoutlazy)
{
Logger::println("skipping arg storage for (%s) %s ", vd->loc.toChars(), vd->toChars());
continue;
}
// static array params don't support debug info it seems
// probably because they're not passed byval
else if (vd->type->toBasetype()->ty == Tsarray)
{
Logger::println("skipping arg storage for static array (%s) %s ", vd->loc.toChars(), vd->toChars());
continue;
}
// debug info for normal aggr params seem to work fine
else if (DtoIsPassedByRef(vd->type))
{
Logger::println("skipping arg storage for aggregate (%s) %s ", vd->loc.toChars(), vd->toChars());
LLValue* vdirval = vd->ir.getIrValue();
LLValue* vdirval = irloc->value;
if (global.params.symdebug && !(isaArgument(vdirval) && !isaArgument(vdirval)->hasByValAttr()))
DtoDwarfLocalVariable(vdirval, vd);
continue;
}
LLValue* a = vd->ir.irLocal->value;
assert(a);
std::string s(a->getName());
Logger::println("giving argument '%s' storage", s.c_str());
s.append("_storage");
LLValue* v = new llvm::AllocaInst(a->getType(),s,allocaPoint);
if (global.params.symdebug)
DtoDwarfLocalVariable(v, vd);
gIR->ir->CreateStore(a,v);
vd->ir.irLocal->value = v;
LLValue* a = irloc->value;
LLValue* v = new llvm::AllocaInst(a->getType(), "."+a->getName(), allocaPoint);
DtoStore(a,v);
irloc->value = v;
}
}

View File

@@ -330,181 +330,154 @@ void DtoLeaveMonitor(LLValue* v)
// NESTED VARIABLE HELPERS
////////////////////////////////////////////////////////////////////////////////////////*/
static const LLType* get_next_frame_ptr_type(Dsymbol* sc)
/*
got:
context pointer of 'this' function
declaration for target context's function
want:
context pointer of target function in call chain
*/
static LLValue* dive_into_nested(Dsymbol* from, LLValue* val)
{
assert(sc->isFuncDeclaration() || sc->isClassDeclaration());
Dsymbol* p = sc->toParent2();
if (!p->isFuncDeclaration() && !p->isClassDeclaration())
Logger::println("unexpected parent symbol found while resolving frame pointer - '%s' kind: '%s'", p->toChars(), p->kind());
assert(p->isFuncDeclaration() || p->isClassDeclaration());
if (FuncDeclaration* fd = p->isFuncDeclaration())
{
LLValue* v = fd->ir.irFunc->nestedVar;
assert(v);
return v->getType();
}
else if (ClassDeclaration* cd = p->isClassDeclaration())
{
return DtoType(cd->type);
}
else
{
Logger::println("symbol: '%s' kind: '%s'", sc->toChars(), sc->kind());
assert(0);
}
}
from = from->toParent2();
//////////////////////////////////////////////////////////////////////////////////////////
static LLValue* get_frame_ptr_impl(FuncDeclaration* func, Dsymbol* sc, LLValue* v)
{
LOG_SCOPE;
if (sc == func)
// parent is a function
if (FuncDeclaration* f = from->isFuncDeclaration())
{
return v;
}
else if (FuncDeclaration* fd = sc->isFuncDeclaration())
{
Logger::println("scope is function: %s", fd->toChars());
if (fd->toParent2() == func)
IrFunction* irfunc = f->ir.irFunc;
// parent has nested var struct
if (irfunc->nestedVar)
{
if (!func->ir.irFunc->nestedVar)
return NULL;
return DtoBitCast(v, func->ir.irFunc->nestedVar->getType());
return DtoBitCast(val, irfunc->nestedVar->getType());
}
v = DtoBitCast(v, get_next_frame_ptr_type(fd));
Logger::cout() << "v = " << *v << '\n';
if (fd->toParent2()->isFuncDeclaration())
// parent has this argument
else if (irfunc->thisVar)
{
v = DtoGEPi(v, 0,0, "tmp");
v = DtoLoad(v);
}
else if (ClassDeclaration* cd = fd->toParent2()->isClassDeclaration())
{
v = DtoGEPi(v,0,2+cd->vthis->ir.irField->index,"tmp");
v = DtoLoad(v);
return DtoBitCast(val, irfunc->thisVar->getType()->getContainedType(0));
}
// none of the above, means no context is required, dummy.
else
{
assert(0);
return getNullPtr(getVoidPtrType());
}
return get_frame_ptr_impl(func, fd->toParent2(), v);
}
else if (ClassDeclaration* cd = sc->isClassDeclaration())
// parent is a class
else if (ClassDeclaration* c = from->isClassDeclaration())
{
Logger::println("scope is class: %s", cd->toChars());
return get_frame_ptr_impl(func, cd->toParent2(), v);
return DtoBitCast(DtoLoad(val), DtoType(c->type));
}
// parent is not valid
else
{
Logger::println("symbol: '%s'", sc->toPrettyChars());
assert(0);
assert(0 && "!(class|function)");
}
}
//////////////////////////////////////////////////////////////////////////////////////////
static LLValue* get_frame_ptr(FuncDeclaration* func)
{
Logger::println("Resolving context pointer for nested function: '%s'", func->toPrettyChars());
LOG_SCOPE;
IrFunction* irfunc = gIR->func();
// in the right scope already
if (func == irfunc->decl)
return irfunc->decl->ir.irFunc->nestedVar;
// use the 'this' pointer
LLValue* ptr = irfunc->decl->ir.irFunc->thisVar;
assert(ptr);
// return the fully resolved frame pointer
ptr = get_frame_ptr_impl(func, irfunc->decl, ptr);
if (ptr) Logger::cout() << "Found context!" << *ptr;
else Logger::cout() << "NULL context!\n";
return ptr;
}
//////////////////////////////////////////////////////////////////////////////////////////
LLValue* DtoNestedContext(FuncDeclaration* func)
{
// resolve frame ptr
LLValue* ptr = get_frame_ptr(func);
Logger::cout() << "Nested context ptr = ";
if (ptr) Logger::cout() << *ptr;
else Logger::cout() << "NULL";
Logger::cout() << '\n';
return ptr;
}
//////////////////////////////////////////////////////////////////////////////////////////
static void print_frame_worker(VarDeclaration* vd, Dsymbol* par)
{
if (vd->toParent2() == par)
{
Logger::println("found: '%s' kind: '%s'", par->toChars(), par->kind());
return;
}
Logger::println("diving into: '%s' kind: '%s'", par->toChars(), par->kind());
Logger::println("listing context frame list for funcdecl '%s'", func->toPrettyChars());
LOG_SCOPE;
print_frame_worker(vd, par->toParent2());
}
//////////////////////////////////////////////////////////////////////////////////////////
int level = 0;
static void print_nested_frame_list(VarDeclaration* vd, Dsymbol* par)
{
Logger::println("Frame pointer list for nested var: '%s'", vd->toPrettyChars());
LOG_SCOPE;
if (vd->toParent2() != par)
print_frame_worker(vd, par);
else
Logger::println("Found at level 0");
Logger::println("Done");
}
//////////////////////////////////////////////////////////////////////////////////////////
LLValue* DtoNestedVariable(VarDeclaration* vd)
{
// log the frame list
IrFunction* irfunc = gIR->func();
if (Logger::enabled())
print_nested_frame_list(vd, irfunc->decl);
Dsymbol* current = irfunc->decl;
// resolve frame ptr
FuncDeclaration* func = vd->toParent2()->isFuncDeclaration();
assert(func);
LLValue* ptr = DtoNestedContext(func);
assert(ptr && "nested var, but no context");
// if there is no nestedVar the context itself is what we're after
if (!func->ir.irFunc->nestedVar)
// this context ?
if (current == func)
{
return ptr;
return irfunc->nestedVar;
}
// handle a "normal" nested variable
// otherwise use the context argument
LLValue* val = dive_into_nested(current, irfunc->thisVar);
current = current->toParent2();
assert(val);
// we must cast here to be sure. nested classes just have a void*
ptr = DtoBitCast(ptr, func->ir.irFunc->nestedVar->getType());
for (;;)
{
Logger::cout() << "context: " << *val << '\n';
Logger::println("(%d) looking in: %s (%s)", level, current->toPrettyChars(), current->kind());
if (FuncDeclaration* f = current->isFuncDeclaration())
{
if (f == func)
{
Logger::println("-> found <-");
Logger::cout() << "-> val: " << *val << '\n';
return val;
}
else
{
val = DtoLoad(DtoGEPi(val,0,0));
}
}
else if (ClassDeclaration* c = current->isClassDeclaration())
{
val = DtoLoad(DtoGEPi(val, 0, 2+c->vthis->ir.irField->index));
val = dive_into_nested(current, val);
}
else
{
Logger::cout() << "val: " << *val << '\n';
assert(0 && "!(class|function)");
}
current = current->toParent2();
++level;
}
// index nested var and load (if necessary)
LLValue* v = DtoGEPi(ptr, 0, vd->ir.irLocal->nestedIndex, "tmp");
// references must be loaded, for normal variables this IS already the variable storage!!!
assert(0);
return val;
}
DValue* DtoNestedVariable(Type* astype, VarDeclaration* vd)
{
IrFunction* irfunc = gIR->func();
// var parent (the scope we're looking for)
Dsymbol* varParent = vd->toParent2();
// on level 0
if (varParent == irfunc->decl)
{
LLValue* nest = irfunc->nestedVar;
LLValue* v = DtoGEPi(nest, 0, vd->ir.irLocal->nestedIndex, "tmp");
// references must be loaded to get the variable address
if (vd->isParameter() && (vd->isRef() || vd->isOut() || DtoIsPassedByRef(vd->type)))
v = DtoLoad(v);
return new DVarValue(astype, vd, v, true);
}
// on level n != 0
FuncDeclaration* varFunc = varParent->isFuncDeclaration();
assert(varFunc);
// get context of variable
LLValue* ctx = DtoNestedContext(varFunc);
// if no local var, it's the context itself (class this)
if (!vd->ir.irLocal)
return new DImValue(astype, ctx);
// extract variable
IrLocal* local = vd->ir.irLocal;
assert(local);
assert(local->nestedIndex >= 0);
LLValue* val = DtoGEPi(ctx, 0, local->nestedIndex);
// references must be loaded to get the variable address
if (vd->isParameter() && (vd->isRef() || vd->isOut() || DtoIsPassedByRef(vd->type)))
v = DtoLoad(v);
val = DtoLoad(val);
// log and return
Logger::cout() << "Nested var ptr = " << *v << '\n';
return v;
Logger::cout() << "value: " << *val << '\n';
return new DVarValue(astype, vd, val, true);
}
/****************************************************************************************/
@@ -576,19 +549,12 @@ void DtoAssign(Loc& loc, DValue* lhs, DValue* rhs)
}
else if (t->ty == Tclass) {
assert(t2->ty == Tclass);
// assignment to this in constructor special case
if (lhs->isThis()) {
LLValue* tmp = rhs->getRVal();
FuncDeclaration* fdecl = gIR->func()->decl;
// respecify the this param
if (!llvm::isa<llvm::AllocaInst>(fdecl->ir.irFunc->thisVar))
fdecl->ir.irFunc->thisVar = new llvm::AllocaInst(tmp->getType(), "newthis", gIR->topallocapoint());
DtoStore(tmp, fdecl->ir.irFunc->thisVar);
}
// regular class ref -> class ref assignment
else {
DtoStore(rhs->getRVal(), lhs->getLVal());
}
LLValue* l = lhs->getLVal();
LLValue* r = rhs->getRVal();
Logger::cout() << "l : " << *l << '\n';
Logger::cout() << "r : " << *r << '\n';
r = DtoBitCast(r, l->getType()->getContainedType(0));
DtoStore(r, l);
}
else if (t->iscomplex()) {
assert(!lhs->isComplex());
@@ -1540,6 +1506,8 @@ LLValue* DtoBoolean(Loc& loc, DValue* dval)
else if (ty == Tpointer || ty == Tclass) {
LLValue* val = dval->getRVal();
LLValue* zero = LLConstant::getNullValue(val->getType());
Logger::cout() << "val: " << *val << '\n';
Logger::cout() << "zero: " << *zero << '\n';
return gIR->ir->CreateICmpNE(val, zero, "tmp");
}
// dynamic array

View File

@@ -36,7 +36,7 @@ void DtoLeaveMonitor(LLValue* v);
// nested variable/class helpers
LLValue* DtoNestedContext(FuncDeclaration* func);
LLValue* DtoNestedVariable(VarDeclaration* vd);
DValue* DtoNestedVariable(Type* astype, VarDeclaration* vd);
// basic operations
void DtoAssign(Loc& loc, DValue* lhs, DValue* rhs);

View File

@@ -106,7 +106,7 @@ DValue* VarExp::toElem(IRState* p)
// nested variable
else if (vd->nestedref) {
Logger::println("nested variable");
return new DVarValue(type, vd, DtoNestedVariable(vd), true);
return DtoNestedVariable(type, vd);
}
// function parameter
else if (vd->isParameter()) {
@@ -114,7 +114,7 @@ DValue* VarExp::toElem(IRState* p)
FuncDeclaration* fd = vd->toParent2()->isFuncDeclaration();
if (fd && fd != p->func()->decl) {
Logger::println("nested parameter");
return new DVarValue(type, vd, DtoNestedVariable(vd), true);
return DtoNestedVariable(type, vd);
}
else if (vd->isRef() || vd->isOut() || DtoIsPassedByRef(vd->type) || llvm::isa<llvm::AllocaInst>(vd->ir.getIrValue())) {
return new DVarValue(type, vd, vd->ir.getIrValue(), true);
@@ -472,16 +472,7 @@ DValue* AssignExp::toElem(IRState* p)
if (l->isSlice() || l->isComplex())
return l;
if (type->toBasetype()->ty == Tstruct && e2->type->isintegral())
{
// handle struct = 0;
return l;
}
else
{
assert(type->equals(e2->type));
return r;
}
return r;
}
//////////////////////////////////////////////////////////////////////////////////////////
@@ -986,25 +977,20 @@ DValue* ThisExp::toElem(IRState* p)
{
LLValue* v = p->func()->thisVar;
assert(v);
return new DImValue(type, v);
return new DVarValue(type, v, true);
}
// regular this expr
else if (VarDeclaration* vd = var->isVarDeclaration()) {
LLValue* v;
if (vd->toParent2() != p->func()->decl) {
Logger::println("nested this exp");
v = DtoLoad(DtoNestedVariable(vd));
return DtoNestedVariable(type, vd);
}
else {
Logger::println("normal this exp");
v = p->func()->decl->ir.irFunc->thisVar;
if (llvm::isa<llvm::AllocaInst>(v))
v = DtoLoad(v);
}
const LLType* t = DtoType(type);
if (v->getType() != t)
v = DtoBitCast(v, t);
return new DThisValue(type, vd, v);
return new DVarValue(type, vd, v, true);
}
// anything we're not yet handling ?
@@ -1497,7 +1483,7 @@ DValue* DeleteExp::toElem(IRState* p)
LLValue* rval = dval->getRVal();
DtoDeleteClass(rval);
}
if (!dval->isThis() && dval->isVar() && dval->isVar()->lval) {
if (dval->isVar() && dval->isVar()->lval) {
LLValue* lval = dval->getLVal();
DtoStore(llvm::Constant::getNullValue(lval->getType()->getContainedType(0)), lval);
}

24
tests/mini/foreach9.d Normal file
View File

@@ -0,0 +1,24 @@
module mini.foreach9;
extern(C) int printf(char* str, ...);
struct array2d(T) {
int test() {
printf("%p\n", cast(void*) this);
foreach (x; *this) {
printf("%p\n", cast(void*) this);
}
return true;
}
int opApply(int delegate(ref int) dg) {
int x;
return dg(x), 0;
}
}
unittest {
array2d!(int) test;
test.test();
//int i = 0; i /= i;
}
void main() { }

35
tests/mini/nested15.d Normal file
View File

@@ -0,0 +1,35 @@
// $HeadURL: svn://svn.berlios.de/dstress/trunk/run/t/this_13_A.d $
// $Date: 2006-12-31 20:59:08 +0100 (Sun, 31 Dec 2006) $
// $Author: thomask $
// @author@ Frank Benoit <benoit@tionex.de>
// @date@ 2006-10-09
// @uri@ http://d.puremagic.com/issues/show_bug.cgi?id=419
// @desc@ [Issue 419] New: Anonymous classes are not working.
// added to mini to catch regressions earlier
module mini.nested15;
class I {
abstract void get( char[] s );
}
class C{
void init(){
I i = new class() I {
void get( char[] s ){
func();
}
};
}
void func( ){ }
}
int main(){
C c = new C();
c.init();
return 0;
}

View File

@@ -11,12 +11,13 @@ void main()
{
void func()
{
printf("Hello world %d\n", i++);
printf("Hello nested world %d\n", i++);
//i++;
}
}
scope c = new C;
auto c = new C;
c.func();
printf("i = %d\n", i);
assert(i == 44);
}

View File

@@ -12,28 +12,31 @@ void main()
int j;
void func()
{
int k;
int k;
printf("C.func() %d\n", i++);
class C2
{
int l;
int l;
void func2()
{
printf("in C2.func2()\n");
printf("C2.func2() %d\n", i++);
}
int m;
int m;
}
{
scope c2 = new C2;
printf("new C2\n");
auto c2 = new C2;
printf("C2.func2()\n");
c2.func2();
}
int n;
int n;
}
int o;
int o;
}
scope c = new C;
auto c = new C;
c.func();
}

View File

@@ -55,6 +55,8 @@ int main(string[] args)
cmd ~= v;
}
int cl = classify(testname);
if (cl == COMPILE || cl == NOCOMPILE)
cmd ~= " -c";
writefln(cmd);
if (system(cmd) != 0) {
if (cl != NOCOMPILE)