/* Compiler implementation of the D programming language * Copyright (C) 1999-2019 by The D Language Foundation, All Rights Reserved * written by Walter Bright * http://www.digitalmars.com * Distributed under the Boost Software License, Version 1.0. * http://www.boost.org/LICENSE_1_0.txt * https://github.com/D-Programming-Language/dmd/blob/master/src/interpret.c */ #include "root/dsystem.h" // mem{cpy|set}() #include "root/rmem.h" #include "mars.h" #include "statement.h" #include "expression.h" #include "cond.h" #include "init.h" #include "staticassert.h" #include "mtype.h" #include "scope.h" #include "declaration.h" #include "aggregate.h" #include "id.h" #include "utf.h" #include "attrib.h" // for AttribDeclaration #include "template.h" #include "ctfe.h" /* Interpreter: what form of return value expression is required? */ enum CtfeGoal { ctfeNeedRvalue, // Must return an Rvalue (== CTFE value) ctfeNeedLvalue, // Must return an Lvalue (== CTFE reference) ctfeNeedNothing // The return value is not required }; bool walkPostorder(Expression *e, StoppableVisitor *v); Expression *interpret(Statement *s, InterState *istate); Expression *interpret(Expression *e, InterState *istate, CtfeGoal goal = ctfeNeedRvalue); Expression *semantic(Expression *e, Scope *sc); Initializer *semantic(Initializer *init, Scope *sc, Type *t, NeedInterpret needInterpret); static Expression *interpret(UnionExp *pue, Expression *e, InterState *istate, CtfeGoal goal = ctfeNeedRvalue); static Expression *interpret(UnionExp *pue, Statement *s, InterState *istate); #define SHOWPERFORMANCE 0 // Maximum allowable recursive function calls in CTFE #define CTFE_RECURSION_LIMIT 1000 /** The values of all CTFE variables */ struct CtfeStack { private: /* The stack. Every declaration we encounter is pushed here, together with the VarDeclaration, and the previous stack address of that variable, so that we can restore it when we leave the stack frame. Note that when a function is forward referenced, the interpreter must run semantic3, and that may start CTFE again with a NULL istate. Thus the stack might not be empty when CTFE begins. Ctfe Stack addresses are just 0-based integers, but we save them as 'void *' because Array can only do pointers. */ Expressions values; // values on the stack VarDeclarations vars; // corresponding variables Array savedId; // id of the previous state of that var Array frames; // all previous frame pointers Expressions savedThis; // all previous values of localThis /* Global constants get saved here after evaluation, so we never * have to redo them. This saves a lot of time and memory. */ Expressions globalValues; // values of global constants size_t framepointer; // current frame pointer size_t maxStackPointer; // most stack we've ever used Expression *localThis; // value of 'this', or NULL if none public: CtfeStack(); size_t stackPointer(); // The current value of 'this', or NULL if none Expression *getThis(); // Largest number of stack positions we've used size_t maxStackUsage(); // Start a new stack frame, using the provided 'this'. void startFrame(Expression *thisexp); void endFrame(); bool isInCurrentFrame(VarDeclaration *v); Expression *getValue(VarDeclaration *v); void setValue(VarDeclaration *v, Expression *e); void push(VarDeclaration *v); void pop(VarDeclaration *v); void popAll(size_t stackpointer); void saveGlobalConstant(VarDeclaration *v, Expression *e); }; struct InterState { InterState *caller; // calling function's InterState FuncDeclaration *fd; // function being interpreted Statement *start; // if !=NULL, start execution at this statement /* target of CTFEExp result; also * target of labelled CTFEExp or * CTFEExp. (NULL if no label). */ Statement *gotoTarget; InterState(); }; /************** CtfeStack ********************************************/ CtfeStack ctfeStack; CtfeStack::CtfeStack() : framepointer(0), maxStackPointer(0) { } size_t CtfeStack::stackPointer() { return values.dim; } Expression *CtfeStack::getThis() { return localThis; } // Largest number of stack positions we've used size_t CtfeStack::maxStackUsage() { return maxStackPointer; } void CtfeStack::startFrame(Expression *thisexp) { frames.push((void *)(size_t)(framepointer)); savedThis.push(localThis); framepointer = stackPointer(); localThis = thisexp; } void CtfeStack::endFrame() { size_t oldframe = (size_t)(frames[frames.dim-1]); localThis = savedThis[savedThis.dim-1]; popAll(framepointer); framepointer = oldframe; frames.setDim(frames.dim - 1); savedThis.setDim(savedThis.dim -1); } bool CtfeStack::isInCurrentFrame(VarDeclaration *v) { if (v->isDataseg() && !v->isCTFE()) return false; // It's a global return v->ctfeAdrOnStack >= (int)framepointer; } Expression *CtfeStack::getValue(VarDeclaration *v) { if ((v->isDataseg() || v->storage_class & STCmanifest) && !v->isCTFE()) { assert(v->ctfeAdrOnStack >= 0 && v->ctfeAdrOnStack < (int)globalValues.dim); return globalValues[v->ctfeAdrOnStack]; } assert(v->ctfeAdrOnStack >= 0 && v->ctfeAdrOnStack < (int)stackPointer()); return values[v->ctfeAdrOnStack]; } void CtfeStack::setValue(VarDeclaration *v, Expression *e) { assert(!v->isDataseg() || v->isCTFE()); assert(v->ctfeAdrOnStack >= 0 && v->ctfeAdrOnStack < (int)stackPointer()); values[v->ctfeAdrOnStack] = e; } void CtfeStack::push(VarDeclaration *v) { assert(!v->isDataseg() || v->isCTFE()); if (v->ctfeAdrOnStack != -1 && v->ctfeAdrOnStack >= (int)framepointer) { // Already exists in this frame, reuse it. values[v->ctfeAdrOnStack] = NULL; return; } savedId.push((void *)(size_t)(v->ctfeAdrOnStack)); v->ctfeAdrOnStack = (int)values.dim; vars.push(v); values.push(NULL); } void CtfeStack::pop(VarDeclaration *v) { assert(!v->isDataseg() || v->isCTFE()); assert(!(v->storage_class & (STCref | STCout))); int oldid = v->ctfeAdrOnStack; v->ctfeAdrOnStack = (int)(size_t)(savedId[oldid]); if (v->ctfeAdrOnStack == (int)values.dim - 1) { values.pop(); vars.pop(); savedId.pop(); } } void CtfeStack::popAll(size_t stackpointer) { if (stackPointer() > maxStackPointer) maxStackPointer = stackPointer(); assert(values.dim >= stackpointer); for (size_t i = stackpointer; i < values.dim; ++i) { VarDeclaration *v = vars[i]; v->ctfeAdrOnStack = (int)(size_t)(savedId[i]); } values.setDim(stackpointer); vars.setDim(stackpointer); savedId.setDim(stackpointer); } void CtfeStack::saveGlobalConstant(VarDeclaration *v, Expression *e) { assert(v->_init && (v->isConst() || v->isImmutable() || v->storage_class & STCmanifest) && !v->isCTFE()); v->ctfeAdrOnStack = (int)globalValues.dim; globalValues.push(e); } /************** InterState ********************************************/ InterState::InterState() { memset(this, 0, sizeof(InterState)); } /************** CtfeStatus ********************************************/ int CtfeStatus::callDepth = 0; int CtfeStatus::stackTraceCallsToSuppress = 0; int CtfeStatus::maxCallDepth = 0; int CtfeStatus::numArrayAllocs = 0; int CtfeStatus::numAssignments = 0; // CTFE diagnostic information void printCtfePerformanceStats() { #if SHOWPERFORMANCE printf(" ---- CTFE Performance ----\n"); printf("max call depth = %d\tmax stack = %d\n", CtfeStatus::maxCallDepth, ctfeStack.maxStackUsage()); printf("array allocs = %d\tassignments = %d\n\n", CtfeStatus::numArrayAllocs, CtfeStatus::numAssignments); #endif } static Expression *evaluateIfBuiltin(UnionExp *pue, InterState *istate, Loc loc, FuncDeclaration *fd, Expressions *arguments, Expression *pthis); static Expression *evaluatePostblit(InterState *istate, Expression *e); static Expression *evaluateDtor(InterState *istate, Expression *e); static bool isEntirelyVoid(Expressions* elems); static Expression *scrubArray(Loc loc, Expressions *elems, bool structlit = false); static Expression *scrubStructLiteral(Loc loc, StructLiteralExp *sle); static Expression *scrubReturnValue(Loc loc, Expression *e); static Expression *scrubArrayCache(Expressions *elems); static Expression *scrubStructLiteralCache(StructLiteralExp *sle); static Expression *scrubCacheValue(Expression *e); /************************************* * CTFE-object code for a single function * * Currently only counts the number of local variables in the function */ struct CompiledCtfeFunction { FuncDeclaration *func; // Function being compiled, NULL if global scope int numVars; // Number of variables declared in this function Loc callingloc; CompiledCtfeFunction(FuncDeclaration *f) { func = f; numVars = 0; } void onDeclaration(VarDeclaration *) { //printf("%s CTFE declare %s\n", v->loc.toChars(), v->toChars()); ++numVars; } void onExpression(Expression *e) { class VarWalker : public StoppableVisitor { public: CompiledCtfeFunction *ccf; VarWalker(CompiledCtfeFunction *ccf) : ccf(ccf) { } void visit(Expression *) { } void visit(ErrorExp *e) { // Currently there's a front-end bug: silent errors // can occur inside delegate literals inside is(typeof()). // Suppress the check in this case. if (global.gag && ccf->func) { stop = 1; return; } ::error(e->loc, "CTFE internal error: ErrorExp in %s\n", ccf->func ? ccf->func->loc.toChars() : ccf->callingloc.toChars()); assert(0); } void visit(DeclarationExp *e) { VarDeclaration *v = e->declaration->isVarDeclaration(); if (!v) return; TupleDeclaration *td = v->toAlias()->isTupleDeclaration(); if (td) { if (!td->objects) return; for (size_t i= 0; i < td->objects->dim; ++i) { RootObject *o = td->objects->tdata()[i]; Expression *ex = isExpression(o); DsymbolExp *s = (ex && ex->op == TOKdsymbol) ? (DsymbolExp *)ex : NULL; assert(s); VarDeclaration *v2 = s->s->isVarDeclaration(); assert(v2); if (!v2->isDataseg() || v2->isCTFE()) ccf->onDeclaration(v2); } } else if (!(v->isDataseg() || v->storage_class & STCmanifest) || v->isCTFE()) ccf->onDeclaration(v); Dsymbol *s = v->toAlias(); if (s == v && !v->isStatic() && v->_init) { ExpInitializer *ie = v->_init->isExpInitializer(); if (ie) ccf->onExpression(ie->exp); } } void visit(IndexExp *e) { if (e->lengthVar) ccf->onDeclaration(e->lengthVar); } void visit(SliceExp *e) { if (e->lengthVar) ccf->onDeclaration(e->lengthVar); } }; VarWalker v(this); walkPostorder(e, &v); } }; class CtfeCompiler : public Visitor { public: CompiledCtfeFunction *ccf; CtfeCompiler(CompiledCtfeFunction *ccf) : ccf(ccf) { } void visit(Statement *) { assert(0); } void visit(ExpStatement *s) { if (s->exp) ccf->onExpression(s->exp); } void visit(CompoundStatement *s) { for (size_t i = 0; i < s->statements->dim; i++) { Statement *sx = (*s->statements)[i]; if (sx) ctfeCompile(sx); } } void visit(UnrolledLoopStatement *s) { for (size_t i = 0; i < s->statements->dim; i++) { Statement *sx = (*s->statements)[i]; if (sx) ctfeCompile(sx); } } void visit(IfStatement *s) { ccf->onExpression(s->condition); if (s->ifbody) ctfeCompile(s->ifbody); if (s->elsebody) ctfeCompile(s->elsebody); } void visit(ScopeStatement *s) { if (s->statement) ctfeCompile(s->statement); } void visit(OnScopeStatement *) { // rewritten to try/catch/finally assert(0); } void visit(DoStatement *s) { ccf->onExpression(s->condition); if (s->_body) ctfeCompile(s->_body); } void visit(WhileStatement *) { // rewritten to ForStatement assert(0); } void visit(ForStatement *s) { if (s->_init) ctfeCompile(s->_init); if (s->condition) ccf->onExpression(s->condition); if (s->increment) ccf->onExpression(s->increment); if (s->_body) ctfeCompile(s->_body); } void visit(ForeachStatement *) { // rewritten for ForStatement assert(0); } void visit(SwitchStatement *s) { ccf->onExpression(s->condition); // Note that the body contains the the Case and Default // statements, so we only need to compile the expressions for (size_t i = 0; i < s->cases->dim; i++) { ccf->onExpression((*s->cases)[i]->exp); } if (s->_body) ctfeCompile(s->_body); } void visit(CaseStatement *s) { if (s->statement) ctfeCompile(s->statement); } void visit(DefaultStatement *s) { if (s->statement) ctfeCompile(s->statement); } void visit(GotoDefaultStatement *) { } void visit(GotoCaseStatement *) { } void visit(SwitchErrorStatement *) { } void visit(ReturnStatement *s) { if (s->exp) ccf->onExpression(s->exp); } void visit(BreakStatement *) { } void visit(ContinueStatement *) { } void visit(WithStatement *s) { // If it is with(Enum) {...}, just execute the body. if (s->exp->op == TOKscope || s->exp->op == TOKtype) { } else { ccf->onDeclaration(s->wthis); ccf->onExpression(s->exp); } if (s->_body) ctfeCompile(s->_body); } void visit(TryCatchStatement *s) { if (s->_body) ctfeCompile(s->_body); for (size_t i = 0; i < s->catches->dim; i++) { Catch *ca = (*s->catches)[i]; if (ca->var) ccf->onDeclaration(ca->var); if (ca->handler) ctfeCompile(ca->handler); } } void visit(TryFinallyStatement *s) { if (s->_body) ctfeCompile(s->_body); if (s->finalbody) ctfeCompile(s->finalbody); } void visit(ThrowStatement *s) { ccf->onExpression(s->exp); } void visit(GotoStatement *) { } void visit(LabelStatement *s) { if (s->statement) ctfeCompile(s->statement); } void visit(ImportStatement *) { // Contains no variables or executable code } void visit(ForeachRangeStatement *) { // rewritten for ForStatement assert(0); } void visit(AsmStatement *) { // we can't compile asm statements } void ctfeCompile(Statement *s) { s->accept(this); } }; /************************************* * Compile this function for CTFE. * At present, this merely allocates variables. */ void ctfeCompile(FuncDeclaration *fd) { assert(!fd->ctfeCode); assert(!fd->semantic3Errors); assert(fd->semanticRun == PASSsemantic3done); fd->ctfeCode = new CompiledCtfeFunction(fd); if (fd->parameters) { Type *tb = fd->type->toBasetype(); assert(tb->ty == Tfunction); for (size_t i = 0; i < fd->parameters->dim; i++) { VarDeclaration *v = (*fd->parameters)[i]; fd->ctfeCode->onDeclaration(v); } } if (fd->vresult) fd->ctfeCode->onDeclaration(fd->vresult); CtfeCompiler v(fd->ctfeCode); v.ctfeCompile(fd->fbody); } /************************************* * Entry point for CTFE. * A compile-time result is required. Give an error if not possible. * * `e` must be semantically valid expression. In other words, it should not * contain any `ErrorExp`s in it. But, CTFE interpretation will cross over * functions and may invoke a function that contains `ErrorStatement` in its body. * If that, the "CTFE failed because of previous errors" error is raised. */ Expression *ctfeInterpret(Expression *e) { switch (e->op) { case TOKint64: case TOKfloat64: case TOKcomplex80: case TOKnull: case TOKvoid: case TOKstring: case TOKthis: case TOKsuper: case TOKtype: case TOKtypeid: case TOKtemplate: // non-eponymous template/instance case TOKscope: // ditto case TOKdottd: // ditto, e.e1 doesn't matter here case TOKdot: // ditto if (e->type->ty == Terror) return new ErrorExp(); /* fall through */ case TOKerror: return e; default: break; } assert(e->type); // Bugzilla 14642 //assert(e->type->ty != Terror); // FIXME if (e->type->ty == Terror) return new ErrorExp(); // This code is outside a function, but still needs to be compiled // (there are compiler-generated temporary variables such as __dollar). // However, this will only be run once and can then be discarded. CompiledCtfeFunction ctfeCodeGlobal(NULL); ctfeCodeGlobal.callingloc = e->loc; ctfeCodeGlobal.onExpression(e); Expression *result = interpret(e, NULL); if (!CTFEExp::isCantExp(result)) result = scrubReturnValue(e->loc, result); if (CTFEExp::isCantExp(result)) result = new ErrorExp(); return result; } /* Run CTFE on the expression, but allow the expression to be a TypeExp * or a tuple containing a TypeExp. (This is required by pragma(msg)). */ Expression *ctfeInterpretForPragmaMsg(Expression *e) { if (e->op == TOKerror || e->op == TOKtype) return e; // It's also OK for it to be a function declaration (happens only with // __traits(getOverloads)) if (e->op == TOKvar && ((VarExp *)e)->var->isFuncDeclaration()) { return e; } if (e->op != TOKtuple) return e->ctfeInterpret(); // Tuples need to be treated seperately, since they are // allowed to contain a TypeExp in this case. TupleExp *tup = (TupleExp *)e; Expressions *expsx = NULL; for (size_t i = 0; i < tup->exps->dim; ++i) { Expression *g = (*tup->exps)[i]; Expression *h = g; h = ctfeInterpretForPragmaMsg(g); if (h != g) { if (!expsx) { expsx = new Expressions(); expsx->setDim(tup->exps->dim); for (size_t j = 0; j < tup->exps->dim; j++) (*expsx)[j] = (*tup->exps)[j]; } (*expsx)[i] = h; } } if (expsx) { TupleExp *te = new TupleExp(e->loc, expsx); expandTuples(te->exps); te->type = new TypeTuple(te->exps); return te; } return e; } /************************************* * Attempt to interpret a function given the arguments. * Input: * pue storage for result * fd function being called * istate state for calling function (NULL if none) * arguments function arguments * thisarg 'this', if a needThis() function, NULL if not. * * Return result expression if successful, TOKcantexp if not, * or CTFEExp if function returned void. */ static Expression *interpretFunction(UnionExp *pue, FuncDeclaration *fd, InterState *istate, Expressions *arguments, Expression *thisarg) { assert(pue); if (fd->semanticRun == PASSsemantic3) { fd->error("circular dependency. Functions cannot be interpreted while being compiled"); return CTFEExp::cantexp; } if (!fd->functionSemantic3()) return CTFEExp::cantexp; if (fd->semanticRun < PASSsemantic3done) return CTFEExp::cantexp; // CTFE-compile the function if (!fd->ctfeCode) ctfeCompile(fd); Type *tb = fd->type->toBasetype(); assert(tb->ty == Tfunction); TypeFunction *tf = (TypeFunction *)tb; if (tf->varargs && arguments && ((fd->parameters && arguments->dim != fd->parameters->dim) || (!fd->parameters && arguments->dim))) { fd->error("C-style variadic functions are not yet implemented in CTFE"); return CTFEExp::cantexp; } // Nested functions always inherit the 'this' pointer from the parent, // except for delegates. (Note that the 'this' pointer may be null). // Func literals report isNested() even if they are in global scope, // so we need to check that the parent is a function. if (fd->isNested() && fd->toParent2()->isFuncDeclaration() && !thisarg && istate) thisarg = ctfeStack.getThis(); if (fd->needThis() && !thisarg) { // error, no this. Prevent segfault. // Here should be unreachable by the strict 'this' check in front-end. fd->error("need 'this' to access member %s", fd->toChars()); return CTFEExp::cantexp; } // Place to hold all the arguments to the function while // we are evaluating them. Expressions eargs; size_t dim = arguments ? arguments->dim : 0; assert((fd->parameters ? fd->parameters->dim : 0) == dim); /* Evaluate all the arguments to the function, * store the results in eargs[] */ eargs.setDim(dim); for (size_t i = 0; i < dim; i++) { Expression *earg = (*arguments)[i]; Parameter *fparam = Parameter::getNth(tf->parameters, i); if (fparam->storageClass & (STCout | STCref)) { if (!istate && (fparam->storageClass & STCout)) { // initializing an out parameter involves writing to it. earg->error("global %s cannot be passed as an 'out' parameter at compile time", earg->toChars()); return CTFEExp::cantexp; } // Convert all reference arguments into lvalue references earg = interpret(earg, istate, ctfeNeedLvalue); if (CTFEExp::isCantExp(earg)) return earg; } else if (fparam->storageClass & STClazy) { } else { /* Value parameters */ Type *ta = fparam->type->toBasetype(); if (ta->ty == Tsarray && earg->op == TOKaddress) { /* Static arrays are passed by a simple pointer. * Skip past this to get at the actual arg. */ earg = ((AddrExp *)earg)->e1; } earg = interpret(earg, istate); if (CTFEExp::isCantExp(earg)) return earg; /* Struct literals are passed by value, but we don't need to * copy them if they are passed as const */ if (earg->op == TOKstructliteral && !(fparam->storageClass & (STCconst | STCimmutable))) earg = copyLiteral(earg).copy(); } if (earg->op == TOKthrownexception) { if (istate) return earg; ((ThrownExceptionExp *)earg)->generateUncaughtError(); return CTFEExp::cantexp; } eargs[i] = earg; } // Now that we've evaluated all the arguments, we can start the frame // (this is the moment when the 'call' actually takes place). InterState istatex; istatex.caller = istate; istatex.fd = fd; ctfeStack.startFrame(thisarg); if (fd->vthis && thisarg) { ctfeStack.push(fd->vthis); setValue(fd->vthis, thisarg); } for (size_t i = 0; i < dim; i++) { Expression *earg = eargs[i]; Parameter *fparam = Parameter::getNth(tf->parameters, i); VarDeclaration *v = (*fd->parameters)[i]; ctfeStack.push(v); if ((fparam->storageClass & (STCout | STCref)) && earg->op == TOKvar && ((VarExp *)earg)->var->toParent2() == fd) { VarDeclaration *vx = ((VarExp *)earg)->var->isVarDeclaration(); if (!vx) { fd->error("cannot interpret %s as a ref parameter", earg->toChars()); return CTFEExp::cantexp; } /* vx is a variable that is declared in fd. * It means that fd is recursively called. e.g. * * void fd(int n, ref int v = dummy) { * int vx; * if (n == 1) fd(2, vx); * } * fd(1); * * The old value of vx on the stack in fd(1) * should be saved at the start of fd(2, vx) call. */ int oldadr = vx->ctfeAdrOnStack; ctfeStack.push(vx); assert(!hasValue(vx)); // vx is made uninitialized // Bugzilla 14299: v->ctfeAdrOnStack should be saved already // in the stack before the overwrite. v->ctfeAdrOnStack = oldadr; assert(hasValue(v)); // ref parameter v should refer existing value. } else { // Value parameters and non-trivial references setValueWithoutChecking(v, earg); } } if (fd->vresult) ctfeStack.push(fd->vresult); // Enter the function ++CtfeStatus::callDepth; if (CtfeStatus::callDepth > CtfeStatus::maxCallDepth) CtfeStatus::maxCallDepth = CtfeStatus::callDepth; Expression *e = NULL; while (1) { if (CtfeStatus::callDepth > CTFE_RECURSION_LIMIT) { // This is a compiler error. It must not be suppressed. global.gag = 0; fd->error("CTFE recursion limit exceeded"); e = CTFEExp::cantexp; break; } e = interpret(pue, fd->fbody, &istatex); if (istatex.start) { fd->error("CTFE internal error: failed to resume at statement %s", istatex.start->toChars()); return CTFEExp::cantexp; } /* This is how we deal with a recursive statement AST * that has arbitrary goto statements in it. * Bubble up a 'result' which is the target of the goto * statement, then go recursively down the AST looking * for that statement, then execute starting there. */ if (CTFEExp::isGotoExp(e)) { istatex.start = istatex.gotoTarget; // set starting statement istatex.gotoTarget = NULL; } else { assert(!e || (e->op != TOKcontinue && e->op != TOKbreak)); break; } } // If fell off the end of a void function, return void if (!e && tf->next->ty == Tvoid) e = CTFEExp::voidexp; if (tf->isref && e->op == TOKvar && ((VarExp *)e)->var == fd->vthis) e = thisarg; assert(e != NULL); // Leave the function --CtfeStatus::callDepth; ctfeStack.endFrame(); // If it generated an uncaught exception, report error. if (!istate && e->op == TOKthrownexception) { if (e == pue->exp()) e = pue->copy(); ((ThrownExceptionExp *)e)->generateUncaughtError(); e = CTFEExp::cantexp; } return e; } class Interpreter : public Visitor { public: InterState *istate; CtfeGoal goal; Expression *result; UnionExp *pue; // storage for `result` Interpreter(UnionExp *pue, InterState *istate, CtfeGoal goal) : istate(istate), goal(goal), pue(pue) { result = NULL; } // If e is TOKthrowexception or TOKcantexp, // set it to 'result' and returns true. bool exceptionOrCant(Expression *e) { if (exceptionOrCantInterpret(e)) { // Make sure e is not pointing to a stack temporary result = (e->op == TOKcantexp) ? CTFEExp::cantexp : e; return true; } return false; } static Expressions *copyArrayOnWrite(Expressions *exps, Expressions *original) { if (exps == original) { if (!original) exps = new Expressions(); else exps = original->copy(); ++CtfeStatus::numArrayAllocs; } return exps; } /******************************** Statement ***************************/ void visit(Statement *s) { if (istate->start) { if (istate->start != s) return; istate->start = NULL; } s->error("statement %s cannot be interpreted at compile time", s->toChars()); result = CTFEExp::cantexp; } void visit(ExpStatement *s) { if (istate->start) { if (istate->start != s) return; istate->start = NULL; } Expression *e = interpret(pue, s->exp, istate, ctfeNeedNothing); if (exceptionOrCant(e)) return; } void visit(CompoundStatement *s) { if (istate->start == s) istate->start = NULL; const size_t dim = s->statements ? s->statements->dim : 0; for (size_t i = 0; i < dim; i++) { Statement *sx = (*s->statements)[i]; result = interpret(pue, sx, istate); if (result) break; } } void visit(UnrolledLoopStatement *s) { if (istate->start == s) istate->start = NULL; const size_t dim = s->statements ? s->statements->dim : 0; for (size_t i = 0; i < dim; i++) { Statement *sx = (*s->statements)[i]; Expression *e = interpret(pue, sx, istate); if (!e) // suceeds to interpret, or goto target continue; // was not fonnd when istate->start != NULL if (exceptionOrCant(e)) return; if (e->op == TOKbreak) { if (istate->gotoTarget && istate->gotoTarget != s) { result = e; // break at a higher level return; } istate->gotoTarget = NULL; result = NULL; return; } if (e->op == TOKcontinue) { if (istate->gotoTarget && istate->gotoTarget != s) { result = e; // continue at a higher level return; } istate->gotoTarget = NULL; continue; } // expression from return statement, or thrown exception result = e; break; } } void visit(IfStatement *s) { if (istate->start == s) istate->start = NULL; if (istate->start) { Expression *e = NULL; e = interpret(s->ifbody, istate); if (!e && istate->start) e = interpret(s->elsebody, istate); result = e; return; } UnionExp ue; Expression *e = interpret(&ue, s->condition, istate); assert(e); if (exceptionOrCant(e)) return; if (isTrueBool(e)) result = interpret(pue, s->ifbody, istate); else if (e->isBool(false)) result = interpret(pue, s->elsebody, istate); else { // no error, or assert(0)? result = CTFEExp::cantexp; } } void visit(ScopeStatement *s) { if (istate->start == s) istate->start = NULL; result = interpret(pue, s->statement, istate); } /** Given an expression e which is about to be returned from the current function, generate an error if it contains pointers to local variables. Only checks expressions passed by value (pointers to local variables may already be stored in members of classes, arrays, or AAs which were passed as mutable function parameters). Returns: true if it is safe to return, false if an error was generated. */ static bool stopPointersEscaping(Loc loc, Expression *e) { if (!e->type->hasPointers()) return true; if (isPointer(e->type)) { Expression *x = e; if (e->op == TOKaddress) x = ((AddrExp *)e)->e1; VarDeclaration *v; while (x->op == TOKvar && (v = ((VarExp *)x)->var->isVarDeclaration()) != NULL) { if (v->storage_class & STCref) { x = getValue(v); if (e->op == TOKaddress) ((AddrExp *)e)->e1 = x; continue; } if (ctfeStack.isInCurrentFrame(v)) { error(loc, "returning a pointer to a local stack variable"); return false; } else break; } // TODO: If it is a TOKdotvar or TOKindex, we should check that it is not // pointing to a local struct or static array. } if (e->op == TOKstructliteral) { StructLiteralExp *se = (StructLiteralExp *)e; return stopPointersEscapingFromArray(loc, se->elements); } if (e->op == TOKarrayliteral) { return stopPointersEscapingFromArray(loc, ((ArrayLiteralExp *)e)->elements); } if (e->op == TOKassocarrayliteral) { AssocArrayLiteralExp *aae = (AssocArrayLiteralExp *)e; if (!stopPointersEscapingFromArray(loc, aae->keys)) return false; return stopPointersEscapingFromArray(loc, aae->values); } return true; } // Check all members of an array for escaping local variables. Return false if error static bool stopPointersEscapingFromArray(Loc loc, Expressions *elems) { for (size_t i = 0; i < elems->dim; i++) { Expression *m = (*elems)[i]; if (!m) continue; if (!stopPointersEscaping(loc, m)) return false; } return true; } void visit(ReturnStatement *s) { if (istate->start) { if (istate->start != s) return; istate->start = NULL; } if (!s->exp) { result = CTFEExp::voidexp; return; } assert(istate && istate->fd && istate->fd->type && istate->fd->type->ty == Tfunction); TypeFunction *tf = (TypeFunction *)istate->fd->type; /* If the function returns a ref AND it's been called from an assignment, * we need to return an lvalue. Otherwise, just do an (rvalue) interpret. */ if (tf->isref) { result = interpret(pue, s->exp, istate, ctfeNeedLvalue); return; } if (tf->next && tf->next->ty == Tdelegate && istate->fd->closureVars.dim > 0) { // To support this, we need to copy all the closure vars // into the delegate literal. s->error("closures are not yet supported in CTFE"); result = CTFEExp::cantexp; return; } // We need to treat pointers specially, because TOKsymoff can be used to // return a value OR a pointer Expression *e = interpret(pue, s->exp, istate); if (exceptionOrCant(e)) return; // Disallow returning pointers to stack-allocated variables (bug 7876) if (!stopPointersEscaping(s->loc, e)) { result = CTFEExp::cantexp; return; } if (needToCopyLiteral(e)) e = copyLiteral(e).copy(); result = e; } static Statement *findGotoTarget(InterState *istate, Identifier *ident) { Statement *target = NULL; if (ident) { LabelDsymbol *label = istate->fd->searchLabel(ident); assert(label && label->statement); LabelStatement *ls = label->statement; target = ls->gotoTarget ? ls->gotoTarget : ls->statement; } return target; } void visit(BreakStatement *s) { if (istate->start) { if (istate->start != s) return; istate->start = NULL; } istate->gotoTarget = findGotoTarget(istate, s->ident); result = CTFEExp::breakexp; } void visit(ContinueStatement *s) { if (istate->start) { if (istate->start != s) return; istate->start = NULL; } istate->gotoTarget = findGotoTarget(istate, s->ident); result = CTFEExp::continueexp; } void visit(WhileStatement *) { assert(0); // rewritten to ForStatement } void visit(DoStatement *s) { if (istate->start == s) istate->start = NULL; while (1) { Expression *e = interpret(s->_body, istate); if (!e && istate->start) // goto target was not found return; assert(!istate->start); if (exceptionOrCant(e)) return; if (e && e->op == TOKbreak) { if (istate->gotoTarget && istate->gotoTarget != s) { result = e; // break at a higher level return; } istate->gotoTarget = NULL; break; } if (e && e->op == TOKcontinue) { if (istate->gotoTarget && istate->gotoTarget != s) { result = e; // continue at a higher level return; } istate->gotoTarget = NULL; e = NULL; } if (e) { result = e; // bubbled up from ReturnStatement return; } UnionExp ue; e = interpret(&ue, s->condition, istate); if (exceptionOrCant(e)) return; if (!e->isConst()) { result = CTFEExp::cantexp; return; } if (e->isBool(false)) break; assert(isTrueBool(e)); } assert(result == NULL); } void visit(ForStatement *s) { if (istate->start == s) istate->start = NULL; UnionExp ueinit; Expression *ei = interpret(&ueinit, s->_init, istate); if (exceptionOrCant(ei)) return; assert(!ei); // s->init never returns from function, or jumps out from it while (1) { if (s->condition && !istate->start) { UnionExp ue; Expression *e = interpret(&ue, s->condition, istate); if (exceptionOrCant(e)) return; if (e->isBool(false)) break; assert(isTrueBool(e)); } Expression *e = interpret(pue, s->_body, istate); if (!e && istate->start) // goto target was not found return; assert(!istate->start); if (exceptionOrCant(e)) return; if (e && e->op == TOKbreak) { if (istate->gotoTarget && istate->gotoTarget != s) { result = e; // break at a higher level return; } istate->gotoTarget = NULL; break; } if (e && e->op == TOKcontinue) { if (istate->gotoTarget && istate->gotoTarget != s) { result = e; // continue at a higher level return; } istate->gotoTarget = NULL; e = NULL; } if (e) { result = e; // bubbled up from ReturnStatement return; } UnionExp uei; e = interpret(&uei, s->increment, istate, ctfeNeedNothing); if (exceptionOrCant(e)) return; } assert(result == NULL); } void visit(ForeachStatement *) { assert(0); // rewritten to ForStatement } void visit(ForeachRangeStatement *) { assert(0); // rewritten to ForStatement } void visit(SwitchStatement *s) { if (istate->start == s) istate->start = NULL; if (istate->start) { Expression *e = interpret(s->_body, istate); if (istate->start) // goto target was not found return; if (exceptionOrCant(e)) return; if (e && e->op == TOKbreak) { if (istate->gotoTarget && istate->gotoTarget != s) { result = e; // break at a higher level return; } istate->gotoTarget = NULL; e = NULL; } result = e; return; } UnionExp uecond; Expression *econdition = interpret(&uecond, s->condition, istate); if (exceptionOrCant(econdition)) return; Statement *scase = NULL; size_t dim = s->cases ? s->cases->dim : 0; for (size_t i = 0; i < dim; i++) { CaseStatement *cs = (*s->cases)[i]; UnionExp uecase; Expression *ecase = interpret(&uecase, cs->exp, istate); if (exceptionOrCant(ecase)) return; if (ctfeEqual(cs->exp->loc, TOKequal, econdition, ecase)) { scase = cs; break; } } if (!scase) { if (s->hasNoDefault) s->error("no default or case for %s in switch statement", econdition->toChars()); scase = s->sdefault; } assert(scase); /* Jump to scase */ istate->start = scase; Expression *e = interpret(pue, s->_body, istate); assert(!istate->start); // jump must not fail if (e && e->op == TOKbreak) { if (istate->gotoTarget && istate->gotoTarget != s) { result = e; // break at a higher level return; } istate->gotoTarget = NULL; e = NULL; } result = e; } void visit(CaseStatement *s) { if (istate->start == s) istate->start = NULL; result = interpret(pue, s->statement, istate); } void visit(DefaultStatement *s) { if (istate->start == s) istate->start = NULL; result = interpret(pue, s->statement, istate); } void visit(GotoStatement *s) { if (istate->start) { if (istate->start != s) return; istate->start = NULL; } assert(s->label && s->label->statement); istate->gotoTarget = s->label->statement; result = CTFEExp::gotoexp; } void visit(GotoCaseStatement *s) { if (istate->start) { if (istate->start != s) return; istate->start = NULL; } assert(s->cs); istate->gotoTarget = s->cs; result = CTFEExp::gotoexp; } void visit(GotoDefaultStatement *s) { if (istate->start) { if (istate->start != s) return; istate->start = NULL; } assert(s->sw && s->sw->sdefault); istate->gotoTarget = s->sw->sdefault; result = CTFEExp::gotoexp; } void visit(LabelStatement *s) { if (istate->start == s) istate->start = NULL; result = interpret(pue, s->statement, istate); } void visit(TryCatchStatement *s) { if (istate->start == s) istate->start = NULL; if (istate->start) { Expression *e = NULL; e = interpret(pue, s->_body, istate); for (size_t i = 0; i < s->catches->dim; i++) { if (e || !istate->start) // goto target was found break; Catch *ca = (*s->catches)[i]; e = interpret(ca->handler, istate); } result = e; return; } Expression *e = interpret(pue, s->_body, istate); // An exception was thrown if (e && e->op == TOKthrownexception) { ThrownExceptionExp *ex = (ThrownExceptionExp *)e; Type *extype = ex->thrown->originalClass()->type; // Search for an appropriate catch clause. for (size_t i = 0; i < s->catches->dim; i++) { Catch *ca = (*s->catches)[i]; Type *catype = ca->type; if (!catype->equals(extype) && !catype->isBaseOf(extype, NULL)) continue; // Execute the handler if (ca->var) { ctfeStack.push(ca->var); setValue(ca->var, ex->thrown); } e = interpret(ca->handler, istate); if (CTFEExp::isGotoExp(e)) { /* This is an optimization that relies on the locality of the jump target. * If the label is in the same catch handler, the following scan * would find it quickly and can reduce jump cost. * Otherwise, the catch block may be unnnecessary scanned again * so it would make CTFE speed slower. */ InterState istatex = *istate; istatex.start = istate->gotoTarget; // set starting statement istatex.gotoTarget = NULL; Expression *eh = interpret(ca->handler, &istatex); if (!istatex.start) { istate->gotoTarget = NULL; e = eh; } } break; } } result = e; } static bool isAnErrorException(ClassDeclaration *cd) { return cd == ClassDeclaration::errorException || ClassDeclaration::errorException->isBaseOf(cd, NULL); } static ThrownExceptionExp *chainExceptions(ThrownExceptionExp *oldest, ThrownExceptionExp *newest) { // Little sanity check to make sure it's really a Throwable ClassReferenceExp *boss = oldest->thrown; const int next = 4; // index of Throwable.next assert((*boss->value->elements)[next]->type->ty == Tclass); // Throwable.next ClassReferenceExp *collateral = newest->thrown; if ( isAnErrorException(collateral->originalClass()) && !isAnErrorException(boss->originalClass())) { /* Find the index of the Error.bypassException field */ int bypass = next + 1; if ((*collateral->value->elements)[bypass]->type->ty == Tuns32) bypass += 1; // skip over _refcount field assert((*collateral->value->elements)[bypass]->type->ty == Tclass); // The new exception bypass the existing chain (*collateral->value->elements)[bypass] = boss; return newest; } while ((*boss->value->elements)[next]->op == TOKclassreference) { boss = (ClassReferenceExp *)(*boss->value->elements)[next]; } (*boss->value->elements)[next] = collateral; return oldest; } void visit(TryFinallyStatement *s) { if (istate->start == s) istate->start = NULL; if (istate->start) { Expression *e = NULL; e = interpret(pue, s->_body, istate); // Jump into/out from finalbody is disabled in semantic analysis. // and jump inside will be handled by the ScopeStatement == finalbody. result = e; return; } Expression *ex = interpret(s->_body, istate); if (CTFEExp::isCantExp(ex)) { result = ex; return; } while (CTFEExp::isGotoExp(ex)) { // If the goto target is within the body, we must not interpret the finally statement, // because that will call destructors for objects within the scope, which we should not do. InterState istatex = *istate; istatex.start = istate->gotoTarget; // set starting statement istatex.gotoTarget = NULL; Expression *bex = interpret(s->_body, &istatex); if (istatex.start) { // The goto target is outside the current scope. break; } // The goto target was within the body. if (CTFEExp::isCantExp(bex)) { result = bex; return; } *istate = istatex; ex = bex; } Expression *ey = interpret(s->finalbody, istate); if (CTFEExp::isCantExp(ey)) { result = ey; return; } if (ey && ey->op == TOKthrownexception) { // Check for collided exceptions if (ex && ex->op == TOKthrownexception) ex = chainExceptions((ThrownExceptionExp *)ex, (ThrownExceptionExp *)ey); else ex = ey; } result = ex; } void visit(ThrowStatement *s) { if (istate->start) { if (istate->start != s) return; istate->start = NULL; } Expression *e = interpret(s->exp, istate); if (exceptionOrCant(e)) return; assert(e->op == TOKclassreference); result = new ThrownExceptionExp(s->loc, (ClassReferenceExp *)e); } void visit(OnScopeStatement *) { assert(0); } void visit(WithStatement *s) { if (istate->start == s) istate->start = NULL; if (istate->start) { result = s->_body ? interpret(s->_body, istate) : NULL; return; } // If it is with(Enum) {...}, just execute the body. if (s->exp->op == TOKscope || s->exp->op == TOKtype) { result = interpret(pue, s->_body, istate); return; } Expression *e = interpret(s->exp, istate); if (exceptionOrCant(e)) return; if (s->wthis->type->ty == Tpointer && s->exp->type->ty != Tpointer) { e = new AddrExp(s->loc, e, s->wthis->type); } ctfeStack.push(s->wthis); setValue(s->wthis, e); e = interpret(s->_body, istate); if (CTFEExp::isGotoExp(e)) { /* This is an optimization that relies on the locality of the jump target. * If the label is in the same WithStatement, the following scan * would find it quickly and can reduce jump cost. * Otherwise, the statement body may be unnnecessary scanned again * so it would make CTFE speed slower. */ InterState istatex = *istate; istatex.start = istate->gotoTarget; // set starting statement istatex.gotoTarget = NULL; Expression *ex = interpret(s->_body, &istatex); if (!istatex.start) { istate->gotoTarget = NULL; e = ex; } } ctfeStack.pop(s->wthis); result = e; } void visit(AsmStatement *s) { if (istate->start) { if (istate->start != s) return; istate->start = NULL; } s->error("asm statements cannot be interpreted at compile time"); result = CTFEExp::cantexp; } void visit(ImportStatement *s) { if (istate->start) { if (istate->start != s) return; istate->start = NULL; } } /******************************** Expression ***************************/ void visit(Expression *e) { e->error("cannot interpret %s at compile time", e->toChars()); result = CTFEExp::cantexp; } void visit(ThisExp *e) { if (goal == ctfeNeedLvalue) { // We might end up here with istate being zero (see bugzilla 16382) if (istate && istate->fd->vthis) { result = new VarExp(e->loc, istate->fd->vthis); result->type = e->type; } else result = e; return; } result = ctfeStack.getThis(); if (result) { assert(result->op == TOKstructliteral || result->op == TOKclassreference); return; } e->error("value of 'this' is not known at compile time"); result = CTFEExp::cantexp; } void visit(NullExp *e) { result = e; } void visit(IntegerExp *e) { result = e; } void visit(RealExp *e) { result = e; } void visit(ComplexExp *e) { result = e; } void visit(StringExp *e) { /* Attempts to modify string literals are prevented * in BinExp::interpretAssignCommon. */ result = e; } void visit(FuncExp *e) { result = e; } void visit(SymOffExp *e) { if (e->var->isFuncDeclaration() && e->offset == 0) { result = e; return; } if (isTypeInfo_Class(e->type) && e->offset == 0) { result = e; return; } if (e->type->ty != Tpointer) { // Probably impossible e->error("cannot interpret %s at compile time", e->toChars()); result = CTFEExp::cantexp; return; } Type *pointee = ((TypePointer *)e->type)->next; if (e->var->isThreadlocal()) { e->error("cannot take address of thread-local variable %s at compile time", e->var->toChars()); result = CTFEExp::cantexp; return; } // Check for taking an address of a shared variable. // If the shared variable is an array, the offset might not be zero. Type *fromType = NULL; if (e->var->type->ty == Tarray || e->var->type->ty == Tsarray) { fromType = ((TypeArray *)(e->var->type))->next; } if (e->var->isDataseg() && ((e->offset == 0 && isSafePointerCast(e->var->type, pointee)) || (fromType && isSafePointerCast(fromType, pointee)))) { result = e; return; } Expression *val = getVarExp(e->loc, istate, e->var, goal); if (exceptionOrCant(val)) return; if (val->type->ty == Tarray || val->type->ty == Tsarray) { // Check for unsupported type painting operations Type *elemtype = ((TypeArray *)(val->type))->next; d_uns64 elemsize = elemtype->size(); // It's OK to cast from fixed length to fixed length array, eg &int[n] to int[d]*. if (val->type->ty == Tsarray && pointee->ty == Tsarray && elemsize == pointee->nextOf()->size()) { size_t d = (size_t)((TypeSArray *)pointee)->dim->toInteger(); Expression *elwr = new IntegerExp(e->loc, e->offset / elemsize, Type::tsize_t); Expression *eupr = new IntegerExp(e->loc, e->offset / elemsize + d, Type::tsize_t); // Create a CTFE pointer &val[ofs..ofs+d] SliceExp *se = new SliceExp(e->loc, val, elwr, eupr); se->type = pointee; new(pue) AddrExp(e->loc, se, e->type); result = pue->exp(); return; } if (!isSafePointerCast(elemtype, pointee)) { // It's also OK to cast from &string to string*. if (e->offset == 0 && isSafePointerCast(e->var->type, pointee)) { // Create a CTFE pointer &var VarExp *ve = new VarExp(e->loc, e->var); ve->type = elemtype; new(pue) AddrExp(e->loc, ve, e->type); result = pue->exp(); return; } e->error("reinterpreting cast from %s to %s is not supported in CTFE", val->type->toChars(), e->type->toChars()); result = CTFEExp::cantexp; return; } const dinteger_t sz = pointee->size(); dinteger_t indx = e->offset / sz; assert(sz * indx == e->offset); Expression *aggregate = NULL; if (val->op == TOKarrayliteral || val->op == TOKstring) { aggregate = val; } else if (val->op == TOKslice) { aggregate = ((SliceExp *)val)->e1; UnionExp uelwr; Expression *lwr = interpret(&uelwr, ((SliceExp *)val)->lwr, istate); indx += lwr->toInteger(); } if (aggregate) { // Create a CTFE pointer &aggregate[ofs] IntegerExp *ofs = new IntegerExp(e->loc, indx, Type::tsize_t); IndexExp *ei = new IndexExp(e->loc, aggregate, ofs); ei->type = elemtype; new(pue) AddrExp(e->loc, ei, e->type); result = pue->exp(); return; } } else if (e->offset == 0 && isSafePointerCast(e->var->type, pointee)) { // Create a CTFE pointer &var VarExp *ve = new VarExp(e->loc, e->var); ve->type = e->var->type; new(pue) AddrExp(e->loc, ve, e->type); result = pue->exp(); return; } e->error("cannot convert &%s to %s at compile time", e->var->type->toChars(), e->type->toChars()); result = CTFEExp::cantexp; } void visit(AddrExp *e) { if (e->e1->op == TOKvar && ((VarExp *)e->e1)->var->isDataseg()) { // Normally this is already done by optimize() // Do it here in case optimize(WANTvalue) wasn't run before CTFE new(pue) SymOffExp(e->loc, ((VarExp *)e->e1)->var, 0); result = pue->exp(); result->type = e->type; return; } Expression *er = interpret(e->e1, istate, ctfeNeedLvalue); if (er->op == TOKvar && ((VarExp *)er)->var == istate->fd->vthis) er = interpret(er, istate); if (exceptionOrCant(er)) return; // Return a simplified address expression new(pue) AddrExp(e->loc, er, e->type); result = pue->exp(); } void visit(DelegateExp *e) { // TODO: Really we should create a CTFE-only delegate expression // of a pointer and a funcptr. // If it is &nestedfunc, just return it // TODO: We should save the context pointer if (e->e1->op == TOKvar && ((VarExp *)e->e1)->var == e->func) { result = e; return; } Expression *er = interpret(pue, e->e1, istate); if (exceptionOrCant(er)) return; if (er == e->e1) { // If it has already been CTFE'd, just return it result = e; } else { er = (er == pue->exp()) ? pue->copy() : er; new(pue) DelegateExp(e->loc, er, e->func, false); result = pue->exp(); result->type = e->type; } } static Expression *getVarExp(Loc loc, InterState *istate, Declaration *d, CtfeGoal goal) { Expression *e = CTFEExp::cantexp; if (VarDeclaration *v = d->isVarDeclaration()) { /* Magic variable __ctfe always returns true when interpreting */ if (v->ident == Id::ctfe) return new IntegerExp(loc, 1, Type::tbool); if (!v->originalType && v->semanticRun < PASSsemanticdone) // semantic() not yet run { v->semantic(NULL); if (v->type->ty == Terror) return CTFEExp::cantexp; } if ((v->isConst() || v->isImmutable() || v->storage_class & STCmanifest) && !hasValue(v) && v->_init && !v->isCTFE()) { if (v->inuse) { error(loc, "circular initialization of %s '%s'", v->kind(), v->toPrettyChars()); return CTFEExp::cantexp; } if (v->_scope) { v->inuse++; v->_init = ::semantic(v->_init, v->_scope, v->type, INITinterpret); // might not be run on aggregate members v->inuse--; } e = initializerToExpression(v->_init, v->type); if (!e) return CTFEExp::cantexp; assert(e->type); if (e->op == TOKconstruct || e->op == TOKblit) { AssignExp *ae = (AssignExp *)e; e = ae->e2; } if (e->op == TOKerror) { // FIXME: Ultimately all errors should be detected in prior semantic analysis stage. } else if (v->isDataseg() || (v->storage_class & STCmanifest)) { /* Bugzilla 14304: e is a value that is not yet owned by CTFE. * Mark as "cached", and use it directly during interpretation. */ e = scrubCacheValue(e); ctfeStack.saveGlobalConstant(v, e); } else { v->inuse++; e = interpret(e, istate); v->inuse--; if (CTFEExp::isCantExp(e) && !global.gag && !CtfeStatus::stackTraceCallsToSuppress) errorSupplemental(loc, "while evaluating %s.init", v->toChars()); if (exceptionOrCantInterpret(e)) return e; } } else if (v->isCTFE() && !hasValue(v)) { if (v->_init && v->type->size() != 0) { if (v->_init->isVoidInitializer()) { // var should have been initialized when it was created error(loc, "CTFE internal error: trying to access uninitialized var"); assert(0); return CTFEExp::cantexp; } e = initializerToExpression(v->_init); } else e = v->type->defaultInitLiteral(e->loc); e = interpret(e, istate); } else if (!(v->isDataseg() || v->storage_class & STCmanifest) && !v->isCTFE() && !istate) { error(loc, "variable %s cannot be read at compile time", v->toChars()); return CTFEExp::cantexp; } else { e = hasValue(v) ? getValue(v) : NULL; if (!e && !v->isCTFE() && v->isDataseg()) { error(loc, "static variable %s cannot be read at compile time", v->toChars()); return CTFEExp::cantexp; } if (!e) { assert(!(v->_init && v->_init->isVoidInitializer())); // CTFE initiated from inside a function error(loc, "variable %s cannot be read at compile time", v->toChars()); return CTFEExp::cantexp; } if (e->op == TOKvoid) { VoidInitExp *ve = (VoidInitExp *)e; error(loc, "cannot read uninitialized variable %s in ctfe", v->toPrettyChars()); errorSupplemental(ve->var->loc, "%s was uninitialized and used before set", ve->var->toChars()); return CTFEExp::cantexp; } if (goal != ctfeNeedLvalue && (v->isRef() || v->isOut())) e = interpret(e, istate, goal); } if (!e) e = CTFEExp::cantexp; } else if (SymbolDeclaration *s = d->isSymbolDeclaration()) { // Struct static initializers, for example e = s->dsym->type->defaultInitLiteral(loc); if (e->op == TOKerror) error(loc, "CTFE failed because of previous errors in %s.init", s->toChars()); e = ::semantic(e, NULL); if (e->op == TOKerror) e = CTFEExp::cantexp; else // Convert NULL to CTFEExp e = interpret(e, istate, goal); } else error(loc, "cannot interpret declaration %s at compile time", d->toChars()); return e; } void visit(VarExp *e) { if (e->var->isFuncDeclaration()) { result = e; return; } if (goal == ctfeNeedLvalue) { VarDeclaration *v = e->var->isVarDeclaration(); if (v && !v->isDataseg() && !v->isCTFE() && !istate) { e->error("variable %s cannot be read at compile time", v->toChars()); result = CTFEExp::cantexp; return; } if (v && !hasValue(v)) { if (!v->isCTFE() && v->isDataseg()) e->error("static variable %s cannot be read at compile time", v->toChars()); else // CTFE initiated from inside a function e->error("variable %s cannot be read at compile time", v->toChars()); result = CTFEExp::cantexp; return; } if (v && (v->storage_class & (STCout | STCref)) && hasValue(v)) { // Strip off the nest of ref variables Expression *ev = getValue(v); if (ev->op == TOKvar || ev->op == TOKindex || ev->op == TOKdotvar) { result = interpret(pue, ev, istate, goal); return; } } result = e; return; } result = getVarExp(e->loc, istate, e->var, goal); if (exceptionOrCant(result)) return; if ((e->var->storage_class & (STCref | STCout)) == 0 && e->type->baseElemOf()->ty != Tstruct) { /* Ultimately, STCref|STCout check should be enough to see the * necessity of type repainting. But currently front-end paints * non-ref struct variables by the const type. * * auto foo(ref const S cs); * S s; * foo(s); // VarExp('s') will have const(S) */ // A VarExp may include an implicit cast. It must be done explicitly. result = paintTypeOntoLiteral(pue, e->type, result); } } void visit(DeclarationExp *e) { Dsymbol *s = e->declaration; if (VarDeclaration *v = s->isVarDeclaration()) { if (TupleDeclaration *td = v->toAlias()->isTupleDeclaration()) { result = NULL; // Reserve stack space for all tuple members if (!td->objects) return; for (size_t i = 0; i < td->objects->dim; ++i) { RootObject * o = (*td->objects)[i]; Expression *ex = isExpression(o); DsymbolExp *ds = (ex && ex->op == TOKdsymbol) ? (DsymbolExp *)ex : NULL; VarDeclaration *v2 = ds ? ds->s->isVarDeclaration() : NULL; assert(v2); if (v2->isDataseg() && !v2->isCTFE()) continue; ctfeStack.push(v2); if (v2->_init) { Expression *einit; if (ExpInitializer *ie = v2->_init->isExpInitializer()) { einit = interpret(ie->exp, istate, goal); if (exceptionOrCant(einit)) return; } else if (v2->_init->isVoidInitializer()) { einit = voidInitLiteral(v2->type, v2).copy(); } else { e->error("declaration %s is not yet implemented in CTFE", e->toChars()); result = CTFEExp::cantexp; return; } setValue(v2, einit); } } return; } if (v->isStatic()) { // Just ignore static variables which aren't read or written yet result = NULL; return; } if (!(v->isDataseg() || v->storage_class & STCmanifest) || v->isCTFE()) ctfeStack.push(v); if (v->_init) { if (ExpInitializer *ie = v->_init->isExpInitializer()) { result = interpret(ie->exp, istate, goal); } else if (v->_init->isVoidInitializer()) { result = voidInitLiteral(v->type, v).copy(); // There is no AssignExp for void initializers, // so set it here. setValue(v, result); } else { e->error("declaration %s is not yet implemented in CTFE", e->toChars()); result = CTFEExp::cantexp; } } else if (v->type->size() == 0) { // Zero-length arrays don't need an initializer result = v->type->defaultInitLiteral(e->loc); } else { e->error("variable %s cannot be modified at compile time", v->toChars()); result = CTFEExp::cantexp; } return; } if (s->isAttribDeclaration() || s->isTemplateMixin() || s->isTupleDeclaration()) { // Check for static struct declarations, which aren't executable AttribDeclaration *ad = e->declaration->isAttribDeclaration(); if (ad && ad->decl && ad->decl->dim == 1) { Dsymbol *sparent = (*ad->decl)[0]; if (sparent->isAggregateDeclaration() || sparent->isTemplateDeclaration() || sparent->isAliasDeclaration()) { result = NULL; return; // static (template) struct declaration. Nothing to do. } } // These can be made to work, too lazy now e->error("declaration %s is not yet implemented in CTFE", e->toChars()); result = CTFEExp::cantexp; return; } // Others should not contain executable code, so are trivial to evaluate result = NULL; } void visit(TypeidExp *e) { if (isType(e->obj)) { result = e; return; } if (Expression *ex = isExpression(e->obj)) { result = interpret(pue, ex, istate); if (exceptionOrCant(ex)) return; if (result->op == TOKnull) { e->error("null pointer dereference evaluating typeid. '%s' is null", ex->toChars()); result = CTFEExp::cantexp; return; } if (result->op != TOKclassreference) { e->error("CTFE internal error: determining classinfo"); result = CTFEExp::cantexp; return; } ClassDeclaration *cd = ((ClassReferenceExp *)result)->originalClass(); assert(cd); new(pue) TypeidExp(e->loc, cd->type); result = pue->exp(); result->type = e->type; return; } visit((Expression *)e); } void visit(TupleExp *e) { if (exceptionOrCant(interpret(e->e0, istate, ctfeNeedNothing))) return; Expressions *expsx = e->exps; for (size_t i = 0; i < expsx->dim; i++) { Expression *exp = (*expsx)[i]; Expression *ex = interpret(exp, istate); if (exceptionOrCant(ex)) return; // A tuple of assignments can contain void (Bug 5676). if (goal == ctfeNeedNothing) continue; if (ex->op == TOKvoidexp) { e->error("CTFE internal error: void element %s in tuple", exp->toChars()); assert(0); } /* If any changes, do Copy On Write */ if (ex != exp) { expsx = copyArrayOnWrite(expsx, e->exps); (*expsx)[i] = ex; } } if (expsx != e->exps) { expandTuples(expsx); new(pue) TupleExp(e->loc, expsx); result = pue->exp(); result->type = new TypeTuple(expsx); } else result = e; } void visit(ArrayLiteralExp *e) { if (e->ownedByCtfe >= OWNEDctfe) // We've already interpreted all the elements { result = e; return; } Type *tn = e->type->toBasetype()->nextOf()->toBasetype(); bool wantCopy = (tn->ty == Tsarray || tn->ty == Tstruct); Expression *basis = interpret(e->basis, istate); if (exceptionOrCant(basis)) return; Expressions *expsx = e->elements; size_t dim = expsx ? expsx->dim : 0; for (size_t i = 0; i < dim; i++) { Expression *exp = (*expsx)[i]; Expression *ex; if (!exp) { ex = copyLiteral(basis).copy(); } else { // segfault bug 6250 assert(exp->op != TOKindex || ((IndexExp *)exp)->e1 != e); ex = interpret(exp, istate); if (exceptionOrCant(ex)) return; /* Each elements should have distinct CFE memory. * int[1] z = 7; * int[1][] pieces = [z,z]; // here */ if (wantCopy) ex = copyLiteral(ex).copy(); } /* If any changes, do Copy On Write */ if (ex != exp) { expsx = copyArrayOnWrite(expsx, e->elements); (*expsx)[i] = ex; } } if (expsx != e->elements) { // todo: all tuple expansions should go in semantic phase. expandTuples(expsx); if (expsx->dim != dim) { e->error("CTFE internal error: invalid array literal"); result = CTFEExp::cantexp; return; } new(pue) ArrayLiteralExp(e->loc, e->type, basis, expsx); ArrayLiteralExp *ale = (ArrayLiteralExp *)pue->exp(); ale->ownedByCtfe = OWNEDctfe; result = ale; } else if (((TypeNext *)e->type)->next->mod & (MODconst | MODimmutable)) { // If it's immutable, we don't need to dup it result = e; } else { *pue = copyLiteral(e); result = pue->exp(); } } void visit(AssocArrayLiteralExp *e) { if (e->ownedByCtfe >= OWNEDctfe) // We've already interpreted all the elements { result = e; return; } Expressions *keysx = e->keys; Expressions *valuesx = e->values; for (size_t i = 0; i < keysx->dim; i++) { Expression *ekey = (*keysx)[i]; Expression *evalue = (*valuesx)[i]; Expression *ek = interpret(ekey, istate); if (exceptionOrCant(ek)) return; Expression *ev = interpret(evalue, istate); if (exceptionOrCant(ev)) return; /* If any changes, do Copy On Write */ if (ek != ekey || ev != evalue) { keysx = copyArrayOnWrite(keysx, e->keys); valuesx = copyArrayOnWrite(valuesx, e->values); (*keysx)[i] = ek; (*valuesx)[i] = ev; } } if (keysx != e->keys) expandTuples(keysx); if (valuesx != e->values) expandTuples(valuesx); if (keysx->dim != valuesx->dim) { e->error("CTFE internal error: invalid AA"); result = CTFEExp::cantexp; return; } /* Remove duplicate keys */ for (size_t i = 1; i < keysx->dim; i++) { Expression *ekey = (*keysx)[i - 1]; for (size_t j = i; j < keysx->dim; j++) { Expression *ekey2 = (*keysx)[j]; if (!ctfeEqual(e->loc, TOKequal, ekey, ekey2)) continue; // Remove ekey keysx = copyArrayOnWrite(keysx, e->keys); valuesx = copyArrayOnWrite(valuesx, e->values); keysx->remove(i - 1); valuesx->remove(i - 1); i -= 1; // redo the i'th iteration break; } } if (keysx != e->keys || valuesx != e->values) { assert(keysx != e->keys && valuesx != e->values); AssocArrayLiteralExp *ae = new AssocArrayLiteralExp(e->loc, keysx, valuesx); ae->type = e->type; ae->ownedByCtfe = OWNEDctfe; result = ae; } else { *pue = copyLiteral(e); result = pue->exp(); } } void visit(StructLiteralExp *e) { if (e->ownedByCtfe >= OWNEDctfe) { result = e; return; } size_t dim = e->elements ? e->elements->dim : 0; Expressions *expsx = e->elements; if (dim != e->sd->fields.dim) { // guaranteed by AggregateDeclaration.fill and TypeStruct.defaultInitLiteral assert(e->sd->isNested() && dim == e->sd->fields.dim - 1); /* If a nested struct has no initialized hidden pointer, * set it to null to match the runtime behaviour. */ NullExp *ne = new NullExp(e->loc); ne->type = e->sd->vthis->type; expsx = copyArrayOnWrite(expsx, e->elements); expsx->push(ne); ++dim; } assert(dim == e->sd->fields.dim); for (size_t i = 0; i < dim; i++) { VarDeclaration *v = e->sd->fields[i]; Expression *exp = (*expsx)[i]; Expression *ex = NULL; if (!exp) { ex = voidInitLiteral(v->type, v).copy(); } else { ex = interpret(exp, istate); if (exceptionOrCant(ex)) return; if ((v->type->ty != ex->type->ty) && v->type->ty == Tsarray) { // Block assignment from inside struct literals TypeSArray *tsa = (TypeSArray *)v->type; size_t len = (size_t)tsa->dim->toInteger(); UnionExp ue; ex = createBlockDuplicatedArrayLiteral(&ue, ex->loc, v->type, ex, len); if (ex == ue.exp()) ex = ue.copy(); } } /* If any changes, do Copy On Write */ if (ex != exp) { expsx = copyArrayOnWrite(expsx, e->elements); (*expsx)[i] = ex; } } if (expsx != e->elements) { expandTuples(expsx); if (expsx->dim != e->sd->fields.dim) { e->error("CTFE internal error: invalid struct literal"); result = CTFEExp::cantexp; return; } new(pue) StructLiteralExp(e->loc, e->sd, expsx); StructLiteralExp *sle = (StructLiteralExp *)pue->exp(); sle->type = e->type; sle->ownedByCtfe = OWNEDctfe; sle->origin = e->origin; result = sle; } else { *pue = copyLiteral(e); result = pue->exp(); } } // Create an array literal of type 'newtype' with dimensions given by // 'arguments'[argnum..$] static Expression *recursivelyCreateArrayLiteral(UnionExp *pue, Loc loc, Type *newtype, InterState *istate, Expressions *arguments, int argnum) { Expression *lenExpr = interpret(pue, (*arguments)[argnum], istate); if (exceptionOrCantInterpret(lenExpr)) return lenExpr; size_t len = (size_t)(lenExpr->toInteger()); Type *elemType = ((TypeArray *)newtype)->next; if (elemType->ty == Tarray && argnum < (int)arguments->dim - 1) { Expression *elem = recursivelyCreateArrayLiteral(pue, loc, elemType, istate, arguments, argnum + 1); if (exceptionOrCantInterpret(elem)) return elem; Expressions *elements = new Expressions(); elements->setDim(len); for (size_t i = 0; i < len; i++) (*elements)[i] = copyLiteral(elem).copy(); new(pue) ArrayLiteralExp(loc, newtype, elements); ArrayLiteralExp *ae = (ArrayLiteralExp *)pue->exp(); ae->ownedByCtfe = OWNEDctfe; return ae; } assert(argnum == (int)arguments->dim - 1); if (elemType->ty == Tchar || elemType->ty == Twchar || elemType->ty == Tdchar) { const unsigned ch = (unsigned)elemType->defaultInitLiteral(loc)->toInteger(); const unsigned char sz = (unsigned char)elemType->size(); return createBlockDuplicatedStringLiteral(pue, loc, newtype, ch, len, sz); } else { Expression *el = interpret(elemType->defaultInitLiteral(loc), istate); return createBlockDuplicatedArrayLiteral(pue, loc, newtype, el, len); } } void visit(NewExp *e) { if (e->allocator) { e->error("member allocators not supported by CTFE"); result = CTFEExp::cantexp; return; } Expression *epre = interpret(pue, e->argprefix, istate, ctfeNeedNothing); if (exceptionOrCant(epre)) return; if (e->newtype->ty == Tarray && e->arguments) { result = recursivelyCreateArrayLiteral(pue, e->loc, e->newtype, istate, e->arguments, 0); return; } if (e->newtype->toBasetype()->ty == Tstruct) { if (e->member) { Expression *se = e->newtype->defaultInitLiteral(e->loc); se = interpret(se, istate); if (exceptionOrCant(se)) return; result = interpretFunction(pue, e->member, istate, e->arguments, se); // Repaint as same as CallExp::interpret() does. result->loc = e->loc; } else { StructDeclaration *sd = ((TypeStruct *)e->newtype->toBasetype())->sym; Expressions *exps = new Expressions(); exps->reserve(sd->fields.dim); if (e->arguments) { exps->setDim(e->arguments->dim); for (size_t i = 0; i < exps->dim; i++) { Expression *ex = (*e->arguments)[i]; ex = interpret(ex, istate); if (exceptionOrCant(ex)) return; (*exps)[i] = ex; } } sd->fill(e->loc, exps, false); StructLiteralExp *se = new StructLiteralExp(e->loc, sd, exps, e->newtype); se->type = e->newtype; se->ownedByCtfe = OWNEDctfe; result = interpret(pue, se, istate); } if (exceptionOrCant(result)) return; Expression *ev = (result == pue->exp()) ? pue->copy() : result; new(pue) AddrExp(e->loc, ev, e->type); result = pue->exp(); return; } if (e->newtype->toBasetype()->ty == Tclass) { ClassDeclaration *cd = ((TypeClass *)e->newtype->toBasetype())->sym; size_t totalFieldCount = 0; for (ClassDeclaration *c = cd; c; c = c->baseClass) totalFieldCount += c->fields.dim; Expressions *elems = new Expressions; elems->setDim(totalFieldCount); size_t fieldsSoFar = totalFieldCount; for (ClassDeclaration *c = cd; c; c = c->baseClass) { fieldsSoFar -= c->fields.dim; for (size_t i = 0; i < c->fields.dim; i++) { VarDeclaration *v = c->fields[i]; if (v->inuse) { e->error("circular reference to '%s'", v->toPrettyChars()); result = CTFEExp::cantexp; return; } Expression *m; if (v->_init) { if (v->_init->isVoidInitializer()) m = voidInitLiteral(v->type, v).copy(); else m = v->getConstInitializer(true); } else m = v->type->defaultInitLiteral(e->loc); if (exceptionOrCant(m)) return; (*elems)[fieldsSoFar+i] = copyLiteral(m).copy(); } } // Hack: we store a ClassDeclaration instead of a StructDeclaration. // We probably won't get away with this. StructLiteralExp *se = new StructLiteralExp(e->loc, (StructDeclaration *)cd, elems, e->newtype); se->ownedByCtfe = OWNEDctfe; new(pue) ClassReferenceExp(e->loc, se, e->type); Expression *eref = pue->exp(); if (e->member) { // Call constructor if (!e->member->fbody) { Expression *ctorfail = evaluateIfBuiltin(pue, istate, e->loc, e->member, e->arguments, eref); if (ctorfail) { if (exceptionOrCant(ctorfail)) return; result = eref; return; } e->member->error("%s cannot be constructed at compile time, because the constructor has no available source code", e->newtype->toChars()); result = CTFEExp::cantexp; return; } UnionExp ue; Expression *ctorfail = interpretFunction(&ue, e->member, istate, e->arguments, eref); if (exceptionOrCant(ctorfail)) return; /* Bugzilla 14465: Repaint the loc, because a super() call * in the constructor modifies the loc of ClassReferenceExp * in CallExp::interpret(). */ eref->loc = e->loc; } result = eref; return; } if (e->newtype->toBasetype()->isscalar()) { Expression *newval; if (e->arguments && e->arguments->dim) newval = (*e->arguments)[0]; else newval = e->newtype->defaultInitLiteral(e->loc); newval = interpret(newval, istate); if (exceptionOrCant(newval)) return; // Create a CTFE pointer &[newval][0] Expressions *elements = new Expressions(); elements->setDim(1); (*elements)[0] = newval; ArrayLiteralExp *ae = new ArrayLiteralExp(e->loc, e->newtype->arrayOf(), elements); ae->ownedByCtfe = OWNEDctfe; IndexExp *ei = new IndexExp(e->loc, ae, new IntegerExp(Loc(), 0, Type::tsize_t)); ei->type = e->newtype; new(pue) AddrExp(e->loc, ei, e->type); result = pue->exp(); return; } e->error("cannot interpret %s at compile time", e->toChars()); result = CTFEExp::cantexp; } void visit(UnaExp *e) { UnionExp ue; Expression *e1 = interpret(&ue, e->e1, istate); if (exceptionOrCant(e1)) return; switch (e->op) { case TOKneg: *pue = Neg(e->type, e1); break; case TOKtilde: *pue = Com(e->type, e1); break; case TOKnot: *pue = Not(e->type, e1); break; default: assert(0); } result = (*pue).exp(); } void visit(DotTypeExp *e) { UnionExp ue; Expression *e1 = interpret(&ue, e->e1, istate); if (exceptionOrCant(e1)) return; if (e1 == e->e1) result = e; // optimize: reuse this CTFE reference else { DotTypeExp *edt = (DotTypeExp *)e->copy(); edt->e1 = (e1 == ue.exp()) ? e1->copy() : e1; // don't return pointer to ue result = edt; } } bool evalOperand(UnionExp *pue, Expression *e, Expression *ex, Expression *&er) { er = interpret(pue, ex, istate); if (exceptionOrCant(er)) return false; if (er->isConst() != 1) { if (er->op == TOKarrayliteral) // Until we get it to work, issue a reasonable error message e->error("cannot interpret array literal expression %s at compile time", e->toChars()); else e->error("CTFE internal error: non-constant value %s", ex->toChars()); result = CTFEExp::cantexp; return false; } return true; } void interpretCommon(BinExp *e, fp_t fp) { if (e->e1->type->ty == Tpointer && e->e2->type->ty == Tpointer && e->op == TOKmin) { UnionExp ue1; Expression *e1 = interpret(&ue1, e->e1, istate); if (exceptionOrCant(e1)) return; UnionExp ue2; Expression *e2 = interpret(&ue2, e->e2, istate); if (exceptionOrCant(e2)) return; *pue = pointerDifference(e->loc, e->type, e1, e2); result = (*pue).exp(); return; } if (e->e1->type->ty == Tpointer && e->e2->type->isintegral()) { UnionExp ue1; Expression *e1 = interpret(&ue1, e->e1, istate); if (exceptionOrCant(e1)) return; UnionExp ue2; Expression *e2 = interpret(&ue2, e->e2, istate); if (exceptionOrCant(e2)) return; *pue = pointerArithmetic(e->loc, e->op, e->type, e1, e2); result = (*pue).exp(); return; } if (e->e2->type->ty == Tpointer && e->e1->type->isintegral() && e->op == TOKadd) { UnionExp ue1; Expression *e1 = interpret(&ue1, e->e1, istate); if (exceptionOrCant(e1)) return; UnionExp ue2; Expression *e2 = interpret(&ue2, e->e2, istate); if (exceptionOrCant(e2)) return; *pue = pointerArithmetic(e->loc, e->op, e->type, e2, e1); result = (*pue).exp(); return; } if (e->e1->type->ty == Tpointer || e->e2->type->ty == Tpointer) { e->error("pointer expression %s cannot be interpreted at compile time", e->toChars()); result = CTFEExp::cantexp; return; } UnionExp ue1; Expression *e1; if (!evalOperand(&ue1, e, e->e1, e1)) return; UnionExp ue2; Expression *e2; if (!evalOperand(&ue2, e, e->e2, e2)) return; if (e->op == TOKshr || e->op == TOKshl || e->op == TOKushr) { const sinteger_t i2 = e2->toInteger(); const d_uns64 sz = e1->type->size() * 8; if (i2 < 0 || (d_uns64)i2 >= sz) { e->error("shift by %lld is outside the range 0..%llu", i2, (ulonglong)sz - 1); result = CTFEExp::cantexp; return; } } *pue = (*fp)(e->loc, e->type, e1, e2); result = (*pue).exp(); if (CTFEExp::isCantExp(result)) e->error("%s cannot be interpreted at compile time", e->toChars()); } void interpretCompareCommon(BinExp *e, fp2_t fp) { UnionExp ue1; UnionExp ue2; if (e->e1->type->ty == Tpointer && e->e2->type->ty == Tpointer) { Expression *e1 = interpret(&ue1, e->e1, istate); if (exceptionOrCant(e1)) return; Expression *e2 = interpret(&ue2, e->e2, istate); if (exceptionOrCant(e2)) return; //printf("e1 = %s %s, e2 = %s %s\n", e1->type->toChars(), e1->toChars(), e2->type->toChars(), e2->toChars()); dinteger_t ofs1, ofs2; Expression *agg1 = getAggregateFromPointer(e1, &ofs1); Expression *agg2 = getAggregateFromPointer(e2, &ofs2); //printf("agg1 = %p %s, agg2 = %p %s\n", agg1, agg1->toChars(), agg2, agg2->toChars()); const int cmp = comparePointers(e->op, agg1, ofs1, agg2, ofs2); if (cmp == -1) { char dir = (e->op == TOKgt || e->op == TOKge) ? '<' : '>'; e->error("the ordering of pointers to unrelated memory blocks is indeterminate in CTFE." " To check if they point to the same memory block, use both > and < inside && or ||, " "eg (%s && %s %c= %s + 1)", e->toChars(), e->e1->toChars(), dir, e->e2->toChars()); result = CTFEExp::cantexp; return; } new(pue) IntegerExp(e->loc, cmp, e->type); result = (*pue).exp(); return; } Expression *e1 = interpret(&ue1, e->e1, istate); if (exceptionOrCant(e1)) return; if (!isCtfeComparable(e1)) { e->error("cannot compare %s at compile time", e1->toChars()); result = CTFEExp::cantexp; return; } Expression *e2 = interpret(&ue2, e->e2, istate); if (exceptionOrCant(e2)) return; if (!isCtfeComparable(e2)) { e->error("cannot compare %s at compile time", e2->toChars()); result = CTFEExp::cantexp; return; } const int cmp = (*fp)(e->loc, e->op, e1, e2); new(pue) IntegerExp(e->loc, cmp, e->type); result = (*pue).exp(); } void visit(BinExp *e) { switch (e->op) { case TOKadd: interpretCommon(e, &Add); return; case TOKmin: interpretCommon(e, &Min); return; case TOKmul: interpretCommon(e, &Mul); return; case TOKdiv: interpretCommon(e, &Div); return; case TOKmod: interpretCommon(e, &Mod); return; case TOKshl: interpretCommon(e, &Shl); return; case TOKshr: interpretCommon(e, &Shr); return; case TOKushr: interpretCommon(e, &Ushr); return; case TOKand: interpretCommon(e, &And); return; case TOKor: interpretCommon(e, &Or); return; case TOKxor: interpretCommon(e, &Xor); return; case TOKpow: interpretCommon(e, &Pow); return; case TOKequal: case TOKnotequal: interpretCompareCommon(e, &ctfeEqual); return; case TOKidentity: case TOKnotidentity: interpretCompareCommon(e, &ctfeIdentity); return; case TOKlt: case TOKle: case TOKgt: case TOKge: interpretCompareCommon(e, &ctfeCmp); return; default: printf("be = '%s' %s at [%s]\n", Token::toChars(e->op), e->toChars(), e->loc.toChars()); assert(0); return; } } /* Helper functions for BinExp::interpretAssignCommon */ // Returns the variable which is eventually modified, or NULL if an rvalue. // thisval is the current value of 'this'. static VarDeclaration *findParentVar(Expression *e) { for (;;) { if (e->op == TOKvar) break; if (e->op == TOKindex) e = ((IndexExp *)e)->e1; else if (e->op == TOKdotvar) e = ((DotVarExp *)e)->e1; else if (e->op == TOKdotti) e = ((DotTemplateInstanceExp *)e)->e1; else if (e->op == TOKslice) e = ((SliceExp *)e)->e1; else return NULL; } VarDeclaration *v = ((VarExp *)e)->var->isVarDeclaration(); assert(v); return v; } void interpretAssignCommon(BinExp *e, fp_t fp, int post = 0) { result = CTFEExp::cantexp; Expression *e1 = e->e1; if (!istate) { e->error("value of %s is not known at compile time", e1->toChars()); return; } ++CtfeStatus::numAssignments; /* Before we begin, we need to know if this is a reference assignment * (dynamic array, AA, or class) or a value assignment. * Determining this for slice assignments are tricky: we need to know * if it is a block assignment (a[] = e) rather than a direct slice * assignment (a[] = b[]). Note that initializers of multi-dimensional * static arrays can have 2D block assignments (eg, int[7][7] x = 6;). * So we need to recurse to determine if it is a block assignment. */ bool isBlockAssignment = false; if (e1->op == TOKslice) { // a[] = e can have const e. So we compare the naked types. Type *tdst = e1->type->toBasetype(); Type *tsrc = e->e2->type->toBasetype(); while (tdst->ty == Tsarray || tdst->ty == Tarray) { tdst = ((TypeArray *)tdst)->next->toBasetype(); if (tsrc->equivalent(tdst)) { isBlockAssignment = true; break; } } } // --------------------------------------- // Deal with reference assignment // --------------------------------------- // If it is a construction of a ref variable, it is a ref assignment if ((e->op == TOKconstruct || e->op == TOKblit) && (((AssignExp *)e)->memset & referenceInit)) { assert(!fp); Expression *newval = interpret(e->e2, istate, ctfeNeedLvalue); if (exceptionOrCant(newval)) return; VarDeclaration *v = ((VarExp *)e1)->var->isVarDeclaration(); setValue(v, newval); // Get the value to return. Note that 'newval' is an Lvalue, // so if we need an Rvalue, we have to interpret again. if (goal == ctfeNeedRvalue) result = interpret(newval, istate); else result = e1; // VarExp is a CTFE reference return; } if (fp) { while (e1->op == TOKcast) { CastExp *ce = (CastExp *)e1; e1 = ce->e1; } } // --------------------------------------- // Interpret left hand side // --------------------------------------- AssocArrayLiteralExp *existingAA = NULL; Expression *lastIndex = NULL; Expression *oldval = NULL; if (e1->op == TOKindex && ((IndexExp *)e1)->e1->type->toBasetype()->ty == Taarray) { // --------------------------------------- // Deal with AA index assignment // --------------------------------------- /* This needs special treatment if the AA doesn't exist yet. * There are two special cases: * (1) If the AA is itself an index of another AA, we may need to create * multiple nested AA literals before we can insert the new value. * (2) If the ultimate AA is null, no insertion happens at all. Instead, * we create nested AA literals, and change it into a assignment. */ IndexExp *ie = (IndexExp *)e1; int depth = 0; // how many nested AA indices are there? while (ie->e1->op == TOKindex && ((IndexExp *)ie->e1)->e1->type->toBasetype()->ty == Taarray) { assert(ie->modifiable); ie = (IndexExp *)ie->e1; ++depth; } // Get the AA value to be modified. Expression *aggregate = interpret(ie->e1, istate); if (exceptionOrCant(aggregate)) return; if (aggregate->op == TOKassocarrayliteral) { existingAA = (AssocArrayLiteralExp *)aggregate; // Normal case, ultimate parent AA already exists // We need to walk from the deepest index up, checking that an AA literal // already exists on each level. lastIndex = interpret(((IndexExp *)e1)->e2, istate); lastIndex = resolveSlice(lastIndex); // only happens with AA assignment if (exceptionOrCant(lastIndex)) return; while (depth > 0) { // Walk the syntax tree to find the indexExp at this depth IndexExp *xe = (IndexExp *)e1; for (int d= 0; d < depth; ++d) xe = (IndexExp *)xe->e1; Expression *ekey = interpret(xe->e2, istate); if (exceptionOrCant(ekey)) return; UnionExp ekeyTmp; ekey = resolveSlice(ekey, &ekeyTmp); // only happens with AA assignment // Look up this index in it up in the existing AA, to get the next level of AA. AssocArrayLiteralExp *newAA = (AssocArrayLiteralExp *)findKeyInAA(e->loc, existingAA, ekey); if (exceptionOrCant(newAA)) return; if (!newAA) { // Doesn't exist yet, create an empty AA... Expressions *keysx = new Expressions(); Expressions *valuesx = new Expressions(); newAA = new AssocArrayLiteralExp(e->loc, keysx, valuesx); newAA->type = xe->type; newAA->ownedByCtfe = OWNEDctfe; //... and insert it into the existing AA. existingAA->keys->push(ekey); existingAA->values->push(newAA); } existingAA = newAA; --depth; } if (fp) { oldval = findKeyInAA(e->loc, existingAA, lastIndex); if (!oldval) oldval = copyLiteral(e->e1->type->defaultInitLiteral(e->loc)).copy(); } } else { /* The AA is currently null. 'aggregate' is actually a reference to * whatever contains it. It could be anything: var, dotvarexp, ... * We rewrite the assignment from: * aa[i][j] op= newval; * into: * aa = [i:[j:T.init]]; * aa[j] op= newval; */ oldval = copyLiteral(e->e1->type->defaultInitLiteral(e->loc)).copy(); Expression *newaae = oldval; while (e1->op == TOKindex && ((IndexExp *)e1)->e1->type->toBasetype()->ty == Taarray) { Expression *ekey = interpret(((IndexExp *)e1)->e2, istate); if (exceptionOrCant(ekey)) return; ekey = resolveSlice(ekey); // only happens with AA assignment Expressions *keysx = new Expressions(); Expressions *valuesx = new Expressions(); keysx->push(ekey); valuesx->push(newaae); AssocArrayLiteralExp *aae = new AssocArrayLiteralExp(e->loc, keysx, valuesx); aae->type = ((IndexExp *)e1)->e1->type; aae->ownedByCtfe = OWNEDctfe; if (!existingAA) { existingAA = aae; lastIndex = ekey; } newaae = aae; e1 = ((IndexExp *)e1)->e1; } // We must set to aggregate with newaae e1 = interpret(e1, istate, ctfeNeedLvalue); if (exceptionOrCant(e1)) return; e1 = assignToLvalue(e, e1, newaae); if (exceptionOrCant(e1)) return; } assert(existingAA && lastIndex); e1 = NULL; // stomp } else if (e1->op == TOKarraylength) { oldval = interpret(e1, istate); if (exceptionOrCant(oldval)) return; } else if (e->op == TOKconstruct || e->op == TOKblit) { // Unless we have a simple var assignment, we're // only modifying part of the variable. So we need to make sure // that the parent variable exists. VarDeclaration *ultimateVar = findParentVar(e1); if (e1->op == TOKvar) { VarDeclaration *v = ((VarExp *)e1)->var->isVarDeclaration(); assert(v); if (v->storage_class & STCout) goto L1; } else if (ultimateVar && !getValue(ultimateVar)) { Expression *ex = interpret(ultimateVar->type->defaultInitLiteral(e->loc), istate); if (exceptionOrCant(ex)) return; setValue(ultimateVar, ex); } else goto L1; } else { L1: e1 = interpret(e1, istate, ctfeNeedLvalue); if (exceptionOrCant(e1)) return; if (e1->op == TOKindex && ((IndexExp *)e1)->e1->type->toBasetype()->ty == Taarray) { IndexExp *ie = (IndexExp *)e1; assert(ie->e1->op == TOKassocarrayliteral); existingAA = (AssocArrayLiteralExp *)ie->e1; lastIndex = ie->e2; } } // --------------------------------------- // Interpret right hand side // --------------------------------------- Expression *newval = interpret(e->e2, istate); if (exceptionOrCant(newval)) return; if (e->op == TOKblit && newval->op == TOKint64) { Type *tbn = e->type->baseElemOf(); if (tbn->ty == Tstruct) { /* Look for special case of struct being initialized with 0. */ newval = e->type->defaultInitLiteral(e->loc); if (newval->op == TOKerror) { result = CTFEExp::cantexp; return; } newval = interpret(newval, istate); // copy and set ownedByCtfe flag if (exceptionOrCant(newval)) return; } } // ---------------------------------------------------- // Deal with read-modify-write assignments. // Set 'newval' to the final assignment value // Also determine the return value (except for slice // assignments, which are more complicated) // ---------------------------------------------------- if (fp) { if (!oldval) { // Load the left hand side after interpreting the right hand side. oldval = interpret(e1, istate); if (exceptionOrCant(oldval)) return; } if (e->e1->type->ty != Tpointer) { // ~= can create new values (see bug 6052) if (e->op == TOKcatass) { // We need to dup it and repaint the type. For a dynamic array // we can skip duplication, because it gets copied later anyway. if (newval->type->ty != Tarray) { newval = copyLiteral(newval).copy(); newval->type = e->e2->type; // repaint type } else { newval = paintTypeOntoLiteral(e->e2->type, newval); newval = resolveSlice(newval); } } oldval = resolveSlice(oldval); newval = (*fp)(e->loc, e->type, oldval, newval).copy(); } else if (e->e2->type->isintegral() && (e->op == TOKaddass || e->op == TOKminass || e->op == TOKplusplus || e->op == TOKminusminus)) { newval = pointerArithmetic(e->loc, e->op, e->type, oldval, newval).copy(); } else { e->error("pointer expression %s cannot be interpreted at compile time", e->toChars()); result = CTFEExp::cantexp; return; } if (exceptionOrCant(newval)) { if (CTFEExp::isCantExp(newval)) e->error("cannot interpret %s at compile time", e->toChars()); return; } } if (existingAA) { if (existingAA->ownedByCtfe != OWNEDctfe) { e->error("cannot modify read-only constant %s", existingAA->toChars()); result = CTFEExp::cantexp; return; } //printf("\t+L%d existingAA = %s, lastIndex = %s, oldval = %s, newval = %s\n", // __LINE__, existingAA->toChars(), lastIndex->toChars(), oldval ? oldval->toChars() : NULL, newval->toChars()); assignAssocArrayElement(e->loc, existingAA, lastIndex, newval); // Determine the return value result = ctfeCast(pue, e->loc, e->type, e->type, fp && post ? oldval : newval); return; } if (e1->op == TOKarraylength) { /* Change the assignment from: * arr.length = n; * into: * arr = new_length_array; (result is n) */ // Determine the return value result = ctfeCast(pue, e->loc, e->type, e->type, fp && post ? oldval : newval); if (exceptionOrCant(result)) return; if (result == pue->exp()) result = pue->copy(); size_t oldlen = (size_t)oldval->toInteger(); size_t newlen = (size_t)newval->toInteger(); if (oldlen == newlen) // no change required -- we're done! return; // We have changed it into a reference assignment // Note that returnValue is still the new length. e1 = ((ArrayLengthExp *)e1)->e1; Type *t = e1->type->toBasetype(); if (t->ty != Tarray) { e->error("%s is not yet supported at compile time", e->toChars()); result = CTFEExp::cantexp; return; } e1 = interpret(e1, istate, ctfeNeedLvalue); if (exceptionOrCant(e1)) return; if (oldlen != 0) // Get the old array literal. oldval = interpret(e1, istate); newval = changeArrayLiteralLength(e->loc, (TypeArray *)t, oldval, oldlen, newlen).copy(); e1 = assignToLvalue(e, e1, newval); if (exceptionOrCant(e1)) return; return; } if (!isBlockAssignment) { newval = ctfeCast(pue, e->loc, e->type, e->type, newval); if (exceptionOrCant(newval)) return; if (newval == pue->exp()) newval = pue->copy(); // Determine the return value if (goal == ctfeNeedLvalue) // Bugzilla 14371 result = e1; else { result = ctfeCast(pue, e->loc, e->type, e->type, fp && post ? oldval : newval); if (result == pue->exp()) result = pue->copy(); } if (exceptionOrCant(result)) return; } if (exceptionOrCant(newval)) return; /* Block assignment or element-wise assignment. */ if (e1->op == TOKslice || e1->op == TOKvector || e1->op == TOKarrayliteral || e1->op == TOKstring || (e1->op == TOKnull && e1->type->toBasetype()->ty == Tarray)) { // Note that slice assignments don't support things like ++, so // we don't need to remember 'returnValue'. result = interpretAssignToSlice(pue, e, e1, newval, isBlockAssignment); if (exceptionOrCant(result)) return; if (e->e1->op == TOKslice) { Expression *e1x = interpret(((SliceExp*)e->e1)->e1, istate, ctfeNeedLvalue); if (e1x->op == TOKdotvar) { DotVarExp *dve = (DotVarExp*)e1x; Expression *ex = dve->e1; StructLiteralExp *sle = ex->op == TOKstructliteral ? ((StructLiteralExp *)ex) : ex->op == TOKclassreference ? ((ClassReferenceExp *)ex)->value : NULL; VarDeclaration *v = dve->var->isVarDeclaration(); if (!sle || !v) { e->error("CTFE internal error: dotvar slice assignment"); result = CTFEExp::cantexp; return; } stompOverlappedFields(sle, v); } } return; } assert(result); /* Assignment to a CTFE reference. */ if (Expression *ex = assignToLvalue(e, e1, newval)) result = ex; return; } /* Set all sibling fields which overlap with v to VoidExp. */ void stompOverlappedFields(StructLiteralExp *sle, VarDeclaration *v) { if (!v->overlapped) return; for (size_t i = 0; i < sle->sd->fields.dim; i++) { VarDeclaration *v2 = sle->sd->fields[i]; if (v == v2 || !v->isOverlappedWith(v2)) continue; Expression *e = (*sle->elements)[i]; if (e->op != TOKvoid) (*sle->elements)[i] = voidInitLiteral(e->type, v).copy(); } } Expression *assignToLvalue(BinExp *e, Expression *e1, Expression *newval) { VarDeclaration *vd = NULL; Expression **payload = NULL; // dead-store to prevent spurious warning Expression *oldval; if (e1->op == TOKvar) { vd = ((VarExp *)e1)->var->isVarDeclaration(); oldval = getValue(vd); } else if (e1->op == TOKdotvar) { /* Assignment to member variable of the form: * e.v = newval */ Expression *ex = ((DotVarExp *)e1)->e1; StructLiteralExp *sle = ex->op == TOKstructliteral ? ((StructLiteralExp *)ex): ex->op == TOKclassreference ? ((ClassReferenceExp *)ex)->value : NULL; VarDeclaration *v = ((DotVarExp *)e1)->var->isVarDeclaration(); if (!sle || !v) { e->error("CTFE internal error: dotvar assignment"); return CTFEExp::cantexp; } if (sle->ownedByCtfe != OWNEDctfe) { e->error("cannot modify read-only constant %s", sle->toChars()); return CTFEExp::cantexp; } int fieldi = ex->op == TOKstructliteral ? findFieldIndexByName(sle->sd, v) : ((ClassReferenceExp *)ex)->findFieldIndexByName(v); if (fieldi == -1) { e->error("CTFE internal error: cannot find field %s in %s", v->toChars(), ex->toChars()); return CTFEExp::cantexp; } assert(0 <= fieldi && fieldi < (int)sle->elements->dim); // If it's a union, set all other members of this union to void stompOverlappedFields(sle, v); payload = &(*sle->elements)[fieldi]; oldval = *payload; } else if (e1->op == TOKindex) { IndexExp *ie = (IndexExp *)e1; assert(ie->e1->type->toBasetype()->ty != Taarray); Expression *aggregate; uinteger_t indexToModify; if (!resolveIndexing(ie, istate, &aggregate, &indexToModify, true)) { return CTFEExp::cantexp; } size_t index = (size_t)indexToModify; if (aggregate->op == TOKstring) { StringExp *existingSE = (StringExp *)aggregate; if (existingSE->ownedByCtfe != OWNEDctfe) { e->error("cannot modify read-only string literal %s", ie->e1->toChars()); return CTFEExp::cantexp; } void *s = existingSE->string; dinteger_t value = newval->toInteger(); switch (existingSE->sz) { case 1: (( utf8_t *)s)[index] = ( utf8_t)value; break; case 2: ((utf16_t *)s)[index] = (utf16_t)value; break; case 4: ((utf32_t *)s)[index] = (utf32_t)value; break; default: assert(0); break; } return NULL; } if (aggregate->op != TOKarrayliteral) { e->error("index assignment %s is not yet supported in CTFE ", e->toChars()); return CTFEExp::cantexp; } ArrayLiteralExp *existingAE = (ArrayLiteralExp *)aggregate; if (existingAE->ownedByCtfe != OWNEDctfe) { e->error("cannot modify read-only constant %s", existingAE->toChars()); return CTFEExp::cantexp; } payload = &(*existingAE->elements)[index]; oldval = *payload; } else { e->error("%s cannot be evaluated at compile time", e->toChars()); return CTFEExp::cantexp; } Type *t1b = e1->type->toBasetype(); bool wantCopy = t1b->baseElemOf()->ty == Tstruct; if (newval->op == TOKstructliteral && oldval) { newval = copyLiteral(newval).copy(); assignInPlace(oldval, newval); } else if (wantCopy && e->op == TOKassign) { // Currently postblit/destructor calls on static array are done // in the druntime internal functions so they don't appear in AST. // Therefore interpreter should handle them specially. assert(oldval); #if 1 // todo: instead we can directly access to each elements of the slice newval = resolveSlice(newval); if (CTFEExp::isCantExp(newval)) { e->error("CTFE internal error: assignment %s", e->toChars()); return CTFEExp::cantexp; } #endif assert(oldval->op == TOKarrayliteral); assert(newval->op == TOKarrayliteral); Expressions *oldelems = ((ArrayLiteralExp *)oldval)->elements; Expressions *newelems = ((ArrayLiteralExp *)newval)->elements; assert(oldelems->dim == newelems->dim); Type *elemtype = oldval->type->nextOf(); for (size_t i = 0; i < newelems->dim; i++) { Expression *oldelem = (*oldelems)[i]; Expression *newelem = paintTypeOntoLiteral(elemtype, (*newelems)[i]); // Bugzilla 9245 if (e->e2->isLvalue()) { if (Expression *ex = evaluatePostblit(istate, newelem)) return ex; } // Bugzilla 13661 if (Expression *ex = evaluateDtor(istate, oldelem)) return ex; (*oldelems)[i] = newelem; } } else { // e1 has its own payload, so we have to create a new literal. if (wantCopy) newval = copyLiteral(newval).copy(); if (t1b->ty == Tsarray && e->op == TOKconstruct && e->e2->isLvalue()) { // Bugzilla 9245 if (Expression *ex = evaluatePostblit(istate, newval)) return ex; } oldval = newval; } if (vd) setValue(vd, oldval); else *payload = oldval; // Blit assignment should return the newly created value. if (e->op == TOKblit) return oldval; return NULL; } /************* * Deal with assignments of the form: * dest[] = newval * dest[low..upp] = newval * where newval has already been interpreted * * This could be a slice assignment or a block assignment, and * dest could be either an array literal, or a string. * * Returns TOKcantexp on failure. If there are no errors, * it returns aggregate[low..upp], except that as an optimisation, * if goal == ctfeNeedNothing, it will return NULL */ Expression *interpretAssignToSlice(UnionExp *pue, BinExp *e, Expression *e1, Expression *newval, bool isBlockAssignment) { dinteger_t lowerbound; dinteger_t upperbound; Expression *aggregate; dinteger_t firstIndex; if (e1->op == TOKslice) { // ------------------------------ // aggregate[] = newval // aggregate[low..upp] = newval // ------------------------------ SliceExp *se = (SliceExp *)e1; #if 1 // should be move in interpretAssignCommon as the evaluation of e1 Expression *oldval = interpret(se->e1, istate); // Set the $ variable uinteger_t dollar = resolveArrayLength(oldval); if (se->lengthVar) { Expression *dollarExp = new IntegerExp(e1->loc, dollar, Type::tsize_t); ctfeStack.push(se->lengthVar); setValue(se->lengthVar, dollarExp); } Expression *lwr = interpret(se->lwr, istate); if (exceptionOrCantInterpret(lwr)) { if (se->lengthVar) ctfeStack.pop(se->lengthVar); return lwr; } Expression *upr = interpret(se->upr, istate); if (exceptionOrCantInterpret(upr)) { if (se->lengthVar) ctfeStack.pop(se->lengthVar); return upr; } if (se->lengthVar) ctfeStack.pop(se->lengthVar); // $ is defined only in [L..U] unsigned dim = (unsigned)dollar; lowerbound = (int)(lwr ? lwr->toInteger() : 0); upperbound = (size_t)(upr ? upr->toInteger() : dim); if ((int)lowerbound < 0 || dim < upperbound) { e->error("array bounds [0..%d] exceeded in slice [%d..%d]", dim, lowerbound, upperbound); return CTFEExp::cantexp; } #endif aggregate = oldval; firstIndex = lowerbound; if (aggregate->op == TOKslice) { // Slice of a slice --> change the bounds SliceExp *oldse = (SliceExp *)aggregate; if (oldse->upr->toInteger() < upperbound + oldse->lwr->toInteger()) { e->error("slice [%d..%d] exceeds array bounds [0..%lld]", lowerbound, upperbound, oldse->upr->toInteger() - oldse->lwr->toInteger()); return CTFEExp::cantexp; } aggregate = oldse->e1; firstIndex = lowerbound + oldse->lwr->toInteger(); } } else { if (e1->op == TOKarrayliteral) { lowerbound = 0; upperbound = ((ArrayLiteralExp *)e1)->elements->dim; } else if (e1->op == TOKstring) { lowerbound = 0; upperbound = ((StringExp *)e1)->len; } else if (e1->op == TOKnull) { lowerbound = 0; upperbound = 0; } else assert(0); aggregate = e1; firstIndex = lowerbound; } if (upperbound == lowerbound) return newval; // For slice assignment, we check that the lengths match. if (!isBlockAssignment) { size_t srclen = (size_t)resolveArrayLength(newval); if (srclen != (upperbound - lowerbound)) { e->error("array length mismatch assigning [0..%d] to [%d..%d]", srclen, lowerbound, upperbound); return CTFEExp::cantexp; } } if (aggregate->op == TOKstring) { StringExp *existingSE = (StringExp *)aggregate; if (existingSE->ownedByCtfe != OWNEDctfe) { e->error("cannot modify read-only string literal %s", existingSE->toChars()); return CTFEExp::cantexp; } if (newval->op == TOKslice) { SliceExp *se = (SliceExp *)newval; Expression *aggr2 = se->e1; const dinteger_t srclower = se->lwr->toInteger(); const dinteger_t srcupper = se->upr->toInteger(); if (aggregate == aggr2 && lowerbound < srcupper && srclower < upperbound) { e->error("overlapping slice assignment [%d..%d] = [%llu..%llu]", lowerbound, upperbound, srclower, srcupper); return CTFEExp::cantexp; } #if 1 // todo: instead we can directly access to each elements of the slice Expression *orignewval = newval; newval = resolveSlice(newval); if (CTFEExp::isCantExp(newval)) { e->error("CTFE internal error: slice %s", orignewval->toChars()); return CTFEExp::cantexp; } #endif assert(newval->op != TOKslice); } if (newval->op == TOKstring) { sliceAssignStringFromString((StringExp *)existingSE, (StringExp *)newval, (size_t)firstIndex); return newval; } if (newval->op == TOKarrayliteral) { /* Mixed slice: it was initialized as a string literal. * Now a slice of it is being set with an array literal. */ sliceAssignStringFromArrayLiteral(existingSE, (ArrayLiteralExp *)newval, (size_t)firstIndex); return newval; } // String literal block slice assign dinteger_t value = newval->toInteger(); void *s = existingSE->string; for (size_t i = 0; i < upperbound - lowerbound; i++) { switch (existingSE->sz) { case 1: (( utf8_t *)s)[(size_t)(i + firstIndex)] = ( utf8_t)value; break; case 2: ((utf16_t *)s)[(size_t)(i + firstIndex)] = (utf16_t)value; break; case 4: ((utf32_t *)s)[(size_t)(i + firstIndex)] = (utf32_t)value; break; default: assert(0); break; } } if (goal == ctfeNeedNothing) return NULL; // avoid creating an unused literal SliceExp *retslice = new SliceExp(e->loc, existingSE, new IntegerExp(e->loc, firstIndex, Type::tsize_t), new IntegerExp(e->loc, firstIndex + upperbound - lowerbound, Type::tsize_t)); retslice->type = e->type; return interpret(pue, retslice, istate); } if (aggregate->op == TOKarrayliteral) { ArrayLiteralExp *existingAE = (ArrayLiteralExp *)aggregate; if (existingAE->ownedByCtfe != OWNEDctfe) { e->error("cannot modify read-only constant %s", existingAE->toChars()); return CTFEExp::cantexp; } if (newval->op == TOKslice && !isBlockAssignment) { SliceExp *se = (SliceExp *)newval; Expression *aggr2 = se->e1; const dinteger_t srclower = se->lwr->toInteger(); const dinteger_t srcupper = se->upr->toInteger(); const bool wantCopy = (newval->type->toBasetype()->nextOf()->baseElemOf()->ty == Tstruct); //printf("oldval = %p %s[%d..%u]\nnewval = %p %s[%llu..%llu] wantCopy = %d\n", // aggregate, aggregate->toChars(), lowerbound, upperbound, // aggr2, aggr2->toChars(), srclower, srcupper, wantCopy); if (wantCopy) { // Currently overlapping for struct array is allowed. // The order of elements processing depends on the overlapping. // See bugzilla 14024. assert(aggr2->op == TOKarrayliteral); Expressions *oldelems = existingAE->elements; Expressions *newelems = ((ArrayLiteralExp *)aggr2)->elements; Type *elemtype = aggregate->type->nextOf(); bool needsPostblit = e->e2->isLvalue(); if (aggregate == aggr2 && srclower < lowerbound && lowerbound < srcupper) { // reverse order for (size_t i = upperbound - lowerbound; 0 < i--; ) { Expression *oldelem = (*oldelems)[(size_t)(i + firstIndex)]; Expression *newelem = (*newelems)[(size_t)(i + srclower)]; newelem = copyLiteral(newelem).copy(); newelem->type = elemtype; if (needsPostblit) { if (Expression *x = evaluatePostblit(istate, newelem)) return x; } if (Expression *x = evaluateDtor(istate, oldelem)) return x; (*oldelems)[lowerbound + i] = newelem; } } else { // normal order for (size_t i = 0; i < upperbound - lowerbound; i++) { Expression *oldelem = (*oldelems)[(size_t)(i + firstIndex)]; Expression *newelem = (*newelems)[(size_t)(i + srclower)]; newelem = copyLiteral(newelem).copy(); newelem->type = elemtype; if (needsPostblit) { if (Expression *x = evaluatePostblit(istate, newelem)) return x; } if (Expression *x = evaluateDtor(istate, oldelem)) return x; (*oldelems)[lowerbound + i] = newelem; } } //assert(0); return newval; // oldval? } if (aggregate == aggr2 && lowerbound < srcupper && srclower < upperbound) { e->error("overlapping slice assignment [%d..%d] = [%llu..%llu]", lowerbound, upperbound, srclower, srcupper); return CTFEExp::cantexp; } #if 1 // todo: instead we can directly access to each elements of the slice Expression *orignewval = newval; newval = resolveSlice(newval); if (CTFEExp::isCantExp(newval)) { e->error("CTFE internal error: slice %s", orignewval->toChars()); return CTFEExp::cantexp; } #endif // no overlapping //length? assert(newval->op != TOKslice); } if (newval->op == TOKstring && !isBlockAssignment) { /* Mixed slice: it was initialized as an array literal of chars/integers. * Now a slice of it is being set with a string. */ sliceAssignArrayLiteralFromString(existingAE, (StringExp *)newval, (size_t)firstIndex); return newval; } if (newval->op == TOKarrayliteral && !isBlockAssignment) { Expressions *oldelems = existingAE->elements; Expressions *newelems = ((ArrayLiteralExp *)newval)->elements; Type *elemtype = existingAE->type->nextOf(); bool needsPostblit = e->op != TOKblit && e->e2->isLvalue(); for (size_t j = 0; j < newelems->dim; j++) { Expression *newelem = (*newelems)[j]; newelem = paintTypeOntoLiteral(elemtype, newelem); if (needsPostblit) { Expression *x = evaluatePostblit(istate, newelem); if (exceptionOrCantInterpret(x)) return x; } (*oldelems)[(size_t)(j + firstIndex)] = newelem; } return newval; } /* Block assignment, initialization of static arrays * x[] = newval * x may be a multidimensional static array. (Note that this * only happens with array literals, never with strings). */ struct RecursiveBlock { InterState *istate; Expression *newval; bool refCopy; bool needsPostblit; bool needsDtor; Expression *assignTo(ArrayLiteralExp *ae) { return assignTo(ae, 0, ae->elements->dim); } Expression *assignTo(ArrayLiteralExp *ae, size_t lwr, size_t upr) { Expressions *w = ae->elements; assert(ae->type->ty == Tsarray || ae->type->ty == Tarray); bool directblk = ((TypeArray *)ae->type)->next->equivalent(newval->type); for (size_t k = lwr; k < upr; k++) { if (!directblk && (*w)[k]->op == TOKarrayliteral) { // Multidimensional array block assign if (Expression *ex = assignTo((ArrayLiteralExp *)(*w)[k])) return ex; } else if (refCopy) { (*w)[k] = newval; } else if (!needsPostblit && !needsDtor) { assignInPlace((*w)[k], newval); } else { Expression *oldelem = (*w)[k]; Expression *tmpelem = needsDtor ? copyLiteral(oldelem).copy() : NULL; assignInPlace(oldelem, newval); if (needsPostblit) { if (Expression *ex = evaluatePostblit(istate, oldelem)) return ex; } if (needsDtor) { // Bugzilla 14860 if (Expression *ex = evaluateDtor(istate, tmpelem)) return ex; } } } return NULL; } }; Type *tn = newval->type->toBasetype(); bool wantRef = (tn->ty == Tarray || isAssocArray(tn) ||tn->ty == Tclass); bool cow = newval->op != TOKstructliteral && newval->op != TOKarrayliteral && newval->op != TOKstring; Type *tb = tn->baseElemOf(); StructDeclaration *sd = (tb->ty == Tstruct ? ((TypeStruct *)tb)->sym : NULL); RecursiveBlock rb; rb.istate = istate; rb.newval = newval; rb.refCopy = wantRef || cow; rb.needsPostblit = sd && sd->postblit && e->op != TOKblit && e->e2->isLvalue(); rb.needsDtor = sd && sd->dtor && e->op == TOKassign; if (Expression *ex = rb.assignTo(existingAE, lowerbound, upperbound)) return ex; if (goal == ctfeNeedNothing) return NULL; // avoid creating an unused literal SliceExp *retslice = new SliceExp(e->loc, existingAE, new IntegerExp(e->loc, firstIndex, Type::tsize_t), new IntegerExp(e->loc, firstIndex + upperbound - lowerbound, Type::tsize_t)); retslice->type = e->type; return interpret(pue, retslice, istate); } e->error("slice operation %s = %s cannot be evaluated at compile time", e1->toChars(), newval->toChars()); return CTFEExp::cantexp; } void visit(AssignExp *e) { interpretAssignCommon(e, NULL); } void visit(BinAssignExp *e) { switch (e->op) { case TOKaddass: interpretAssignCommon(e, &Add); return; case TOKminass: interpretAssignCommon(e, &Min); return; case TOKcatass: interpretAssignCommon(e, &ctfeCat); return; case TOKmulass: interpretAssignCommon(e, &Mul); return; case TOKdivass: interpretAssignCommon(e, &Div); return; case TOKmodass: interpretAssignCommon(e, &Mod); return; case TOKshlass: interpretAssignCommon(e, &Shl); return; case TOKshrass: interpretAssignCommon(e, &Shr); return; case TOKushrass: interpretAssignCommon(e, &Ushr); return; case TOKandass: interpretAssignCommon(e, &And); return; case TOKorass: interpretAssignCommon(e, &Or); return; case TOKxorass: interpretAssignCommon(e, &Xor); return; case TOKpowass: interpretAssignCommon(e, &Pow); return; default: assert(0); return; } } void visit(PostExp *e) { if (e->op == TOKplusplus) interpretAssignCommon(e, &Add, 1); else interpretAssignCommon(e, &Min, 1); } /* Return 1 if e is a p1 > p2 or p1 >= p2 pointer comparison; * -1 if e is a p1 < p2 or p1 <= p2 pointer comparison; * 0 otherwise */ static int isPointerCmpExp(Expression *e, Expression **p1, Expression **p2) { int ret = 1; while (e->op == TOKnot) { ret *= -1; e = ((NotExp *)e)->e1; } switch (e->op) { case TOKlt: case TOKle: ret *= -1; /* fall through */ case TOKgt: case TOKge: *p1 = ((BinExp *)e)->e1; *p2 = ((BinExp *)e)->e2; if (!(isPointer((*p1)->type) && isPointer((*p2)->type))) ret = 0; break; default: ret = 0; break; } return ret; } /** Negate a relational operator, eg >= becomes < */ static TOK reverseRelation(TOK op) { switch (op) { case TOKge: return TOKlt; case TOKgt: return TOKle; case TOKle: return TOKgt; case TOKlt: return TOKge; default: return assert(0), TOKreserved; } } /** If this is a four pointer relation, evaluate it, else return NULL. * * This is an expression of the form (p1 > q1 && p2 < q2) or (p1 < q1 || p2 > q2) * where p1, p2 are expressions yielding pointers to memory block p, * and q1, q2 are expressions yielding pointers to memory block q. * This expression is valid even if p and q are independent memory * blocks and are therefore not normally comparable; the && form returns true * if [p1..p2] lies inside [q1..q2], and false otherwise; the || form returns * true if [p1..p2] lies outside [q1..q2], and false otherwise. * * Within the expression, any ordering of p1, p2, q1, q2 is permissible; * the comparison operators can be any of >, <, <=, >=, provided that * both directions (p > q and p < q) are checked. Additionally the * relational sub-expressions can be negated, eg * (!(q1 < p1) && p2 <= q2) is valid. */ void interpretFourPointerRelation(UnionExp *pue, BinExp *e) { assert(e->op == TOKandand || e->op == TOKoror); /* It can only be an isInside expression, if both e1 and e2 are * directional pointer comparisons. * Note that this check can be made statically; it does not depends on * any runtime values. This allows a JIT implementation to compile a * special AndAndPossiblyInside, keeping the normal AndAnd case efficient. */ // Save the pointer expressions and the comparison directions, // so we can use them later. Expression *p1 = NULL; Expression *p2 = NULL; Expression *p3 = NULL; Expression *p4 = NULL; int dir1 = isPointerCmpExp(e->e1, &p1, &p2); int dir2 = isPointerCmpExp(e->e2, &p3, &p4); if (dir1 == 0 || dir2 == 0) { result = NULL; return; } //printf("FourPointerRelation %s\n", toChars()); UnionExp ue1; UnionExp ue2; UnionExp ue3; UnionExp ue4; // Evaluate the first two pointers p1 = interpret(&ue1, p1, istate); if (exceptionOrCant(p1)) return; p2 = interpret(&ue2, p2, istate); if (exceptionOrCant(p2)) return; dinteger_t ofs1, ofs2; Expression *agg1 = getAggregateFromPointer(p1, &ofs1); Expression *agg2 = getAggregateFromPointer(p2, &ofs2); if (!pointToSameMemoryBlock(agg1, agg2) && agg1->op != TOKnull && agg2->op != TOKnull) { // Here it is either CANT_INTERPRET, // or an IsInside comparison returning false. p3 = interpret(&ue3, p3, istate); if (CTFEExp::isCantExp(p3)) return; // Note that it is NOT legal for it to throw an exception! Expression *except = NULL; if (exceptionOrCantInterpret(p3)) except = p3; else { p4 = interpret(&ue4, p4, istate); if (CTFEExp::isCantExp(p4)) { result = p4; return; } if (exceptionOrCantInterpret(p4)) except = p4; } if (except) { e->error("comparison %s of pointers to unrelated memory blocks remains " "indeterminate at compile time " "because exception %s was thrown while evaluating %s", e->e1->toChars(), except->toChars(), e->e2->toChars()); result = CTFEExp::cantexp; return; } dinteger_t ofs3, ofs4; Expression *agg3 = getAggregateFromPointer(p3, &ofs3); Expression *agg4 = getAggregateFromPointer(p4, &ofs4); // The valid cases are: // p1 > p2 && p3 > p4 (same direction, also for < && <) // p1 > p2 && p3 < p4 (different direction, also < && >) // Changing any > into >= doesnt affect the result if ((dir1 == dir2 && pointToSameMemoryBlock(agg1, agg4) && pointToSameMemoryBlock(agg2, agg3)) || (dir1 != dir2 && pointToSameMemoryBlock(agg1, agg3) && pointToSameMemoryBlock(agg2, agg4))) { // it's a legal two-sided comparison new(pue) IntegerExp(e->loc, (e->op == TOKandand) ? 0 : 1, e->type); result = pue->exp(); return; } // It's an invalid four-pointer comparison. Either the second // comparison is in the same direction as the first, or else // more than two memory blocks are involved (either two independent // invalid comparisons are present, or else agg3 == agg4). e->error("comparison %s of pointers to unrelated memory blocks is " "indeterminate at compile time, even when combined with %s.", e->e1->toChars(), e->e2->toChars()); result = CTFEExp::cantexp; return; } // The first pointer expression didn't need special treatment, so we // we need to interpret the entire expression exactly as a normal && or ||. // This is easy because we haven't evaluated e2 at all yet, and we already // know it will return a bool. // But we mustn't evaluate the pointer expressions in e1 again, in case // they have side-effects. bool nott = false; Expression *ex = e->e1; while (ex->op == TOKnot) { nott = !nott; ex = ((NotExp *)ex)->e1; } const TOK cmpop = nott ? reverseRelation(ex->op) : ex->op; const int cmp = comparePointers(cmpop, agg1, ofs1, agg2, ofs2); // We already know this is a valid comparison. assert(cmp >= 0); if ((e->op == TOKandand && cmp == 1) || (e->op == TOKoror && cmp == 0)) { result = interpret(pue, e->e2, istate); return; } new(pue) IntegerExp(e->loc, (e->op == TOKandand) ? 0 : 1, e->type); result = pue->exp(); } void visit(AndAndExp *e) { // Check for an insidePointer expression, evaluate it if so interpretFourPointerRelation(pue, e); if (result) return; result = interpret(e->e1, istate); if (exceptionOrCant(result)) return; int res; if (result->isBool(false)) res = 0; else if (isTrueBool(result)) { UnionExp ue2; result = interpret(&ue2, e->e2, istate); if (exceptionOrCant(result)) return; if (result->op == TOKvoidexp) { assert(e->type->ty == Tvoid); result = NULL; return; } if (result->isBool(false)) res = 0; else if (isTrueBool(result)) res = 1; else { result->error("%s does not evaluate to a boolean", result->toChars()); result = CTFEExp::cantexp; return; } } else { result->error("%s cannot be interpreted as a boolean", result->toChars()); result = CTFEExp::cantexp; return; } if (goal != ctfeNeedNothing) { new(pue) IntegerExp(e->loc, res, e->type); result = pue->exp(); } } void visit(OrOrExp *e) { // Check for an insidePointer expression, evaluate it if so interpretFourPointerRelation(pue, e); if (result) return; result = interpret(e->e1, istate); if (exceptionOrCant(result)) return; int res; if (isTrueBool(result)) res = 1; else if (result->isBool(false)) { UnionExp ue2; result = interpret(&ue2, e->e2, istate); if (exceptionOrCant(result)) return; if (result->op == TOKvoidexp) { assert(e->type->ty == Tvoid); result = NULL; return; } if (result->isBool(false)) res = 0; else if (isTrueBool(result)) res = 1; else { result->error("%s cannot be interpreted as a boolean", result->toChars()); result = CTFEExp::cantexp; return; } } else { result->error("%s cannot be interpreted as a boolean", result->toChars()); result = CTFEExp::cantexp; return; } if (goal != ctfeNeedNothing) { new(pue) IntegerExp(e->loc, res, e->type); result = pue->exp(); } } // Print a stack trace, starting from callingExp which called fd. // To shorten the stack trace, try to detect recursion. void showCtfeBackTrace(CallExp * callingExp, FuncDeclaration *fd) { if (CtfeStatus::stackTraceCallsToSuppress > 0) { --CtfeStatus::stackTraceCallsToSuppress; return; } errorSupplemental(callingExp->loc, "called from here: %s", callingExp->toChars()); // Quit if it's not worth trying to compress the stack trace if (CtfeStatus::callDepth < 6 || global.params.verbose) return; // Recursion happens if the current function already exists in the call stack. int numToSuppress = 0; int recurseCount = 0; int depthSoFar = 0; InterState *lastRecurse = istate; for (InterState * cur = istate; cur; cur = cur->caller) { if (cur->fd == fd) { ++recurseCount; numToSuppress = depthSoFar; lastRecurse = cur; } ++depthSoFar; } // We need at least three calls to the same function, to make compression worthwhile if (recurseCount < 2) return; // We found a useful recursion. Print all the calls involved in the recursion errorSupplemental(fd->loc, "%d recursive calls to function %s", recurseCount, fd->toChars()); for (InterState *cur = istate; cur->fd != fd; cur = cur->caller) { errorSupplemental(cur->fd->loc, "recursively called from function %s", cur->fd->toChars()); } // We probably didn't enter the recursion in this function. // Go deeper to find the real beginning. InterState * cur = istate; while (lastRecurse->caller && cur->fd == lastRecurse->caller->fd) { cur = cur->caller; lastRecurse = lastRecurse->caller; ++numToSuppress; } CtfeStatus::stackTraceCallsToSuppress = numToSuppress; } void visit(CallExp *e) { Expression *pthis = NULL; FuncDeclaration *fd = NULL; Expression *ecall = interpret(e->e1, istate); if (exceptionOrCant(ecall)) return; if (ecall->op == TOKdotvar) { DotVarExp *dve = (DotVarExp *)ecall; // Calling a member function pthis = dve->e1; fd = dve->var->isFuncDeclaration(); assert(fd); if (pthis->op == TOKdottype) pthis = ((DotTypeExp *)dve->e1)->e1; } else if (ecall->op == TOKvar) { fd = ((VarExp *)ecall)->var->isFuncDeclaration(); assert(fd); if (fd->ident == Id::__ArrayPostblit || fd->ident == Id::__ArrayDtor) { assert(e->arguments->dim == 1); Expression *ea = (*e->arguments)[0]; //printf("1 ea = %s %s\n", ea->type->toChars(), ea->toChars()); if (ea->op == TOKslice) ea = ((SliceExp *)ea)->e1; if (ea->op == TOKcast) ea = ((CastExp *)ea)->e1; //printf("2 ea = %s, %s %s\n", ea->type->toChars(), Token::toChars(ea->op), ea->toChars()); if (ea->op == TOKvar || ea->op == TOKsymoff) result = getVarExp(e->loc, istate, ((SymbolExp *)ea)->var, ctfeNeedRvalue); else if (ea->op == TOKaddress) result = interpret(((AddrExp *)ea)->e1, istate); // https://issues.dlang.org/show_bug.cgi?id=18871 // https://issues.dlang.org/show_bug.cgi?id=18819 else if (ea->op == TOKarrayliteral) result = interpret((ArrayLiteralExp *)ea, istate); else assert(0); if (CTFEExp::isCantExp(result)) return; if (fd->ident == Id::__ArrayPostblit) result = evaluatePostblit(istate, result); else result = evaluateDtor(istate, result); if (!result) result = CTFEExp::voidexp; return; } } else if (ecall->op == TOKsymoff) { SymOffExp *soe = (SymOffExp *)ecall; fd = soe->var->isFuncDeclaration(); assert(fd && soe->offset == 0); } else if (ecall->op == TOKdelegate) { // Calling a delegate fd = ((DelegateExp *)ecall)->func; pthis = ((DelegateExp *)ecall)->e1; // Special handling for: &nestedfunc --> DelegateExp(VarExp(nestedfunc), nestedfunc) if (pthis->op == TOKvar && ((VarExp *)pthis)->var == fd) pthis = NULL; // context is not necessary for CTFE } else if (ecall->op == TOKfunction) { // Calling a delegate literal fd = ((FuncExp *)ecall)->fd; } else { // delegate.funcptr() // others e->error("cannot call %s at compile time", e->toChars()); result = CTFEExp::cantexp; return; } if (!fd) { e->error("CTFE internal error: cannot evaluate %s at compile time", e->toChars()); result = CTFEExp::cantexp; return; } if (pthis) { // Member function call // Currently this is satisfied because closure is not yet supported. assert(!fd->isNested()); if (pthis->op == TOKtypeid) { pthis->error("static variable %s cannot be read at compile time", pthis->toChars()); result = CTFEExp::cantexp; return; } assert(pthis); if (pthis->op == TOKnull) { assert(pthis->type->toBasetype()->ty == Tclass); e->error("function call through null class reference %s", pthis->toChars()); result = CTFEExp::cantexp; return; } assert(pthis->op == TOKstructliteral || pthis->op == TOKclassreference); if (fd->isVirtual() && !e->directcall) { // Make a virtual function call. // Get the function from the vtable of the original class assert(pthis->op == TOKclassreference); ClassDeclaration *cd = ((ClassReferenceExp *)pthis)->originalClass(); // We can't just use the vtable index to look it up, because // vtables for interfaces don't get populated until the glue layer. fd = cd->findFunc(fd->ident, (TypeFunction *)fd->type); assert(fd); } } if (fd && fd->semanticRun >= PASSsemantic3done && fd->semantic3Errors) { e->error("CTFE failed because of previous errors in %s", fd->toChars()); result = CTFEExp::cantexp; return; } // Check for built-in functions result = evaluateIfBuiltin(pue, istate, e->loc, fd, e->arguments, pthis); if (result) return; if (!fd->fbody) { e->error("%s cannot be interpreted at compile time," " because it has no available source code", fd->toChars()); result = CTFEExp::cantexp; return; } result = interpretFunction(pue, fd, istate, e->arguments, pthis); if (result->op == TOKvoidexp) return; if (!exceptionOrCantInterpret(result)) { if (goal != ctfeNeedLvalue) // Peel off CTFE reference if it's unnecessary { if (result == pue->exp()) result = pue->copy(); result = interpret(pue, result, istate); } } if (!exceptionOrCantInterpret(result)) { result = paintTypeOntoLiteral(e->type, result); result->loc = e->loc; } else if (CTFEExp::isCantExp(result) && !global.gag) showCtfeBackTrace(e, fd); // Print a stack trace. } void endTempStackFrame(InterState *pistateComma) { // If we created a temporary stack frame, end it now. if (istate == pistateComma) ctfeStack.endFrame(); } void visit(CommaExp *e) { CommaExp *firstComma = e; while (firstComma->e1->op == TOKcomma) firstComma = (CommaExp *)firstComma->e1; // If it creates a variable, and there's no context for // the variable to be created in, we need to create one now. InterState istateComma; if (!istate && firstComma->e1->op == TOKdeclaration) { ctfeStack.startFrame(NULL); istate = &istateComma; } result = CTFEExp::cantexp; // If the comma returns a temporary variable, it needs to be an lvalue // (this is particularly important for struct constructors) if (e->e1->op == TOKdeclaration && e->e2->op == TOKvar && ((DeclarationExp *)e->e1)->declaration == ((VarExp*)e->e2)->var && ((VarExp*)e->e2)->var->storage_class & STCctfe) // same as Expression::isTemp { VarExp *ve = (VarExp *)e->e2; VarDeclaration *v = ve->var->isVarDeclaration(); ctfeStack.push(v); if (!v->_init && !getValue(v)) { setValue(v, copyLiteral(v->type->defaultInitLiteral(e->loc)).copy()); } if (!getValue(v)) { Expression *newval = initializerToExpression(v->_init); // Bug 4027. Copy constructors are a weird case where the // initializer is a void function (the variable is modified // through a reference parameter instead). newval = interpret(newval, istate); if (exceptionOrCant(newval)) return endTempStackFrame(&istateComma); if (newval->op != TOKvoidexp) { // v isn't necessarily null. setValueWithoutChecking(v, copyLiteral(newval).copy()); } } } else { UnionExp ue; Expression *e1 = interpret(&ue, e->e1, istate, ctfeNeedNothing); if (exceptionOrCant(e1)) return endTempStackFrame(&istateComma); } result = interpret(pue, e->e2, istate, goal); return endTempStackFrame(&istateComma); } void visit(CondExp *e) { UnionExp uecond; Expression *econd; econd = interpret(&uecond, e->econd, istate); if (exceptionOrCant(econd)) return; if (isPointer(e->econd->type)) { if (econd->op != TOKnull) { new(&uecond) IntegerExp(e->loc, 1, Type::tbool); econd = uecond.exp(); } } if (isTrueBool(econd)) result = interpret(pue, e->e1, istate, goal); else if (econd->isBool(false)) result = interpret(pue, e->e2, istate, goal); else { e->error("%s does not evaluate to boolean result at compile time", e->econd->toChars()); result = CTFEExp::cantexp; } } void visit(ArrayLengthExp *e) { UnionExp ue1; Expression *e1 = interpret(&ue1, e->e1, istate); assert(e1); if (exceptionOrCant(e1)) return; if (e1->op != TOKstring && e1->op != TOKarrayliteral && e1->op != TOKslice && e1->op != TOKnull) { e->error("%s cannot be evaluated at compile time", e->toChars()); result = CTFEExp::cantexp; return; } new(pue) IntegerExp(e->loc, resolveArrayLength(e1), e->type); result = pue->exp(); } /** * Interpret the vector expression as an array literal. * Params: * pue = non-null pointer to temporary storage that can be used to store the return value * e = Expression to interpret * Returns: * resulting array literal or 'e' if unable to interpret */ static Expression *interpretVectorToArray(UnionExp *pue, VectorExp *e) { if (e->e1->op == TOKarrayliteral) return (ArrayLiteralExp *)e->e1; if (e->e1->op == TOKint64 || e->e1->op == TOKfloat64) { // Convert literal __vector(int) -> __vector([array]) Expressions *elements = new Expressions(); elements->setDim(e->dim); for (size_t i = 0; i < elements->dim; i++) (*elements)[i] = copyLiteral(e->e1).copy(); TypeSArray *type = NULL; if (e->type->ty == Tvector) { TypeVector *tv = (TypeVector *)e->type; if (tv->basetype->ty == Tsarray) type = (TypeSArray *)tv->basetype; } else if (e->type->ty == Tsarray) type = (TypeSArray *)e->type; assert(type); new(pue) ArrayLiteralExp(e->loc, type, elements); ArrayLiteralExp *ale = (ArrayLiteralExp *)pue->exp(); ale->ownedByCtfe = OWNEDctfe; return ale; } return e; } void visit(VectorExp *e) { if (e->ownedByCtfe >= OWNEDctfe) // We've already interpreted all the elements { result = e; return; } Expression *e1 = interpret(pue, e->e1, istate); assert(e1); if (exceptionOrCant(e1)) return; if (e1->op != TOKarrayliteral && e1->op != TOKint64 && e1->op != TOKfloat64) { e->error("`%s` cannot be evaluated at compile time", e->toChars()); result = CTFEExp::cantexp; return; } if (e1 == pue->exp()) e1 = pue->copy(); new(pue) VectorExp(e->loc, e1, e->to); VectorExp *ve = (VectorExp *)pue->exp(); ve->type = e->type; ve->dim = e->dim; ve->ownedByCtfe = OWNEDctfe; result = ve; } void visit(VectorArrayExp *e) { Expression *e1 = interpret(pue, e->e1, istate); assert(e1); if (exceptionOrCant(e1)) return; if (e1->op == TOKvector) { VectorExp *ve = (VectorExp *)e1; result = interpretVectorToArray(pue, ve); if (result->op != TOKvector) return; } e->error("`%s` cannot be evaluated at compile time", e->toChars()); result = CTFEExp::cantexp; } void visit(DelegatePtrExp *e) { Expression *e1 = interpret(pue, e->e1, istate); assert(e1); if (exceptionOrCant(e1)) return; e->error("%s cannot be evaluated at compile time", e->toChars()); result = CTFEExp::cantexp; } void visit(DelegateFuncptrExp *e) { Expression *e1 = interpret(pue, e->e1, istate); assert(e1); if (exceptionOrCant(e1)) return; e->error("%s cannot be evaluated at compile time", e->toChars()); result = CTFEExp::cantexp; } static bool resolveIndexing(IndexExp *e, InterState *istate, Expression **pagg, uinteger_t *pidx, bool modify) { assert(e->e1->type->toBasetype()->ty != Taarray); if (e->e1->type->toBasetype()->ty == Tpointer) { // Indexing a pointer. Note that there is no $ in this case. Expression *e1 = interpret(e->e1, istate); if (exceptionOrCantInterpret(e1)) return false; Expression *e2 = interpret(e->e2, istate); if (exceptionOrCantInterpret(e2)) return false; sinteger_t indx = e2->toInteger(); dinteger_t ofs; Expression *agg = getAggregateFromPointer(e1, &ofs); if (agg->op == TOKnull) { e->error("cannot index through null pointer %s", e->e1->toChars()); return false; } if (agg->op == TOKint64) { e->error("cannot index through invalid pointer %s of value %s", e->e1->toChars(), e1->toChars()); return false; } // Pointer to a non-array variable if (agg->op == TOKsymoff) { e->error("mutable variable %s cannot be %s at compile time, even through a pointer", (modify ? "modified" : "read"), ((SymOffExp *)agg)->var->toChars()); return false; } if (agg->op == TOKarrayliteral || agg->op == TOKstring) { dinteger_t len = resolveArrayLength(agg); if (ofs + indx >= len) { e->error("pointer index [%lld] exceeds allocated memory block [0..%lld]", ofs + indx, len); return false; } } else { if (ofs + indx != 0) { e->error("pointer index [%lld] lies outside memory block [0..1]", ofs + indx); return false; } } *pagg = agg; *pidx = ofs + indx; return true; } Expression *e1 = interpret(e->e1, istate); if (exceptionOrCantInterpret(e1)) return false; if (e1->op == TOKnull) { e->error("cannot index null array %s", e->e1->toChars()); return false; } if (e1->op == TOKvector) { UnionExp ue; e1 = interpretVectorToArray(&ue, (VectorExp *)e1); e1 = (e1 == ue.exp()) ? ue.copy() : e1; } // Set the $ variable, and find the array literal to modify if (e1->op != TOKarrayliteral && e1->op != TOKstring && e1->op != TOKslice && e1->op != TOKvector) { e->error("cannot determine length of %s at compile time", e->e1->toChars()); return false; } dinteger_t len = resolveArrayLength(e1); if (e->lengthVar) { Expression *dollarExp = new IntegerExp(e->loc, len, Type::tsize_t); ctfeStack.push(e->lengthVar); setValue(e->lengthVar, dollarExp); } Expression *e2 = interpret(e->e2, istate); if (e->lengthVar) ctfeStack.pop(e->lengthVar); // $ is defined only inside [] if (exceptionOrCantInterpret(e2)) return false; if (e2->op != TOKint64) { e->error("CTFE internal error: non-integral index [%s]", e->e2->toChars()); return false; } if (e1->op == TOKslice) { // Simplify index of slice: agg[lwr..upr][indx] --> agg[indx'] uinteger_t index = e2->toInteger(); uinteger_t ilwr = ((SliceExp *)e1)->lwr->toInteger(); uinteger_t iupr = ((SliceExp *)e1)->upr->toInteger(); if (index > iupr - ilwr) { e->error("index %llu exceeds array length %llu", index, iupr - ilwr); return false; } *pagg = ((SliceExp *)e1)->e1; *pidx = index + ilwr; } else { *pagg = e1; *pidx = e2->toInteger(); if (len <= *pidx) { e->error("array index %lld is out of bounds [0..%lld]", *pidx, len); return false; } } return true; } void visit(IndexExp *e) { if (e->e1->type->toBasetype()->ty == Tpointer) { Expression *agg; uinteger_t indexToAccess; if (!resolveIndexing(e, istate, &agg, &indexToAccess, false)) { result = CTFEExp::cantexp; return; } if (agg->op == TOKarrayliteral || agg->op == TOKstring) { if (goal == ctfeNeedLvalue) { // if we need a reference, IndexExp shouldn't be interpreting // the expression to a value, it should stay as a reference new(pue) IndexExp(e->loc, agg, new IntegerExp(e->e2->loc, indexToAccess, e->e2->type)); result = pue->exp(); result->type = e->type; return; } result = ctfeIndex(e->loc, e->type, agg, indexToAccess); return; } else { assert(indexToAccess == 0); result = interpret(agg, istate, goal); if (exceptionOrCant(result)) return; result = paintTypeOntoLiteral(e->type, result); return; } } if (e->e1->type->toBasetype()->ty == Taarray) { Expression *e1 = interpret(e->e1, istate); if (exceptionOrCant(e1)) return; if (e1->op == TOKnull) { if (goal == ctfeNeedLvalue && e1->type->ty == Taarray && e->modifiable) { assert(0); // does not reach here? return; } e->error("cannot index null array %s", e->e1->toChars()); result = CTFEExp::cantexp; return; } Expression *e2 = interpret(e->e2, istate); if (exceptionOrCant(e2)) return; if (goal == ctfeNeedLvalue) { // Pointer or reference of a scalar type if (e1 == e->e1 && e2 == e->e2) result = e; else { new(pue) IndexExp(e->loc, e1, e2); result = pue->exp(); result->type = e->type; } return; } assert(e1->op == TOKassocarrayliteral); UnionExp e2tmp; e2 = resolveSlice(e2, &e2tmp); result = findKeyInAA(e->loc, (AssocArrayLiteralExp *)e1, e2); if (!result) { e->error("key %s not found in associative array %s", e2->toChars(), e->e1->toChars()); result = CTFEExp::cantexp; } return; } Expression *agg; uinteger_t indexToAccess; if (!resolveIndexing(e, istate, &agg, &indexToAccess, false)) { result = CTFEExp::cantexp; return; } if (goal == ctfeNeedLvalue) { Expression *e2 = new IntegerExp(e->e2->loc, indexToAccess, Type::tsize_t); new(pue) IndexExp(e->loc, agg, e2); result = pue->exp(); result->type = e->type; return; } result = ctfeIndex(e->loc, e->type, agg, indexToAccess); if (exceptionOrCant(result)) return; if (result->op == TOKvoid) { e->error("%s is used before initialized", e->toChars()); errorSupplemental(result->loc, "originally uninitialized here"); result = CTFEExp::cantexp; return; } result = paintTypeOntoLiteral(e->type, result); } void visit(SliceExp *e) { if (e->e1->type->toBasetype()->ty == Tpointer) { // Slicing a pointer. Note that there is no $ in this case. Expression *e1 = interpret(e->e1, istate); if (exceptionOrCant(e1)) return; if (e1->op == TOKint64) { e->error("cannot slice invalid pointer %s of value %s", e->e1->toChars(), e1->toChars()); result = CTFEExp::cantexp; return; } /* Evaluate lower and upper bounds of slice */ Expression *lwr = interpret(e->lwr, istate); if (exceptionOrCant(lwr)) return; Expression *upr = interpret(e->upr, istate); if (exceptionOrCant(upr)) return; uinteger_t ilwr = lwr->toInteger(); uinteger_t iupr = upr->toInteger(); dinteger_t ofs; Expression *agg = getAggregateFromPointer(e1, &ofs); ilwr += ofs; iupr += ofs; if (agg->op == TOKnull) { if (iupr == ilwr) { result = new NullExp(e->loc); result->type = e->type; return; } e->error("cannot slice null pointer %s", e->e1->toChars()); result = CTFEExp::cantexp; return; } if (agg->op == TOKsymoff) { e->error("slicing pointers to static variables is not supported in CTFE"); result = CTFEExp::cantexp; return; } if (agg->op != TOKarrayliteral && agg->op != TOKstring) { e->error("pointer %s cannot be sliced at compile time (it does not point to an array)", e->e1->toChars()); result = CTFEExp::cantexp; return; } assert(agg->op == TOKarrayliteral || agg->op == TOKstring); dinteger_t len = ArrayLength(Type::tsize_t, agg).exp()->toInteger(); //Type *pointee = ((TypePointer *)agg->type)->next; if (iupr > (len + 1) || iupr < ilwr) { e->error("pointer slice [%lld..%lld] exceeds allocated memory block [0..%lld]", ilwr, iupr, len); result = CTFEExp::cantexp; return; } if (ofs != 0) { lwr = new IntegerExp(e->loc, ilwr, lwr->type); upr = new IntegerExp(e->loc, iupr, upr->type); } new(pue) SliceExp(e->loc, agg, lwr, upr); result = pue->exp(); result->type = e->type; return; } Expression *e1 = interpret(e->e1, istate); if (exceptionOrCant(e1)) return; if (!e->lwr) { result = paintTypeOntoLiteral(e->type, e1); return; } if (e1->op == TOKvector) { e1 = interpretVectorToArray(pue, (VectorExp *)e1); e1 = (e1 == pue->exp()) ? pue->copy() : e1; } /* Set the $ variable */ if (e1->op != TOKarrayliteral && e1->op != TOKstring && e1->op != TOKnull && e1->op != TOKslice && e1->op != TOKvector) { e->error("cannot determine length of %s at compile time", e1->toChars()); result = CTFEExp::cantexp; return; } uinteger_t dollar = resolveArrayLength(e1); if (e->lengthVar) { IntegerExp *dollarExp = new IntegerExp(e->loc, dollar, Type::tsize_t); ctfeStack.push(e->lengthVar); setValue(e->lengthVar, dollarExp); } /* Evaluate lower and upper bounds of slice */ Expression *lwr = interpret(e->lwr, istate); if (exceptionOrCant(lwr)) { if (e->lengthVar) ctfeStack.pop(e->lengthVar); return; } Expression *upr = interpret(e->upr, istate); if (exceptionOrCant(upr)) { if (e->lengthVar) ctfeStack.pop(e->lengthVar); return; } if (e->lengthVar) ctfeStack.pop(e->lengthVar); // $ is defined only inside [L..U] uinteger_t ilwr = lwr->toInteger(); uinteger_t iupr = upr->toInteger(); if (e1->op == TOKnull) { if (ilwr == 0 && iupr == 0) { result = e1; return; } e1->error("slice [%llu..%llu] is out of bounds", ilwr, iupr); result = CTFEExp::cantexp; return; } if (e1->op == TOKslice) { SliceExp *se = (SliceExp *)e1; // Simplify slice of slice: // aggregate[lo1..up1][lwr..upr] ---> aggregate[lwr'..upr'] uinteger_t lo1 = se->lwr->toInteger(); uinteger_t up1 = se->upr->toInteger(); if (ilwr > iupr || iupr > up1 - lo1) { e->error("slice[%llu..%llu] exceeds array bounds[%llu..%llu]", ilwr, iupr, lo1, up1); result = CTFEExp::cantexp; return; } ilwr += lo1; iupr += lo1; new(pue) SliceExp(e->loc, se->e1, new IntegerExp(e->loc, ilwr, lwr->type), new IntegerExp(e->loc, iupr, upr->type)); result = pue->exp(); result->type = e->type; return; } if (e1->op == TOKarrayliteral || e1->op == TOKstring) { if (iupr < ilwr || dollar < iupr) { e->error("slice [%lld..%lld] exceeds array bounds [0..%lld]", ilwr, iupr, dollar); result = CTFEExp::cantexp; return; } } new(pue) SliceExp(e->loc, e1, lwr, upr); result = pue->exp(); result->type = e->type; } void visit(InExp *e) { Expression *e1 = interpret(e->e1, istate); if (exceptionOrCant(e1)) return; Expression *e2 = interpret(e->e2, istate); if (exceptionOrCant(e2)) return; if (e2->op == TOKnull) { new(pue) NullExp(e->loc, e->type); result = pue->exp(); return; } if (e2->op != TOKassocarrayliteral) { e->error("%s cannot be interpreted at compile time", e->toChars()); result = CTFEExp::cantexp; return; } e1 = resolveSlice(e1); result = findKeyInAA(e->loc, (AssocArrayLiteralExp *)e2, e1); if (exceptionOrCant(result)) return; if (!result) { new(pue) NullExp(e->loc, e->type); result = pue->exp(); } else { // Create a CTFE pointer &aa[index] result = new IndexExp(e->loc, e2, e1); result->type = e->type->nextOf(); new(pue) AddrExp(e->loc, result, e->type); result = pue->exp(); } } void visit(CatExp *e) { UnionExp ue1; Expression *e1 = interpret(&ue1, e->e1, istate); if (exceptionOrCant(e1)) return; UnionExp ue2; Expression *e2 = interpret(&ue2, e->e2, istate); if (exceptionOrCant(e2)) return; UnionExp e1tmp; e1 = resolveSlice(e1, &e1tmp); UnionExp e2tmp; e2 = resolveSlice(e2, &e2tmp); /* e1 and e2 can't go on the stack because of x~[y] and [x]~y will * result in [x,y] and then x or y is on the stack. * But if they are both strings, we can, because it isn't the x~[y] case. */ if (!(e1->op == TOKstring && e2->op == TOKstring)) { if (e1 == ue1.exp()) e1 = ue1.copy(); if (e2 == ue2.exp()) e2 = ue2.copy(); } *pue = ctfeCat(e->loc, e->type, e1, e2); result = pue->exp(); if (CTFEExp::isCantExp(result)) { e->error("%s cannot be interpreted at compile time", e->toChars()); return; } // We know we still own it, because we interpreted both e1 and e2 if (result->op == TOKarrayliteral) { ArrayLiteralExp *ale = (ArrayLiteralExp *)result; ale->ownedByCtfe = OWNEDctfe; // Bugzilla 14686 for (size_t i = 0; i < ale->elements->dim; i++) { Expression *ex = evaluatePostblit(istate, (*ale->elements)[i]); if (exceptionOrCant(ex)) return; } } if (result->op == TOKstring) ((StringExp *)result)->ownedByCtfe = OWNEDctfe; } void visit(DeleteExp *e) { result = interpret(e->e1, istate); if (exceptionOrCant(result)) return; if (result->op == TOKnull) { result = CTFEExp::voidexp; return; } Type *tb = e->e1->type->toBasetype(); switch (tb->ty) { case Tclass: { if (result->op != TOKclassreference) { e->error("delete on invalid class reference '%s'", result->toChars()); result = CTFEExp::cantexp; return; } ClassReferenceExp *cre = (ClassReferenceExp *)result; ClassDeclaration *cd = cre->originalClass(); if (cd->aggDelete) { e->error("member deallocators not supported by CTFE"); result = CTFEExp::cantexp; return; } if (cd->dtor) { result = interpretFunction(pue, cd->dtor, istate, NULL, cre); if (exceptionOrCant(result)) return; } break; } case Tpointer: { tb = ((TypePointer *)tb)->next->toBasetype(); if (tb->ty == Tstruct) { if (result->op != TOKaddress || ((AddrExp *)result)->e1->op != TOKstructliteral) { e->error("delete on invalid struct pointer '%s'", result->toChars()); result = CTFEExp::cantexp; return; } StructDeclaration *sd = ((TypeStruct *)tb)->sym; StructLiteralExp *sle = (StructLiteralExp *)((AddrExp *)result)->e1; if (sd->aggDelete) { e->error("member deallocators not supported by CTFE"); result = CTFEExp::cantexp; return; } if (sd->dtor) { result = interpretFunction(pue, sd->dtor, istate, NULL, sle); if (exceptionOrCant(result)) return; } } break; } case Tarray: { Type *tv = tb->nextOf()->baseElemOf(); if (tv->ty == Tstruct) { if (result->op != TOKarrayliteral) { e->error("delete on invalid struct array '%s'", result->toChars()); result = CTFEExp::cantexp; return; } StructDeclaration *sd = ((TypeStruct *)tv)->sym; if (sd->aggDelete) { e->error("member deallocators not supported by CTFE"); result = CTFEExp::cantexp; return; } if (sd->dtor) { ArrayLiteralExp *ale = (ArrayLiteralExp *)result; for (size_t i = 0; i < ale->elements->dim; i++) { Expression *el = (*ale->elements)[i]; result = interpretFunction(pue, sd->dtor, istate, NULL, el); if (exceptionOrCant(result)) return; } } } break; } default: assert(0); } result = CTFEExp::voidexp; } void visit(CastExp *e) { Expression *e1 = interpret(e->e1, istate, goal); if (exceptionOrCant(e1)) return; // If the expression has been cast to void, do nothing. if (e->to->ty == Tvoid) { result = CTFEExp::voidexp; return; } if (e->to->ty == Tpointer && e1->op != TOKnull) { Type *pointee = ((TypePointer *)e->type)->next; // Implement special cases of normally-unsafe casts if (e1->op == TOKint64) { // Happens with Windows HANDLEs, for example. result = paintTypeOntoLiteral(pue, e->to, e1); return; } bool castToSarrayPointer = false; bool castBackFromVoid = false; if (e1->type->ty == Tarray || e1->type->ty == Tsarray || e1->type->ty == Tpointer) { // Check for unsupported type painting operations // For slices, we need the type being sliced, // since it may have already been type painted Type *elemtype = e1->type->nextOf(); if (e1->op == TOKslice) elemtype = ((SliceExp *)e1)->e1->type->nextOf(); // Allow casts from X* to void *, and X** to void** for any X. // But don't allow cast from X* to void**. // So, we strip all matching * from source and target to find X. // Allow casts to X* from void* only if the 'void' was originally an X; // we check this later on. Type *ultimatePointee = pointee; Type *ultimateSrc = elemtype; while (ultimatePointee->ty == Tpointer && ultimateSrc->ty == Tpointer) { ultimatePointee = ultimatePointee->nextOf(); ultimateSrc = ultimateSrc->nextOf(); } if (ultimatePointee->ty == Tsarray && ultimatePointee->nextOf()->equivalent(ultimateSrc)) { castToSarrayPointer = true; } else if (ultimatePointee->ty != Tvoid && ultimateSrc->ty != Tvoid && !isSafePointerCast(elemtype, pointee)) { e->error("reinterpreting cast from %s* to %s* is not supported in CTFE", elemtype->toChars(), pointee->toChars()); result = CTFEExp::cantexp; return; } if (ultimateSrc->ty == Tvoid) castBackFromVoid = true; } if (e1->op == TOKslice) { SliceExp *se = (SliceExp *)e1; if (se->e1->op == TOKnull) { result = paintTypeOntoLiteral(pue, e->type, se->e1); return; } // Create a CTFE pointer &aggregate[1..2] IndexExp *ei = new IndexExp(e->loc, se->e1, se->lwr); ei->type = e->type->nextOf(); new(pue) AddrExp(e->loc, ei, e->type); result = pue->exp(); return; } if (e1->op == TOKarrayliteral || e1->op == TOKstring) { // Create a CTFE pointer &[1,2,3][0] or &"abc"[0] IndexExp *ei = new IndexExp(e->loc, e1, new IntegerExp(e->loc, 0, Type::tsize_t)); ei->type = e->type->nextOf(); new(pue) AddrExp(e->loc, ei, e->type); result = pue->exp(); return; } if (e1->op == TOKindex && !((IndexExp *)e1)->e1->type->equals(e1->type)) { // type painting operation IndexExp *ie = (IndexExp *)e1; if (castBackFromVoid) { // get the original type. For strings, it's just the type... Type *origType = ie->e1->type->nextOf(); // ..but for arrays of type void*, it's the type of the element if (ie->e1->op == TOKarrayliteral && ie->e2->op == TOKint64) { ArrayLiteralExp *ale = (ArrayLiteralExp *)ie->e1; const size_t indx = (size_t)ie->e2->toInteger(); if (indx < ale->elements->dim) { Expression *xx = (*ale->elements)[indx]; if (xx) { if (xx->op == TOKindex) origType = ((IndexExp *)xx)->e1->type->nextOf(); else if (xx->op == TOKaddress) origType= ((AddrExp *)xx)->e1->type; else if (xx->op == TOKvar) origType = ((VarExp *)xx)->var->type; } } } if (!isSafePointerCast(origType, pointee)) { e->error("using void* to reinterpret cast from %s* to %s* is not supported in CTFE", origType->toChars(), pointee->toChars()); result = CTFEExp::cantexp; return; } } new(pue) IndexExp(e1->loc, ie->e1, ie->e2); result = pue->exp(); result->type = e->type; return; } if (e1->op == TOKaddress) { AddrExp *ae = (AddrExp *)e1; Type *origType = ae->e1->type; if (isSafePointerCast(origType, pointee)) { new(pue) AddrExp(e->loc, ae->e1, e->type); result = pue->exp(); return; } if (castToSarrayPointer && pointee->toBasetype()->ty == Tsarray && ae->e1->op == TOKindex) { // &val[idx] dinteger_t dim = ((TypeSArray *)pointee->toBasetype())->dim->toInteger(); IndexExp *ie = (IndexExp *)ae->e1; Expression *lwr = ie->e2; Expression *upr = new IntegerExp(ie->e2->loc, ie->e2->toInteger() + dim, Type::tsize_t); // Create a CTFE pointer &val[idx..idx+dim] SliceExp *er = new SliceExp(e->loc, ie->e1, lwr, upr); er->type = pointee; new(pue) AddrExp(e->loc, er, e->type); result = pue->exp(); return; } } if (e1->op == TOKvar || e1->op == TOKsymoff) { // type painting operation Type *origType = ((SymbolExp *)e1)->var->type; if (castBackFromVoid && !isSafePointerCast(origType, pointee)) { e->error("using void* to reinterpret cast from %s* to %s* is not supported in CTFE", origType->toChars(), pointee->toChars()); result = CTFEExp::cantexp; return; } if (e1->op == TOKvar) new(pue) VarExp(e->loc, ((VarExp *)e1)->var); else new(pue) SymOffExp(e->loc, ((SymOffExp *)e1)->var, ((SymOffExp *)e1)->offset); result = pue->exp(); result->type = e->to; return; } // Check if we have a null pointer (eg, inside a struct) e1 = interpret(e1, istate); if (e1->op != TOKnull) { e->error("pointer cast from %s to %s is not supported at compile time", e1->type->toChars(), e->to->toChars()); result = CTFEExp::cantexp; return; } } if (e->to->ty == Tsarray && e->e1->type->ty == Tvector) { // Special handling for: cast(float[4])__vector([w, x, y, z]) e1 = interpret(e->e1, istate); if (exceptionOrCant(e1)) return; assert(e1->op == TOKvector); e1 = interpretVectorToArray(pue, (VectorExp *)e1); } if (e->to->ty == Tarray && e1->op == TOKslice) { // Note that the slice may be void[], so when checking for dangerous // casts, we need to use the original type, which is se->e1. SliceExp *se = (SliceExp *)e1; if (!isSafePointerCast(se->e1->type->nextOf(), e->to->nextOf())) { e->error("array cast from %s to %s is not supported at compile time", se->e1->type->toChars(), e->to->toChars()); result = CTFEExp::cantexp; return; } new(pue) SliceExp(e1->loc, se->e1, se->lwr, se->upr); result = pue->exp(); result->type = e->to; return; } // Disallow array type painting, except for conversions between built-in // types of identical size. if ((e->to->ty == Tsarray || e->to->ty == Tarray) && (e1->type->ty == Tsarray || e1->type->ty == Tarray) && !isSafePointerCast(e1->type->nextOf(), e->to->nextOf())) { e->error("array cast from %s to %s is not supported at compile time", e1->type->toChars(), e->to->toChars()); result = CTFEExp::cantexp; return; } if (e->to->ty == Tsarray) e1 = resolveSlice(e1); Type *tobt = e->to->toBasetype(); if (tobt->ty == Tbool && e1->type->ty == Tpointer) { new(pue) IntegerExp(e->loc, e1->op != TOKnull, e->to); result = pue->exp(); return; } else if (tobt->isTypeBasic() && e1->op == TOKnull) { if (tobt->isintegral()) new(pue) IntegerExp(e->loc, 0, e->to); else if (tobt->isreal()) new(pue) RealExp(e->loc, CTFloat::zero, e->to); result = pue->exp(); return; } result = ctfeCast(pue, e->loc, e->type, e->to, e1); } void visit(AssertExp *e) { Expression *e1 = interpret(pue, e->e1, istate); if (exceptionOrCant(e1)) return; if (isTrueBool(e1)) { } else if (e1->isBool(false)) { if (e->msg) { UnionExp ue; result = interpret(&ue, e->msg, istate); if (exceptionOrCant(result)) return; e->error("%s", result->toChars()); } else e->error("%s failed", e->toChars()); result = CTFEExp::cantexp; return; } else { e->error("%s is not a compile time boolean expression", e1->toChars()); result = CTFEExp::cantexp; return; } result = e1; return; } void visit(PtrExp *e) { // Check for int<->float and long<->double casts. if (e->e1->op == TOKsymoff) { SymOffExp *soe = (SymOffExp *)e->e1; if (soe->offset == 0 && soe->var->isVarDeclaration() && isFloatIntPaint(e->type, soe->var->type)) { // *(cast(int*)&v), where v is a float variable result = paintFloatInt(pue, getVarExp(e->loc, istate, soe->var, ctfeNeedRvalue), e->type); return; } } if (e->e1->op == TOKcast) { CastExp *ce1 = (CastExp *)e->e1; if (ce1->e1->op == TOKaddress) { AddrExp *ae11 = (AddrExp *)ce1->e1; // *(cast(int*)&x), where x is a float expression Expression *x = ae11->e1; if (isFloatIntPaint(e->type, x->type)) { result = paintFloatInt(pue, interpret(x, istate), e->type); return; } } } // Constant fold *(&structliteral + offset) if (e->e1->op == TOKadd) { AddExp *ae = (AddExp *)e->e1; if (ae->e1->op == TOKaddress && ae->e2->op == TOKint64) { AddrExp *ade = (AddrExp *)ae->e1; Expression *ex = interpret(ade->e1, istate); if (exceptionOrCant(ex)) return; if (ex->op == TOKstructliteral) { StructLiteralExp *se = (StructLiteralExp *)ex; dinteger_t offset = ae->e2->toInteger(); result = se->getField(e->type, (unsigned)offset); if (result) return; } } } // It's possible we have an array bounds error. We need to make sure it // errors with this line number, not the one where the pointer was set. result = interpret(e->e1, istate); if (exceptionOrCant(result)) return; if (result->op == TOKfunction) return; if (result->op == TOKsymoff) { SymOffExp *soe = (SymOffExp *)result; if (soe->offset == 0 && soe->var->isFuncDeclaration()) return; e->error("cannot dereference pointer to static variable %s at compile time", soe->var->toChars()); result = CTFEExp::cantexp; return; } if (result->op != TOKaddress) { if (result->op == TOKnull) e->error("dereference of null pointer '%s'", e->e1->toChars()); else e->error("dereference of invalid pointer '%s'", result->toChars()); result = CTFEExp::cantexp; return; } // *(&x) ==> x result = ((AddrExp *)result)->e1; if (result->op == TOKslice && e->type->toBasetype()->ty == Tsarray) { /* aggr[lwr..upr] * upr may exceed the upper boundary of aggr, but the check is deferred * until those out-of-bounds elements will be touched. */ return; } result = interpret(pue, result, istate, goal); if (exceptionOrCant(result)) return; } void visit(DotVarExp *e) { Expression *ex = interpret(e->e1, istate); if (exceptionOrCant(ex)) return; if (FuncDeclaration *f = e->var->isFuncDeclaration()) { if (ex == e->e1) result = e; // optimize: reuse this CTFE reference else { new(pue) DotVarExp(e->loc, ex, f, false); result = pue->exp(); result->type = e->type; } return; } VarDeclaration *v = e->var->isVarDeclaration(); if (!v) { e->error("CTFE internal error: %s", e->toChars()); result = CTFEExp::cantexp; return; } if (ex->op == TOKnull) { if (ex->type->toBasetype()->ty == Tclass) e->error("class '%s' is null and cannot be dereferenced", e->e1->toChars()); else e->error("CTFE internal error: null this '%s'", e->e1->toChars()); result = CTFEExp::cantexp; return; } if (ex->op != TOKstructliteral && ex->op != TOKclassreference) { e->error("%s.%s is not yet implemented at compile time", e->e1->toChars(), e->var->toChars()); result = CTFEExp::cantexp; return; } StructLiteralExp *se; int i; // We can't use getField, because it makes a copy if (ex->op == TOKclassreference) { se = ((ClassReferenceExp *)ex)->value; i = ((ClassReferenceExp *)ex)->findFieldIndexByName(v); } else { se = (StructLiteralExp *)ex; i = findFieldIndexByName(se->sd, v); } if (i == -1) { e->error("couldn't find field %s of type %s in %s", v->toChars(), e->type->toChars(), se->toChars()); result = CTFEExp::cantexp; return; } if (goal == ctfeNeedLvalue) { Expression *ev = (*se->elements)[i]; if (!ev || ev->op == TOKvoid) (*se->elements)[i] = voidInitLiteral(e->type, v).copy(); // just return the (simplified) dotvar expression as a CTFE reference if (e->e1 == ex) result = e; else { new(pue) DotVarExp(e->loc, ex, v); result = pue->exp(); result->type = e->type; } return; } result = (*se->elements)[i]; if (!result) { // https://issues.dlang.org/show_bug.cgi?id=19897 // Zero-length fields don't have an initializer. if (v->type->size() == 0) result = voidInitLiteral(e->type, v).copy(); else { e->error("Internal Compiler Error: null field %s", v->toChars()); result = CTFEExp::cantexp; return; } } if (result->op == TOKvoid) { VoidInitExp *ve = (VoidInitExp *)result; const char *s = ve->var->toChars(); if (v->overlapped) { e->error("reinterpretation through overlapped field %s is not allowed in CTFE", s); result = CTFEExp::cantexp; return; } e->error("cannot read uninitialized variable %s in CTFE", s); result = CTFEExp::cantexp; return; } if (v->type->ty != result->type->ty && v->type->ty == Tsarray) { // Block assignment from inside struct literals TypeSArray *tsa = (TypeSArray *)v->type; size_t len = (size_t)tsa->dim->toInteger(); UnionExp ue; result = createBlockDuplicatedArrayLiteral(&ue, ex->loc, v->type, ex, len); if (result == ue.exp()) result = ue.copy(); (*se->elements)[i] = result; } } void visit(RemoveExp *e) { Expression *agg = interpret(e->e1, istate); if (exceptionOrCant(agg)) return; Expression *index = interpret(e->e2, istate); if (exceptionOrCant(index)) return; if (agg->op == TOKnull) { result = CTFEExp::voidexp; return; } assert(agg->op == TOKassocarrayliteral); AssocArrayLiteralExp *aae = (AssocArrayLiteralExp *)agg; Expressions *keysx = aae->keys; Expressions *valuesx = aae->values; size_t removed = 0; for (size_t j = 0; j < valuesx->dim; ++j) { Expression *ekey = (*keysx)[j]; int eq = ctfeEqual(e->loc, TOKequal, ekey, index); if (eq) ++removed; else if (removed != 0) { (*keysx)[j - removed] = ekey; (*valuesx)[j - removed] = (*valuesx)[j]; } } valuesx->dim = valuesx->dim - removed; keysx->dim = keysx->dim - removed; new(pue) IntegerExp(e->loc, removed ? 1 : 0, Type::tbool); result = pue->exp(); } void visit(ClassReferenceExp *e) { //printf("ClassReferenceExp::interpret() %s\n", e->value->toChars()); result = e; } void visit(VoidInitExp *e) { e->error("CTFE internal error: trying to read uninitialized variable"); assert(0); result = CTFEExp::cantexp; } void visit(ThrownExceptionExp *e) { assert(0); // This should never be interpreted result = e; } }; /******************************************** * Interpret the expression. * Params: * pue = non-null pointer to temporary storage that can be used to store the return value * e = Expression to interpret * istate = context * goal = what the result will be used for * Returns: * resulting expression */ static Expression *interpret(UnionExp *pue, Expression *e, InterState *istate, CtfeGoal goal) { if (!e) return NULL; Interpreter v(pue, istate, goal); e->accept(&v); Expression *ex = v.result; assert(goal == ctfeNeedNothing || ex != NULL); return ex; } /// Expression *interpret(Expression *e, InterState *istate, CtfeGoal goal) { UnionExp ue; Expression *result = interpret(&ue, e, istate, goal); if (result == ue.exp()) result = ue.copy(); return result; } /*********************************** * Interpret the statement. * Params: * pue = non-null pointer to temporary storage that can be used to store the return value * s = Statement to interpret * istate = context * Returns: * NULL continue to next statement * TOKcantexp cannot interpret statement at compile time * !NULL expression from return statement, or thrown exception */ static Expression *interpret(UnionExp *pue, Statement *s, InterState *istate) { if (!s) return NULL; Interpreter v(pue, istate, ctfeNeedNothing); s->accept(&v); return v.result; } Expression *interpret(Statement *s, InterState *istate) { UnionExp ue; Expression *result = interpret(&ue, s, istate); if (result == ue.exp()) result = ue.copy(); return result; } /** * All results destined for use outside of CTFE need to have their CTFE-specific * features removed. * In particular, * 1. all slices must be resolved. * 2. all .ownedByCtfe set to OWNEDcode */ Expression *scrubReturnValue(Loc loc, Expression *e) { if (e->op == TOKclassreference) { StructLiteralExp *sle = ((ClassReferenceExp*)e)->value; if (Expression *ex = scrubStructLiteral(loc, sle)) return ex; } else if (e->op == TOKvoid) { error(loc, "uninitialized variable '%s' cannot be returned from CTFE", ((VoidInitExp *)e)->var->toChars()); return new ErrorExp(); } e = resolveSlice(e); if (e->op == TOKstructliteral) { StructLiteralExp *sle = (StructLiteralExp *)e; if (Expression *ex = scrubStructLiteral(loc, sle)) return ex; } else if (e->op == TOKstring) { ((StringExp *)e)->ownedByCtfe = OWNEDcode; } else if (e->op == TOKarrayliteral) { ArrayLiteralExp *ale = (ArrayLiteralExp *)e; ale->ownedByCtfe = OWNEDcode; if (Expression *ex = scrubArray(loc, ale->elements)) return ex; } else if (e->op == TOKassocarrayliteral) { AssocArrayLiteralExp *aae = (AssocArrayLiteralExp *)e; aae->ownedByCtfe = OWNEDcode; if (Expression *ex = scrubArray(loc, aae->keys)) return ex; if (Expression *ex = scrubArray(loc, aae->values)) return ex; aae->type = toBuiltinAAType(aae->type); } else if (e->op == TOKvector) { VectorExp *ve = (VectorExp *)e; ve->ownedByCtfe = OWNEDcode; if (ve->e1->op == TOKarrayliteral) { ArrayLiteralExp *ale = (ArrayLiteralExp *)ve->e1; ale->ownedByCtfe = OWNEDcode; if (Expression *ex = scrubArray(loc, ale->elements)) return ex; } } return e; } /* Returns: true if e is void, * or is an array literal or struct literal of void elements. */ static bool isVoid(Expression *e, bool checkArray = false) { if (e->op == TOKvoid) return true; if (checkArray && e->type->ty != Tsarray) return false; if (e->op == TOKarrayliteral) return isEntirelyVoid(((ArrayLiteralExp *)e)->elements); if (e->op == TOKstructliteral) return isEntirelyVoid(((StructLiteralExp *)e)->elements); return false; } // Return true if every element is either void, // or is an array literal or struct literal of void elements. bool isEntirelyVoid(Expressions *elems) { for (size_t i = 0; i < elems->dim; i++) { Expression *e = (*elems)[i]; // It can be NULL for performance reasons, // see StructLiteralExp::interpret(). if (e && !isVoid(e)) return false; } return true; } // Scrub all members of an array. Return false if error Expression *scrubArray(Loc loc, Expressions *elems, bool structlit) { for (size_t i = 0; i < elems->dim; i++) { Expression *e = (*elems)[i]; // It can be NULL for performance reasons, // see StructLiteralExp::interpret(). if (!e) continue; // A struct .init may contain void members. // Static array members are a weird special case (bug 10994). if (structlit && isVoid(e, true)) { e = NULL; } else { e = scrubReturnValue(loc, e); if (CTFEExp::isCantExp(e) || e->op == TOKerror) return e; } (*elems)[i] = e; } return NULL; } Expression *scrubStructLiteral(Loc loc, StructLiteralExp *sle) { sle->ownedByCtfe = OWNEDcode; if (!(sle->stageflags & stageScrub)) { const int old = sle->stageflags; sle->stageflags |= stageScrub; // prevent infinite recursion if (Expression *ex = scrubArray(loc, sle->elements, true)) return ex; sle->stageflags = old; } return NULL; } /************************************** * Transitively set all .ownedByCtfe to OWNEDcache */ Expression *scrubCacheValue(Expression *e) { if (!e) return e; if (e->op == TOKclassreference) { StructLiteralExp *sle = ((ClassReferenceExp*)e)->value; if (Expression *ex = scrubStructLiteralCache(sle)) return ex; } else if (e->op == TOKstructliteral) { StructLiteralExp *sle = (StructLiteralExp *)e; if (Expression *ex = scrubStructLiteralCache(sle)) return ex; } else if (e->op == TOKstring) { ((StringExp *)e)->ownedByCtfe = OWNEDcache; } else if (e->op == TOKarrayliteral) { ArrayLiteralExp *ale = (ArrayLiteralExp *)e; ale->ownedByCtfe = OWNEDcache; if (Expression *ex = scrubArrayCache(ale->elements)) return ex; } else if (e->op == TOKassocarrayliteral) { AssocArrayLiteralExp *aae = (AssocArrayLiteralExp *)e; aae->ownedByCtfe = OWNEDcache; if (Expression *ex = scrubArrayCache(aae->keys)) return ex; if (Expression *ex = scrubArrayCache(aae->values)) return ex; } else if (e->op == TOKvector) { VectorExp *ve = (VectorExp *)e; ve->ownedByCtfe = OWNEDcache; if (ve->e1->op == TOKarrayliteral) { ArrayLiteralExp *ale = (ArrayLiteralExp *)ve->e1; ale->ownedByCtfe = OWNEDcache; if (Expression *ex = scrubArrayCache(ale->elements)) return ex; } } return e; } Expression *scrubArrayCache(Expressions *elems) { for (size_t i = 0; i < elems->dim; i++) { Expression *e = (*elems)[i]; (*elems)[i] = scrubCacheValue(e); } return NULL; } Expression *scrubStructLiteralCache(StructLiteralExp *sle) { sle->ownedByCtfe = OWNEDcache; if (!(sle->stageflags & stageScrub)) { const int old = sle->stageflags; sle->stageflags |= stageScrub; // prevent infinite recursion if (Expression *ex = scrubArrayCache(sle->elements)) return ex; sle->stageflags = old; } return NULL; } /******************************* Special Functions ***************************/ static Expression *interpret_length(UnionExp *pue, InterState *istate, Expression *earg) { //printf("interpret_length()\n"); earg = interpret(pue, earg, istate); if (exceptionOrCantInterpret(earg)) return earg; dinteger_t len = 0; if (earg->op == TOKassocarrayliteral) len = ((AssocArrayLiteralExp *)earg)->keys->dim; else assert(earg->op == TOKnull); new(pue) IntegerExp(earg->loc, len, Type::tsize_t); return pue->exp(); } static Expression *interpret_keys(UnionExp *pue, InterState *istate, Expression *earg, Type *returnType) { earg = interpret(pue, earg, istate); if (exceptionOrCantInterpret(earg)) return earg; if (earg->op == TOKnull) { new(pue) NullExp(earg->loc, earg->type); return pue->exp(); } if (earg->op != TOKassocarrayliteral && earg->type->toBasetype()->ty != Taarray) return NULL; assert(earg->op == TOKassocarrayliteral); AssocArrayLiteralExp *aae = (AssocArrayLiteralExp *)earg; ArrayLiteralExp *ae = new ArrayLiteralExp(aae->loc, returnType, aae->keys); ae->ownedByCtfe = aae->ownedByCtfe; *pue = copyLiteral(ae); return pue->exp(); } static Expression *interpret_values(UnionExp *pue, InterState *istate, Expression *earg, Type *returnType) { earg = interpret(pue, earg, istate); if (exceptionOrCantInterpret(earg)) return earg; if (earg->op == TOKnull) { new(pue) NullExp(earg->loc, earg->type); return pue->exp(); } if (earg->op != TOKassocarrayliteral && earg->type->toBasetype()->ty != Taarray) return NULL; assert(earg->op == TOKassocarrayliteral); AssocArrayLiteralExp *aae = (AssocArrayLiteralExp *)earg; ArrayLiteralExp *ae = new ArrayLiteralExp(aae->loc, returnType, aae->values); ae->ownedByCtfe = aae->ownedByCtfe; //printf("result is %s\n", e->toChars()); *pue = copyLiteral(ae); return pue->exp(); } Expression *interpret_dup(UnionExp *pue, InterState *istate, Expression *earg) { earg = interpret(pue, earg, istate); if (exceptionOrCantInterpret(earg)) return earg; if (earg->op == TOKnull) { new(pue) NullExp(earg->loc, earg->type); return pue->exp(); } if (earg->op != TOKassocarrayliteral && earg->type->toBasetype()->ty != Taarray) return NULL; assert(earg->op == TOKassocarrayliteral); AssocArrayLiteralExp *aae = (AssocArrayLiteralExp *)copyLiteral(earg).copy(); for (size_t i = 0; i < aae->keys->dim; i++) { if (Expression *e = evaluatePostblit(istate, (*aae->keys)[i])) return e; if (Expression *e = evaluatePostblit(istate, (*aae->values)[i])) return e; } aae->type = earg->type->mutableOf(); // repaint type from const(int[int]) to const(int)[int] //printf("result is %s\n", aae->toChars()); return aae; } // signature is int delegate(ref Value) OR int delegate(ref Key, ref Value) Expression *interpret_aaApply(UnionExp *pue, InterState *istate, Expression *aa, Expression *deleg) { aa = interpret(aa, istate); if (exceptionOrCantInterpret(aa)) return aa; if (aa->op != TOKassocarrayliteral) { new(pue) IntegerExp(deleg->loc, 0, Type::tsize_t); return pue->exp(); } FuncDeclaration *fd = NULL; Expression *pthis = NULL; if (deleg->op == TOKdelegate) { fd = ((DelegateExp *)deleg)->func; pthis = ((DelegateExp *)deleg)->e1; } else if (deleg->op == TOKfunction) fd = ((FuncExp*)deleg)->fd; assert(fd && fd->fbody); assert(fd->parameters); size_t numParams = fd->parameters->dim; assert(numParams == 1 || numParams == 2); Parameter *fparam = Parameter::getNth(((TypeFunction *)fd->type)->parameters, numParams - 1); bool wantRefValue = 0 != (fparam->storageClass & (STCout | STCref)); Expressions args; args.setDim(numParams); AssocArrayLiteralExp *ae = (AssocArrayLiteralExp *)aa; if (!ae->keys || ae->keys->dim == 0) return new IntegerExp(deleg->loc, 0, Type::tsize_t); Expression *eresult; for (size_t i = 0; i < ae->keys->dim; ++i) { Expression *ekey = (*ae->keys)[i]; Expression *evalue = (*ae->values)[i]; if (wantRefValue) { Type *t = evalue->type; evalue = new IndexExp(deleg->loc, ae, ekey); evalue->type = t; } args[numParams - 1] = evalue; if (numParams == 2) args[0] = ekey; UnionExp ue; eresult = interpretFunction(&ue, fd, istate, &args, pthis); if (eresult == ue.exp()) eresult = ue.copy(); if (exceptionOrCantInterpret(eresult)) return eresult; assert(eresult->op == TOKint64); if (((IntegerExp *)eresult)->getInteger() != 0) return eresult; } return eresult; } /* Decoding UTF strings for foreach loops. Duplicates the functionality of * the twelve _aApplyXXn functions in aApply.d in the runtime. */ static Expression *foreachApplyUtf(UnionExp *pue, InterState *istate, Expression *str, Expression *deleg, bool rvs) { FuncDeclaration *fd = NULL; Expression *pthis = NULL; if (deleg->op == TOKdelegate) { fd = ((DelegateExp *)deleg)->func; pthis = ((DelegateExp *)deleg)->e1; } else if (deleg->op == TOKfunction) fd = ((FuncExp*)deleg)->fd; assert(fd && fd->fbody); assert(fd->parameters); size_t numParams = fd->parameters->dim; assert(numParams == 1 || numParams == 2); Type *charType = (*fd->parameters)[numParams-1]->type; Type *indexType = numParams == 2 ? (*fd->parameters)[0]->type : Type::tsize_t; size_t len = (size_t)resolveArrayLength(str); if (len == 0) { new(pue) IntegerExp(deleg->loc, 0, indexType); return pue->exp(); } str = resolveSlice(str); StringExp *se = NULL; ArrayLiteralExp *ale = NULL; if (str->op == TOKstring) se = (StringExp *) str; else if (str->op == TOKarrayliteral) ale = (ArrayLiteralExp *)str; else { str->error("CTFE internal error: cannot foreach %s", str->toChars()); return CTFEExp::cantexp; } Expressions args; args.setDim(numParams); Expression *eresult = NULL; // ded-store to prevent spurious warning // Buffers for encoding; also used for decoding array literals utf8_t utf8buf[4]; unsigned short utf16buf[2]; size_t start = rvs ? len : 0; size_t end = rvs ? 0: len; for (size_t indx = start; indx != end;) { // Step 1: Decode the next dchar from the string. const char *errmsg = NULL; // Used for reporting decoding errors dchar_t rawvalue; // Holds the decoded dchar size_t currentIndex = indx; // The index of the decoded character if (ale) { // If it is an array literal, copy the code points into the buffer size_t buflen = 1; // #code points in the buffer size_t n = 1; // #code points in this char size_t sz = (size_t)ale->type->nextOf()->size(); switch (sz) { case 1: if (rvs) { // find the start of the string --indx; buflen = 1; while (indx > 0 && buflen < 4) { Expression * r = (*ale->elements)[indx]; assert(r->op == TOKint64); utf8_t x = (utf8_t)(((IntegerExp *)r)->getInteger()); if ((x & 0xC0) != 0x80) break; ++buflen; } } else buflen = (indx + 4 > len) ? len - indx : 4; for (size_t i = 0; i < buflen; ++i) { Expression * r = (*ale->elements)[indx + i]; assert(r->op == TOKint64); utf8buf[i] = (utf8_t)(((IntegerExp *)r)->getInteger()); } n = 0; errmsg = utf_decodeChar(&utf8buf[0], buflen, &n, &rawvalue); break; case 2: if (rvs) { // find the start of the string --indx; buflen = 1; Expression * r = (*ale->elements)[indx]; assert(r->op == TOKint64); unsigned short x = (unsigned short)(((IntegerExp *)r)->getInteger()); if (indx > 0 && x >= 0xDC00 && x <= 0xDFFF) { --indx; ++buflen; } } else buflen = (indx + 2 > len) ? len - indx : 2; for (size_t i=0; i < buflen; ++i) { Expression * r = (*ale->elements)[indx + i]; assert(r->op == TOKint64); utf16buf[i] = (unsigned short)(((IntegerExp *)r)->getInteger()); } n = 0; errmsg = utf_decodeWchar(&utf16buf[0], buflen, &n, &rawvalue); break; case 4: { if (rvs) --indx; Expression * r = (*ale->elements)[indx]; assert(r->op == TOKint64); rawvalue = (dchar_t)((IntegerExp *)r)->getInteger(); n = 1; } break; default: assert(0); } if (!rvs) indx += n; } else { // String literals size_t saveindx; // used for reverse iteration switch (se->sz) { case 1: if (rvs) { // find the start of the string utf8_t *s = (utf8_t *)se->string; --indx; while (indx > 0 && ((s[indx]&0xC0) == 0x80)) --indx; saveindx = indx; } errmsg = utf_decodeChar((utf8_t *)se->string, se->len, &indx, &rawvalue); if (rvs) indx = saveindx; break; case 2: if (rvs) { // find the start unsigned short *s = (unsigned short *)se->string; --indx; if (s[indx] >= 0xDC00 && s[indx]<= 0xDFFF) --indx; saveindx = indx; } errmsg = utf_decodeWchar((unsigned short *)se->string, se->len, &indx, &rawvalue); if (rvs) indx = saveindx; break; case 4: if (rvs) --indx; rawvalue = ((unsigned *)(se->string))[indx]; if (!rvs) ++indx; break; default: assert(0); } } if (errmsg) { deleg->error("%s", errmsg); return CTFEExp::cantexp; } // Step 2: encode the dchar in the target encoding int charlen = 1; // How many codepoints are involved? switch (charType->size()) { case 1: charlen = utf_codeLengthChar(rawvalue); utf_encodeChar(&utf8buf[0], rawvalue); break; case 2: charlen = utf_codeLengthWchar(rawvalue); utf_encodeWchar(&utf16buf[0], rawvalue); break; case 4: break; default: assert(0); } if (rvs) currentIndex = indx; // Step 3: call the delegate once for each code point // The index only needs to be set once if (numParams == 2) args[0] = new IntegerExp(deleg->loc, currentIndex, indexType); Expression *val = NULL; for (int k= 0; k < charlen; ++k) { dchar_t codepoint; switch (charType->size()) { case 1: codepoint = utf8buf[k]; break; case 2: codepoint = utf16buf[k]; break; case 4: codepoint = rawvalue; break; default: assert(0); } val = new IntegerExp(str->loc, codepoint, charType); args[numParams - 1] = val; UnionExp ue; eresult = interpretFunction(&ue, fd, istate, &args, pthis); if (eresult == ue.exp()) eresult = ue.copy(); if (exceptionOrCantInterpret(eresult)) return eresult; assert(eresult->op == TOKint64); if (((IntegerExp *)eresult)->getInteger() != 0) return eresult; } } return eresult; } /* If this is a built-in function, return the interpreted result, * Otherwise, return NULL. */ Expression *evaluateIfBuiltin(UnionExp *pue, InterState *istate, Loc loc, FuncDeclaration *fd, Expressions *arguments, Expression *pthis) { Expression *e = NULL; size_t nargs = arguments ? arguments->dim : 0; if (!pthis) { if (isBuiltin(fd) == BUILTINyes) { Expressions args; args.setDim(nargs); for (size_t i = 0; i < args.dim; i++) { Expression *earg = (*arguments)[i]; earg = interpret(earg, istate); if (exceptionOrCantInterpret(earg)) return earg; args[i] = earg; } e = eval_builtin(loc, fd, &args); if (!e) { error(loc, "cannot evaluate unimplemented builtin %s at compile time", fd->toChars()); e = CTFEExp::cantexp; } } } if (!pthis) { Expression *firstarg = nargs > 0 ? (*arguments)[0] : NULL; if (firstarg && firstarg->type->toBasetype()->ty == Taarray) { TypeAArray *firstAAtype = (TypeAArray *)firstarg->type; const Identifier *id = fd->ident; if (nargs == 1) { if (fd->ident == Id::aaLen) return interpret_length(pue, istate, firstarg); if (fd->toParent2()->ident == Id::object) { if (id == Id::keys) return interpret_keys(pue, istate, firstarg, firstAAtype->index->arrayOf()); if (id == Id::values) return interpret_values(pue, istate, firstarg, firstAAtype->nextOf()->arrayOf()); if (id == Id::rehash) return interpret(pue, firstarg, istate); if (id == Id::dup) return interpret_dup(pue, istate, firstarg); } } else // (nargs == 3) { if (id == Id::_aaApply) return interpret_aaApply(pue, istate, firstarg, (Expression *)(arguments->data[2])); if (id == Id::_aaApply2) return interpret_aaApply(pue, istate, firstarg, (Expression *)(arguments->data[2])); } } } if (pthis && !fd->fbody && fd->isCtorDeclaration() && fd->parent && fd->parent->parent && fd->parent->parent->ident == Id::object) { if (pthis->op == TOKclassreference && fd->parent->ident == Id::Throwable) { // At present, the constructors just copy their arguments into the struct. // But we might need some magic if stack tracing gets added to druntime. StructLiteralExp *se = ((ClassReferenceExp *)pthis)->value; assert(arguments->dim <= se->elements->dim); for (size_t i = 0; i < arguments->dim; ++i) { e = interpret((*arguments)[i], istate); if (exceptionOrCantInterpret(e)) return e; (*se->elements)[i] = e; } return CTFEExp::voidexp; } } if (nargs == 1 && !pthis && (fd->ident == Id::criticalenter || fd->ident == Id::criticalexit)) { // Support synchronized{} as a no-op return CTFEExp::voidexp; } if (!pthis) { const char *id = fd->ident->toChars(); size_t idlen = strlen(id); if (nargs == 2 && (idlen == 10 || idlen == 11) && !strncmp(id, "_aApply", 7)) { // Functions from aApply.d and aApplyR.d in the runtime bool rvs = (idlen == 11); // true if foreach_reverse char c = id[idlen-3]; // char width: 'c', 'w', or 'd' char s = id[idlen-2]; // string width: 'c', 'w', or 'd' char n = id[idlen-1]; // numParams: 1 or 2. // There are 12 combinations if ((n == '1' || n == '2') && (c == 'c' || c == 'w' || c == 'd') && (s == 'c' || s == 'w' || s == 'd') && c != s) { Expression *str = (*arguments)[0]; str = interpret(str, istate); if (exceptionOrCantInterpret(str)) return str; return foreachApplyUtf(pue, istate, str, (*arguments)[1], rvs); } } } return e; } Expression *evaluatePostblit(InterState *istate, Expression *e) { Type *tb = e->type->baseElemOf(); if (tb->ty != Tstruct) return NULL; StructDeclaration *sd = ((TypeStruct *)tb)->sym; if (!sd->postblit) return NULL; if (e->op == TOKarrayliteral) { ArrayLiteralExp *ale = (ArrayLiteralExp *)e; for (size_t i = 0; i < ale->elements->dim; i++) { e = evaluatePostblit(istate, (*ale->elements)[i]); if (e) return e; } return NULL; } if (e->op == TOKstructliteral) { // e.__postblit() UnionExp ue; e = interpretFunction(&ue, sd->postblit, istate, NULL, e); if (e == ue.exp()) e = ue.copy(); if (exceptionOrCantInterpret(e)) return e; return NULL; } assert(0); return NULL; } Expression *evaluateDtor(InterState *istate, Expression *e) { Type *tb = e->type->baseElemOf(); if (tb->ty != Tstruct) return NULL; StructDeclaration *sd = ((TypeStruct *)tb)->sym; if (!sd->dtor) return NULL; UnionExp ue; if (e->op == TOKarrayliteral) { ArrayLiteralExp *alex = (ArrayLiteralExp *)e; for (size_t i = alex->elements->dim; 0 < i--; ) e = evaluateDtor(istate, (*alex->elements)[i]); } else if (e->op == TOKstructliteral) { // e.__dtor() e = interpretFunction(&ue, sd->dtor, istate, NULL, e); } else assert(0); if (exceptionOrCantInterpret(e)) { if (e == ue.exp()) e = ue.copy(); return e; } return NULL; } /*************************** CTFE Sanity Checks ***************************/ /* Setter functions for CTFE variable values. * These functions exist to check for compiler CTFE bugs. */ bool hasValue(VarDeclaration *vd) { if (vd->ctfeAdrOnStack == -1) return false; return NULL != getValue(vd); } Expression *getValue(VarDeclaration *vd) { return ctfeStack.getValue(vd); } void setValueNull(VarDeclaration *vd) { ctfeStack.setValue(vd, NULL); } // Don't check for validity void setValueWithoutChecking(VarDeclaration *vd, Expression *newval) { ctfeStack.setValue(vd, newval); } void setValue(VarDeclaration *vd, Expression *newval) { assert((vd->storage_class & (STCout | STCref)) ? isCtfeReferenceValid(newval) : isCtfeValueValid(newval)); ctfeStack.setValue(vd, newval); }