diff options
author | Andreas Rumpf <rumpf_a@web.de> | 2020-07-15 23:00:06 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-07-15 23:00:06 +0200 |
commit | c5358b0d4b1d27db04b974a0183c9b7312bc7bdc (patch) | |
tree | ca3a71953358c8f9475188045ba61c5dfb5caf87 /compiler | |
parent | 813dd1b670b953b0ac8348b04079faadace46c29 (diff) | |
download | Nim-c5358b0d4b1d27db04b974a0183c9b7312bc7bdc.tar.gz |
An optimizer for ARC (#14962)
* WIP: an optimizer for ARC * do not optimize away destructors in 'finally' if unstructured control flow is involved * optimized the optimizer * minor code cleanup * first steps to .cursor inference * cursor inference: big steps to a working solution * baby steps * better .cursor inference * new feature: expandArc for easy inspection of the AST after ARC transformations * added topt_cursor test * adapt tests * cleanups, make tests green * optimize common traversal patterns * moved test case * fixes .cursor inference so that npeg compiles once again * cursor inference: more bugfixes Co-authored-by: Clyybber <darkmine956@gmail.com>
Diffstat (limited to 'compiler')
-rw-r--r-- | compiler/commands.nim | 3 | ||||
-rw-r--r-- | compiler/cursor_inference.nim | 295 | ||||
-rw-r--r-- | compiler/injectdestructors.nim | 54 | ||||
-rw-r--r-- | compiler/optimizer.nim | 285 | ||||
-rw-r--r-- | compiler/options.nim | 2 | ||||
-rw-r--r-- | compiler/renderer.nim | 21 | ||||
-rw-r--r-- | compiler/transf.nim | 2 |
7 files changed, 632 insertions, 30 deletions
diff --git a/compiler/commands.nim b/compiler/commands.nim index 1984c894a..d0936a0c6 100644 --- a/compiler/commands.nim +++ b/compiler/commands.nim @@ -866,6 +866,9 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo; of "expandmacro": expectArg(conf, switch, arg, pass, info) conf.macrosToExpand[arg] = "T" + of "expandarc": + expectArg(conf, switch, arg, pass, info) + conf.arcToExpand[arg] = "T" of "oldgensym": processOnOffSwitchG(conf, {optNimV019}, arg, pass, info) of "useversion": diff --git a/compiler/cursor_inference.nim b/compiler/cursor_inference.nim new file mode 100644 index 000000000..7806a535f --- /dev/null +++ b/compiler/cursor_inference.nim @@ -0,0 +1,295 @@ +# +# +# The Nim Compiler +# (c) Copyright 2020 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## Cursor inference: +## The basic idea was like this: Elide 'destroy(x)' calls if only +## special literals are assigned to 'x' and 'x' is not mutated or +## passed by 'var T' to something else. Special literals are string literals or +## arrays / tuples of string literals etc. +## +## However, there is a much more general rule here: Compute which variables +## can be annotated with `.cursor`. To see how and when we can do that, +## think about this question: In `dest = src` when do we really have to +## *materialize* the full copy? - Only if `dest` or `src` are mutated +## afterwards. `dest` is the potential cursor variable, so that is +## simple to analyse. And if `src` is a location derived from a +## formal parameter, we also know it is not mutated! In other words, we +## do a compile-time copy-on-write analysis. + +import + ast, types, renderer, idents, intsets, options, msgs + +type + Cursor = object + s: PSym + deps: IntSet + Con = object + cursors: seq[Cursor] + mayOwnData: IntSet + mutations: IntSet + reassigns: IntSet + config: ConfigRef + +proc locationRoot(e: PNode; followDotExpr = true): PSym = + var n = e + while true: + case n.kind + of nkSym: + if n.sym.kind in {skVar, skResult, skTemp, skLet, skForVar, skParam}: + return n.sym + else: + return nil + of nkDotExpr, nkDerefExpr, nkBracketExpr, nkHiddenDeref, + nkCheckedFieldExpr, nkAddr, nkHiddenAddr: + if followDotExpr: + n = n[0] + else: + return nil + of nkObjUpConv, nkObjDownConv: + n = n[0] + of nkHiddenStdConv, nkHiddenSubConv, nkConv, nkCast: + n = n[1] + of nkStmtList, nkStmtListExpr: + if n.len > 0: + n = n[^1] + else: + return nil + else: + return nil + +proc addDep(c: var Con; dest: var Cursor; dependsOn: PSym) = + if dest.s != dependsOn: + dest.deps.incl dependsOn.id + +proc cursorId(c: Con; x: PSym): int = + for i in 0..<c.cursors.len: + if c.cursors[i].s == x: return i + return -1 + +proc getCursors(c: Con): IntSet = + #[ + Question: if x depends on y and y depends on z then also y depends on z. + + Or does it? + + var y = x # y gets the copy already + + var harmless = x + + y.s = "mutate" + + ]# + result = initIntSet() + for cur in c.cursors: + if not c.mayOwnData.contains(cur.s.id) and + cur.s.typ.skipTypes({tyGenericInst, tyAlias}).kind != tyOwned: + block doAdd: + for d in cur.deps: + # you cannot borrow from a location that is mutated or (owns data and is re-assigned) + if c.mutations.contains(d) or (c.mayOwnData.contains(d) and c.reassigns.contains(d)): + #echo "bah, not a cursor ", cur.s, " bad dependency ", d + break doAdd + when true: + result.incl cur.s.id + when true: + echo "computed as a cursor ", cur.s, " ", cur.deps, " ", c.config $ cur.s.info + +proc analyseAsgn(c: var Con; dest: var Cursor; n: PNode) = + case n.kind + of nkEmpty, nkCharLit..pred(nkNilLit): + # primitive literals including the empty are harmless: + discard + of nkNilLit: + when false: + # XXX think about this case. Is 'nil' an owned location? + if hasDestructor(n.typ): + # 'myref = nil' is destructive and so 'myref' should not be a cursor: + c.mayOwnData.incl dest.s.id + + of nkExprEqExpr, nkExprColonExpr, nkHiddenStdConv, nkHiddenSubConv, nkCast, nkConv: + analyseAsgn(c, dest, n[1]) + + of nkIfStmt, nkIfExpr: + for i in 0..<n.len: + analyseAsgn(c, dest, n[i].lastSon) + + of nkCaseStmt: + for i in 1..<n.len: + analyseAsgn(c, dest, n[i].lastSon) + + of nkStmtList, nkStmtListExpr: + if n.len > 0: + analyseAsgn(c, dest, n[^1]) + + of nkClosure: + for i in 1..<n.len: + analyseAsgn(c, dest, n[i]) + # you must destroy a closure: + c.mayOwnData.incl dest.s.id + + of nkObjConstr: + for i in 1..<n.len: + analyseAsgn(c, dest, n[i]) + if hasDestructor(n.typ): + # you must destroy a ref object: + c.mayOwnData.incl dest.s.id + + of nkCurly, nkBracket, nkPar, nkTupleConstr: + for son in n: + analyseAsgn(c, dest, son) + if n.typ.skipTypes(abstractInst).kind == tySequence: + # you must destroy a sequence: + c.mayOwnData.incl dest.s.id + + of nkSym: + if n.sym.kind in {skVar, skResult, skTemp, skLet, skForVar, skParam}: + if n.sym.flags * {sfThread, sfGlobal} != {}: + # aliasing a global is inherently dangerous: + c.mayOwnData.incl dest.s.id + else: + # otherwise it's just a dependency, nothing to worry about: + c.addDep(dest, n.sym) + + of nkDotExpr, nkBracketExpr, nkHiddenDeref, nkDerefExpr, + nkObjUpConv, nkObjDownConv, nkCheckedFieldExpr, nkAddr, nkHiddenAddr: + analyseAsgn(c, dest, n[0]) + + of nkCallKinds: + if hasDestructor(n.typ): + # calls do construct, what we construct must be destroyed, + # so dest cannot be a cursor: + c.mayOwnData.incl dest.s.id + elif n.typ.kind in {tyLent, tyVar}: + # we know the result is derived from the first argument: + let r = locationRoot(n[1]) + if r != nil: + c.addDep(dest, r) + else: + let magic = if n[0].kind == nkSym: n[0].sym.magic else: mNone + # this list is subtle, we try to answer the question if after 'dest = f(src)' + # there is a connection betwen 'src' and 'dest' so that mutations to 'src' + # also reflect 'dest': + if magic in {mNone, mMove, mSlice, mAppendStrCh, mAppendStrStr, mAppendSeqElem, mArrToSeq}: + for i in 1..<n.len: + # we always have to assume a 'select(...)' like mechanism. + # But at least we do filter out simple POD types from the + # list of dependencies via the 'hasDestructor' check for + # the root's symbol. + if hasDestructor(n[i].typ.skipTypes({tyVar, tySink, tyLent, tyGenericInst, tyAlias})): + analyseAsgn(c, dest, n[i]) + + else: + # something we cannot handle: + c.mayOwnData.incl dest.s.id + +proc rhsIsSink(c: var Con, n: PNode) = + if n.kind == nkSym and n.typ.skipTypes(abstractInst-{tyOwned}).kind == tyRef: + discard "do no pessimize simple refs further, injectdestructors.nim will prevent moving from it" + else: + let r = locationRoot(n, followDotExpr = false) + if r != nil and r.kind != skParam: + # let x = cursor? --> treat it like a sink parameter + c.mayOwnData.incl r.id + c.mutations.incl r.id + +proc analyse(c: var Con; n: PNode) = + case n.kind + of nkCallKinds: + let parameters = n[0].typ + let L = if parameters != nil: parameters.len else: 0 + + analyse(c, n[0]) + for i in 1..<n.len: + let it = n[i] + let r = locationRoot(it) + if r != nil and i < L: + let paramType = parameters[i].skipTypes({tyGenericInst, tyAlias}) + if paramType.kind in {tyVar, tySink, tyOwned}: + # pass by var? mayOwnData the root + # Else we seek to take ownership of 'r'. This is only valid when 'r' + # actually owns its data. Thus 'r' cannot be a cursor: + c.mayOwnData.incl r.id + # and we assume it might get wasMoved(...) too: + c.mutations.incl r.id + analyse(c, it) + + of nkAsgn, nkFastAsgn: + analyse(c, n[0]) + analyse(c, n[1]) + + if n[0].kind == nkSym: + if hasDestructor(n[0].typ): + # re-assignment to the full object is fundamentally different: + let idx = cursorId(c, n[0].sym) + if idx >= 0: + analyseAsgn(c, c.cursors[idx], n[1]) + + c.reassigns.incl n[0].sym.id + else: + # assignments like 'x.field = value' mean that 'x' itself cannot + # be a cursor: + let r = locationRoot(n[0]) + if r != nil and r.typ.skipTypes(abstractInst).kind notin {tyPtr, tyRef}: + # however, an assignment like 'it.field = x' does not influence r's + # cursorness property: + c.mayOwnData.incl r.id + c.mutations.incl r.id + + if hasDestructor(n[1].typ): + rhsIsSink(c, n[1]) + + of nkAddr, nkHiddenAddr: + analyse(c, n[0]) + let r = locationRoot(n[0]) + if r != nil: + c.mayOwnData.incl r.id + c.mutations.incl r.id + + of nkTupleConstr, nkBracket, nkObjConstr: + for i in ord(n.kind == nkObjConstr)..<n.len: + if n[i].kind == nkSym: + # we assume constructions with cursors are better without + # the cursors because it's likely we can move then, see + # test arc/topt_no_cursor.nim + let r = n[i].sym + c.mayOwnData.incl r.id + c.mutations.incl r.id + + of nkVarSection, nkLetSection: + for it in n: + let value = it[^1] + analyse(c, value) + if hasDestructor(it[0].typ): + for i in 0..<it.len-2: + let v = it[i] + if v.kind == nkSym and v.sym.flags * {sfThread, sfGlobal} == {}: + # assume it's a cursor: + c.cursors.add Cursor(s: it[i].sym) + if it.kind == nkVarTuple and value.kind in {nkPar, nkTupleConstr} and + it.len-2 == value.len: + # this might mayOwnData it again: + analyseAsgn(c, c.cursors[^1], value[i]) + rhsIsSink(c, value[i]) + else: + # this might mayOwnData it again: + analyseAsgn(c, c.cursors[^1], value) + rhsIsSink(c, value) + + of nkNone..nkNilLit, nkTypeSection, nkProcDef, nkConverterDef, + nkMethodDef, nkIteratorDef, nkMacroDef, nkTemplateDef, nkLambda, nkDo, + nkFuncDef, nkConstSection, nkConstDef, nkIncludeStmt, nkImportStmt, + nkExportStmt, nkPragma, nkCommentStmt, nkBreakState, nkTypeOfExpr: + discard + else: + for child in n: analyse(c, child) + +proc computeCursors*(n: PNode; config: ConfigRef): IntSet = + var c = Con(config: config) + analyse(c, n) + result = getCursors c diff --git a/compiler/injectdestructors.nim b/compiler/injectdestructors.nim index 98cfb4743..556d098f4 100644 --- a/compiler/injectdestructors.nim +++ b/compiler/injectdestructors.nim @@ -13,18 +13,11 @@ ## See doc/destructors.rst for a spec of the implemented rewrite rules -## XXX Optimization to implement: if a local variable is only assigned -## string literals as in ``let x = conf: "foo" else: "bar"`` do not -## produce a destructor call for ``x``. The address of ``x`` must also -## not have been taken. ``x = "abc"; x.add(...)`` - -# Todo: -# - eliminate 'wasMoved(x); destroy(x)' pairs as a post processing step. - import - intsets, ast, astalgo, msgs, renderer, magicsys, types, idents, + intsets, strtabs, ast, astalgo, msgs, renderer, magicsys, types, idents, strutils, options, dfa, lowerings, tables, modulegraphs, msgs, - lineinfos, parampatterns, sighashes, liftdestructors + lineinfos, parampatterns, sighashes, liftdestructors, optimizer, + cursor_inference from trees import exprStructuralEquivalent, getRoot @@ -42,6 +35,7 @@ type Con = object owner: PSym g: ControlFlowGraph + cursors: IntSet graph: ModuleGraph otherRead: PNode inLoop, inSpawn: int @@ -155,6 +149,17 @@ proc isLastRead(location: PNode; cfg: ControlFlowGraph; otherRead: var PNode; pc pc = min(variantA, variantB) return pc +proc isCursor(n: PNode; c: Con): bool = + case n.kind + of nkSym: + result = sfCursor in n.sym.flags or c.cursors.contains(n.sym.id) + of nkDotExpr: + result = sfCursor in n[1].sym.flags + of nkCheckedFieldExpr: + result = isCursor(n[0], c) + else: + result = false + proc isLastRead(n: PNode; c: var Con): bool = # first we need to search for the instruction that belongs to 'n': var instr = -1 @@ -486,17 +491,6 @@ proc ensureDestruction(arg, orig: PNode; c: var Con; s: var Scope): PNode = else: result = arg -proc isCursor(n: PNode): bool = - case n.kind - of nkSym: - result = sfCursor in n.sym.flags - of nkDotExpr: - result = sfCursor in n[1].sym.flags - of nkCheckedFieldExpr: - result = isCursor(n[0]) - else: - result = false - proc cycleCheck(n: PNode; c: var Con) = if c.graph.config.selectedGC != gcArc: return var value = n[1] @@ -727,7 +721,8 @@ proc p(n: PNode; c: var Con; s: var Scope; mode: ProcessMode): PNode = elif n.kind in {nkBracket, nkObjConstr, nkTupleConstr, nkClosure, nkNilLit} + nkCallKinds + nkLiterals: result = p(n, c, s, consumed) - elif ((n.kind == nkSym and isSinkParam(n.sym)) or isAnalysableFieldAccess(n, c.owner)) and isLastRead(n, c): + elif ((n.kind == nkSym and isSinkParam(n.sym)) or isAnalysableFieldAccess(n, c.owner)) and + isLastRead(n, c) and not (n.kind == nkSym and isCursor(n, c)): # Sinked params can be consumed only once. We need to reset the memory # to disable the destructor which we have not elided result = destructiveMoveVar(n, c, s) @@ -830,7 +825,7 @@ proc p(n: PNode; c: var Con; s: var Scope; mode: ProcessMode): PNode = if it.kind == nkVarTuple and hasDestructor(ri.typ): let x = lowerTupleUnpacking(c.graph, it, c.owner) result.add p(x, c, s, consumed) - elif it.kind == nkIdentDefs and hasDestructor(it[0].typ) and not isCursor(it[0]): + elif it.kind == nkIdentDefs and hasDestructor(it[0].typ) and not isCursor(it[0], c): for j in 0..<it.len-2: let v = it[j] if v.kind == nkSym: @@ -851,7 +846,7 @@ proc p(n: PNode; c: var Con; s: var Scope; mode: ProcessMode): PNode = result.add v of nkAsgn, nkFastAsgn: if hasDestructor(n[0].typ) and n[1].kind notin {nkProcDef, nkDo, nkLambda} and - not isCursor(n[0]): + not isCursor(n[0], c): # rule (self-assignment-removal): if n[1].kind == nkSym and n[0].kind == nkSym and n[0].sym == n[1].sym: result = newNodeI(nkEmpty, n.info) @@ -988,7 +983,7 @@ proc moveOrCopy(dest, ri: PNode; c: var Con; s: var Scope, isDecl = false): PNod let snk = c.genSink(dest, ri, isDecl) result = newTree(nkStmtList, snk, c.genWasMoved(ri)) elif ri.sym.kind != skParam and ri.sym.owner == c.owner and - isLastRead(ri, c) and canBeMoved(c, dest.typ): + isLastRead(ri, c) and canBeMoved(c, dest.typ) and not isCursor(ri, c): # Rule 3: `=sink`(x, z); wasMoved(z) let snk = c.genSink(dest, ri, isDecl) result = newTree(nkStmtList, snk, c.genWasMoved(ri)) @@ -1048,6 +1043,8 @@ proc injectDestructorCalls*(g: ModuleGraph; owner: PSym; n: PNode): PNode = echoCfg(c.g) echo n + c.cursors = computeCursors(n, g.config) + var scope: Scope let body = p(n, c, scope, normal) @@ -1059,7 +1056,12 @@ proc injectDestructorCalls*(g: ModuleGraph; owner: PSym; n: PNode): PNode = scope.final.add c.genDestroy(params[i]) #if optNimV2 in c.graph.config.globalOptions: # injectDefaultCalls(n, c) - result = processScope(c, scope, body) + result = optimize processScope(c, scope, body) dbg: echo ">---------transformed-to--------->" echo renderTree(result, {renderIds}) + + if g.config.arcToExpand.hasKey(owner.name.s): + echo "--expandArc: ", owner.name.s + echo renderTree(result, {renderIr}) + echo "-- end of expandArc ------------------------" diff --git a/compiler/optimizer.nim b/compiler/optimizer.nim new file mode 100644 index 000000000..5d1139bfd --- /dev/null +++ b/compiler/optimizer.nim @@ -0,0 +1,285 @@ +# +# +# The Nim Compiler +# (c) Copyright 2020 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## Optimizer: +## - elide 'wasMoved(x); destroy(x)' pairs +## - recognize "all paths lead to 'wasMoved(x)'" + +import + ast, renderer, idents, intsets + +from trees import exprStructuralEquivalent + +const + nfMarkForDeletion = nfNone # faster than a lookup table + +type + BasicBlock = object + wasMovedLocs: seq[PNode] + kind: TNodeKind + hasReturn, hasBreak: bool + label: PSym # can be nil + parent: ptr BasicBlock + + Con = object + somethingTodo: bool + inFinally: int + +proc nestedBlock(parent: var BasicBlock; kind: TNodeKind): BasicBlock = + BasicBlock(wasMovedLocs: @[], kind: kind, hasReturn: false, hasBreak: false, + label: nil, parent: addr(parent)) + +proc breakStmt(b: var BasicBlock; n: PNode) = + var it = addr(b) + while it != nil: + it.wasMovedLocs.setLen 0 + it.hasBreak = true + + if n.kind == nkSym: + if it.label == n.sym: break + else: + # unnamed break leaves the block is nkWhileStmt or the like: + if it.kind in {nkWhileStmt, nkBlockStmt, nkBlockExpr}: break + + it = it.parent + +proc returnStmt(b: var BasicBlock) = + b.hasReturn = true + var it = addr(b) + while it != nil: + it.wasMovedLocs.setLen 0 + it = it.parent + +proc mergeBasicBlockInfo(parent: var BasicBlock; this: BasicBlock) {.inline.} = + if this.hasReturn: + parent.wasMovedLocs.setLen 0 + parent.hasReturn = true + +proc wasMovedTarget(matches: var IntSet; branch: seq[PNode]; moveTarget: PNode): bool = + result = false + for i in 0..<branch.len: + if exprStructuralEquivalent(branch[i][1].skipAddr, moveTarget, + strictSymEquality = true): + result = true + matches.incl i + +proc intersect(summary: var seq[PNode]; branch: seq[PNode]) = + # keep all 'wasMoved(x)' calls in summary that are also in 'branch': + var i = 0 + var matches = initIntSet() + while i < summary.len: + if wasMovedTarget(matches, branch, summary[i][1].skipAddr): + inc i + else: + summary.del i + for m in matches: + summary.add branch[m] + + +proc invalidateWasMoved(c: var BasicBlock; x: PNode) = + var i = 0 + while i < c.wasMovedLocs.len: + if exprStructuralEquivalent(c.wasMovedLocs[i][1].skipAddr, x, + strictSymEquality = true): + c.wasMovedLocs.del i + else: + inc i + +proc wasMovedDestroyPair(c: var Con; b: var BasicBlock; d: PNode) = + var i = 0 + while i < b.wasMovedLocs.len: + if exprStructuralEquivalent(b.wasMovedLocs[i][1].skipAddr, d[1].skipAddr, + strictSymEquality = true): + b.wasMovedLocs[i].flags.incl nfMarkForDeletion + c.somethingTodo = true + d.flags.incl nfMarkForDeletion + b.wasMovedLocs.del i + else: + inc i + +proc analyse(c: var Con; b: var BasicBlock; n: PNode) = + case n.kind + of nkCallKinds: + var special = false + var reverse = false + if n[0].kind == nkSym: + let s = n[0].sym + if s.magic == mWasMoved: + b.wasMovedLocs.add n + special = true + elif s.name.s == "=destroy": + if c.inFinally > 0 and (b.hasReturn or b.hasBreak): + discard "cannot optimize away the destructor" + else: + c.wasMovedDestroyPair b, n + special = true + elif s.name.s == "=sink": + reverse = true + + if not special: + if not reverse: + for i in 0 ..< n.len: + analyse(c, b, n[i]) + else: + #[ Test tmatrix.test3: + Prevent this from being elided. We should probably + find a better solution... + + `=sink`(b, - ( + let blitTmp = b; + wasMoved(b); + blitTmp + a) + `=destroy`(b) + + ]# + for i in countdown(n.len-1, 0): + analyse(c, b, n[i]) + if canRaise(n[0]): returnStmt(b) + + of nkSym: + # any usage of the location before destruction implies we + # cannot elide the 'wasMoved(x)': + b.invalidateWasMoved n + + of nkNone..pred(nkSym), succ(nkSym)..nkNilLit, nkTypeSection, nkProcDef, nkConverterDef, + nkMethodDef, nkIteratorDef, nkMacroDef, nkTemplateDef, nkLambda, nkDo, + nkFuncDef, nkConstSection, nkConstDef, nkIncludeStmt, nkImportStmt, + nkExportStmt, nkPragma, nkCommentStmt, nkBreakState, nkTypeOfExpr: + discard "do not follow the construct" + + of nkAsgn, nkFastAsgn: + # reverse order, see remark for `=sink`: + analyse(c, b, n[1]) + analyse(c, b, n[0]) + + of nkIfStmt, nkIfExpr: + let isExhaustive = n[^1].kind in {nkElse, nkElseExpr} + var wasMovedSet: seq[PNode] = @[] + + for i in 0 ..< n.len: + var branch = nestedBlock(b, n[i].kind) + + analyse(c, branch, n[i]) + mergeBasicBlockInfo(b, branch) + if isExhaustive: + if i == 0: + wasMovedSet = move(branch.wasMovedLocs) + else: + wasMovedSet.intersect(branch.wasMovedLocs) + for i in 0..<wasMovedSet.len: + b.wasMovedLocs.add wasMovedSet[i] + + of nkCaseStmt: + let isExhaustive = skipTypes(n[0].typ, + abstractVarRange-{tyTypeDesc}).kind notin {tyFloat..tyFloat128, tyString} or + n[^1].kind == nkElse + + analyse(c, b, n[0]) + + var wasMovedSet: seq[PNode] = @[] + + for i in 1 ..< n.len: + var branch = nestedBlock(b, n[i].kind) + + analyse(c, branch, n[i]) + mergeBasicBlockInfo(b, branch) + if isExhaustive: + if i == 1: + wasMovedSet = move(branch.wasMovedLocs) + else: + wasMovedSet.intersect(branch.wasMovedLocs) + for i in 0..<wasMovedSet.len: + b.wasMovedLocs.add wasMovedSet[i] + + of nkTryStmt: + for i in 0 ..< n.len: + var tryBody = nestedBlock(b, nkTryStmt) + + analyse(c, tryBody, n[i]) + mergeBasicBlockInfo(b, tryBody) + + of nkWhileStmt: + analyse(c, b, n[0]) + var loopBody = nestedBlock(b, nkWhileStmt) + analyse(c, loopBody, n[1]) + mergeBasicBlockInfo(b, loopBody) + + of nkBlockStmt, nkBlockExpr: + var blockBody = nestedBlock(b, n.kind) + if n[0].kind == nkSym: + blockBody.label = n[0].sym + analyse(c, blockBody, n[1]) + mergeBasicBlockInfo(b, blockBody) + + of nkBreakStmt: + breakStmt(b, n[0]) + + of nkReturnStmt, nkRaiseStmt: + for child in n: analyse(c, b, child) + returnStmt(b) + + of nkFinally: + inc c.inFinally + for child in n: analyse(c, b, child) + dec c.inFinally + + else: + for child in n: analyse(c, b, child) + +proc opt(c: Con; n, parent: PNode; parentPos: int) = + template recurse() = + let x = shallowCopy(n) + for i in 0 ..< n.len: + opt(c, n[i], x, i) + parent[parentPos] = x + + case n.kind + of nkCallKinds: + if nfMarkForDeletion in n.flags: + parent[parentPos] = newNodeI(nkEmpty, n.info) + else: + recurse() + + of nkNone..nkNilLit, nkTypeSection, nkProcDef, nkConverterDef, + nkMethodDef, nkIteratorDef, nkMacroDef, nkTemplateDef, nkLambda, nkDo, + nkFuncDef, nkConstSection, nkConstDef, nkIncludeStmt, nkImportStmt, + nkExportStmt, nkPragma, nkCommentStmt, nkBreakState, nkTypeOfExpr: + parent[parentPos] = n + + else: + recurse() + + +proc optimize*(n: PNode): PNode = + # optimize away simple 'wasMoved(x); destroy(x)' pairs. + #[ Unfortunately this optimization is only really safe when no exceptions + are possible, see for example: + + proc main(inp: string; cond: bool) = + if cond: + try: + var s = ["hi", inp & "more"] + for i in 0..4: + use s + consume(s) + wasMoved(s) + finally: + destroy(s) + + Now assume 'use' raises, then we shouldn't do the 'wasMoved(s)' + ]# + var c: Con + var b: BasicBlock + analyse(c, b, n) + if c.somethingTodo: + result = shallowCopy(n) + for i in 0 ..< n.safeLen: + opt(c, n[i], result, i) + else: + result = n diff --git a/compiler/options.nim b/compiler/options.nim index 80881d903..8b73c07fc 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -235,6 +235,7 @@ type options*: TOptions # (+) globalOptions*: TGlobalOptions # (+) macrosToExpand*: StringTableRef + arcToExpand*: StringTableRef m*: MsgConfig evalTemplateCounter*: int evalMacroCounter*: int @@ -410,6 +411,7 @@ proc newConfigRef*(): ConfigRef = options: DefaultOptions, globalOptions: DefaultGlobalOptions, macrosToExpand: newStringTable(modeStyleInsensitive), + arcToExpand: newStringTable(modeStyleInsensitive), m: initMsgConfig(), evalExpr: "", cppDefines: initHashSet[string](), diff --git a/compiler/renderer.nim b/compiler/renderer.nim index 449ee4504..3cd532e15 100644 --- a/compiler/renderer.nim +++ b/compiler/renderer.nim @@ -20,7 +20,8 @@ import type TRenderFlag* = enum renderNone, renderNoBody, renderNoComments, renderDocComments, - renderNoPragmas, renderIds, renderNoProcDefs, renderSyms, renderRunnableExamples + renderNoPragmas, renderIds, renderNoProcDefs, renderSyms, renderRunnableExamples, + renderIr TRenderFlags* = set[TRenderFlag] TRenderTok* = object kind*: TTokType @@ -47,11 +48,20 @@ type pendingNewlineCount: int fid*: FileIndex config*: ConfigRef + mangler: seq[PSym] # We render the source code in a two phases: The first # determines how long the subtree will likely be, the second # phase appends to a buffer that will be the output. +proc disamb(g: var TSrcGen; s: PSym): int = + # we group by 's.name.s' to compute the stable name ID. + result = 0 + for i in 0 ..< g.mangler.len: + if s == g.mangler[i]: return result + if s.name.s == g.mangler[i].name.s: inc result + g.mangler.add s + proc isKeyword*(i: PIdent): bool = if (i.id >= ord(tokKeywordLow) - ord(tkSymbol)) and (i.id <= ord(tokKeywordHigh) - ord(tkSymbol)): @@ -850,7 +860,12 @@ proc gident(g: var TSrcGen, n: PNode) = t = tkSymbol else: t = tkOpr - if n.kind == nkSym and (renderIds in g.flags or sfGenSym in n.sym.flags or n.sym.kind == skTemp): + if renderIr in g.flags and n.kind == nkSym: + let localId = disamb(g, n.sym) + if localId != 0 and n.sym.magic == mNone: + s.add '_' + s.addInt localId + elif n.kind == nkSym and (renderIds in g.flags or sfGenSym in n.sym.flags or n.sym.kind == skTemp): s.add '_' s.addInt n.sym.id when defined(debugMagics): @@ -1022,7 +1037,7 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) = else: put(g, tkSymbol, "(wrong conv)") of nkHiddenCallConv: - if renderIds in g.flags: + if {renderIds, renderIr} * g.flags != {}: accentedName(g, n[0]) put(g, tkParLe, "(") gcomma(g, n, 1) diff --git a/compiler/transf.nim b/compiler/transf.nim index 10a2680ae..be559abb8 100644 --- a/compiler/transf.nim +++ b/compiler/transf.nim @@ -233,7 +233,7 @@ proc hasContinue(n: PNode): bool = proc newLabel(c: PTransf, n: PNode): PSym = result = newSym(skLabel, nil, getCurrOwner(c), n.info) - result.name = getIdent(c.graph.cache, genPrefix & $result.id) + result.name = getIdent(c.graph.cache, genPrefix) proc transformBlock(c: PTransf, n: PNode): PNode = var labl: PSym |