dce.nim 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. #
  2. #
  3. # The Nim Compiler
  4. # (c) Copyright 2021 Andreas Rumpf
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## Dead code elimination (=DCE) for IC.
  10. import std/[intsets, tables]
  11. when defined(nimPreviewSlimSystem):
  12. import std/assertions
  13. import ".." / [ast, options, lineinfos, types]
  14. import packed_ast, ic, bitabs
  15. type
  16. AliveSyms* = seq[IntSet]
  17. AliveContext* = object ## Purpose is to fill the 'alive' field.
  18. stack: seq[(int, TOptions, NodePos)] ## A stack for marking symbols as alive.
  19. decoder: PackedDecoder ## We need a PackedDecoder for module ID address translations.
  20. thisModule: int ## The module we're currently analysing for DCE.
  21. alive: AliveSyms ## The final result of our computation.
  22. options: TOptions
  23. compilerProcs: Table[string, (int, int32)]
  24. proc isExportedToC(c: var AliveContext; g: PackedModuleGraph; symId: int32): bool =
  25. ## "Exported to C" procs are special (these are marked with '.exportc') because these
  26. ## must not be optimized away!
  27. let symPtr = unsafeAddr g[c.thisModule].fromDisk.syms[symId]
  28. let flags = symPtr.flags
  29. # due to a bug/limitation in the lambda lifting, unused inner procs
  30. # are not transformed correctly; issue (#411). However, the whole purpose here
  31. # is to eliminate unused procs. So there is no special logic required for this case.
  32. if sfCompileTime notin flags:
  33. if ({sfExportc, sfCompilerProc} * flags != {}) or
  34. (symPtr.kind == skMethod):
  35. result = true
  36. else:
  37. result = false
  38. # XXX: This used to be a condition to:
  39. # (sfExportc in prc.flags and lfExportLib in prc.loc.flags) or
  40. if sfCompilerProc in flags:
  41. c.compilerProcs[g[c.thisModule].fromDisk.strings[symPtr.name]] = (c.thisModule, symId)
  42. else:
  43. result = false
  44. template isNotGeneric(n: NodePos): bool = ithSon(tree, n, genericParamsPos).kind == nkEmpty
  45. proc followLater(c: var AliveContext; g: PackedModuleGraph; module: int; item: int32) =
  46. ## Marks a symbol 'item' as used and later in 'followNow' the symbol's body will
  47. ## be analysed.
  48. if not c.alive[module].containsOrIncl(item):
  49. var body = g[module].fromDisk.syms[item].ast
  50. if body != emptyNodeId:
  51. let opt = g[module].fromDisk.syms[item].options
  52. if g[module].fromDisk.syms[item].kind in routineKinds:
  53. body = NodeId ithSon(g[module].fromDisk.bodies, NodePos body, bodyPos)
  54. c.stack.add((module, opt, NodePos(body)))
  55. when false:
  56. let nid = g[module].fromDisk.syms[item].name
  57. if nid != LitId(0):
  58. let name = g[module].fromDisk.strings[nid]
  59. if name in ["nimFrame", "callDepthLimitReached"]:
  60. echo "I was called! ", name, " body exists: ", body != emptyNodeId, " ", module, " ", item
  61. proc requestCompilerProc(c: var AliveContext; g: PackedModuleGraph; name: string) =
  62. let (module, item) = c.compilerProcs[name]
  63. followLater(c, g, module, item)
  64. proc loadTypeKind(t: PackedItemId; c: AliveContext; g: PackedModuleGraph; toSkip: set[TTypeKind]): TTypeKind =
  65. template kind(t: ItemId): TTypeKind = g[t.module].fromDisk.types[t.item].kind
  66. var t2 = translateId(t, g, c.thisModule, c.decoder.config)
  67. result = t2.kind
  68. while result in toSkip:
  69. t2 = translateId(g[t2.module].fromDisk.types[t2.item].types[^1], g, t2.module, c.decoder.config)
  70. result = t2.kind
  71. proc rangeCheckAnalysis(c: var AliveContext; g: PackedModuleGraph; tree: PackedTree; n: NodePos) =
  72. ## Replicates the logic of `ccgexprs.genRangeChck`.
  73. ## XXX Refactor so that the duplicated logic is avoided. However, for now it's not clear
  74. ## the approach has enough merit.
  75. var dest = loadTypeKind(n.typ, c, g, abstractVar)
  76. if optRangeCheck notin c.options or dest in {tyUInt..tyUInt64}:
  77. discard "no need to generate a check because it was disabled"
  78. else:
  79. let n0t = loadTypeKind(n.firstSon.typ, c, g, {})
  80. if n0t in {tyUInt, tyUInt64}:
  81. c.requestCompilerProc(g, "raiseRangeErrorNoArgs")
  82. else:
  83. let raiser =
  84. case loadTypeKind(n.typ, c, g, abstractVarRange)
  85. of tyUInt..tyUInt64, tyChar: "raiseRangeErrorU"
  86. of tyFloat..tyFloat128: "raiseRangeErrorF"
  87. else: "raiseRangeErrorI"
  88. c.requestCompilerProc(g, raiser)
  89. proc aliveCode(c: var AliveContext; g: PackedModuleGraph; tree: PackedTree; n: NodePos) =
  90. ## Marks the symbols we encounter when we traverse the AST at `tree[n]` as alive, unless
  91. ## it is purely in a declarative context (type section etc.).
  92. case n.kind
  93. of nkNone..pred(nkSym), succ(nkSym)..nkNilLit:
  94. discard "ignore non-sym atoms"
  95. of nkSym:
  96. # This symbol is alive and everything its body references.
  97. followLater(c, g, c.thisModule, n.operand)
  98. of nkModuleRef:
  99. let (n1, n2) = sons2(tree, n)
  100. assert n1.kind == nkInt32Lit
  101. assert n2.kind == nkInt32Lit
  102. let m = n1.litId
  103. let item = n2.operand
  104. let otherModule = toFileIndexCached(c.decoder, g, c.thisModule, m).int
  105. followLater(c, g, otherModule, item)
  106. of nkMacroDef, nkTemplateDef, nkTypeSection, nkTypeOfExpr,
  107. nkCommentStmt, nkIncludeStmt,
  108. nkImportStmt, nkImportExceptStmt, nkExportStmt, nkExportExceptStmt,
  109. nkFromStmt, nkStaticStmt:
  110. discard
  111. of nkVarSection, nkLetSection, nkConstSection:
  112. # XXX ignore the defining local variable name?
  113. for son in sonsReadonly(tree, n):
  114. aliveCode(c, g, tree, son)
  115. of nkChckRangeF, nkChckRange64, nkChckRange:
  116. rangeCheckAnalysis(c, g, tree, n)
  117. of nkProcDef, nkConverterDef, nkMethodDef, nkFuncDef, nkIteratorDef:
  118. if n.firstSon.kind == nkSym and isNotGeneric(n):
  119. let item = n.firstSon.operand
  120. if isExportedToC(c, g, item):
  121. # This symbol is alive and everything its body references.
  122. followLater(c, g, c.thisModule, item)
  123. else:
  124. for son in sonsReadonly(tree, n):
  125. aliveCode(c, g, tree, son)
  126. proc followNow(c: var AliveContext; g: PackedModuleGraph) =
  127. ## Mark all entries in the stack. Marking can add more entries
  128. ## to the stack but eventually we have looked at every alive symbol.
  129. while c.stack.len > 0:
  130. let (modId, opt, ast) = c.stack.pop()
  131. c.thisModule = modId
  132. c.options = opt
  133. aliveCode(c, g, g[modId].fromDisk.bodies, ast)
  134. proc computeAliveSyms*(g: PackedModuleGraph; conf: ConfigRef): AliveSyms =
  135. ## Entry point for our DCE algorithm.
  136. var c = AliveContext(stack: @[], decoder: PackedDecoder(config: conf),
  137. thisModule: -1, alive: newSeq[IntSet](g.len),
  138. options: conf.options)
  139. for i in countdown(high(g), 0):
  140. if g[i].status != undefined:
  141. c.thisModule = i
  142. for p in allNodes(g[i].fromDisk.topLevel):
  143. aliveCode(c, g, g[i].fromDisk.topLevel, p)
  144. followNow(c, g)
  145. result = move(c.alive)
  146. proc isAlive*(a: AliveSyms; module: int, item: int32): bool =
  147. ## Backends use this to query if a symbol is `alive` which means
  148. ## we need to produce (C/C++/etc) code for it.
  149. result = a[module].contains(item)