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
10## New entry point into our C/C++ code generator. Ideally
11## somebody would rewrite the old backend (which is 8000 lines of crufty Nim code)
12## to work on packed trees directly and produce the C code as an AST which can
13## then be rendered to text in a very simple manner. Unfortunately nobody wrote
14## this code. So instead we wrap the existing cgen.nim and its friends so that
15## we call directly into the existing code generation logic but avoiding the
16## naive, outdated `passes` design. Thus you will see some
17## `useAliveDataFromDce in flags` checks in the old code -- the old code is
18## also doing cross-module dependency tracking and DCE that we don't need
19## anymore. DCE is now done as prepass over the entire packed module graph.
20
21import std/packedsets, algorithm, tables
22
23import ".."/[ast, options, lineinfos, modulegraphs, cgendata, cgen,
24  pathutils, extccomp, msgs]
25
26import packed_ast, ic, dce, rodfiles
27
28proc unpackTree(g: ModuleGraph; thisModule: int;
29                tree: PackedTree; n: NodePos): PNode =
30  var decoder = initPackedDecoder(g.config, g.cache)
31  result = loadNodes(decoder, g.packed, thisModule, tree, n)
32
33proc setupBackendModule(g: ModuleGraph; m: var LoadedModule) =
34  if g.backend == nil:
35    g.backend = cgendata.newModuleList(g)
36  assert g.backend != nil
37  var bmod = cgen.newModule(BModuleList(g.backend), m.module, g.config)
38  bmod.idgen = idgenFromLoadedModule(m)
39
40proc generateCodeForModule(g: ModuleGraph; m: var LoadedModule; alive: var AliveSyms) =
41  var bmod = BModuleList(g.backend).modules[m.module.position]
42  assert bmod != nil
43  bmod.flags.incl useAliveDataFromDce
44  bmod.alive = move alive[m.module.position]
45
46  for p in allNodes(m.fromDisk.topLevel):
47    let n = unpackTree(g, m.module.position, m.fromDisk.topLevel, p)
48    cgen.genTopLevelStmt(bmod, n)
49
50  finalCodegenActions(g, bmod, newNodeI(nkStmtList, m.module.info))
51  m.fromDisk.backendFlags = cgen.whichInitProcs(bmod)
52
53proc replayTypeInfo(g: ModuleGraph; m: var LoadedModule; origin: FileIndex) =
54  for x in mitems(m.fromDisk.emittedTypeInfo):
55    #echo "found type ", x, " for file ", int(origin)
56    g.emittedTypeInfo[x] = origin
57
58proc addFileToLink(config: ConfigRef; m: PSym) =
59  let filename = AbsoluteFile toFullPath(config, m.position.FileIndex)
60  let ext =
61      if config.backend == backendCpp: ".nim.cpp"
62      elif config.backend == backendObjc: ".nim.m"
63      else: ".nim.c"
64  let cfile = changeFileExt(completeCfilePath(config, withPackageName(config, filename)), ext)
65  let objFile = completeCfilePath(config, toObjFile(config, cfile))
66  if fileExists(objFile):
67    var cf = Cfile(nimname: m.name.s, cname: cfile,
68                   obj: objFile,
69                   flags: {CfileFlag.Cached})
70    addFileToCompile(config, cf)
71
72when defined(debugDce):
73  import os, std/packedsets
74
75proc storeAliveSymsImpl(asymFile: AbsoluteFile; s: seq[int32]) =
76  var f = rodfiles.create(asymFile.string)
77  f.storeHeader()
78  f.storeSection aliveSymsSection
79  f.storeSeq(s)
80  close f
81
82template prepare {.dirty.} =
83  let asymFile = toRodFile(config, AbsoluteFile toFullPath(config, position.FileIndex), ".alivesyms")
84  var s = newSeqOfCap[int32](alive[position].len)
85  for a in items(alive[position]): s.add int32(a)
86  sort(s)
87
88proc storeAliveSyms(config: ConfigRef; position: int; alive: AliveSyms) =
89  prepare()
90  storeAliveSymsImpl(asymFile, s)
91
92proc aliveSymsChanged(config: ConfigRef; position: int; alive: AliveSyms): bool =
93  prepare()
94  var f2 = rodfiles.open(asymFile.string)
95  f2.loadHeader()
96  f2.loadSection aliveSymsSection
97  var oldData: seq[int32]
98  f2.loadSeq(oldData)
99  f2.close
100  if f2.err == ok and oldData == s:
101    result = false
102  else:
103    when defined(debugDce):
104      let oldAsSet = toPackedSet[int32](oldData)
105      let newAsSet = toPackedSet[int32](s)
106      echo "set of live symbols changed ", asymFile.changeFileExt("rod"), " ", position, " ", f2.err
107      echo "in old but not in new ", oldAsSet.difference(newAsSet), " number of entries in old ", oldAsSet.len
108      echo "in new but not in old ", newAsSet.difference(oldAsSet), " number of entries in new ", newAsSet.len
109      #if execShellCmd(getAppFilename() & " rod " & quoteShell(asymFile.changeFileExt("rod"))) != 0:
110      #  echo "command failed"
111    result = true
112    storeAliveSymsImpl(asymFile, s)
113
114proc genPackedModule(g: ModuleGraph, i: int; alive: var AliveSyms) =
115  # case statement here to enforce exhaustive checks.
116  case g.packed[i].status
117  of undefined:
118    discard "nothing to do"
119  of loading, stored:
120    assert false
121  of storing, outdated:
122    storeAliveSyms(g.config, g.packed[i].module.position, alive)
123    generateCodeForModule(g, g.packed[i], alive)
124    closeRodFile(g, g.packed[i].module)
125  of loaded:
126    if g.packed[i].loadedButAliveSetChanged:
127      generateCodeForModule(g, g.packed[i], alive)
128    else:
129      addFileToLink(g.config, g.packed[i].module)
130      replayTypeInfo(g, g.packed[i], FileIndex(i))
131
132      if g.backend == nil:
133        g.backend = cgendata.newModuleList(g)
134      registerInitProcs(BModuleList(g.backend), g.packed[i].module, g.packed[i].fromDisk.backendFlags)
135
136proc generateCode*(g: ModuleGraph) =
137  ## The single entry point, generate C(++) code for the entire
138  ## Nim program aka `ModuleGraph`.
139  resetForBackend(g)
140  var alive = computeAliveSyms(g.packed, g.config)
141
142  when false:
143    for i in 0..high(g.packed):
144      echo i, " is of status ", g.packed[i].status, " ", toFullPath(g.config, FileIndex(i))
145
146  # First pass: Setup all the backend modules for all the modules that have
147  # changed:
148  for i in 0..high(g.packed):
149    # case statement here to enforce exhaustive checks.
150    case g.packed[i].status
151    of undefined:
152      discard "nothing to do"
153    of loading, stored:
154      assert false
155    of storing, outdated:
156      setupBackendModule(g, g.packed[i])
157    of loaded:
158      # Even though this module didn't change, DCE might trigger a change.
159      # Consider this case: Module A uses symbol S from B and B does not use
160      # S itself. A is then edited not to use S either. Thus we have to
161      # recompile B in order to remove S from the final result.
162      if aliveSymsChanged(g.config, g.packed[i].module.position, alive):
163        g.packed[i].loadedButAliveSetChanged = true
164        setupBackendModule(g, g.packed[i])
165
166  # Second pass: Code generation.
167  let mainModuleIdx = g.config.projectMainIdx2.int
168  # We need to generate the main module last, because only then
169  # all init procs have been registered:
170  for i in 0..high(g.packed):
171    if i != mainModuleIdx:
172      genPackedModule(g, i, alive)
173  if mainModuleIdx >= 0:
174    genPackedModule(g, mainModuleIdx, alive)
175