blob: 32aed16e79693f2d65f64ef055db5f7282c3c6b2 [file] [log] [blame]
/**
* CTFE for expressions involving pointers, slices, array concatenation etc.
*
* Copyright: Copyright (C) 1999-2022 by The D Language Foundation, All Rights Reserved
* Authors: $(LINK2 https://www.digitalmars.com, Walter Bright)
* License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
* Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/ctfeexpr.d, _ctfeexpr.d)
* Documentation: https://dlang.org/phobos/dmd_ctfeexpr.html
* Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/ctfeexpr.d
*/
module dmd.ctfeexpr;
import core.stdc.stdio;
import core.stdc.stdlib;
import core.stdc.string;
import dmd.arraytypes;
import dmd.astenums;
import dmd.constfold;
import dmd.compiler;
import dmd.dclass;
import dmd.declaration;
import dmd.dinterpret;
import dmd.dstruct;
import dmd.dtemplate;
import dmd.errors;
import dmd.expression;
import dmd.func;
import dmd.globals;
import dmd.mtype;
import dmd.root.complex;
import dmd.root.ctfloat;
import dmd.root.port;
import dmd.root.rmem;
import dmd.tokens;
import dmd.visitor;
/***********************************************************
* A reference to a class, or an interface. We need this when we
* point to a base class (we must record what the type is).
*/
extern (C++) final class ClassReferenceExp : Expression
{
StructLiteralExp value;
extern (D) this(const ref Loc loc, StructLiteralExp lit, Type type)
{
super(loc, EXP.classReference, __traits(classInstanceSize, ClassReferenceExp));
assert(lit && lit.sd && lit.sd.isClassDeclaration());
this.value = lit;
this.type = type;
}
ClassDeclaration originalClass()
{
return value.sd.isClassDeclaration();
}
// Return index of the field, or -1 if not found
private int getFieldIndex(Type fieldtype, uint fieldoffset)
{
ClassDeclaration cd = originalClass();
uint fieldsSoFar = 0;
for (size_t j = 0; j < value.elements.dim; j++)
{
while (j - fieldsSoFar >= cd.fields.dim)
{
fieldsSoFar += cd.fields.dim;
cd = cd.baseClass;
}
VarDeclaration v2 = cd.fields[j - fieldsSoFar];
if (fieldoffset == v2.offset && fieldtype.size() == v2.type.size())
{
return cast(int)(value.elements.dim - fieldsSoFar - cd.fields.dim + (j - fieldsSoFar));
}
}
return -1;
}
// Return index of the field, or -1 if not found
// Same as getFieldIndex, but checks for a direct match with the VarDeclaration
int findFieldIndexByName(VarDeclaration v)
{
ClassDeclaration cd = originalClass();
size_t fieldsSoFar = 0;
for (size_t j = 0; j < value.elements.dim; j++)
{
while (j - fieldsSoFar >= cd.fields.dim)
{
fieldsSoFar += cd.fields.dim;
cd = cd.baseClass;
}
VarDeclaration v2 = cd.fields[j - fieldsSoFar];
if (v == v2)
{
return cast(int)(value.elements.dim - fieldsSoFar - cd.fields.dim + (j - fieldsSoFar));
}
}
return -1;
}
override void accept(Visitor v)
{
v.visit(this);
}
}
/*************************
* Same as getFieldIndex, but checks for a direct match with the VarDeclaration
* Returns:
* index of the field, or -1 if not found
*/
int findFieldIndexByName(const StructDeclaration sd, const VarDeclaration v) pure
{
foreach (i, field; sd.fields)
{
if (field == v)
return cast(int)i;
}
return -1;
}
/***********************************************************
* Fake class which holds the thrown exception.
* Used for implementing exception handling.
*/
extern (C++) final class ThrownExceptionExp : Expression
{
ClassReferenceExp thrown; // the thing being tossed
extern (D) this(const ref Loc loc, ClassReferenceExp victim)
{
super(loc, EXP.thrownException, __traits(classInstanceSize, ThrownExceptionExp));
this.thrown = victim;
this.type = victim.type;
}
override const(char)* toChars() const
{
return "CTFE ThrownException";
}
// Generate an error message when this exception is not caught
extern (D) void generateUncaughtError()
{
UnionExp ue = void;
Expression e = resolveSlice((*thrown.value.elements)[0], &ue);
StringExp se = e.toStringExp();
thrown.error("uncaught CTFE exception `%s(%s)`", thrown.type.toChars(), se ? se.toChars() : e.toChars());
/* Also give the line where the throw statement was. We won't have it
* in the case where the ThrowStatement is generated internally
* (eg, in ScopeStatement)
*/
if (loc.isValid() && !loc.equals(thrown.loc))
.errorSupplemental(loc, "thrown from here");
}
override void accept(Visitor v)
{
v.visit(this);
}
}
/***********************************************************
* This type is only used by the interpreter.
*/
extern (C++) final class CTFEExp : Expression
{
extern (D) this(EXP tok)
{
super(Loc.initial, tok, __traits(classInstanceSize, CTFEExp));
type = Type.tvoid;
}
override const(char)* toChars() const
{
switch (op)
{
case EXP.cantExpression:
return "<cant>";
case EXP.voidExpression:
return "cast(void)0";
case EXP.showCtfeContext:
return "<error>";
case EXP.break_:
return "<break>";
case EXP.continue_:
return "<continue>";
case EXP.goto_:
return "<goto>";
default:
assert(0);
}
}
extern (D) __gshared CTFEExp cantexp;
extern (D) __gshared CTFEExp voidexp;
extern (D) __gshared CTFEExp breakexp;
extern (D) __gshared CTFEExp continueexp;
extern (D) __gshared CTFEExp gotoexp;
/* Used when additional information is needed regarding
* a ctfe error.
*/
extern (D) __gshared CTFEExp showcontext;
extern (D) static bool isCantExp(const Expression e)
{
return e && e.op == EXP.cantExpression;
}
extern (D) static bool isGotoExp(const Expression e)
{
return e && e.op == EXP.goto_;
}
}
// True if 'e' is CTFEExp::cantexp, or an exception
bool exceptionOrCantInterpret(const Expression e)
{
return e && (e.op == EXP.cantExpression || e.op == EXP.thrownException || e.op == EXP.showCtfeContext);
}
/************** Aggregate literals (AA/string/array/struct) ******************/
// Given expr, which evaluates to an array/AA/string literal,
// return true if it needs to be copied
bool needToCopyLiteral(const Expression expr)
{
Expression e = cast()expr;
for (;;)
{
switch (e.op)
{
case EXP.arrayLiteral:
return e.isArrayLiteralExp().ownedByCtfe == OwnedBy.code;
case EXP.assocArrayLiteral:
return e.isAssocArrayLiteralExp().ownedByCtfe == OwnedBy.code;
case EXP.structLiteral:
return e.isStructLiteralExp().ownedByCtfe == OwnedBy.code;
case EXP.string_:
case EXP.this_:
case EXP.variable:
return false;
case EXP.assign:
return false;
case EXP.index:
case EXP.dotVariable:
case EXP.slice:
case EXP.cast_:
e = e.isUnaExp().e1;
continue;
case EXP.concatenate:
return needToCopyLiteral(e.isBinExp().e1) || needToCopyLiteral(e.isBinExp().e2);
case EXP.concatenateAssign:
case EXP.concatenateElemAssign:
case EXP.concatenateDcharAssign:
e = e.isBinExp().e2;
continue;
default:
return false;
}
}
}
private Expressions* copyLiteralArray(Expressions* oldelems, Expression basis = null)
{
if (!oldelems)
return oldelems;
incArrayAllocs();
auto newelems = new Expressions(oldelems.dim);
foreach (i, el; *oldelems)
{
(*newelems)[i] = copyLiteral(el ? el : basis).copy();
}
return newelems;
}
// Make a copy of the ArrayLiteral, AALiteral, String, or StructLiteral.
// This value will be used for in-place modification.
UnionExp copyLiteral(Expression e)
{
UnionExp ue = void;
if (auto se = e.isStringExp()) // syntaxCopy doesn't make a copy for StringExp!
{
char* s = cast(char*)mem.xcalloc(se.len + 1, se.sz);
const slice = se.peekData();
memcpy(s, slice.ptr, slice.length);
emplaceExp!(StringExp)(&ue, se.loc, s[0 .. se.len * se.sz], se.len, se.sz);
StringExp se2 = ue.exp().isStringExp();
se2.committed = se.committed;
se2.postfix = se.postfix;
se2.type = se.type;
se2.ownedByCtfe = OwnedBy.ctfe;
return ue;
}
if (auto ale = e.isArrayLiteralExp())
{
auto elements = copyLiteralArray(ale.elements, ale.basis);
emplaceExp!(ArrayLiteralExp)(&ue, e.loc, e.type, elements);
ArrayLiteralExp r = ue.exp().isArrayLiteralExp();
r.ownedByCtfe = OwnedBy.ctfe;
return ue;
}
if (auto aae = e.isAssocArrayLiteralExp())
{
emplaceExp!(AssocArrayLiteralExp)(&ue, e.loc, copyLiteralArray(aae.keys), copyLiteralArray(aae.values));
AssocArrayLiteralExp r = ue.exp().isAssocArrayLiteralExp();
r.type = e.type;
r.ownedByCtfe = OwnedBy.ctfe;
return ue;
}
if (auto sle = e.isStructLiteralExp())
{
/* syntaxCopy doesn't work for struct literals, because of a nasty special
* case: block assignment is permitted inside struct literals, eg,
* an int[4] array can be initialized with a single int.
*/
auto oldelems = sle.elements;
auto newelems = new Expressions(oldelems.dim);
foreach (i, ref el; *newelems)
{
// We need the struct definition to detect block assignment
auto v = sle.sd.fields[i];
auto m = (*oldelems)[i];
// If it is a void assignment, use the default initializer
if (!m)
m = voidInitLiteral(v.type, v).copy();
if (v.type.ty == Tarray || v.type.ty == Taarray)
{
// Don't have to copy array references
}
else
{
// Buzilla 15681: Copy the source element always.
m = copyLiteral(m).copy();
// Block assignment from inside struct literals
if (v.type.ty != m.type.ty && v.type.ty == Tsarray)
{
auto tsa = v.type.isTypeSArray();
auto len = cast(size_t)tsa.dim.toInteger();
m = createBlockDuplicatedArrayLiteral(&ue, e.loc, v.type, m, len);
if (m == ue.exp())
m = ue.copy();
}
}
el = m;
}
emplaceExp!(StructLiteralExp)(&ue, e.loc, sle.sd, newelems, sle.stype);
auto r = ue.exp().isStructLiteralExp();
r.type = e.type;
r.ownedByCtfe = OwnedBy.ctfe;
r.origin = sle.origin;
return ue;
}
if (e.op == EXP.function_ || e.op == EXP.delegate_ || e.op == EXP.symbolOffset || e.op == EXP.null_ || e.op == EXP.variable || e.op == EXP.dotVariable || e.op == EXP.int64 || e.op == EXP.float64 || e.op == EXP.char_ || e.op == EXP.complex80 || e.op == EXP.void_ || e.op == EXP.vector || e.op == EXP.typeid_)
{
// Simple value types
// Keep e1 for DelegateExp and DotVarExp
emplaceExp!(UnionExp)(&ue, e);
Expression r = ue.exp();
r.type = e.type;
return ue;
}
if (auto se = e.isSliceExp())
{
if (se.type.toBasetype().ty == Tsarray)
{
// same with resolveSlice()
if (se.e1.op == EXP.null_)
{
emplaceExp!(NullExp)(&ue, se.loc, se.type);
return ue;
}
ue = Slice(se.type, se.e1, se.lwr, se.upr);
auto r = ue.exp().isArrayLiteralExp();
r.elements = copyLiteralArray(r.elements);
r.ownedByCtfe = OwnedBy.ctfe;
return ue;
}
else
{
// Array slices only do a shallow copy
emplaceExp!(SliceExp)(&ue, e.loc, se.e1, se.lwr, se.upr);
Expression r = ue.exp();
r.type = e.type;
return ue;
}
}
if (isPointer(e.type))
{
// For pointers, we only do a shallow copy.
if (auto ae = e.isAddrExp())
emplaceExp!(AddrExp)(&ue, e.loc, ae.e1);
else if (auto ie = e.isIndexExp())
emplaceExp!(IndexExp)(&ue, e.loc, ie.e1, ie.e2);
else if (auto dve = e.isDotVarExp())
{
emplaceExp!(DotVarExp)(&ue, e.loc, dve.e1, dve.var, dve.hasOverloads);
}
else
assert(0);
Expression r = ue.exp();
r.type = e.type;
return ue;
}
if (auto cre = e.isClassReferenceExp())
{
emplaceExp!(ClassReferenceExp)(&ue, e.loc, cre.value, e.type);
return ue;
}
if (e.op == EXP.error)
{
emplaceExp!(UnionExp)(&ue, e);
return ue;
}
e.error("CTFE internal error: literal `%s`", e.toChars());
assert(0);
}
/* Deal with type painting.
* Type painting is a major nuisance: we can't just set
* e.type = type, because that would change the original literal.
* But, we can't simply copy the literal either, because that would change
* the values of any pointers.
*/
Expression paintTypeOntoLiteral(Type type, Expression lit)
{
if (lit.type.equals(type))
return lit;
return paintTypeOntoLiteralCopy(type, lit).copy();
}
Expression paintTypeOntoLiteral(UnionExp* pue, Type type, Expression lit)
{
if (lit.type.equals(type))
return lit;
*pue = paintTypeOntoLiteralCopy(type, lit);
return pue.exp();
}
private UnionExp paintTypeOntoLiteralCopy(Type type, Expression lit)
{
UnionExp ue;
if (lit.type.equals(type))
{
emplaceExp!(UnionExp)(&ue, lit);
return ue;
}
// If it is a cast to inout, retain the original type of the referenced part.
if (type.hasWild())
{
emplaceExp!(UnionExp)(&ue, lit);
ue.exp().type = type;
return ue;
}
if (auto se = lit.isSliceExp())
{
emplaceExp!(SliceExp)(&ue, lit.loc, se.e1, se.lwr, se.upr);
}
else if (auto ie = lit.isIndexExp())
{
emplaceExp!(IndexExp)(&ue, lit.loc, ie.e1, ie.e2);
}
else if (lit.op == EXP.arrayLiteral)
{
emplaceExp!(SliceExp)(&ue, lit.loc, lit, ctfeEmplaceExp!IntegerExp(Loc.initial, 0, Type.tsize_t), ArrayLength(Type.tsize_t, lit).copy());
}
else if (lit.op == EXP.string_)
{
// For strings, we need to introduce another level of indirection
emplaceExp!(SliceExp)(&ue, lit.loc, lit, ctfeEmplaceExp!IntegerExp(Loc.initial, 0, Type.tsize_t), ArrayLength(Type.tsize_t, lit).copy());
}
else if (auto aae = lit.isAssocArrayLiteralExp())
{
// TODO: we should be creating a reference to this AAExp, not
// just a ref to the keys and values.
OwnedBy wasOwned = aae.ownedByCtfe;
emplaceExp!(AssocArrayLiteralExp)(&ue, lit.loc, aae.keys, aae.values);
aae = ue.exp().isAssocArrayLiteralExp();
aae.ownedByCtfe = wasOwned;
}
else
{
// Can't type paint from struct to struct*; this needs another
// level of indirection
if (lit.op == EXP.structLiteral && isPointer(type))
lit.error("CTFE internal error: painting `%s`", type.toChars());
ue = copyLiteral(lit);
}
ue.exp().type = type;
return ue;
}
/*************************************
* If e is a SliceExp, constant fold it.
* Params:
* e = expression to resolve
* pue = if not null, store resulting expression here
* Returns:
* resulting expression
*/
Expression resolveSlice(Expression e, UnionExp* pue = null)
{
SliceExp se = e.isSliceExp();
if (!se)
return e;
if (se.e1.op == EXP.null_)
return se.e1;
if (pue)
{
*pue = Slice(e.type, se.e1, se.lwr, se.upr);
return pue.exp();
}
else
return Slice(e.type, se.e1, se.lwr, se.upr).copy();
}
/* Determine the array length, without interpreting it.
* e must be an array literal, or a slice
* It's very wasteful to resolve the slice when we only
* need the length.
*/
uinteger_t resolveArrayLength(Expression e)
{
switch (e.op)
{
case EXP.vector:
return e.isVectorExp().dim;
case EXP.null_:
return 0;
case EXP.slice:
{
auto se = e.isSliceExp();
const ilo = se.lwr.toInteger();
const iup = se.upr.toInteger();
return iup - ilo;
}
case EXP.string_:
return e.isStringExp().len;
case EXP.arrayLiteral:
{
const ale = e.isArrayLiteralExp();
return ale.elements ? ale.elements.dim : 0;
}
case EXP.assocArrayLiteral:
{
return e.isAssocArrayLiteralExp().keys.dim;
}
default:
assert(0);
}
}
/******************************
* Helper for NewExp
* Create an array literal consisting of 'elem' duplicated 'dim' times.
* Params:
* pue = where to store result
* loc = source location where the interpretation occurs
* type = target type of the result
* elem = the source of array element, it will be owned by the result
* dim = element number of the result
* Returns:
* Constructed ArrayLiteralExp
*/
ArrayLiteralExp createBlockDuplicatedArrayLiteral(UnionExp* pue, const ref Loc loc, Type type, Expression elem, size_t dim)
{
if (type.ty == Tsarray && type.nextOf().ty == Tsarray && elem.type.ty != Tsarray)
{
// If it is a multidimensional array literal, do it recursively
auto tsa = type.nextOf().isTypeSArray();
const len = cast(size_t)tsa.dim.toInteger();
elem = createBlockDuplicatedArrayLiteral(pue, loc, type.nextOf(), elem, len);
if (elem == pue.exp())
elem = pue.copy();
}
// Buzilla 15681
const tb = elem.type.toBasetype();
const mustCopy = tb.ty == Tstruct || tb.ty == Tsarray;
auto elements = new Expressions(dim);
foreach (i, ref el; *elements)
{
el = mustCopy && i ? copyLiteral(elem).copy() : elem;
}
emplaceExp!(ArrayLiteralExp)(pue, loc, type, elements);
auto ale = pue.exp().isArrayLiteralExp();
ale.ownedByCtfe = OwnedBy.ctfe;
return ale;
}
/******************************
* Helper for NewExp
* Create a string literal consisting of 'value' duplicated 'dim' times.
*/
StringExp createBlockDuplicatedStringLiteral(UnionExp* pue, const ref Loc loc, Type type, dchar value, size_t dim, ubyte sz)
{
auto s = cast(char*)mem.xcalloc(dim, sz);
foreach (elemi; 0 .. dim)
{
switch (sz)
{
case 1:
s[elemi] = cast(char)value;
break;
case 2:
(cast(wchar*)s)[elemi] = cast(wchar)value;
break;
case 4:
(cast(dchar*)s)[elemi] = value;
break;
default:
assert(0);
}
}
emplaceExp!(StringExp)(pue, loc, s[0 .. dim * sz], dim, sz);
auto se = pue.exp().isStringExp();
se.type = type;
se.committed = true;
se.ownedByCtfe = OwnedBy.ctfe;
return se;
}
// Return true if t is an AA
bool isAssocArray(Type t)
{
return t.toBasetype().isTypeAArray() !is null;
}
// Given a template AA type, extract the corresponding built-in AA type
TypeAArray toBuiltinAAType(Type t)
{
return t.toBasetype().isTypeAArray();
}
/************** TypeInfo operations ************************************/
// Return true if type is TypeInfo_Class
bool isTypeInfo_Class(const Type type)
{
auto tc = cast()type.isTypeClass();
return tc && (Type.dtypeinfo == tc.sym || Type.dtypeinfo.isBaseOf(tc.sym, null));
}
/************** Pointer operations ************************************/
// Return true if t is a pointer (not a function pointer)
bool isPointer(Type t)
{
Type tb = t.toBasetype();
return tb.ty == Tpointer && tb.nextOf().ty != Tfunction;
}
// For CTFE only. Returns true if 'e' is true or a non-null pointer.
bool isTrueBool(Expression e)
{
return e.toBool().hasValue(true) || ((e.type.ty == Tpointer || e.type.ty == Tclass) && e.op != EXP.null_);
}
/* Is it safe to convert from srcPointee* to destPointee* ?
* srcPointee is the genuine type (never void).
* destPointee may be void.
*/
bool isSafePointerCast(Type srcPointee, Type destPointee)
{
// It's safe to cast S** to D** if it's OK to cast S* to D*
while (srcPointee.ty == Tpointer && destPointee.ty == Tpointer)
{
srcPointee = srcPointee.nextOf();
destPointee = destPointee.nextOf();
}
// It's OK if both are the same (modulo const)
if (srcPointee.constConv(destPointee))
return true;
// It's ok to cast from/to shared because CTFE is single threaded anyways
if (srcPointee.unSharedOf() == destPointee.unSharedOf())
return true;
// It's OK if function pointers differ only in safe/pure/nothrow
if (srcPointee.ty == Tfunction && destPointee.ty == Tfunction)
return srcPointee.covariant(destPointee) == Covariant.yes ||
destPointee.covariant(srcPointee) == Covariant.yes;
// it's OK to cast to void*
if (destPointee.ty == Tvoid)
return true;
// It's OK to cast from V[K] to void*
if (srcPointee.ty == Taarray && destPointee == Type.tvoidptr)
return true;
// It's OK if they are the same size (static array of) integers, eg:
// int* --> uint*
// int[5][] --> uint[5][]
if (srcPointee.ty == Tsarray && destPointee.ty == Tsarray)
{
if (srcPointee.size() != destPointee.size())
return false;
srcPointee = srcPointee.baseElemOf();
destPointee = destPointee.baseElemOf();
}
return srcPointee.isintegral() && destPointee.isintegral() && srcPointee.size() == destPointee.size();
}
Expression getAggregateFromPointer(Expression e, dinteger_t* ofs)
{
*ofs = 0;
if (auto ae = e.isAddrExp())
e = ae.e1;
if (auto soe = e.isSymOffExp())
*ofs = soe.offset;
if (auto dve = e.isDotVarExp())
{
auto ex = dve.e1;
const v = dve.var.isVarDeclaration();
assert(v);
StructLiteralExp se = (ex.op == EXP.classReference)
? ex.isClassReferenceExp().value
: ex.isStructLiteralExp();
// We can't use getField, because it makes a copy
const i = (ex.op == EXP.classReference)
? ex.isClassReferenceExp().getFieldIndex(e.type, v.offset)
: se.getFieldIndex(e.type, v.offset);
e = (*se.elements)[i];
}
if (auto ie = e.isIndexExp())
{
// Note that each AA element is part of its own memory block
if ((ie.e1.type.ty == Tarray || ie.e1.type.ty == Tsarray || ie.e1.op == EXP.string_ || ie.e1.op == EXP.arrayLiteral) && ie.e2.op == EXP.int64)
{
*ofs = ie.e2.toInteger();
return ie.e1;
}
}
if (auto se = e.isSliceExp())
{
if (se && e.type.toBasetype().ty == Tsarray &&
(se.e1.type.ty == Tarray || se.e1.type.ty == Tsarray || se.e1.op == EXP.string_ || se.e1.op == EXP.arrayLiteral) && se.lwr.op == EXP.int64)
{
*ofs = se.lwr.toInteger();
return se.e1;
}
}
// It can be a `null` disguised as a cast, e.g. `cast(void*)0`.
if (auto ie = e.isIntegerExp())
if (ie.type.ty == Tpointer && ie.getInteger() == 0)
return new NullExp(ie.loc, e.type.nextOf());
// Those casts are invalid, but let the rest of the code handle it,
// as it could be something like `x !is null`, which doesn't need
// to dereference the pointer, even if the pointer is `cast(void*)420`.
return e;
}
/** Return true if agg1 and agg2 are pointers to the same memory block
*/
bool pointToSameMemoryBlock(Expression agg1, Expression agg2)
{
if (agg1 == agg2)
return true;
// For integers cast to pointers, we regard them as non-comparable
// unless they are identical. (This may be overly strict).
if (agg1.op == EXP.int64 && agg2.op == EXP.int64 && agg1.toInteger() == agg2.toInteger())
{
return true;
}
// Note that type painting can occur with VarExp, so we
// must compare the variables being pointed to.
if (agg1.op == EXP.variable && agg2.op == EXP.variable && agg1.isVarExp().var == agg2.isVarExp().var)
{
return true;
}
if (agg1.op == EXP.symbolOffset && agg2.op == EXP.symbolOffset && agg1.isSymOffExp().var == agg2.isSymOffExp().var)
{
return true;
}
return false;
}
// return e1 - e2 as an integer, or error if not possible
Expression pointerDifference(UnionExp* pue, const ref Loc loc, Type type, Expression e1, Expression e2)
{
dinteger_t ofs1, ofs2;
Expression agg1 = getAggregateFromPointer(e1, &ofs1);
Expression agg2 = getAggregateFromPointer(e2, &ofs2);
if (agg1 == agg2)
{
Type pointee = (cast(TypePointer)agg1.type).next;
const sz = pointee.size();
emplaceExp!(IntegerExp)(pue, loc, (ofs1 - ofs2) * sz, type);
}
else if (agg1.op == EXP.string_ && agg2.op == EXP.string_ &&
agg1.isStringExp().peekString().ptr == agg2.isStringExp().peekString().ptr)
{
Type pointee = (cast(TypePointer)agg1.type).next;
const sz = pointee.size();
emplaceExp!(IntegerExp)(pue, loc, (ofs1 - ofs2) * sz, type);
}
else if (agg1.op == EXP.symbolOffset && agg2.op == EXP.symbolOffset &&
agg1.isSymOffExp().var == agg2.isSymOffExp().var)
{
emplaceExp!(IntegerExp)(pue, loc, ofs1 - ofs2, type);
}
else
{
error(loc, "`%s - %s` cannot be interpreted at compile time: cannot subtract pointers to two different memory blocks", e1.toChars(), e2.toChars());
emplaceExp!(CTFEExp)(pue, EXP.cantExpression);
}
return pue.exp();
}
// Return eptr op e2, where eptr is a pointer, e2 is an integer,
// and op is EXP.add or EXP.min
Expression pointerArithmetic(UnionExp* pue, const ref Loc loc, EXP op, Type type, Expression eptr, Expression e2)
{
if (eptr.type.nextOf().ty == Tvoid)
{
error(loc, "cannot perform arithmetic on `void*` pointers at compile time");
Lcant:
emplaceExp!(CTFEExp)(pue, EXP.cantExpression);
return pue.exp();
}
if (eptr.op == EXP.address)
eptr = eptr.isAddrExp().e1;
dinteger_t ofs1;
Expression agg1 = getAggregateFromPointer(eptr, &ofs1);
if (agg1.op == EXP.symbolOffset)
{
if (agg1.isSymOffExp().var.type.ty != Tsarray)
{
error(loc, "cannot perform pointer arithmetic on arrays of unknown length at compile time");
goto Lcant;
}
}
else if (agg1.op != EXP.string_ && agg1.op != EXP.arrayLiteral)
{
error(loc, "cannot perform pointer arithmetic on non-arrays at compile time");
goto Lcant;
}
dinteger_t ofs2 = e2.toInteger();
Type pointee = (cast(TypeNext)agg1.type.toBasetype()).next;
dinteger_t sz = pointee.size();
sinteger_t indx;
dinteger_t len;
if (agg1.op == EXP.symbolOffset)
{
indx = ofs1 / sz;
len = (cast(TypeSArray)agg1.isSymOffExp().var.type).dim.toInteger();
}
else
{
Expression dollar = ArrayLength(Type.tsize_t, agg1).copy();
assert(!CTFEExp.isCantExp(dollar));
indx = ofs1;
len = dollar.toInteger();
}
if (op == EXP.add || op == EXP.addAssign || op == EXP.plusPlus)
indx += ofs2 / sz;
else if (op == EXP.min || op == EXP.minAssign || op == EXP.minusMinus)
indx -= ofs2 / sz;
else
{
error(loc, "CTFE internal error: bad pointer operation");
goto Lcant;
}
if (indx < 0 || len < indx)
{
error(loc, "cannot assign pointer to index %lld inside memory block `[0..%lld]`", indx, len);
goto Lcant;
}
if (agg1.op == EXP.symbolOffset)
{
emplaceExp!(SymOffExp)(pue, loc, agg1.isSymOffExp().var, indx * sz);
SymOffExp se = pue.exp().isSymOffExp();
se.type = type;
return pue.exp();
}
if (agg1.op != EXP.arrayLiteral && agg1.op != EXP.string_)
{
error(loc, "CTFE internal error: pointer arithmetic `%s`", agg1.toChars());
goto Lcant;
}
if (eptr.type.toBasetype().ty == Tsarray)
{
dinteger_t dim = (cast(TypeSArray)eptr.type.toBasetype()).dim.toInteger();
// Create a CTFE pointer &agg1[indx .. indx+dim]
auto se = ctfeEmplaceExp!SliceExp(loc, agg1,
ctfeEmplaceExp!IntegerExp(loc, indx, Type.tsize_t),
ctfeEmplaceExp!IntegerExp(loc, indx + dim, Type.tsize_t));
se.type = type.toBasetype().nextOf();
emplaceExp!(AddrExp)(pue, loc, se);
pue.exp().type = type;
return pue.exp();
}
// Create a CTFE pointer &agg1[indx]
auto ofs = ctfeEmplaceExp!IntegerExp(loc, indx, Type.tsize_t);
Expression ie = ctfeEmplaceExp!IndexExp(loc, agg1, ofs);
ie.type = type.toBasetype().nextOf(); // https://issues.dlang.org/show_bug.cgi?id=13992
emplaceExp!(AddrExp)(pue, loc, ie);
pue.exp().type = type;
return pue.exp();
}
// Return 1 if true, 0 if false
// -1 if comparison is illegal because they point to non-comparable memory blocks
int comparePointers(EXP op, Expression agg1, dinteger_t ofs1, Expression agg2, dinteger_t ofs2)
{
if (pointToSameMemoryBlock(agg1, agg2))
{
int n;
switch (op)
{
case EXP.lessThan:
n = (ofs1 < ofs2);
break;
case EXP.lessOrEqual:
n = (ofs1 <= ofs2);
break;
case EXP.greaterThan:
n = (ofs1 > ofs2);
break;
case EXP.greaterOrEqual:
n = (ofs1 >= ofs2);
break;
case EXP.identity:
case EXP.equal:
n = (ofs1 == ofs2);
break;
case EXP.notIdentity:
case EXP.notEqual:
n = (ofs1 != ofs2);
break;
default:
assert(0);
}
return n;
}
const null1 = (agg1.op == EXP.null_);
const null2 = (agg2.op == EXP.null_);
int cmp;
if (null1 || null2)
{
switch (op)
{
case EXP.lessThan:
cmp = null1 && !null2;
break;
case EXP.greaterThan:
cmp = !null1 && null2;
break;
case EXP.lessOrEqual:
cmp = null1;
break;
case EXP.greaterOrEqual:
cmp = null2;
break;
case EXP.identity:
case EXP.equal:
case EXP.notIdentity: // 'cmp' gets inverted below
case EXP.notEqual:
cmp = (null1 == null2);
break;
default:
assert(0);
}
}
else
{
switch (op)
{
case EXP.identity:
case EXP.equal:
case EXP.notIdentity: // 'cmp' gets inverted below
case EXP.notEqual:
cmp = 0;
break;
default:
return -1; // memory blocks are different
}
}
if (op == EXP.notIdentity || op == EXP.notEqual)
cmp ^= 1;
return cmp;
}
// True if conversion from type 'from' to 'to' involves a reinterpret_cast
// floating point -> integer or integer -> floating point
bool isFloatIntPaint(Type to, Type from)
{
return from.size() == to.size() && (from.isintegral() && to.isfloating() || from.isfloating() && to.isintegral());
}
// Reinterpret float/int value 'fromVal' as a float/integer of type 'to'.
Expression paintFloatInt(UnionExp* pue, Expression fromVal, Type to)
{
if (exceptionOrCantInterpret(fromVal))
return fromVal;
assert(to.size() == 4 || to.size() == 8);
return Compiler.paintAsType(pue, fromVal, to);
}
/******** Constant folding, with support for CTFE ***************************/
/// Return true if non-pointer expression e can be compared
/// with >,is, ==, etc, using ctfeCmp, ctfeEqual, ctfeIdentity
bool isCtfeComparable(Expression e)
{
if (e.op == EXP.slice)
e = e.isSliceExp().e1;
if (e.isConst() != 1)
{
if (e.op == EXP.null_ || e.op == EXP.string_ || e.op == EXP.function_ || e.op == EXP.delegate_ || e.op == EXP.arrayLiteral || e.op == EXP.structLiteral || e.op == EXP.assocArrayLiteral || e.op == EXP.classReference)
{
return true;
}
// https://issues.dlang.org/show_bug.cgi?id=14123
// TypeInfo object is comparable in CTFE
if (e.op == EXP.typeid_)
return true;
return false;
}
return true;
}
/// Map EXP comparison ops
private bool numCmp(N)(EXP op, N n1, N n2)
{
switch (op)
{
case EXP.lessThan:
return n1 < n2;
case EXP.lessOrEqual:
return n1 <= n2;
case EXP.greaterThan:
return n1 > n2;
case EXP.greaterOrEqual:
return n1 >= n2;
default:
assert(0);
}
}
/// Returns cmp OP 0; where OP is ==, !=, <, >=, etc. Result is 0 or 1
bool specificCmp(EXP op, int rawCmp)
{
return numCmp!int(op, rawCmp, 0);
}
/// Returns e1 OP e2; where OP is ==, !=, <, >=, etc. Result is 0 or 1
bool intUnsignedCmp(EXP op, dinteger_t n1, dinteger_t n2)
{
return numCmp!dinteger_t(op, n1, n2);
}
/// Returns e1 OP e2; where OP is ==, !=, <, >=, etc. Result is 0 or 1
bool intSignedCmp(EXP op, sinteger_t n1, sinteger_t n2)
{
return numCmp!sinteger_t(op, n1, n2);
}
/// Returns e1 OP e2; where OP is ==, !=, <, >=, etc. Result is 0 or 1
bool realCmp(EXP op, real_t r1, real_t r2)
{
// Don't rely on compiler, handle NAN arguments separately
if (CTFloat.isNaN(r1) || CTFloat.isNaN(r2)) // if unordered
{
switch (op)
{
case EXP.lessThan:
case EXP.lessOrEqual:
case EXP.greaterThan:
case EXP.greaterOrEqual:
return false;
default:
assert(0);
}
}
else
{
return numCmp!real_t(op, r1, r2);
}
}
/* Conceptually the same as memcmp(e1, e2).
* e1 and e2 may be strings, arrayliterals, or slices.
* For string types, return <0 if e1 < e2, 0 if e1==e2, >0 if e1 > e2.
* For all other types, return 0 if e1 == e2, !=0 if e1 != e2.
* Returns:
* -1,0,1
*/
private int ctfeCmpArrays(const ref Loc loc, Expression e1, Expression e2, uinteger_t len)
{
// Resolve slices, if necessary
uinteger_t lo1 = 0;
uinteger_t lo2 = 0;
Expression x1 = e1;
if (auto sle1 = x1.isSliceExp())
{
lo1 = sle1.lwr.toInteger();
x1 = sle1.e1;
}
auto se1 = x1.isStringExp();
auto ae1 = x1.isArrayLiteralExp();
Expression x2 = e2;
if (auto sle2 = x2.isSliceExp())
{
lo2 = sle2.lwr.toInteger();
x2 = sle2.e1;
}
auto se2 = x2.isStringExp();
auto ae2 = x2.isArrayLiteralExp();
// Now both must be either EXP.arrayLiteral or EXP.string_
if (se1 && se2)
return sliceCmpStringWithString(se1, se2, cast(size_t)lo1, cast(size_t)lo2, cast(size_t)len);
if (se1 && ae2)
return sliceCmpStringWithArray(se1, ae2, cast(size_t)lo1, cast(size_t)lo2, cast(size_t)len);
if (se2 && ae1)
return -sliceCmpStringWithArray(se2, ae1, cast(size_t)lo2, cast(size_t)lo1, cast(size_t)len);
assert(ae1 && ae2);
// Comparing two array literals. This case is potentially recursive.
// If they aren't strings, we just need an equality check rather than
// a full cmp.
const bool needCmp = ae1.type.nextOf().isintegral();
foreach (size_t i; 0 .. cast(size_t)len)
{
Expression ee1 = (*ae1.elements)[cast(size_t)(lo1 + i)];
Expression ee2 = (*ae2.elements)[cast(size_t)(lo2 + i)];
if (needCmp)
{
const sinteger_t c = ee1.toInteger() - ee2.toInteger();
if (c > 0)
return 1;
if (c < 0)
return -1;
}
else
{
if (ctfeRawCmp(loc, ee1, ee2))
return 1;
}
}
return 0;
}
/* Given a delegate expression e, return .funcptr.
* If e is NullExp, return NULL.
*/
private FuncDeclaration funcptrOf(Expression e)
{
assert(e.type.ty == Tdelegate);
if (auto de = e.isDelegateExp())
return de.func;
if (auto fe = e.isFuncExp())
return fe.fd;
assert(e.op == EXP.null_);
return null;
}
private bool isArray(const Expression e)
{
return e.op == EXP.arrayLiteral || e.op == EXP.string_ || e.op == EXP.slice || e.op == EXP.null_;
}
/*****
* Params:
* loc = source file location
* e1 = left operand
* e2 = right operand
* identity = true for `is` identity comparisons
* Returns:
* For strings, return <0 if e1 < e2, 0 if e1==e2, >0 if e1 > e2.
* For all other types, return 0 if e1 == e2, !=0 if e1 != e2.
*/
private int ctfeRawCmp(const ref Loc loc, Expression e1, Expression e2, bool identity = false)
{
if (e1.op == EXP.classReference || e2.op == EXP.classReference)
{
if (e1.op == EXP.classReference && e2.op == EXP.classReference &&
e1.isClassReferenceExp().value == e2.isClassReferenceExp().value)
return 0;
return 1;
}
if (e1.op == EXP.typeid_ && e2.op == EXP.typeid_)
{
// printf("e1: %s\n", e1.toChars());
// printf("e2: %s\n", e2.toChars());
Type t1 = isType(e1.isTypeidExp().obj);
Type t2 = isType(e2.isTypeidExp().obj);
assert(t1);
assert(t2);
return t1 != t2;
}
// null == null, regardless of type
if (e1.op == EXP.null_ && e2.op == EXP.null_)
return 0;
if (e1.type.ty == Tpointer && e2.type.ty == Tpointer)
{
// Can only be an equality test.
dinteger_t ofs1, ofs2;
Expression agg1 = getAggregateFromPointer(e1, &ofs1);
Expression agg2 = getAggregateFromPointer(e2, &ofs2);
if ((agg1 == agg2) || (agg1.op == EXP.variable && agg2.op == EXP.variable && agg1.isVarExp().var == agg2.isVarExp().var))
{
if (ofs1 == ofs2)
return 0;
}
return 1;
}
if (e1.type.ty == Tdelegate && e2.type.ty == Tdelegate)
{
// If .funcptr isn't the same, they are not equal
if (funcptrOf(e1) != funcptrOf(e2))
return 1;
// If both are delegate literals, assume they have the
// same closure pointer. TODO: We don't support closures yet!
if (e1.op == EXP.function_ && e2.op == EXP.function_)
return 0;
assert(e1.op == EXP.delegate_ && e2.op == EXP.delegate_);
// Same .funcptr. Do they have the same .ptr?
Expression ptr1 = e1.isDelegateExp().e1;
Expression ptr2 = e2.isDelegateExp().e1;
dinteger_t ofs1, ofs2;
Expression agg1 = getAggregateFromPointer(ptr1, &ofs1);
Expression agg2 = getAggregateFromPointer(ptr2, &ofs2);
// If they are EXP.variable, it means they are FuncDeclarations
if ((agg1 == agg2 && ofs1 == ofs2) || (agg1.op == EXP.variable && agg2.op == EXP.variable && agg1.isVarExp().var == agg2.isVarExp().var))
{
return 0;
}
return 1;
}
if (isArray(e1) && isArray(e2))
{
const uinteger_t len1 = resolveArrayLength(e1);
const uinteger_t len2 = resolveArrayLength(e2);
// workaround for dmc optimizer bug calculating wrong len for
// uinteger_t len = (len1 < len2 ? len1 : len2);
// if (len == 0) ...
if (len1 > 0 && len2 > 0)
{
const uinteger_t len = (len1 < len2 ? len1 : len2);
const int res = ctfeCmpArrays(loc, e1, e2, len);
if (res != 0)
return res;
}
return cast(int)(len1 - len2);
}
if (e1.type.isintegral())
{
return e1.toInteger() != e2.toInteger();
}
if (e1.type.isreal() || e1.type.isimaginary())
{
real_t r1 = e1.type.isreal() ? e1.toReal() : e1.toImaginary();
real_t r2 = e1.type.isreal() ? e2.toReal() : e2.toImaginary();
if (identity)
return !RealIdentical(r1, r2);
if (CTFloat.isNaN(r1) || CTFloat.isNaN(r2)) // if unordered
{
return 1; // they are not equal
}
else
{
return (r1 != r2);
}
}
else if (e1.type.iscomplex())
{
auto c1 = e1.toComplex();
auto c2 = e2.toComplex();
if (identity)
{
return !RealIdentical(c1.re, c2.re) && !RealIdentical(c1.im, c2.im);
}
return c1 != c2;
}
if (e1.op == EXP.structLiteral && e2.op == EXP.structLiteral)
{
StructLiteralExp es1 = e1.isStructLiteralExp();
StructLiteralExp es2 = e2.isStructLiteralExp();
// For structs, we only need to return 0 or 1 (< and > aren't legal).
if (es1.sd != es2.sd)
return 1;
else if ((!es1.elements || !es1.elements.dim) && (!es2.elements || !es2.elements.dim))
return 0; // both arrays are empty
else if (!es1.elements || !es2.elements)
return 1;
else if (es1.elements.dim != es2.elements.dim)
return 1;
else
{
foreach (size_t i; 0 .. es1.elements.dim)
{
Expression ee1 = (*es1.elements)[i];
Expression ee2 = (*es2.elements)[i];
// https://issues.dlang.org/show_bug.cgi?id=16284
if (ee1.op == EXP.void_ && ee2.op == EXP.void_) // if both are VoidInitExp
continue;
if (ee1 == ee2)
continue;
if (!ee1 || !ee2)
return 1;
const int cmp = ctfeRawCmp(loc, ee1, ee2, identity);
if (cmp)
return 1;
}
return 0; // All elements are equal
}
}
if (e1.op == EXP.assocArrayLiteral && e2.op == EXP.assocArrayLiteral)
{
AssocArrayLiteralExp es1 = e1.isAssocArrayLiteralExp();
AssocArrayLiteralExp es2 = e2.isAssocArrayLiteralExp();
size_t dim = es1.keys.dim;
if (es2.keys.dim != dim)
return 1;
bool* used = cast(bool*)mem.xmalloc(bool.sizeof * dim);
memset(used, 0, bool.sizeof * dim);
foreach (size_t i; 0 .. dim)
{
Expression k1 = (*es1.keys)[i];
Expression v1 = (*es1.values)[i];
Expression v2 = null;
foreach (size_t j; 0 .. dim)
{
if (used[j])
continue;
Expression k2 = (*es2.keys)[j];
if (ctfeRawCmp(loc, k1, k2, identity))
continue;
used[j] = true;
v2 = (*es2.values)[j];
break;
}
if (!v2 || ctfeRawCmp(loc, v1, v2, identity))
{
mem.xfree(used);
return 1;
}
}
mem.xfree(used);
return 0;
}
else if (e1.op == EXP.assocArrayLiteral && e2.op == EXP.null_)
{
return e1.isAssocArrayLiteralExp.keys.dim != 0;
}
else if (e1.op == EXP.null_ && e2.op == EXP.assocArrayLiteral)
{
return e2.isAssocArrayLiteralExp.keys.dim != 0;
}
error(loc, "CTFE internal error: bad compare of `%s` and `%s`", e1.toChars(), e2.toChars());
assert(0);
}
/// Evaluate ==, !=. Resolves slices before comparing. Returns 0 or 1
bool ctfeEqual(const ref Loc loc, EXP op, Expression e1, Expression e2)
{
return !ctfeRawCmp(loc, e1, e2) ^ (op == EXP.notEqual);
}
/// Evaluate is, !is. Resolves slices before comparing. Returns 0 or 1
bool ctfeIdentity(const ref Loc loc, EXP op, Expression e1, Expression e2)
{
//printf("ctfeIdentity %s %s\n", e1.toChars(), e2.toChars());
//printf("ctfeIdentity op = '%s', e1 = %s %s, e2 = %s %s\n", EXPtoString(op).ptr,
// EXPtoString(e1.op).ptr, e1.toChars(), EXPtoString(e2.op).ptr, e1.toChars());
bool cmp;
if (e1.op == EXP.null_)
{
cmp = (e2.op == EXP.null_);
}
else if (e2.op == EXP.null_)
{
cmp = false;
}
else if (e1.op == EXP.symbolOffset && e2.op == EXP.symbolOffset)
{
SymOffExp es1 = e1.isSymOffExp();
SymOffExp es2 = e2.isSymOffExp();
cmp = (es1.var == es2.var && es1.offset == es2.offset);
}
else if (e1.type.isreal())
cmp = RealIdentical(e1.toReal(), e2.toReal());
else if (e1.type.isimaginary())
cmp = RealIdentical(e1.toImaginary(), e2.toImaginary());
else if (e1.type.iscomplex())
{
complex_t v1 = e1.toComplex();
complex_t v2 = e2.toComplex();
cmp = RealIdentical(creall(v1), creall(v2)) && RealIdentical(cimagl(v1), cimagl(v1));
}
else
{
cmp = !ctfeRawCmp(loc, e1, e2, true);
}
if (op == EXP.notIdentity || op == EXP.notEqual)
cmp ^= true;
return cmp;
}
/// Evaluate >,<=, etc. Resolves slices before comparing. Returns 0 or 1
bool ctfeCmp(const ref Loc loc, EXP op, Expression e1, Expression e2)
{
Type t1 = e1.type.toBasetype();
Type t2 = e2.type.toBasetype();
if (t1.isString() && t2.isString())
return specificCmp(op, ctfeRawCmp(loc, e1, e2));
else if (t1.isreal())
return realCmp(op, e1.toReal(), e2.toReal());
else if (t1.isimaginary())
return realCmp(op, e1.toImaginary(), e2.toImaginary());
else if (t1.isunsigned() || t2.isunsigned())
return intUnsignedCmp(op, e1.toInteger(), e2.toInteger());
else
return intSignedCmp(op, e1.toInteger(), e2.toInteger());
}
UnionExp ctfeCat(const ref Loc loc, Type type, Expression e1, Expression e2)
{
Type t1 = e1.type.toBasetype();
Type t2 = e2.type.toBasetype();
UnionExp ue;
if (e2.op == EXP.string_ && e1.op == EXP.arrayLiteral && t1.nextOf().isintegral())
{
// [chars] ~ string => string (only valid for CTFE)
StringExp es1 = e2.isStringExp();
ArrayLiteralExp es2 = e1.isArrayLiteralExp();
const len = es1.len + es2.elements.dim;
const sz = es1.sz;
void* s = mem.xmalloc((len + 1) * sz);
const data1 = es1.peekData();
memcpy(cast(char*)s + sz * es2.elements.dim, data1.ptr, data1.length);
foreach (size_t i; 0 .. es2.elements.dim)
{
Expression es2e = (*es2.elements)[i];
if (es2e.op != EXP.int64)
{
emplaceExp!(CTFEExp)(&ue, EXP.cantExpression);
return ue;
}
dinteger_t v = es2e.toInteger();
Port.valcpy(cast(char*)s + i * sz, v, sz);
}
// Add terminating 0
memset(cast(char*)s + len * sz, 0, sz);
emplaceExp!(StringExp)(&ue, loc, s[0 .. len * sz], len, sz);
StringExp es = ue.exp().isStringExp();
es.committed = 0;
es.type = type;
return ue;
}
if (e1.op == EXP.string_ && e2.op == EXP.arrayLiteral && t2.nextOf().isintegral())
{
// string ~ [chars] => string (only valid for CTFE)
// Concatenate the strings
StringExp es1 = e1.isStringExp();
ArrayLiteralExp es2 = e2.isArrayLiteralExp();
const len = es1.len + es2.elements.dim;
const sz = es1.sz;
void* s = mem.xmalloc((len + 1) * sz);
auto slice = es1.peekData();
memcpy(s, slice.ptr, slice.length);
foreach (size_t i; 0 .. es2.elements.dim)
{
Expression es2e = (*es2.elements)[i];
if (es2e.op != EXP.int64)
{
emplaceExp!(CTFEExp)(&ue, EXP.cantExpression);
return ue;
}
const v = es2e.toInteger();
Port.valcpy(cast(char*)s + (es1.len + i) * sz, v, sz);
}
// Add terminating 0
memset(cast(char*)s + len * sz, 0, sz);
emplaceExp!(StringExp)(&ue, loc, s[0 .. len * sz], len, sz);
StringExp es = ue.exp().isStringExp();
es.sz = sz;
es.committed = 0; //es1.committed;
es.type = type;
return ue;
}
if (e1.op == EXP.arrayLiteral && e2.op == EXP.arrayLiteral && t1.nextOf().equals(t2.nextOf()))
{
// [ e1 ] ~ [ e2 ] ---> [ e1, e2 ]
ArrayLiteralExp es1 = e1.isArrayLiteralExp();
ArrayLiteralExp es2 = e2.isArrayLiteralExp();
emplaceExp!(ArrayLiteralExp)(&ue, es1.loc, type, copyLiteralArray(es1.elements));
es1 = ue.exp().isArrayLiteralExp();
es1.elements.insert(es1.elements.dim, copyLiteralArray(es2.elements));
return ue;
}
if (e1.op == EXP.arrayLiteral && e2.op == EXP.null_ && t1.nextOf().equals(t2.nextOf()))
{
// [ e1 ] ~ null ----> [ e1 ].dup
ue = paintTypeOntoLiteralCopy(type, copyLiteral(e1).copy());
return ue;
}
if (e1.op == EXP.null_ && e2.op == EXP.arrayLiteral && t1.nextOf().equals(t2.nextOf()))
{
// null ~ [ e2 ] ----> [ e2 ].dup
ue = paintTypeOntoLiteralCopy(type, copyLiteral(e2).copy());
return ue;
}
ue = Cat(loc, type, e1, e2);
return ue;
}
/* Given an AA literal 'ae', and a key 'e2':
* Return ae[e2] if present, or NULL if not found.
*/
Expression findKeyInAA(const ref Loc loc, AssocArrayLiteralExp ae, Expression e2)
{
/* Search the keys backwards, in case there are duplicate keys
*/
for (size_t i = ae.keys.dim; i;)
{
--i;
Expression ekey = (*ae.keys)[i];
const int eq = ctfeEqual(loc, EXP.equal, ekey, e2);
if (eq)
{
return (*ae.values)[i];
}
}
return null;
}
/* Same as for constfold.Index, except that it only works for static arrays,
* dynamic arrays, and strings. We know that e1 is an
* interpreted CTFE expression, so it cannot have side-effects.
*/
Expression ctfeIndex(UnionExp* pue, const ref Loc loc, Type type, Expression e1, uinteger_t indx)
{
//printf("ctfeIndex(e1 = %s)\n", e1.toChars());
assert(e1.type);
if (auto es1 = e1.isStringExp())
{
if (indx >= es1.len)
{
error(loc, "string index %llu is out of bounds `[0 .. %llu]`", indx, cast(ulong)es1.len);
return CTFEExp.cantexp;
}
emplaceExp!IntegerExp(pue, loc, es1.getCodeUnit(cast(size_t) indx), type);
return pue.exp();
}
if (auto ale = e1.isArrayLiteralExp())
{
if (indx >= ale.elements.dim)
{
error(loc, "array index %llu is out of bounds `%s[0 .. %llu]`", indx, e1.toChars(), cast(ulong)ale.elements.dim);
return CTFEExp.cantexp;
}
Expression e = (*ale.elements)[cast(size_t)indx];
return paintTypeOntoLiteral(pue, type, e);
}
assert(0);
}
Expression ctfeCast(UnionExp* pue, const ref Loc loc, Type type, Type to, Expression e, bool explicitCast = false)
{
Expression paint()
{
return paintTypeOntoLiteral(pue, to, e);
}
if (e.op == EXP.null_)
return paint();
if (e.op == EXP.classReference)
{
// Disallow reinterpreting class casts. Do this by ensuring that
// the original class can implicitly convert to the target class.
// Also do not check 'alias this' for explicit cast expressions.
auto tclass = e.isClassReferenceExp().originalClass().type.isTypeClass();
auto match = explicitCast ? tclass.implicitConvToWithoutAliasThis(to.mutableOf())
: tclass.implicitConvTo(to.mutableOf());
if (match)
return paint();
else
{
emplaceExp!(NullExp)(pue, loc, to);
return pue.exp();
}
}
// Allow TypeInfo type painting
if (isTypeInfo_Class(e.type) && e.type.implicitConvTo(to))
return paint();
// Allow casting away const for struct literals
if (e.op == EXP.structLiteral && e.type.toBasetype().castMod(0) == to.toBasetype().castMod(0))
return paint();
Expression r;
if (e.type.equals(type) && type.equals(to))
{
// necessary not to change e's address for pointer comparisons
r = e;
}
else if (to.toBasetype().ty == Tarray &&
type.toBasetype().ty == Tarray &&
to.toBasetype().nextOf().size() == type.toBasetype().nextOf().size())
{
// https://issues.dlang.org/show_bug.cgi?id=12495
// Array reinterpret casts: eg. string to immutable(ubyte)[]
return paint();
}
else
{
*pue = Cast(loc, type, to, e);
r = pue.exp();
}
if (CTFEExp.isCantExp(r))
error(loc, "cannot cast `%s` to `%s` at compile time", e.toChars(), to.toChars());
if (auto ae = e.isArrayLiteralExp())
ae.ownedByCtfe = OwnedBy.ctfe;
if (auto se = e.isStringExp())
se.ownedByCtfe = OwnedBy.ctfe;
return r;
}
/******** Assignment helper functions ***************************/
/* Set dest = src, where both dest and src are container value literals
* (ie, struct literals, or static arrays (can be an array literal or a string))
* Assignment is recursively in-place.
* Purpose: any reference to a member of 'dest' will remain valid after the
* assignment.
*/
void assignInPlace(Expression dest, Expression src)
{
if (!(dest.op == EXP.structLiteral || dest.op == EXP.arrayLiteral || dest.op == EXP.string_))
{
printf("invalid op %d %d\n", src.op, dest.op);
assert(0);
}
Expressions* oldelems;
Expressions* newelems;
if (dest.op == EXP.structLiteral)
{
assert(dest.op == src.op);
oldelems = dest.isStructLiteralExp().elements;
newelems = src.isStructLiteralExp().elements;
auto sd = dest.isStructLiteralExp().sd;
const nfields = sd.nonHiddenFields();
const nvthis = sd.fields.dim - nfields;
if (nvthis && oldelems.dim >= nfields && oldelems.dim < newelems.dim)
foreach (_; 0 .. newelems.dim - oldelems.dim)
oldelems.push(null);
}
else if (dest.op == EXP.arrayLiteral && src.op == EXP.arrayLiteral)
{
oldelems = dest.isArrayLiteralExp().elements;
newelems = src.isArrayLiteralExp().elements;
}
else if (dest.op == EXP.string_ && src.op == EXP.string_)
{
sliceAssignStringFromString(dest.isStringExp(), src.isStringExp(), 0);
return;
}
else if (dest.op == EXP.arrayLiteral && src.op == EXP.string_)
{
sliceAssignArrayLiteralFromString(dest.isArrayLiteralExp(), src.isStringExp(), 0);
return;
}
else if (src.op == EXP.arrayLiteral && dest.op == EXP.string_)
{
sliceAssignStringFromArrayLiteral(dest.isStringExp(), src.isArrayLiteralExp(), 0);
return;
}
else
{
printf("invalid op %d %d\n", src.op, dest.op);
assert(0);
}
assert(oldelems.dim == newelems.dim);
foreach (size_t i; 0 .. oldelems.dim)
{
Expression e = (*newelems)[i];
Expression o = (*oldelems)[i];
if (e.op == EXP.structLiteral)
{
assert(o.op == e.op);
assignInPlace(o, e);
}
else if (e.type.ty == Tsarray && e.op != EXP.void_ && o.type.ty == Tsarray)
{
assignInPlace(o, e);
}
else
{
(*oldelems)[i] = (*newelems)[i];
}
}
}
// Given an AA literal aae, set aae[index] = newval and return newval.
Expression assignAssocArrayElement(const ref Loc loc, AssocArrayLiteralExp aae, Expression index, Expression newval)
{
/* Create new associative array literal reflecting updated key/value
*/
Expressions* keysx = aae.keys;
Expressions* valuesx = aae.values;
int updated = 0;
for (size_t j = valuesx.dim; j;)
{
j--;
Expression ekey = (*aae.keys)[j];
int eq = ctfeEqual(loc, EXP.equal, ekey, index);
if (eq)
{
(*valuesx)[j] = newval;
updated = 1;
}
}
if (!updated)
{
// Append index/newval to keysx[]/valuesx[]
valuesx.push(newval);
keysx.push(index);
}
return newval;
}
/// Given array literal oldval of type ArrayLiteralExp or StringExp, of length
/// oldlen, change its length to newlen. If the newlen is longer than oldlen,
/// all new elements will be set to the default initializer for the element type.
Expression changeArrayLiteralLength(UnionExp* pue, const ref Loc loc, TypeArray arrayType, Expression oldval, size_t oldlen, size_t newlen)
{
Type elemType = arrayType.next;
assert(elemType);
Expression defaultElem = elemType.defaultInitLiteral(loc);
auto elements = new Expressions(newlen);
// Resolve slices
size_t indxlo = 0;
if (oldval.op == EXP.slice)
{
indxlo = cast(size_t)oldval.isSliceExp().lwr.toInteger();
oldval = oldval.isSliceExp().e1;
}
size_t copylen = oldlen < newlen ? oldlen : newlen;
if (oldval.op == EXP.string_)
{
StringExp oldse = oldval.isStringExp();
void* s = mem.xcalloc(newlen + 1, oldse.sz);
const data = oldse.peekData();
memcpy(s, data.ptr, copylen * oldse.sz);
const defaultValue = cast(uint)defaultElem.toInteger();
foreach (size_t elemi; copylen .. newlen)
{
switch (oldse.sz)
{
case 1:
(cast(char*)s)[cast(size_t)(indxlo + elemi)] = cast(char)defaultValue;
break;
case 2:
(cast(wchar*)s)[cast(size_t)(indxlo + elemi)] = cast(wchar)defaultValue;
break;
case 4:
(cast(dchar*)s)[cast(size_t)(indxlo + elemi)] = cast(dchar)defaultValue;
break;
default:
assert(0);
}
}
emplaceExp!(StringExp)(pue, loc, s[0 .. newlen * oldse.sz], newlen, oldse.sz);
StringExp se = pue.exp().isStringExp();
se.type = arrayType;
se.sz = oldse.sz;
se.committed = oldse.committed;
se.ownedByCtfe = OwnedBy.ctfe;
}
else
{
if (oldlen != 0)
{
assert(oldval.op == EXP.arrayLiteral);
ArrayLiteralExp ae = oldval.isArrayLiteralExp();
foreach (size_t i; 0 .. copylen)
(*elements)[i] = (*ae.elements)[indxlo + i];
}
if (elemType.ty == Tstruct || elemType.ty == Tsarray)
{
/* If it is an aggregate literal representing a value type,
* we need to create a unique copy for each element
*/
foreach (size_t i; copylen .. newlen)
(*elements)[i] = copyLiteral(defaultElem).copy();
}
else
{
foreach (size_t i; copylen .. newlen)
(*elements)[i] = defaultElem;
}
emplaceExp!(ArrayLiteralExp)(pue, loc, arrayType, elements);
ArrayLiteralExp aae = pue.exp().isArrayLiteralExp();
aae.ownedByCtfe = OwnedBy.ctfe;
}
return pue.exp();
}
/*************************** CTFE Sanity Checks ***************************/
bool isCtfeValueValid(Expression newval)
{
Type tb = newval.type.toBasetype();
switch (newval.op)
{
case EXP.int64:
case EXP.float64:
case EXP.char_:
case EXP.complex80:
return tb.isscalar();
case EXP.null_:
return tb.ty == Tnull ||
tb.ty == Tpointer ||
tb.ty == Tarray ||
tb.ty == Taarray ||
tb.ty == Tclass ||
tb.ty == Tdelegate;
case EXP.string_:
return true; // CTFE would directly use the StringExp in AST.
case EXP.arrayLiteral:
return true; //((ArrayLiteralExp *)newval)->ownedByCtfe;
case EXP.assocArrayLiteral:
return true; //((AssocArrayLiteralExp *)newval)->ownedByCtfe;
case EXP.structLiteral:
return true; //((StructLiteralExp *)newval)->ownedByCtfe;
case EXP.classReference:
return true;
case EXP.type:
return true;
case EXP.vector:
return true; // vector literal
case EXP.function_:
return true; // function literal or delegate literal
case EXP.delegate_:
{
// &struct.func or &clasinst.func
// &nestedfunc
Expression ethis = newval.isDelegateExp().e1;
return (ethis.op == EXP.structLiteral || ethis.op == EXP.classReference || ethis.op == EXP.variable && ethis.isVarExp().var == newval.isDelegateExp().func);
}
case EXP.symbolOffset:
{
// function pointer, or pointer to static variable
Declaration d = newval.isSymOffExp().var;
return d.isFuncDeclaration() || d.isDataseg();
}
case EXP.typeid_:
{
// always valid
return true;
}
case EXP.address:
{
// e1 should be a CTFE reference
Expression e1 = newval.isAddrExp().e1;
return tb.ty == Tpointer &&
(
(e1.op == EXP.structLiteral || e1.op == EXP.arrayLiteral) && isCtfeValueValid(e1) ||
e1.op == EXP.variable ||
e1.op == EXP.dotVariable && isCtfeReferenceValid(e1) ||
e1.op == EXP.index && isCtfeReferenceValid(e1) ||
e1.op == EXP.slice && e1.type.toBasetype().ty == Tsarray
);
}
case EXP.slice:
{
// e1 should be an array aggregate
const SliceExp se = newval.isSliceExp();
assert(se.lwr && se.lwr.op == EXP.int64);
assert(se.upr && se.upr.op == EXP.int64);
return (tb.ty == Tarray || tb.ty == Tsarray) && (se.e1.op == EXP.string_ || se.e1.op == EXP.arrayLiteral);
}
case EXP.void_:
return true; // uninitialized value
default:
newval.error("CTFE internal error: illegal CTFE value `%s`", newval.toChars());
return false;
}
}
bool isCtfeReferenceValid(Expression newval)
{
switch (newval.op)
{
case EXP.this_:
return true;
case EXP.variable:
{
const VarDeclaration v = newval.isVarExp().var.isVarDeclaration();
assert(v);
// Must not be a reference to a reference
return true;
}
case EXP.index:
{
const Expression eagg = newval.isIndexExp().e1;
return eagg.op == EXP.string_ || eagg.op == EXP.arrayLiteral || eagg.op == EXP.assocArrayLiteral;
}
case EXP.dotVariable:
{
Expression eagg = newval.isDotVarExp().e1;
return (eagg.op == EXP.structLiteral || eagg.op == EXP.classReference) && isCtfeValueValid(eagg);
}
default:
// Internally a ref variable may directly point a stack memory.
// e.g. ref int v = 1;
return isCtfeValueValid(newval);
}
}
// Used for debugging only
void showCtfeExpr(Expression e, int level = 0)
{
for (int i = level; i > 0; --i)
printf(" ");
Expressions* elements = null;
// We need the struct definition to detect block assignment
StructDeclaration sd = null;
ClassDeclaration cd = null;
if (e.op == EXP.structLiteral)
{
elements = e.isStructLiteralExp().elements;
sd = e.isStructLiteralExp().sd;
printf("STRUCT type = %s %p:\n", e.type.toChars(), e);
}
else if (e.op == EXP.classReference)
{
elements = e.isClassReferenceExp().value.elements;
cd = e.isClassReferenceExp().originalClass();
printf("CLASS type = %s %p:\n", e.type.toChars(), e.isClassReferenceExp().value);
}
else if (e.op == EXP.arrayLiteral)
{
elements = e.isArrayLiteralExp().elements;
printf("ARRAY LITERAL type=%s %p:\n", e.type.toChars(), e);
}
else if (e.op == EXP.assocArrayLiteral)
{
printf("AA LITERAL type=%s %p:\n", e.type.toChars(), e);
}
else if (e.op == EXP.string_)
{
printf("STRING %s %p\n", e.toChars(), e.isStringExp.peekString.ptr);
}
else if (e.op == EXP.slice)
{
printf("SLICE %p: %s\n", e, e.toChars());
showCtfeExpr(e.isSliceExp().e1, level + 1);
}
else if (e.op == EXP.variable)
{
printf("VAR %p %s\n", e, e.toChars());
VarDeclaration v = e.isVarExp().var.isVarDeclaration();
if (v && getValue(v))
showCtfeExpr(getValue(v), level + 1);
}
else if (e.op == EXP.address)
{
// This is potentially recursive. We mustn't try to print the thing we're pointing to.
printf("POINTER %p to %p: %s\n", e, e.isAddrExp().e1, e.toChars());
}
else
printf("VALUE %p: %s\n", e, e.toChars());
if (elements)
{
size_t fieldsSoFar = 0;
for (size_t i = 0; i < elements.dim; i++)
{
Expression z = null;
VarDeclaration v = null;
if (i > 15)
{
printf("...(total %d elements)\n", cast(int)elements.dim);
return;
}
if (sd)
{
v = sd.fields[i];
z = (*elements)[i];
}
else if (cd)
{
while (i - fieldsSoFar >= cd.fields.dim)
{
fieldsSoFar += cd.fields.dim;
cd = cd.baseClass;
for (int j = level; j > 0; --j)
printf(" ");
printf(" BASE CLASS: %s\n", cd.toChars());
}
v = cd.fields[i - fieldsSoFar];
assert((elements.dim + i) >= (fieldsSoFar + cd.fields.dim));
size_t indx = (elements.dim - fieldsSoFar) - cd.fields.dim + i;
assert(indx < elements.dim);
z = (*elements)[indx];
}
if (!z)
{
for (int j = level; j > 0; --j)
printf(" ");
printf(" void\n");
continue;
}
if (v)
{
// If it is a void assignment, use the default initializer
if ((v.type.ty != z.type.ty) && v.type.ty == Tsarray)
{
for (int j = level; --j;)
printf(" ");
printf(" field: block initialized static array\n");
continue;
}
}
showCtfeExpr(z, level + 1);
}
}
}
/*************************** Void initialization ***************************/
UnionExp voidInitLiteral(Type t, VarDeclaration var)
{
UnionExp ue;
if (t.ty == Tsarray)
{
TypeSArray tsa = cast(TypeSArray)t;
Expression elem = voidInitLiteral(tsa.next, var).copy();
// For aggregate value types (structs, static arrays) we must
// create an a separate copy for each element.
const mustCopy = (elem.op == EXP.arrayLiteral || elem.op == EXP.structLiteral);
const d = cast(size_t)tsa.dim.toInteger();
auto elements = new Expressions(d);
foreach (i; 0 .. d)
{
if (mustCopy && i > 0)
elem = copyLiteral(elem).copy();
(*elements)[i] = elem;
}
emplaceExp!(ArrayLiteralExp)(&ue, var.loc, tsa, elements);
ArrayLiteralExp ae = ue.exp().isArrayLiteralExp();
ae.ownedByCtfe = OwnedBy.ctfe;
}
else if (t.ty == Tstruct)
{
TypeStruct ts = cast(TypeStruct)t;
auto exps = new Expressions(ts.sym.fields.dim);
foreach (size_t i; 0 .. ts.sym.fields.dim)
{
(*exps)[i] = voidInitLiteral(ts.sym.fields[i].type, ts.sym.fields[i]).copy();
}
emplaceExp!(StructLiteralExp)(&ue, var.loc, ts.sym, exps);
StructLiteralExp se = ue.exp().isStructLiteralExp();
se.type = ts;
se.ownedByCtfe = OwnedBy.ctfe;
}
else
emplaceExp!(VoidInitExp)(&ue, var);
return ue;
}