ldc/ddmd/arrayop.d
Rainer Schuetze 9abf85ab1a fix test failure in runnable/arrayop
manual merge of the actual fix in 66376051a4
2016-04-18 08:32:40 +02:00

655 lines
19 KiB
D

// Compiler implementation of the D programming language
// Copyright (c) 1999-2015 by Digital Mars
// 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
module ddmd.arrayop;
import ddmd.arraytypes;
import ddmd.declaration;
import ddmd.dscope;
import ddmd.dsymbol;
import ddmd.expression;
import ddmd.func;
import ddmd.globals;
import ddmd.id;
import ddmd.identifier;
import ddmd.mtype;
import ddmd.root.outbuffer;
import ddmd.statement;
import ddmd.tokens;
import ddmd.visitor;
/**************************************
* Hash table of array op functions already generated or known about.
*/
version(IN_LLVM) {} else
private __gshared FuncDeclaration[void*] arrayfuncs;
/**************************************
* Structure to contain information needed to insert an array op call
*/
extern (C++) FuncDeclaration buildArrayOp(Identifier ident, BinExp exp, Scope* sc, Loc loc)
{
auto fparams = new Parameters();
Expression loopbody = buildArrayLoop(exp, fparams);
/* Construct the function body:
* foreach (i; 0 .. p.length) for (size_t i = 0; i < p.length; i++)
* loopbody;
* return p;
*/
Parameter p = (*fparams)[0];
// foreach (i; 0 .. p.length)
Statement s1 = new ForeachRangeStatement(Loc(), TOKforeach, new Parameter(0, null, Id.p, null), new IntegerExp(Loc(), 0, Type.tsize_t), new ArrayLengthExp(Loc(), new IdentifierExp(Loc(), p.ident)), new ExpStatement(Loc(), loopbody), Loc());
//printf("%s\n", s1->toChars());
Statement s2 = new ReturnStatement(Loc(), new IdentifierExp(Loc(), p.ident));
//printf("s2: %s\n", s2->toChars());
Statement fbody = new CompoundStatement(Loc(), s1, s2);
// Built-in array ops should be @trusted, pure, nothrow and nogc
StorageClass stc = STCtrusted | STCpure | STCnothrow | STCnogc;
/* Construct the function
*/
auto ftype = new TypeFunction(fparams, exp.e1.type, 0, LINKc, stc);
//printf("fd: %s %s\n", ident->toChars(), ftype->toChars());
auto fd = new FuncDeclaration(Loc(), Loc(), ident, STCundefined, ftype);
fd.fbody = fbody;
fd.protection = Prot(PROTpublic);
fd.linkage = LINKc;
fd.isArrayOp = 1;
sc._module.importedFrom.members.push(fd);
sc = sc.push();
sc.parent = sc._module.importedFrom;
sc.stc = 0;
sc.linkage = LINKc;
fd.semantic(sc);
fd.semantic2(sc);
uint errors = global.startGagging();
fd.semantic3(sc);
if (global.endGagging(errors))
{
fd.type = Type.terror;
fd.errors = true;
fd.fbody = null;
}
sc.pop();
return fd;
}
/**********************************************
* Check that there are no uses of arrays without [].
*/
extern (C++) bool isArrayOpValid(Expression e)
{
if (e.op == TOKslice)
return true;
if (e.op == TOKarrayliteral)
{
Type t = e.type.toBasetype();
while (t.ty == Tarray || t.ty == Tsarray)
t = t.nextOf().toBasetype();
return (t.ty != Tvoid);
}
Type tb = e.type.toBasetype();
if (tb.ty == Tarray || tb.ty == Tsarray)
{
if (isUnaArrayOp(e.op))
{
return isArrayOpValid((cast(UnaExp)e).e1);
}
if (isBinArrayOp(e.op) || isBinAssignArrayOp(e.op) || e.op == TOKassign)
{
BinExp be = cast(BinExp)e;
return isArrayOpValid(be.e1) && isArrayOpValid(be.e2);
}
if (e.op == TOKconstruct)
{
BinExp be = cast(BinExp)e;
return be.e1.op == TOKslice && isArrayOpValid(be.e2);
}
if (e.op == TOKcall)
{
return false; // TODO: Decide if [] is required after arrayop calls.
}
else
{
return false;
}
}
return true;
}
extern (C++) bool isNonAssignmentArrayOp(Expression e)
{
if (e.op == TOKslice)
return isNonAssignmentArrayOp((cast(SliceExp)e).e1);
Type tb = e.type.toBasetype();
if (tb.ty == Tarray || tb.ty == Tsarray)
{
return (isUnaArrayOp(e.op) || isBinArrayOp(e.op));
}
return false;
}
extern (C++) bool checkNonAssignmentArrayOp(Expression e, bool suggestion = false)
{
if (isNonAssignmentArrayOp(e))
{
const(char)* s = "";
if (suggestion)
s = " (possible missing [])";
e.error("array operation %s without destination memory not allowed%s", e.toChars(), s);
return true;
}
return false;
}
/***********************************
* Construct the array operation expression.
*/
extern (C++) Expression arrayOp(BinExp e, Scope* sc)
{
//printf("BinExp::arrayOp() %s\n", toChars());
Type tb = e.type.toBasetype();
assert(tb.ty == Tarray || tb.ty == Tsarray);
Type tbn = tb.nextOf().toBasetype();
if (tbn.ty == Tvoid)
{
e.error("cannot perform array operations on void[] arrays");
return new ErrorExp();
}
if (!isArrayOpValid(e))
{
e.error("invalid array operation %s (possible missing [])", e.toChars());
return new ErrorExp();
}
auto arguments = new Expressions();
/* The expression to generate an array operation for is mangled
* into a name to use as the array operation function name.
* Mangle in the operands and operators in RPN order, and type.
*/
OutBuffer buf;
buf.writestring("_array");
buildArrayIdent(e, &buf, arguments);
buf.writeByte('_');
/* Append deco of array element type
*/
buf.writestring(e.type.toBasetype().nextOf().toBasetype().mutableOf().deco);
auto ident = Identifier.idPool(buf.peekSlice());
version(IN_LLVM)
{
auto arrayfuncs = sc._module.arrayfuncs;
}
FuncDeclaration* pFd = cast(void*)ident in arrayfuncs;
FuncDeclaration fd;
if (pFd)
fd = *pFd;
else
fd = buildArrayOp(ident, e, sc, e.loc);
if (fd && fd.errors)
{
const(char)* fmt;
if (tbn.ty == Tstruct || tbn.ty == Tclass)
fmt = "invalid array operation '%s' because %s doesn't support necessary arithmetic operations";
else if (!tbn.isscalar())
fmt = "invalid array operation '%s' because %s is not a scalar type";
else
fmt = "invalid array operation '%s' for element type %s";
e.error(fmt, e.toChars(), tbn.toChars());
return new ErrorExp();
}
if (!pFd)
arrayfuncs[cast(void*)ident] = fd;
Expression ev = new VarExp(e.loc, fd);
Expression ec = new CallExp(e.loc, ev, arguments);
return ec.semantic(sc);
}
extern (C++) Expression arrayOp(BinAssignExp e, Scope* sc)
{
//printf("BinAssignExp::arrayOp() %s\n", toChars());
/* Check that the elements of e1 can be assigned to
*/
Type tn = e.e1.type.toBasetype().nextOf();
if (tn && (!tn.isMutable() || !tn.isAssignable()))
{
e.error("slice %s is not mutable", e.e1.toChars());
return new ErrorExp();
}
if (e.e1.op == TOKarrayliteral)
{
return e.e1.modifiableLvalue(sc, e.e1);
}
return arrayOp(cast(BinExp)e, sc);
}
/******************************************
* Construct the identifier for the array operation function,
* and build the argument list to pass to it.
*/
extern (C++) void buildArrayIdent(Expression e, OutBuffer* buf, Expressions* arguments)
{
extern (C++) final class BuildArrayIdentVisitor : Visitor
{
alias visit = super.visit;
OutBuffer* buf;
Expressions* arguments;
public:
extern (D) this(OutBuffer* buf, Expressions* arguments)
{
this.buf = buf;
this.arguments = arguments;
}
override void visit(Expression e)
{
buf.writestring("Exp");
arguments.shift(e);
}
override void visit(CastExp e)
{
Type tb = e.type.toBasetype();
if (tb.ty == Tarray || tb.ty == Tsarray)
{
e.e1.accept(this);
}
else
visit(cast(Expression)e);
}
override void visit(ArrayLiteralExp e)
{
buf.writestring("Slice");
arguments.shift(e);
}
override void visit(SliceExp e)
{
buf.writestring("Slice");
arguments.shift(e);
}
override void visit(AssignExp e)
{
/* Evaluate assign expressions right to left
*/
e.e2.accept(this);
e.e1.accept(this);
buf.writestring("Assign");
}
override void visit(BinAssignExp e)
{
/* Evaluate assign expressions right to left
*/
e.e2.accept(this);
e.e1.accept(this);
const(char)* s;
switch (e.op)
{
case TOKaddass:
s = "Addass";
break;
case TOKminass:
s = "Subass";
break;
case TOKmulass:
s = "Mulass";
break;
case TOKdivass:
s = "Divass";
break;
case TOKmodass:
s = "Modass";
break;
case TOKxorass:
s = "Xorass";
break;
case TOKandass:
s = "Andass";
break;
case TOKorass:
s = "Orass";
break;
case TOKpowass:
s = "Powass";
break;
default:
assert(0);
}
buf.writestring(s);
}
override void visit(NegExp e)
{
e.e1.accept(this);
buf.writestring("Neg");
}
override void visit(ComExp e)
{
e.e1.accept(this);
buf.writestring("Com");
}
override void visit(BinExp e)
{
/* Evaluate assign expressions left to right
*/
const(char)* s = null;
switch (e.op)
{
case TOKadd:
s = "Add";
break;
case TOKmin:
s = "Sub";
break;
case TOKmul:
s = "Mul";
break;
case TOKdiv:
s = "Div";
break;
case TOKmod:
s = "Mod";
break;
case TOKxor:
s = "Xor";
break;
case TOKand:
s = "And";
break;
case TOKor:
s = "Or";
break;
case TOKpow:
s = "Pow";
break;
default:
break;
}
if (s)
{
Type tb = e.type.toBasetype();
Type t1 = e.e1.type.toBasetype();
Type t2 = e.e2.type.toBasetype();
e.e1.accept(this);
if (t1.ty == Tarray && (t2.ty == Tarray && !t1.equivalent(tb) || t2.ty != Tarray && !t1.nextOf().equivalent(e.e2.type)))
{
// Bugzilla 12780: if A is narrower than B
// A[] op B[]
// A[] op B
buf.writestring("Of");
buf.writestring(t1.nextOf().mutableOf().deco);
}
e.e2.accept(this);
if (t2.ty == Tarray && (t1.ty == Tarray && !t2.equivalent(tb) || t1.ty != Tarray && !t2.nextOf().equivalent(e.e1.type)))
{
// Bugzilla 12780: if B is narrower than A:
// A[] op B[]
// A op B[]
buf.writestring("Of");
buf.writestring(t2.nextOf().mutableOf().deco);
}
buf.writestring(s);
}
else
visit(cast(Expression)e);
}
}
scope BuildArrayIdentVisitor v = new BuildArrayIdentVisitor(buf, arguments);
e.accept(v);
}
/******************************************
* Construct the inner loop for the array operation function,
* and build the parameter list.
*/
extern (C++) Expression buildArrayLoop(Expression e, Parameters* fparams)
{
extern (C++) final class BuildArrayLoopVisitor : Visitor
{
alias visit = super.visit;
Parameters* fparams;
Expression result;
public:
extern (D) this(Parameters* fparams)
{
this.fparams = fparams;
}
override void visit(Expression e)
{
Identifier id = Identifier.generateId("c", fparams.dim);
auto param = new Parameter(0, e.type, id, null);
fparams.shift(param);
result = new IdentifierExp(Loc(), id);
}
override void visit(CastExp e)
{
Type tb = e.type.toBasetype();
if (tb.ty == Tarray || tb.ty == Tsarray)
{
e.e1.accept(this);
}
else
visit(cast(Expression)e);
}
override void visit(ArrayLiteralExp e)
{
Identifier id = Identifier.generateId("p", fparams.dim);
auto param = new Parameter(STCconst, e.type, id, null);
fparams.shift(param);
Expression ie = new IdentifierExp(Loc(), id);
Expression index = new IdentifierExp(Loc(), Id.p);
result = new ArrayExp(Loc(), ie, index);
}
override void visit(SliceExp e)
{
Identifier id = Identifier.generateId("p", fparams.dim);
auto param = new Parameter(STCconst, e.type, id, null);
fparams.shift(param);
Expression ie = new IdentifierExp(Loc(), id);
Expression index = new IdentifierExp(Loc(), Id.p);
result = new ArrayExp(Loc(), ie, index);
}
override void visit(AssignExp e)
{
/* Evaluate assign expressions right to left
*/
Expression ex2 = buildArrayLoop(e.e2);
/* Need the cast because:
* b = c + p[i];
* where b is a byte fails because (c + p[i]) is an int
* which cannot be implicitly cast to byte.
*/
ex2 = new CastExp(Loc(), ex2, e.e1.type.nextOf());
Expression ex1 = buildArrayLoop(e.e1);
Parameter param = (*fparams)[0];
param.storageClass = 0;
result = new AssignExp(Loc(), ex1, ex2);
}
override void visit(BinAssignExp e)
{
/* Evaluate assign expressions right to left
*/
Expression ex2 = buildArrayLoop(e.e2);
Expression ex1 = buildArrayLoop(e.e1);
Parameter param = (*fparams)[0];
param.storageClass = 0;
switch (e.op)
{
case TOKaddass:
result = new AddAssignExp(e.loc, ex1, ex2);
return;
case TOKminass:
result = new MinAssignExp(e.loc, ex1, ex2);
return;
case TOKmulass:
result = new MulAssignExp(e.loc, ex1, ex2);
return;
case TOKdivass:
result = new DivAssignExp(e.loc, ex1, ex2);
return;
case TOKmodass:
result = new ModAssignExp(e.loc, ex1, ex2);
return;
case TOKxorass:
result = new XorAssignExp(e.loc, ex1, ex2);
return;
case TOKandass:
result = new AndAssignExp(e.loc, ex1, ex2);
return;
case TOKorass:
result = new OrAssignExp(e.loc, ex1, ex2);
return;
case TOKpowass:
result = new PowAssignExp(e.loc, ex1, ex2);
return;
default:
assert(0);
}
}
override void visit(NegExp e)
{
Expression ex1 = buildArrayLoop(e.e1);
result = new NegExp(Loc(), ex1);
}
override void visit(ComExp e)
{
Expression ex1 = buildArrayLoop(e.e1);
result = new ComExp(Loc(), ex1);
}
override void visit(BinExp e)
{
if (isBinArrayOp(e.op))
{
/* Evaluate assign expressions left to right
*/
BinExp be = cast(BinExp)e.copy();
be.e1 = buildArrayLoop(be.e1);
be.e2 = buildArrayLoop(be.e2);
be.type = null;
result = be;
return;
}
else
{
visit(cast(Expression)e);
return;
}
}
Expression buildArrayLoop(Expression e)
{
e.accept(this);
return result;
}
}
scope BuildArrayLoopVisitor v = new BuildArrayLoopVisitor(fparams);
return v.buildArrayLoop(e);
}
/***********************************************
* Test if expression is a unary array op.
*/
extern (C++) bool isUnaArrayOp(TOK op)
{
switch (op)
{
case TOKneg:
case TOKtilde:
return true;
default:
break;
}
return false;
}
/***********************************************
* Test if expression is a binary array op.
*/
extern (C++) bool isBinArrayOp(TOK op)
{
switch (op)
{
case TOKadd:
case TOKmin:
case TOKmul:
case TOKdiv:
case TOKmod:
case TOKxor:
case TOKand:
case TOKor:
case TOKpow:
return true;
default:
break;
}
return false;
}
/***********************************************
* Test if expression is a binary assignment array op.
*/
extern (C++) bool isBinAssignArrayOp(TOK op)
{
switch (op)
{
case TOKaddass:
case TOKminass:
case TOKmulass:
case TOKdivass:
case TOKmodass:
case TOKxorass:
case TOKandass:
case TOKorass:
case TOKpowass:
return true;
default:
break;
}
return false;
}
/***********************************************
* Test if operand is a valid array op operand.
*/
extern (C++) bool isArrayOpOperand(Expression e)
{
//printf("Expression::isArrayOpOperand() %s\n", e->toChars());
if (e.op == TOKslice)
return true;
if (e.op == TOKarrayliteral)
{
Type t = e.type.toBasetype();
while (t.ty == Tarray || t.ty == Tsarray)
t = t.nextOf().toBasetype();
return (t.ty != Tvoid);
}
Type tb = e.type.toBasetype();
if (tb.ty == Tarray)
{
return (isUnaArrayOp(e.op) || isBinArrayOp(e.op) || isBinAssignArrayOp(e.op) || e.op == TOKassign);
}
return false;
}