| /** |
| * Flow analysis for Ownership/Borrowing |
| * |
| * Copyright: Copyright (C) 1999-2023 by The D Language Foundation, All Rights Reserved |
| * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) |
| * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) |
| * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/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.location; |
| import dmd.mtype; |
| import dmd.printast; |
| import dmd.statement; |
| import dmd.stmtstate; |
| import dmd.tokens; |
| import dmd.visitor; |
| |
| import dmd.root.bitarray; |
| import dmd.common.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) scope |
| { |
| 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.length : 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) scope |
| { |
| 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(), EXPtoString(e.op).ptr, 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()) |
| { |
| td.foreachVar(&Dsymbol_visit); |
| } |
| } |
| |
| 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 != EXP.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.isReference()) |
| { |
| 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.isReference()) |
| { |
| 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 |
| { |
| if (pvs.state == PtrState.Owner && v.type.hasPointersToMutableFields()) |
| v.error(e.loc, "assigning to Owner without disposing of owned value"); |
| |
| 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) scope |
| { |
| 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()) |
| { |
| td.foreachVar(&Dsymbol_visit); |
| } |
| } |
| |
| 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 != EXP.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 not disposed of before 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(); |
| } |