123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436 |
- import
- ast, idents, renderer,
- msgs, modulegraphs, syntaxes, options, modulepaths,
- lineinfos
- import std/[algorithm, strutils, intsets]
- when defined(nimPreviewSlimSystem):
- import std/assertions
- when defined(nimDebugReorder):
- import std/tables
- type
- DepN = ref object
- pnode: PNode
- id, idx, lowLink: int
- onStack: bool
- kids: seq[DepN]
- hAQ, hIS, hB, hCmd: int
- when defined(nimDebugReorder):
- expls: seq[string]
- DepG = seq[DepN]
- when defined(nimDebugReorder):
- var idNames = newTable[int, string]()
- proc newDepN(id: int, pnode: PNode): DepN =
- result = DepN(id: id, pnode: pnode, idx: -1,
- lowLink: -1, onStack: false,
- kids: @[], hAQ: -1, hIS: -1,
- hB: -1, hCmd: -1
- )
- when defined(nimDebugReorder):
- result.expls = @[]
- proc accQuoted(cache: IdentCache; n: PNode): PIdent =
- var id = ""
- for i in 0..<n.len:
- let ident = n[i].getPIdent
- if ident != nil: id.add(ident.s)
- result = getIdent(cache, id)
- proc addDecl(cache: IdentCache; n: PNode; declares: var IntSet) =
- case n.kind
- of nkPostfix: addDecl(cache, n[1], declares)
- of nkPragmaExpr: addDecl(cache, n[0], declares)
- of nkIdent:
- declares.incl n.ident.id
- when defined(nimDebugReorder):
- idNames[n.ident.id] = n.ident.s
- of nkSym:
- declares.incl n.sym.name.id
- when defined(nimDebugReorder):
- idNames[n.sym.name.id] = n.sym.name.s
- of nkAccQuoted:
- let a = accQuoted(cache, n)
- declares.incl a.id
- when defined(nimDebugReorder):
- idNames[a.id] = a.s
- of nkEnumFieldDef:
- addDecl(cache, n[0], declares)
- else: discard
- proc computeDeps(cache: IdentCache; n: PNode, declares, uses: var IntSet; topLevel: bool) =
- template deps(n) = computeDeps(cache, n, declares, uses, false)
- template decl(n) =
- if topLevel: addDecl(cache, n, declares)
- case n.kind
- of procDefs, nkMacroDef, nkTemplateDef:
- decl(n[0])
- for i in 1..bodyPos: deps(n[i])
- of nkLetSection, nkVarSection, nkUsingStmt:
- for a in n:
- if a.kind in {nkIdentDefs, nkVarTuple}:
- for j in 0..<a.len-2: decl(a[j])
- for j in a.len-2..<a.len: deps(a[j])
- of nkConstSection, nkTypeSection:
- for a in n:
- if a.len >= 3:
- decl(a[0])
- for i in 1..<a.len:
- if a[i].kind == nkEnumTy:
- # declare enum members
- for b in a[i]:
- decl(b)
- else:
- deps(a[i])
- of nkIdentDefs:
- for i in 1..<n.len: # avoid members identifiers in object definition
- deps(n[i])
- of nkIdent: uses.incl n.ident.id
- of nkSym: uses.incl n.sym.name.id
- of nkAccQuoted: uses.incl accQuoted(cache, n).id
- of nkOpenSymChoice, nkClosedSymChoice:
- uses.incl n[0].sym.name.id
- of nkStmtList, nkStmtListExpr, nkWhenStmt, nkElifBranch, nkElse, nkStaticStmt:
- for i in 0..<n.len: computeDeps(cache, n[i], declares, uses, topLevel)
- of nkPragma:
- let a = n[0]
- if a.kind == nkExprColonExpr and a[0].kind == nkIdent and a[0].ident.s == "pragma":
- # user defined pragma
- decl(a[1])
- for i in 1..<n.safeLen: deps(n[i])
- else:
- for i in 0..<n.safeLen: deps(n[i])
- of nkMixinStmt, nkBindStmt: discard
- else:
- # XXX: for callables, this technically adds the return type dep before args
- for i in 0..<n.safeLen: deps(n[i])
- proc hasIncludes(n: PNode): bool =
- result = false
- for a in n:
- if a.kind == nkIncludeStmt:
- return true
- proc includeModule*(graph: ModuleGraph; s: PSym, fileIdx: FileIndex): PNode =
- result = syntaxes.parseFile(fileIdx, graph.cache, graph.config)
- graph.addDep(s, fileIdx)
- graph.addIncludeDep(FileIndex s.position, fileIdx)
- proc expandIncludes(graph: ModuleGraph, module: PSym, n: PNode,
- modulePath: string, includedFiles: var IntSet): PNode =
- # Parses includes and injects them in the current tree
- if not n.hasIncludes:
- return n
- result = newNodeI(nkStmtList, n.info)
- for a in n:
- if a.kind == nkIncludeStmt:
- for i in 0..<a.len:
- var f = checkModuleName(graph.config, a[i])
- if f != InvalidFileIdx:
- if containsOrIncl(includedFiles, f.int):
- localError(graph.config, a.info, "recursive dependency: '$1'" %
- toMsgFilename(graph.config, f))
- else:
- let nn = includeModule(graph, module, f)
- let nnn = expandIncludes(graph, module, nn, modulePath,
- includedFiles)
- excl(includedFiles, f.int)
- for b in nnn:
- result.add b
- else:
- result.add a
- proc splitSections(n: PNode): PNode =
- # Split typeSections and ConstSections into
- # sections that contain only one definition
- assert n.kind == nkStmtList
- result = newNodeI(nkStmtList, n.info)
- for a in n:
- if a.kind in {nkTypeSection, nkConstSection} and a.len > 1:
- for b in a:
- var s = newNode(a.kind)
- s.info = b.info
- s.add b
- result.add s
- else:
- result.add a
- proc haveSameKind(dns: seq[DepN]): bool =
- # Check if all the nodes in a strongly connected
- # component have the same kind
- result = true
- let kind = dns[0].pnode.kind
- for dn in dns:
- if dn.pnode.kind != kind:
- return false
- proc mergeSections(conf: ConfigRef; comps: seq[seq[DepN]], res: PNode) =
- # Merges typeSections and ConstSections when they form
- # a strong component (ex: circular type definition)
- for c in comps:
- assert c.len > 0
- if c.len == 1:
- res.add c[0].pnode
- else:
- let fstn = c[0].pnode
- let kind = fstn.kind
- # always return to the original order when we got circular dependencies
- let cs = c.sortedByIt(it.id)
- if kind in {nkTypeSection, nkConstSection} and haveSameKind(cs):
- # Circular dependency between type or const sections, we just
- # need to merge them
- var sn = newNode(kind)
- for dn in cs:
- sn.add dn.pnode[0]
- res.add sn
- else:
- # Problematic circular dependency, we arrange the nodes into
- # their original relative order and make sure to re-merge
- # consecutive type and const sections
- var wmsg = "Circular dependency detected. `codeReordering` pragma may not be able to" &
- " reorder some nodes properly"
- when defined(nimDebugReorder):
- wmsg &= ":\n"
- for i in 0..<cs.len-1:
- for j in i..<cs.len:
- for ci in 0..<cs[i].kids.len:
- if cs[i].kids[ci].id == cs[j].id:
- wmsg &= "line " & $cs[i].pnode.info.line &
- " depends on line " & $cs[j].pnode.info.line &
- ": " & cs[i].expls[ci] & "\n"
- for j in 0..<cs.len-1:
- for ci in 0..<cs[^1].kids.len:
- if cs[^1].kids[ci].id == cs[j].id:
- wmsg &= "line " & $cs[^1].pnode.info.line &
- " depends on line " & $cs[j].pnode.info.line &
- ": " & cs[^1].expls[ci] & "\n"
- message(conf, cs[0].pnode.info, warnUser, wmsg)
- var i = 0
- while i < cs.len:
- if cs[i].pnode.kind in {nkTypeSection, nkConstSection}:
- let ckind = cs[i].pnode.kind
- var sn = newNode(ckind)
- sn.add cs[i].pnode[0]
- inc i
- while i < cs.len and cs[i].pnode.kind == ckind:
- sn.add cs[i].pnode[0]
- inc i
- res.add sn
- else:
- res.add cs[i].pnode
- inc i
- proc hasImportStmt(n: PNode): bool =
- # Checks if the node is an import statement or
- # i it contains one
- case n.kind
- of nkImportStmt, nkFromStmt, nkImportExceptStmt:
- result = true
- of nkStmtList, nkStmtListExpr, nkWhenStmt, nkElifBranch, nkElse, nkStaticStmt:
- result = false
- for a in n:
- if a.hasImportStmt:
- return true
- else:
- result = false
- proc hasImportStmt(n: DepN): bool =
- if n.hIS < 0:
- n.hIS = ord(n.pnode.hasImportStmt)
- result = bool(n.hIS)
- proc hasCommand(n: PNode): bool =
- # Checks if the node is a command or a call
- # or if it contains one
- case n.kind
- of nkCommand, nkCall:
- result = true
- of nkStmtList, nkStmtListExpr, nkWhenStmt, nkElifBranch, nkElse,
- nkStaticStmt, nkLetSection, nkConstSection, nkVarSection,
- nkIdentDefs:
- result = false
- for a in n:
- if a.hasCommand:
- return true
- else:
- return false
- proc hasCommand(n: DepN): bool =
- if n.hCmd < 0:
- n.hCmd = ord(n.pnode.hasCommand)
- result = bool(n.hCmd)
- proc hasAccQuoted(n: PNode): bool =
- result = false
- if n.kind == nkAccQuoted:
- return true
- for a in n:
- if hasAccQuoted(a):
- return true
- const extendedProcDefs = procDefs + {nkMacroDef, nkTemplateDef}
- proc hasAccQuotedDef(n: PNode): bool =
- # Checks if the node is a function, macro, template ...
- # with a quoted name or if it contains one
- case n.kind
- of extendedProcDefs:
- result = n[0].hasAccQuoted
- of nkStmtList, nkStmtListExpr, nkWhenStmt, nkElifBranch, nkElse, nkStaticStmt:
- result = false
- for a in n:
- if hasAccQuotedDef(a):
- return true
- else:
- result = false
- proc hasAccQuotedDef(n: DepN): bool =
- if n.hAQ < 0:
- n.hAQ = ord(n.pnode.hasAccQuotedDef)
- result = bool(n.hAQ)
- proc hasBody(n: PNode): bool =
- # Checks if the node is a function, macro, template ...
- # with a body or if it contains one
- case n.kind
- of nkCommand, nkCall:
- result = true
- of extendedProcDefs:
- result = n[^1].kind == nkStmtList
- of nkStmtList, nkStmtListExpr, nkWhenStmt, nkElifBranch, nkElse, nkStaticStmt:
- result = false
- for a in n:
- if a.hasBody:
- return true
- else:
- result = false
- proc hasBody(n: DepN): bool =
- if n.hB < 0:
- n.hB = ord(n.pnode.hasBody)
- result = bool(n.hB)
- proc intersects(s1, s2: IntSet): bool =
- result = false
- for a in s1:
- if s2.contains(a):
- return true
- proc hasPushOrPopPragma(n: DepN): bool =
- # Checks if the tree node has some pragmas that do not
- # play well with reordering, like the push/pop pragma
- # no crossing for push/pop barrier
- let a = n.pnode
- result = a.kind == nkPragma and a[0].kind == nkIdent and
- (a[0].ident.s == "push" or a[0].ident.s == "pop")
- proc buildGraph(n: PNode, deps: seq[(IntSet, IntSet)]): DepG =
- # Build a dependency graph
- result = newSeqOfCap[DepN](deps.len)
- for i in 0..<deps.len:
- result.add newDepN(i, n[i])
- for i in 0..<deps.len:
- var ni = result[i]
- let uses = deps[i][1]
- let niHasBody = ni.hasBody
- let niHasCmd = ni.hasCommand
- for j in 0..<deps.len:
- if i == j: continue
- var nj = result[j]
- let declares = deps[j][0]
- if j < i and nj.hasCommand and niHasCmd:
- # Preserve order for commands and calls
- ni.kids.add nj
- when defined(nimDebugReorder):
- ni.expls.add "both have commands and one comes after the other"
- elif j < i and nj.hasImportStmt:
- # Every node that comes after an import statement must
- # depend on that import
- ni.kids.add nj
- when defined(nimDebugReorder):
- ni.expls.add "parent is, or contains, an import statement and child comes after it"
- elif j < i and niHasBody and nj.hasAccQuotedDef:
- # Every function, macro, template... with a body depends
- # on precedent function declarations that have quoted names.
- # That's because it is hard to detect the use of functions
- # like "[]=", "[]", "or" ... in their bodies.
- ni.kids.add nj
- when defined(nimDebugReorder):
- ni.expls.add "one declares a quoted identifier and the other has a body and comes after it"
- elif j < i and niHasBody and not nj.hasBody and
- intersects(deps[i][0], declares):
- # Keep function declaration before function definition
- ni.kids.add nj
- when defined(nimDebugReorder):
- for dep in deps[i][0]:
- if dep in declares:
- ni.expls.add "one declares \"" & idNames[dep] & "\" and the other defines it"
- elif hasPushOrPopPragma(nj):
- # Every node that comes after a push/pop pragma must
- # depend on it; vice versa
- if j < i:
- ni.kids.add nj
- else:
- nj.kids.add ni
- else:
- for d in declares:
- if uses.contains(d):
- ni.kids.add nj
- when defined(nimDebugReorder):
- ni.expls.add "one declares \"" & idNames[d] & "\" and the other uses it"
- proc strongConnect(v: var DepN, idx: var int, s: var seq[DepN],
- res: var seq[seq[DepN]]) =
- # Recursive part of trajan's algorithm
- v.idx = idx
- v.lowLink = idx
- inc idx
- s.add v
- v.onStack = true
- for w in v.kids.mitems:
- if w.idx < 0:
- strongConnect(w, idx, s, res)
- v.lowLink = min(v.lowLink, w.lowLink)
- elif w.onStack:
- v.lowLink = min(v.lowLink, w.idx)
- if v.lowLink == v.idx:
- var comp = newSeq[DepN]()
- while true:
- var w = s.pop
- w.onStack = false
- comp.add w
- if w.id == v.id: break
- res.add comp
- proc getStrongComponents(g: var DepG): seq[seq[DepN]] =
- ## Tarjan's algorithm. Performs a topological sort
- ## and detects strongly connected components.
- result = @[]
- var s: seq[DepN] = @[]
- var idx = 0
- for v in g.mitems:
- if v.idx < 0:
- strongConnect(v, idx, s, result)
- proc reorder*(graph: ModuleGraph, n: PNode, module: PSym): PNode =
- var includedFiles = initIntSet()
- let mpath = toFullPath(graph.config, module.fileIdx)
- let n = expandIncludes(graph, module, n, mpath,
- includedFiles).splitSections
- result = newNodeI(nkStmtList, n.info)
- var deps = newSeq[(IntSet, IntSet)](n.len)
- for i in 0..<n.len:
- deps[i][0] = initIntSet()
- deps[i][1] = initIntSet()
- computeDeps(graph.cache, n[i], deps[i][0], deps[i][1], true)
- var g = buildGraph(n, deps)
- let comps = getStrongComponents(g)
- mergeSections(graph.config, comps, result)
|