| /** |
| * The entry point for CTFE. |
| * |
| * Specification: ($LINK2 https://dlang.org/spec/function.html#interpretation, Compile Time Function Execution (CTFE)) |
| * |
| * Copyright: Copyright (C) 1999-2022 by The D Language Foundation, All Rights Reserved |
| * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) |
| * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) |
| * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/dinterpret.d, _dinterpret.d) |
| * Documentation: https://dlang.org/phobos/dmd_dinterpret.html |
| * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/dinterpret.d |
| */ |
| |
| module dmd.dinterpret; |
| |
| import core.stdc.stdio; |
| import core.stdc.stdlib; |
| import core.stdc.string; |
| import dmd.apply; |
| import dmd.arraytypes; |
| import dmd.astenums; |
| import dmd.attrib; |
| import dmd.builtin; |
| import dmd.constfold; |
| import dmd.ctfeexpr; |
| import dmd.dclass; |
| import dmd.declaration; |
| import dmd.dstruct; |
| import dmd.dsymbol; |
| import dmd.dsymbolsem; |
| import dmd.dtemplate; |
| import dmd.errors; |
| import dmd.expression; |
| import dmd.expressionsem; |
| import dmd.func; |
| import dmd.globals; |
| import dmd.hdrgen; |
| import dmd.id; |
| import dmd.identifier; |
| import dmd.init; |
| import dmd.initsem; |
| import dmd.mtype; |
| import dmd.printast; |
| import dmd.root.rmem; |
| import dmd.root.array; |
| import dmd.root.ctfloat; |
| import dmd.root.region; |
| import dmd.root.rootobject; |
| import dmd.root.utf; |
| import dmd.statement; |
| import dmd.tokens; |
| import dmd.visitor; |
| |
| /************************************* |
| * 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. |
| */ |
| public Expression ctfeInterpret(Expression e) |
| { |
| switch (e.op) |
| { |
| case EXP.int64: |
| case EXP.float64: |
| case EXP.complex80: |
| case EXP.null_: |
| case EXP.void_: |
| case EXP.string_: |
| case EXP.this_: |
| case EXP.super_: |
| case EXP.type: |
| case EXP.typeid_: |
| case EXP.template_: // non-eponymous template/instance |
| case EXP.scope_: // ditto |
| case EXP.dotTemplateDeclaration: // ditto, e.e1 doesn't matter here |
| case EXP.dotTemplateInstance: // ditto |
| case EXP.dot: // ditto |
| if (e.type.ty == Terror) |
| return ErrorExp.get(); |
| goto case EXP.error; |
| |
| case EXP.error: |
| return e; |
| |
| default: |
| break; |
| } |
| |
| assert(e.type); // https://issues.dlang.org/show_bug.cgi?id=14642 |
| //assert(e.type.ty != Terror); // FIXME |
| if (e.type.ty == Terror) |
| return ErrorExp.get(); |
| |
| auto rgnpos = ctfeGlobals.region.savePos(); |
| |
| Expression result = interpret(e, null); |
| |
| // Report an error if the expression contained a `ThrowException` and |
| // hence generated an uncaught exception |
| if (auto tee = result.isThrownExceptionExp()) |
| { |
| tee.generateUncaughtError(); |
| result = CTFEExp.cantexp; |
| } |
| else |
| result = copyRegionExp(result); |
| |
| if (!CTFEExp.isCantExp(result)) |
| result = scrubReturnValue(e.loc, result); |
| if (CTFEExp.isCantExp(result)) |
| result = ErrorExp.get(); |
| |
| ctfeGlobals.region.release(rgnpos); |
| |
| 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)). |
| */ |
| public Expression ctfeInterpretForPragmaMsg(Expression e) |
| { |
| if (e.op == EXP.error || e.op == EXP.type) |
| return e; |
| |
| // It's also OK for it to be a function declaration (happens only with |
| // __traits(getOverloads)) |
| if (auto ve = e.isVarExp()) |
| if (ve.var.isFuncDeclaration()) |
| { |
| return e; |
| } |
| |
| auto tup = e.isTupleExp(); |
| if (!tup) |
| return e.ctfeInterpret(); |
| |
| // Tuples need to be treated separately, since they are |
| // allowed to contain a TypeExp in this case. |
| |
| Expressions* expsx = null; |
| foreach (i, g; *tup.exps) |
| { |
| auto h = ctfeInterpretForPragmaMsg(g); |
| if (h != g) |
| { |
| if (!expsx) |
| { |
| expsx = tup.exps.copy(); |
| } |
| (*expsx)[i] = h; |
| } |
| } |
| if (expsx) |
| { |
| auto te = new TupleExp(e.loc, expsx); |
| expandTuples(te.exps); |
| te.type = new TypeTuple(te.exps); |
| return te; |
| } |
| return e; |
| } |
| |
| public extern (C++) Expression getValue(VarDeclaration vd) |
| { |
| return ctfeGlobals.stack.getValue(vd); |
| } |
| |
| /************************************************* |
| * Allocate an Expression in the ctfe region. |
| * Params: |
| * T = type of Expression to allocate |
| * args = arguments to Expression's constructor |
| * Returns: |
| * allocated Expression |
| */ |
| T ctfeEmplaceExp(T : Expression, Args...)(Args args) |
| { |
| if (mem.isGCEnabled) |
| return new T(args); |
| auto p = ctfeGlobals.region.malloc(__traits(classInstanceSize, T)); |
| emplaceExp!T(p, args); |
| return cast(T)p; |
| } |
| |
| // CTFE diagnostic information |
| public extern (C++) void printCtfePerformanceStats() |
| { |
| debug (SHOWPERFORMANCE) |
| { |
| printf(" ---- CTFE Performance ----\n"); |
| printf("max call depth = %d\tmax stack = %d\n", ctfeGlobals.maxCallDepth, ctfeGlobals.stack.maxStackUsage()); |
| printf("array allocs = %d\tassignments = %d\n\n", ctfeGlobals.numArrayAllocs, ctfeGlobals.numAssignments); |
| } |
| } |
| |
| /************************** |
| */ |
| |
| void incArrayAllocs() |
| { |
| ++ctfeGlobals.numArrayAllocs; |
| } |
| |
| /* ================================================ Implementation ======================================= */ |
| |
| private: |
| |
| /*************** |
| * Collect together globals used by CTFE |
| */ |
| struct CtfeGlobals |
| { |
| Region region; |
| |
| CtfeStack stack; |
| |
| int callDepth = 0; // current number of recursive calls |
| |
| // When printing a stack trace, suppress this number of calls |
| int stackTraceCallsToSuppress = 0; |
| |
| int maxCallDepth = 0; // highest number of recursive calls |
| int numArrayAllocs = 0; // Number of allocated arrays |
| int numAssignments = 0; // total number of assignments executed |
| } |
| |
| __gshared CtfeGlobals ctfeGlobals; |
| |
| enum CTFEGoal : int |
| { |
| RValue, /// Must return an Rvalue (== CTFE value) |
| LValue, /// Must return an Lvalue (== CTFE reference) |
| Nothing, /// The return value is not required |
| } |
| |
| //debug = LOG; |
| //debug = LOGASSIGN; |
| //debug = LOGCOMPILE; |
| //debug = SHOWPERFORMANCE; |
| |
| // Maximum allowable recursive function calls in CTFE |
| enum 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: |
| extern (C++) size_t stackPointer() |
| { |
| return values.dim; |
| } |
| |
| // The current value of 'this', or NULL if none |
| extern (C++) Expression getThis() |
| { |
| return localThis; |
| } |
| |
| // Largest number of stack positions we've used |
| extern (C++) size_t maxStackUsage() |
| { |
| return maxStackPointer; |
| } |
| |
| // Start a new stack frame, using the provided 'this'. |
| extern (C++) void startFrame(Expression thisexp) |
| { |
| frames.push(cast(void*)cast(size_t)framepointer); |
| savedThis.push(localThis); |
| framepointer = stackPointer(); |
| localThis = thisexp; |
| } |
| |
| extern (C++) void endFrame() |
| { |
| size_t oldframe = cast(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); |
| } |
| |
| extern (C++) bool isInCurrentFrame(VarDeclaration v) |
| { |
| if (v.isDataseg() && !v.isCTFE()) |
| return false; // It's a global |
| return v.ctfeAdrOnStack >= framepointer; |
| } |
| |
| extern (C++) Expression getValue(VarDeclaration v) |
| { |
| //printf("getValue() %s\n", v.toChars()); |
| if ((v.isDataseg() || v.storage_class & STC.manifest) && !v.isCTFE()) |
| { |
| assert(v.ctfeAdrOnStack < globalValues.dim); |
| return globalValues[v.ctfeAdrOnStack]; |
| } |
| assert(v.ctfeAdrOnStack < stackPointer()); |
| return values[v.ctfeAdrOnStack]; |
| } |
| |
| extern (C++) void setValue(VarDeclaration v, Expression e) |
| { |
| //printf("setValue() %s : %s\n", v.toChars(), e.toChars()); |
| assert(!v.isDataseg() || v.isCTFE()); |
| assert(v.ctfeAdrOnStack < stackPointer()); |
| values[v.ctfeAdrOnStack] = e; |
| } |
| |
| extern (C++) void push(VarDeclaration v) |
| { |
| //printf("push() %s\n", v.toChars()); |
| assert(!v.isDataseg() || v.isCTFE()); |
| if (v.ctfeAdrOnStack != VarDeclaration.AdrOnStackNone && v.ctfeAdrOnStack >= framepointer) |
| { |
| // Already exists in this frame, reuse it. |
| values[v.ctfeAdrOnStack] = null; |
| return; |
| } |
| savedId.push(cast(void*)cast(size_t)v.ctfeAdrOnStack); |
| v.ctfeAdrOnStack = cast(uint)values.dim; |
| vars.push(v); |
| values.push(null); |
| } |
| |
| extern (C++) void pop(VarDeclaration v) |
| { |
| assert(!v.isDataseg() || v.isCTFE()); |
| assert(!v.isReference()); |
| const oldid = v.ctfeAdrOnStack; |
| v.ctfeAdrOnStack = cast(uint)cast(size_t)savedId[oldid]; |
| if (v.ctfeAdrOnStack == values.dim - 1) |
| { |
| values.pop(); |
| vars.pop(); |
| savedId.pop(); |
| } |
| } |
| |
| extern (C++) void 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 = cast(uint)cast(size_t)savedId[i]; |
| } |
| values.setDim(stackpointer); |
| vars.setDim(stackpointer); |
| savedId.setDim(stackpointer); |
| } |
| |
| extern (C++) void saveGlobalConstant(VarDeclaration v, Expression e) |
| { |
| assert(v._init && (v.isConst() || v.isImmutable() || v.storage_class & STC.manifest) && !v.isCTFE()); |
| v.ctfeAdrOnStack = cast(uint)globalValues.dim; |
| globalValues.push(copyRegionExp(e)); |
| } |
| } |
| |
| private 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; |
| } |
| |
| /************************************* |
| * Attempt to interpret a function given the arguments. |
| * Params: |
| * 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. |
| * |
| * Returns: |
| * result expression if successful, EXP.cantExpression if not, |
| * or CTFEExp if function returned void. |
| */ |
| private Expression interpretFunction(UnionExp* pue, FuncDeclaration fd, InterState* istate, Expressions* arguments, Expression thisarg) |
| { |
| debug (LOG) |
| { |
| printf("\n********\n%s FuncDeclaration::interpret(istate = %p) %s\n", fd.loc.toChars(), istate, fd.toChars()); |
| } |
| assert(pue); |
| if (fd.semanticRun == PASS.semantic3) |
| { |
| fd.error("circular dependency. Functions cannot be interpreted while being compiled"); |
| return CTFEExp.cantexp; |
| } |
| if (!fd.functionSemantic3()) |
| return CTFEExp.cantexp; |
| if (fd.semanticRun < PASS.semantic3done) |
| { |
| fd.error("circular dependency. Functions cannot be interpreted while being compiled"); |
| return CTFEExp.cantexp; |
| } |
| |
| auto tf = fd.type.toBasetype().isTypeFunction(); |
| if (tf.parameterList.varargs != VarArg.none && 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.toParentLocal().isFuncDeclaration() && !thisarg && istate) |
| thisarg = ctfeGlobals.stack.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. |
| 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[] |
| */ |
| Expressions eargs = Expressions(dim); |
| for (size_t i = 0; i < dim; i++) |
| { |
| Expression earg = (*arguments)[i]; |
| Parameter fparam = tf.parameterList[i]; |
| |
| if (fparam.isReference()) |
| { |
| if (!istate && (fparam.storageClass & STC.out_)) |
| { |
| // 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 = interpretRegion(earg, istate, CTFEGoal.LValue); |
| if (CTFEExp.isCantExp(earg)) |
| return earg; |
| } |
| else if (fparam.isLazy()) |
| { |
| } |
| else |
| { |
| /* Value parameters |
| */ |
| Type ta = fparam.type.toBasetype(); |
| if (ta.ty == Tsarray) |
| if (auto eaddr = earg.isAddrExp()) |
| { |
| /* Static arrays are passed by a simple pointer. |
| * Skip past this to get at the actual arg. |
| */ |
| earg = eaddr.e1; |
| } |
| |
| earg = interpretRegion(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 == EXP.structLiteral && !(fparam.storageClass & (STC.const_ | STC.immutable_))) |
| earg = copyLiteral(earg).copy(); |
| } |
| if (auto tee = earg.isThrownExceptionExp()) |
| { |
| if (istate) |
| return tee; |
| tee.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; |
| |
| if (fd.hasDualContext()) |
| { |
| Expression arg0 = thisarg; |
| if (arg0 && arg0.type.ty == Tstruct) |
| { |
| Type t = arg0.type.pointerTo(); |
| arg0 = ctfeEmplaceExp!AddrExp(arg0.loc, arg0); |
| arg0.type = t; |
| } |
| auto elements = new Expressions(2); |
| (*elements)[0] = arg0; |
| (*elements)[1] = ctfeGlobals.stack.getThis(); |
| Type t2 = Type.tvoidptr.sarrayOf(2); |
| const loc = thisarg ? thisarg.loc : fd.loc; |
| thisarg = ctfeEmplaceExp!ArrayLiteralExp(loc, t2, elements); |
| thisarg = ctfeEmplaceExp!AddrExp(loc, thisarg); |
| thisarg.type = t2.pointerTo(); |
| } |
| |
| ctfeGlobals.stack.startFrame(thisarg); |
| if (fd.vthis && thisarg) |
| { |
| ctfeGlobals.stack.push(fd.vthis); |
| setValue(fd.vthis, thisarg); |
| } |
| |
| for (size_t i = 0; i < dim; i++) |
| { |
| Expression earg = eargs[i]; |
| Parameter fparam = tf.parameterList[i]; |
| VarDeclaration v = (*fd.parameters)[i]; |
| debug (LOG) |
| { |
| printf("arg[%zu] = %s\n", i, earg.toChars()); |
| } |
| ctfeGlobals.stack.push(v); |
| |
| if (fparam.isReference() && earg.op == EXP.variable && |
| earg.isVarExp().var.toParent2() == fd) |
| { |
| VarDeclaration vx = earg.isVarExp().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. |
| */ |
| const oldadr = vx.ctfeAdrOnStack; |
| |
| ctfeGlobals.stack.push(vx); |
| assert(!hasValue(vx)); // vx is made uninitialized |
| |
| // https://issues.dlang.org/show_bug.cgi?id=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); |
| } |
| debug (LOG) |
| { |
| printf("interpreted arg[%zu] = %s\n", i, earg.toChars()); |
| showCtfeExpr(earg); |
| } |
| debug (LOGASSIGN) |
| { |
| printf("interpreted arg[%zu] = %s\n", i, earg.toChars()); |
| showCtfeExpr(earg); |
| } |
| } |
| |
| if (fd.vresult) |
| ctfeGlobals.stack.push(fd.vresult); |
| |
| // Enter the function |
| ++ctfeGlobals.callDepth; |
| if (ctfeGlobals.callDepth > ctfeGlobals.maxCallDepth) |
| ctfeGlobals.maxCallDepth = ctfeGlobals.callDepth; |
| |
| Expression e = null; |
| while (1) |
| { |
| if (ctfeGlobals.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 (CTFEExp.isCantExp(e)) |
| { |
| debug (LOG) |
| { |
| printf("function body failed to interpret\n"); |
| } |
| } |
| |
| 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 != EXP.continue_ && e.op != EXP.break_)); |
| break; |
| } |
| } |
| // If fell off the end of a void function, return void |
| if (!e) |
| { |
| if (tf.next.ty == Tvoid) |
| e = CTFEExp.voidexp; |
| else |
| { |
| /* missing a return statement can happen with C functions |
| * https://issues.dlang.org/show_bug.cgi?id=23056 |
| */ |
| fd.error("no return value from function"); |
| e = CTFEExp.cantexp; |
| } |
| } |
| |
| if (tf.isref && e.op == EXP.variable && e.isVarExp().var == fd.vthis) |
| e = thisarg; |
| if (tf.isref && fd.hasDualContext() && e.op == EXP.index) |
| { |
| auto ie = e.isIndexExp(); |
| auto pe = ie.e1.isPtrExp(); |
| auto ve = !pe ? null : pe.e1.isVarExp(); |
| if (ve && ve.var == fd.vthis) |
| { |
| auto ne = ie.e2.isIntegerExp(); |
| assert(ne); |
| auto ale = thisarg.isAddrExp().e1.isArrayLiteralExp(); |
| e = (*ale.elements)[cast(size_t)ne.getInteger()]; |
| if (auto ae = e.isAddrExp()) |
| { |
| e = ae.e1; |
| } |
| } |
| } |
| |
| // Leave the function |
| --ctfeGlobals.callDepth; |
| |
| ctfeGlobals.stack.endFrame(); |
| |
| // If it generated an uncaught exception, report error. |
| if (!istate && e.isThrownExceptionExp()) |
| { |
| if (e == pue.exp()) |
| e = pue.copy(); |
| e.isThrownExceptionExp().generateUncaughtError(); |
| e = CTFEExp.cantexp; |
| } |
| |
| return e; |
| } |
| |
| /// used to collect coverage information in ctfe |
| void incUsageCtfe(InterState* istate, const ref Loc loc) |
| { |
| if (global.params.ctfe_cov && istate) |
| { |
| auto line = loc.linnum; |
| auto mod = istate.fd.getModule(); |
| |
| ++mod.ctfe_cov[line]; |
| } |
| } |
| |
| private extern (C++) final class Interpreter : Visitor |
| { |
| alias visit = Visitor.visit; |
| public: |
| InterState* istate; |
| CTFEGoal goal; |
| Expression result; |
| UnionExp* pue; // storage for `result` |
| |
| extern (D) this(UnionExp* pue, InterState* istate, CTFEGoal goal) |
| { |
| this.pue = pue; |
| this.istate = istate; |
| this.goal = goal; |
| } |
| |
| // If e is EXP.throw_exception or EXP.cantExpression, |
| // 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 == EXP.cantExpression) ? CTFEExp.cantexp : e; |
| return true; |
| } |
| return false; |
| } |
| |
| static Expressions* copyArrayOnWrite(Expressions* exps, Expressions* original) |
| { |
| if (exps is original) |
| { |
| if (!original) |
| exps = new Expressions(); |
| else |
| exps = original.copy(); |
| ++ctfeGlobals.numArrayAllocs; |
| } |
| return exps; |
| } |
| |
| /******************************** Statement ***************************/ |
| |
| override void visit(Statement s) |
| { |
| debug (LOG) |
| { |
| printf("%s Statement::interpret()\n", s.loc.toChars()); |
| } |
| 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; |
| } |
| |
| override void visit(ExpStatement s) |
| { |
| debug (LOG) |
| { |
| printf("%s ExpStatement::interpret(%s)\n", s.loc.toChars(), s.exp ? s.exp.toChars() : ""); |
| } |
| if (istate.start) |
| { |
| if (istate.start != s) |
| return; |
| istate.start = null; |
| } |
| if (s.exp && s.exp.hasCode) |
| incUsageCtfe(istate, s.loc); |
| |
| Expression e = interpret(pue, s.exp, istate, CTFEGoal.Nothing); |
| if (exceptionOrCant(e)) |
| return; |
| } |
| |
| override void visit(CompoundStatement s) |
| { |
| debug (LOG) |
| { |
| printf("%s CompoundStatement::interpret()\n", s.loc.toChars()); |
| } |
| if (istate.start == s) |
| istate.start = null; |
| |
| const dim = s.statements ? s.statements.dim : 0; |
| foreach (i; 0 .. dim) |
| { |
| Statement sx = (*s.statements)[i]; |
| result = interpret(pue, sx, istate); |
| if (result) |
| break; |
| } |
| debug (LOG) |
| { |
| printf("%s -CompoundStatement::interpret() %p\n", s.loc.toChars(), result); |
| } |
| } |
| |
| override void visit(UnrolledLoopStatement s) |
| { |
| debug (LOG) |
| { |
| printf("%s UnrolledLoopStatement::interpret()\n", s.loc.toChars()); |
| } |
| if (istate.start == s) |
| istate.start = null; |
| |
| const dim = s.statements ? s.statements.dim : 0; |
| foreach (i; 0 .. dim) |
| { |
| Statement sx = (*s.statements)[i]; |
| Expression e = interpret(pue, sx, istate); |
| if (!e) // succeeds to interpret, or goto target was not found |
| continue; |
| if (exceptionOrCant(e)) |
| return; |
| if (e.op == EXP.break_) |
| { |
| if (istate.gotoTarget && istate.gotoTarget != s) |
| { |
| result = e; // break at a higher level |
| return; |
| } |
| istate.gotoTarget = null; |
| result = null; |
| return; |
| } |
| if (e.op == EXP.continue_) |
| { |
| 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; |
| } |
| } |
| |
| override void visit(IfStatement s) |
| { |
| debug (LOG) |
| { |
| printf("%s IfStatement::interpret(%s)\n", s.loc.toChars(), s.condition.toChars()); |
| } |
| incUsageCtfe(istate, s.loc); |
| 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 = void; |
| 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.toBool().hasValue(false)) |
| result = interpret(pue, s.elsebody, istate); |
| else |
| { |
| // no error, or assert(0)? |
| result = CTFEExp.cantexp; |
| } |
| } |
| |
| override void visit(ScopeStatement s) |
| { |
| debug (LOG) |
| { |
| printf("%s ScopeStatement::interpret()\n", s.loc.toChars()); |
| } |
| 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(const ref Loc loc, Expression e) |
| { |
| if (!e.type.hasPointers()) |
| return true; |
| if (isPointer(e.type)) |
| { |
| Expression x = e; |
| if (auto eaddr = e.isAddrExp()) |
| x = eaddr.e1; |
| VarDeclaration v; |
| while (x.op == EXP.variable && (v = x.isVarExp().var.isVarDeclaration()) !is null) |
| { |
| if (v.storage_class & STC.ref_) |
| { |
| x = getValue(v); |
| if (auto eaddr = e.isAddrExp()) |
| eaddr.e1 = x; |
| continue; |
| } |
| if (ctfeGlobals.stack.isInCurrentFrame(v)) |
| { |
| error(loc, "returning a pointer to a local stack variable"); |
| return false; |
| } |
| else |
| break; |
| } |
| // TODO: If it is a EXP.dotVariable or EXP.index, we should check that it is not |
| // pointing to a local struct or static array. |
| } |
| if (auto se = e.isStructLiteralExp()) |
| { |
| return stopPointersEscapingFromArray(loc, se.elements); |
| } |
| if (auto ale = e.isArrayLiteralExp()) |
| { |
| return stopPointersEscapingFromArray(loc, ale.elements); |
| } |
| if (auto aae = e.isAssocArrayLiteralExp()) |
| { |
| if (!stopPointersEscapingFromArray(loc, aae.keys)) |
| return false; |
| return stopPointersEscapingFromArray(loc, aae.values); |
| } |
| return true; |
| } |
| |
| // Check all elements of an array for escaping local variables. Return false if error |
| static bool stopPointersEscapingFromArray(const ref Loc loc, Expressions* elems) |
| { |
| foreach (e; *elems) |
| { |
| if (e && !stopPointersEscaping(loc, e)) |
| return false; |
| } |
| return true; |
| } |
| |
| override void visit(ReturnStatement s) |
| { |
| debug (LOG) |
| { |
| printf("%s ReturnStatement::interpret(%s)\n", s.loc.toChars(), s.exp ? s.exp.toChars() : ""); |
| } |
| if (istate.start) |
| { |
| if (istate.start != s) |
| return; |
| istate.start = null; |
| } |
| |
| if (!s.exp) |
| { |
| result = CTFEExp.voidexp; |
| return; |
| } |
| |
| incUsageCtfe(istate, s.loc); |
| assert(istate && istate.fd && istate.fd.type && istate.fd.type.ty == Tfunction); |
| TypeFunction tf = cast(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, CTFEGoal.LValue); |
| 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 EXP.symbolOffset can be used to |
| // return a value OR a pointer |
| Expression e = interpret(pue, s.exp, istate); |
| if (exceptionOrCant(e)) |
| return; |
| |
| /** |
| * Interpret `return a ~= b` (i.e. `return _d_arrayappendT{,Trace}(a, b)`) as: |
| * a ~= b; |
| * return a; |
| * This is needed because `a ~= b` has to be interpreted as an lvalue, in order to avoid |
| * assigning a larger array into a smaller one, such as: |
| * `a = [1, 2], a ~= [3]` => `[1, 2] ~= [3]` => `[1, 2] = [1, 2, 3]` |
| */ |
| if (isRuntimeHook(s.exp, Id._d_arrayappendT) || isRuntimeHook(s.exp, Id._d_arrayappendTTrace)) |
| { |
| auto rs = new ReturnStatement(s.loc, e); |
| rs.accept(this); |
| 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(); |
| debug (LOGASSIGN) |
| { |
| printf("RETURN %s\n", s.loc.toChars()); |
| showCtfeExpr(e); |
| } |
| 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; |
| } |
| |
| override void visit(BreakStatement s) |
| { |
| debug (LOG) |
| { |
| printf("%s BreakStatement::interpret()\n", s.loc.toChars()); |
| } |
| incUsageCtfe(istate, s.loc); |
| if (istate.start) |
| { |
| if (istate.start != s) |
| return; |
| istate.start = null; |
| } |
| |
| istate.gotoTarget = findGotoTarget(istate, s.ident); |
| result = CTFEExp.breakexp; |
| } |
| |
| override void visit(ContinueStatement s) |
| { |
| debug (LOG) |
| { |
| printf("%s ContinueStatement::interpret()\n", s.loc.toChars()); |
| } |
| incUsageCtfe(istate, s.loc); |
| if (istate.start) |
| { |
| if (istate.start != s) |
| return; |
| istate.start = null; |
| } |
| |
| istate.gotoTarget = findGotoTarget(istate, s.ident); |
| result = CTFEExp.continueexp; |
| } |
| |
| override void visit(WhileStatement s) |
| { |
| debug (LOG) |
| { |
| printf("WhileStatement::interpret()\n"); |
| } |
| assert(0); // rewritten to ForStatement |
| } |
| |
| override void visit(DoStatement s) |
| { |
| debug (LOG) |
| { |
| printf("%s DoStatement::interpret()\n", s.loc.toChars()); |
| } |
| 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 == EXP.break_) |
| { |
| if (istate.gotoTarget && istate.gotoTarget != s) |
| { |
| result = e; // break at a higher level |
| return; |
| } |
| istate.gotoTarget = null; |
| break; |
| } |
| if (e && e.op == EXP.continue_) |
| { |
| 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 = void; |
| incUsageCtfe(istate, s.condition.loc); |
| e = interpret(&ue, s.condition, istate); |
| if (exceptionOrCant(e)) |
| return; |
| if (!e.isConst()) |
| { |
| result = CTFEExp.cantexp; |
| return; |
| } |
| if (e.toBool().hasValue(false)) |
| break; |
| assert(isTrueBool(e)); |
| } |
| assert(result is null); |
| } |
| |
| override void visit(ForStatement s) |
| { |
| debug (LOG) |
| { |
| printf("%s ForStatement::interpret()\n", s.loc.toChars()); |
| } |
| if (istate.start == s) |
| istate.start = null; |
| |
| UnionExp ueinit = void; |
| 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 = void; |
| incUsageCtfe(istate, s.condition.loc); |
| Expression e = interpret(&ue, s.condition, istate); |
| if (exceptionOrCant(e)) |
| return; |
| if (e.toBool().hasValue(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 == EXP.break_) |
| { |
| if (istate.gotoTarget && istate.gotoTarget != s) |
| { |
| result = e; // break at a higher level |
| return; |
| } |
| istate.gotoTarget = null; |
| break; |
| } |
| if (e && e.op == EXP.continue_) |
| { |
| 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 = void; |
| if (s.increment) |
| incUsageCtfe(istate, s.increment.loc); |
| e = interpret(&uei, s.increment, istate, CTFEGoal.Nothing); |
| if (exceptionOrCant(e)) |
| return; |
| } |
| assert(result is null); |
| } |
| |
| override void visit(ForeachStatement s) |
| { |
| assert(0); // rewritten to ForStatement |
| } |
| |
| override void visit(ForeachRangeStatement s) |
| { |
| assert(0); // rewritten to ForStatement |
| } |
| |
| override void visit(SwitchStatement s) |
| { |
| debug (LOG) |
| { |
| printf("%s SwitchStatement::interpret()\n", s.loc.toChars()); |
| } |
| incUsageCtfe(istate, s.loc); |
| 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 == EXP.break_) |
| { |
| if (istate.gotoTarget && istate.gotoTarget != s) |
| { |
| result = e; // break at a higher level |
| return; |
| } |
| istate.gotoTarget = null; |
| e = null; |
| } |
| result = e; |
| return; |
| } |
| |
| UnionExp uecond = void; |
| Expression econdition = interpret(&uecond, s.condition, istate); |
| if (exceptionOrCant(econdition)) |
| return; |
| |
| Statement scase = null; |
| if (s.cases) |
| foreach (cs; *s.cases) |
| { |
| UnionExp uecase = void; |
| Expression ecase = interpret(&uecase, cs.exp, istate); |
| if (exceptionOrCant(ecase)) |
| return; |
| if (ctfeEqual(cs.exp.loc, EXP.equal, 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 == EXP.break_) |
| { |
| if (istate.gotoTarget && istate.gotoTarget != s) |
| { |
| result = e; // break at a higher level |
| return; |
| } |
| istate.gotoTarget = null; |
| e = null; |
| } |
| result = e; |
| } |
| |
| override void visit(CaseStatement s) |
| { |
| debug (LOG) |
| { |
| printf("%s CaseStatement::interpret(%s) this = %p\n", s.loc.toChars(), s.exp.toChars(), s); |
| } |
| incUsageCtfe(istate, s.loc); |
| if (istate.start == s) |
| istate.start = null; |
| |
| result = interpret(pue, s.statement, istate); |
| } |
| |
| override void visit(DefaultStatement s) |
| { |
| debug (LOG) |
| { |
| printf("%s DefaultStatement::interpret()\n", s.loc.toChars()); |
| } |
| incUsageCtfe(istate, s.loc); |
| if (istate.start == s) |
| istate.start = null; |
| |
| result = interpret(pue, s.statement, istate); |
| } |
| |
| override void visit(GotoStatement s) |
| { |
| debug (LOG) |
| { |
| printf("%s GotoStatement::interpret()\n", s.loc.toChars()); |
| } |
| if (istate.start) |
| { |
| if (istate.start != s) |
| return; |
| istate.start = null; |
| } |
| incUsageCtfe(istate, s.loc); |
| |
| assert(s.label && s.label.statement); |
| istate.gotoTarget = s.label.statement; |
| result = CTFEExp.gotoexp; |
| } |
| |
| override void visit(GotoCaseStatement s) |
| { |
| debug (LOG) |
| { |
| printf("%s GotoCaseStatement::interpret()\n", s.loc.toChars()); |
| } |
| if (istate.start) |
| { |
| if (istate.start != s) |
| return; |
| istate.start = null; |
| } |
| incUsageCtfe(istate, s.loc); |
| |
| assert(s.cs); |
| istate.gotoTarget = s.cs; |
| result = CTFEExp.gotoexp; |
| } |
| |
| override void visit(GotoDefaultStatement s) |
| { |
| debug (LOG) |
| { |
| printf("%s GotoDefaultStatement::interpret()\n", s.loc.toChars()); |
| } |
| if (istate.start) |
| { |
| if (istate.start != s) |
| return; |
| istate.start = null; |
| } |
| incUsageCtfe(istate, s.loc); |
| |
| assert(s.sw && s.sw.sdefault); |
| istate.gotoTarget = s.sw.sdefault; |
| result = CTFEExp.gotoexp; |
| } |
| |
| override void visit(LabelStatement s) |
| { |
| debug (LOG) |
| { |
| printf("%s LabelStatement::interpret()\n", s.loc.toChars()); |
| } |
| if (istate.start == s) |
| istate.start = null; |
| |
| result = interpret(pue, s.statement, istate); |
| } |
| |
| override void visit(TryCatchStatement s) |
| { |
| debug (LOG) |
| { |
| printf("%s TryCatchStatement::interpret()\n", s.loc.toChars()); |
| } |
| if (istate.start == s) |
| istate.start = null; |
| if (istate.start) |
| { |
| Expression e = null; |
| e = interpret(pue, s._body, istate); |
| foreach (ca; *s.catches) |
| { |
| if (e || !istate.start) // goto target was found |
| break; |
| e = interpret(pue, ca.handler, istate); |
| } |
| result = e; |
| return; |
| } |
| |
| Expression e = interpret(s._body, istate); |
| |
| // An exception was thrown |
| if (e && e.isThrownExceptionExp()) |
| { |
| ThrownExceptionExp ex = e.isThrownExceptionExp(); |
| Type extype = ex.thrown.originalClass().type; |
| |
| // Search for an appropriate catch clause. |
| foreach (ca; *s.catches) |
| { |
| Type catype = ca.type; |
| if (!catype.equals(extype) && !catype.isBaseOf(extype, null)) |
| continue; |
| |
| // Execute the handler |
| if (ca.var) |
| { |
| ctfeGlobals.stack.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) |
| { |
| debug (LOG) |
| { |
| printf("Collided exceptions %s %s\n", oldest.thrown.toChars(), newest.thrown.toChars()); |
| } |
| // Little sanity check to make sure it's really a Throwable |
| ClassReferenceExp boss = oldest.thrown; |
| const 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 |
| */ |
| auto 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 == EXP.classReference) |
| { |
| boss = (*boss.value.elements)[next].isClassReferenceExp(); |
| } |
| (*boss.value.elements)[next] = collateral; |
| return oldest; |
| } |
| |
| override void visit(TryFinallyStatement s) |
| { |
| debug (LOG) |
| { |
| printf("%s TryFinallyStatement::interpret()\n", s.loc.toChars()); |
| } |
| 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.isThrownExceptionExp()) |
| { |
| // Check for collided exceptions |
| if (ex && ex.isThrownExceptionExp()) |
| ex = chainExceptions(ex.isThrownExceptionExp(), ey.isThrownExceptionExp()); |
| else |
| ex = ey; |
| } |
| result = ex; |
| } |
| |
| override void visit(ThrowStatement s) |
| { |
| debug (LOG) |
| { |
| printf("%s ThrowStatement::interpret()\n", s.loc.toChars()); |
| } |
| if (istate.start) |
| { |
| if (istate.start != s) |
| return; |
| istate.start = null; |
| } |
| |
| interpretThrow(s.exp, s.loc); |
| } |
| |
| /// Interpret `throw <exp>` found at the specified location `loc` |
| private void interpretThrow(Expression exp, const ref Loc loc) |
| { |
| incUsageCtfe(istate, loc); |
| |
| Expression e = interpretRegion(exp, istate); |
| if (exceptionOrCant(e)) |
| return; |
| |
| assert(e.op == EXP.classReference); |
| result = ctfeEmplaceExp!ThrownExceptionExp(loc, e.isClassReferenceExp()); |
| } |
| |
| override void visit(ScopeGuardStatement s) |
| { |
| assert(0); |
| } |
| |
| override void visit(WithStatement s) |
| { |
| debug (LOG) |
| { |
| printf("%s WithStatement::interpret()\n", s.loc.toChars()); |
| } |
| 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 == EXP.scope_ || s.exp.op == EXP.type) |
| { |
| result = interpret(pue, s._body, istate); |
| return; |
| } |
| |
| incUsageCtfe(istate, s.loc); |
| |
| Expression e = interpret(s.exp, istate); |
| if (exceptionOrCant(e)) |
| return; |
| |
| if (s.wthis.type.ty == Tpointer && s.exp.type.ty != Tpointer) |
| { |
| e = ctfeEmplaceExp!AddrExp(s.loc, e, s.wthis.type); |
| } |
| ctfeGlobals.stack.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; |
| } |
| } |
| ctfeGlobals.stack.pop(s.wthis); |
| result = e; |
| } |
| |
| override void visit(AsmStatement s) |
| { |
| debug (LOG) |
| { |
| printf("%s AsmStatement::interpret()\n", s.loc.toChars()); |
| } |
| if (istate.start) |
| { |
| if (istate.start != s) |
| return; |
| istate.start = null; |
| } |
| s.error("`asm` statements cannot be interpreted at compile time"); |
| result = CTFEExp.cantexp; |
| } |
| |
| override void visit(ImportStatement s) |
| { |
| debug (LOG) |
| { |
| printf("ImportStatement::interpret()\n"); |
| } |
| if (istate.start) |
| { |
| if (istate.start != s) |
| return; |
| istate.start = null; |
| } |
| } |
| |
| /******************************** Expression ***************************/ |
| |
| override void visit(Expression e) |
| { |
| debug (LOG) |
| { |
| printf("%s Expression::interpret() '%s' %s\n", e.loc.toChars(), EXPtoString(e.op).ptr, e.toChars()); |
| printf("type = %s\n", e.type.toChars()); |
| showCtfeExpr(e); |
| } |
| e.error("cannot interpret `%s` at compile time", e.toChars()); |
| result = CTFEExp.cantexp; |
| } |
| |
| override void visit(TypeExp e) |
| { |
| debug (LOG) |
| { |
| printf("%s TypeExp.interpret() %s\n", e.loc.toChars(), e.toChars()); |
| } |
| result = e; |
| } |
| |
| override void visit(ThisExp e) |
| { |
| debug (LOG) |
| { |
| printf("%s ThisExp::interpret() %s\n", e.loc.toChars(), e.toChars()); |
| } |
| if (goal == CTFEGoal.LValue) |
| { |
| // We might end up here with istate being zero |
| // https://issues.dlang.org/show_bug.cgi?id=16382 |
| if (istate && istate.fd.vthis) |
| { |
| result = ctfeEmplaceExp!VarExp(e.loc, istate.fd.vthis); |
| if (istate.fd.hasDualContext()) |
| { |
| result = ctfeEmplaceExp!PtrExp(e.loc, result); |
| result.type = Type.tvoidptr.sarrayOf(2); |
| result = ctfeEmplaceExp!IndexExp(e.loc, result, IntegerExp.literal!0); |
| } |
| result.type = e.type; |
| } |
| else |
| result = e; |
| return; |
| } |
| |
| result = ctfeGlobals.stack.getThis(); |
| if (result) |
| { |
| if (istate && istate.fd.hasDualContext()) |
| { |
| assert(result.op == EXP.address); |
| result = result.isAddrExp().e1; |
| assert(result.op == EXP.arrayLiteral); |
| result = (*result.isArrayLiteralExp().elements)[0]; |
| if (e.type.ty == Tstruct) |
| { |
| result = result.isAddrExp().e1; |
| } |
| return; |
| } |
| assert(result.op == EXP.structLiteral || result.op == EXP.classReference || result.op == EXP.type); |
| return; |
| } |
| e.error("value of `this` is not known at compile time"); |
| result = CTFEExp.cantexp; |
| } |
| |
| override void visit(NullExp e) |
| { |
| result = e; |
| } |
| |
| override void visit(IntegerExp e) |
| { |
| debug (LOG) |
| { |
| printf("%s IntegerExp::interpret() %s\n", e.loc.toChars(), e.toChars()); |
| } |
| result = e; |
| } |
| |
| override void visit(RealExp e) |
| { |
| debug (LOG) |
| { |
| printf("%s RealExp::interpret() %s\n", e.loc.toChars(), e.toChars()); |
| } |
| result = e; |
| } |
| |
| override void visit(ComplexExp e) |
| { |
| result = e; |
| } |
| |
| override void visit(StringExp e) |
| { |
| debug (LOG) |
| { |
| printf("%s StringExp::interpret() %s\n", e.loc.toChars(), e.toChars()); |
| } |
| /* Attempts to modify string literals are prevented |
| * in BinExp::interpretAssignCommon. |
| */ |
| result = e; |
| } |
| |
| override void visit(FuncExp e) |
| { |
| debug (LOG) |
| { |
| printf("%s FuncExp::interpret() %s\n", e.loc.toChars(), e.toChars()); |
| } |
| result = e; |
| } |
| |
| override void visit(SymOffExp e) |
| { |
| debug (LOG) |
| { |
| printf("%s SymOffExp::interpret() %s\n", e.loc.toChars(), e.toChars()); |
| } |
| 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 = (cast(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 = (cast(TypeArray)e.var.type).next; |
| } |
| if (e.var.isDataseg() && ((e.offset == 0 && isSafePointerCast(e.var.type, pointee)) || |
| (fromType && isSafePointerCast(fromType, pointee)) || |
| (e.var.isCsymbol() && e.offset + pointee.size() <= e.var.type.size()))) |
| { |
| 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 = (cast(TypeArray)val.type).next; |
| const 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 = cast(size_t)(cast(TypeSArray)pointee).dim.toInteger(); |
| Expression elwr = ctfeEmplaceExp!IntegerExp(e.loc, e.offset / elemsize, Type.tsize_t); |
| Expression eupr = ctfeEmplaceExp!IntegerExp(e.loc, e.offset / elemsize + d, Type.tsize_t); |
| |
| // Create a CTFE pointer &val[ofs..ofs+d] |
| auto se = ctfeEmplaceExp!SliceExp(e.loc, val, elwr, eupr); |
| se.type = pointee; |
| emplaceExp!(AddrExp)(pue, 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 |
| auto ve = ctfeEmplaceExp!VarExp(e.loc, e.var); |
| ve.type = elemtype; |
| emplaceExp!(AddrExp)(pue, 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 == EXP.arrayLiteral || val.op == EXP.string_) |
| { |
| aggregate = val; |
| } |
| else if (auto se = val.isSliceExp()) |
| { |
| aggregate = se.e1; |
| UnionExp uelwr = void; |
| Expression lwr = interpret(&uelwr, se.lwr, istate); |
| indx += lwr.toInteger(); |
| } |
| if (aggregate) |
| { |
| // Create a CTFE pointer &aggregate[ofs] |
| auto ofs = ctfeEmplaceExp!IntegerExp(e.loc, indx, Type.tsize_t); |
| auto ei = ctfeEmplaceExp!IndexExp(e.loc, aggregate, ofs); |
| ei.type = elemtype; |
| emplaceExp!(AddrExp)(pue, e.loc, ei, e.type); |
| result = pue.exp(); |
| return; |
| } |
| } |
| else if (e.offset == 0 && isSafePointerCast(e.var.type, pointee)) |
| { |
| // Create a CTFE pointer &var |
| auto ve = ctfeEmplaceExp!VarExp(e.loc, e.var); |
| ve.type = e.var.type; |
| emplaceExp!(AddrExp)(pue, 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; |
| } |
| |
| override void visit(AddrExp e) |
| { |
| debug (LOG) |
| { |
| printf("%s AddrExp::interpret() %s\n", e.loc.toChars(), e.toChars()); |
| } |
| if (auto ve = e.e1.isVarExp()) |
| { |
| Declaration decl = ve.var; |
| |
| // We cannot take the address of an imported symbol at compile time |
| if (decl.isImportedSymbol()) { |
| e.error("cannot take address of imported symbol `%s` at compile time", decl.toChars()); |
| result = CTFEExp.cantexp; |
| return; |
| } |
| |
| if (decl.isDataseg()) { |
| // Normally this is already done by optimize() |
| // Do it here in case optimize(WANTvalue) wasn't run before CTFE |
| emplaceExp!(SymOffExp)(pue, e.loc, e.e1.isVarExp().var, 0); |
| result = pue.exp(); |
| result.type = e.type; |
| return; |
| } |
| } |
| auto er = interpret(e.e1, istate, CTFEGoal.LValue); |
| if (auto ve = er.isVarExp()) |
| if (ve.var == istate.fd.vthis) |
| er = interpret(er, istate); |
| |
| if (exceptionOrCant(er)) |
| return; |
| |
| // Return a simplified address expression |
| emplaceExp!(AddrExp)(pue, e.loc, er, e.type); |
| result = pue.exp(); |
| } |
| |
| override void visit(DelegateExp e) |
| { |
| debug (LOG) |
| { |
| printf("%s DelegateExp::interpret() %s\n", e.loc.toChars(), e.toChars()); |
| } |
| // 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 (auto ve1 = e.e1.isVarExp()) |
| if (ve1.var == e.func) |
| { |
| result = e; |
| return; |
| } |
| |
| auto 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; |
| emplaceExp!(DelegateExp)(pue, e.loc, er, e.func, false); |
| result = pue.exp(); |
| result.type = e.type; |
| } |
| } |
| |
| static Expression getVarExp(const ref 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 IntegerExp.createBool(true); |
| |
| if (!v.originalType && v.semanticRun < PASS.semanticdone) // semantic() not yet run |
| { |
| v.dsymbolSemantic(null); |
| if (v.type.ty == Terror) |
| return CTFEExp.cantexp; |
| } |
| |
| if ((v.isConst() || v.isImmutable() || v.storage_class & STC.manifest) && !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 = v._init.initializerSemantic(v._scope, v.type, INITinterpret); // might not be run on aggregate members |
| v.inuse--; |
| } |
| e = v._init.initializerToExpression(v.type); |
| if (!e) |
| return CTFEExp.cantexp; |
| assert(e.type); |
| |
| if (e.op == EXP.construct || e.op == EXP.blit) |
| { |
| AssignExp ae = cast(AssignExp)e; |
| e = ae.e2; |
| } |
| |
| if (e.op == EXP.error) |
| { |
| // FIXME: Ultimately all errors should be detected in prior semantic analysis stage. |
| } |
| else if (v.isDataseg() || (v.storage_class & STC.manifest)) |
| { |
| /* https://issues.dlang.org/show_bug.cgi?id=14304 |
| * e is a value that is not yet owned by CTFE. |
| * Mark as "cached", and use it directly during interpretation. |
| */ |
| e = scrubCacheValue(e); |
| ctfeGlobals.stack.saveGlobalConstant(v, e); |
| } |
| else |
| { |
| v.inuse++; |
| e = interpret(e, istate); |
| v.inuse--; |
| if (CTFEExp.isCantExp(e) && !global.gag && !ctfeGlobals.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); |
| } |
| e = v._init.initializerToExpression(); |
| } |
| else |
| // Zero-length arrays don't have an initializer |
| e = v.type.defaultInitLiteral(e.loc); |
| |
| e = interpret(e, istate); |
| } |
| else if (!(v.isDataseg() || v.storage_class & STC.manifest) && !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) |
| { |
| // Zero-length arrays don't have an initializer |
| if (v.type.size() == 0) |
| e = v.type.defaultInitLiteral(loc); |
| else if (!v.isCTFE() && v.isDataseg()) |
| { |
| error(loc, "static variable `%s` cannot be read at compile time", v.toChars()); |
| return CTFEExp.cantexp; |
| } |
| else |
| { |
| 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 (auto vie = e.isVoidInitExp()) |
| { |
| error(loc, "cannot read uninitialized variable `%s` in ctfe", v.toPrettyChars()); |
| errorSupplemental(vie.var.loc, "`%s` was uninitialized and used before set", vie.var.toChars()); |
| return CTFEExp.cantexp; |
| } |
| if (goal != CTFEGoal.LValue && v.isReference()) |
| e = interpret(e, istate, goal); |
| } |
| if (!e) |
| e = CTFEExp.cantexp; |
| } |
| else if (SymbolDeclaration s = d.isSymbolDeclaration()) |
| { |
| // exclude void[]-typed `__traits(initSymbol)` |
| if (auto ta = s.type.toBasetype().isTypeDArray()) |
| { |
| assert(ta.next.ty == Tvoid); |
| error(loc, "cannot determine the address of the initializer symbol during CTFE"); |
| return CTFEExp.cantexp; |
| } |
| |
| // Struct static initializers, for example |
| e = s.dsym.type.defaultInitLiteral(loc); |
| if (e.op == EXP.error) |
| error(loc, "CTFE failed because of previous errors in `%s.init`", s.toChars()); |
| e = e.expressionSemantic(null); |
| if (e.op == EXP.error) |
| 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; |
| } |
| |
| override void visit(VarExp e) |
| { |
| debug (LOG) |
| { |
| printf("%s VarExp::interpret() `%s`, goal = %d\n", e.loc.toChars(), e.toChars(), goal); |
| } |
| if (e.var.isFuncDeclaration()) |
| { |
| result = e; |
| return; |
| } |
| |
| if (goal == CTFEGoal.LValue) |
| { |
| if (auto v = e.var.isVarDeclaration()) |
| { |
| if (!hasValue(v)) |
| { |
| // Compile-time known non-CTFE variable from an outer context |
| // e.g. global or from a ref argument |
| if (v.isConst() || v.isImmutable()) |
| { |
| result = getVarExp(e.loc, istate, v, goal); |
| return; |
| } |
| |
| 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.storage_class & (STC.out_ | STC.ref_)) |
| { |
| // Strip off the nest of ref variables |
| Expression ev = getValue(v); |
| if (ev.op == EXP.variable || |
| ev.op == EXP.index || |
| (ev.op == EXP.slice && ev.type.toBasetype().ty == Tsarray) || |
| ev.op == EXP.dotVariable) |
| { |
| result = interpret(pue, ev, istate, goal); |
| return; |
| } |
| } |
| } |
| result = e; |
| return; |
| } |
| result = getVarExp(e.loc, istate, e.var, goal); |
| if (exceptionOrCant(result)) |
| return; |
| |
| // Visit the default initializer for noreturn variables |
| // (Custom initializers would abort the current function call and exit above) |
| if (result.type.ty == Tnoreturn) |
| { |
| result.accept(this); |
| return; |
| } |
| |
| if ((e.var.storage_class & (STC.ref_ | STC.out_)) == 0 && e.type.baseElemOf().ty != Tstruct) |
| { |
| /* Ultimately, STC.ref_|STC.out_ 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); |
| } |
| } |
| |
| override void visit(DeclarationExp e) |
| { |
| debug (LOG) |
| { |
| printf("%s DeclarationExp::interpret() %s\n", e.loc.toChars(), e.toChars()); |
| } |
| Dsymbol s = e.declaration; |
| while (s.isAttribDeclaration()) |
| { |
| auto ad = cast(AttribDeclaration)s; |
| assert(ad.decl && ad.decl.dim == 1); // Currently, only one allowed when parsing |
| s = (*ad.decl)[0]; |
| } |
| if (VarDeclaration v = s.isVarDeclaration()) |
| { |
| if (TupleDeclaration td = v.toAlias().isTupleDeclaration()) |
| { |
| result = null; |
| |
| // Reserve stack space for all tuple members |
| td.foreachVar((s) |
| { |
| VarDeclaration v2 = s.isVarDeclaration(); |
| assert(v2); |
| if (v2.isDataseg() && !v2.isCTFE()) |
| return 0; |
| |
| ctfeGlobals.stack.push(v2); |
| if (v2._init) |
| { |
| Expression einit; |
| if (ExpInitializer ie = v2._init.isExpInitializer()) |
| { |
| einit = interpretRegion(ie.exp, istate, goal); |
| if (exceptionOrCant(einit)) |
| return 1; |
| } |
| 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 1; |
| } |
| setValue(v2, einit); |
| } |
| return 0; |
| }); |
| return; |
| } |
| if (v.isStatic()) |
| { |
| // Just ignore static variables which aren't read or written yet |
| result = null; |
| return; |
| } |
| if (!(v.isDataseg() || v.storage_class & STC.manifest) || v.isCTFE()) |
| ctfeGlobals.stack.push(v); |
| if (v._init) |
| { |
| if (ExpInitializer ie = v._init.isExpInitializer()) |
| { |
| result = interpretRegion(ie.exp, istate, goal); |
| return; |
| } |
| 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); |
| return; |
| } |
| else if (v._init.isArrayInitializer()) |
| { |
| result = v._init.initializerToExpression(v.type); |
| if (result !is null) |
| return; |
| } |
| 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.isTemplateMixin() || s.isTupleDeclaration()) |
| { |
| // 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; |
| debug (LOG) |
| { |
| printf("-DeclarationExp::interpret(%s): %p\n", e.toChars(), result); |
| } |
| } |
| |
| override void visit(TypeidExp e) |
| { |
| debug (LOG) |
| { |
| printf("%s TypeidExp::interpret() %s\n", e.loc.toChars(), e.toChars()); |
| } |
| if (Type t = isType(e.obj)) |
| { |
| result = e; |
| return; |
| } |
| if (Expression ex = isExpression(e.obj)) |
| { |
| result = interpret(pue, ex, istate); |
| if (exceptionOrCant(ex)) |
| return; |
| |
| if (result.op == EXP.null_) |
| { |
| e.error("null pointer dereference evaluating typeid. `%s` is `null`", ex.toChars()); |
| result = CTFEExp.cantexp; |
| return; |
| } |
| if (result.op != EXP.classReference) |
| { |
| e.error("CTFE internal error: determining classinfo"); |
| result = CTFEExp.cantexp; |
| return; |
| } |
| |
| ClassDeclaration cd = result.isClassReferenceExp().originalClass(); |
| assert(cd); |
| |
| emplaceExp!(TypeidExp)(pue, e.loc, cd.type); |
| result = pue.exp(); |
| result.type = e.type; |
| return; |
| } |
| visit(cast(Expression)e); |
| } |
| |
| override void visit(TupleExp e) |
| { |
| debug (LOG) |
| { |
| printf("%s TupleExp::interpret() %s\n", e.loc.toChars(), e.toChars()); |
| } |
| if (exceptionOrCant(interpretRegion(e.e0, istate, CTFEGoal.Nothing))) |
| return; |
| |
| auto expsx = e.exps; |
| foreach (i, exp; *expsx) |
| { |
| Expression ex = interpretRegion(exp, istate); |
| if (exceptionOrCant(ex)) |
| return; |
| |
| // A tuple of assignments can contain void (Bug 5676). |
| if (goal == CTFEGoal.Nothing) |
| continue; |
| if (ex.op == EXP.voidExpression) |
| { |
| e.error("CTFE internal error: void element `%s` in tuple", exp.toChars()); |
| assert(0); |
| } |
| |
| /* If any changes, do Copy On Write |
| */ |
| if (ex !is exp) |
| { |
| expsx = copyArrayOnWrite(expsx, e.exps); |
| (*expsx)[i] = copyRegionExp(ex); |
| } |
| } |
| |
| if (expsx !is e.exps) |
| { |
| expandTuples(expsx); |
| emplaceExp!(TupleExp)(pue, e.loc, expsx); |
| result = pue.exp(); |
| result.type = new TypeTuple(expsx); |
| } |
| else |
| result = e; |
| } |
| |
| override void visit(ArrayLiteralExp e) |
| { |
| debug (LOG) |
| { |
| printf("%s ArrayLiteralExp::interpret() %s\n", e.loc.toChars(), e.toChars()); |
| } |
| if (e.ownedByCtfe >= OwnedBy.ctfe) // 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); |
| |
| auto basis = interpretRegion(e.basis, istate); |
| if (exceptionOrCant(basis)) |
| return; |
| |
| auto 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 != EXP.index || exp.isIndexExp().e1 != e); |
| |
| ex = interpretRegion(exp, istate); |
| if (exceptionOrCant(ex)) |
| return; |
| |
| /* Each elements should have distinct CTFE 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 !is exp) |
| { |
| expsx = copyArrayOnWrite(expsx, e.elements); |
| (*expsx)[i] = ex; |
| } |
| } |
| |
| if (expsx !is 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; |
| } |
| emplaceExp!(ArrayLiteralExp)(pue, e.loc, e.type, basis, expsx); |
| auto ale = pue.exp().isArrayLiteralExp(); |
| ale.ownedByCtfe = OwnedBy.ctfe; |
| result = ale; |
| } |
| else if ((cast(TypeNext)e.type).next.mod & (MODFlags.const_ | MODFlags.immutable_)) |
| { |
| // If it's immutable, we don't need to dup it |
| result = e; |
| } |
| else |
| { |
| *pue = copyLiteral(e); |
| result = pue.exp(); |
| } |
| } |
| |
| override void visit(AssocArrayLiteralExp e) |
| { |
| debug (LOG) |
| { |
| printf("%s AssocArrayLiteralExp::interpret() %s\n", e.loc.toChars(), e.toChars()); |
| } |
| if (e.ownedByCtfe >= OwnedBy.ctfe) // We've already interpreted all the elements |
| { |
| result = e; |
| return; |
| } |
| |
| auto keysx = e.keys; |
| auto valuesx = e.values; |
| foreach (i, ekey; *keysx) |
| { |
| auto evalue = (*valuesx)[i]; |
| |
| auto ek = interpretRegion(ekey, istate); |
| if (exceptionOrCant(ek)) |
| return; |
| auto ev = interpretRegion(evalue, istate); |
| if (exceptionOrCant(ev)) |
| return; |
| |
| /* If any changes, do Copy On Write |
| */ |
| if (ek !is ekey || |
| ev !is evalue) |
| { |
| keysx = copyArrayOnWrite(keysx, e.keys); |
| valuesx = copyArrayOnWrite(valuesx, e.values); |
| (*keysx)[i] = ek; |
| (*valuesx)[i] = ev; |
| } |
| } |
| if (keysx !is e.keys) |
| expandTuples(keysx); |
| if (valuesx !is 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++) |
| { |
| auto ekey = (*keysx)[i - 1]; |
| for (size_t j = i; j < keysx.dim; j++) |
| { |
| auto ekey2 = (*keysx)[j]; |
| if (!ctfeEqual(e.loc, EXP.equal, 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 !is e.keys || |
| valuesx !is e.values) |
| { |
| assert(keysx !is e.keys && |
| valuesx !is e.values); |
| auto aae = ctfeEmplaceExp!AssocArrayLiteralExp(e.loc, keysx, valuesx); |
| aae.type = e.type; |
| aae.ownedByCtfe = OwnedBy.ctfe; |
| result = aae; |
| } |
| else |
| { |
| *pue = copyLiteral(e); |
| result = pue.exp(); |
| } |
| } |
| |
| override void visit(StructLiteralExp e) |
| { |
| debug (LOG) |
| { |
| printf("%s StructLiteralExp::interpret() %s ownedByCtfe = %d\n", e.loc.toChars(), e.toChars(), e.ownedByCtfe); |
| } |
| if (e.ownedByCtfe >= OwnedBy.ctfe) |
| { |
| result = e; |
| return; |
| } |
| |
| size_t dim = e.elements ? e.elements.dim : 0; |
| auto expsx = e.elements; |
| |
| if (dim != e.sd.fields.dim) |
| { |
| // guaranteed by AggregateDeclaration.fill and TypeStruct.defaultInitLiteral |
| const nvthis = e.sd.fields.dim - e.sd.nonHiddenFields(); |
| assert(e.sd.fields.dim - dim == nvthis); |
| |
| /* If a nested struct has no initialized hidden pointer, |
| * set it to null to match the runtime behaviour. |
| */ |
| foreach (const i; 0 .. nvthis) |
| { |
| auto ne = ctfeEmplaceExp!NullExp(e.loc); |
| auto vthis = i == 0 ? e.sd.vthis : e.sd.vthis2; |
| ne.type = vthis.type; |
| |
| expsx = copyArrayOnWrite(expsx, e.elements); |
| expsx.push(ne); |
| ++dim; |
| } |
| } |
| assert(dim == e.sd.fields.dim); |
| |
| foreach (i; 0 .. dim) |
| { |
| auto v = e.sd.fields[i]; |
| Expression exp = (*expsx)[i]; |
| Expression ex; |
| if (!exp) |
| { |
| ex = voidInitLiteral(v.type, v).copy(); |
| } |
| else |
| { |
| ex = interpretRegion(exp, istate); |
| if (exceptionOrCant(ex)) |
| return; |
| if ((v.type.ty != ex.type.ty) && v.type.ty == Tsarray) |
| { |
| // Block assignment from inside struct literals |
| auto tsa = cast(TypeSArray)v.type; |
| auto len = cast(size_t)tsa.dim.toInteger(); |
| UnionExp ue = void; |
| 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 !is exp) |
| { |
| expsx = copyArrayOnWrite(expsx, e.elements); |
| (*expsx)[i] = ex; |
| } |
| } |
| |
| if (expsx !is e.elements) |
| { |
| expandTuples(expsx); |
| if (expsx.dim != e.sd.fields.dim) |
| { |
| e.error("CTFE internal error: invalid struct literal"); |
| result = CTFEExp.cantexp; |
| return; |
| } |
| emplaceExp!(StructLiteralExp)(pue, e.loc, e.sd, expsx); |
| auto sle = pue.exp().isStructLiteralExp(); |
| sle.type = e.type; |
| sle.ownedByCtfe = OwnedBy.ctfe; |
| 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, const ref 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 = cast(size_t)lenExpr.toInteger(); |
| Type elemType = (cast(TypeArray)newtype).next; |
| if (elemType.ty == Tarray && argnum < arguments.dim - 1) |
| { |
| Expression elem = recursivelyCreateArrayLiteral(pue, loc, elemType, istate, arguments, argnum + 1); |
| if (exceptionOrCantInterpret(elem)) |
| return elem; |
| |
| auto elements = new Expressions(len); |
| foreach (ref element; *elements) |
| element = copyLiteral(elem).copy(); |
| emplaceExp!(ArrayLiteralExp)(pue, loc, newtype, elements); |
| auto ae = pue.exp().isArrayLiteralExp(); |
| ae.ownedByCtfe = OwnedBy.ctfe; |
| return ae; |
| } |
| assert(argnum == arguments.dim - 1); |
| if (elemType.ty.isSomeChar) |
| { |
| const ch = cast(dchar)elemType.defaultInitLiteral(loc).toInteger(); |
| const sz = cast(ubyte)elemType.size(); |
| return createBlockDuplicatedStringLiteral(pue, loc, newtype, ch, len, sz); |
| } |
| else |
| { |
| auto el = interpret(elemType.defaultInitLiteral(loc), istate); |
| return createBlockDuplicatedArrayLiteral(pue, loc, newtype, el, len); |
| } |
| } |
| |
| override void visit(NewExp e) |
| { |
| debug (LOG) |
| { |
| printf("%s NewExp::interpret() %s\n", e.loc.toChars(), e.toChars()); |
| } |
| |
| Expression epre = interpret(pue, e.argprefix, istate, CTFEGoal.Nothing); |
| if (exceptionOrCant(epre)) |
| return; |
| |
| if (e.newtype.ty == Tarray && e.arguments) |
| { |
| result = recursivelyCreateArrayLiteral(pue, e.loc, e.newtype, istate, e.arguments, 0); |
| return; |
| } |
| if (auto ts = e.newtype.toBasetype().isTypeStruct()) |
| { |
| 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 = ts.sym; |
| auto exps = new Expressions(); |
| exps.reserve(sd.fields.dim); |
| if (e.arguments) |
| { |
| exps.setDim(e.arguments.dim); |
| foreach (i, ex; *e.arguments) |
| { |
| ex = interpretRegion(ex, istate); |
| if (exceptionOrCant(ex)) |
| return; |
| (*exps)[i] = ex; |
| } |
| } |
| sd.fill(e.loc, *exps, false); |
| |
| auto se = ctfeEmplaceExp!StructLiteralExp(e.loc, sd, exps, e.newtype); |
| se.origin = se; |
| se.type = e.newtype; |
| se.ownedByCtfe = OwnedBy.ctfe; |
| result = interpret(pue, se, istate); |
| } |
| if (exceptionOrCant(result)) |
| return; |
| Expression ev = (result == pue.exp()) ? pue.copy() : result; |
| emplaceExp!(AddrExp)(pue, e.loc, ev, e.type); |
| result = pue.exp(); |
| return; |
| } |
| if (auto tc = e.newtype.toBasetype().isTypeClass()) |
| { |
| ClassDeclaration cd = tc.sym; |
| size_t totalFieldCount = 0; |
| for (ClassDeclaration c = cd; c; c = c.baseClass) |
| totalFieldCount += c.fields.dim; |
| auto elems = new Expressions(totalFieldCount); |
| size_t fieldsSoFar = totalFieldCount; |
| for (ClassDeclaration c = cd; c; c = c.baseClass) |
| { |
| fieldsSoFar -= c.fields.dim; |
| foreach (i, v; c.fields) |
| { |
| 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 if (v.type.isTypeNoreturn()) |
| { |
| // Noreturn field with default initializer |
| (*elems)[fieldsSoFar + i] = null; |
| continue; |
| } |
| 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. |
| // auto se = new StructLiteralExp(e.loc, cast(StructDeclaration)cd, elems, e.newtype); |
| auto se = ctfeEmplaceExp!StructLiteralExp(e.loc, cast(StructDeclaration)cd, elems, e.newtype); |
| se.origin = se; |
| se.ownedByCtfe = OwnedBy.ctfe; |
| Expression eref = ctfeEmplaceExp!ClassReferenceExp(e.loc, se, e.type); |
| 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( |