summary refs log tree commit diff stats
path: root/compiler/ic/dce.nim
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/ic/dce.nim')
-rw-r--r--compiler/ic/dce.nim169
1 files changed, 169 insertions, 0 deletions
diff --git a/compiler/ic/dce.nim b/compiler/ic/dce.nim
new file mode 100644
index 000000000..6eb36431e
--- /dev/null
+++ b/compiler/ic/dce.nim
@@ -0,0 +1,169 @@
+#
+#
+#           The Nim Compiler
+#        (c) Copyright 2021 Andreas Rumpf
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+## Dead code elimination (=DCE) for IC.
+
+import std/[intsets, tables]
+
+when defined(nimPreviewSlimSystem):
+  import std/assertions
+
+import ".." / [ast, options, lineinfos, types]
+
+import packed_ast, ic, bitabs
+
+type
+  AliveSyms* = seq[IntSet]
+  AliveContext* = object ## Purpose is to fill the 'alive' field.
+    stack: seq[(int, TOptions, NodePos)] ## A stack for marking symbols as alive.
+    decoder: PackedDecoder ## We need a PackedDecoder for module ID address translations.
+    thisModule: int  ## The module we're currently analysing for DCE.
+    alive: AliveSyms ## The final result of our computation.
+    options: TOptions
+    compilerProcs: Table[string, (int, int32)]
+
+proc isExportedToC(c: var AliveContext; g: PackedModuleGraph; symId: int32): bool =
+  ## "Exported to C" procs are special (these are marked with '.exportc') because these
+  ## must not be optimized away!
+  let symPtr = unsafeAddr g[c.thisModule].fromDisk.syms[symId]
+  let flags = symPtr.flags
+  # due to a bug/limitation in the lambda lifting, unused inner procs
+  # are not transformed correctly; issue (#411). However, the whole purpose here
+  # is to eliminate unused procs. So there is no special logic required for this case.
+  if sfCompileTime notin flags:
+    if ({sfExportc, sfCompilerProc} * flags != {}) or
+        (symPtr.kind == skMethod):
+      result = true
+    else:
+      result = false
+      # XXX: This used to be a condition to:
+      #  (sfExportc in prc.flags and lfExportLib in prc.loc.flags) or
+    if sfCompilerProc in flags:
+      c.compilerProcs[g[c.thisModule].fromDisk.strings[symPtr.name]] = (c.thisModule, symId)
+  else:
+    result = false
+
+template isNotGeneric(n: NodePos): bool = ithSon(tree, n, genericParamsPos).kind == nkEmpty
+
+proc followLater(c: var AliveContext; g: PackedModuleGraph; module: int; item: int32) =
+  ## Marks a symbol 'item' as used and later in 'followNow' the symbol's body will
+  ## be analysed.
+  if not c.alive[module].containsOrIncl(item):
+    var body = g[module].fromDisk.syms[item].ast
+    if body != emptyNodeId:
+      let opt = g[module].fromDisk.syms[item].options
+      if g[module].fromDisk.syms[item].kind in routineKinds:
+        body = NodeId ithSon(g[module].fromDisk.bodies, NodePos body, bodyPos)
+      c.stack.add((module, opt, NodePos(body)))
+
+    when false:
+      let nid = g[module].fromDisk.syms[item].name
+      if nid != LitId(0):
+        let name = g[module].fromDisk.strings[nid]
+        if name in ["nimFrame", "callDepthLimitReached"]:
+          echo "I was called! ", name, " body exists: ", body != emptyNodeId, " ", module, " ", item
+
+proc requestCompilerProc(c: var AliveContext; g: PackedModuleGraph; name: string) =
+  let (module, item) = c.compilerProcs[name]
+  followLater(c, g, module, item)
+
+proc loadTypeKind(t: PackedItemId; c: AliveContext; g: PackedModuleGraph; toSkip: set[TTypeKind]): TTypeKind =
+  template kind(t: ItemId): TTypeKind = g[t.module].fromDisk.types[t.item].kind
+
+  var t2 = translateId(t, g, c.thisModule, c.decoder.config)
+  result = t2.kind
+  while result in toSkip:
+    t2 = translateId(g[t2.module].fromDisk.types[t2.item].types[^1], g, t2.module, c.decoder.config)
+    result = t2.kind
+
+proc rangeCheckAnalysis(c: var AliveContext; g: PackedModuleGraph; tree: PackedTree; n: NodePos) =
+  ## Replicates the logic of `ccgexprs.genRangeChck`.
+  ## XXX Refactor so that the duplicated logic is avoided. However, for now it's not clear
+  ## the approach has enough merit.
+  var dest = loadTypeKind(n.typ, c, g, abstractVar)
+  if optRangeCheck notin c.options or dest in {tyUInt..tyUInt64}:
+    discard "no need to generate a check because it was disabled"
+  else:
+    let n0t = loadTypeKind(n.firstSon.typ, c, g, {})
+    if n0t in {tyUInt, tyUInt64}:
+      c.requestCompilerProc(g, "raiseRangeErrorNoArgs")
+    else:
+      let raiser =
+        case loadTypeKind(n.typ, c, g, abstractVarRange)
+        of tyUInt..tyUInt64, tyChar: "raiseRangeErrorU"
+        of tyFloat..tyFloat128: "raiseRangeErrorF"
+        else: "raiseRangeErrorI"
+      c.requestCompilerProc(g, raiser)
+
+proc aliveCode(c: var AliveContext; g: PackedModuleGraph; tree: PackedTree; n: NodePos) =
+  ## Marks the symbols we encounter when we traverse the AST at `tree[n]` as alive, unless
+  ## it is purely in a declarative context (type section etc.).
+  case n.kind
+  of nkNone..pred(nkSym), succ(nkSym)..nkNilLit:
+    discard "ignore non-sym atoms"
+  of nkSym:
+    # This symbol is alive and everything its body references.
+    followLater(c, g, c.thisModule, tree[n].soperand)
+  of nkModuleRef:
+    let (n1, n2) = sons2(tree, n)
+    assert n1.kind == nkNone
+    assert n2.kind == nkNone
+    let m = n1.litId
+    let item = tree[n2].soperand
+    let otherModule = toFileIndexCached(c.decoder, g, c.thisModule, m).int
+    followLater(c, g, otherModule, item)
+  of nkMacroDef, nkTemplateDef, nkTypeSection, nkTypeOfExpr,
+     nkCommentStmt, nkIncludeStmt,
+     nkImportStmt, nkImportExceptStmt, nkExportStmt, nkExportExceptStmt,
+     nkFromStmt, nkStaticStmt:
+    discard
+  of nkVarSection, nkLetSection, nkConstSection:
+    # XXX ignore the defining local variable name?
+    for son in sonsReadonly(tree, n):
+      aliveCode(c, g, tree, son)
+  of nkChckRangeF, nkChckRange64, nkChckRange:
+    rangeCheckAnalysis(c, g, tree, n)
+  of nkProcDef, nkConverterDef, nkMethodDef, nkFuncDef, nkIteratorDef:
+    if n.firstSon.kind == nkSym and isNotGeneric(n):
+      let item = tree[n.firstSon].soperand
+      if isExportedToC(c, g, item):
+        # This symbol is alive and everything its body references.
+        followLater(c, g, c.thisModule, item)
+  else:
+    for son in sonsReadonly(tree, n):
+      aliveCode(c, g, tree, son)
+
+proc followNow(c: var AliveContext; g: PackedModuleGraph) =
+  ## Mark all entries in the stack. Marking can add more entries
+  ## to the stack but eventually we have looked at every alive symbol.
+  while c.stack.len > 0:
+    let (modId, opt, ast) = c.stack.pop()
+    c.thisModule = modId
+    c.options = opt
+    aliveCode(c, g, g[modId].fromDisk.bodies, ast)
+
+proc computeAliveSyms*(g: PackedModuleGraph; conf: ConfigRef): AliveSyms =
+  ## Entry point for our DCE algorithm.
+  var c = AliveContext(stack: @[], decoder: PackedDecoder(config: conf),
+                       thisModule: -1, alive: newSeq[IntSet](g.len),
+                       options: conf.options)
+  for i in countdown(len(g)-1, 0):
+    if g[i].status != undefined:
+      c.thisModule = i
+      for p in allNodes(g[i].fromDisk.topLevel):
+        aliveCode(c, g, g[i].fromDisk.topLevel, p)
+
+  followNow(c, g)
+  result = move(c.alive)
+
+proc isAlive*(a: AliveSyms; module: int, item: int32): bool =
+  ## Backends use this to query if a symbol is `alive` which means
+  ## we need to produce (C/C++/etc) code for it.
+  result = a[module].contains(item)
+