blob: 6c7dc9eb1a34fc9a137f440507f0fa8606dcbea8 [file] [log] [blame]
/* Compiler implementation of the D programming language
* Copyright (C) 1999-2021 by The D Language Foundation, All Rights Reserved
* written by Walter Bright
* http://www.digitalmars.com
* Distributed under the Boost Software License, Version 1.0.
* http://www.boost.org/LICENSE_1_0.txt
* https://github.com/D-Programming-Language/dmd/blob/master/src/cond.c
*/
#include "root/dsystem.h" // strcmp()
#include "mars.h"
#include "id.h"
#include "init.h"
#include "aggregate.h"
#include "declaration.h"
#include "identifier.h"
#include "expression.h"
#include "cond.h"
#include "module.h"
#include "template.h"
#include "mtype.h"
#include "scope.h"
#include "statement.h"
#include "arraytypes.h"
#include "tokens.h"
bool evalStaticCondition(Scope *sc, Expression *exp, Expression *e, bool &errors);
int findCondition(Identifiers *ids, Identifier *ident)
{
if (ids)
{
for (size_t i = 0; i < ids->length; i++)
{
Identifier *id = (*ids)[i];
if (id == ident)
return true;
}
}
return false;
}
/* ============================================================ */
Condition::Condition(Loc loc)
{
this->loc = loc;
inc = 0;
}
/* ============================================================ */
StaticForeach::StaticForeach(Loc loc, ForeachStatement *aggrfe, ForeachRangeStatement *rangefe)
{
assert(!!aggrfe ^ !!rangefe);
this->loc = loc;
this->aggrfe = aggrfe;
this->rangefe = rangefe;
this->needExpansion = false;
}
StaticForeach *StaticForeach::syntaxCopy()
{
return new StaticForeach(
loc,
aggrfe ? (ForeachStatement *)aggrfe->syntaxCopy() : NULL,
rangefe ? (ForeachRangeStatement *)rangefe->syntaxCopy() : NULL
);
}
/*****************************************
* Turn an aggregate which is an array into an expression tuple
* of its elements. I.e., lower
* static foreach (x; [1, 2, 3, 4]) { ... }
* to
* static foreach (x; AliasSeq!(1, 2, 3, 4)) { ... }
*/
static void lowerArrayAggregate(StaticForeach *sfe, Scope *sc)
{
Expression *aggr = sfe->aggrfe->aggr;
Expression *el = new ArrayLengthExp(aggr->loc, aggr);
sc = sc->startCTFE();
el = expressionSemantic(el, sc);
sc = sc->endCTFE();
el = el->optimize(WANTvalue);
el = el->ctfeInterpret();
if (el->op == TOKint64)
{
Expressions *es;
if (ArrayLiteralExp *ale = aggr->isArrayLiteralExp())
{
// Directly use the elements of the array for the TupleExp creation
es = ale->elements;
}
else
{
size_t length = (size_t)el->toInteger();
es = new Expressions();
es->setDim(length);
for (size_t i = 0; i < length; i++)
{
IntegerExp *index = new IntegerExp(sfe->loc, i, Type::tsize_t);
Expression *value = new IndexExp(aggr->loc, aggr, index);
(*es)[i] = value;
}
}
sfe->aggrfe->aggr = new TupleExp(aggr->loc, es);
sfe->aggrfe->aggr = expressionSemantic(sfe->aggrfe->aggr, sc);
sfe->aggrfe->aggr = sfe->aggrfe->aggr->optimize(WANTvalue);
sfe->aggrfe->aggr = sfe->aggrfe->aggr->ctfeInterpret();
}
else
{
sfe->aggrfe->aggr = new ErrorExp();
}
}
/*****************************************
* Wrap a statement into a function literal and call it.
*
* Params:
* loc = The source location.
* s = The statement.
* Returns:
* AST of the expression `(){ s; }()` with location loc.
*/
static Expression *wrapAndCall(Loc loc, Statement *s)
{
TypeFunction *tf = new TypeFunction(ParameterList(), NULL, LINKdefault, 0);
FuncLiteralDeclaration *fd = new FuncLiteralDeclaration(loc, loc, tf, TOKreserved, NULL);
fd->fbody = s;
FuncExp *fe = new FuncExp(loc, fd);
Expression *ce = new CallExp(loc, fe, new Expressions());
return ce;
}
/*****************************************
* Create a `foreach` statement from `aggrefe/rangefe` with given
* `foreach` variables and body `s`.
*
* Params:
* loc = The source location.
* parameters = The foreach variables.
* s = The `foreach` body.
* Returns:
* `foreach (parameters; aggregate) s;` or
* `foreach (parameters; lower .. upper) s;`
* Where aggregate/lower, upper are as for the current StaticForeach.
*/
static Statement *createForeach(StaticForeach *sfe, Loc loc, Parameters *parameters, Statement *s)
{
if (sfe->aggrfe)
{
return new ForeachStatement(loc, sfe->aggrfe->op, parameters, sfe->aggrfe->aggr->syntaxCopy(), s, loc);
}
else
{
assert(sfe->rangefe && parameters->length == 1);
return new ForeachRangeStatement(loc, sfe->rangefe->op, (*parameters)[0],
sfe->rangefe->lwr->syntaxCopy(),
sfe->rangefe->upr->syntaxCopy(), s, loc);
}
}
/*****************************************
* For a `static foreach` with multiple loop variables, the
* aggregate is lowered to an array of tuples. As D does not have
* built-in tuples, we need a suitable tuple type. This generates
* a `struct` that serves as the tuple type. This type is only
* used during CTFE and hence its typeinfo will not go to the
* object file.
*
* Params:
* loc = The source location.
* e = The expressions we wish to store in the tuple.
* sc = The current scope.
* Returns:
* A struct type of the form
* struct Tuple
* {
* typeof(AliasSeq!(e)) tuple;
* }
*/
static TypeStruct *createTupleType(Loc loc, Expressions *e)
{ // TODO: move to druntime?
Identifier *sid = Identifier::generateId("Tuple");
StructDeclaration *sdecl = new StructDeclaration(loc, sid, false);
sdecl->storage_class |= STCstatic;
sdecl->members = new Dsymbols();
Identifier *fid = Identifier::idPool("tuple");
Type *ty = new TypeTypeof(loc, new TupleExp(loc, e));
sdecl->members->push(new VarDeclaration(loc, ty, fid, NULL));
TypeStruct *r = (TypeStruct *)sdecl->type;
if (global.params.useTypeInfo && Type::dtypeinfo)
r->vtinfo = TypeInfoStructDeclaration::create(r); // prevent typeinfo from going to object file
return r;
}
/*****************************************
* Create the AST for an instantiation of a suitable tuple type.
*
* Params:
* loc = The source location.
* type = A Tuple type, created with createTupleType.
* e = The expressions we wish to store in the tuple.
* Returns:
* An AST for the expression `Tuple(e)`.
*/
static Expression *createTuple(Loc loc, TypeStruct *type, Expressions *e)
{ // TODO: move to druntime?
return new CallExp(loc, new TypeExp(loc, type), e);
}
/*****************************************
* Lower any aggregate that is not an array to an array using a
* regular foreach loop within CTFE. If there are multiple
* `static foreach` loop variables, an array of tuples is
* generated. In thise case, the field `needExpansion` is set to
* true to indicate that the static foreach loop expansion will
* need to expand the tuples into multiple variables.
*
* For example, `static foreach (x; range) { ... }` is lowered to:
*
* static foreach (x; {
* typeof({
* foreach (x; range) return x;
* }())[] __res;
* foreach (x; range) __res ~= x;
* return __res;
* }()) { ... }
*
* Finally, call `lowerArrayAggregate` to turn the produced
* array into an expression tuple.
*
* Params:
* sc = The current scope.
*/
static void lowerNonArrayAggregate(StaticForeach *sfe, Scope *sc)
{
size_t nvars = sfe->aggrfe ? sfe->aggrfe->parameters->length : 1;
Loc aloc = sfe->aggrfe ? sfe->aggrfe->aggr->loc : sfe->rangefe->lwr->loc;
// We need three sets of foreach loop variables because the
// lowering contains three foreach loops.
Parameters *pparams[3] = {new Parameters(), new Parameters(), new Parameters()};
for (size_t i = 0; i < nvars; i++)
{
for (size_t j = 0; j < 3; j++)
{
Parameters *params = pparams[j];
Parameter *p = sfe->aggrfe ? (*sfe->aggrfe->parameters)[i] : sfe->rangefe->prm;
params->push(new Parameter(p->storageClass, p->type, p->ident, NULL, NULL));
}
}
Expression *res[2];
TypeStruct *tplty = NULL;
if (nvars == 1) // only one `static foreach` variable, generate identifiers.
{
for (size_t i = 0; i < 2; i++)
{
res[i] = new IdentifierExp(aloc, (*pparams[i])[0]->ident);
}
}
else // multiple `static foreach` variables, generate tuples.
{
for (size_t i = 0; i < 2; i++)
{
Expressions *e = new Expressions();
for (size_t j = 0; j < pparams[0]->length; j++)
{
Parameter *p = (*pparams[i])[j];
e->push(new IdentifierExp(aloc, p->ident));
}
if (!tplty)
{
tplty = createTupleType(aloc, e);
}
res[i] = createTuple(aloc, tplty, e);
}
sfe->needExpansion = true; // need to expand the tuples later
}
// generate remaining code for the new aggregate which is an
// array (see documentation comment).
if (sfe->rangefe)
{
sc = sc->startCTFE();
sfe->rangefe->lwr = expressionSemantic(sfe->rangefe->lwr, sc);
sfe->rangefe->lwr = resolveProperties(sc, sfe->rangefe->lwr);
sfe->rangefe->upr = expressionSemantic(sfe->rangefe->upr, sc);
sfe->rangefe->upr = resolveProperties(sc, sfe->rangefe->upr);
sc = sc->endCTFE();
sfe->rangefe->lwr = sfe->rangefe->lwr->optimize(WANTvalue);
sfe->rangefe->lwr = sfe->rangefe->lwr->ctfeInterpret();
sfe->rangefe->upr = sfe->rangefe->upr->optimize(WANTvalue);
sfe->rangefe->upr = sfe->rangefe->upr->ctfeInterpret();
}
Statements *s1 = new Statements();
Statements *sfebody = new Statements();
if (tplty) sfebody->push(new ExpStatement(sfe->loc, tplty->sym));
sfebody->push(new ReturnStatement(aloc, res[0]));
s1->push(createForeach(sfe, aloc, pparams[0], new CompoundStatement(aloc, sfebody)));
s1->push(new ExpStatement(aloc, new AssertExp(aloc, new IntegerExp(aloc, 0, Type::tint32))));
Type *ety = new TypeTypeof(aloc, wrapAndCall(aloc, new CompoundStatement(aloc, s1)));
Type *aty = ety->arrayOf();
Identifier *idres = Identifier::generateId("__res");
VarDeclaration *vard = new VarDeclaration(aloc, aty, idres, NULL);
Statements *s2 = new Statements();
// Run 'typeof' gagged to avoid duplicate errors and if it fails just create
// an empty foreach to expose them.
unsigned olderrors = global.startGagging();
ety = typeSemantic(ety, aloc, sc);
if (global.endGagging(olderrors))
s2->push(createForeach(sfe, aloc, pparams[1], NULL));
else
{
s2->push(new ExpStatement(aloc, vard));
Expression *catass = new CatAssignExp(aloc, new IdentifierExp(aloc, idres), res[1]);
s2->push(createForeach(sfe, aloc, pparams[1], new ExpStatement(aloc, catass)));
s2->push(new ReturnStatement(aloc, new IdentifierExp(aloc, idres)));
}
Expression *aggr;
Type *indexty;
if (sfe->rangefe && (indexty = ety)->isintegral())
{
sfe->rangefe->lwr->type = indexty;
sfe->rangefe->upr->type = indexty;
IntRange lwrRange = getIntRange(sfe->rangefe->lwr);
IntRange uprRange = getIntRange(sfe->rangefe->upr);
const dinteger_t lwr = sfe->rangefe->lwr->toInteger();
dinteger_t upr = sfe->rangefe->upr->toInteger();
size_t length = 0;
if (lwrRange.imin <= uprRange.imax)
length = (size_t)(upr - lwr);
Expressions *exps = new Expressions();
exps->setDim(length);
if (sfe->rangefe->op == TOKforeach)
{
for (size_t i = 0; i < length; i++)
(*exps)[i] = new IntegerExp(aloc, lwr + i, indexty);
}
else
{
--upr;
for (size_t i = 0; i < length; i++)
(*exps)[i] = new IntegerExp(aloc, upr - i, indexty);
}
aggr = new ArrayLiteralExp(aloc, indexty->arrayOf(), exps);
}
else
{
aggr = wrapAndCall(aloc, new CompoundStatement(aloc, s2));
sc = sc->startCTFE();
aggr = expressionSemantic(aggr, sc);
aggr = resolveProperties(sc, aggr);
sc = sc->endCTFE();
aggr = aggr->optimize(WANTvalue);
aggr = aggr->ctfeInterpret();
}
assert(!!sfe->aggrfe ^ !!sfe->rangefe);
sfe->aggrfe = new ForeachStatement(sfe->loc, TOKforeach, pparams[2], aggr,
sfe->aggrfe ? sfe->aggrfe->_body : sfe->rangefe->_body,
sfe->aggrfe ? sfe->aggrfe->endloc : sfe->rangefe->endloc);
sfe->rangefe = NULL;
lowerArrayAggregate(sfe, sc); // finally, turn generated array into expression tuple
}
/*****************************************
* Perform `static foreach` lowerings that are necessary in order
* to finally expand the `static foreach` using
* `ddmd.statementsem.makeTupleForeach`.
*/
void staticForeachPrepare(StaticForeach *sfe, Scope *sc)
{
assert(sc);
if (sfe->aggrfe)
{
sc = sc->startCTFE();
sfe->aggrfe->aggr = expressionSemantic(sfe->aggrfe->aggr, sc);
sc = sc->endCTFE();
sfe->aggrfe->aggr = sfe->aggrfe->aggr->optimize(WANTvalue);
}
if (sfe->aggrfe && sfe->aggrfe->aggr->type->toBasetype()->ty == Terror)
{
return;
}
if (!staticForeachReady(sfe))
{
if (sfe->aggrfe && sfe->aggrfe->aggr->type->toBasetype()->ty == Tarray)
{
lowerArrayAggregate(sfe, sc);
}
else
{
lowerNonArrayAggregate(sfe, sc);
}
}
}
/*****************************************
* Returns:
* `true` iff ready to call `ddmd.statementsem.makeTupleForeach`.
*/
bool staticForeachReady(StaticForeach *sfe)
{
return sfe->aggrfe && sfe->aggrfe->aggr && sfe->aggrfe->aggr->type &&
sfe->aggrfe->aggr->type->toBasetype()->ty == Ttuple;
}
/* ============================================================ */
DVCondition::DVCondition(Module *mod, unsigned level, Identifier *ident)
: Condition(Loc())
{
this->mod = mod;
this->level = level;
this->ident = ident;
}
Condition *DVCondition::syntaxCopy()
{
return this; // don't need to copy
}
/* ============================================================ */
void DebugCondition::addGlobalIdent(const char *ident)
{
if (!global.debugids)
global.debugids = new Identifiers();
global.debugids->push(Identifier::idPool(ident));
}
DebugCondition::DebugCondition(Module *mod, unsigned level, Identifier *ident)
: DVCondition(mod, level, ident)
{
}
// Helper for printing dependency information
void printDepsConditional(Scope *sc, DVCondition* condition, const char* depType)
{
if (!global.params.moduleDeps || global.params.moduleDepsFile.length)
return;
OutBuffer *ob = global.params.moduleDeps;
Module* imod = sc ? sc->instantiatingModule() : condition->mod;
if (!imod)
return;
ob->writestring(depType);
ob->writestring(imod->toPrettyChars());
ob->writestring(" (");
escapePath(ob, imod->srcfile->toChars());
ob->writestring(") : ");
if (condition->ident)
ob->printf("%s\n", condition->ident->toChars());
else
ob->printf("%d\n", condition->level);
}
int DebugCondition::include(Scope *sc)
{
//printf("DebugCondition::include() level = %d, debuglevel = %d\n", level, global.params.debuglevel);
if (inc == 0)
{
inc = 2;
bool definedInModule = false;
if (ident)
{
if (findCondition(mod->debugids, ident))
{
inc = 1;
definedInModule = true;
}
else if (findCondition(global.debugids, ident))
inc = 1;
else
{ if (!mod->debugidsNot)
mod->debugidsNot = new Identifiers();
mod->debugidsNot->push(ident);
}
}
else if (level <= global.params.debuglevel || level <= mod->debuglevel)
inc = 1;
if (!definedInModule)
printDepsConditional(sc, this, "depsDebug ");
}
return (inc == 1);
}
/* ============================================================ */
static bool isReserved(const char *ident)
{
static const char* reserved[] =
{
"DigitalMars",
"GNU",
"LDC",
"SDC",
"Windows",
"Win32",
"Win64",
"linux",
"OSX",
"FreeBSD",
"OpenBSD",
"NetBSD",
"DragonFlyBSD",
"BSD",
"Solaris",
"Posix",
"AIX",
"Haiku",
"SkyOS",
"SysV3",
"SysV4",
"Hurd",
"Android",
"PlayStation",
"PlayStation4",
"Cygwin",
"MinGW",
"FreeStanding",
"X86",
"X86_64",
"ARM",
"ARM_Thumb",
"ARM_SoftFloat",
"ARM_SoftFP",
"ARM_HardFloat",
"AArch64",
"Epiphany",
"PPC",
"PPC_SoftFloat",
"PPC_HardFloat",
"PPC64",
"IA64",
"MIPS32",
"MIPS64",
"MIPS_O32",
"MIPS_N32",
"MIPS_O64",
"MIPS_N64",
"MIPS_EABI",
"MIPS_SoftFloat",
"MIPS_HardFloat",
"MSP430",
"NVPTX",
"NVPTX64",
"RISCV32",
"RISCV64",
"SPARC",
"SPARC_V8Plus",
"SPARC_SoftFloat",
"SPARC_HardFloat",
"SPARC64",
"S390",
"S390X",
"HPPA",
"HPPA64",
"SH",
"Alpha",
"Alpha_SoftFloat",
"Alpha_HardFloat",
"LittleEndian",
"BigEndian",
"ELFv1",
"ELFv2",
"CRuntime_Digitalmars",
"CRuntime_Glibc",
"CRuntime_Microsoft",
"CRuntime_Musl",
"CRuntime_UClibc",
"CppRuntime_Clang",
"CppRuntime_DigitalMars",
"CppRuntime_Gcc",
"CppRuntime_Microsoft",
"CppRuntime_Sun",
"D_Coverage",
"D_Ddoc",
"D_InlineAsm_X86",
"D_InlineAsm_X86_64",
"D_LP64",
"D_X32",
"D_HardFloat",
"D_SoftFloat",
"D_PIC",
"D_SIMD",
"D_Version2",
"D_NoBoundsChecks",
"unittest",
"assert",
"all",
"none",
NULL
};
for (unsigned i = 0; reserved[i]; i++)
{
if (strcmp(ident, reserved[i]) == 0)
return true;
}
if (ident[0] == 'D' && ident[1] == '_')
return true;
return false;
}
void checkReserved(Loc loc, const char *ident)
{
if (isReserved(ident))
error(loc, "version identifier `%s` is reserved and cannot be set", ident);
}
void VersionCondition::addGlobalIdent(const char *ident)
{
checkReserved(Loc(), ident);
addPredefinedGlobalIdent(ident);
}
void VersionCondition::addPredefinedGlobalIdent(const char *ident)
{
if (!global.versionids)
global.versionids = new Identifiers();
global.versionids->push(Identifier::idPool(ident));
}
VersionCondition::VersionCondition(Module *mod, unsigned level, Identifier *ident)
: DVCondition(mod, level, ident)
{
}
int VersionCondition::include(Scope *sc)
{
//printf("VersionCondition::include() level = %d, versionlevel = %d\n", level, global.params.versionlevel);
//if (ident) printf("\tident = '%s'\n", ident->toChars());
if (inc == 0)
{
inc = 2;
bool definedInModule=false;
if (ident)
{
if (findCondition(mod->versionids, ident))
{
inc = 1;
definedInModule = true;
}
else if (findCondition(global.versionids, ident))
inc = 1;
else
{
if (!mod->versionidsNot)
mod->versionidsNot = new Identifiers();
mod->versionidsNot->push(ident);
}
}
else if (level <= global.params.versionlevel || level <= mod->versionlevel)
inc = 1;
if (!definedInModule && (!ident || (!isReserved(ident->toChars()) && ident != Id::_unittest && ident != Id::_assert)))
printDepsConditional(sc, this, "depsVersion ");
}
return (inc == 1);
}
/**************************** StaticIfCondition *******************************/
StaticIfCondition::StaticIfCondition(Loc loc, Expression *exp)
: Condition(loc)
{
this->exp = exp;
}
Condition *StaticIfCondition::syntaxCopy()
{
return new StaticIfCondition(loc, exp->syntaxCopy());
}
int StaticIfCondition::include(Scope *sc)
{
if (inc == 0)
{
if (!sc)
{
error(loc, "static if conditional cannot be at global scope");
inc = 2;
return 0;
}
sc = sc->push(sc->scopesym);
bool errors = false;
if (!exp)
goto Lerror;
bool result = evalStaticCondition(sc, exp, exp, errors);
sc->pop();
// Prevent repeated condition evaluation.
// See: fail_compilation/fail7815.d
if (inc != 0)
return (inc == 1);
if (errors)
goto Lerror;
if (result)
inc = 1;
else
inc = 2;
}
return (inc == 1);
Lerror:
if (!global.gag)
inc = 2; // so we don't see the error message again
return 0;
}