# # # The Nim Compiler # (c) Copyright 2017 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. # ## Data flow analysis for Nim. ## We transform the AST into a linear list of instructions first to ## make this easier to handle: There are only 3 different branching ## instructions: 'goto X' is an unconditional goto, 'fork X' ## is a conditional goto (either the next instruction or 'X' can be ## taken), 'loop X' is the only jump that jumps back. ## ## Exhaustive case statements are translated ## so that the last branch is transformed into an 'else' branch. ## ``return`` and ``break`` are all covered by 'goto'. ## ## The data structures and algorithms used here are inspired by ## "A Graph–Free Approach to Data–Flow Analysis" by Markus Mohnen. ## https://link.springer.com/content/pdf/10.1007/3-540-45937-5_6.pdf import ast, lineinfos, renderer, aliasanalysis import std/private/asciitables import std/intsets when defined(nimPreviewSlimSystem): import std/assertions type InstrKind* = enum goto, loop, fork, def, use Instr* = object case kind*: InstrKind of goto, fork, loop: dest*: int of def, use: n*: PNode # contains the def/use location. ControlFlowGraph* = seq[Instr] TPosition = distinct int TBlock = object case isTryBlock: bool of false: label: PSym breakFixups: seq[(TPosition, seq[PNode])] # Contains the gotos for the breaks along with their pending finales of true: finale: PNode raiseFixups: seq[TPosition] # Contains the gotos for the raises Con = object code: ControlFlowGraph inTryStmt, interestingInstructions: int blocks: seq[TBlock] owner: PSym root: PSym proc codeListing(c: ControlFlowGraph, start = 0; last = -1): string = # for debugging purposes # first iteration: compute all necessary labels: result = "" var jumpTargets = initIntSet() let last = if last < 0: c.len-1 else: min(last, c.len-1) for i in start..last: if c[i].kind in {goto, fork, loop}: jumpTargets.incl(i+c[i].dest) var i = start while i <= last: if i in jumpTargets: result.add("L" & $i & ":\n") result.add "\t" result.add ($i & " " & $c[i].kind) result.add "\t" case c[i].kind of def, use: result.add renderTree(c[i].n) result.add("\t#") result.add($c[i].n.info.line) result.add("\n") of goto, fork, loop: result.add "L" result.addInt c[i].dest+i inc i if i in jumpTargets: result.add("L" & $i & ": End\n") proc echoCfg*(c: ControlFlowGraph; start = 0; last = -1) {.deprecated.} = ## echos the ControlFlowGraph for debugging purposes. echo codeListing(c, start, last).alignTable proc forkI(c: var Con): TPosition = result = TPosition(c.code.len) c.code.add Instr(kind: fork, dest: 0) proc gotoI(c: var Con): TPosition = result = TPosition(c.code.len) c.code.add Instr(kind: goto, dest: 0) proc genLabel(c: Con): TPosition = TPosition(c.code.len) template checkedDistance(dist): int = doAssert low(int) div 2 + 1 < dist and dist < high(int) div 2 dist proc jmpBack(c: var Con, p = TPosition(0)) = c.code.add Instr(kind: loop, dest: checkedDistance(p.int - c.code.len)) proc patch(c: var Con, p: TPosition) = # patch with current index c.code[p.int].dest = checkedDistance(c.code.len - p.int) proc gen(c: var Con; n: PNode) proc popBlock(c: var Con; oldLen: int) = var exits: seq[TPosition] = @[] exits.add c.gotoI() for f in c.blocks[oldLen].breakFixups: c.patch(f[0]) for finale in f[1]: c.gen(finale) exits.add c.gotoI() for e in exits: c.patch e c.blocks.setLen(oldLen) template withBlock(labl: PSym; body: untyped) = let oldLen = c.blocks.len c.blocks.add TBlock(isTryBlock: false, label: labl) body popBlock(c, oldLen) template forkT(body) = let lab1 = c.forkI() body c.patch(lab1) proc genWhile(c: var Con; n: PNode) = # lab1: # cond, tmp # fork tmp, lab2 # body # jmp lab1 # lab2: let lab1 = c.genLabel withBlock(nil): if isTrue(n[0]): c.gen(n[1]) c.jmpBack(lab1) else: c.gen(n[0]) forkT: c.gen(n[1]) c.jmpBack(lab1) proc genIf(c: var Con, n: PNode) = #[ if cond: A elif condB: B elif condC: C else: D cond fork lab1 A goto Lend lab1: condB fork lab2 B goto Lend2 lab2: condC fork L3 C goto Lend3 L3: D ]# var endings: seq[TPosition] = @[] let oldInteresting = c.interestingInstructions let oldLen = c.code.len for i in 0.. 0: # Ok, we are in a try, lets see which (if any) try's we break out from: for b in countdown(c.blocks.high, i): if c.blocks[b].isTryBlock: trailingFinales.add c.blocks[b].finale c.blocks[i].breakFixups.add (lab1, trailingFinales) proc genBreak(c: var Con; n: PNode) = inc c.interestingInstructions if n[0].kind == nkSym: for i in countdown(c.blocks.high, 0): if not c.blocks[i].isTryBlock and c.blocks[i].label == n[0].sym: genBreakOrRaiseAux(c, i, n) return #globalError(n.info, "VM problem: cannot find 'break' target") else: for i in countdown(c.blocks.high, 0): if not c.blocks[i].isTryBlock: genBreakOrRaiseAux(c, i, n) return proc genTry(c: var Con; n: PNode) = var endings: seq[TPosition] = @[] let oldLen = c.blocks.len c.blocks.add TBlock(isTryBlock: true, finale: if n[^1].kind == nkFinally: n[^1] else: newNode(nkEmpty)) inc c.inTryStmt c.gen(n[0]) dec c.inTryStmt for f in c.blocks[oldLen].raiseFixups: c.patch(f) c.blocks.setLen oldLen for i in 1.. 0: for i in countdown(c.blocks.high, 0): if c.blocks[i].isTryBlock: genBreakOrRaiseAux(c, i, n) return assert false # Unreachable else: genNoReturn(c) proc genImplicitReturn(c: var Con) = if c.owner.kind in {skProc, skFunc, skMethod, skIterator, skConverter} and resultPos < c.owner.ast.len: gen(c, c.owner.ast[resultPos]) proc genReturn(c: var Con; n: PNode) = inc c.interestingInstructions if n[0].kind != nkEmpty: gen(c, n[0]) else: genImplicitReturn(c) genBreakOrRaiseAux(c, 0, n) const InterestingSyms = {skVar, skResult, skLet, skParam, skForVar, skTemp} proc skipTrivials(c: var Con, n: PNode): PNode = result = n while true: case result.kind of PathKinds0 - {nkBracketExpr}: result = result[0] of nkBracketExpr: gen(c, result[1]) result = result[0] of PathKinds1: result = result[1] else: break proc genUse(c: var Con; orig: PNode) = let n = c.skipTrivials(orig) if n.kind == nkSym: if n.sym.kind in InterestingSyms and n.sym == c.root: c.code.add Instr(kind: use, n: orig) inc c.interestingInstructions else: gen(c, n) proc genDef(c: var Con; orig: PNode) = let n = c.skipTrivials(orig) if n.kind == nkSym and n.sym.kind in InterestingSyms: if n.sym == c.root: c.code.add Instr(kind: def, n: orig) inc c.interestingInstructions proc genCall(c: var Con; n: PNode) = gen(c, n[0]) var t = n[0].typ if t != nil: t = t.skipTypes(abstractInst) for i in 1.. 0 and canRaiseConservative(n[0]): inc c.interestingInstructions # we generate the instruction sequence: # fork lab1 # goto exceptionHandler (except or finally) # lab1: forkT: for i in countdown(c.blocks.high, 0): if c.blocks[i].isTryBlock: genBreakOrRaiseAux(c, i, n) break proc genMagic(c: var Con; n: PNode; m: TMagic) = case m of mAnd, mOr: c.genAndOr(n) of mNew, mNewFinalize: genDef(c, n[1]) for i in 2.. pc: pc = newPc else: break c[i].dest = pc - i of loop, def, use: discard proc constructCfg*(s: PSym; body: PNode; root: PSym): ControlFlowGraph = ## constructs a control flow graph for ``body``. var c = Con(code: @[], blocks: @[], owner: s, root: root) withBlock(s): gen(c, body) if root.kind == skResult: genImplicitReturn(c) when defined(gcArc) or defined(gcOrc) or defined(gcAtomicArc): result = c.code # will move else: shallowCopy(result, c.code) when false: optimizeJumps result