blob: e6ef704be8688e7d65e015af73431bfc7d92c42c [file] [log] [blame]
/**
* The entry point for CTFE.
*
* Specification: ($LINK2 https://dlang.org/spec/function.html#interpretation, Compile Time Function Execution (CTFE))
*
* Copyright: Copyright (C) 1999-2023 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.location;
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 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:
size_t stackPointer()
{
return values.length;
}
// The current value of 'this', or NULL if none
Expression getThis()
{
return localThis;
}
// Largest number of stack positions we've used
size_t maxStackUsage()
{
return maxStackPointer;
}
// Start a new stack frame, using the provided 'this'.
void startFrame(Expression thisexp)
{
frames.push(cast(void*)cast(size_t)framepointer);
savedThis.push(localThis);
framepointer = stackPointer();
localThis = thisexp;
}
void endFrame()
{
size_t oldframe = cast(size_t)frames[frames.length - 1];
localThis = savedThis[savedThis.length - 1];
popAll(framepointer);
framepointer = oldframe;
frames.setDim(frames.length - 1);
savedThis.setDim(savedThis.length - 1);
}
bool isInCurrentFrame(VarDeclaration v)
{
if (v.isDataseg() && !v.isCTFE())
return false; // It's a global
return v.ctfeAdrOnStack >= framepointer;
}
Expression getValue(VarDeclaration v)
{
//printf("getValue() %s\n", v.toChars());
if ((v.isDataseg() || v.storage_class & STC.manifest) && !v.isCTFE())
{
assert(v.ctfeAdrOnStack < globalValues.length);
return globalValues[v.ctfeAdrOnStack];
}
assert(v.ctfeAdrOnStack < stackPointer());
return values[v.ctfeAdrOnStack];
}
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;
}
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.length;
vars.push(v);
values.push(null);
}
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.length - 1)
{
values.pop();
vars.pop();
savedId.pop();
}
}
void popAll(size_t stackpointer)
{
if (stackPointer() > maxStackPointer)
maxStackPointer = stackPointer();
assert(values.length >= stackpointer);
for (size_t i = stackpointer; i < values.length; ++i)
{
VarDeclaration v = vars[i];
v.ctfeAdrOnStack = cast(uint)cast(size_t)savedId[i];
}
values.setDim(stackpointer);
vars.setDim(stackpointer);
savedId.setDim(stackpointer);
}
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.length;
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.length != fd.parameters.length) || (!fd.parameters && arguments.length)))
{
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.length : 0;
assert((fd.parameters ? fd.parameters.length : 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) scope
{
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.length : 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.length : 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.length > 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 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 = 5; // index of Throwable.next
assert((*boss.value.elements)[next].type.ty == Tclass); // Throwable.next
ClassReferenceExp collateral = newest.thrown;
if (collateral.originalClass().isErrorException() && !boss.originalClass().isErrorException())
{
/* 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;
if (e.op == EXP.classReference)
{
result = ctfeEmplaceExp!ThrownExceptionExp(loc, e.isClassReferenceExp());
}
else
{
exp.error("to be thrown `%s` must be non-null", exp.toChars());
result = ErrorExp.get();
}
}
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());
}
if (e.ownedByCtfe >= OwnedBy.ctfe) // We've already interpreted the string
{
result = e;
return;
}
if (e.type.ty != Tsarray ||
(cast(TypeNext)e.type).next.mod & (MODFlags.const_ | MODFlags.immutable_))
{
// If it's immutable, we don't need to dup it. Attempts to modify
// string literals are prevented in BinExp::interpretAssignCommon.
result = e;
}
else
{
// https://issues.dlang.org/show_bug.cgi?id=20811
// Create a copy of mutable string literals, so that any change in
// value via an index or slice will not survive CTFE.
*pue = copyLiteral(e);
result = pue.exp();
}
}
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 (istate && 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);
// There's a terrible hack in `dmd.dsymbolsem` that special case
// a struct with all zeros to an `ExpInitializer(BlitExp(IntegerExp(0)))`
// There's matching code for it in e2ir (toElem's visitAssignExp),
// so we need the same hack here.
// This does not trigger for global as they get a normal initializer.
if (auto ts = e.type.isTypeStruct())
if (auto ae = e.isBlitExp())
if (ae.e2.op == EXP.int64)
e = ts.defaultInitLiteral(loc);
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.length == 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.length : 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.length != 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.length != valuesx.length)
{
e.error("CTFE internal error: invalid AA");
result = CTFEExp.cantexp;
return;
}
/* Remove duplicate keys
*/
for (size_t i = 1; i < keysx.length; i++)
{
auto ekey = (*keysx)[i - 1];
for (size_t j = i; j < keysx.length; 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.length : 0;
auto expsx = e.elements;
if (dim != e.sd.fields.length)
{
// guaranteed by AggregateDeclaration.fill and TypeStruct.defaultInitLiteral
const nvthis = e.sd.fields.length - e.sd.nonHiddenFields();
assert(e.sd.fields.length - 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.length);
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.length != e.sd.fields.length)
{
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.length - 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.length - 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.length);
if (e.arguments)
{
exps.setDim(e.arguments.length);
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.length;
auto elems = new Expressions(totalFieldCount);
size_t fieldsSoFar = totalFieldCount;
for (ClassDeclaration c = cd; c; c = c.baseClass)
{
fieldsSoFar -= c.fields.length;
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(ctorfail))
return;
result = eref;
return;
}
e.member.error("`%s` cannot be constructed at compile time, because the constructor has no available source code", e.newtype.toChars());
result = CTFEExp.cantexp;
return;
}
UnionExp ue = void;
Expression ctorfail = interpretFunction(&ue, e.member, istate, e.arguments, eref);
if (exceptionOrCant(ctorfail))
return;
/* https://issues.dlang.org/show_bug.cgi?id=14465
* Repaint the loc, because a super() call
* in the constructor modifies the loc of ClassReferenceExp
* in CallExp::interpret().
*/
eref.loc = e.loc;
}
result = eref;
return;
}
if (e.newtype.toBasetype().isscalar())
{
Expression newval;
if (e.arguments && e.arguments.length)
newval = (*e.arguments)[0];
else
newval = e.newtype.defaultInitLiteral(e.loc);
newval = interpretRegion(newval, istate);
if (exceptionOrCant(newval))
return;
// Create a CTFE pointer &[newval][0]
auto elements = new Expressions(1);
(*elements)[0] = newval;
auto ae = ctfeEmplaceExp!ArrayLiteralExp(e.loc, e.newtype.arrayOf(), elements);
ae.ownedByCtfe = OwnedBy.ctfe;
auto ei = ctfeEmplaceExp!IndexExp(e.loc, ae, ctfeEmplaceExp!IntegerExp(Loc.initial, 0, Type.tsize_t));
ei.type = e.newtype;
emplaceExp!(AddrExp)(pue, e.loc, ei, e.type);
result = pue.exp();
return;
}
e.error("cannot interpret `%s` at compile time", e.toChars());
result = CTFEExp.cantexp;
}
override void visit(UnaExp e)
{
debug (LOG)
{
printf("%s UnaExp::interpret() %s\n", e.loc.toChars(), e.toChars());
}
UnionExp ue = void;
Expression e1 = interpret(&ue, e.e1, istate);
if (exceptionOrCant(e1))
return;
switch (e.op)
{
case EXP.negate:
*pue = Neg(e.type, e1);
break;
case EXP.tilde:
*pue = Com(e.type, e1);
break;
case EXP.not:
*pue = Not(e.type, e1);
break;
default:
assert(0);
}
result = (*pue).exp();
}
override void visit(DotTypeExp e)
{
debug (LOG)
{
printf("%s DotTypeExp::interpret() %s\n", e.loc.toChars(), e.toChars());
}
UnionExp ue = void;
Expression e1 = interpret(&ue, e.e1, istate);
if (exceptionOrCant(e1))
return;
if (e1 == e.e1)
result = e; // optimize: reuse this CTFE reference
else
{
auto edt = e.copy().isDotTypeExp();
edt.e1 = (e1 == ue.exp()) ? e1.copy() : e1; // don't return pointer to ue
result = edt;
}
}
extern (D) private void interpretCommon(BinExp e, fp_t fp)
{
debug (LOG)
{
printf("%s BinExp::interpretCommon() %s\n", e.loc.toChars(), e.toChars());
}
if (e.e1.type.ty == Tpointer && e.e2.type.ty == Tpointer && e.op == EXP.min)
{
UnionExp ue1 = void;
Expression e1 = interpret(&ue1, e.e1, istate);
if (exceptionOrCant(e1))
return;
UnionExp ue2 = void;
Expression e2 = interpret(&ue2, e.e2, istate);
if (exceptionOrCant(e2))
return;
result = pointerDifference(pue, e.loc, e.type, e1, e2);
return;
}
if (e.e1.type.ty == Tpointer && e.e2.type.isintegral())
{
UnionExp ue1 = void;
Expression e1 = interpret(&ue1, e.e1, istate);
if (exceptionOrCant(e1))
return;
UnionExp ue2 = void;
Expression e2 = interpret(&ue2, e.e2, istate);
if (exceptionOrCant(e2))
return;
result = pointerArithmetic(pue, e.loc, e.op, e.type, e1, e2);
return;
}
if (e.e2.type.ty == Tpointer && e.e1.type.isintegral() && e.op == EXP.add)
{
UnionExp ue1 = void;
Expression e1 = interpret(&ue1, e.e1, istate);
if (exceptionOrCant(e1))
return;
UnionExp ue2 = void;
Expression e2 = interpret(&ue2, e.e2, istate);
if (exceptionOrCant(e2))
return;
result = pointerArithmetic(pue, e.loc, e.op, e.type, e2, e1);
return;
}
if (e.e1.type.ty == Tpointer || e.e2.type.ty == Tpointer)
{
e.error("pointer expression `%s` cannot be interpreted at compile time", e.toChars());
result = CTFEExp.cantexp;
return;
}
bool evalOperand(UnionExp* pue, Expression ex, out Expression er)
{
er = interpret(pue, ex, istate);
if (exceptionOrCant(er))
return false;
return true;
}
UnionExp ue1 = void;
Expression e1;
if (!evalOperand(&ue1, e.e1, e1))
return;
UnionExp ue2 = void;
Expression e2;
if (!evalOperand(&ue2, e.e2, e2))
return;
if (e.op == EXP.rightShift || e.op == EXP.leftShift || e.op == EXP.unsignedRightShift)
{
const sinteger_t i2 = e2.toInteger();
const uinteger_t sz = e1.type.size() * 8;
if (i2 < 0 || i2 >= sz)
{
e.error("shift by %lld is outside the range 0..%llu", i2, cast(ulong)sz - 1);
result = CTFEExp.cantexp;
return;
}
}
/******************************************
* Perform the operation fp on operands e1 and e2.
*/
UnionExp evaluate(Loc loc, Type type, Expression e1, Expression e2)
{
UnionExp ue = void;
auto ae1 = e1.isArrayLiteralExp();
auto ae2 = e2.isArrayLiteralExp();
if (ae1 || ae2)
{
/* Cases:
* 1. T[] op T[]
* 2. T op T[]
* 3. T[] op T
*/
if (ae1 && e2.implicitConvTo(e1.type.toBasetype().nextOf())) // case 3
ae2 = null;
else if (ae2 && e1.implicitConvTo(e2.type.toBasetype().nextOf())) // case 2
ae1 = null;
// else case 1
auto aex = ae1 ? ae1 : ae2;
if (!aex.elements)
{
emplaceExp!ArrayLiteralExp(&ue, loc, type, cast(Expressions*) null);
return ue;
}
const length = aex.elements.length;
Expressions* elements = new Expressions(length);
emplaceExp!ArrayLiteralExp(&ue, loc, type, elements);
foreach (i; 0 .. length)
{
Expression e1x = ae1 ? ae1[i] : e1;
Expression e2x = ae2 ? ae2[i] : e2;
UnionExp uex = evaluate(loc, e1x.type, e1x, e2x);
// This can be made more efficient by making use of ue.basis
(*elements)[i] = uex.copy();
}
return ue;
}
if (e1.isConst() != 1)
{
// The following should really be an assert()
e1.error("CTFE internal error: non-constant value `%s`", e1.toChars());
emplaceExp!CTFEExp(&ue, EXP.cantExpression);
return ue;
}
if (e2.isConst() != 1)
{
e2.error("CTFE internal error: non-constant value `%s`", e2.toChars());
emplaceExp!CTFEExp(&ue, EXP.cantExpression);
return ue;
}
return (*fp)(loc, type, e1, e2);
}
*pue = evaluate(e.loc, e.type, e1, e2);
result = (*pue).exp();
if (CTFEExp.isCantExp(result))
e.error("`%s` cannot be interpreted at compile time", e.toChars());
}
extern (D) private void interpretCompareCommon(BinExp e, fp2_t fp)
{
debug (LOG)
{
printf("%s BinExp::interpretCompareCommon() %s\n", e.loc.toChars(), e.toChars());
}
UnionExp ue1 = void;
UnionExp ue2 = void;
if (e.e1.type.ty == Tpointer && e.e2.type.ty == Tpointer)
{
Expression e1 = interpret(&ue1, e.e1, istate);
if (exceptionOrCant(e1))
return;
Expression e2 = interpret(&ue2, e.e2, istate);
if (exceptionOrCant(e2))
return;
//printf("e1 = %s %s, e2 = %s %s\n", e1.type.toChars(), e1.toChars(), e2.type.toChars(), e2.toChars());
dinteger_t ofs1, ofs2;
Expression agg1 = getAggregateFromPointer(e1, &ofs1);
Expression agg2 = getAggregateFromPointer(e2, &ofs2);
//printf("agg1 = %p %s, agg2 = %p %s\n", agg1, agg1.toChars(), agg2, agg2.toChars());
const cmp = comparePointers(e.op, agg1, ofs1, agg2, ofs2);
if (cmp == -1)
{
char dir = (e.op == EXP.greaterThan || e.op == EXP.greaterOrEqual) ? '<' : '>';
e.error("the ordering of pointers to unrelated memory blocks is indeterminate in CTFE. To check if they point to the same memory block, use both `>` and `<` inside `&&` or `||`, eg `%s && %s %c= %s + 1`", e.toChars(), e.e1.toChars(), dir, e.e2.toChars());
result = CTFEExp.cantexp;
return;
}
if (e.type.equals(Type.tbool))
result = IntegerExp.createBool(cmp != 0);
else
{
emplaceExp!(IntegerExp)(pue, e.loc, cmp, e.type);
result = (*pue).exp();
}
return;
}
Expression e1 = interpret(&ue1, e.e1, istate);
if (exceptionOrCant(e1))
return;
if (!isCtfeComparable(e1))
{
e.error("cannot compare `%s` at compile time", e1.toChars());
result = CTFEExp.cantexp;
return;
}
Expression e2 = interpret(&ue2, e.e2, istate);
if (exceptionOrCant(e2))
return;
if (!isCtfeComparable(e2))
{
e.error("cannot compare `%s` at compile time", e2.toChars());
result = CTFEExp.cantexp;
return;
}
const cmp = (*fp)(e.loc, e.op, e1, e2);
if (e.type.equals(Type.tbool))
result = IntegerExp.createBool(cmp);
else
{
emplaceExp!(IntegerExp)(pue, e.loc, cmp, e.type);
result = (*pue).exp();
}
}
override void visit(BinExp e)
{
switch (e.op)
{
case EXP.add:
interpretCommon(e, &Add);
return;
case EXP.min:
interpretCommon(e, &Min);
return;
case EXP.mul:
interpretCommon(e, &Mul);
return;
case EXP.div:
interpretCommon(e, &Div);
return;
case EXP.mod:
interpretCommon(e, &Mod);
return;
case EXP.leftShift:
interpretCommon(e, &Shl);
return;
case EXP.rightShift:
interpretCommon(e, &Shr);
return;
case EXP.unsignedRightShift:
interpretCommon(e, &Ushr);
return;
case EXP.and:
interpretCommon(e, &And);
return;
case EXP.or:
interpretCommon(e, &Or);
return;
case EXP.xor:
interpretCommon(e, &Xor);
return;
case EXP.pow:
interpretCommon(e, &Pow);
return;
case EXP.equal:
case EXP.notEqual:
interpretCompareCommon(e, &ctfeEqual);
return;
case EXP.identity:
case EXP.notIdentity:
interpretCompareCommon(e, &ctfeIdentity);
return;
case EXP.lessThan:
case EXP.lessOrEqual:
case EXP.greaterThan:
case EXP.greaterOrEqual:
interpretCompareCommon(e, &ctfeCmp);
return;
default:
printf("be = '%s' %s at [%s]\n", EXPtoString(e.op).ptr, e.toChars(), e.loc.toChars());
assert(0);
}
}
/* Helper functions for BinExp::interpretAssignCommon
*/
// Returns the variable which is eventually modified, or NULL if an rvalue.
// thisval is the current value of 'this'.
static VarDeclaration findParentVar(Expression e)
{
for (;;)
{
if (auto ve = e.isVarExp())
{
VarDeclaration v = ve.var.isVarDeclaration();
assert(v);
return v;
}
if (auto ie = e.isIndexExp())
e = ie.e1;
else if (auto dve = e.isDotVarExp())
e = dve.e1;
else if (auto dtie = e.isDotTemplateInstanceExp())
e = dtie.e1;
else if (auto se = e.isSliceExp())
e = se.e1;
else
return null;
}
}
extern (D) private void interpretAssignCommon(BinExp e, fp_t fp, int post = 0)
{
debug (LOG)
{
printf("%s BinExp::interpretAssignCommon() %s\n", e.loc.toChars(), e.toChars());
}
result = CTFEExp.cantexp;
Expression e1 = e.e1;
if (!istate)
{
e.error("value of `%s` is not known at compile time", e1.toChars());
return;
}
++ctfeGlobals.numAssignments;
/* Before we begin, we need to know if this is a reference assignment
* (dynamic array, AA, or class) or a value assignment.
* Determining this for slice assignments are tricky: we need to know
* if it is a block assignment (a[] = e) rather than a direct slice
* assignment (a[] = b[]). Note that initializers of multi-dimensional
* static arrays can have 2D block assignments (eg, int[7][7] x = 6;).
* So we need to recurse to determine if it is a block assignment.
*/
bool isBlockAssignment = false;
if (e1.op == EXP.slice)
{
// a[] = e can have const e. So we compare the naked types.
Type tdst = e1.type.toBasetype();
Type tsrc = e.e2.type.toBasetype();
while (tdst.ty == Tsarray || tdst.ty == Tarray)
{
tdst = (cast(TypeArray)tdst).next.toBasetype();
if (tsrc.equivalent(tdst))
{
isBlockAssignment = true;
break;
}
}
}
// ---------------------------------------
// Deal with reference assignment
// ---------------------------------------
// If it is a construction of a ref variable, it is a ref assignment
if ((e.op == EXP.construct || e.op == EXP.blit) &&
((cast(AssignExp)e).memset == MemorySet.referenceInit))
{
assert(!fp);
Expression newval = interpretRegion(e.e2, istate, CTFEGoal.LValue);
if (exceptionOrCant(newval))
return;
VarDeclaration v = e1.isVarExp().var.isVarDeclaration();
setValue(v, newval);
// Get the value to return. Note that 'newval' is an Lvalue,
// so if we need an Rvalue, we have to interpret again.
if (goal == CTFEGoal.RValue)
result = interpretRegion(newval, istate);
else
result = e1; // VarExp is a CTFE reference
return;
}
if (fp)
{
while (e1.op == EXP.cast_)
{
CastExp ce = e1.isCastExp();
e1 = ce.e1;
}
}
// ---------------------------------------
// Interpret left hand side
// ---------------------------------------
AssocArrayLiteralExp existingAA = null;
Expression lastIndex = null;
Expression oldval = null;
if (e1.op == EXP.index && e1.isIndexExp().e1.type.toBasetype().ty == Taarray)
{
// ---------------------------------------
// Deal with AA index assignment
// ---------------------------------------
/* This needs special treatment if the AA doesn't exist yet.
* There are two special cases:
* (1) If the AA is itself an index of another AA, we may need to create
* multiple nested AA literals before we can insert the new value.
* (2) If the ultimate AA is null, no insertion happens at all. Instead,
* we create nested AA literals, and change it into a assignment.
*/
IndexExp ie = e1.isIndexExp();
int depth = 0; // how many nested AA indices are there?
while (ie.e1.op == EXP.index && ie.e1.isIndexExp().e1.type.toBasetype().ty == Taarray)
{
assert(ie.modifiable);
ie = ie.e1.isIndexExp();
++depth;
}
// Get the AA value to be modified.
Expression aggregate = interpretRegion(ie.e1, istate);
if (exceptionOrCant(aggregate))
return;
if ((existingAA = aggregate.isAssocArrayLiteralExp()) !is null)
{
// Normal case, ultimate parent AA already exists
// We need to walk from the deepest index up, checking that an AA literal
// already exists on each level.
lastIndex = interpretRegion(e1.isIndexExp().e2, istate);
lastIndex = resolveSlice(lastIndex); // only happens with AA assignment
if (exceptionOrCant(lastIndex))
return;
while (depth > 0)
{
// Walk the syntax tree to find the indexExp at this depth
IndexExp xe = e1.isIndexExp();
foreach (d; 0 .. depth)
xe = xe.e1.isIndexExp();
Expression ekey = interpretRegion(xe.e2, istate);
if (exceptionOrCant(ekey))
return;
UnionExp ekeyTmp = void;
ekey = resolveSlice(ekey, &ekeyTmp); // only happens with AA assignment
// Look up this index in it up in the existing AA, to get the next level of AA.
AssocArrayLiteralExp newAA = cast(AssocArrayLiteralExp)findKeyInAA(e.loc, existingAA, ekey);
if (exceptionOrCant(newAA))
return;
if (!newAA)
{
// Doesn't exist yet, create an empty AA...
auto keysx = new Expressions();
auto valuesx = new Expressions();
newAA = ctfeEmplaceExp!AssocArrayLiteralExp(e.loc, keysx, valuesx);
newAA.type = xe.type;
newAA.ownedByCtfe = OwnedBy.ctfe;
//... and insert it into the existing AA.
existingAA.keys.push(ekey);
existingAA.values.push(newAA);
}
existingAA = newAA;
--depth;
}
if (fp)
{
oldval = findKeyInAA(e.loc, existingAA, lastIndex);
if (!oldval)
oldval = copyLiteral(e.e1.type.defaultInitLiteral(e.loc)).copy();
}
}
else
{
/* The AA is currently null. 'aggregate' is actually a reference to
* whatever contains it. It could be anything: var, dotvarexp, ...
* We rewrite the assignment from:
* aa[i][j] op= newval;
* into:
* aa = [i:[j:T.init]];
* aa[j] op= newval;
*/
oldval = copyLiteral(e.e1.type.defaultInitLiteral(e.loc)).copy();
Expression newaae = oldval;
while (e1.op == EXP.index && e1.isIndexExp().e1.type.toBasetype().ty == Taarray)
{
Expression ekey = interpretRegion(e1.isIndexExp().e2, istate);
if (exceptionOrCant(ekey))
return;
ekey = resolveSlice(ekey); // only happens with AA assignment
auto keysx = new Expressions();
auto valuesx = new Expressions();
keysx.push(ekey);
valuesx.push(newaae);
auto aae = ctfeEmplaceExp!AssocArrayLiteralExp(e.loc, keysx, valuesx);
aae.type = e1.isIndexExp().e1.type;
aae.ownedByCtfe = OwnedBy.ctfe;
if (!existingAA)
{
existingAA = aae;
lastIndex = ekey;
}
newaae = aae;
e1 = e1.isIndexExp().e1;
}
// We must set to aggregate with newaae
e1 = interpretRegion(e1, istate, CTFEGoal.LValue);
if (exceptionOrCant(e1))
return;
e1 = assignToLvalue(e, e1, newaae);
if (exceptionOrCant(e1))
return;
}
assert(existingAA && lastIndex);
e1 = null; // stomp
}
else if (e1.op == EXP.arrayLength)
{
oldval = interpretRegion(e1, istate);
if (exceptionOrCant(oldval))
return;
}
else if (e.op == EXP.construct || e.op == EXP.blit)
{
// Unless we have a simple var assignment, we're
// only modifying part of the variable. So we need to make sure
// that the parent variable exists.
VarDeclaration ultimateVar = findParentVar(e1);
if (auto ve = e1.isVarExp())
{
VarDeclaration v = ve.var.isVarDeclaration();
assert(v);
if (v.storage_class & STC.out_)
goto L1;
}
else if (ultimateVar && !getValue(ultimateVar))
{
Expression ex = interpretRegion(ultimateVar.type.defaultInitLiteral(e.loc), istate);
if (exceptionOrCant(ex))
return;
setValue(ultimateVar, ex);
}
else
goto L1;
}
else
{
L1:
e1 = interpretRegion(e1, istate, CTFEGoal.LValue);
if (exceptionOrCant(e1))
return;
if (e1.op == EXP.index && e1.isIndexExp().e1.type.toBasetype().ty == Taarray)
{
IndexExp ie = e1.isIndexExp();
assert(ie.e1.op == EXP.assocArrayLiteral);
existingAA = ie.e1.isAssocArrayLiteralExp();
lastIndex = ie.e2;
}
}
// ---------------------------------------
// Interpret right hand side
// ---------------------------------------
Expression newval = interpretRegion(e.e2, istate);
if (exceptionOrCant(newval))
return;
if (e.op == EXP.blit && newval.op == EXP.int64)
{
Type tbn = e.type.baseElemOf();
if (tbn.ty == Tstruct)
{
/* Look for special case of struct being initialized with 0.
*/
newval = e.type.defaultInitLiteral(e.loc);
if (newval.op == EXP.error)
{
result = CTFEExp.cantexp;
return;
}
newval = interpretRegion(newval, istate); // copy and set ownedByCtfe flag
if (exceptionOrCant(newval))
return;
}
}
// ----------------------------------------------------
// Deal with read-modify-write assignments.
// Set 'newval' to the final assignment value
// Also determine the return value (except for slice
// assignments, which are more complicated)
// ----------------------------------------------------
if (fp)
{
if (!oldval)
{
// Load the left hand side after interpreting the right hand side.
oldval = interpretRegion(e1, istate);
if (exceptionOrCant(oldval))
return;
}
if (e.e1.type.ty != Tpointer)
{
// ~= can create new values (see bug 6052)
if (e.op == EXP.concatenateAssign || e.op == EXP.concatenateElemAssign || e.op == EXP.concatenateDcharAssign)
{
// We need to dup it and repaint the type. For a dynamic array
// we can skip duplication, because it gets copied later anyway.
if (newval.type.ty != Tarray)
{
newval = copyLiteral(newval).copy();
newval.type = e.e2.type; // repaint type
}
else
{
newval = paintTypeOntoLiteral(e.e2.type, newval);
newval = resolveSlice(newval);
}
}
oldval = resolveSlice(oldval);
newval = (*fp)(e.loc, e.type, oldval, newval).copy();
}
else if (e.e2.type.isintegral() &&
(e.op == EXP.addAssign ||
e.op == EXP.minAssign ||
e.op == EXP.plusPlus ||
e.op == EXP.minusMinus))
{
newval = pointerArithmetic(pue, e.loc, e.op, e.type, oldval, newval).copy();
if (newval == pue.exp())
newval = pue.copy();
}
else
{
e.error("pointer expression `%s` cannot be interpreted at compile time", e.toChars());
result = CTFEExp.cantexp;
return;
}
if (exceptionOrCant(newval))
{
if (CTFEExp.isCantExp(newval))
e.error("cannot interpret `%s` at compile time", e.toChars());
return;
}
}
if (existingAA)
{
if (existingAA.ownedByCtfe != OwnedBy.ctfe)
{
e.error("cannot modify read-only constant `%s`", existingAA.toChars());
result = CTFEExp.cantexp;
return;
}
//printf("\t+L%d existingAA = %s, lastIndex = %s, oldval = %s, newval = %s\n",
// __LINE__, existingAA.toChars(), lastIndex.toChars(), oldval ? oldval.toChars() : NULL, newval.toChars());
assignAssocArrayElement(e.loc, existingAA, lastIndex, newval);
// Determine the return value
result = ctfeCast(pue, e.loc, e.type, e.type, fp && post ? oldval : newval);
return;
}
if (e1.op == EXP.arrayLength)
{
/* Change the assignment from:
* arr.length = n;
* into:
* arr = new_length_array; (result is n)
*/
// Determine the return value
result = ctfeCast(pue, e.loc, e.type, e.type, fp && post ? oldval : newval);
if (exceptionOrCant(result))
return;
if (result == pue.exp())
result = pue.copy();
size_t oldlen = cast(size_t)oldval.toInteger();
size_t newlen = cast(size_t)newval.toInteger();
if (oldlen == newlen) // no change required -- we're done!
return;
// We have changed it into a reference assignment
// Note that returnValue is still the new length.
e1 = e1.isArrayLengthExp().e1;
Type t = e1.type.toBasetype();
if (t.ty != Tarray)
{
e.error("`%s` is not yet supported at compile time", e.toChars());
result = CTFEExp.cantexp;
return;
}
e1 = interpretRegion(e1, istate, CTFEGoal.LValue);
if (exceptionOrCant(e1))
return;
if (oldlen != 0) // Get the old array literal.
oldval = interpretRegion(e1, istate);
UnionExp utmp = void;
oldval = resolveSlice(oldval, &utmp);
newval = changeArrayLiteralLength(pue, e.loc, cast(TypeArray)t, oldval, oldlen, newlen);
if (newval == pue.exp())
newval = pue.copy();
e1 = assignToLvalue(e, e1, newval);
if (exceptionOrCant(e1))
return;
return;
}
if (!isBlockAssignment)
{
newval = ctfeCast(pue, e.loc, e.type, e.type, newval);
if (exceptionOrCant(newval))
return;
if (newval == pue.exp())
newval = pue.copy();
// Determine the return value
if (goal == CTFEGoal.LValue) // https://issues.dlang.org/show_bug.cgi?id=14371
result = e1;
else
{
result = ctfeCast(pue, e.loc, e.type, e.type, fp && post ? oldval : newval);
if (result == pue.exp())
result = pue.copy();
}
if (exceptionOrCant(result))
return;
}
if (exceptionOrCant(newval))
return;
debug (LOGASSIGN)
{
printf("ASSIGN: %s=%s\n", e1.toChars(), newval.toChars());
showCtfeExpr(newval);
}
/* Block assignment or element-wise assignment.
*/
if (e1.op == EXP.slice ||
e1.op == EXP.vector ||
e1.op == EXP.arrayLiteral ||
e1.op == EXP.string_ ||
e1.op == EXP.null_ && e1.type.toBasetype().ty == Tarray)
{
// Note that slice assignments don't support things like ++, so
// we don't need to remember 'returnValue'.
result = interpretAssignToSlice(pue, e, e1, newval, isBlockAssignment);
if (exceptionOrCant(result))
return;
if (auto se = e.e1.isSliceExp())
{
Expression e1x = interpretRegion(se.e1, istate, CTFEGoal.LValue);
if (auto dve = e1x.isDotVarExp())
{
auto ex = dve.e1;
auto sle = ex.op == EXP.structLiteral ? ex.isStructLiteralExp()
: ex.op == EXP.classReference ? ex.isClassReferenceExp().value
: null;
auto v = dve.var.isVarDeclaration();
if (!sle || !v)
{
e.error("CTFE internal error: dotvar slice assignment");
result = CTFEExp.cantexp;
return;
}
stompOverlappedFields(sle, v);
}
}
return;
}
assert(result);
/* Assignment to a CTFE reference.
*/
if (Expression ex = assignToLvalue(e, e1, newval))
result = ex;
return;
}
/* Set all sibling fields which overlap with v to VoidExp.
*/
private void stompOverlappedFields(StructLiteralExp sle, VarDeclaration v)
{
if (!v.overlapped)
return;
foreach (size_t i, v2; sle.sd.fields)
{
if (v is v2 || !v.isOverlappedWith(v2))
continue;
auto e = (*sle.elements)[i];
if (e.op != EXP.void_)
(*sle.elements)[i] = voidInitLiteral(e.type, v).copy();
}
}
private Expression assignToLvalue(BinExp e, Expression e1, Expression newval)
{
//printf("assignToLvalue() e: %s e1: %s newval: %s\n", e.toChars(), e1.toChars(), newval.toChars());
VarDeclaration vd = null;
Expression* payload = null; // dead-store to prevent spurious warning
Expression oldval;
if (auto ve = e1.isVarExp())
{
vd = ve.var.isVarDeclaration();
oldval = getValue(vd);
}
else if (auto dve = e1.isDotVarExp())
{
/* Assignment to member variable of the form:
* e.v = newval
*/
auto ex = dve.e1;
auto sle = ex.op == EXP.structLiteral ? ex.isStructLiteralExp()
: ex.op == EXP.classReference ? ex.isClassReferenceExp().value
: null;
auto v = e1.isDotVarExp().var.isVarDeclaration();
if (!sle || !v)
{
e.error("CTFE internal error: dotvar assignment");
return CTFEExp.cantexp;
}
if (sle.ownedByCtfe != OwnedBy.ctfe)
{
e.error("cannot modify read-only constant `%s`", sle.toChars());
return CTFEExp.cantexp;
}
int fieldi = ex.op == EXP.structLiteral ? findFieldIndexByName(sle.sd, v)
: ex.isClassReferenceExp().findFieldIndexByName(v);
if (fieldi == -1)
{
e.error("CTFE internal error: cannot find field `%s` in `%s`", v.toChars(), ex.toChars());
return CTFEExp.cantexp;
}
assert(0 <= fieldi && fieldi < sle.elements.length);
// If it's a union, set all other members of this union to void
stompOverlappedFields(sle, v);
payload = &(*sle.elements)[fieldi];
oldval = *payload;
if (auto ival = newval.isIntegerExp())
{
if (auto bf = v.isBitFieldDeclaration())
{
sinteger_t value = ival.toInteger();
if (bf.type.isunsigned())
value &= (1L << bf.fieldWidth) - 1; // zero extra bits
else
{ // sign extend extra bits
value = value << (64 - bf.fieldWidth);
value = value >> (64 - bf.fieldWidth);
}
ival.setInteger(value);
}
}
}
else if (auto ie = e1.isIndexExp())
{
assert(ie.e1.type.toBasetype().ty != Taarray);
Expression aggregate;
uinteger_t indexToModify;
if (!resolveIndexing(ie, istate, &aggregate, &indexToModify, true))
{
return CTFEExp.cantexp;
}
size_t index = cast(size_t)indexToModify;
if (auto existingSE = aggregate.isStringExp())
{
if (existingSE.ownedByCtfe != OwnedBy.ctfe)
{
e.error("cannot modify read-only string literal `%s`", ie.e1.toChars());
return CTFEExp.cantexp;
}
existingSE.setCodeUnit(index, cast(dchar)newval.toInteger());
return null;
}
if (aggregate.op != EXP.arrayLiteral)
{
e.error("index assignment `%s` is not yet supported in CTFE ", e.toChars());
return CTFEExp.cantexp;
}
ArrayLiteralExp existingAE = aggregate.isArrayLiteralExp();
if (existingAE.ownedByCtfe != OwnedBy.ctfe)
{
e.error("cannot modify read-only constant `%s`", existingAE.toChars());
return CTFEExp.cantexp;
}
payload = &(*existingAE.elements)[index];
oldval = *payload;
}
else
{
e.error("`%s` cannot be evaluated at compile time", e.toChars());
return CTFEExp.cantexp;
}
Type t1b = e1.type.toBasetype();
bool wantCopy = t1b.baseElemOf().ty == Tstruct;
if (auto ve = newval.isVectorExp())
{
// Ensure ve is an array literal, and not a broadcast
if (ve.e1.op == EXP.int64 || ve.e1.op == EXP.float64) // if broadcast
{
UnionExp ue = void;
Expression ex = interpretVectorToArray(&ue, ve);
ve.e1 = (ex == ue.exp()) ? ue.copy() : ex;
}
}
if (newval.op == EXP.structLiteral && oldval)
{
assert(oldval.op == EXP.structLiteral || oldval.op == EXP.arrayLiteral || oldval.op == EXP.string_);
newval = copyLiteral(newval).copy();
assignInPlace(oldval, newval);
}
else if (wantCopy && e.op == EXP.assign)
{
// Currently postblit/destructor calls on static array are done
// in the druntime internal functions so they don't appear in AST.
// Therefore interpreter should handle them specially.
assert(oldval);
version (all) // todo: instead we can directly access to each elements of the slice
{
newval = resolveSlice(newval);
if (CTFEExp.isCantExp(newval))
{
e.error("CTFE internal error: assignment `%s`", e.toChars());
return CTFEExp.cantexp;
}
}
assert(oldval.op == EXP.arrayLiteral);
assert(newval.op == EXP.arrayLiteral);
Expressions* oldelems = oldval.isArrayLiteralExp().elements;
Expressions* newelems = newval.isArrayLiteralExp().elements;
assert(oldelems.length == newelems.length);
Type elemtype = oldval.type.nextOf();
foreach (i, ref oldelem; *oldelems)
{
Expression newelem = paintTypeOntoLiteral(elemtype, (*newelems)[i]);
// https://issues.dlang.org/show_bug.cgi?id=9245
if (e.e2.isLvalue())
{
if (Expression ex = evaluatePostblit(istate, newelem))
return ex;
}
// https://issues.dlang.org/show_bug.cgi?id=13661
if (Expression ex = evaluateDtor(istate, oldelem))
return ex;
oldelem = newelem;
}
}
else
{
// e1 has its own payload, so we have to create a new literal.
if (wantCopy)
newval = copyLiteral(newval).copy();
if (t1b.ty == Tsarray && e.op == EXP.construct && e.e2.isLvalue())
{
// https://issues.dlang.org/show_bug.cgi?id=9245
if (Expression ex = evaluatePostblit(istate, newval))
return ex;
}
oldval = newval;
}
if (vd)
setValue(vd, oldval);
else
*payload = oldval;
// Blit assignment should return the newly created value.
if (e.op == EXP.blit)
return oldval;
return null;
}
/*************
* Deal with assignments of the form:
* dest[] = newval
* dest[low..upp] = newval
* where newval has already been interpreted
*
* This could be a slice assignment or a block assignment, and
* dest could be either an array literal, or a string.
*
* Returns EXP.cantExpression on failure. If there are no errors,
* it returns aggregate[low..upp], except that as an optimisation,
* if goal == CTFEGoal.Nothing, it will return NULL
*/
private Expression interpretAssignToSlice(UnionExp* pue, BinExp e, Expression e1, Expression newval, bool isBlockAssignment)
{
dinteger_t lowerbound;
dinteger_t upperbound;
dinteger_t firstIndex;
Expression aggregate;
if (auto se = e1.isSliceExp())
{
// ------------------------------
// aggregate[] = newval
// aggregate[low..upp] = newval
// ------------------------------
aggregate = interpretRegion(se.e1, istate);
lowerbound = se.lwr ? se.lwr.toInteger() : 0;
upperbound = se.upr ? se.upr.toInteger() : resolveArrayLength(aggregate);
// Slice of a slice --> change the bounds
if (auto oldse = aggregate.isSliceExp())
{
aggregate = oldse.e1;
firstIndex = lowerbound + oldse.lwr.toInteger();
}
else
firstIndex = lowerbound;
}
else
{
if (auto ale = e1.isArrayLiteralExp())
{
lowerbound = 0;
upperbound = ale.elements.length;
}
else if (auto se = e1.isStringExp())
{
lowerbound = 0;
upperbound = se.len;
}
else if (e1.op == EXP.null_)
{
lowerbound = 0;
upperbound = 0;
}
else if (VectorExp ve = e1.isVectorExp())
{
// ve is not handled but a proper error message is returned
// this is to prevent https://issues.dlang.org/show_bug.cgi?id=20042
lowerbound = 0;
upperbound = ve.dim;
}
else
assert(0);
aggregate = e1;
firstIndex = lowerbound;
}
if (upperbound == lowerbound)
return newval;
// For slice assignment, we check that the lengths match.
if (!isBlockAssignment)
{
const srclen = resolveArrayLength(newval);
if (srclen != (upperbound - lowerbound))
{
e.error("array length mismatch assigning `[0..%llu]` to `[%llu..%llu]`",
ulong(srclen), ulong(lowerbound), ulong(upperbound));
return CTFEExp.cantexp;
}
}
if (auto existingSE = aggregate.isStringExp())
{
if (existingSE.ownedByCtfe != OwnedBy.ctfe)
{
e.error("cannot modify read-only string literal `%s`", existingSE.toChars());
return CTFEExp.cantexp;
}
if (auto se = newval.isSliceExp())
{
auto aggr2 = se.e1;
const srclower = se.lwr.toInteger();
const srcupper = se.upr.toInteger();
if (aggregate == aggr2 &&
lowerbound < srcupper && srclower < upperbound)
{
e.error("overlapping slice assignment `[%llu..%llu] = [%llu..%llu]`",
ulong(lowerbound), ulong(upperbound), ulong(srclower), ulong(srcupper));
return CTFEExp.cantexp;
}
version (all) // todo: instead we can directly access to each elements of the slice
{
Expression orignewval = newval;
newval = resolveSlice(newval);
if (CTFEExp.isCantExp(newval))
{
e.error("CTFE internal error: slice `%s`", orignewval.toChars());
return CTFEExp.cantexp;
}
}
assert(newval.op != EXP.slice);
}
if (auto se = newval.isStringExp())
{
sliceAssignStringFromString(existingSE, se, cast(size_t)firstIndex);
return newval;
}
if (auto ale = newval.isArrayLiteralExp())
{
/* Mixed slice: it was initialized as a string literal.
* Now a slice of it is being set with an array literal.
*/
sliceAssignStringFromArrayLiteral(existingSE, ale, cast(size_t)firstIndex);
return newval;
}
// String literal block slice assign
const value = cast(dchar)newval.toInteger();
foreach (i; 0 .. upperbound - lowerbound)
{
existingSE.setCodeUnit(cast(size_t)(i + firstIndex), value);
}
if (goal == CTFEGoal.Nothing)
return null; // avoid creating an unused literal
auto retslice = ctfeEmplaceExp!SliceExp(e.loc, existingSE,
ctfeEmplaceExp!IntegerExp(e.loc, firstIndex, Type.tsize_t),
ctfeEmplaceExp!IntegerExp(e.loc, firstIndex + upperbound - lowerbound, Type.tsize_t));
retslice.type = e.type;
return interpret(pue, retslice, istate);
}
if (auto existingAE = aggregate.isArrayLiteralExp())
{
if (existingAE.ownedByCtfe != OwnedBy.ctfe)
{
e.error("cannot modify read-only constant `%s`", existingAE.toChars());
return CTFEExp.cantexp;
}
if (newval.op == EXP.slice && !isBlockAssignment)
{
auto se = newval.isSliceExp();
auto aggr2 = se.e1;
const srclower = se.lwr.toInteger();
const srcupper = se.upr.toInteger();
const wantCopy = (newval.type.toBasetype().nextOf().baseElemOf().ty == Tstruct);
//printf("oldval = %p %s[%d..%u]\nnewval = %p %s[%llu..%llu] wantCopy = %d\n",
// aggregate, aggregate.toChars(), lowerbound, upperbound,
// aggr2, aggr2.toChars(), srclower, srcupper, wantCopy);
if (wantCopy)
{
// Currently overlapping for struct array is allowed.
// The order of elements processing depends on the overlapping.
// https://issues.dlang.org/show_bug.cgi?id=14024
assert(aggr2.op == EXP.arrayLiteral);
Expressions* oldelems = existingAE.elements;
Expressions* newelems = aggr2.isArrayLiteralExp().elements;
Type elemtype = aggregate.type.nextOf();
bool needsPostblit = e.e2.isLvalue();
if (aggregate == aggr2 && srclower < lowerbound && lowerbound < srcupper)
{
// reverse order
for (auto i = upperbound - lowerbound; 0 < i--;)
{
Expression oldelem = (*oldelems)[cast(size_t)(i + firstIndex)];
Expression newelem = (*newelems)[cast(size_t)(i + srclower)];
newelem = copyLiteral(newelem).copy();
newelem.type = elemtype;
if (needsPostblit)
{
if (Expression x = evaluatePostblit(istate, newelem))
return x;
}
if (Expression x = evaluateDtor(istate, oldelem))
return x;
(*oldelems)[cast(size_t)(lowerbound + i)] = newelem;
}
}
else
{
// normal order
for (auto i = 0; i < upperbound - lowerbound; i++)
{
Expression oldelem = (*oldelems)[cast(size_t)(i + firstIndex)];
Expression newelem = (*newelems)[cast(size_t)(i + srclower)];
newelem = copyLiteral(newelem).copy();
newelem.type = elemtype;
if (needsPostblit)
{
if (Expression x = evaluatePostblit(istate, newelem))
return x;
}
if (Expression x = evaluateDtor(istate, oldelem))
return x;
(*oldelems)[cast(size_t)(lowerbound + i)] = newelem;
}
}
//assert(0);
return newval; // oldval?
}
if (aggregate == aggr2 &&
lowerbound < srcupper && srclower < upperbound)
{
e.error("overlapping slice assignment `[%llu..%llu] = [%llu..%llu]`",
ulong(lowerbound), ulong(upperbound), ulong(srclower), ulong(srcupper));
return CTFEExp.cantexp;
}
version (all) // todo: instead we can directly access to each elements of the slice
{
Expression orignewval = newval;
newval = resolveSlice(newval);
if (CTFEExp.isCantExp(newval))
{
e.error("CTFE internal error: slice `%s`", orignewval.toChars());
return CTFEExp.cantexp;
}
}
// no overlapping
//length?
assert(newval.op != EXP.slice);
}
if (newval.op == EXP.string_ && !isBlockAssignment)
{
/* Mixed slice: it was initialized as an array literal of chars/integers.
* Now a slice of it is being set with a string.
*/
sliceAssignArrayLiteralFromString(existingAE, newval.isStringExp(), cast(size_t)firstIndex);
return newval;
}
if (newval.op == EXP.arrayLiteral && !isBlockAssignment)
{
Expressions* oldelems = existingAE.elements;
Expressions* newelems = newval.isArrayLiteralExp().elements;
Type elemtype = existingAE.type.nextOf();
bool needsPostblit = e.op != EXP.blit && e.e2.isLvalue();
foreach (j, newelem; *newelems)
{
newelem = paintTypeOntoLiteral(elemtype, newelem);
if (needsPostblit)
{
Expression x = evaluatePostblit(istate, newelem);
if (exceptionOrCantInterpret(x))
return x;
}
(*oldelems)[cast(size_t)(j + firstIndex)] = newelem;
}
return newval;
}
/* Block assignment, initialization of static arrays
* x[] = newval
* x may be a multidimensional static array. (Note that this
* only happens with array literals, never with strings).
*/
struct RecursiveBlock
{
InterState* istate;
Expression newval;
bool refCopy;
bool needsPostblit;
bool needsDtor;
Expression assignTo(ArrayLiteralExp ae)
{
return assignTo(ae, 0, ae.elements.length);
}
Expression assignTo(ArrayLiteralExp ae, size_t lwr, size_t upr)
{
Expressions* w = ae.elements;
assert(ae.type.ty == Tsarray || ae.type.ty == Tarray);
bool directblk = (cast(TypeArray)ae.type).next.equivalent(newval.type);
for (size_t k = lwr; k < upr; k++)
{
if (!directblk && (*w)[k].op == EXP.arrayLiteral)
{
// Multidimensional array block assign
if (Expression ex = assignTo((*w)[k].isArrayLiteralExp()))
return ex;
}
else if (refCopy)
{
(*w)[k] = newval;
}
else if (!needsPostblit && !needsDtor)
{
assignInPlace((*w)[k], newval);
}
else
{
Expression oldelem = (*w)[k];
Expression tmpelem = needsDtor ? copyLiteral(oldelem).copy() : null;
assignInPlace(oldelem, newval);
if (needsPostblit)
{
if (Expression ex = evaluatePostblit(istate, oldelem))
return ex;
}
if (needsDtor)
{
// https://issues.dlang.org/show_bug.cgi?id=14860
if (Expression ex = evaluateDtor(istate, tmpelem))
return ex;
}
}
}
return null;
}
}
Type tn = newval.type.toBasetype();
bool wantRef = (tn.ty == Tarray || isAssocArray(tn) || tn.ty == Tclass);
bool cow = newval.op != EXP.structLiteral && newval.op != EXP.arrayLiteral && newval.op != EXP.string_;
Type tb = tn.baseElemOf();
StructDeclaration sd = (tb.ty == Tstruct ? (cast(TypeStruct)tb).sym : null);
RecursiveBlock rb;
rb.istate = istate;
rb.newval = newval;
rb.refCopy = wantRef || cow;
rb.needsPostblit = sd && sd.postblit && e.op != EXP.blit && e.e2.isLvalue();
rb.needsDtor = sd && sd.dtor && e.op == EXP.assign;
if (Expression ex = rb.assignTo(existingAE, cast(size_t)lowerbound, cast(size_t)upperbound))
return ex;
if (goal == CTFEGoal.Nothing)
return null; // avoid creating an unused literal
auto retslice = ctfeEmplaceExp!SliceExp(e.loc, existingAE,
ctfeEmplaceExp!IntegerExp(e.loc, firstIndex, Type.tsize_t),
ctfeEmplaceExp!IntegerExp(e.loc, firstIndex + upperbound - lowerbound, Type.tsize_t));
retslice.type = e.type;
return interpret(pue, retslice, istate);
}
e.error("slice operation `%s = %s` cannot be evaluated at compile time", e1.toChars(), newval.toChars());
return CTFEExp.cantexp;
}
override void visit(AssignExp e)
{
interpretAssignCommon(e, null);
}
override void visit(BinAssignExp e)
{
switch (e.op)
{
case EXP.addAssign:
interpretAssignCommon(e, &Add);
return;
case EXP.minAssign:
interpretAssignCommon(e, &Min);
return;
case EXP.concatenateAssign:
case EXP.concatenateElemAssign:
case EXP.concatenateDcharAssign:
interpretAssignCommon(e, &ctfeCat);
return;
case EXP.mulAssign:
interpretAssignCommon(e, &Mul);
return;
case EXP.divAssign:
interpretAssignCommon(e, &Div);
return;
case EXP.modAssign:
interpretAssignCommon(e, &Mod);
return;
case EXP.leftShiftAssign:
interpretAssignCommon(e, &Shl);
return;
case EXP.rightShiftAssign:
interpretAssignCommon(e, &Shr);
return;
case EXP.unsignedRightShiftAssign:
interpretAssignCommon(e, &Ushr);
return;
case EXP.andAssign:
interpretAssignCommon(e, &And);
return;
case EXP.orAssign:
interpretAssignCommon(e, &Or);
return;
case EXP.xorAssign:
interpretAssignCommon(e, &Xor);
return;
case EXP.powAssign:
interpretAssignCommon(e, &Pow);
return;
default:
assert(0);
}
}
override void visit(PostExp e)
{
debug (LOG)
{
printf("%s PostExp::interpret() %s\n", e.loc.toChars(), e.toChars());
}
if (e.op == EXP.plusPlus)
interpretAssignCommon(e, &Add, 1);
else
interpretAssignCommon(e, &Min, 1);
debug (LOG)
{
if (CTFEExp.isCantExp(result))
printf("PostExp::interpret() CANT\n");
}
}
/* Return 1 if e is a p1 > p2 or p1 >= p2 pointer comparison;
* -1 if e is a p1 < p2 or p1 <= p2 pointer comparison;
* 0 otherwise
*/
static int isPointerCmpExp(Expression e, Expression* p1, Expression* p2)
{
int ret = 1;
while (e.op == EXP.not)
{
ret *= -1;
e = e.isNotExp().e1;
}
switch (e.op)
{
case EXP.lessThan:
case EXP.lessOrEqual:
ret *= -1;
goto case; /+ fall through +/
case EXP.greaterThan:
case EXP.greaterOrEqual:
*p1 = e.isBinExp().e1;
*p2 = e.isBinExp().e2;
if (!(isPointer((*p1).type) && isPointer((*p2).type)))
ret = 0;
break;
default:
ret = 0;
break;
}
return ret;
}
/** If this is a four pointer relation, evaluate it, else return NULL.
*
* This is an expression of the form (p1 > q1 && p2 < q2) or (p1 < q1 || p2 > q2)
* where p1, p2 are expressions yielding pointers to memory block p,
* and q1, q2 are expressions yielding pointers to memory block q.
* This expression is valid even if p and q are independent memory
* blocks and are therefore not normally comparable; the && form returns true
* if [p1..p2] lies inside [q1..q2], and false otherwise; the || form returns
* true if [p1..p2] lies outside [q1..q2], and false otherwise.
*
* Within the expression, any ordering of p1, p2, q1, q2 is permissible;
* the comparison operators can be any of >, <, <=, >=, provided that
* both directions (p > q and p < q) are checked. Additionally the
* relational sub-expressions can be negated, eg
* (!(q1 < p1) && p2 <= q2) is valid.
*/
private void interpretFourPointerRelation(UnionExp* pue, BinExp e)
{
assert(e.op == EXP.andAnd || e.op == EXP.orOr);
/* It can only be an isInside expression, if both e1 and e2 are
* directional pointer comparisons.
* Note that this check can be made statically; it does not depends on
* any runtime values. This allows a JIT implementation to compile a
* special AndAndPossiblyInside, keeping the normal AndAnd case efficient.
*/
// Save the pointer expressions and the comparison directions,
// so we can use them later.
Expression p1 = null;
Expression p2 = null;
Expression p3 = null;
Expression p4 = null;
int dir1 = isPointerCmpExp(e.e1, &p1, &p2);
int dir2 = isPointerCmpExp(e.e2, &p3, &p4);
if (dir1 == 0 || dir2 == 0)
{
result = null;
return;
}
//printf("FourPointerRelation %s\n", toChars());
UnionExp ue1 = void;
UnionExp ue2 = void;
UnionExp ue3 = void;
UnionExp ue4 = void;
// Evaluate the first two pointers
p1 = interpret(&ue1, p1, istate);
if (exceptionOrCant(p1))
return;
p2 = interpret(&ue2, p2, istate);
if (exceptionOrCant(p2))
return;
dinteger_t ofs1, ofs2;
Expression agg1 = getAggregateFromPointer(p1, &ofs1);
Expression agg2 = getAggregateFromPointer(p2, &ofs2);
if (!pointToSameMemoryBlock(agg1, agg2) && agg1.op != EXP.null_ && agg2.op != EXP.null_)
{
// Here it is either CANT_INTERPRET,
// or an IsInside comparison returning false.
p3 = interpret(&ue3, p3, istate);
if (CTFEExp.isCantExp(p3))
return;
// Note that it is NOT legal for it to throw an exception!
Expression except = null;
if (exceptionOrCantInterpret(p3))
except = p3;
else
{
p4 = interpret(&ue4, p4, istate);
if (CTFEExp.isCantExp(p4))
{
result = p4;
return;
}
if (exceptionOrCantInterpret(p4))
except = p4;
}
if (except)
{
e.error("comparison `%s` of pointers to unrelated memory blocks remains indeterminate at compile time because exception `%s` was thrown while evaluating `%s`", e.e1.toChars(), except.toChars(), e.e2.toChars());
result = CTFEExp.cantexp;
return;
}
dinteger_t ofs3, ofs4;
Expression agg3 = getAggregateFromPointer(p3, &ofs3);
Expression agg4 = getAggregateFromPointer(p4, &ofs4);
// The valid cases are:
// p1 > p2 && p3 > p4 (same direction, also for < && <)
// p1 > p2 && p3 < p4 (different direction, also < && >)
// Changing any > into >= doesn't affect the result
if ((dir1 == dir2 && pointToSameMemoryBlock(agg1, agg4) && pointToSameMemoryBlock(agg2, agg3)) ||
(dir1 != dir2 && pointToSameMemoryBlock(agg1, agg3) && pointToSameMemoryBlock(agg2, agg4)))
{
// it's a legal two-sided comparison
emplaceExp!(IntegerExp)(pue, e.loc, (e.op == EXP.andAnd) ? 0 : 1, e.type);
result = pue.exp();
return;
}
// It's an invalid four-pointer comparison. Either the second
// comparison is in the same direction as the first, or else
// more than two memory blocks are involved (either two independent
// invalid comparisons are present, or else agg3 == agg4).
e.error("comparison `%s` of pointers to unrelated memory blocks is indeterminate at compile time, even when combined with `%s`.", e.e1.toChars(), e.e2.toChars());
result = CTFEExp.cantexp;
return;
}
// The first pointer expression didn't need special treatment, so we
// we need to interpret the entire expression exactly as a normal && or ||.
// This is easy because we haven't evaluated e2 at all yet, and we already
// know it will return a bool.
// But we mustn't evaluate the pointer expressions in e1 again, in case
// they have side-effects.
bool nott = false;
Expression ex = e.e1;
while (1)
{
if (auto ne = ex.isNotExp())
{
nott = !nott;
ex = ne.e1;
}
else
break;
}
/** Negate relational operator, eg >= becomes <
* Params:
* op = comparison operator to negate
* Returns:
* negate operator
*/
static EXP negateRelation(EXP op) pure
{
switch (op)
{
case EXP.greaterOrEqual: op = EXP.lessThan; break;
case EXP.greaterThan: op = EXP.lessOrEqual; break;
case EXP.lessOrEqual: op = EXP.greaterThan; break;
case EXP.lessThan: op = EXP.greaterOrEqual; break;
default: assert(0);
}
return op;
}
const EXP cmpop = nott ? negateRelation(ex.op) : ex.op;
const cmp = comparePointers(cmpop, agg1, ofs1, agg2, ofs2);
// We already know this is a valid comparison.
assert(cmp >= 0);
if (e.op == EXP.andAnd && cmp == 1 || e.op == EXP.orOr && cmp == 0)
{
result = interpret(pue, e.e2, istate);
return;
}
emplaceExp!(IntegerExp)(pue, e.loc, (e.op == EXP.andAnd) ? 0 : 1, e.type);
result = pue.exp();
}
override void visit(LogicalExp e)
{
debug (LOG)
{
printf("%s LogicalExp::interpret() %s\n", e.loc.toChars(), e.toChars());
}
// Check for an insidePointer expression, evaluate it if so
interpretFourPointerRelation(pue, e);
if (result)
return;
UnionExp ue1 = void;
result = interpret(&ue1, e.e1, istate);
if (exceptionOrCant(result))
return;
bool res;
const andand = e.op == EXP.andAnd;
if (andand ? result.toBool().hasValue(false) : isTrueBool(result))
res = !andand;
else if (andand ? isTrueBool(result) : result.toBool().hasValue(false))
{
UnionExp ue2 = void;
result = interpret(&ue2, e.e2, istate);
if (exceptionOrCant(result))
return;
if (result.op == EXP.voidExpression)
{
assert(e.type.ty == Tvoid);
result = null;
return;
}
if (result.toBool().hasValue(false))
res = false;
else if (isTrueBool(result))
res = true;
else
{
e.error("`%s` does not evaluate to a `bool`", result.toChars());
result = CTFEExp.cantexp;
return;
}
}
else
{
e.error("`%s` cannot be interpreted as a `bool`", result.toChars());
result = CTFEExp.cantexp;
return;
}
incUsageCtfe(istate, e.e2.loc);
if (goal != CTFEGoal.Nothing)
{
if (e.type.equals(Type.tbool))
result = IntegerExp.createBool(res);
else
{
emplaceExp!(IntegerExp)(pue, e.loc, res, e.type);
result = pue.exp();
}
}
}
// Print a stack trace, starting from callingExp which called fd.
// To shorten the stack trace, try to detect recursion.
private void showCtfeBackTrace(CallExp callingExp, FuncDeclaration fd)
{
if (ctfeGlobals.stackTraceCallsToSuppress > 0)
{
--ctfeGlobals.stackTraceCallsToSuppress;
return;
}
errorSupplemental(callingExp.loc, "called from here: `%s`", callingExp.toChars());
// Quit if it's not worth trying to compress the stack trace
if (ctfeGlobals.callDepth < 6 || global.params.verbose)
return;
// Recursion happens if the current function already exists in the call stack.
int numToSuppress = 0;
int recurseCount = 0;
int depthSoFar = 0;
InterState* lastRecurse = istate;
for (InterState* cur = istate; cur; cur = cur.caller)
{
if (cur.fd == fd)
{
++recurseCount;
numToSuppress = depthSoFar;
lastRecurse = cur;
}
++depthSoFar;
}
// We need at least three calls to the same function, to make compression worthwhile
if (recurseCount < 2)
return;
// We found a useful recursion. Print all the calls involved in the recursion
errorSupplemental(fd.loc, "%d recursive calls to function `%s`", recurseCount, fd.toChars());
for (InterState* cur = istate; cur.fd != fd; cur = cur.caller)
{
errorSupplemental(cur.fd.loc, "recursively called from function `%s`", cur.fd.toChars());
}
// We probably didn't enter the recursion in this function.
// Go deeper to find the real beginning.
InterState* cur = istate;
while (lastRecurse.caller && cur.fd == lastRecurse.caller.fd)
{
cur = cur.caller;
lastRecurse = lastRecurse.caller;
++numToSuppress;
}
ctfeGlobals.stackTraceCallsToSuppress = numToSuppress;
}
override void visit(CallExp e)
{
debug (LOG)
{
printf("%s CallExp::interpret() %s\n", e.loc.toChars(), e.toChars());
}
Expression pthis = null;
FuncDeclaration fd = null;
Expression ecall = interpretRegion(e.e1, istate);
if (exceptionOrCant(ecall))
return;
if (auto dve = ecall.isDotVarExp())
{
// Calling a member function
pthis = dve.e1;
fd = dve.var.isFuncDeclaration();
assert(fd);
if (auto dte = pthis.isDotTypeExp())
pthis = dte.e1;
}
else if (auto ve = ecall.isVarExp())
{
fd = ve.var.isFuncDeclaration();
assert(fd);
// If `_d_HookTraceImpl` is found, resolve the underlying hook and replace `e` and `fd` with it.
removeHookTraceImpl(e, fd);
if (fd.ident == Id.__ArrayPostblit || fd.ident == Id.__ArrayDtor)
{
assert(e.arguments.length == 1);
Expression ea = (*e.arguments)[0];
// printf("1 ea = %s %s\n", ea.type.toChars(), ea.toChars());
if (auto se = ea.isSliceExp())
ea = se.e1;
if (auto ce = ea.isCastExp())
ea = ce.e1;
// printf("2 ea = %s, %s %s\n", ea.type.toChars(), EXPtoString(ea.op).ptr, ea.toChars());
if (ea.op == EXP.variable || ea.op == EXP.symbolOffset)
result = getVarExp(e.loc, istate, (cast(SymbolExp)ea).var, CTFEGoal.RValue);
else if (auto ae = ea.isAddrExp())
result = interpretRegion(ae.e1, istate);
// https://issues.dlang.org/show_bug.cgi?id=18871
// https://issues.dlang.org/show_bug.cgi?id=18819
else if (auto ale = ea.isArrayLiteralExp())
result = interpretRegion(ale, istate);
else
assert(0);
if (CTFEExp.isCantExp(result))
return;
if (fd.ident == Id.__ArrayPostblit)
result = evaluatePostblit(istate, result);
else
result = evaluateDtor(istate, result);
if (!result)
result = CTFEExp.voidexp;
return;
}
else if (fd.ident == Id._d_arraysetlengthT)
{
// In expressionsem.d `ea.length = eb;` got lowered to `_d_arraysetlengthT(ea, eb);`.
// The following code will rewrite it back to `ea.length = eb` and then interpret that expression.
assert(e.arguments.length == 2);
Expression ea = (*e.arguments)[0];
Expression eb = (*e.arguments)[1];
auto ale = ctfeEmplaceExp!ArrayLengthExp(e.loc, ea);
ale.type = Type.tsize_t;
AssignExp ae = ctfeEmplaceExp!AssignExp(e.loc, ale, eb);
ae.type = ea.type;
// if (global.params.verbose)
// message("interpret %s =>\n %s", e.toChars(), ae.toChars());
result = interpretRegion(ae, istate);
return;
}
else if (isArrayConstructionOrAssign(fd.ident))
{
// In expressionsem.d, the following lowerings were performed:
// * `T[x] ea = eb;` to `_d_array{,set}ctor(ea[], eb[]);`.
// * `ea = eb` to `_d_array{,setassign,assign_l,assign_r}(ea[], eb)`.
// The following code will rewrite them back to `ea = eb` and
// then interpret that expression.
if (fd.ident == Id._d_arrayctor)
assert(e.arguments.length == 3);
else
assert(e.arguments.length == 2);
Expression ea = (*e.arguments)[0];
if (ea.isCastExp)
ea = ea.isCastExp.e1;
Expression eb = (*e.arguments)[1];
if (eb.isCastExp() && fd.ident != Id._d_arraysetctor)
eb = eb.isCastExp.e1;
Expression rewrittenExp;
if (fd.ident == Id._d_arrayctor || fd.ident == Id._d_arraysetctor)
rewrittenExp = new ConstructExp(e.loc, ea, eb);
else
rewrittenExp = new AssignExp(e.loc, ea, eb);
rewrittenExp.type = ea.type;
result = interpret(rewrittenExp, istate);
return;
}
else if (fd.ident == Id._d_arrayappendT || fd.ident == Id._d_arrayappendTTrace)
{
// In expressionsem.d `ea ~= eb` was lowered to `_d_arrayappendT{,Trace}({file, line, funcname}, ea, eb);`.
// The following code will rewrite it back to `ea ~= eb` and then interpret that expression.
Expression lhs, rhs;
if (fd.ident == Id._d_arrayappendT)
{
assert(e.arguments.length == 2);
lhs = (*e.arguments)[0];
rhs = (*e.arguments)[1];
}
else
{
assert(e.arguments.length == 5);
lhs = (*e.arguments)[3];
rhs = (*e.arguments)[4];
}
auto cae = new CatAssignExp(e.loc, lhs, rhs);
cae.type = e.type;
result = interpretRegion(cae, istate, CTFEGoal.LValue);
return;
}
else if (fd.ident == Id._d_arrayappendcTX)
assert(0, "CTFE cannot interpret _d_arrayappendcTX!");
}
else if (auto soe = ecall.isSymOffExp())
{
fd = soe.var.isFuncDeclaration();
assert(fd && soe.offset == 0);
}
else if (auto de = ecall.isDelegateExp())
{
// Calling a delegate
fd = de.func;
pthis = de.e1;
// Special handling for: &nestedfunc --> DelegateExp(VarExp(nestedfunc), nestedfunc)
if (auto ve = pthis.isVarExp())
if (ve.var == fd)
pthis = null; // context is not necessary for CTFE
}
else if (auto fe = ecall.isFuncExp())
{
// Calling a delegate literal
fd = fe.fd;
}
else
{
// delegate.funcptr()
// others
e.error("cannot call `%s` at compile time", e.toChars());
result = CTFEExp.cantexp;
return;
}
if (!fd)
{
e.error("CTFE internal error: cannot evaluate `%s` at compile time", e.toChars());
result = CTFEExp.cantexp;
return;
}
if (pthis)
{
// Member function call
// Currently this is satisfied because closure is not yet supported.
assert(!fd.isNested() || fd.needThis());
if (pthis.op == EXP.typeid_)
{
pthis.error("static variable `%s` cannot be read at compile time", pthis.toChars());
result = CTFEExp.cantexp;
return;
}
assert(pthis);
if (pthis.op == EXP.null_)
{
assert(pthis.type.toBasetype().ty == Tclass);
e.error("function call through null class reference `%s`", pthis.toChars());
result = CTFEExp.cantexp;
return;
}
assert(pthis.op == EXP.structLiteral || pthis.op == EXP.classReference || pthis.op == EXP.type);
if (fd.isVirtual() && !e.directcall)
{
// Make a virtual function call.
// Get the function from the vtable of the original class
ClassDeclaration cd = pthis.isClassReferenceExp().originalClass();
// We can't just use the vtable index to look it up, because
// vtables for interfaces don't get populated until the glue layer.
fd = cd.findFunc(fd.ident, fd.type.isTypeFunction());
assert(fd);
}
}
if (fd && fd.semanticRun >= PASS.semantic3done && fd.hasSemantic3Errors())
{
e.error("CTFE failed because of previous errors in `%s`", fd.toChars());
result = CTFEExp.cantexp;
return;
}
// Check for built-in functions
result = evaluateIfBuiltin(pue, istate, e.loc, fd, e.arguments, pthis);
if (result)
return;
if (!fd.fbody)
{
e.error("`%s` cannot be interpreted at compile time, because it has no available source code", fd.toChars());
result = CTFEExp.showcontext;
return;
}
result = interpretFunction(pue, fd, istate, e.arguments, pthis);
if (result.op == EXP.voidExpression)
return;
if (!exceptionOrCantInterpret(result))
{
if (goal != CTFEGoal.LValue) // Peel off CTFE reference if it's unnecessary
{
if (result == pue.exp())
result = pue.copy();
result = interpret(pue, result, istate);
}
}
if (!exceptionOrCantInterpret(result))
{
result = paintTypeOntoLiteral(pue, e.type, result);
result.loc = e.loc;
}
else if (CTFEExp.isCantExp(result) && !global.gag)
showCtfeBackTrace(e, fd); // Print a stack trace.
}
override void visit(CommaExp e)
{
debug (LOG)
{
printf("%s CommaExp::interpret() %s\n", e.loc.toChars(), e.toChars());
}
bool isNewThrowableHook()
{
auto de = e.e1.isDeclarationExp();
if (de is null)
return false;
auto vd = de.declaration.isVarDeclaration();
if (vd is null)
return false;
auto ei = vd._init.isExpInitializer();
if (ei is null)
return false;
auto ce = ei.exp.isConstructExp();
if (ce is null)
return false;
return isRuntimeHook(ce.e2, Id._d_newThrowable) !is null;
}
if (auto ce = isRuntimeHook(e.e1, Id._d_arrayappendcTX))
{
// In expressionsem.d `arr ~= elem` was lowered to
// `_d_arrayappendcTX(arr, elem), arr[arr.length - 1] = elem, elem;`.
// The following code will rewrite it back to `arr ~= elem`
// and then interpret that expression.
assert(ce.arguments.length == 2);
auto arr = (*ce.arguments)[0];
auto elem = e.e2.isConstructExp().e2;
assert(elem);
auto cae = new CatAssignExp(e.loc, arr, elem);
cae.type = arr.type;
result = interpret(cae, istate);
return;
}
else if (isNewThrowableHook())
{
// In expressionsem.d `throw new Exception(args)` was lowered to
// `throw (tmp = _d_newThrowable!Exception(), tmp.ctor(args), tmp)`.
// The following code will rewrite it back to `throw new Exception(args)`
// and then interpret this expression instead.
auto ce = e.e2.isCallExp();
assert(ce);
auto ne = new NewExp(e.loc, null, e.type, ce.arguments);
ne.type = e.e1.type;
result = interpret(ne, istate);
return;
}
// If it creates a variable, and there's no context for
// the variable to be created in, we need to create one now.
InterState istateComma;
if (!istate && firstComma(e.e1).op == EXP.declaration)
{
ctfeGlobals.stack.startFrame(null);
istate = &istateComma;
}
void endTempStackFrame()
{
// If we created a temporary stack frame, end it now.
if (istate == &istateComma)
ctfeGlobals.stack.endFrame();
}
result = CTFEExp.cantexp;
// If the comma returns a temporary variable, it needs to be an lvalue
// (this is particularly important for struct constructors)
if (e.e1.op == EXP.declaration &&
e.e2.op == EXP.variable &&
e.e1.isDeclarationExp().declaration == e.e2.isVarExp().var &&
e.e2.isVarExp().var.storage_class & STC.ctfe)
{
VarExp ve = e.e2.isVarExp();
VarDeclaration v = ve.var.isVarDeclaration();
ctfeGlobals.stack.push(v);
if (!v._init && !getValue(v))
{
setValue(v, copyLiteral(v.type.defaultInitLiteral(e.loc)).copy());
}
if (!getValue(v))
{
Expression newval = v._init.initializerToExpression();
// Bug 4027. Copy constructors are a weird case where the
// initializer is a void function (the variable is modified
// through a reference parameter instead).
newval = interpretRegion(newval, istate);
if (exceptionOrCant(newval))
return endTempStackFrame();
if (newval.op != EXP.voidExpression)
{
// v isn't necessarily null.
setValueWithoutChecking(v, copyLiteral(newval).copy());
}
}
}
else
{
UnionExp ue = void;
auto e1 = interpret(&ue, e.e1, istate, CTFEGoal.Nothing);
if (exceptionOrCant(e1))
return endTempStackFrame();
}
result = interpret(pue, e.e2, istate, goal);
return endTempStackFrame();
}
override void visit(CondExp e)
{
debug (LOG)
{
printf("%s CondExp::interpret() %s\n", e.loc.toChars(), e.toChars());
}
UnionExp uecond = void;
Expression econd;
econd = interpret(&uecond, e.econd, istate);
if (exceptionOrCant(econd))
return;
if (isPointer(e.econd.type))
{
if (econd.op != EXP.null_)
{
econd = IntegerExp.createBool(true);
}
}
if (isTrueBool(econd))
{
result = interpret(pue, e.e1, istate, goal);
incUsageCtfe(istate, e.e1.loc);
}
else if (econd.toBool().hasValue(false))
{
result = interpret(pue, e.e2, istate, goal);
incUsageCtfe(istate, e.e2.loc);
}
else
{
e.error("`%s` does not evaluate to boolean result at compile time", e.econd.toChars());
result = CTFEExp.cantexp;
}
}
override void visit(ArrayLengthExp e)
{
debug (LOG)
{
printf("%s ArrayLengthExp::interpret() %s\n", e.loc.toChars(), e.toChars());
}
UnionExp ue1;
Expression e1 = interpret(&ue1, e.e1, istate);
assert(e1);
if (exceptionOrCant(e1))
return;
if (e1.op != EXP.string_ && e1.op != EXP.arrayLiteral && e1.op != EXP.slice && e1.op != EXP.null_)
{
e.error("`%s` cannot be evaluated at compile time", e.toChars());
result = CTFEExp.cantexp;
return;
}
emplaceExp!(IntegerExp)(pue, e.loc, resolveArrayLength(e1), e.type);
result = pue.exp();
}
/**
* Interpret the vector expression as an array literal.
* Params:
* pue = non-null pointer to temporary storage that can be used to store the return value
* e = Expression to interpret
* Returns:
* resulting array literal or 'e' if unable to interpret
*/
static Expression interpretVectorToArray(UnionExp* pue, VectorExp e)
{
if (auto ale = e.e1.isArrayLiteralExp())
return ale; // it's already an array literal
if (e.e1.op == EXP.int64 || e.e1.op == EXP.float64)
{
// Convert literal __vector(int) -> __vector([array])
auto elements = new Expressions(e.dim);
foreach (ref element; *elements)
element = copyLiteral(e.e1).copy();
auto type = (e.type.ty == Tvector) ? e.type.isTypeVector().basetype : e.type.isTypeSArray();
assert(type);
emplaceExp!(ArrayLiteralExp)(pue, e.loc, type, elements);
auto ale = pue.exp().isArrayLiteralExp();
ale.ownedByCtfe = OwnedBy.ctfe;
return ale;
}
return e;
}
override void visit(VectorExp e)
{
debug (LOG)
{
printf("%s VectorExp::interpret() %s\n", e.loc.toChars(), e.toChars());
}
if (e.ownedByCtfe >= OwnedBy.ctfe) // We've already interpreted all the elements
{
result = e;
return;
}
Expression e1 = interpret(pue, e.e1, istate);
assert(e1);
if (exceptionOrCant(e1))
return;
if (e1.op != EXP.arrayLiteral && e1.op != EXP.int64 && e1.op != EXP.float64)
{
e.error("`%s` cannot be evaluated at compile time", e.toChars());
result = CTFEExp.cantexp;
return;
}
if (e1 == pue.exp())
e1 = pue.copy();
emplaceExp!(VectorExp)(pue, e.loc, e1, e.to);
auto ve = pue.exp().isVectorExp();
ve.type = e.type;
ve.dim = e.dim;
ve.ownedByCtfe = OwnedBy.ctfe;
result = ve;
}
override void visit(VectorArrayExp e)
{
debug (LOG)
{
printf("%s VectorArrayExp::interpret() %s\n", e.loc.toChars(), e.toChars());
}
Expression e1 = interpret(pue, e.e1, istate);
assert(e1);
if (exceptionOrCant(e1))
return;
if (auto ve = e1.isVectorExp())
{
result = interpretVectorToArray(pue, ve);
if (result.op != EXP.vector)
return;
}
e.error("`%s` cannot be evaluated at compile time", e.toChars());
result = CTFEExp.cantexp;
}
override void visit(DelegatePtrExp e)
{
debug (LOG)
{
printf("%s DelegatePtrExp::interpret() %s\n", e.loc.toChars(), e.toChars());
}
Expression e1 = interpret(pue, e.e1, istate);
assert(e1);
if (exceptionOrCant(e1))
return;
e.error("`%s` cannot be evaluated at compile time", e.toChars());
result = CTFEExp.cantexp;
}
override void visit(DelegateFuncptrExp e)
{
debug (LOG)
{
printf("%s DelegateFuncptrExp::interpret() %s\n", e.loc.toChars(), e.toChars());
}
Expression e1 = interpret(pue, e.e1, istate);
assert(e1);
if (exceptionOrCant(e1))
return;
e.error("`%s` cannot be evaluated at compile time", e.toChars());
result = CTFEExp.cantexp;
}
static bool resolveIndexing(IndexExp e, InterState* istate, Expression* pagg, uinteger_t* pidx, bool modify)
{
assert(e.e1.type.toBasetype().ty != Taarray);
if (e.e1.type.toBasetype().ty == Tpointer)
{
// Indexing a pointer. Note that there is no $ in this case.
Expression e1 = interpretRegion(e.e1, istate);
if (exceptionOrCantInterpret(e1))
return false;
Expression e2 = interpretRegion(e.e2, istate);
if (exceptionOrCantInterpret(e2))
return false;
sinteger_t indx = e2.toInteger();
dinteger_t ofs;
Expression agg = getAggregateFromPointer(e1, &ofs);
if (agg.op == EXP.null_)
{
e.error("cannot index through null pointer `%s`", e.e1.toChars());
return false;
}
if (agg.op == EXP.int64)
{
e.error("cannot index through invalid pointer `%s` of value `%s`", e.e1.toChars(), e1.toChars());
return false;
}
// Pointer to a non-array variable
if (agg.op == EXP.symbolOffset)
{
e.error("mutable variable `%s` cannot be %s at compile time, even through a pointer", cast(char*)(modify ? "modified" : "read"), agg.isSymOffExp().var.toChars());
return false;
}
if (agg.op == EXP.arrayLiteral || agg.op == EXP.string_)
{
dinteger_t len = resolveArrayLength(agg);
if (ofs + indx >= len)
{
e.error("pointer index `[%lld]` exceeds allocated memory block `[0..%lld]`", ofs + indx, len);
return false;
}
}
else
{
if (ofs + indx != 0)
{
e.error("pointer index `[%lld]` lies outside memory block `[0..1]`", ofs + indx);
return false;
}
}
*pagg = agg;
*pidx = ofs + indx;
return true;
}
Expression e1 = interpretRegion(e.e1, istate);
if (exceptionOrCantInterpret(e1))
return false;
if (e1.op == EXP.null_)
{
e.error("cannot index null array `%s`", e.e1.toChars());
return false;
}
if (auto ve = e1.isVectorExp())
{
UnionExp ue = void;
e1 = interpretVectorToArray(&ue, ve);
e1 = (e1 == ue.exp()) ? ue.copy() : e1;
}
// Set the $ variable, and find the array literal to modify
dinteger_t len;
if (e1.op == EXP.variable && e1.type.toBasetype().ty == Tsarray)
len = e1.type.toBasetype().isTypeSArray().dim.toInteger();
else
{
if (e1.op != EXP.arrayLiteral && e1.op != EXP.string_ && e1.op != EXP.slice && e1.op != EXP.vector)
{
e.error("cannot determine length of `%s` at compile time", e.e1.toChars());
return false;
}
len = resolveArrayLength(e1);
}
if (e.lengthVar)
{
Expression dollarExp = ctfeEmplaceExp!IntegerExp(e.loc, len, Type.tsize_t);
ctfeGlobals.stack.push(e.lengthVar);
setValue(e.lengthVar, dollarExp);
}
Expression e2 = interpretRegion(e.e2, istate);
if (e.lengthVar)
ctfeGlobals.stack.pop(e.lengthVar); // $ is defined only inside []
if (exceptionOrCantInterpret(e2))
return false;
if (e2.op != EXP.int64)
{
e.error("CTFE internal error: non-integral index `[%s]`", e.e2.toChars());
return false;
}
if (auto se = e1.isSliceExp())
{
// Simplify index of slice: agg[lwr..upr][indx] --> agg[indx']
uinteger_t index = e2.toInteger();
uinteger_t ilwr = se.lwr.toInteger();
uinteger_t iupr = se.upr.toInteger();
if (index > iupr - ilwr)
{
e.error("index %llu exceeds array length %llu", index, iupr - ilwr);
return false;
}
*pagg = e1.isSliceExp().e1;
*pidx = index + ilwr;
}
else
{
*pagg = e1;
*pidx = e2.toInteger();
if (len <= *pidx)
{
e.error("array index %lld is out of bounds `[0..%lld]`", *pidx, len);
return false;
}
}
return true;
}
override void visit(IndexExp e)
{
debug (LOG)
{
printf("%s IndexExp::interpret() %s, goal = %d\n", e.loc.toChars(), e.toChars(), goal);
}
if (e.e1.type.toBasetype().ty == Tpointer)
{
Expression agg;
uinteger_t indexToAccess;
if (!resolveIndexing(e, istate, &agg, &indexToAccess, false))
{
result = CTFEExp.cantexp;
return;
}
if (agg.op == EXP.arrayLiteral || agg.op == EXP.string_)
{
if (goal == CTFEGoal.LValue)
{
// if we need a reference, IndexExp shouldn't be interpreting
// the expression to a value, it should stay as a reference
emplaceExp!(IndexExp)(pue, e.loc, agg, ctfeEmplaceExp!IntegerExp(e.e2.loc, indexToAccess, e.e2.type));
result = pue.exp();
result.type = e.type;
return;
}
result = ctfeIndex(pue, e.loc, e.type, agg, indexToAccess);
return;
}
else
{
assert(indexToAccess == 0);
result = interpretRegion(agg, istate, goal);
if (exceptionOrCant(result))
return;
result = paintTypeOntoLiteral(pue, e.type, result);
return;
}
}
if (e.e1.type.toBasetype().ty == Taarray)
{
Expression e1 = interpretRegion(e.e1, istate);
if (exceptionOrCant(e1))
return;
if (e1.op == EXP.null_)
{
if (goal == CTFEGoal.LValue && e1.type.ty == Taarray && e.modifiable)
{
assert(0); // does not reach here?
}
e.error("cannot index null array `%s`", e.e1.toChars());
result = CTFEExp.cantexp;
return;
}
Expression e2 = interpretRegion(e.e2, istate);
if (exceptionOrCant(e2))
return;
if (goal == CTFEGoal.LValue)
{
// Pointer or reference of a scalar type
if (e1 == e.e1 && e2 == e.e2)
result = e;
else
{
emplaceExp!(IndexExp)(pue, e.loc, e1, e2);
result = pue.exp();
result.type = e.type;
}
return;
}
assert(e1.op == EXP.assocArrayLiteral);
UnionExp e2tmp = void;
e2 = resolveSlice(e2, &e2tmp);
result = findKeyInAA(e.loc, e1.isAssocArrayLiteralExp(), e2);
if (!result)
{
e.error("key `%s` not found in associative array `%s`", e2.toChars(), e.e1.toChars());
result = CTFEExp.cantexp;
}
return;
}
Expression agg;
uinteger_t indexToAccess;
if (!resolveIndexing(e, istate, &agg, &indexToAccess, false))
{
result = CTFEExp.cantexp;
return;
}
if (goal == CTFEGoal.LValue)
{
Expression e2 = ctfeEmplaceExp!IntegerExp(e.e2.loc, indexToAccess, Type.tsize_t);
emplaceExp!(IndexExp)(pue, e.loc, agg, e2);
result = pue.exp();
result.type = e.type;
return;
}
result = ctfeIndex(pue, e.loc, e.type, agg, indexToAccess);
if (exceptionOrCant(result))
return;
if (result.op == EXP.void_)
{
e.error("`%s` is used before initialized", e.toChars());
errorSupplemental(result.loc, "originally uninitialized here");
result = CTFEExp.cantexp;
return;
}
if (result == pue.exp())
result = result.copy();
}
override void visit(SliceExp e)
{
debug (LOG)
{
printf("%s SliceExp::interpret() %s\n", e.loc.toChars(), e.toChars());
}
if (e.e1.type.toBasetype().ty == Tpointer)
{
// Slicing a pointer. Note that there is no $ in this case.
Expression e1 = interpretRegion(e.e1, istate);
if (exceptionOrCant(e1))
return;
if (e1.op == EXP.int64)
{
e.error("cannot slice invalid pointer `%s` of value `%s`", e.e1.toChars(), e1.toChars());
result = CTFEExp.cantexp;
return;
}
/* Evaluate lower and upper bounds of slice
*/
Expression lwr = interpretRegion(e.lwr, istate);
if (exceptionOrCant(lwr))
return;
Expression upr = interpretRegion(e.upr, istate);
if (exceptionOrCant(upr))
return;
uinteger_t ilwr = lwr.toInteger();
uinteger_t iupr = upr.toInteger();
dinteger_t ofs;
Expression agg = getAggregateFromPointer(e1, &ofs);
ilwr += ofs;
iupr += ofs;
if (agg.op == EXP.null_)
{
if (iupr == ilwr)
{
result = ctfeEmplaceExp!NullExp(e.loc);
result.type = e.type;
return;
}
e.error("cannot slice null pointer `%s`", e.e1.toChars());
result = CTFEExp.cantexp;
return;
}
if (agg.op == EXP.symbolOffset)
{
e.error("slicing pointers to static variables is not supported in CTFE");
result = CTFEExp.cantexp;
return;
}
if (agg.op != EXP.arrayLiteral && agg.op != EXP.string_)
{
e.error("pointer `%s` cannot be sliced at compile time (it does not point to an array)", e.e1.toChars());
result = CTFEExp.cantexp;
return;
}
assert(agg.op == EXP.arrayLiteral || agg.op == EXP.string_);
dinteger_t len = ArrayLength(Type.tsize_t, agg).exp().toInteger();
//Type *pointee = ((TypePointer *)agg.type)->next;
if (sliceBoundsCheck(0, len, ilwr, iupr))
{
e.error("pointer slice `[%lld..%lld]` exceeds allocated memory block `[0..%lld]`", ilwr, iupr, len);
result = CTFEExp.cantexp;
return;
}
if (ofs != 0)
{
lwr = ctfeEmplaceExp!IntegerExp(e.loc, ilwr, lwr.type);
upr = ctfeEmplaceExp!IntegerExp(e.loc, iupr, upr.type);
}
emplaceExp!(SliceExp)(pue, e.loc, agg, lwr, upr);
result = pue.exp();
result.type = e.type;
return;
}
CTFEGoal goal1 = CTFEGoal.RValue;
if (goal == CTFEGoal.LValue)
{
if (e.e1.type.toBasetype().ty == Tsarray)
if (auto ve = e.e1.isVarExp())
if (auto vd = ve.var.isVarDeclaration())
if (vd.storage_class & STC.ref_)
goal1 = CTFEGoal.LValue;
}
Expression e1 = interpret(e.e1, istate, goal1);
if (exceptionOrCant(e1))
return;
if (!e.lwr)
{
result = paintTypeOntoLiteral(pue, e.type, e1);
return;
}
if (auto ve = e1.isVectorExp())
{
e1 = interpretVectorToArray(pue, ve);
e1 = (e1 == pue.exp()) ? pue.copy() : e1;
}
/* Set dollar to the length of the array
*/
uinteger_t dollar;
if ((e1.op == EXP.variable || e1.op == EXP.dotVariable) && e1.type.toBasetype().ty == Tsarray)
dollar = e1.type.toBasetype().isTypeSArray().dim.toInteger();
else
{
if (e1.op != EXP.arrayLiteral && e1.op != EXP.string_ && e1.op != EXP.null_ && e1.op != EXP.slice && e1.op != EXP.vector)
{
e.error("cannot determine length of `%s` at compile time", e1.toChars());
result = CTFEExp.cantexp;
return;
}
dollar = resolveArrayLength(e1);
}
/* Set the $ variable
*/
if (e.lengthVar)
{
auto dollarExp = ctfeEmplaceExp!IntegerExp(e.loc, dollar, Type.tsize_t);
ctfeGlobals.stack.push(e.lengthVar);
setValue(e.lengthVar, dollarExp);
}
/* Evaluate lower and upper bounds of slice
*/
Expression lwr = interpretRegion(e.lwr, istate);
if (exceptionOrCant(lwr))
{
if (e.lengthVar)
ctfeGlobals.stack.pop(e.lengthVar);
return;
}
Expression upr = interpretRegion(e.upr, istate);
if (exceptionOrCant(upr))
{
if (e.lengthVar)
ctfeGlobals.stack.pop(e.lengthVar);
return;
}
if (e.lengthVar)
ctfeGlobals.stack.pop(e.lengthVar); // $ is defined only inside [L..U]
uinteger_t ilwr = lwr.toInteger();
uinteger_t iupr = upr.toInteger();
if (e1.op == EXP.null_)
{
if (ilwr == 0 && iupr == 0)
{
result = e1;
return;
}
e1.error("slice `[%llu..%llu]` is out of bounds", ilwr, iupr);
result = CTFEExp.cantexp;
return;
}
if (auto se = e1.isSliceExp())
{
// Simplify slice of slice:
// aggregate[lo1..up1][lwr..upr] ---> aggregate[lwr'..upr']
uinteger_t lo1 = se.lwr.toInteger();
uinteger_t up1 = se.upr.toInteger();
if (sliceBoundsCheck(0, up1 - lo1, ilwr, iupr))
{
e.error("slice `[%llu..%llu]` exceeds array bounds `[0..%llu]`", ilwr, iupr, up1 - lo1);
result = CTFEExp.cantexp;
return;
}
ilwr += lo1;
iupr += lo1;
emplaceExp!(SliceExp)(pue, e.loc, se.e1,
ctfeEmplaceExp!IntegerExp(e.loc, ilwr, lwr.type),
ctfeEmplaceExp!IntegerExp(e.loc, iupr, upr.type));
result = pue.exp();
result.type = e.type;
return;
}
if (e1.op == EXP.arrayLiteral || e1.op == EXP.string_)
{
if (sliceBoundsCheck(0, dollar, ilwr, iupr))
{
e.error("slice `[%lld..%lld]` exceeds array bounds `[0..%lld]`", ilwr, iupr, dollar);
result = CTFEExp.cantexp;
return;
}
}
emplaceExp!(SliceExp)(pue, e.loc, e1, lwr, upr);
result = pue.exp();
result.type = e.type;
}
override void visit(InExp e)
{
debug (LOG)
{
printf("%s InExp::interpret() %s\n", e.loc.toChars(), e.toChars());
}
Expression e1 = interpretRegion(e.e1, istate);
if (exceptionOrCant(e1))
return;
Expression e2 = interpretRegion(e.e2, istate);
if (exceptionOrCant(e2))
return;
if (e2.op == EXP.null_)
{
emplaceExp!(NullExp)(pue, e.loc, e.type);
result = pue.exp();
return;
}
if (e2.op != EXP.assocArrayLiteral)
{
e.error("`%s` cannot be interpreted at compile time", e.toChars());
result = CTFEExp.cantexp;
return;
}
e1 = resolveSlice(e1);
result = findKeyInAA(e.loc, e2.isAssocArrayLiteralExp(), e1);
if (exceptionOrCant(result))
return;
if (!result)
{
emplaceExp!(NullExp)(pue, e.loc, e.type);
result = pue.exp();
}
else
{
// Create a CTFE pointer &aa[index]
result = ctfeEmplaceExp!IndexExp(e.loc, e2, e1);
result.type = e.type.nextOf();
emplaceExp!(AddrExp)(pue, e.loc, result, e.type);
result = pue.exp();
}
}
override void visit(CatExp e)
{
debug (LOG)
{
printf("%s CatExp::interpret() %s\n", e.loc.toChars(), e.toChars());
}
UnionExp ue1 = void;
Expression e1 = interpret(&ue1, e.e1, istate);
if (exceptionOrCant(e1))
return;
UnionExp ue2 = void;
Expression e2 = interpret(&ue2, e.e2, istate);
if (exceptionOrCant(e2))
return;
UnionExp e1tmp = void;
e1 = resolveSlice(e1, &e1tmp);
UnionExp e2tmp = void;
e2 = resolveSlice(e2, &e2tmp);
/* e1 and e2 can't go on the stack because of x~[y] and [x]~y will
* result in [x,y] and then x or y is on the stack.
* But if they are both strings, we can, because it isn't the x~[y] case.
*/
if (!(e1.op == EXP.string_ && e2.op == EXP.string_))
{
if (e1 == ue1.exp())
e1 = ue1.copy();
if (e2 == ue2.exp())
e2 = ue2.copy();
}
*pue = ctfeCat(e.loc, e.type, e1, e2);
result = pue.exp();
if (CTFEExp.isCantExp(result))
{
e.error("`%s` cannot be interpreted at compile time", e.toChars());
return;
}
// We know we still own it, because we interpreted both e1 and e2
if (auto ale = result.isArrayLiteralExp())
{
ale.ownedByCtfe = OwnedBy.ctfe;
// https://issues.dlang.org/show_bug.cgi?id=14686
foreach (elem; *ale.elements)
{
Expression ex = evaluatePostblit(istate, elem);
if (exceptionOrCant(ex))
return;
}
}
else if (auto se = result.isStringExp())
se.ownedByCtfe = OwnedBy.ctfe;
}
override void visit(DeleteExp e)
{
debug (LOG)
{
printf("%s DeleteExp::interpret() %s\n", e.loc.toChars(), e.toChars());
}
result = interpretRegion(e.e1, istate);
if (exceptionOrCant(result))
return;
if (result.op == EXP.null_)
{
result = CTFEExp.voidexp;
return;
}
auto tb = e.e1.type.toBasetype();
switch (tb.ty)
{
case Tclass:
if (result.op != EXP.classReference)
{
e.error("`delete` on invalid class reference `%s`", result.toChars());
result = CTFEExp.cantexp;
return;
}
auto cre = result.isClassReferenceExp();
auto cd = cre.originalClass();
// Find dtor(s) in inheritance chain
do
{
if (cd.dtor)
{
result = interpretFunction(pue, cd.dtor, istate, null, cre);
if (exceptionOrCant(result))
return;
// Dtors of Non-extern(D) classes use implicit chaining (like structs)
import dmd.aggregate : ClassKind;
if (cd.classKind != ClassKind.d)
break;
}
// Emulate manual chaining as done in rt_finalize2
cd = cd.baseClass;
} while (cd); // Stop after Object
break;
default:
assert(0);
}
result = CTFEExp.voidexp;
}
override void visit(CastExp e)
{
debug (LOG)
{
printf("%s CastExp::interpret() %s\n", e.loc.toChars(), e.toChars());
}
Expression e1 = interpretRegion(e.e1, istate, goal);
if (exceptionOrCant(e1))
return;
// If the expression has been cast to void, do nothing.
if (e.to.ty == Tvoid)
{
result = CTFEExp.voidexp;
return;
}
if (e.to.ty == Tpointer && e1.op != EXP.null_)
{
Type pointee = (cast(TypePointer)e.type).next;
// Implement special cases of normally-unsafe casts
if (e1.op == EXP.int64)
{
// Happens with Windows HANDLEs, for example.
result = paintTypeOntoLiteral(pue, e.to, e1);
return;
}
bool castToSarrayPointer = false;
bool castBackFromVoid = false;
if (e1.type.ty == Tarray || e1.type.ty == Tsarray || e1.type.ty == Tpointer)
{
// Check for unsupported type painting operations
// For slices, we need the type being sliced,
// since it may have already been type painted
Type elemtype = e1.type.nextOf();
if (auto se = e1.isSliceExp())
elemtype = se.e1.type.nextOf();
// Allow casts from X* to void *, and X** to void** for any X.
// But don't allow cast from X* to void**.
// So, we strip all matching * from source and target to find X.
// Allow casts to X* from void* only if the 'void' was originally an X;
// we check this later on.
Type ultimatePointee = pointee;
Type ultimateSrc = elemtype;
while (ultimatePointee.ty == Tpointer && ultimateSrc.ty == Tpointer)
{
ultimatePointee = ultimatePointee.nextOf();
ultimateSrc = ultimateSrc.nextOf();
}
if (ultimatePointee.ty == Tsarray && ultimatePointee.nextOf().equivalent(ultimateSrc))
{
castToSarrayPointer = true;
}
else if (ultimatePointee.ty != Tvoid && ultimateSrc.ty != Tvoid && !isSafePointerCast(elemtype, pointee))
{
e.error("reinterpreting cast from `%s*` to `%s*` is not supported in CTFE", elemtype.toChars(), pointee.toChars());
result = CTFEExp.cantexp;
return;
}
if (ultimateSrc.ty == Tvoid)
castBackFromVoid = true;
}
if (auto se = e1.isSliceExp())
{
if (se.e1.op == EXP.null_)
{
result = paintTypeOntoLiteral(pue, e.type, se.e1);
return;
}
// Create a CTFE pointer &aggregate[1..2]
auto ei = ctfeEmplaceExp!IndexExp(e.loc, se.e1, se.lwr);
ei.type = e.type.nextOf();
emplaceExp!(AddrExp)(pue, e.loc, ei, e.type);
result = pue.exp();
return;
}
if (e1.op == EXP.arrayLiteral || e1.op == EXP.string_)
{
// Create a CTFE pointer &[1,2,3][0] or &"abc"[0]
auto ei = ctfeEmplaceExp!IndexExp(e.loc, e1, ctfeEmplaceExp!IntegerExp(e.loc, 0, Type.tsize_t));
ei.type = e.type.nextOf();
emplaceExp!(AddrExp)(pue, e.loc, ei, e.type);
result = pue.exp();
return;
}
if (e1.op == EXP.index && !e1.isIndexExp().e1.type.equals(e1.type))
{
// type painting operation
IndexExp ie = e1.isIndexExp();
if (castBackFromVoid)
{
// get the original type. For strings, it's just the type...
Type origType = ie.e1.type.nextOf();
// ..but for arrays of type void*, it's the type of the element
if (ie.e1.op == EXP.arrayLiteral && ie.e2.op == EXP.int64)
{
ArrayLiteralExp ale = ie.e1.isArrayLiteralExp();
const indx = cast(size_t)ie.e2.toInteger();
if (indx < ale.elements.length)
{
if (Expression xx = (*ale.elements)[indx])
{
if (auto iex = xx.isIndexExp())
origType = iex.e1.type.nextOf();
else if (auto ae = xx.isAddrExp())
origType = ae.e1.type;
else if (auto ve = xx.isVarExp())
origType = ve.var.type;
}
}
}
if (!isSafePointerCast(origType, pointee))
{
e.error("using `void*` to reinterpret cast from `%s*` to `%s*` is not supported in CTFE", origType.toChars(), pointee.toChars());
result = CTFEExp.cantexp;
return;
}
}
emplaceExp!(IndexExp)(pue, e1.loc, ie.e1, ie.e2);
result = pue.exp();
result.type = e.type;
return;
}
if (auto ae = e1.isAddrExp())
{
Type origType = ae.e1.type;
if (isSafePointerCast(origType, pointee))
{
emplaceExp!(AddrExp)(pue, e.loc, ae.e1, e.type);
result = pue.exp();
return;
}
if (castToSarrayPointer && pointee.toBasetype().ty == Tsarray && ae.e1.op == EXP.index)
{
// &val[idx]
dinteger_t dim = (cast(TypeSArray)pointee.toBasetype()).dim.toInteger();
IndexExp ie = ae.e1.isIndexExp();
Expression lwr = ie.e2;
Expression upr = ctfeEmplaceExp!IntegerExp(ie.e2.loc, ie.e2.toInteger() + dim, Type.tsize_t);
// Create a CTFE pointer &val[idx..idx+dim]
auto er = ctfeEmplaceExp!SliceExp(e.loc, ie.e1, lwr, upr);
er.type = pointee;
emplaceExp!(AddrExp)(pue, e.loc, er, e.type);
result = pue.exp();
return;
}
}
if (e1.op == EXP.variable || e1.op == EXP.symbolOffset)
{
// type painting operation
Type origType = (cast(SymbolExp)e1).var.type;
if (castBackFromVoid && !isSafePointerCast(origType, pointee))
{
e.error("using `void*` to reinterpret cast from `%s*` to `%s*` is not supported in CTFE", origType.toChars(), pointee.toChars());
result = CTFEExp.cantexp;
return;
}
if (auto ve = e1.isVarExp())
emplaceExp!(VarExp)(pue, e.loc, ve.var);
else
emplaceExp!(SymOffExp)(pue, e.loc, e1.isSymOffExp().var, e1.isSymOffExp().offset);
result = pue.exp();
result.type = e.to;
return;
}
// Check if we have a null pointer (eg, inside a struct)
e1 = interpretRegion(e1, istate);
if (e1.op != EXP.null_)
{
e.error("pointer cast from `%s` to `%s` is not supported at compile time", e1.type.toChars(), e.to.toChars());
result = CTFEExp.cantexp;
return;
}
}
if (e.to.ty == Tsarray && e.e1.type.ty == Tvector)
{
// Special handling for: cast(float[4])__vector([w, x, y, z])
e1 = interpretRegion(e.e1, istate);
if (exceptionOrCant(e1))
return;
assert(e1.op == EXP.vector);
e1 = interpretVectorToArray(pue, e1.isVectorExp());
}
if (e.to.ty == Tarray && e1.op == EXP.slice)
{
// Note that the slice may be void[], so when checking for dangerous
// casts, we need to use the original type, which is se.e1.
SliceExp se = e1.isSliceExp();
if (!isSafePointerCast(se.e1.type.nextOf(), e.to.nextOf()))
{
e.error("array cast from `%s` to `%s` is not supported at compile time", se.e1.type.toChars(), e.to.toChars());
result = CTFEExp.cantexp;
return;
}
emplaceExp!(SliceExp)(pue, e1.loc, se.e1, se.lwr, se.upr);
result = pue.exp();
result.type = e.to;
return;
}
// Disallow array type painting, except for conversions between built-in
// types of identical size.
if ((e.to.ty == Tsarray || e.to.ty == Tarray) && (e1.type.ty == Tsarray || e1.type.ty == Tarray) && !isSafePointerCast(e1.type.nextOf(), e.to.nextOf()))
{
e.error("array cast from `%s` to `%s` is not supported at compile time", e1.type.toChars(), e.to.toChars());
result = CTFEExp.cantexp;
return;
}
if (e.to.ty == Tsarray)
e1 = resolveSlice(e1);
auto tobt = e.to.toBasetype();
if (tobt.ty == Tbool && e1.type.ty == Tpointer)
{
emplaceExp!(IntegerExp)(pue, e.loc, e1.op != EXP.null_, e.to);
result = pue.exp();
return;
}
else if (tobt.isTypeBasic() && e1.op == EXP.null_)
{
if (tobt.isintegral())
emplaceExp!(IntegerExp)(pue, e.loc, 0, e.to);
else if (tobt.isreal())
emplaceExp!(RealExp)(pue, e.loc, CTFloat.zero, e.to);
result = pue.exp();
return;
}
result = ctfeCast(pue, e.loc, e.type, e.to, e1, true);
}
override void visit(AssertExp e)
{
debug (LOG)
{
printf("%s AssertExp::interpret() %s\n", e.loc.toChars(), e.toChars());
}
Expression e1 = interpret(pue, e.e1, istate);
if (exceptionOrCant(e1))
return;
if (isTrueBool(e1))
{
}
else if (e1.toBool().hasValue(false))
{
if (e.msg)
{
UnionExp ue = void;
result = interpret(&ue, e.msg, istate);
if (exceptionOrCant(result))
return;
e.error("`%s`", result.toChars());
}
else
e.error("`%s` failed", e.toChars());
result = CTFEExp.cantexp;
return;
}
else
{
e.error("`%s` is not a compile time boolean expression", e1.toChars());
result = CTFEExp.cantexp;
return;
}
result = e1;
return;
}
override void visit(ThrowExp te)
{
debug (LOG)
{
printf("%s ThrowExpression::interpret()\n", te.loc.toChars());
}
interpretThrow(te.e1, te.loc);
}
override void visit(PtrExp e)
{
debug (LOG)
{
printf("%s PtrExp::interpret() %s\n", e.loc.toChars(), e.toChars());
}
// Check for int<->float and long<->double casts.
if (auto soe1 = e.e1.isSymOffExp())
if (soe1.offset == 0 && soe1.var.isVarDeclaration() && isFloatIntPaint(e.type, soe1.var.type))
{
// *(cast(int*)&v), where v is a float variable
result = paintFloatInt(pue, getVarExp(e.loc, istate, soe1.var, CTFEGoal.RValue), e.type);
return;
}
if (auto ce1 = e.e1.isCastExp())
if (auto ae11 = ce1.e1.isAddrExp())
{
// *(cast(int*)&x), where x is a float expression
Expression x = ae11.e1;
if (isFloatIntPaint(e.type, x.type))
{
result = paintFloatInt(pue, interpretRegion(x, istate), e.type);
return;
}
}
// Constant fold *(&structliteral + offset)
if (auto ae = e.e1.isAddExp())
{
if (ae.e1.op == EXP.address && ae.e2.op == EXP.int64)
{
AddrExp ade = ae.e1.isAddrExp();
Expression ex = interpretRegion(ade.e1, istate);
if (exceptionOrCant(ex))
return;
if (auto se = ex.isStructLiteralExp())
{
dinteger_t offset = ae.e2.toInteger();
result = se.getField(e.type, cast(uint)offset);
if (result)
return;
}
}
}
// It's possible we have an array bounds error. We need to make sure it
// errors with this line number, not the one where the pointer was set.
result = interpretRegion(e.e1, istate);
if (exceptionOrCant(result))
return;
if (result.op == EXP.function_)
return;
if (auto soe = result.isSymOffExp())
{
if (soe.offset == 0 && soe.var.isFuncDeclaration())
return;
e.error("cannot dereference pointer to static variable `%s` at compile time", soe.var.toChars());
result = CTFEExp.cantexp;
return;
}
if (result.isStringExp() || result.isArrayLiteralExp())
return;
if (result.op != EXP.address)
{
if (result.op == EXP.null_)
e.error("dereference of null pointer `%s`", e.e1.toChars());
else
e.error("dereference of invalid pointer `%s`", result.toChars());
result = CTFEExp.cantexp;
return;
}
// *(&x) ==> x
result = result.isAddrExp().e1;
if (result.op == EXP.slice && e.type.toBasetype().ty == Tsarray)
{
/* aggr[lwr..upr]
* upr may exceed the upper boundary of aggr, but the check is deferred
* until those out-of-bounds elements will be touched.
*/
return;
}
result = interpret(pue, result, istate, goal);
if (exceptionOrCant(result))
return;
debug (LOG)
{
if (CTFEExp.isCantExp(result))
printf("PtrExp::interpret() %s = CTFEExp::cantexp\n", e.toChars());
}
}
override void visit(DotVarExp e)
{
void notImplementedYet()
{
e.error("`%s.%s` is not yet implemented at compile time", e.e1.toChars(), e.var.toChars());
result = CTFEExp.cantexp;
return;
}
debug (LOG)
{
printf("%s DotVarExp::interpret() %s, goal = %d\n", e.loc.toChars(), e.toChars(), goal);
}
Expression ex = interpretRegion(e.e1, istate);
if (exceptionOrCant(ex))
return;
if (FuncDeclaration f = e.var.isFuncDeclaration())
{
if (ex == e.e1)
result = e; // optimize: reuse this CTFE reference
else
{
emplaceExp!(DotVarExp)(pue, e.loc, ex, f, false);
result = pue.exp();
result.type = e.type;
}
return;
}
VarDeclaration v = e.var.isVarDeclaration();
if (!v)
{
e.error("CTFE internal error: `%s`", e.toChars());
result = CTFEExp.cantexp;
return;
}
if (ex.op == EXP.null_)
{
if (ex.type.toBasetype().ty == Tclass)
e.error("class `%s` is `null` and cannot be dereferenced", e.e1.toChars());
else
e.error("CTFE internal error: null this `%s`", e.e1.toChars());
result = CTFEExp.cantexp;
return;
}
StructLiteralExp se;
int i;
if (ex.op != EXP.structLiteral && ex.op != EXP.classReference && ex.op != EXP.typeid_)
{
return notImplementedYet();
}
// We can't use getField, because it makes a copy
if (ex.op == EXP.classReference)
{
se = ex.isClassReferenceExp().value;
i = ex.isClassReferenceExp().findFieldIndexByName(v);
}
else if (ex.op == EXP.typeid_)
{
if (v.ident == Identifier.idPool("name"))
{
if (auto t = isType(ex.isTypeidExp().obj))
{
auto sym = t.toDsymbol(null);
if (auto ident = (sym ? sym.ident : null))
{
result = new StringExp(e.loc, ident.toString());
result.expressionSemantic(null);
return ;
}
}
}
return notImplementedYet();
}
else
{
se = ex.isStructLiteralExp();
i = findFieldIndexByName(se.sd, v);
}
if (i == -1)
{
e.error("couldn't find field `%s` of type `%s` in `%s`", v.toChars(), e.type.toChars(), se.toChars());
result = CTFEExp.cantexp;
return;
}
// https://issues.dlang.org/show_bug.cgi?id=19897
// https://issues.dlang.org/show_bug.cgi?id=20710
// Zero-elements fields don't have an initializer. See: scrubArray function
if ((*se.elements)[i] is null)
(*se.elements)[i] = voidInitLiteral(e.type, v).copy();
if (goal == CTFEGoal.LValue)
{
// just return the (simplified) dotvar expression as a CTFE reference
if (e.e1 == ex)
result = e;
else
{
emplaceExp!(DotVarExp)(pue, e.loc, ex, v);
result = pue.exp();
result.type = e.type;
}
return;
}
result = (*se.elements)[i];
if (!result)
{
e.error("internal compiler error: null field `%s`", v.toChars());
result = CTFEExp.cantexp;
return;
}
if (auto vie = result.isVoidInitExp())
{
const s = vie.var.toChars();
if (v.overlapped)
{
e.error("reinterpretation through overlapped field `%s` is not allowed in CTFE", s);
result = CTFEExp.cantexp;
return;
}
e.error("cannot read uninitialized variable `%s` in CTFE", s);
result = CTFEExp.cantexp;
return;
}
if (v.type.ty != result.type.ty && v.type.ty == Tsarray)
{
// Block assignment from inside struct literals
auto tsa = cast(TypeSArray)v.type;
auto len = cast(size_t)tsa.dim.toInteger();
UnionExp ue = void;
result = createBlockDuplicatedArrayLiteral(&ue, e.loc, v.type, result, len);
if (result == ue.exp())
result = ue.copy();
(*se.elements)[i] = result;
}
debug (LOG)
{
if (CTFEExp.isCantExp(result))
printf("DotVarExp::interpret() %s = CTFEExp::cantexp\n", e.toChars());
}
}
override void visit(RemoveExp e)
{
debug (LOG)
{
printf("%s RemoveExp::interpret() %s\n", e.loc.toChars(), e.toChars());
}
Expression agg = interpret(e.e1, istate);
if (exceptionOrCant(agg))
return;
Expression index = interpret(e.e2, istate);
if (exceptionOrCant(index))
return;
if (agg.op == EXP.null_)
{
result = CTFEExp.voidexp;
return;
}
AssocArrayLiteralExp aae = agg.isAssocArrayLiteralExp();
Expressions* keysx = aae.keys;
Expressions* valuesx = aae.values;
size_t removed = 0;
foreach (j, evalue; *valuesx)
{
Expression ekey = (*keysx)[j];
int eq = ctfeEqual(e.loc, EXP.equal, ekey, index);
if (eq)
++removed;
else if (removed != 0)
{
(*keysx)[j - removed] = ekey;
(*valuesx)[j - removed] = evalue;
}
}
valuesx.length = valuesx.length - removed;
keysx.length = keysx.length - removed;
result = IntegerExp.createBool(removed != 0);
}
override void visit(ClassReferenceExp e)
{
//printf("ClassReferenceExp::interpret() %s\n", e.value.toChars());
result = e;
}
override void visit(VoidInitExp e)
{
e.error("CTFE internal error: trying to read uninitialized variable");
assert(0);
}
override void visit(ThrownExceptionExp e)
{
assert(0); // This should never be interpreted
}
/*********************************************
* Checks if the given expresion is a call to the runtime hook `id`.
* Params:
* e = the expression to check
* id = the identifier of the runtime hook
* Returns:
* `e` cast to `CallExp` if it's the hook, `null` otherwise
*/
private CallExp isRuntimeHook(Expression e, Identifier id)
{
if (auto ce = e.isCallExp())
{
if (auto ve = ce.e1.isVarExp())
{
if (auto fd = ve.var.isFuncDeclaration())
{
// If `_d_HookTraceImpl` is found, resolve the underlying
// hook and replace `e` and `fd` with it.
removeHookTraceImpl(ce, fd);
return fd.ident == id ? ce : null;
}
}
}
return null;
}
}
/********************************************
* Interpret the expression.
* Params:
* pue = non-null pointer to temporary storage that can be used to store the return value
* e = Expression to interpret
* istate = context
* goal = what the result will be used for
* Returns:
* resulting expression
*/
Expression interpret(UnionExp* pue, Expression e, InterState* istate, CTFEGoal goal = CTFEGoal.RValue)
{
if (!e)
return null;
scope Interpreter v = new Interpreter(pue, istate, goal);
e.accept(v);
Expression ex = v.result;
assert(goal == CTFEGoal.Nothing || ex !is null);
return ex;
}
///
Expression interpret(Expression e, InterState* istate, CTFEGoal goal = CTFEGoal.RValue)
{
UnionExp ue = void;
auto result = interpret(&ue, e, istate, goal);
if (result == ue.exp())
result = ue.copy();
return result;
}
/*****************************
* Same as interpret(), but return result allocated in Region.
* Params:
* e = Expression to interpret
* istate = context
* goal = what the result will be used for
* Returns:
* resulting expression
*/
Expression interpretRegion(Expression e, InterState* istate, CTFEGoal goal = CTFEGoal.RValue)
{
UnionExp ue = void;
auto result = interpret(&ue, e, istate, goal);
auto uexp = ue.exp();
if (result != uexp)
return result;
if (mem.isGCEnabled)
return ue.copy();
// mimicking UnionExp.copy, but with region allocation
switch (uexp.op)
{
case EXP.cantExpression: return CTFEExp.cantexp;
case EXP.voidExpression: return CTFEExp.voidexp;
case EXP.break_: return CTFEExp.breakexp;
case EXP.continue_: return CTFEExp.continueexp;
case EXP.goto_: return CTFEExp.gotoexp;
default: break;
}
auto p = ctfeGlobals.region.malloc(uexp.size);
return cast(Expression)memcpy(p, cast(void*)uexp, uexp.size);
}
/***********************************
* Interpret the statement.
* Params:
* pue = non-null pointer to temporary storage that can be used to store the return value
* s = Statement to interpret
* istate = context
* Returns:
* NULL continue to next statement
* EXP.cantExpression cannot interpret statement at compile time
* !NULL expression from return statement, or thrown exception
*/
Expression interpret(UnionExp* pue, Statement s, InterState* istate)
{
if (!s)
return null;
scope Interpreter v = new Interpreter(pue, istate, CTFEGoal.Nothing);
s.accept(v);
return v.result;
}
///
Expression interpret(Statement s, InterState* istate)
{
UnionExp ue = void;
auto result = interpret(&ue, s, istate);
if (result == ue.exp())
result = ue.copy();
return result;
}
/**
* All results destined for use outside of CTFE need to have their CTFE-specific
* features removed.
* In particular,
* 1. all slices must be resolved.
* 2. all .ownedByCtfe set to OwnedBy.code
*/
private Expression scrubReturnValue(const ref Loc loc, Expression e)
{
/* Returns: true if e is void,
* or is an array literal or struct literal of void elements.
*/
static bool isVoid(const Expression e, bool checkArrayType = false) pure
{
if (e.op == EXP.void_)
return true;
static bool isEntirelyVoid(const Expressions* elems)
{
foreach (e; *elems)
{
// It can be NULL for performance reasons,
// see StructLiteralExp::interpret().
if (e && !isVoid(e))
return false;
}
return true;
}
if (auto sle = e.isStructLiteralExp())
return isEntirelyVoid(sle.elements);
if (checkArrayType && e.type.ty != Tsarray)
return false;
if (auto ale = e.isArrayLiteralExp())
return isEntirelyVoid(ale.elements);
return false;
}
/* Scrub all elements of elems[].
* Returns: null for success, error Expression for failure
*/
Expression scrubArray(Expressions* elems, bool structlit = false)
{
foreach (ref e; *elems)
{
// It can be NULL for performance reasons,
// see StructLiteralExp::interpret().
if (!e)
continue;
// A struct .init may contain void members.
// Static array members are a weird special case https://issues.dlang.org/show_bug.cgi?id=10994
if (structlit && isVoid(e, true))
{
e = null;
}
else
{
e = scrubReturnValue(loc, e);
if (CTFEExp.isCantExp(e) || e.op == EXP.error)
return e;
}
}
return null;
}
Expression scrubSE(StructLiteralExp sle)
{
sle.ownedByCtfe = OwnedBy.code;
if (!(sle.stageflags & stageScrub))
{
const old = sle.stageflags;
sle.stageflags |= stageScrub; // prevent infinite recursion
if (auto ex = scrubArray(sle.elements, true))
return ex;
sle.stageflags = old;
}
return null;
}
if (e.op == EXP.classReference)
{
StructLiteralExp sle = e.isClassReferenceExp().value;
if (auto ex = scrubSE(sle))
return ex;
}
else if (auto vie = e.isVoidInitExp())
{
error(loc, "uninitialized variable `%s` cannot be returned from CTFE", vie.var.toChars());
return ErrorExp.get();
}
e = resolveSlice(e);
if (auto sle = e.isStructLiteralExp())
{
if (auto ex = scrubSE(sle))
return ex;
}
else if (auto se = e.isStringExp())
{
se.ownedByCtfe = OwnedBy.code;
}
else if (auto ale = e.isArrayLiteralExp())
{
ale.ownedByCtfe = OwnedBy.code;
if (auto ex = scrubArray(ale.elements))
return ex;
}
else if (auto aae = e.isAssocArrayLiteralExp())
{
aae.ownedByCtfe = OwnedBy.code;
if (auto ex = scrubArray(aae.keys))
return ex;
if (auto ex = scrubArray(aae.values))
return ex;
aae.type = toBuiltinAAType(aae.type);
}
else if (auto ve = e.isVectorExp())
{
ve.ownedByCtfe = OwnedBy.code;
if (auto ale = ve.e1.isArrayLiteralExp())
{
ale.ownedByCtfe = OwnedBy.code;
if (auto ex = scrubArray(ale.elements))
return ex;
}
}
return e;
}
/**************************************
* Transitively set all .ownedByCtfe to OwnedBy.cache
*/
private Expression scrubCacheValue(Expression e)
{
if (!e)
return e;
Expression scrubArrayCache(Expressions* elems)
{
foreach (ref e; *elems)
e = scrubCacheValue(e);
return null;
}
Expression scrubSE(StructLiteralExp sle)
{
sle.ownedByCtfe = OwnedBy.cache;
if (!(sle.stageflags & stageScrub))
{
const old = sle.stageflags;
sle.stageflags |= stageScrub; // prevent infinite recursion
if (auto ex = scrubArrayCache(sle.elements))
return ex;
sle.stageflags = old;
}
return null;
}
if (e.op == EXP.classReference)
{
if (auto ex = scrubSE(e.isClassReferenceExp().value))
return ex;
}
else if (auto sle = e.isStructLiteralExp())
{
if (auto ex = scrubSE(sle))
return ex;
}
else if (auto se = e.isStringExp())
{
se.ownedByCtfe = OwnedBy.cache;
}
else if (auto ale = e.isArrayLiteralExp())
{
ale.ownedByCtfe = OwnedBy.cache;
if (Expression ex = scrubArrayCache(ale.elements))
return ex;
}
else if (auto aae = e.isAssocArrayLiteralExp())
{
aae.ownedByCtfe = OwnedBy.cache;
if (auto ex = scrubArrayCache(aae.keys))
return ex;
if (auto ex = scrubArrayCache(aae.values))
return ex;
}
else if (auto ve = e.isVectorExp())
{
ve.ownedByCtfe = OwnedBy.cache;
if (auto ale = ve.e1.isArrayLiteralExp())
{
ale.ownedByCtfe = OwnedBy.cache;
if (auto ex = scrubArrayCache(ale.elements))
return ex;
}
}
return e;
}
/********************************************
* Transitively replace all Expressions allocated in ctfeGlobals.region
* with Mem owned copies.
* Params:
* e = possible ctfeGlobals.region owned expression
* Returns:
* Mem owned expression
*/
private Expression copyRegionExp(Expression e)
{
if (!e)
return e;
static void copyArray(Expressions* elems)
{
foreach (ref e; *elems)
{
auto ex = e;
e = null;
e = copyRegionExp(ex);
}
}
static void copySE(StructLiteralExp sle)
{
if (1 || !(sle.stageflags & stageScrub))
{
const old = sle.stageflags;
sle.stageflags |= stageScrub; // prevent infinite recursion
copyArray(sle.elements);
sle.stageflags = old;
}
}
switch (e.op)
{
case EXP.classReference:
{
auto cre = e.isClassReferenceExp();
cre.value = copyRegionExp(cre.value).isStructLiteralExp();
break;
}
case EXP.structLiteral:
{
auto sle = e.isStructLiteralExp();
/* The following is to take care of updating sle.origin correctly,
* which may have multiple objects pointing to it.
*/
if (sle.isOriginal && !ctfeGlobals.region.contains(cast(void*)sle.origin))
{
/* This means sle has already been moved out of the region,
* and sle.origin is the new location.
*/
return sle.origin;
}
copySE(sle);
sle.isOriginal = sle is sle.origin;
auto slec = ctfeGlobals.region.contains(cast(void*)e)
? e.copy().isStructLiteralExp() // move sle out of region to slec
: sle;
if (ctfeGlobals.region.contains(cast(void*)sle.origin))
{
auto sleo = sle.origin == sle ? slec : sle.origin.copy().isStructLiteralExp();
sle.origin = sleo;
slec.origin = sleo;
}
return slec;
}
case EXP.arrayLiteral:
{
auto ale = e.isArrayLiteralExp();
ale.basis = copyRegionExp(ale.basis);
copyArray(ale.elements);
break;
}
case EXP.assocArrayLiteral:
copyArray(e.isAssocArrayLiteralExp().keys);
copyArray(e.isAssocArrayLiteralExp().values);
break;
case EXP.slice:
{
auto se = e.isSliceExp();
se.e1 = copyRegionExp(se.e1);
se.upr = copyRegionExp(se.upr);
se.lwr = copyRegionExp(se.lwr);
break;
}
case EXP.tuple:
{
auto te = e.isTupleExp();
te.e0 = copyRegionExp(te.e0);
copyArray(te.exps);
break;
}
case EXP.address:
case EXP.delegate_:
case EXP.vector:
case EXP.dotVariable:
{
UnaExp ue = e.isUnaExp();
ue.e1 = copyRegionExp(ue.e1);
break;
}
case EXP.index:
{
BinExp be = e.isBinExp();
be.e1 = copyRegionExp(be.e1);
be.e2 = copyRegionExp(be.e2);
break;
}
case EXP.this_:
case EXP.super_:
case EXP.variable:
case EXP.type:
case EXP.function_:
case EXP.typeid_:
case EXP.string_:
case EXP.int64:
case EXP.error:
case EXP.float64:
case EXP.complex80:
case EXP.null_:
case EXP.void_:
case EXP.symbolOffset:
case EXP.char_:
break;
case EXP.cantExpression:
case EXP.voidExpression:
case EXP.showCtfeContext:
return e;
default:
printf("e: %s, %s\n", EXPtoString(e.op).ptr, e.toChars());
assert(0);
}
if (ctfeGlobals.region.contains(cast(void*)e))
{
return e.copy();
}
return e;
}
/******************************* Special Functions ***************************/
private Expression interpret_length(UnionExp* pue, InterState* istate, Expression earg)
{
//printf("interpret_length()\n");
earg = interpret(pue, earg, istate);
if (exceptionOrCantInterpret(earg))
return earg;
dinteger_t len = 0;
if (auto aae = earg.isAssocArrayLiteralExp())
len = aae.keys.length;
else
assert(earg.op == EXP.null_);
emplaceExp!(IntegerExp)(pue, earg.loc, len, Type.tsize_t);
return pue.exp();
}
private Expression interpret_keys(UnionExp* pue, InterState* istate, Expression earg, Type returnType)
{
debug (LOG)
{
printf("interpret_keys()\n");
}
earg = interpret(pue, earg, istate);
if (exceptionOrCantInterpret(earg))
return earg;
if (earg.op == EXP.null_)
{
emplaceExp!(NullExp)(pue, earg.loc, earg.type);
return pue.exp();
}
if (earg.op != EXP.assocArrayLiteral && earg.type.toBasetype().ty != Taarray)
return null;
AssocArrayLiteralExp aae = earg.isAssocArrayLiteralExp();
auto ae = ctfeEmplaceExp!ArrayLiteralExp(aae.loc, returnType, aae.keys);
ae.ownedByCtfe = aae.ownedByCtfe;
*pue = copyLiteral(ae);
return pue.exp();
}
private Expression interpret_values(UnionExp* pue, InterState* istate, Expression earg, Type returnType)
{
debug (LOG)
{
printf("interpret_values()\n");
}
earg = interpret(pue, earg, istate);
if (exceptionOrCantInterpret(earg))
return earg;
if (earg.op == EXP.null_)
{
emplaceExp!(NullExp)(pue, earg.loc, earg.type);
return pue.exp();
}
if (earg.op != EXP.assocArrayLiteral && earg.type.toBasetype().ty != Taarray)
return null;
auto aae = earg.isAssocArrayLiteralExp();
auto ae = ctfeEmplaceExp!ArrayLiteralExp(aae.loc, returnType, aae.values);
ae.ownedByCtfe = aae.ownedByCtfe;
//printf("result is %s\n", e.toChars());
*pue = copyLiteral(ae);
return pue.exp();
}
private Expression interpret_dup(UnionExp* pue, InterState* istate, Expression earg)
{
debug (LOG)
{
printf("interpret_dup()\n");
}
earg = interpret(pue, earg, istate);
if (exceptionOrCantInterpret(earg))
return earg;
if (earg.op == EXP.null_)
{
emplaceExp!(NullExp)(pue, earg.loc, earg.type);
return pue.exp();
}
if (earg.op != EXP.assocArrayLiteral && earg.type.toBasetype().ty != Taarray)
return null;
auto aae = copyLiteral(earg).copy().isAssocArrayLiteralExp();
for (size_t i = 0; i < aae.keys.length; i++)
{
if (Expression e = evaluatePostblit(istate, (*aae.keys)[i]))
return e;
if (Expression e = evaluatePostblit(istate, (*aae.values)[i]))
return e;
}
aae.type = earg.type.mutableOf(); // repaint type from const(int[int]) to const(int)[int]
//printf("result is %s\n", aae.toChars());
return aae;
}
// signature is int delegate(ref Value) OR int delegate(ref Key, ref Value)
private Expression interpret_aaApply(UnionExp* pue, InterState* istate, Expression aa, Expression deleg)
{
aa = interpret(aa, istate);
if (exceptionOrCantInterpret(aa))
return aa;
if (aa.op != EXP.assocArrayLiteral)
{
emplaceExp!(IntegerExp)(pue, deleg.loc, 0, Type.tsize_t);
return pue.exp();
}
FuncDeclaration fd = null;
Expression pthis = null;
if (auto de = deleg.isDelegateExp())
{
fd = de.func;
pthis = de.e1;
}
else if (auto fe = deleg.isFuncExp())
fd = fe.fd;
assert(fd && fd.fbody);
assert(fd.parameters);
size_t numParams = fd.parameters.length;
assert(numParams == 1 || numParams == 2);
Parameter fparam = fd.type.isTypeFunction().parameterList[numParams - 1];
const wantRefValue = fparam.isReference();
Expressions args = Expressions(numParams);
AssocArrayLiteralExp ae = aa.isAssocArrayLiteralExp();
if (!ae.keys || ae.keys.length == 0)
return ctfeEmplaceExp!IntegerExp(deleg.loc, 0, Type.tsize_t);
Expression eresult;
for (size_t i = 0; i < ae.keys.length; ++i)
{
Expression ekey = (*ae.keys)[i];
Expression evalue = (*ae.values)[i];
if (wantRefValue)
{
Type t = evalue.type;
evalue = ctfeEmplaceExp!IndexExp(deleg.loc, ae, ekey);
evalue.type = t;
}
args[numParams - 1] = evalue;
if (numParams == 2)
args[0] = ekey;
UnionExp ue = void;
eresult = interpretFunction(&ue, fd, istate, &args, pthis);
if (eresult == ue.exp())
eresult = ue.copy();
if (exceptionOrCantInterpret(eresult))
return eresult;
if (eresult.isIntegerExp().getInteger() != 0)
return eresult;
}
return eresult;
}
/* Decoding UTF strings for foreach loops. Duplicates the functionality of
* the twelve _aApplyXXn functions in aApply.d in the runtime.
*/
private Expression foreachApplyUtf(UnionExp* pue, InterState* istate, Expression str, Expression deleg, bool rvs)
{
debug (LOG)
{
printf("foreachApplyUtf(%s, %s)\n", str.toChars(), deleg.toChars());
}
FuncDeclaration fd = null;
Expression pthis = null;
if (auto de = deleg.isDelegateExp())
{
fd = de.func;
pthis = de.e1;
}
else if (auto fe = deleg.isFuncExp())
fd = fe.fd;
assert(fd && fd.fbody);
assert(fd.parameters);
size_t numParams = fd.parameters.length;
assert(numParams == 1 || numParams == 2);
Type charType = (*fd.parameters)[numParams - 1].type;
Type indexType = numParams == 2 ? (*fd.parameters)[0].type : Type.tsize_t;
size_t len = cast(size_t)resolveArrayLength(str);
if (len == 0)
{
emplaceExp!(IntegerExp)(pue, deleg.loc, 0, indexType);
return pue.exp();
}
UnionExp strTmp = void;
str = resolveSlice(str, &strTmp);
auto se = str.isStringExp();
auto ale = str.isArrayLiteralExp();
if (!se && !ale)
{
str.error("CTFE internal error: cannot foreach `%s`", str.toChars());
return CTFEExp.cantexp;
}
Expressions args = Expressions(numParams);
Expression eresult = null; // ded-store to prevent spurious warning
// Buffers for encoding; also used for decoding array literals
char[4] utf8buf = void;
wchar[2] utf16buf = void;
size_t start = rvs ? len : 0;
size_t end = rvs ? 0 : len;
for (size_t indx = start; indx != end;)
{
// Step 1: Decode the next dchar from the string.
string errmsg = null; // Used for reporting decoding errors
dchar rawvalue; // Holds the decoded dchar
size_t currentIndex = indx; // The index of the decoded character
if (ale)
{
// If it is an array literal, copy the code points into the buffer
size_t buflen = 1; // #code points in the buffer
size_t n = 1; // #code points in this char
size_t sz = cast(size_t)ale.type.nextOf().size();
switch (sz)
{
case 1:
if (rvs)
{
// find the start of the string
--indx;
buflen = 1;
while (indx > 0 && buflen < 4)
{
Expression r = (*ale.elements)[indx];
char x = cast(char)r.isIntegerExp().getInteger();
if ((x & 0xC0) != 0x80)
break;
--indx;
++buflen;
}
}
else
buflen = (indx + 4 > len) ? len - indx : 4;
for (size_t i = 0; i < buflen; ++i)
{
Expression r = (*ale.elements)[indx + i];
utf8buf[i] = cast(char)r.isIntegerExp().getInteger();
}
n = 0;
errmsg = utf_decodeChar(utf8buf[0 .. buflen], n, rawvalue);
break;
case 2:
if (rvs)
{
// find the start of the string
--indx;
buflen = 1;
Expression r = (*ale.elements)[indx];
ushort x = cast(ushort)r.isIntegerExp().getInteger();
if (indx > 0 && x >= 0xDC00 && x <= 0xDFFF)
{
--indx;
++buflen;
}
}
else
buflen = (indx + 2 > len) ? len - indx : 2;
for (size_t i = 0; i < buflen; ++i)
{
Expression r = (*ale.elements)[indx + i];
utf16buf[i] = cast(ushort)r.isIntegerExp().getInteger();
}
n = 0;
errmsg = utf_decodeWchar(utf16buf[0 .. buflen], n, rawvalue);
break;
case 4:
{
if (rvs)
--indx;
Expression r = (*ale.elements)[indx];
rawvalue = cast(dchar)r.isIntegerExp().getInteger();
n = 1;
}
break;
default:
assert(0);
}
if (!rvs)
indx += n;
}
else
{
// String literals
size_t saveindx; // used for reverse iteration
switch (se.sz)
{
case 1:
{
if (rvs)
{
// find the start of the string
--indx;
while (indx > 0 && ((se.getCodeUnit(indx) & 0xC0) == 0x80))
--indx;
saveindx = indx;
}
auto slice = se.peekString();
errmsg = utf_decodeChar(slice, indx, rawvalue);
if (rvs)
indx = saveindx;
break;
}
case 2:
if (rvs)
{
// find the start
--indx;
auto wc = se.getCodeUnit(indx);
if (wc >= 0xDC00 && wc <= 0xDFFF)
--indx;
saveindx = indx;
}
const slice = se.peekWstring();
errmsg = utf_decodeWchar(slice, indx, rawvalue);
if (rvs)
indx = saveindx;
break;
case 4:
if (rvs)
--indx;
rawvalue = se.getCodeUnit(indx);
if (!rvs)
++indx;
break;
default:
assert(0);
}
}
if (errmsg)
{
deleg.error("`%.*s`", cast(int)errmsg.length, errmsg.ptr);
return CTFEExp.cantexp;
}
// Step 2: encode the dchar in the target encoding
int charlen = 1; // How many codepoints are involved?
switch (charType.size())
{
case 1:
charlen = utf_codeLengthChar(rawvalue);
utf_encodeChar(&utf8buf[0], rawvalue);
break;
case 2:
charlen = utf_codeLengthWchar(rawvalue);
utf_encodeWchar(&utf16buf[0], rawvalue);
break;
case 4:
break;
default:
assert(0);
}
if (rvs)
currentIndex = indx;
// Step 3: call the delegate once for each code point
// The index only needs to be set once
if (numParams == 2)
args[0] = ctfeEmplaceExp!IntegerExp(deleg.loc, currentIndex, indexType);
Expression val = null;
foreach (k; 0 .. charlen)
{
dchar codepoint;
switch (charType.size())
{
case 1:
codepoint = utf8buf[k];
break;
case 2:
codepoint = utf16buf[k];
break;
case 4:
codepoint = rawvalue;
break;
default:
assert(0);
}
val = ctfeEmplaceExp!IntegerExp(str.loc, codepoint, charType);
args[numParams - 1] = val;
UnionExp ue = void;
eresult = interpretFunction(&ue, fd, istate, &args, pthis);
if (eresult == ue.exp())
eresult = ue.copy();
if (exceptionOrCantInterpret(eresult))
return eresult;
if (eresult.isIntegerExp().getInteger() != 0)
return eresult;
}
}
return eresult;
}
/* If this is a built-in function, return the interpreted result,
* Otherwise, return NULL.
*/
private Expression evaluateIfBuiltin(UnionExp* pue, InterState* istate, const ref Loc loc, FuncDeclaration fd, Expressions* arguments, Expression pthis)
{
Expression e = null;
size_t nargs = arguments ? arguments.length : 0;
if (!pthis)
{
if (isBuiltin(fd) != BUILTIN.unimp)
{
Expressions args = Expressions(nargs);
foreach (i, ref arg; args)
{
Expression earg = (*arguments)[i];
earg = interpret(earg, istate);
if (exceptionOrCantInterpret(earg))
return earg;
arg = earg;
}
e = eval_builtin(loc, fd, &args);
if (!e)
{
error(loc, "cannot evaluate unimplemented builtin `%s` at compile time", fd.toChars());
e = CTFEExp.cantexp;
}
}
}
if (!pthis)
{
if (nargs == 1 || nargs == 3)
{
Expression firstarg = (*arguments)[0];
if (auto firstAAtype = firstarg.type.toBasetype().isTypeAArray())
{
const id = fd.ident;
if (nargs == 1)
{
if (id == Id.aaLen)
return interpret_length(pue, istate, firstarg);
if (fd.toParent2().ident == Id.object)
{
if (id == Id.keys)
return interpret_keys(pue, istate, firstarg, firstAAtype.index.arrayOf());
if (id == Id.values)
return interpret_values(pue, istate, firstarg, firstAAtype.nextOf().arrayOf());
if (id == Id.rehash)
return interpret(pue, firstarg, istate);
if (id == Id.dup)
return interpret_dup(pue, istate, firstarg);
}
}
else // (nargs == 3)
{
if (id == Id._aaApply)
return interpret_aaApply(pue, istate, firstarg, (*arguments)[2]);
if (id == Id._aaApply2)
return interpret_aaApply(pue, istate, firstarg, (*arguments)[2]);
}
}
}
}
if (pthis && !fd.fbody && fd.isCtorDeclaration() && fd.parent && fd.parent.parent && fd.parent.parent.ident == Id.object)
{
if (pthis.op == EXP.classReference && fd.parent.ident == Id.Throwable)
{
// At present, the constructors just copy their arguments into the struct.
// But we might need some magic if stack tracing gets added to druntime.
StructLiteralExp se = pthis.isClassReferenceExp().value;
assert(arguments.length <= se.elements.length);
foreach (i, arg; *arguments)
{
auto elem = interpret(arg, istate);
if (exceptionOrCantInterpret(elem))
return elem;
(*se.elements)[i] = elem;
}
return CTFEExp.voidexp;
}
}
if (nargs == 1 && !pthis && (fd.ident == Id.criticalenter || fd.ident == Id.criticalexit))
{
// Support synchronized{} as a no-op
return CTFEExp.voidexp;
}
if (!pthis)
{
const idlen = fd.ident.toString().length;
const id = fd.ident.toChars();
if (nargs == 2 && (idlen == 10 || idlen == 11) && !strncmp(id, "_aApply", 7))
{
// Functions from aApply.d and aApplyR.d in the runtime
bool rvs = (idlen == 11); // true if foreach_reverse
char c = id[idlen - 3]; // char width: 'c', 'w', or 'd'
char s = id[idlen - 2]; // string width: 'c', 'w', or 'd'
char n = id[idlen - 1]; // numParams: 1 or 2.
// There are 12 combinations
if ((n == '1' || n == '2') &&
(c == 'c' || c == 'w' || c == 'd') &&
(s == 'c' || s == 'w' || s == 'd') &&
c != s)
{
Expression str = (*arguments)[0];
str = interpret(str, istate);
if (exceptionOrCantInterpret(str))
return str;
return foreachApplyUtf(pue, istate, str, (*arguments)[1], rvs);
}
}
}
return e;
}
private Expression evaluatePostblit(InterState* istate, Expression e)
{
auto ts = e.type.baseElemOf().isTypeStruct();
if (!ts)
return null;
StructDeclaration sd = ts.sym;
if (!sd.postblit)
return null;
if (auto ale = e.isArrayLiteralExp())
{
foreach (elem; *ale.elements)
{
if (auto ex = evaluatePostblit(istate, elem))
return ex;
}
return null;
}
if (e.op == EXP.structLiteral)
{
// e.__postblit()
UnionExp ue = void;
e = interpretFunction(&ue, sd.postblit, istate, null, e);
if (e == ue.exp())
e = ue.copy();
if (exceptionOrCantInterpret(e))
return e;
return null;
}
assert(0);
}
private Expression evaluateDtor(InterState* istate, Expression e)
{
auto ts = e.type.baseElemOf().isTypeStruct();
if (!ts)
return null;
StructDeclaration sd = ts.sym;
if (!sd.dtor)
return null;
UnionExp ue = void;
if (auto ale = e.isArrayLiteralExp())
{
foreach_reverse (elem; *ale.elements)
e = evaluateDtor(istate, elem);
}
else if (e.op == EXP.structLiteral)
{
// e.__dtor()
e = interpretFunction(&ue, sd.dtor, istate, null, e);
}
else
assert(0);
if (exceptionOrCantInterpret(e))
{
if (e == ue.exp())
e = ue.copy();
return e;
}
return null;
}
/*************************** CTFE Sanity Checks ***************************/
/* Setter functions for CTFE variable values.
* These functions exist to check for compiler CTFE bugs.
*/
private bool hasValue(VarDeclaration vd)
{
return vd.ctfeAdrOnStack != VarDeclaration.AdrOnStackNone &&
getValue(vd) !is null;
}
// Don't check for validity
private void setValueWithoutChecking(VarDeclaration vd, Expression newval)
{
ctfeGlobals.stack.setValue(vd, newval);
}
private void setValue(VarDeclaration vd, Expression newval)
{
//printf("setValue() vd: %s newval: %s\n", vd.toChars(), newval.toChars());
version (none)
{
if (!((vd.storage_class & (STC.out_ | STC.ref_)) ? isCtfeReferenceValid(newval) : isCtfeValueValid(newval)))
{
printf("[%s] vd = %s %s, newval = %s\n", vd.loc.toChars(), vd.type.toChars(), vd.toChars(), newval.toChars());
}
}
assert((vd.storage_class & (STC.out_ | STC.ref_)) ? isCtfeReferenceValid(newval) : isCtfeValueValid(newval));
ctfeGlobals.stack.setValue(vd, newval);
}
/**
* Removes `_d_HookTraceImpl` if found from `ce` and `fd`.
* This is needed for the CTFE interception code to be able to find hooks that are called though the hook's `*Trace`
* wrapper.
*
* This is done by replacing `_d_HookTraceImpl!(T, Hook, errMsg)(..., parameters)` with `Hook(parameters)`.
* Parameters:
* ce = The CallExp that possible will be be replaced
* fd = Fully resolve function declaration that `ce` would call
*/
private void removeHookTraceImpl(ref CallExp ce, ref FuncDeclaration fd)
{
if (fd.ident != Id._d_HookTraceImpl)
return;
auto oldCE = ce;
// Get the Hook from the second template parameter
TemplateInstance templateInstance = fd.parent.isTemplateInstance;
RootObject hook = (*templateInstance.tiargs)[1];
assert(hook.dyncast() == DYNCAST.dsymbol, "Expected _d_HookTraceImpl's second template parameter to be an alias to the hook!");
fd = (cast(Dsymbol)hook).isFuncDeclaration;
// Remove the first three trace parameters
auto arguments = new Expressions();
arguments.reserve(ce.arguments.length - 3);
arguments.pushSlice((*ce.arguments)[3 .. $]);
ce = ctfeEmplaceExp!CallExp(ce.loc, ctfeEmplaceExp!VarExp(ce.loc, fd, false), arguments);
if (global.params.verbose)
message("strip %s =>\n %s", oldCE.toChars(), ce.toChars());
}