| |
| /* 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<void *> savedId; // id of the previous state of that var |
| |
| Array<void *> 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 TOKstring: |
| 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; |
| assert((*boss->value->elements)[4]->type->ty == Tclass); // Throwable.next |
| ClassReferenceExp *collateral = newest->thrown; |
| if ( isAnErrorException(collateral->originalClass()) && |
| !isAnErrorException(boss->originalClass())) |
| { |
| // The new exception bypass the existing chain |
| assert((*collateral->value->elements)[5]->type->ty == Tclass); |
| (*collateral->value->elements)[5] = boss; |
| return newest; |
| } |
| while ((*boss->value->elements)[4]->op == TOKclassreference) |
| { |
| boss = (ClassReferenceExp *)(*boss->value->elements)[4]; |
| } |
| (*boss->value->elements)[4] = 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 dynamic array, eg &int[3] to int[]* |
| if (val->type->ty == Tsarray && pointee->ty == Tarray && |
| elemsize == pointee->nextOf()->size()) |
| { |
| new(pue) AddrExp(e->loc, val, e->type); |
| result = pue->exp(); |
| return; |
| } |
| |
| // 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->_scope) // semantic() not yet run |
| { |
| v->semantic (v->_scope); |
| 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; |
| } |
| |