aboutsummaryrefslogtreecommitdiff
path: root/gcc/d/dmd/ob.d
diff options
context:
space:
mode:
Diffstat (limited to 'gcc/d/dmd/ob.d')
-rw-r--r--gcc/d/dmd/ob.d2680
1 files changed, 2680 insertions, 0 deletions
diff --git a/gcc/d/dmd/ob.d b/gcc/d/dmd/ob.d
new file mode 100644
index 0000000..7719ccf
--- /dev/null
+++ b/gcc/d/dmd/ob.d
@@ -0,0 +1,2680 @@
+/**
+ * Flow analysis for Ownership/Borrowing
+ *
+ * Copyright: Copyright (C) 1999-2021 by The D Language Foundation, All Rights Reserved
+ * Authors: $(LINK2 http://www.digitalmars.com, Walter Bright)
+ * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
+ * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/ob.d, _ob.d)
+ * Documentation: https://dlang.org/phobos/dmd_escape.html
+ * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/ob.d
+ */
+
+module dmd.ob;
+
+import core.stdc.stdio : printf;
+import core.stdc.stdlib;
+import core.stdc.string;
+
+import dmd.root.array;
+import dmd.root.rootobject;
+import dmd.root.rmem;
+
+import dmd.aggregate;
+import dmd.apply;
+import dmd.arraytypes;
+import dmd.astenums;
+import dmd.declaration;
+import dmd.dscope;
+import dmd.dsymbol;
+import dmd.dtemplate;
+import dmd.errors;
+import dmd.escape;
+import dmd.expression;
+import dmd.foreachvar;
+import dmd.func;
+import dmd.globals;
+import dmd.identifier;
+import dmd.init;
+import dmd.mtype;
+import dmd.printast;
+import dmd.statement;
+import dmd.stmtstate;
+import dmd.tokens;
+import dmd.visitor;
+
+import dmd.root.bitarray;
+import dmd.root.outbuffer;
+
+/**********************************
+ * Perform ownership/borrowing checks for funcdecl.
+ * Does not modify the AST, just checks for errors.
+ */
+
+void oblive(FuncDeclaration funcdecl)
+{
+ //printf("oblive() %s\n", funcdecl.toChars());
+ //printf("fbody: %s\n", funcdecl.fbody.toChars());
+ ObState obstate;
+
+ /* Build the flow graph
+ */
+ setLabelStatementExtraFields(funcdecl.labtab);
+ toObNodes(obstate.nodes, funcdecl.fbody);
+ insertFinallyBlockCalls(obstate.nodes);
+ insertFinallyBlockGotos(obstate.nodes);
+ removeUnreachable(obstate.nodes);
+ computePreds(obstate.nodes);
+
+ numberNodes(obstate.nodes);
+ //foreach (ob; obstate.nodes) ob.print();
+
+ collectVars(funcdecl, obstate.vars);
+ allocStates(obstate);
+ doDataFlowAnalysis(obstate);
+
+ checkObErrors(obstate);
+}
+
+alias ObNodes = Array!(ObNode*);
+
+alias StmtState = dmd.stmtstate.StmtState!ObNode;
+
+/*******************************************
+ * Collect the state information.
+ */
+struct ObState
+{
+ ObNodes nodes;
+ VarDeclarations vars;
+
+ Array!size_t varStack; /// temporary storage
+ Array!bool mutableStack; /// parallel to varStack[], is type mutable?
+
+ PtrVarState[] varPool; /// memory pool
+
+ ~this()
+ {
+ mem.xfree(varPool.ptr);
+ }
+}
+
+/***********************************************
+ * A node in the function's expression graph, and its edges to predecessors and successors.
+ */
+struct ObNode
+{
+ Expression exp; /// expression for the node
+ ObNodes preds; /// predecessors
+ ObNodes succs; /// successors
+ ObNode* tryBlock; /// try-finally block we're inside
+ ObType obtype;
+ uint index; /// index of this in obnodes
+
+ PtrVarState[] gen; /// new states generated for this node
+ PtrVarState[] input; /// variable states on entry to exp
+ PtrVarState[] output; /// variable states on exit to exp
+
+ this(ObNode* tryBlock)
+ {
+ this.tryBlock = tryBlock;
+ }
+
+ void print()
+ {
+ printf("%d: %s %s\n", index, obtype.toString.ptr, exp ? exp.toChars() : "-");
+ printf(" preds: ");
+ foreach (ob; preds)
+ printf(" %d", ob.index);
+ printf("\n succs: ");
+ foreach (ob; succs)
+ printf(" %d", ob.index);
+ printf("\n\n");
+ }
+}
+
+
+enum ObType : ubyte
+{
+ goto_, /// goto one of the succs[]
+ return_, /// returns from function
+ retexp, /// returns expression from function
+ throw_, /// exits with throw
+ exit, /// exits program
+ try_,
+ finally_,
+ fend,
+}
+
+string toString(ObType obtype)
+{
+ return obtype == ObType.goto_ ? "goto " :
+ obtype == ObType.return_ ? "ret " :
+ obtype == ObType.retexp ? "retexp" :
+ obtype == ObType.throw_ ? "throw" :
+ obtype == ObType.exit ? "exit" :
+ obtype == ObType.try_ ? "try" :
+ obtype == ObType.finally_ ? "finally" :
+ obtype == ObType.fend ? "fend" :
+ "---";
+}
+
+/***********
+ Pointer variable states:
+
+ Initial state is not known; ignore for now
+
+ Undefined not in a usable state
+
+ T* p = void;
+
+ Owner mutable pointer
+
+ T* p = initializer;
+
+ Borrowed scope mutable pointer, borrowed from [p]
+
+ T* p = initializer;
+ scope T* b = p;
+
+ Readonly scope const pointer, copied from [p]
+
+ T* p = initializer;
+ scope const(T)* cp = p;
+
+ Examples:
+
+ T* p = initializer; // p is owner
+ T** pp = &p; // pp borrows from p
+
+ T* p = initialize; // p is owner
+ T* q = p; // transfer: q is owner, p is undefined
+ */
+
+enum PtrState : ubyte
+{
+ Initial, Undefined, Owner, Borrowed, Readonly
+}
+
+/************
+ */
+const(char)* toChars(PtrState state)
+{
+ return toString(state).ptr;
+}
+
+string toString(PtrState state)
+{
+ return ["Initial", "Undefined", "Owner", "Borrowed", "Readonly"][state];
+}
+
+/******
+ * Carries the state of a pointer variable.
+ */
+struct PtrVarState
+{
+ BitArray deps; /// dependencies
+ PtrState state; /// state the pointer variable is in
+
+ void opAssign(const ref PtrVarState pvs)
+ {
+ state = pvs.state;
+ deps = pvs.deps;
+ }
+
+ /* Combine `this` and `pvs` into `this`,
+ * on the idea that the `this` and the `pvs` paths
+ * are being merged
+ * Params:
+ * pvs = path to be merged with `this`
+ */
+ void combine(ref PtrVarState pvs, size_t vi, PtrVarState[] gen)
+ {
+ static uint X(PtrState x1, PtrState x2) { return x1 * (PtrState.max + 1) + x2; }
+
+ with (PtrState)
+ {
+ switch (X(state, pvs.state))
+ {
+ case X(Initial, Initial):
+ break;
+
+ case X(Initial, Owner ):
+ case X(Initial, Borrowed ):
+ case X(Initial, Readonly ):
+ // Transfer state to `this`
+ state = pvs.state;
+ deps = pvs.deps;
+ break;
+
+ case X(Owner, Initial):
+ case X(Borrowed, Initial):
+ case X(Readonly, Initial):
+ break;
+
+ case X(Undefined, Initial):
+ case X(Undefined, Undefined):
+ case X(Undefined, Owner ):
+ case X(Undefined, Borrowed ):
+ case X(Undefined, Readonly ):
+ break;
+
+ case X(Owner , Owner ):
+ break;
+
+ case X(Borrowed , Borrowed):
+ case X(Readonly , Readonly):
+ deps.or(pvs.deps);
+ break;
+
+ default:
+ makeUndefined(vi, gen);
+ break;
+ }
+ }
+ }
+
+ bool opEquals(const ref PtrVarState pvs) const
+ {
+ return state == pvs.state &&
+ deps == pvs.deps;
+ }
+
+ /***********************
+ */
+ void print(VarDeclaration[] vars)
+ {
+ string s = toString(state);
+ printf("%.*s [", cast(int)s.length, s.ptr);
+ assert(vars.length == deps.length);
+ OutBuffer buf;
+ depsToBuf(buf, vars);
+ auto t = buf[];
+ printf("%.*s]\n", cast(int)t.length, t.ptr);
+ }
+
+ /*****************************
+ * Produce a user-readable comma separated string of the
+ * dependencies.
+ * Params:
+ * buf = write resulting string here
+ * vars = array from which to get the variable names
+ */
+ void depsToBuf(ref OutBuffer buf, const VarDeclaration[] vars)
+ {
+ bool any = false;
+ foreach (i; 0 .. deps.length)
+ {
+ if (deps[i])
+ {
+ if (any)
+ buf.writestring(", ");
+ buf.writestring(vars[i].toString());
+ any = true;
+ }
+ }
+ }
+}
+
+
+/*****************************************
+ * Set the `.extra` field for LabelStatements in labtab[].
+ */
+void setLabelStatementExtraFields(DsymbolTable labtab)
+{
+ if (labtab)
+ foreach (keyValue; labtab.tab.asRange)
+ {
+ //printf(" KV: %s = %s\n", keyValue.key.toChars(), keyValue.value.toChars());
+ auto label = cast(LabelDsymbol)keyValue.value;
+ if (label.statement)
+ label.statement.extra = cast(void*) new ObNode(null);
+ }
+}
+
+/*****************************************
+ * Convert statement into ObNodes.
+ */
+
+void toObNodes(ref ObNodes obnodes, Statement s)
+{
+ ObNode* curblock = new ObNode(null);
+ obnodes.push(curblock);
+
+ void visit(Statement s, StmtState* stmtstate)
+ {
+ if (!s)
+ return;
+
+ ObNode* newNode()
+ {
+ return new ObNode(stmtstate.tryBlock);
+ }
+
+ ObNode* nextNodeIs(ObNode* ob)
+ {
+ obnodes.push(ob);
+ curblock = ob;
+ return ob;
+ }
+
+ ObNode* nextNode()
+ {
+ return nextNodeIs(newNode());
+ }
+
+ ObNode* gotoNextNodeIs(ObNode* ob)
+ {
+ obnodes.push(ob);
+ curblock.succs.push(ob);
+ curblock = ob;
+ return ob;
+ }
+
+ // block_goto(blx, BCgoto, null)
+ ObNode* gotoNextNode()
+ {
+ return gotoNextNodeIs(newNode());
+ }
+
+ /***
+ * Doing a goto to dest
+ */
+ ObNode* gotoDest(ObNode* dest)
+ {
+ curblock.succs.push(dest);
+ return nextNode();
+ }
+
+ void visitExp(ExpStatement s)
+ {
+ curblock.obtype = ObType.goto_;
+ curblock.exp = s.exp;
+ gotoNextNode();
+ }
+
+ void visitDtorExp(DtorExpStatement s)
+ {
+ visitExp(s);
+ }
+
+ void visitCompound(CompoundStatement s)
+ {
+ if (s.statements)
+ {
+ foreach (s2; *s.statements)
+ {
+ visit(s2, stmtstate);
+ }
+ }
+ }
+
+ void visitCompoundDeclaration(CompoundDeclarationStatement s)
+ {
+ visitCompound(s);
+ }
+
+ void visitUnrolledLoop(UnrolledLoopStatement s)
+ {
+ StmtState mystate = StmtState(stmtstate, s);
+ mystate.breakBlock = newNode();
+
+ gotoNextNode();
+
+ foreach (s2; *s.statements)
+ {
+ if (s2)
+ {
+ mystate.contBlock = newNode();
+
+ visit(s2, &mystate);
+
+ gotoNextNodeIs(mystate.contBlock);
+ }
+ }
+
+ gotoNextNodeIs(mystate.breakBlock);
+ }
+
+ void visitScope(ScopeStatement s)
+ {
+ if (s.statement)
+ {
+ StmtState mystate = StmtState(stmtstate, s);
+
+ if (mystate.prev.ident)
+ mystate.ident = mystate.prev.ident;
+
+ visit(s.statement, &mystate);
+
+ if (mystate.breakBlock)
+ gotoNextNodeIs(mystate.breakBlock);
+ }
+ }
+
+ void visitDo(DoStatement s)
+ {
+ StmtState mystate = StmtState(stmtstate, s);
+ mystate.breakBlock = newNode();
+ mystate.contBlock = newNode();
+
+ auto bpre = curblock;
+
+ auto ob = newNode();
+ obnodes.push(ob);
+ curblock.succs.push(ob);
+ curblock = ob;
+ bpre.succs.push(curblock);
+
+ mystate.contBlock.succs.push(curblock);
+ mystate.contBlock.succs.push(mystate.breakBlock);
+
+ visit(s._body, &mystate);
+
+ gotoNextNodeIs(mystate.contBlock);
+ mystate.contBlock.exp = s.condition;
+ nextNodeIs(mystate.breakBlock);
+ }
+
+ void visitFor(ForStatement s)
+ {
+ //printf("visit(ForStatement)) %u..%u\n", s.loc.linnum, s.endloc.linnum);
+ StmtState mystate = StmtState(stmtstate, s);
+ mystate.breakBlock = newNode();
+ mystate.contBlock = newNode();
+
+ visit(s._init, &mystate);
+
+ auto bcond = gotoNextNode();
+ mystate.contBlock.succs.push(bcond);
+
+ if (s.condition)
+ {
+ bcond.exp = s.condition;
+ auto ob = newNode();
+ obnodes.push(ob);
+ bcond.succs.push(ob);
+ bcond.succs.push(mystate.breakBlock);
+ curblock = ob;
+ }
+ else
+ { /* No conditional, it's a straight goto
+ */
+ bcond.exp = s.condition;
+ bcond.succs.push(nextNode());
+ }
+
+ visit(s._body, &mystate);
+ /* End of the body goes to the continue block
+ */
+ curblock.succs.push(mystate.contBlock);
+ nextNodeIs(mystate.contBlock);
+
+ if (s.increment)
+ curblock.exp = s.increment;
+
+ /* The 'break' block follows the for statement.
+ */
+ nextNodeIs(mystate.breakBlock);
+ }
+
+ void visitIf(IfStatement s)
+ {
+ StmtState mystate = StmtState(stmtstate, s);
+
+ // bexit is the block that gets control after this IfStatement is done
+ auto bexit = mystate.breakBlock ? mystate.breakBlock : newNode();
+
+ curblock.exp = s.condition;
+
+ auto bcond = curblock;
+ gotoNextNode();
+
+ visit(s.ifbody, &mystate);
+ curblock.succs.push(bexit);
+
+ if (s.elsebody)
+ {
+ bcond.succs.push(nextNode());
+
+ visit(s.elsebody, &mystate);
+
+ gotoNextNodeIs(bexit);
+ }
+ else
+ {
+ bcond.succs.push(bexit);
+ nextNodeIs(bexit);
+ }
+ }
+
+ void visitSwitch(SwitchStatement s)
+ {
+ StmtState mystate = StmtState(stmtstate, s);
+
+ mystate.switchBlock = curblock;
+
+ /* Block for where "break" goes to
+ */
+ mystate.breakBlock = newNode();
+
+ /* Block for where "default" goes to.
+ * If there is a default statement, then that is where default goes.
+ * If not, then do:
+ * default: break;
+ * by making the default block the same as the break block.
+ */
+ mystate.defaultBlock = s.sdefault ? newNode() : mystate.breakBlock;
+
+ const numcases = s.cases ? s.cases.dim : 0;
+
+ /* allocate a block for each case
+ */
+ if (numcases)
+ foreach (cs; *s.cases)
+ {
+ cs.extra = cast(void*)newNode();
+ }
+
+ curblock.exp = s.condition;
+
+ if (s.hasVars)
+ { /* Generate a sequence of if-then-else blocks for the cases.
+ */
+ if (numcases)
+ foreach (cs; *s.cases)
+ {
+ auto ecase = newNode();
+ obnodes.push(ecase);
+ ecase.exp = cs.exp;
+ curblock.succs.push(ecase);
+
+ auto cn = cast(ObNode*)cs.extra;
+ ecase.succs.push(cn);
+ ecase.succs.push(nextNode());
+ }
+
+ /* The final 'else' clause goes to the default
+ */
+ curblock.succs.push(mystate.defaultBlock);
+ nextNode();
+
+ visit(s._body, &mystate);
+
+ /* Have the end of the switch body fall through to the block
+ * following the switch statement.
+ */
+ gotoNextNodeIs(mystate.breakBlock);
+ return;
+ }
+
+ auto ob = newNode();
+ obnodes.push(ob);
+ curblock = ob;
+
+ mystate.switchBlock.succs.push(mystate.defaultBlock);
+
+ visit(s._body, &mystate);
+
+ /* Have the end of the switch body fall through to the block
+ * following the switch statement.
+ */
+ gotoNextNodeIs(mystate.breakBlock);
+ }
+
+ void visitCase(CaseStatement s)
+ {
+ auto cb = cast(ObNode*)s.extra;
+ cb.tryBlock = stmtstate.tryBlock;
+ auto bsw = stmtstate.getSwitchBlock();
+ bsw.succs.push(cb);
+ gotoNextNodeIs(cb);
+
+ visit(s.statement, stmtstate);
+ }
+
+ void visitDefault(DefaultStatement s)
+ {
+ auto bdefault = stmtstate.getDefaultBlock;
+ bdefault.tryBlock = stmtstate.tryBlock;
+ gotoNextNodeIs(bdefault);
+ visit(s.statement, stmtstate);
+ }
+
+ void visitGotoDefault(GotoDefaultStatement s)
+ {
+ gotoDest(stmtstate.getDefaultBlock);
+ }
+
+ void visitGotoCase(GotoCaseStatement s)
+ {
+ gotoDest(cast(ObNode*)s.cs.extra);
+ }
+
+ void visitSwitchError(SwitchErrorStatement s)
+ {
+ curblock.obtype = ObType.throw_;
+ curblock.exp = s.exp;
+
+ nextNode();
+ }
+
+ void visitReturn(ReturnStatement s)
+ {
+ //printf("visitReturn() %s\n", s.toChars());
+ curblock.obtype = s.exp && s.exp.type.toBasetype().ty != Tvoid
+ ? ObType.retexp
+ : ObType.return_;
+ curblock.exp = s.exp;
+
+ nextNode();
+ }
+
+ void visitBreak(BreakStatement s)
+ {
+ gotoDest(stmtstate.getBreakBlock(s.ident));
+ }
+
+ void visitContinue(ContinueStatement s)
+ {
+ gotoDest(stmtstate.getContBlock(s.ident));
+ }
+
+ void visitWith(WithStatement s)
+ {
+ visit(s._body, stmtstate);
+ }
+
+ void visitTryCatch(TryCatchStatement s)
+ {
+ /* tryblock
+ * body
+ * breakBlock
+ * catches
+ * breakBlock2
+ */
+
+ StmtState mystate = StmtState(stmtstate, s);
+ mystate.breakBlock = newNode();
+
+ auto tryblock = gotoNextNode();
+
+ visit(s._body, &mystate);
+
+ gotoNextNodeIs(mystate.breakBlock);
+
+ // create new break block that follows all the catches
+ auto breakBlock2 = newNode();
+
+ gotoDest(breakBlock2);
+
+ foreach (cs; *s.catches)
+ {
+ /* Each catch block is a successor to tryblock
+ * and the last block of try body
+ */
+ StmtState catchState = StmtState(stmtstate, s);
+
+ auto bcatch = curblock;
+ tryblock.succs.push(bcatch);
+ mystate.breakBlock.succs.push(bcatch);
+
+ nextNode();
+
+ visit(cs.handler, &catchState);
+
+ gotoDest(breakBlock2);
+ }
+
+ curblock.succs.push(breakBlock2);
+ obnodes.push(breakBlock2);
+ curblock = breakBlock2;
+ }
+
+ void visitTryFinally(TryFinallyStatement s)
+ {
+ /* Build this:
+ * 1 goto [2]
+ * 2 try_ [3] [5] [7]
+ * 3 body
+ * 4 goto [8]
+ * 5 finally_ [6]
+ * 6 finalbody
+ * 7 fend [8]
+ * 8 lastblock
+ */
+
+ StmtState bodyState = StmtState(stmtstate, s);
+
+ auto b2 = gotoNextNode();
+ b2.obtype = ObType.try_;
+ bodyState.tryBlock = b2;
+
+ gotoNextNode();
+
+ visit(s._body, &bodyState);
+
+ auto b4 = gotoNextNode();
+
+ auto b5 = newNode();
+ b5.obtype = ObType.finally_;
+ nextNodeIs(b5);
+ gotoNextNode();
+
+ StmtState finallyState = StmtState(stmtstate, s);
+ visit(s.finalbody, &finallyState);
+
+ auto b7 = gotoNextNode();
+ b7.obtype = ObType.fend;
+
+ auto b8 = gotoNextNode();
+
+ b2.succs.push(b5);
+ b2.succs.push(b7);
+
+ b4.succs.push(b8);
+ }
+
+ void visitThrow(ThrowStatement s)
+ {
+ curblock.obtype = ObType.throw_;
+ curblock.exp = s.exp;
+ nextNode();
+ }
+
+ void visitGoto(GotoStatement s)
+ {
+ gotoDest(cast(ObNode*)s.label.statement.extra);
+ }
+
+ void visitLabel(LabelStatement s)
+ {
+ StmtState mystate = StmtState(stmtstate, s);
+ mystate.ident = s.ident;
+
+ auto ob = cast(ObNode*)s.extra;
+ ob.tryBlock = mystate.tryBlock;
+ visit(s.statement, &mystate);
+ }
+
+ final switch (s.stmt)
+ {
+ case STMT.Exp: visitExp(s.isExpStatement()); break;
+ case STMT.DtorExp: visitDtorExp(s.isDtorExpStatement()); break;
+ case STMT.Compound: visitCompound(s.isCompoundStatement()); break;
+ case STMT.CompoundDeclaration: visitCompoundDeclaration(s.isCompoundDeclarationStatement()); break;
+ case STMT.UnrolledLoop: visitUnrolledLoop(s.isUnrolledLoopStatement()); break;
+ case STMT.Scope: visitScope(s.isScopeStatement()); break;
+ case STMT.Do: visitDo(s.isDoStatement()); break;
+ case STMT.For: visitFor(s.isForStatement()); break;
+ case STMT.If: visitIf(s.isIfStatement()); break;
+ case STMT.Switch: visitSwitch(s.isSwitchStatement()); break;
+ case STMT.Case: visitCase(s.isCaseStatement()); break;
+ case STMT.Default: visitDefault(s.isDefaultStatement()); break;
+ case STMT.GotoDefault: visitGotoDefault(s.isGotoDefaultStatement()); break;
+ case STMT.GotoCase: visitGotoCase(s.isGotoCaseStatement()); break;
+ case STMT.SwitchError: visitSwitchError(s.isSwitchErrorStatement()); break;
+ case STMT.Return: visitReturn(s.isReturnStatement()); break;
+ case STMT.Break: visitBreak(s.isBreakStatement()); break;
+ case STMT.Continue: visitContinue(s.isContinueStatement()); break;
+ case STMT.With: visitWith(s.isWithStatement()); break;
+ case STMT.TryCatch: visitTryCatch(s.isTryCatchStatement()); break;
+ case STMT.TryFinally: visitTryFinally(s.isTryFinallyStatement()); break;
+ case STMT.Throw: visitThrow(s.isThrowStatement()); break;
+ case STMT.Goto: visitGoto(s.isGotoStatement()); break;
+ case STMT.Label: visitLabel(s.isLabelStatement()); break;
+
+ case STMT.CompoundAsm:
+ case STMT.Asm:
+ case STMT.InlineAsm:
+ case STMT.GccAsm:
+
+ case STMT.Pragma:
+ case STMT.Import:
+ case STMT.ScopeGuard:
+ case STMT.Error:
+ break; // ignore these
+
+ case STMT.Foreach:
+ case STMT.ForeachRange:
+ case STMT.Debug:
+ case STMT.CaseRange:
+ case STMT.StaticForeach:
+ case STMT.StaticAssert:
+ case STMT.Conditional:
+ case STMT.While:
+ case STMT.Forwarding:
+ case STMT.Compile:
+ case STMT.Peel:
+ case STMT.Synchronized:
+ debug printf("s: %s\n", s.toChars());
+ assert(0); // should have been rewritten
+ }
+ }
+
+ StmtState stmtstate;
+ visit(s, &stmtstate);
+ curblock.obtype = ObType.return_;
+
+ static if (0)
+ {
+ printf("toObNodes()\n");
+ printf("------- before ----------\n");
+ numberNodes(obnodes);
+ foreach (ob; obnodes) ob.print();
+ printf("-------------------------\n");
+ }
+
+ assert(stmtstate.breakBlock is null);
+ assert(stmtstate.contBlock is null);
+ assert(stmtstate.switchBlock is null);
+ assert(stmtstate.defaultBlock is null);
+ assert(stmtstate.tryBlock is null);
+}
+
+/***************************************************
+ * Insert finally block calls when doing a goto from
+ * inside a try block to outside.
+ * Done after blocks are generated because then we know all
+ * the edges of the graph, but before the pred's are computed.
+ * Params:
+ * obnodes = graph of the function
+ */
+
+void insertFinallyBlockCalls(ref ObNodes obnodes)
+{
+ ObNode* bcret = null;
+ ObNode* bcretexp = null;
+
+ enum log = false;
+
+ static if (log)
+ {
+ printf("insertFinallyBlockCalls()\n");
+ printf("------- before ----------\n");
+ numberNodes(obnodes);
+ foreach (ob; obnodes) ob.print();
+ printf("-------------------------\n");
+ }
+
+ foreach (ob; obnodes)
+ {
+ if (!ob.tryBlock)
+ continue;
+
+ switch (ob.obtype)
+ {
+ case ObType.return_:
+ // Rewrite into a ObType.goto_ => ObType.return_
+ if (!bcret)
+ {
+ bcret = new ObNode();
+ bcret.obtype = ob.obtype;
+ }
+ ob.obtype = ObType.goto_;
+ ob.succs.push(bcret);
+ goto case_goto;
+
+ case ObType.retexp:
+ // Rewrite into a ObType.goto_ => ObType.retexp
+ if (!bcretexp)
+ {
+ bcretexp = new ObNode();
+ bcretexp.obtype = ob.obtype;
+ }
+ ob.obtype = ObType.goto_;
+ ob.succs.push(bcretexp);
+ goto case_goto;
+
+ case ObType.goto_:
+ if (ob.succs.length != 1)
+ break;
+
+ case_goto:
+ {
+ auto target = ob.succs[0]; // destination of goto
+ ob.succs.setDim(0);
+ auto lasttry = target.tryBlock;
+ auto blast = ob;
+ for (auto bt = ob.tryBlock; bt != lasttry; bt = bt.tryBlock)
+ {
+ assert(bt.obtype == ObType.try_);
+ auto bf = bt.succs[1];
+ assert(bf.obtype == ObType.finally_);
+ auto bfend = bt.succs[2];
+ assert(bfend.obtype == ObType.fend);
+
+ if (!blast.succs.contains(bf.succs[0]))
+ blast.succs.push(bf.succs[0]);
+
+ blast = bfend;
+ }
+ if (!blast.succs.contains(target))
+ blast.succs.push(target);
+
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+ if (bcret)
+ obnodes.push(bcret);
+ if (bcretexp)
+ obnodes.push(bcretexp);
+
+ static if (log)
+ {
+ printf("------- after ----------\n");
+ numberNodes(obnodes);
+ foreach (ob; obnodes) ob.print();
+ printf("-------------------------\n");
+ }
+}
+
+/***************************************************
+ * Remove try-finally scaffolding.
+ * Params:
+ * obnodes = nodes for the function
+ */
+
+void insertFinallyBlockGotos(ref ObNodes obnodes)
+{
+ /* Remove all the try_, finally_, lpad and ret nodes.
+ * Actually, just make them into no-ops.
+ */
+ foreach (ob; obnodes)
+ {
+ ob.tryBlock = null;
+ switch (ob.obtype)
+ {
+ case ObType.try_:
+ ob.obtype = ObType.goto_;
+ ob.succs.remove(2); // remove fend
+ ob.succs.remove(1); // remove finally_
+ break;
+
+ case ObType.finally_:
+ ob.obtype = ObType.goto_;
+ break;
+
+ case ObType.fend:
+ ob.obtype = ObType.goto_;
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+/*********************************
+ * Set the `index` field of each ObNode
+ * to its index in the `obnodes[]` array.
+ */
+void numberNodes(ref ObNodes obnodes)
+{
+ //printf("numberNodes()\n");
+ foreach (i, ob; obnodes)
+ {
+ //printf("ob = %d, %p\n", i, ob);
+ ob.index = cast(uint)i;
+ }
+
+ // Verify that nodes do not appear more than once in obnodes[]
+ debug
+ foreach (i, ob; obnodes)
+ {
+ assert(ob.index == cast(uint)i);
+ }
+}
+
+
+/*********************************
+ * Remove unreachable nodes and compress
+ * them out of obnodes[].
+ * Params:
+ * obnodes = array of nodes
+ */
+void removeUnreachable(ref ObNodes obnodes)
+{
+ if (!obnodes.length)
+ return;
+
+ /* Mark all nodes as unreachable,
+ * temporarilly reusing ObNode.index
+ */
+ foreach (ob; obnodes)
+ ob.index = 0;
+
+ /* Recursively mark ob and all its successors as reachable
+ */
+ static void mark(ObNode* ob)
+ {
+ ob.index = 1;
+ foreach (succ; ob.succs)
+ {
+ if (!succ.index)
+ mark(succ);
+ }
+ }
+
+ mark(obnodes[0]); // first node is entry point
+
+ /* Remove unreachable nodes by shifting the remainder left
+ */
+ size_t j = 1;
+ foreach (i; 1 .. obnodes.length)
+ {
+ if (obnodes[i].index)
+ {
+ if (i != j)
+ obnodes[j] = obnodes[i];
+ ++j;
+ }
+ else
+ {
+ obnodes[i].destroy();
+ }
+ }
+ obnodes.setDim(j);
+}
+
+
+
+/*************************************
+ * Compute predecessors.
+ */
+void computePreds(ref ObNodes obnodes)
+{
+ foreach (ob; obnodes)
+ {
+ foreach (succ; ob.succs)
+ {
+ succ.preds.push(ob);
+ }
+ }
+}
+
+/*******************************
+ * Are we interested in tracking variable `v`?
+ */
+bool isTrackableVar(VarDeclaration v)
+{
+ //printf("isTrackableVar() %s\n", v.toChars());
+ auto tb = v.type.toBasetype();
+
+ /* Assume class references are managed by the GC,
+ * don't need to track them
+ */
+ if (tb.ty == Tclass)
+ return false;
+
+ /* Assume types with a destructor are doing their own tracking,
+ * such as being a ref counted type
+ */
+ if (v.needsScopeDtor())
+ return false;
+
+ /* Not tracking function parameters that are not mutable
+ */
+ if (v.storage_class & STC.parameter && !tb.hasPointersToMutableFields())
+ return false;
+
+ /* Not tracking global variables
+ */
+ return !v.isDataseg();
+}
+
+/*******************************
+ * Are we interested in tracking this expression?
+ * Returns:
+ * variable if so, null if not
+ */
+VarDeclaration isTrackableVarExp(Expression e)
+{
+ if (auto ve = e.isVarExp())
+ {
+ if (auto v = ve.var.isVarDeclaration())
+ if (isTrackableVar(v))
+ return v;
+ }
+ return null;
+}
+
+
+/**************
+ * Find the pointer variable declarations in this function,
+ * and fill `vars` with them.
+ * Params:
+ * funcdecl = function we are in
+ * vars = array to fill in
+ */
+void collectVars(FuncDeclaration funcdecl, out VarDeclarations vars)
+{
+ enum log = false;
+ if (log)
+ printf("----------------collectVars()---------------\n");
+
+ if (funcdecl.parameters)
+ foreach (v; (*funcdecl.parameters)[])
+ {
+ if (isTrackableVar(v))
+ vars.push(v);
+ }
+
+ void dgVar(VarDeclaration v)
+ {
+ if (isTrackableVar(v))
+ vars.push(v);
+ }
+
+ void dgExp(Expression e)
+ {
+ foreachVar(e, &dgVar);
+ }
+
+ foreachExpAndVar(funcdecl.fbody, &dgExp, &dgVar);
+
+ static if (log)
+ {
+ foreach (i, v; vars[])
+ {
+ printf("vars[%d] = %s\n", cast(int)i, v.toChars());
+ }
+ }
+}
+
+/***********************************
+ * Allocate BitArrays in PtrVarState.
+ * Can be allocated much more efficiently by subdividing a single
+ * large array of bits
+ */
+void allocDeps(PtrVarState[] pvss)
+{
+ //printf("allocDeps()\n");
+ foreach (ref pvs; pvss)
+ {
+ pvs.deps.length = pvss.length;
+ }
+}
+
+
+/**************************************
+ * Allocate state variables foreach node.
+ */
+void allocStates(ref ObState obstate)
+{
+ //printf("---------------allocStates()------------------\n");
+ const vlen = obstate.vars.length;
+ PtrVarState* p = cast(PtrVarState*) mem.xcalloc(obstate.nodes.length * 3 * vlen, PtrVarState.sizeof);
+ obstate.varPool = p[0 .. obstate.nodes.length * 3 * vlen];
+ foreach (i, ob; obstate.nodes)
+ {
+ //printf(" [%d]\n", cast(int)i);
+// ob.kill.length = obstate.vars.length;
+// ob.comb.length = obstate.vars.length;
+ ob.gen = p[0 .. vlen]; p += vlen;
+ ob.input = p[0 .. vlen]; p += vlen;
+ ob.output = p[0 .. vlen]; p += vlen;
+
+ allocDeps(ob.gen);
+ allocDeps(ob.input);
+ allocDeps(ob.output);
+ }
+}
+
+/******************************
+ * Does v meet the definiton of a `Borrowed` pointer?
+ * Returns:
+ * true if it does
+ */
+bool isBorrowedPtr(VarDeclaration v)
+{
+ return v.isScope() && !v.isowner && v.type.nextOf().isMutable();
+}
+
+/******************************
+ * Does v meet the definiton of a `Readonly` pointer?
+ * Returns:
+ * true if it does
+ */
+bool isReadonlyPtr(VarDeclaration v)
+{
+ return v.isScope() && !v.type.nextOf().isMutable();
+}
+
+/***************************************
+ * Compute the gen vector for ob.
+ */
+void genKill(ref ObState obstate, ObNode* ob)
+{
+ enum log = false;
+ if (log)
+ printf("-----------computeGenKill()-----------\n");
+
+ /***************
+ * Assigning result of expression `e` to variable `v`.
+ */
+ void dgWriteVar(ObNode* ob, VarDeclaration v, Expression e, bool initializer)
+ {
+ if (log)
+ printf("dgWriteVar() %s := %s %d\n", v.toChars(), e.toChars(), initializer);
+ const vi = obstate.vars.find(v);
+ assert(vi != size_t.max);
+ PtrVarState* pvs = &ob.gen[vi];
+ readVar(ob, vi, true, ob.gen);
+ if (e)
+ {
+ if (isBorrowedPtr(v))
+ pvs.state = PtrState.Borrowed;
+ else if (isReadonlyPtr(v))
+ pvs.state = PtrState.Readonly;
+ else
+ pvs.state = PtrState.Owner;
+ pvs.deps.zero();
+
+ EscapeByResults er;
+ escapeByValue(e, &er, true);
+ bool any = false; // if any variables are assigned to v
+
+ void by(VarDeclaration r)
+ {
+ const ri = obstate.vars.find(r);
+ if (ri != size_t.max && ri != vi)
+ {
+ pvs.deps[ri] = true; // v took from r
+ auto pvsr = &ob.gen[ri];
+ any = true;
+
+ if (isBorrowedPtr(v))
+ {
+ // v is borrowing from r
+ pvs.state = PtrState.Borrowed;
+ }
+ else if (isReadonlyPtr(v))
+ {
+ pvs.state = PtrState.Readonly;
+ }
+ else
+ {
+ // move r to v, which "consumes" r
+ pvsr.state = PtrState.Undefined;
+ pvsr.deps.zero();
+ }
+ }
+ }
+
+ foreach (VarDeclaration v2; er.byvalue)
+ by(v2);
+ foreach (VarDeclaration v2; er.byref)
+ by(v2);
+
+ /* Make v an Owner for initializations like:
+ * scope v = malloc();
+ */
+ if (initializer && !any && isBorrowedPtr(v))
+ {
+ v.isowner = true;
+ pvs.state = PtrState.Owner;
+ }
+ }
+ else
+ {
+ if (isBorrowedPtr(v))
+ pvs.state = PtrState.Borrowed;
+ else if (isReadonlyPtr(v))
+ pvs.state = PtrState.Readonly;
+ else
+ pvs.state = PtrState.Owner;
+ pvs.deps.zero();
+ }
+ }
+
+ void dgReadVar(const ref Loc loc, ObNode* ob, VarDeclaration v, bool mutable)
+ {
+ if (log)
+ printf("dgReadVar() %s %d\n", v.toChars(), mutable);
+ const vi = obstate.vars.find(v);
+ assert(vi != size_t.max);
+ readVar(ob, vi, mutable, ob.gen);
+ }
+
+ void foreachExp(ObNode* ob, Expression e)
+ {
+ extern (C++) final class ExpWalker : Visitor
+ {
+ alias visit = typeof(super).visit;
+ extern (D) void delegate(ObNode*, VarDeclaration, Expression, bool) dgWriteVar;
+ extern (D) void delegate(const ref Loc loc, ObNode* ob, VarDeclaration v, bool mutable) dgReadVar;
+ ObNode* ob;
+ ObState* obstate;
+
+ extern (D) this(void delegate(ObNode*, VarDeclaration, Expression, bool) dgWriteVar,
+ void delegate(const ref Loc loc, ObNode* ob, VarDeclaration v, bool mutable) dgReadVar,
+ ObNode* ob, ref ObState obstate)
+ {
+ this.dgWriteVar = dgWriteVar;
+ this.dgReadVar = dgReadVar;
+ this.ob = ob;
+ this.obstate = &obstate;
+ }
+
+ override void visit(Expression e)
+ {
+ //printf("[%s] %s: %s\n", e.loc.toChars(), Token.toChars(e.op), e.toChars());
+ //assert(0);
+ }
+
+ void visitAssign(AssignExp ae, bool initializer)
+ {
+ ae.e2.accept(this);
+ if (auto ve = ae.e1.isVarExp())
+ {
+ if (auto v = ve.var.isVarDeclaration())
+ if (isTrackableVar(v))
+ dgWriteVar(ob, v, ae.e2, initializer);
+ }
+ else
+ ae.e1.accept(this);
+ }
+
+ override void visit(AssignExp ae)
+ {
+ visitAssign(ae, false);
+ }
+
+ override void visit(DeclarationExp e)
+ {
+ void Dsymbol_visit(Dsymbol s)
+ {
+ if (auto vd = s.isVarDeclaration())
+ {
+ s = s.toAlias();
+ if (s != vd)
+ return Dsymbol_visit(s);
+ if (!isTrackableVar(vd))
+ return;
+
+ if (!(vd._init && vd._init.isVoidInitializer()))
+ {
+ auto ei = vd._init ? vd._init.isExpInitializer() : null;
+ if (ei)
+ visitAssign(cast(AssignExp)ei.exp, true);
+ else
+ dgWriteVar(ob, vd, null, false);
+ }
+ }
+ else if (auto td = s.isTupleDeclaration())
+ {
+ foreach (o; *td.objects)
+ {
+ if (auto eo = o.isExpression())
+ {
+ if (auto se = eo.isDsymbolExp())
+ {
+ Dsymbol_visit(se.s);
+ }
+ }
+ }
+ }
+ }
+
+ Dsymbol_visit(e.declaration);
+ }
+
+ override void visit(VarExp ve)
+ {
+ //printf("VarExp: %s\n", ve.toChars());
+ if (auto v = ve.var.isVarDeclaration())
+ if (isTrackableVar(v))
+ {
+ dgReadVar(ve.loc, ob, v, isMutableRef(ve.type));
+ }
+ }
+
+ override void visit(CallExp ce)
+ {
+ //printf("CallExp() %s\n", ce.toChars());
+ ce.e1.accept(this);
+ auto t = ce.e1.type.toBasetype();
+ auto tf = t.isTypeFunction();
+ if (!tf)
+ {
+ assert(t.ty == Tdelegate);
+ tf = t.nextOf().isTypeFunction();
+ assert(tf);
+ }
+
+ // j=1 if _arguments[] is first argument
+ const int j = tf.isDstyleVariadic();
+ bool hasOut;
+ const varStackSave = obstate.varStack.length;
+
+ foreach (const i, arg; (*ce.arguments)[])
+ {
+ if (i - j < tf.parameterList.length &&
+ i >= j)
+ {
+ Parameter p = tf.parameterList[i - j];
+ auto pt = p.type.toBasetype();
+
+ EscapeByResults er;
+ escapeByValue(arg, &er, true);
+
+ if (!(p.storageClass & STC.out_ && arg.isVarExp()))
+ arg.accept(this);
+
+ void by(VarDeclaration v)
+ {
+ if (!isTrackableVar(v))
+ return;
+
+ const vi = obstate.vars.find(v);
+ if (vi == size_t.max)
+ return;
+
+ if (p.storageClass & STC.out_)
+ {
+ /// initialize
+ hasOut = true;
+ makeUndefined(vi, ob.gen);
+ }
+ else if (p.storageClass & STC.scope_)
+ {
+ // borrow
+ obstate.varStack.push(vi);
+ obstate.mutableStack.push(isMutableRef(pt));
+ }
+ else
+ {
+ // move (i.e. consume arg)
+ makeUndefined(vi, ob.gen);
+ }
+ }
+
+ foreach (VarDeclaration v2; er.byvalue)
+ by(v2);
+ foreach (VarDeclaration v2; er.byref)
+ by(v2);
+ }
+ else // variadic args
+ {
+ arg.accept(this);
+
+ EscapeByResults er;
+ escapeByValue(arg, &er, true);
+
+ void byv(VarDeclaration v)
+ {
+ if (!isTrackableVar(v))
+ return;
+
+ const vi = obstate.vars.find(v);
+ if (vi == size_t.max)
+ return;
+
+ if (tf.parameterList.stc & STC.scope_)
+ {
+ // borrow
+ obstate.varStack.push(vi);
+ obstate.mutableStack.push(isMutableRef(arg.type) &&
+ !(tf.parameterList.stc & (STC.const_ | STC.immutable_)));
+ }
+ else
+ // move (i.e. consume arg)
+ makeUndefined(vi, ob.gen);
+ }
+
+ foreach (VarDeclaration v2; er.byvalue)
+ byv(v2);
+ foreach (VarDeclaration v2; er.byref)
+ byv(v2);
+ }
+ }
+
+ /* Do a dummy 'read' of each variable passed to the function,
+ * to detect O/B errors
+ */
+ assert(obstate.varStack.length == obstate.mutableStack.length);
+ foreach (i; varStackSave .. obstate.varStack.length)
+ {
+ const vi = obstate.varStack[i];
+ // auto pvs = &ob.gen[vi];
+ auto v = obstate.vars[vi];
+ //if (pvs.state == PtrState.Undefined)
+ //v.error(ce.loc, "is Undefined, cannot pass to function");
+
+ dgReadVar(ce.loc, ob, v, obstate.mutableStack[i]);
+ }
+
+ /* Pop off stack all variables for this function call
+ */
+ obstate.varStack.setDim(varStackSave);
+ obstate.mutableStack.setDim(varStackSave);
+
+ if (hasOut)
+ // Initialization of out's only happens after the function call
+ foreach (const i, arg; (*ce.arguments)[])
+ {
+ if (i - j < tf.parameterList.length &&
+ i >= j)
+ {
+ Parameter p = tf.parameterList[i - j];
+ if (p.storageClass & STC.out_)
+ {
+ if (auto v = isTrackableVarExp(arg))
+ dgWriteVar(ob, v, null, true);
+ }
+ }
+ }
+ }
+
+ override void visit(SymOffExp e)
+ {
+ if (auto v = e.var.isVarDeclaration())
+ if (isTrackableVar(v))
+ {
+ dgReadVar(e.loc, ob, v, isMutableRef(e.type));
+ }
+ }
+
+ override void visit(LogicalExp e)
+ {
+ e.e1.accept(this);
+
+ const vlen = obstate.vars.length;
+ auto p = cast(PtrVarState*)mem.xcalloc(vlen, PtrVarState.sizeof);
+ PtrVarState[] gen1 = p[0 .. vlen];
+ foreach (i, ref pvs; gen1)
+ {
+ pvs = ob.gen[i];
+ }
+
+ e.e2.accept(this);
+
+ // Merge gen1 into ob.gen
+ foreach (i; 0 .. vlen)
+ {
+ ob.gen[i].combine(gen1[i], i, ob.gen);
+ }
+
+ mem.xfree(p); // should free .deps too
+ }
+
+ override void visit(CondExp e)
+ {
+ e.econd.accept(this);
+
+ const vlen = obstate.vars.length;
+ auto p = cast(PtrVarState*)mem.xcalloc(vlen, PtrVarState.sizeof);
+ PtrVarState[] gen1 = p[0 .. vlen];
+ foreach (i, ref pvs; gen1)
+ {
+ pvs = ob.gen[i];
+ }
+
+ e.e1.accept(this);
+
+ // Swap gen1 with ob.gen
+ foreach (i; 0 .. vlen)
+ {
+ gen1[i].deps.swap(ob.gen[i].deps);
+ const state = gen1[i].state;
+ gen1[i].state = ob.gen[i].state;
+ ob.gen[i].state = state;
+ }
+
+ e.e2.accept(this);
+
+ /* xxx1 is the state from expression e1, ob.xxx is the state from e2.
+ * Merge xxx1 into ob.xxx to get the state from `e`.
+ */
+ foreach (i; 0 .. vlen)
+ {
+ ob.gen[i].combine(gen1[i], i, ob.gen);
+ }
+
+ mem.xfree(p); // should free .deps too
+ }
+
+ override void visit(AddrExp e)
+ {
+ /* Taking the address of struct literal is normally not
+ * allowed, but CTFE can generate one out of a new expression,
+ * but it'll be placed in static data so no need to check it.
+ */
+ if (e.e1.op != TOK.structLiteral)
+ e.e1.accept(this);
+ }
+
+ override void visit(UnaExp e)
+ {
+ e.e1.accept(this);
+ }
+
+ override void visit(BinExp e)
+ {
+ e.e1.accept(this);
+ e.e2.accept(this);
+ }
+
+ override void visit(ArrayLiteralExp e)
+ {
+ Type tb = e.type.toBasetype();
+ if (tb.ty == Tsarray || tb.ty == Tarray)
+ {
+ if (e.basis)
+ e.basis.accept(this);
+ foreach (el; *e.elements)
+ {
+ if (el)
+ el.accept(this);
+ }
+ }
+ }
+
+ override void visit(StructLiteralExp e)
+ {
+ if (e.elements)
+ {
+ foreach (ex; *e.elements)
+ {
+ if (ex)
+ ex.accept(this);
+ }
+ }
+ }
+
+ override void visit(AssocArrayLiteralExp e)
+ {
+ if (e.keys)
+ {
+ foreach (i, key; *e.keys)
+ {
+ if (key)
+ key.accept(this);
+ if (auto value = (*e.values)[i])
+ value.accept(this);
+ }
+ }
+ }
+
+ override void visit(NewExp e)
+ {
+ if (e.arguments)
+ {
+ foreach (ex; *e.arguments)
+ {
+ if (ex)
+ ex.accept(this);
+ }
+ }
+ }
+
+ override void visit(SliceExp e)
+ {
+ e.e1.accept(this);
+ if (e.lwr)
+ e.lwr.accept(this);
+ if (e.upr)
+ e.upr.accept(this);
+ }
+ }
+
+ if (e)
+ {
+ scope ExpWalker ew = new ExpWalker(&dgWriteVar, &dgReadVar, ob, obstate);
+ e.accept(ew);
+ }
+ }
+
+ foreachExp(ob, ob.exp);
+}
+
+/***************************************
+ * Determine the state of a variable based on
+ * its type and storage class.
+ */
+PtrState toPtrState(VarDeclaration v)
+{
+ /* pointer to mutable: Owner
+ * pointer to mutable, scope: Borrowed
+ * pointer to const: Owner
+ * pointer to const, scope: Readonly
+ * ref: Borrowed
+ * const ref: Readonly
+ */
+
+ auto t = v.type;
+ if (v.isRef())
+ {
+ return t.hasMutableFields() ? PtrState.Borrowed : PtrState.Readonly;
+ }
+ if (v.isScope())
+ {
+ return t.hasPointersToMutableFields() ? PtrState.Borrowed : PtrState.Readonly;
+ }
+ else
+ return PtrState.Owner;
+}
+
+/**********************************
+ * Does type `t` contain any pointers to mutable?
+ */
+bool hasPointersToMutableFields(Type t)
+{
+ auto tb = t.toBasetype();
+ if (!tb.isMutable())
+ return false;
+ if (auto tsa = tb.isTypeSArray())
+ {
+ return tsa.nextOf().hasPointersToMutableFields();
+ }
+ if (auto ts = tb.isTypeStruct())
+ {
+ foreach (v; ts.sym.fields)
+ {
+ if (v.isRef())
+ {
+ if (v.type.hasMutableFields())
+ return true;
+ }
+ else if (v.type.hasPointersToMutableFields())
+ return true;
+ }
+ return false;
+ }
+ auto tbn = tb.nextOf();
+ return tbn && tbn.hasMutableFields();
+}
+
+/********************************
+ * Does type `t` have any mutable fields?
+ */
+bool hasMutableFields(Type t)
+{
+ auto tb = t.toBasetype();
+ if (!tb.isMutable())
+ return false;
+ if (auto tsa = tb.isTypeSArray())
+ {
+ return tsa.nextOf().hasMutableFields();
+ }
+ if (auto ts = tb.isTypeStruct())
+ {
+ foreach (v; ts.sym.fields)
+ {
+ if (v.type.hasMutableFields())
+ return true;
+ }
+ return false;
+ }
+ return true;
+}
+
+
+
+/***************************************
+ * Do the data flow analysis (i.e. compute the input[]
+ * and output[] vectors for each ObNode).
+ */
+void doDataFlowAnalysis(ref ObState obstate)
+{
+ enum log = false;
+ if (log)
+ {
+ printf("-----------------doDataFlowAnalysis()-------------------------\n");
+ foreach (ob; obstate.nodes) ob.print();
+ printf("------------------------------------------\n");
+ }
+
+ if (!obstate.nodes.length)
+ return;
+
+ auto startnode = obstate.nodes[0];
+ assert(startnode.preds.length == 0);
+
+ /* Set opening state `input[]` for first node
+ */
+ foreach (i, ref ps; startnode.input)
+ {
+ auto v = obstate.vars[i];
+ auto state = toPtrState(v);
+ if (v.isParameter())
+ {
+ if (v.isOut())
+ state = PtrState.Undefined;
+ else if (v.isBorrowedPtr())
+ state = PtrState.Borrowed;
+ else
+ state = PtrState.Owner;
+ }
+ else
+ state = PtrState.Undefined;
+ ps.state = state;
+ ps.deps.zero();
+ startnode.gen[i] = ps;
+ }
+
+ /* Set all output[]s to Initial
+ */
+ foreach (ob; obstate.nodes[])
+ {
+ foreach (ref ps; ob.output)
+ {
+ ps.state = PtrState.Initial;
+ ps.deps.zero();
+ }
+ }
+
+ const vlen = obstate.vars.length;
+ PtrVarState pvs;
+ pvs.deps.length = vlen;
+ int counter = 0;
+ bool changes;
+ do
+ {
+ changes = false;
+ assert(++counter <= 1000); // should converge, but don't hang if it doesn't
+ foreach (ob; obstate.nodes[])
+ {
+ /* Construct ob.gen[] by combining the .output[]s of each ob.preds[]
+ * and set ob.input[] to the same state
+ */
+ if (ob != startnode)
+ {
+ assert(ob.preds.length);
+
+ foreach (i; 0 .. vlen)
+ {
+ ob.gen[i] = ob.preds[0].output[i];
+ }
+
+ foreach (j; 1 .. ob.preds.length)
+ {
+ foreach (i; 0 .. vlen)
+ {
+ ob.gen[i].combine(ob.preds[j].output[i], i, ob.gen);
+ }
+ }
+
+ /* Set ob.input[] to ob.gen[],
+ * if any changes were made we'll have to do another iteration
+ */
+ foreach (i; 0 .. vlen)
+ {
+ if (ob.gen[i] != ob.input[i])
+ {
+ ob.input[i] = ob.gen[i];
+ changes = true;
+ }
+ }
+ }
+
+ /* Compute gen[] for node ob
+ */
+ genKill(obstate, ob);
+
+ foreach (i; 0 .. vlen)
+ {
+ if (ob.gen[i] != ob.output[i])
+ {
+ ob.output[i] = ob.gen[i];
+ changes = true;
+ }
+ }
+ }
+ } while (changes);
+
+ static if (log)
+ {
+ foreach (obi, ob; obstate.nodes)
+ {
+ printf("%d: %s\n", obi, ob.exp ? ob.exp.toChars() : "".ptr);
+ printf(" input:\n");
+ foreach (i, ref pvs2; ob.input[])
+ {
+ printf(" %s: ", obstate.vars[i].toChars()); pvs2.print(obstate.vars[]);
+ }
+
+ printf(" gen:\n");
+ foreach (i, ref pvs2; ob.gen[])
+ {
+ printf(" %s: ", obstate.vars[i].toChars()); pvs2.print(obstate.vars[]);
+ }
+ printf(" output:\n");
+ foreach (i, ref pvs2; ob.output[])
+ {
+ printf(" %s: ", obstate.vars[i].toChars()); pvs2.print(obstate.vars[]);
+ }
+ }
+ printf("\n");
+ }
+}
+
+
+/***************************************
+ * Check for Ownership/Borrowing errors.
+ */
+void checkObErrors(ref ObState obstate)
+{
+ enum log = false;
+ if (log)
+ printf("------------checkObErrors()----------\n");
+
+ void dgWriteVar(ObNode* ob, PtrVarState[] cpvs, VarDeclaration v, Expression e)
+ {
+ if (log) printf("dgWriteVar(v:%s, e:%s)\n", v.toChars(), e ? e.toChars() : "null");
+ const vi = obstate.vars.find(v);
+ assert(vi != size_t.max);
+ PtrVarState* pvs = &cpvs[vi];
+ readVar(ob, vi, true, cpvs);
+ if (e)
+ {
+ if (isBorrowedPtr(v))
+ pvs.state = PtrState.Borrowed;
+ else if (isReadonlyPtr(v))
+ pvs.state = PtrState.Readonly;
+ else
+ pvs.state = PtrState.Owner;
+ pvs.deps.zero();
+
+ EscapeByResults er;
+ escapeByValue(e, &er, true);
+
+ void by(VarDeclaration r) // `v` = `r`
+ {
+ //printf(" by(%s)\n", r.toChars());
+ const ri = obstate.vars.find(r);
+ if (ri == size_t.max)
+ return;
+
+ with (PtrState)
+ {
+ pvs.deps[ri] = true; // v took from r
+ auto pvsr = &cpvs[ri];
+
+ if (pvsr.state == Undefined)
+ {
+ v.error(e.loc, "is reading from `%s` which is Undefined", r.toChars());
+ }
+ else if (isBorrowedPtr(v)) // v is going to borrow from r
+ {
+ if (pvsr.state == Readonly)
+ v.error(e.loc, "is borrowing from `%s` which is Readonly", r.toChars());
+
+ pvs.state = Borrowed;
+ }
+ else if (isReadonlyPtr(v))
+ {
+ pvs.state = Readonly;
+ }
+ else
+ {
+ // move from r to v
+ pvsr.state = Undefined;
+ pvsr.deps.zero();
+ }
+ }
+ }
+
+ foreach (VarDeclaration v2; er.byvalue)
+ by(v2);
+ foreach (VarDeclaration v2; er.byref)
+ by(v2);
+ }
+ else
+ {
+ if (isBorrowedPtr(v))
+ pvs.state = PtrState.Borrowed;
+ else if (isReadonlyPtr(v))
+ pvs.state = PtrState.Readonly;
+ else
+ pvs.state = PtrState.Owner;
+ pvs.deps.zero();
+ }
+ }
+
+ void dgReadVar(const ref Loc loc, ObNode* ob, VarDeclaration v, bool mutable, PtrVarState[] gen)
+ {
+ if (log) printf("dgReadVar() %s\n", v.toChars());
+ const vi = obstate.vars.find(v);
+ assert(vi != size_t.max);
+ auto pvs = &gen[vi];
+ if (pvs.state == PtrState.Undefined)
+ v.error(loc, "has undefined state and cannot be read");
+
+ readVar(ob, vi, mutable, gen);
+ }
+
+ void foreachExp(ObNode* ob, Expression e, PtrVarState[] cpvs)
+ {
+ extern (C++) final class ExpWalker : Visitor
+ {
+ alias visit = typeof(super).visit;
+ extern (D) void delegate(ObNode*, PtrVarState[], VarDeclaration, Expression) dgWriteVar;
+ extern (D) void delegate(const ref Loc loc, ObNode* ob, VarDeclaration v, bool mutable, PtrVarState[]) dgReadVar;
+ PtrVarState[] cpvs;
+ ObNode* ob;
+ ObState* obstate;
+
+ extern (D) this(void delegate(const ref Loc loc, ObNode* ob, VarDeclaration v, bool mutable, PtrVarState[]) dgReadVar,
+ void delegate(ObNode*, PtrVarState[], VarDeclaration, Expression) dgWriteVar,
+ PtrVarState[] cpvs, ObNode* ob, ref ObState obstate)
+ {
+ this.dgReadVar = dgReadVar;
+ this.dgWriteVar = dgWriteVar;
+ this.cpvs = cpvs;
+ this.ob = ob;
+ this.obstate = &obstate;
+ }
+
+ override void visit(Expression)
+ {
+ }
+
+ override void visit(DeclarationExp e)
+ {
+ void Dsymbol_visit(Dsymbol s)
+ {
+ if (auto vd = s.isVarDeclaration())
+ {
+ s = s.toAlias();
+ if (s != vd)
+ return Dsymbol_visit(s);
+ if (!isTrackableVar(vd))
+ return;
+
+ if (vd._init && vd._init.isVoidInitializer())
+ return;
+
+ auto ei = vd._init ? vd._init.isExpInitializer() : null;
+ if (ei)
+ {
+ auto e = ei.exp;
+ if (auto ae = e.isConstructExp())
+ e = ae.e2;
+ dgWriteVar(ob, cpvs, vd, e);
+ }
+ else
+ dgWriteVar(ob, cpvs, vd, null);
+ }
+ else if (auto td = s.isTupleDeclaration())
+ {
+ foreach (o; *td.objects)
+ {
+ if (auto eo = o.isExpression())
+ {
+ if (auto se = eo.isDsymbolExp())
+ {
+ Dsymbol_visit(se.s);
+ }
+ }
+ }
+ }
+ }
+
+ Dsymbol_visit(e.declaration);
+ }
+
+ override void visit(AssignExp ae)
+ {
+ ae.e2.accept(this);
+ if (auto ve = ae.e1.isVarExp())
+ {
+ if (auto v = ve.var.isVarDeclaration())
+ if (isTrackableVar(v))
+ dgWriteVar(ob, cpvs, v, ae.e2);
+ }
+ else
+ ae.e1.accept(this);
+ }
+
+ override void visit(VarExp ve)
+ {
+ //printf("VarExp: %s\n", ve.toChars());
+ if (auto v = ve.var.isVarDeclaration())
+ if (isTrackableVar(v))
+ {
+ dgReadVar(ve.loc, ob, v, isMutableRef(ve.type), cpvs);
+ }
+ }
+
+ override void visit(CallExp ce)
+ {
+ //printf("CallExp(%s)\n", ce.toChars());
+ ce.e1.accept(this);
+ auto t = ce.e1.type.toBasetype();
+ auto tf = t.isTypeFunction();
+ if (!tf)
+ {
+ assert(t.ty == Tdelegate);
+ tf = t.nextOf().isTypeFunction();
+ assert(tf);
+ }
+
+ // j=1 if _arguments[] is first argument
+ const int j = tf.isDstyleVariadic();
+ bool hasOut;
+ const varStackSave = obstate.varStack.length;
+
+ foreach (const i, arg; (*ce.arguments)[])
+ {
+ if (i - j < tf.parameterList.length &&
+ i >= j)
+ {
+ Parameter p = tf.parameterList[i - j];
+ auto pt = p.type.toBasetype();
+
+ if (!(p.storageClass & STC.out_ && arg.isVarExp()))
+ arg.accept(this);
+
+ EscapeByResults er;
+ escapeByValue(arg, &er, true);
+
+ void by(VarDeclaration v)
+ {
+ if (!isTrackableVar(v))
+ return;
+
+ const vi = obstate.vars.find(v);
+ if (vi == size_t.max)
+ return;
+
+ auto pvs = &cpvs[vi];
+
+ if (p.storageClass & STC.out_)
+ {
+ /// initialize
+ hasOut = true;
+ makeUndefined(vi, cpvs);
+ }
+ else if (p.storageClass & STC.scope_)
+ {
+ // borrow
+ obstate.varStack.push(vi);
+ obstate.mutableStack.push(isMutableRef(pt));
+ }
+ else
+ {
+ // move (i.e. consume arg)
+ if (pvs.state != PtrState.Owner)
+ v.error(arg.loc, "is not Owner, cannot consume its value");
+ makeUndefined(vi, cpvs);
+ }
+ }
+
+ foreach (VarDeclaration v2; er.byvalue)
+ by(v2);
+ foreach (VarDeclaration v2; er.byref)
+ by(v2);
+ }
+ else // variadic args
+ {
+ arg.accept(this);
+
+ EscapeByResults er;
+ escapeByValue(arg, &er, true);
+
+ void byv(VarDeclaration v)
+ {
+ if (!isTrackableVar(v))
+ return;
+
+ const vi = obstate.vars.find(v);
+ if (vi == size_t.max)
+ return;
+
+ auto pvs = &cpvs[vi];
+
+ if (tf.parameterList.stc & STC.scope_)
+ {
+ // borrow
+ obstate.varStack.push(vi);
+ obstate.mutableStack.push(isMutableRef(arg.type) &&
+ !(tf.parameterList.stc & (STC.const_ | STC.immutable_)));
+ }
+ else
+ {
+ // move (i.e. consume arg)
+ if (pvs.state != PtrState.Owner)
+ v.error(arg.loc, "is not Owner, cannot consume its value");
+ makeUndefined(vi, cpvs);
+ }
+ }
+
+ foreach (VarDeclaration v2; er.byvalue)
+ byv(v2);
+ foreach (VarDeclaration v2; er.byref)
+ byv(v2);
+ }
+ }
+
+ /* Do a dummy 'read' of each variable passed to the function,
+ * to detect O/B errors
+ */
+ assert(obstate.varStack.length == obstate.mutableStack.length);
+ foreach (i; varStackSave .. obstate.varStack.length)
+ {
+ const vi = obstate.varStack[i];
+ auto pvs = &cpvs[vi];
+ auto v = obstate.vars[vi];
+ //if (pvs.state == PtrState.Undefined)
+ //v.error(ce.loc, "is Undefined, cannot pass to function");
+
+ dgReadVar(ce.loc, ob, v, obstate.mutableStack[i], cpvs);
+
+ if (pvs.state == PtrState.Owner)
+ {
+ for (size_t k = i + 1; k < obstate.varStack.length;++k)
+ {
+ const vk = obstate.varStack[k];
+ if (vk == vi)
+ {
+ if (obstate.mutableStack[vi] || obstate.mutableStack[vk])
+ {
+ v.error(ce.loc, "is passed as Owner more than once");
+ break; // no need to continue
+ }
+ }
+ }
+ }
+ }
+
+ /* Pop off stack all variables for this function call
+ */
+ obstate.varStack.setDim(varStackSave);
+ obstate.mutableStack.setDim(varStackSave);
+
+ if (hasOut)
+ // Initialization of out's only happens after the function call
+ foreach (const i, arg; (*ce.arguments)[])
+ {
+ if (i - j < tf.parameterList.length &&
+ i >= j)
+ {
+ Parameter p = tf.parameterList[i - j];
+ if (p.storageClass & STC.out_)
+ {
+ if (auto v = isTrackableVarExp(arg))
+ {
+ dgWriteVar(ob, cpvs, v, null);
+ }
+ }
+ }
+ }
+ }
+
+ override void visit(SymOffExp e)
+ {
+ if (auto v = e.var.isVarDeclaration())
+ if (isTrackableVar(v))
+ {
+ dgReadVar(e.loc, ob, v, isMutableRef(e.type), cpvs);
+ }
+ }
+
+ override void visit(LogicalExp e)
+ {
+ e.e1.accept(this);
+
+ const vlen = obstate.vars.length;
+ auto p = cast(PtrVarState*)mem.xcalloc(vlen, PtrVarState.sizeof);
+ PtrVarState[] out1 = p[0 .. vlen];
+ foreach (i, ref pvs; out1)
+ {
+ pvs = cpvs[i];
+ }
+
+ e.e2.accept(this);
+
+ // Merge out1 into cpvs
+ foreach (i; 0 .. vlen)
+ {
+ cpvs[i].combine(out1[i], i, cpvs);
+ }
+
+ mem.xfree(p); // should free .deps too
+ }
+
+ override void visit(CondExp e)
+ {
+ e.econd.accept(this);
+
+ const vlen = obstate.vars.length;
+ auto p = cast(PtrVarState*)mem.xcalloc(vlen, PtrVarState.sizeof);
+ PtrVarState[] out1 = p[0 .. vlen];
+ foreach (i, ref pvs; out1)
+ {
+ pvs = cpvs[i];
+ }
+
+ e.e1.accept(this);
+
+ // Swap out1 with cpvs
+ foreach (i; 0 .. vlen)
+ {
+ out1[i].deps.swap(cpvs[i].deps);
+ const state = out1[i].state;
+ out1[i].state = cpvs[i].state;
+ cpvs[i].state = state;
+ }
+
+ e.e2.accept(this);
+
+ // Merge out1 into cpvs
+ foreach (i; 0 .. vlen)
+ {
+ cpvs[i].combine(out1[i], i, cpvs);
+ }
+
+ mem.xfree(p); // should free .deps too
+ }
+
+ override void visit(AddrExp e)
+ {
+ /* Taking the address of struct literal is normally not
+ * allowed, but CTFE can generate one out of a new expression,
+ * but it'll be placed in static data so no need to check it.
+ */
+ if (e.e1.op != TOK.structLiteral)
+ e.e1.accept(this);
+ }
+
+ override void visit(UnaExp e)
+ {
+ e.e1.accept(this);
+ }
+
+ override void visit(BinExp e)
+ {
+ e.e1.accept(this);
+ e.e2.accept(this);
+ }
+
+ override void visit(ArrayLiteralExp e)
+ {
+ Type tb = e.type.toBasetype();
+ if (tb.ty == Tsarray || tb.ty == Tarray)
+ {
+ if (e.basis)
+ e.basis.accept(this);
+ foreach (el; *e.elements)
+ {
+ if (el)
+ el.accept(this);
+ }
+ }
+ }
+
+ override void visit(StructLiteralExp e)
+ {
+ if (e.elements)
+ {
+ foreach (ex; *e.elements)
+ {
+ if (ex)
+ ex.accept(this);
+ }
+ }
+ }
+
+ override void visit(AssocArrayLiteralExp e)
+ {
+ if (e.keys)
+ {
+ foreach (i, key; *e.keys)
+ {
+ if (key)
+ key.accept(this);
+ if (auto value = (*e.values)[i])
+ value.accept(this);
+ }
+ }
+ }
+
+ override void visit(NewExp e)
+ {
+ if (e.arguments)
+ {
+ foreach (ex; *e.arguments)
+ {
+ if (ex)
+ ex.accept(this);
+ }
+ }
+ }
+
+ override void visit(SliceExp e)
+ {
+ e.e1.accept(this);
+ if (e.lwr)
+ e.lwr.accept(this);
+ if (e.upr)
+ e.upr.accept(this);
+ }
+ }
+
+ if (e)
+ {
+ scope ExpWalker ew = new ExpWalker(&dgReadVar, &dgWriteVar, cpvs, ob, obstate);
+ e.accept(ew);
+ }
+ }
+
+ const vlen = obstate.vars.length;
+ auto p = cast(PtrVarState*)mem.xcalloc(vlen, PtrVarState.sizeof);
+ PtrVarState[] cpvs = p[0 .. vlen];
+ foreach (ref pvs; cpvs)
+ pvs.deps.length = vlen;
+
+ foreach (obi, ob; obstate.nodes)
+ {
+ static if (log)
+ {
+ printf("%d: %s\n", obi, ob.exp ? ob.exp.toChars() : "".ptr);
+ printf(" input:\n");
+ foreach (i, ref pvs; ob.input[])
+ {
+ printf(" %s: ", obstate.vars[i].toChars()); pvs.print(obstate.vars[]);
+ }
+ }
+
+ /* Combine the .output[]s of each ob.preds[] looking for errors
+ */
+ if (obi) // skip startnode
+ {
+ assert(ob.preds.length);
+
+ foreach (i; 0 .. vlen)
+ {
+ ob.gen[i] = ob.preds[0].output[i];
+ }
+
+ foreach (j; 1 .. ob.preds.length)
+ {
+ foreach (i; 0 .. vlen)
+ {
+ auto pvs1 = &ob.gen[i];
+ auto pvs2 = &ob.preds[j].output[i];
+ const s1 = pvs1.state;
+ const s2 = pvs2.state;
+ if (s1 != s2 && (s1 == PtrState.Owner || s2 == PtrState.Owner))
+ {
+ auto v = obstate.vars[i];
+ v.error(ob.exp ? ob.exp.loc : v.loc, "is both %s and %s", s1.toChars(), s2.toChars());
+ }
+ pvs1.combine(*pvs2, i, ob.gen);
+ }
+ }
+ }
+
+ /* Prolly should use gen[] instead of cpvs[], or vice versa
+ */
+ foreach (i, ref pvs; ob.input)
+ {
+ cpvs[i] = pvs;
+ }
+
+ foreachExp(ob, ob.exp, cpvs);
+
+ static if (log)
+ {
+ printf(" cpvs:\n");
+ foreach (i, ref pvs; cpvs[])
+ {
+ printf(" %s: ", obstate.vars[i].toChars()); pvs.print(obstate.vars[]);
+ }
+ printf(" output:\n");
+ foreach (i, ref pvs; ob.output[])
+ {
+ printf(" %s: ", obstate.vars[i].toChars()); pvs.print(obstate.vars[]);
+ }
+ }
+
+ if (ob.obtype == ObType.retexp)
+ {
+ EscapeByResults er;
+ escapeByValue(ob.exp, &er, true);
+
+ void by(VarDeclaration r) // `r` is the rvalue
+ {
+ const ri = obstate.vars.find(r);
+ if (ri == size_t.max)
+ return;
+ with (PtrState)
+ {
+ auto pvsr = &ob.output[ri];
+ switch (pvsr.state)
+ {
+ case Undefined:
+ r.error(ob.exp.loc, "is returned but is Undefined");
+ break;
+
+ case Owner:
+ pvsr.state = Undefined; // returning a pointer "consumes" it
+ break;
+
+ case Borrowed:
+ case Readonly:
+ break;
+
+ default:
+ assert(0);
+ }
+ }
+ }
+
+ foreach (VarDeclaration v2; er.byvalue)
+ by(v2);
+ foreach (VarDeclaration v2; er.byref)
+ by(v2);
+ }
+
+ if (ob.obtype == ObType.return_ || ob.obtype == ObType.retexp)
+ {
+ foreach (i, ref pvs; ob.output[])
+ {
+ //printf("%s: ", obstate.vars[i].toChars()); pvs.print(obstate.vars[]);
+ if (pvs.state == PtrState.Owner)
+ {
+ auto v = obstate.vars[i];
+ if (v.type.hasPointers())
+ v.error(v.loc, "is left dangling at return");
+ }
+ }
+ }
+ }
+}
+
+
+/***************************************************
+ * Read from variable vi.
+ * The beginning of the 'scope' of a variable is when it is first read.
+ * Hence, when a read is done, instead of when assignment to the variable is done, the O/B rules are enforced.
+ * (Also called "non-lexical scoping".)
+ */
+void readVar(ObNode* ob, const size_t vi, bool mutable, PtrVarState[] gen)
+{
+ //printf("readVar(v%d)\n", cast(int)vi);
+ auto pvso = &gen[vi];
+ switch (pvso.state)
+ {
+ case PtrState.Owner:
+ //printf("t: %s\n", t.toChars());
+ if (mutable) // if mutable read
+ {
+ makeChildrenUndefined(vi, gen);
+ }
+ else // const read
+ {
+ // If there's a Borrow child, set that to Undefined
+ foreach (di; 0 .. gen.length)
+ {
+ auto pvsd = &gen[di];
+ if (pvsd.deps[vi] && pvsd.state == PtrState.Borrowed) // if di borrowed vi
+ {
+ makeUndefined(di, gen);
+ }
+ }
+ }
+ break;
+
+ case PtrState.Borrowed:
+ /* All children become Undefined
+ */
+ makeChildrenUndefined(vi, gen);
+ break;
+
+ case PtrState.Readonly:
+ break;
+
+ case PtrState.Undefined:
+ break;
+
+ default:
+ break;
+ }
+}
+
+/********************
+ * Recursively make Undefined all who list vi as a dependency
+ */
+void makeChildrenUndefined(size_t vi, PtrVarState[] gen)
+{
+ //printf("makeChildrenUndefined(%d)\n", vi);
+ foreach (di; 0 .. gen.length)
+ {
+ if (gen[di].deps[vi]) // if di depends on vi
+ {
+ if (gen[di].state != PtrState.Undefined)
+ {
+ gen[di].state = PtrState.Undefined; // set this first to avoid infinite recursion
+ makeChildrenUndefined(di, gen);
+ gen[di].deps.zero();
+ }
+ }
+ }
+}
+
+
+/********************
+ * Recursively make Undefined vi undefined and all who list vi as a dependency
+ */
+void makeUndefined(size_t vi, PtrVarState[] gen)
+{
+ auto pvs = &gen[vi];
+ pvs.state = PtrState.Undefined; // set this first to avoid infinite recursion
+ makeChildrenUndefined(vi, gen);
+ pvs.deps.zero();
+}
+
+/*************************
+ * Is type `t` a reference to a const or a reference to a mutable?
+ */
+bool isMutableRef(Type t)
+{
+ auto tb = t.toBasetype();
+ return (tb.nextOf() ? tb.nextOf() : tb).isMutable();
+}