summary refs log tree commit diff stats
path: root/compiler/cgen.nim
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/cgen.nim')
-rw-r--r--compiler/cgen.nim2591
1 files changed, 1743 insertions, 848 deletions
diff --git a/compiler/cgen.nim b/compiler/cgen.nim
index 56002459e..091f5c842 100644
--- a/compiler/cgen.nim
+++ b/compiler/cgen.nim
@@ -10,16 +10,34 @@
 ## This module implements the C code generator.
 
 import
-  ast, astalgo, hashes, trees, platform, magicsys, extccomp, options, intsets,
-  nversion, nimsets, msgs, securehash, bitsets, idents, types,
-  ccgutils, os, ropes, math, passes, rodread, wordrecg, treetab, cgmeth,
-  condsyms, rodutils, renderer, idgen, cgendata, ccgmerge, semfold, aliases,
-  lowerings, semparallel, tables, sets, ndi
+  ast, astalgo, trees, platform, magicsys, extccomp, options,
+  nversion, nimsets, msgs, bitsets, idents, types,
+  ccgutils, ropes, wordrecg, treetab, cgmeth,
+  rodutils, renderer, cgendata, aliases,
+  lowerings, ndi, lineinfos, pathutils, transf,
+  injectdestructors, astmsgs, modulepaths, pushpoppragmas,
+  mangleutils
 
-import strutils except `%` # collides with ropes.`%`
+from expanddefaults import caseObjDefaultBranch
 
-from modulegraphs import ModuleGraph
-import dynlib
+import pipelineutils
+
+when defined(nimPreviewSlimSystem):
+  import std/assertions
+
+when not defined(leanCompiler):
+  import spawn, semparallel
+
+import std/strutils except `%`, addf # collides with ropes.`%`
+
+from ic / ic import ModuleBackendFlag
+import std/[dynlib, math, tables, sets, os, intsets, hashes]
+
+const
+  # we use some ASCII control characters to insert directives that will be converted to real code in a postprocessing pass
+  postprocessDirStart = '\1'
+  postprocessDirSep = '\31'
+  postprocessDirEnd = '\23'
 
 when not declared(dynlib.libCandidates):
   proc libCandidates(s: string, dest: var seq[string]) =
@@ -32,35 +50,44 @@ when not declared(dynlib.libCandidates):
       for middle in split(substr(s, le + 1, ri - 1), '|'):
         libCandidates(prefix & middle & suffix, dest)
     else:
-      add(dest, s)
+      dest.add(s)
 
 when options.hasTinyCBackend:
   import tccgen
 
-# implementation
+proc hcrOn(m: BModule): bool = m.config.hcrOn
+proc hcrOn(p: BProc): bool = p.module.config.hcrOn
 
 proc addForwardedProc(m: BModule, prc: PSym) =
-  m.forwardedProcs.add(prc)
-  inc(m.g.forwardedProcsCounter)
+  m.g.forwardedProcs.add(prc)
 
 proc findPendingModule(m: BModule, s: PSym): BModule =
-  var ms = getModule(s)
-  result = m.g.modules[ms.position]
+  # TODO fixme
+  if m.config.symbolFiles == v2Sf:
+    let ms = s.itemId.module  #getModule(s)
+    result = m.g.modules[ms]
+  else:
+    var ms = getModule(s)
+    result = m.g.modules[ms.position]
+
+proc initLoc(k: TLocKind, lode: PNode, s: TStorageLoc, flags: TLocFlags = {}): TLoc =
+  result = TLoc(k: k, storage: s, lode: lode,
+                snippet: "", flags: flags)
 
-proc initLoc(result: var TLoc, k: TLocKind, lode: PNode, s: TStorageLoc) =
-  result.k = k
-  result.storage = s
-  result.lode = lode
-  result.r = nil
-  result.flags = {}
+proc fillLoc(a: var TLoc, k: TLocKind, lode: PNode, r: Rope, s: TStorageLoc) {.inline.} =
+  # fills the loc if it is not already initialized
+  if a.k == locNone:
+    a.k = k
+    a.lode = lode
+    a.storage = s
+    if a.snippet == "": a.snippet = r
 
-proc fillLoc(a: var TLoc, k: TLocKind, lode: PNode, r: Rope, s: TStorageLoc) =
+proc fillLoc(a: var TLoc, k: TLocKind, lode: PNode, s: TStorageLoc) {.inline.} =
   # fills the loc if it is not already initialized
   if a.k == locNone:
     a.k = k
     a.lode = lode
     a.storage = s
-    if a.r == nil: a.r = r
 
 proc t(a: TLoc): PType {.inline.} =
   if a.lode.kind == nkSym:
@@ -78,125 +105,195 @@ proc isSimpleConst(typ: PType): bool =
       {tyTuple, tyObject, tyArray, tySet, tySequence} and not
       (t.kind == tyProc and t.callConv == ccClosure)
 
-proc useStringh(m: BModule) =
-  if includesStringh notin m.flags:
-    incl m.flags, includesStringh
-    m.includeHeader("<string.h>")
-
 proc useHeader(m: BModule, sym: PSym) =
   if lfHeader in sym.loc.flags:
     assert(sym.annex != nil)
     let str = getStr(sym.annex.path)
     m.includeHeader(str)
 
-proc cgsym(m: BModule, name: string): Rope
+proc cgsym(m: BModule, name: string)
+proc cgsymValue(m: BModule, name: string): Rope
+
+proc getCFile(m: BModule): AbsoluteFile
+
+proc getModuleDllPath(m: BModule): Rope =
+  let (dir, name, ext) = splitFile(getCFile(m))
+  let filename = strutils.`%`(platform.OS[m.g.config.target.targetOS].dllFrmt, [name & ext])
+  result = makeCString(dir.string & "/" & filename)
+
+proc getModuleDllPath(m: BModule, module: int): Rope =
+  result = getModuleDllPath(m.g.modules[module])
+
+proc getModuleDllPath(m: BModule, s: PSym): Rope =
+  result = getModuleDllPath(m.g.modules[s.itemId.module])
+
+import std/macros
+
+proc cgFormatValue(result: var string; value: string) =
+  result.add value
+
+proc cgFormatValue(result: var string; value: BiggestInt) =
+  result.addInt value
 
-proc ropecg(m: BModule, frmt: FormatStr, args: varargs[Rope]): Rope =
+proc cgFormatValue(result: var string; value: Int128) =
+  result.addInt128 value
+
+# TODO: please document
+macro ropecg(m: BModule, frmt: static[FormatStr], args: untyped): Rope =
+  args.expectKind nnkBracket
+  # echo "ropecg ", newLit(frmt).repr, ", ", args.repr
   var i = 0
-  var length = len(frmt)
-  result = nil
+  result = nnkStmtListExpr.newTree()
+
+  result.add quote do:
+    assert `m` != nil
+
+  let resVar = genSym(nskVar, "res")
+  # during `koch boot` the median of all generates strings from this
+  # macro is around 40 bytes in length.
+  result.add newVarStmt(resVar, newCall(bindSym"newStringOfCap", newLit(80)))
+  let formatValue = bindSym"cgFormatValue"
+
   var num = 0
-  while i < length:
+  var strLit = ""
+
+  template flushStrLit() =
+    if strLit != "":
+      result.add newCall(ident "add", resVar, newLit(strLit))
+      strLit.setLen 0
+
+  while i < frmt.len:
     if frmt[i] == '$':
       inc(i)                  # skip '$'
       case frmt[i]
       of '$':
-        add(result, "$")
+        strLit.add '$'
         inc(i)
       of '#':
+        flushStrLit()
         inc(i)
-        add(result, args[num])
+        result.add newCall(formatValue, resVar, args[num])
+        inc(num)
+      of '^':
+        flushStrLit()
+        inc(i)
+        result.add newCall(formatValue, resVar, args[^1])
         inc(num)
       of '0'..'9':
         var j = 0
         while true:
           j = (j * 10) + ord(frmt[i]) - ord('0')
           inc(i)
-          if i >= length or not (frmt[i] in {'0'..'9'}): break
+          if i >= frmt.len or not (frmt[i] in {'0'..'9'}): break
         num = j
-        if j > high(args) + 1:
-          internalError("ropes: invalid format string $" & $j)
-        add(result, args[j-1])
+        if j > args.len:
+          error("ropes: invalid format string " & newLit(frmt).repr & " args.len: " & $args.len)
+
+        flushStrLit()
+        result.add newCall(formatValue, resVar, args[j-1])
       of 'n':
-        if optLineDir notin gOptions: add(result, rnl)
+        flushStrLit()
+        result.add quote do:
+          if optLineDir notin `m`.config.options:
+            `resVar`.add("\L")
         inc(i)
       of 'N':
-        add(result, rnl)
+        strLit.add "\L"
         inc(i)
-      else: internalError("ropes: invalid format string $" & frmt[i])
+      else:
+        error("ropes: invalid format string $" & frmt[i])
     elif frmt[i] == '#' and frmt[i+1] in IdentStartChars:
       inc(i)
       var j = i
       while frmt[j] in IdentChars: inc(j)
-      var ident = substr(frmt, i, j-1)
+      var ident = newLit(substr(frmt, i, j-1))
       i = j
-      add(result, cgsym(m, ident))
+      flushStrLit()
+      result.add newCall(formatValue, resVar, newCall(ident"cgsymValue", m, ident))
     elif frmt[i] == '#' and frmt[i+1] == '$':
       inc(i, 2)
       var j = 0
       while frmt[i] in Digits:
         j = (j * 10) + ord(frmt[i]) - ord('0')
         inc(i)
-      add(result, cgsym(m, $args[j-1]))
-    var start = i
-    while i < length:
-      if frmt[i] != '$' and frmt[i] != '#': inc(i)
-      else: break
-    if i - 1 >= start:
-      add(result, substr(frmt, start, i - 1))
-
-template rfmt(m: BModule, fmt: string, args: varargs[Rope]): untyped =
-  ropecg(m, fmt, args)
-
-var indent = "\t".rope
+      let ident = args[j-1]
+      flushStrLit()
+      result.add newCall(formatValue, resVar, newCall(ident"cgsymValue", m, ident))
+    elif frmt[i] == '#' and frmt[i+1] == '#':
+      inc(i, 2)
+      strLit.add("#")
+    else:
+      strLit.add(frmt[i])
+      inc(i)
 
-proc indentLine(p: BProc, r: Rope): Rope =
-  result = r
-  for i in countup(0, p.blocks.len-1):
-    prepend(result, indent)
+  flushStrLit()
+  result.add newCall(ident"rope", resVar)
 
-proc appcg(m: BModule, c: var Rope, frmt: FormatStr,
-           args: varargs[Rope]) =
-  add(c, ropecg(m, frmt, args))
+proc addIndent(p: BProc; result: var Rope) =
+  var i = result.len
+  let newLen = i + p.blocks.len
+  result.setLen newLen
+  while i < newLen:
+    result[i] = '\t'
+    inc i
 
-proc appcg(m: BModule, s: TCFileSection, frmt: FormatStr,
-           args: varargs[Rope]) =
-  add(m.s[s], ropecg(m, frmt, args))
+template appcg(m: BModule, c: var Rope, frmt: FormatStr,
+           args: untyped) =
+  c.add(ropecg(m, frmt, args))
 
-proc appcg(p: BProc, s: TCProcSection, frmt: FormatStr,
-           args: varargs[Rope]) =
-  add(p.s(s), ropecg(p.module, frmt, args))
+template appcg(m: BModule, sec: TCFileSection, frmt: FormatStr,
+           args: untyped) =
+  m.s[sec].add(ropecg(m, frmt, args))
 
-proc line(p: BProc, s: TCProcSection, r: Rope) =
-  add(p.s(s), indentLine(p, r))
+template appcg(p: BProc, sec: TCProcSection, frmt: FormatStr,
+           args: untyped) =
+  p.s(sec).add(ropecg(p.module, frmt, args))
 
-proc line(p: BProc, s: TCProcSection, r: string) =
-  add(p.s(s), indentLine(p, r.rope))
+template line(p: BProc, sec: TCProcSection, r: string) =
+  addIndent p, p.s(sec)
+  p.s(sec).add(r)
 
-proc lineF(p: BProc, s: TCProcSection, frmt: FormatStr,
-              args: openarray[Rope]) =
-  add(p.s(s), indentLine(p, frmt % args))
+template lineF(p: BProc, sec: TCProcSection, frmt: FormatStr,
+              args: untyped) =
+  addIndent p, p.s(sec)
+  p.s(sec).add(frmt % args)
 
-proc lineCg(p: BProc, s: TCProcSection, frmt: FormatStr,
-               args: varargs[Rope]) =
-  add(p.s(s), indentLine(p, ropecg(p.module, frmt, args)))
+template lineCg(p: BProc, sec: TCProcSection, frmt: FormatStr,
+               args: untyped) =
+  addIndent p, p.s(sec)
+  p.s(sec).add(ropecg(p.module, frmt, args))
 
-proc linefmt(p: BProc, s: TCProcSection, frmt: FormatStr,
-             args: varargs[Rope]) =
-  add(p.s(s), indentLine(p, ropecg(p.module, frmt, args)))
+template linefmt(p: BProc, sec: TCProcSection, frmt: FormatStr,
+             args: untyped) =
+  addIndent p, p.s(sec)
+  p.s(sec).add(ropecg(p.module, frmt, args))
 
 proc safeLineNm(info: TLineInfo): int =
   result = toLinenumber(info)
   if result < 0: result = 0 # negative numbers are not allowed in #line
 
-proc genCLineDir(r: var Rope, filename: string, line: int) =
+proc genPostprocessDir(field1, field2, field3: string): string =
+  result = postprocessDirStart & field1 & postprocessDirSep & field2 & postprocessDirSep & field3 & postprocessDirEnd
+
+proc genCLineDir(r: var Rope, fileIdx: FileIndex, line: int; conf: ConfigRef) =
   assert line >= 0
-  if optLineDir in gOptions:
-    addf(r, "$N#line $2 $1$N",
-        [rope(makeSingleLineCString(filename)), rope(line)])
+  if optLineDir in conf.options and line > 0:
+    if fileIdx == InvalidFileIdx:
+      r.add(rope("\n#line " & $line & " \"generated_not_to_break_here\"\n"))
+    else:
+      r.add(rope("\n#line " & $line & " FX_" & $fileIdx.int32 & "\n"))
 
-proc genCLineDir(r: var Rope, info: TLineInfo) =
-  genCLineDir(r, info.toFullPath, info.safeLineNm)
+proc genCLineDir(r: var Rope, fileIdx: FileIndex, line: int; p: BProc; info: TLineInfo; lastFileIndex: FileIndex) =
+  assert line >= 0
+  if optLineDir in p.config.options and line > 0:
+    if fileIdx == InvalidFileIdx:
+      r.add(rope("\n#line " & $line & " \"generated_not_to_break_here\"\n"))
+    else:
+      r.add(rope("\n#line " & $line & " FX_" & $fileIdx.int32 & "\n"))
+
+proc genCLineDir(r: var Rope, info: TLineInfo; conf: ConfigRef) =
+  if optLineDir in conf.options:
+    genCLineDir(r, info.fileIndex, info.safeLineNm, conf)
 
 proc freshLineInfo(p: BProc; info: TLineInfo): bool =
   if p.lastLineInfo.line != info.line or
@@ -204,53 +301,107 @@ proc freshLineInfo(p: BProc; info: TLineInfo): bool =
     p.lastLineInfo.line = info.line
     p.lastLineInfo.fileIndex = info.fileIndex
     result = true
+  else:
+    result = false
+
+proc genCLineDir(r: var Rope, p: BProc, info: TLineInfo; conf: ConfigRef) =
+  if optLineDir in conf.options:
+    let lastFileIndex = p.lastLineInfo.fileIndex
+    if freshLineInfo(p, info):
+      genCLineDir(r, info.fileIndex, info.safeLineNm, p, info, lastFileIndex)
 
 proc genLineDir(p: BProc, t: PNode) =
-  var tt = t
-  #while tt.kind in {nkStmtListExpr}+nkCallKinds:
-  #  tt = tt.lastSon
-  if tt.kind in nkCallKinds and tt.len > 1:
-    tt = tt.sons[1]
-  let line = tt.info.safeLineNm
-
-  if optEmbedOrigSrc in gGlobalOptions:
-    add(p.s(cpsStmts), ~"//" & tt.info.sourceLine & rnl)
-  genCLineDir(p.s(cpsStmts), tt.info.toFullPath, line)
-  if ({optStackTrace, optEndb} * p.options == {optStackTrace, optEndb}) and
-      (p.prc == nil or sfPure notin p.prc.flags):
-    if freshLineInfo(p, tt.info):
-      linefmt(p, cpsStmts, "#endb($1, $2);$N",
-              line.rope, makeCString(toFilename(tt.info)))
-  elif ({optLineTrace, optStackTrace} * p.options ==
-      {optLineTrace, optStackTrace}) and
-      (p.prc == nil or sfPure notin p.prc.flags) and tt.info.fileIndex >= 0:
-    if freshLineInfo(p, tt.info):
-      linefmt(p, cpsStmts, "nimln_($1, $2);$n",
-              line.rope, tt.info.quotedFilename)
-
-proc postStmtActions(p: BProc) {.inline.} =
-  add(p.s(cpsStmts), p.module.injectStmt)
+  if p == p.module.preInitProc: return
+  let line = t.info.safeLineNm
+
+  if optEmbedOrigSrc in p.config.globalOptions:
+    p.s(cpsStmts).add("//" & sourceLine(p.config, t.info) & "\L")
+  let lastFileIndex = p.lastLineInfo.fileIndex
+  let freshLine = freshLineInfo(p, t.info)
+  if freshLine:
+    genCLineDir(p.s(cpsStmts), t.info.fileIndex, line, p, t.info, lastFileIndex)
+  if ({optLineTrace, optStackTrace} * p.options == {optLineTrace, optStackTrace}) and
+      (p.prc == nil or sfPure notin p.prc.flags) and t.info.fileIndex != InvalidFileIdx:
+      if freshLine:
+        line(p, cpsStmts, genPostprocessDir("nimln", $line, $t.info.fileIndex.int32))
 
 proc accessThreadLocalVar(p: BProc, s: PSym)
-proc emulatedThreadVars(): bool {.inline.}
+proc emulatedThreadVars(conf: ConfigRef): bool {.inline.}
 proc genProc(m: BModule, prc: PSym)
+proc raiseInstr(p: BProc; result: var Rope)
 
 template compileToCpp(m: BModule): untyped =
-  gCmd == cmdCompileToCpp or sfCompileToCpp in m.module.flags
+  m.config.backend == backendCpp or sfCompileToCpp in m.module.flags
 
-include "ccgtypes.nim"
-
-# ------------------------------ Manager of temporaries ------------------
+proc getTempName(m: BModule): Rope =
+  result = m.tmpBase & rope(m.labels)
+  inc m.labels
 
 proc rdLoc(a: TLoc): Rope =
   # 'read' location (deref if indirect)
-  result = a.r
-  if lfIndirect in a.flags: result = "(*$1)" % [result]
+  if lfIndirect in a.flags:
+    result = "(*" & a.snippet & ")"
+  else:
+    result = a.snippet
+
+proc addRdLoc(a: TLoc; result: var Rope) =
+  if lfIndirect in a.flags:
+    result.add "(*" & a.snippet & ")"
+  else:
+    result.add a.snippet
+
+proc lenField(p: BProc): Rope {.inline.} =
+  result = rope(if p.module.compileToCpp: "len" else: "Sup.len")
 
-proc addrLoc(a: TLoc): Rope =
-  result = a.r
-  if lfIndirect notin a.flags and mapType(a.t) != ctArray:
-    result = "(&" & result & ")"
+proc lenExpr(p: BProc; a: TLoc): Rope =
+  if optSeqDestructors in p.config.globalOptions:
+    result = rdLoc(a) & ".len"
+  else:
+    result = "($1 ? $1->$2 : 0)" % [rdLoc(a), lenField(p)]
+
+proc dataFieldAccessor(p: BProc, sym: Rope): Rope =
+  if optSeqDestructors in p.config.globalOptions:
+    result = "(" & sym & ").p"
+  else:
+    result = sym
+
+proc dataField(p: BProc): Rope =
+  if optSeqDestructors in p.config.globalOptions:
+    result = rope".p->data"
+  else:
+    result = rope"->data"
+
+proc genProcPrototype(m: BModule, sym: PSym)
+
+include cbuilder
+include ccgliterals
+include ccgtypes
+
+# ------------------------------ Manager of temporaries ------------------
+
+template mapTypeChooser(n: PNode): TSymKind =
+  (if n.kind == nkSym: n.sym.kind else: skVar)
+
+template mapTypeChooser(a: TLoc): TSymKind = mapTypeChooser(a.lode)
+
+proc addAddrLoc(conf: ConfigRef; a: TLoc; result: var Rope) =
+  if lfIndirect notin a.flags and mapType(conf, a.t, mapTypeChooser(a) == skParam) != ctArray:
+    result.add "(&" & a.snippet & ")"
+  else:
+    result.add a.snippet
+
+proc addrLoc(conf: ConfigRef; a: TLoc): Rope =
+  if lfIndirect notin a.flags and mapType(conf, a.t, mapTypeChooser(a) == skParam) != ctArray:
+    result = "(&" & a.snippet & ")"
+  else:
+    result = a.snippet
+
+proc byRefLoc(p: BProc; a: TLoc): Rope =
+  if lfIndirect notin a.flags and mapType(p.config, a.t, mapTypeChooser(a) == skParam) != ctArray and not
+      p.module.compileToCpp:
+    result = "(&" & a.snippet & ")"
+  else:
+    result = a.snippet
 
 proc rdCharLoc(a: TLoc): Rope =
   # read a location that may need a char-cast:
@@ -258,80 +409,146 @@ proc rdCharLoc(a: TLoc): Rope =
   if skipTypes(a.t, abstractRange).kind == tyChar:
     result = "((NU8)($1))" % [result]
 
-proc genObjectInit(p: BProc, section: TCProcSection, t: PType, a: TLoc,
-                   takeAddr: bool) =
+type
+  TAssignmentFlag = enum
+    needToCopy
+    needToCopySinkParam
+    needTempForOpenArray
+    needAssignCall
+  TAssignmentFlags = set[TAssignmentFlag]
+
+proc genObjConstr(p: BProc, e: PNode, d: var TLoc)
+proc rawConstExpr(p: BProc, n: PNode; d: var TLoc)
+proc genAssignment(p: BProc, dest, src: TLoc, flags: TAssignmentFlags)
+
+type
+  ObjConstrMode = enum
+    constructObj,
+    constructRefObj
+
+proc genObjectInit(p: BProc, section: TCProcSection, t: PType, a: var TLoc,
+                   mode: ObjConstrMode) =
+  #if optNimV2 in p.config.globalOptions: return
   case analyseObjectWithTypeField(t)
   of frNone:
     discard
   of frHeader:
     var r = rdLoc(a)
-    if not takeAddr: r = "(*$1)" % [r]
+    if mode == constructRefObj: r = "(*$1)" % [r]
     var s = skipTypes(t, abstractInst)
     if not p.module.compileToCpp:
-      while (s.kind == tyObject) and (s.sons[0] != nil):
-        add(r, ".Sup")
-        s = skipTypes(s.sons[0], skipPtrs)
-    linefmt(p, section, "$1.m_type = $2;$n", r, genTypeInfo(p.module, t))
+      while s.kind == tyObject and s[0] != nil:
+        r.add(".Sup")
+        s = skipTypes(s[0], skipPtrs)
+    if optTinyRtti in p.config.globalOptions:
+      linefmt(p, section, "$1.m_type = $2;$n", [r, genTypeInfoV2(p.module, t, a.lode.info)])
+    else:
+      linefmt(p, section, "$1.m_type = $2;$n", [r, genTypeInfoV1(p.module, t, a.lode.info)])
   of frEmbedded:
-    # worst case for performance:
-    var r = if takeAddr: addrLoc(a) else: rdLoc(a)
-    linefmt(p, section, "#objectInit($1, $2);$n", r, genTypeInfo(p.module, t))
+    if optTinyRtti in p.config.globalOptions:
+      var tmp: TLoc = default(TLoc)
+      if mode == constructRefObj:
+        let objType = t.skipTypes(abstractInst+{tyRef})
+        rawConstExpr(p, newNodeIT(nkType, a.lode.info, objType), tmp)
+        linefmt(p, cpsStmts,
+            "#nimCopyMem((void*)$1, (NIM_CONST void*)&$2, sizeof($3));$n",
+            [rdLoc(a), rdLoc(tmp), getTypeDesc(p.module, objType, descKindFromSymKind mapTypeChooser(a))])
+      else:
+        rawConstExpr(p, newNodeIT(nkType, a.lode.info, t), tmp)
+        genAssignment(p, a, tmp, {})
+    else:
+      # worst case for performance:
+      var r = if mode == constructObj: addrLoc(p.config, a) else: rdLoc(a)
+      linefmt(p, section, "#objectInit($1, $2);$n", [r, genTypeInfoV1(p.module, t, a.lode.info)])
 
-type
-  TAssignmentFlag = enum
-    needToCopy, afDestIsNil, afDestIsNotNil, afSrcIsNil, afSrcIsNotNil
-  TAssignmentFlags = set[TAssignmentFlag]
+  if isException(t):
+    var r = rdLoc(a)
+    if mode == constructRefObj: r = "(*$1)" % [r]
+    var s = skipTypes(t, abstractInst)
+    if not p.module.compileToCpp:
+      while s.kind == tyObject and s[0] != nil and s.sym.magic != mException:
+        r.add(".Sup")
+        s = skipTypes(s[0], skipPtrs)
+    linefmt(p, section, "$1.name = $2;$n", [r, makeCString(t.skipTypes(abstractInst).sym.name.s)])
 
-proc genRefAssign(p: BProc, dest, src: TLoc, flags: TAssignmentFlags)
+proc genRefAssign(p: BProc, dest, src: TLoc)
 
 proc isComplexValueType(t: PType): bool {.inline.} =
-  let t = t.skipTypes(abstractInst)
-  result = t.kind in {tyArray, tySet, tyTuple, tyObject} or
+  let t = t.skipTypes(abstractInst + tyUserTypeClasses)
+  result = t.kind in {tyArray, tySet, tyTuple, tyObject, tyOpenArray} or
     (t.kind == tyProc and t.callConv == ccClosure)
 
+include ccgreset
+
 proc resetLoc(p: BProc, loc: var TLoc) =
-  let containsGcRef = containsGarbageCollectedRef(loc.t)
+  let containsGcRef = optSeqDestructors notin p.config.globalOptions and containsGarbageCollectedRef(loc.t)
   let typ = skipTypes(loc.t, abstractVarRange)
-  if isImportedCppType(typ): return
-  if not isComplexValueType(typ):
+  if isImportedCppType(typ): 
+    var didGenTemp = false
+    linefmt(p, cpsStmts, "$1 = $2;$n", [rdLoc(loc), genCppInitializer(p.module, p, typ, didGenTemp)])
+    return
+  if optSeqDestructors in p.config.globalOptions and typ.kind in {tyString, tySequence}:
+    assert loc.snippet != ""
+
+    let atyp = skipTypes(loc.t, abstractInst)
+    if atyp.kind in {tyVar, tyLent}:
+      linefmt(p, cpsStmts, "$1->len = 0; $1->p = NIM_NIL;$n", [rdLoc(loc)])
+    else:
+      linefmt(p, cpsStmts, "$1.len = 0; $1.p = NIM_NIL;$n", [rdLoc(loc)])
+  elif not isComplexValueType(typ):
     if containsGcRef:
-      var nilLoc: TLoc
-      initLoc(nilLoc, locTemp, loc.lode, OnStack)
-      nilLoc.r = rope("NIM_NIL")
-      genRefAssign(p, loc, nilLoc, {afSrcIsNil})
+      var nilLoc: TLoc = initLoc(locTemp, loc.lode, OnStack)
+      nilLoc.snippet = rope("NIM_NIL")
+      genRefAssign(p, loc, nilLoc)
     else:
-      linefmt(p, cpsStmts, "$1 = 0;$n", rdLoc(loc))
+      linefmt(p, cpsStmts, "$1 = 0;$n", [rdLoc(loc)])
   else:
-    if optNilCheck in p.options:
-      linefmt(p, cpsStmts, "#chckNil((void*)$1);$n", addrLoc(loc))
-    if loc.storage != OnStack:
-      linefmt(p, cpsStmts, "#genericReset((void*)$1, $2);$n",
-              addrLoc(loc), genTypeInfo(p.module, loc.t))
+    if loc.storage != OnStack and containsGcRef:
+      specializeReset(p, loc)
+      when false:
+        linefmt(p, cpsStmts, "#genericReset((void*)$1, $2);$n",
+                [addrLoc(p.config, loc), genTypeInfoV1(p.module, loc.t, loc.lode.info)])
       # XXX: generated reset procs should not touch the m_type
       # field, so disabling this should be safe:
-      genObjectInit(p, cpsStmts, loc.t, loc, true)
+      genObjectInit(p, cpsStmts, loc.t, loc, constructObj)
     else:
-      useStringh(p.module)
-      linefmt(p, cpsStmts, "memset((void*)$1, 0, sizeof($2));$n",
-              addrLoc(loc), rdLoc(loc))
+      # array passed as argument decayed into pointer, bug #7332
+      # so we use getTypeDesc here rather than rdLoc(loc)
+      let tyDesc = getTypeDesc(p.module, loc.t, descKindFromSymKind mapTypeChooser(loc))
+      if p.module.compileToCpp and isOrHasImportedCppType(typ):
+        if lfIndirect in loc.flags:
+          #C++ cant be just zeroed. We need to call the ctors
+          var tmp = getTemp(p, loc.t)
+          linefmt(p, cpsStmts,"#nimCopyMem((void*)$1, (NIM_CONST void*)$2, sizeof($3));$n",
+                  [addrLoc(p.config, loc), addrLoc(p.config, tmp), tyDesc])
+      else:
+        linefmt(p, cpsStmts, "#nimZeroMem((void*)$1, sizeof($2));$n",
+                [addrLoc(p.config, loc), tyDesc])
+
       # XXX: We can be extra clever here and call memset only
       # on the bytes following the m_type field?
-      genObjectInit(p, cpsStmts, loc.t, loc, true)
+      genObjectInit(p, cpsStmts, loc.t, loc, constructObj)
 
-proc constructLoc(p: BProc, loc: TLoc, isTemp = false) =
+proc constructLoc(p: BProc, loc: var TLoc, isTemp = false) =
   let typ = loc.t
-  if not isComplexValueType(typ):
-    linefmt(p, cpsStmts, "$1 = ($2)0;$n", rdLoc(loc),
-      getTypeDesc(p.module, typ))
+  if optSeqDestructors in p.config.globalOptions and skipTypes(typ, abstractInst + {tyStatic}).kind in {tyString, tySequence}:
+    linefmt(p, cpsStmts, "$1.len = 0; $1.p = NIM_NIL;$n", [rdLoc(loc)])
+  elif not isComplexValueType(typ):
+    if containsGarbageCollectedRef(loc.t):
+      var nilLoc: TLoc = initLoc(locTemp, loc.lode, OnStack)
+      nilLoc.snippet = rope("NIM_NIL")
+      genRefAssign(p, loc, nilLoc)
+    else:
+      linefmt(p, cpsStmts, "$1 = ($2)0;$n", [rdLoc(loc),
+        getTypeDesc(p.module, typ, descKindFromSymKind mapTypeChooser(loc))])
   else:
-    if not isTemp or containsGarbageCollectedRef(loc.t):
-      # don't use memset for temporary values for performance if we can
+    if (not isTemp or containsGarbageCollectedRef(loc.t)) and not hasNoInit(loc.t):
+      # don't use nimZeroMem for temporary values for performance if we can
       # avoid it:
-      if not isImportedCppType(typ):
-        useStringh(p.module)
-        linefmt(p, cpsStmts, "memset((void*)$1, 0, sizeof($2));$n",
-                addrLoc(loc), rdLoc(loc))
-    genObjectInit(p, cpsStmts, loc.t, loc, true)
+      if not isOrHasImportedCppType(typ):
+        linefmt(p, cpsStmts, "#nimZeroMem((void*)$1, sizeof($2));$n",
+                [addrLoc(p.config, loc), getTypeDesc(p.module, typ, descKindFromSymKind mapTypeChooser(loc))])
+    genObjectInit(p, cpsStmts, loc.t, loc, constructObj)
 
 proc initLocalVar(p: BProc, v: PSym, immediateAsgn: bool) =
   if sfNoInit notin v.flags:
@@ -345,155 +562,255 @@ proc initLocalVar(p: BProc, v: PSym, immediateAsgn: bool) =
     if not immediateAsgn:
       constructLoc(p, v.loc)
 
-proc getTemp(p: BProc, t: PType, result: var TLoc; needsInit=false) =
+proc getTemp(p: BProc, t: PType, needsInit=false): TLoc =
   inc(p.labels)
-  result.r = "T" & rope(p.labels) & "_"
-  linefmt(p, cpsLocals, "$1 $2;$n", getTypeDesc(p.module, t), result.r)
-  result.k = locTemp
-  result.lode = lodeTyp t
-  result.storage = OnStack
-  result.flags = {}
+  result = TLoc(snippet: "T" & rope(p.labels) & "_", k: locTemp, lode: lodeTyp t,
+                storage: OnStack, flags: {})
+  if p.module.compileToCpp and isOrHasImportedCppType(t):
+    var didGenTemp = false
+    linefmt(p, cpsLocals, "$1 $2$3;$n", [getTypeDesc(p.module, t, dkVar), result.snippet,
+      genCppInitializer(p.module, p, t, didGenTemp)])
+  else:
+    linefmt(p, cpsLocals, "$1 $2;$n", [getTypeDesc(p.module, t, dkVar), result.snippet])
   constructLoc(p, result, not needsInit)
+  when false:
+    # XXX Introduce a compiler switch in order to detect these easily.
+    if getSize(p.config, t) > 1024 * 1024:
+      if p.prc != nil:
+        echo "ENORMOUS TEMPORARY! ", p.config $ p.prc.info
+      else:
+        echo "ENORMOUS TEMPORARY! ", p.config $ p.lastLineInfo
+      writeStackTrace()
 
-proc getIntTemp(p: BProc, result: var TLoc) =
+proc getTempCpp(p: BProc, t: PType, value: Rope): TLoc =
   inc(p.labels)
-  result.r = "T" & rope(p.labels) & "_"
-  linefmt(p, cpsLocals, "NI $1;$n", result.r)
-  result.k = locTemp
-  result.storage = OnStack
-  result.lode = lodeTyp getSysType(tyInt)
-  result.flags = {}
-
-proc initGCFrame(p: BProc): Rope =
-  if p.gcFrameId > 0: result = "struct {$1} GCFRAME_;$n" % [p.gcFrameType]
-
-proc deinitGCFrame(p: BProc): Rope =
-  if p.gcFrameId > 0:
-    result = ropecg(p.module,
-                    "if (((NU)&GCFRAME_) < 4096) #nimGCFrame(&GCFRAME_);$n")
-
-proc localDebugInfo(p: BProc, s: PSym) =
-  if {optStackTrace, optEndb} * p.options != {optStackTrace, optEndb}: return
-  # XXX work around a bug: No type information for open arrays possible:
-  if skipTypes(s.typ, abstractVar).kind in {tyOpenArray, tyVarargs}: return
-  var a = "&" & s.loc.r
-  if s.kind == skParam and ccgIntroducedPtr(s): a = s.loc.r
-  lineF(p, cpsInit,
-       "FR_.s[$1].address = (void*)$3; FR_.s[$1].typ = $4; FR_.s[$1].name = $2;$n",
-       [p.maxFrameLen.rope, makeCString(normalize(s.name.s)), a,
-        genTypeInfo(p.module, s.loc.t)])
-  inc(p.maxFrameLen)
-  inc p.blocks[p.blocks.len-1].frameLen
+  result = TLoc(snippet: "T" & rope(p.labels) & "_", k: locTemp, lode: lodeTyp t,
+                storage: OnStack, flags: {})
+  linefmt(p, cpsStmts, "auto $1 = $2;$n", [result.snippet, value])
+
+proc getIntTemp(p: BProc): TLoc =
+  inc(p.labels)
+  result = TLoc(snippet: "T" & rope(p.labels) & "_", k: locTemp,
+                storage: OnStack, lode: lodeTyp getSysType(p.module.g.graph, unknownLineInfo, tyInt),
+                flags: {})
+  linefmt(p, cpsLocals, "NI $1;$n", [result.snippet])
 
 proc localVarDecl(p: BProc; n: PNode): Rope =
+  result = ""
   let s = n.sym
   if s.loc.k == locNone:
-    fillLoc(s.loc, locLocalVar, n, mangleLocalName(p, s), OnStack)
+    fillLocalName(p, s)
+    fillLoc(s.loc, locLocalVar, n, OnStack)
     if s.kind == skLet: incl(s.loc.flags, lfNoDeepCopy)
-  result = getTypeDesc(p.module, s.typ)
-  if s.constraint.isNil:
-    if sfRegister in s.flags: add(result, " register")
+  if s.kind in {skLet, skVar, skField, skForVar} and s.alignment > 0:
+    result.addf("NIM_ALIGN($1) ", [rope(s.alignment)])
+
+  genCLineDir(result, p, n.info, p.config)
+
+  result.add getTypeDesc(p.module, s.typ, dkVar)
+  if sfCodegenDecl notin s.flags:
+    if sfRegister in s.flags: result.add(" register")
     #elif skipTypes(s.typ, abstractInst).kind in GcTypeKinds:
-    #  add(decl, " GC_GUARD")
-    if sfVolatile in s.flags: add(result, " volatile")
-    add(result, " ")
-    add(result, s.loc.r)
+    #  decl.add(" GC_GUARD")
+    if sfVolatile in s.flags: result.add(" volatile")
+    if sfNoalias in s.flags: result.add(" NIM_NOALIAS")
+    result.add(" ")
+    result.add(s.loc.snippet)
   else:
-    result = s.cgDeclFrmt % [result, s.loc.r]
+    result = runtimeFormat(s.cgDeclFrmt, [result, s.loc.snippet])
 
 proc assignLocalVar(p: BProc, n: PNode) =
   #assert(s.loc.k == locNone) # not yet assigned
   # this need not be fulfilled for inline procs; they are regenerated
   # for each module that uses them!
-  let nl = if optLineDir in gOptions: "" else: tnl
-  let decl = localVarDecl(p, n) & ";" & nl
+  let nl = if optLineDir in p.config.options: "" else: "\n"
+  var decl = localVarDecl(p, n)
+  if p.module.compileToCpp and isOrHasImportedCppType(n.typ):
+    var didGenTemp = false
+    decl.add genCppInitializer(p.module, p, n.typ, didGenTemp)
+  decl.add ";" & nl
   line(p, cpsLocals, decl)
-  localDebugInfo(p, n.sym)
 
 include ccgthreadvars
 
 proc varInDynamicLib(m: BModule, sym: PSym)
-proc mangleDynLibProc(sym: PSym): Rope
 
-proc assignGlobalVar(p: BProc, n: PNode) =
+proc treatGlobalDifferentlyForHCR(m: BModule, s: PSym): bool =
+  return m.hcrOn and {sfThread, sfGlobal} * s.flags == {sfGlobal} and
+      ({lfNoDecl, lfHeader} * s.loc.flags == {})
+      # and s.owner.kind == skModule # owner isn't always a module (global pragma on local var)
+      # and s.loc.k == locGlobalVar  # loc isn't always initialized when this proc is used
+
+proc genGlobalVarDecl(p: BProc, n: PNode; td, value: Rope; decl: var Rope) =
+  let s = n.sym
+  if sfCodegenDecl notin s.flags:
+    if s.kind in {skLet, skVar, skField, skForVar} and s.alignment > 0:
+      decl.addf "NIM_ALIGN($1) ", [rope(s.alignment)]
+    if p.hcrOn: decl.add("static ")
+    elif sfImportc in s.flags: decl.add("extern ")
+    elif lfExportLib in s.loc.flags: decl.add("N_LIB_EXPORT_VAR ")
+    else: decl.add("N_LIB_PRIVATE ")
+    if s.kind == skLet and value != "": decl.add("NIM_CONST ")
+    decl.add(td)
+    if p.hcrOn: decl.add("*")
+    if sfRegister in s.flags: decl.add(" register")
+    if sfVolatile in s.flags: decl.add(" volatile")
+    if sfNoalias in s.flags: decl.add(" NIM_NOALIAS")
+  else:
+    if value != "":
+      decl = runtimeFormat(s.cgDeclFrmt & " = $#;$n", [td, s.loc.snippet, value])
+    else:
+      decl = runtimeFormat(s.cgDeclFrmt & ";$n", [td, s.loc.snippet])
+
+proc genCppVarForCtor(p: BProc; call: PNode; decl: var Rope; didGenTemp: var bool)
+
+proc assignGlobalVar(p: BProc, n: PNode; value: Rope) =
   let s = n.sym
   if s.loc.k == locNone:
-    fillLoc(s.loc, locGlobalVar, n, mangleName(p.module, s), OnHeap)
+    fillBackendName(p.module, s)
+    fillLoc(s.loc, locGlobalVar, n, OnHeap)
+    if treatGlobalDifferentlyForHCR(p.module, s): incl(s.loc.flags, lfIndirect)
 
   if lfDynamicLib in s.loc.flags:
     var q = findPendingModule(p.module, s)
     if q != nil and not containsOrIncl(q.declaredThings, s.id):
       varInDynamicLib(q, s)
     else:
-      s.loc.r = mangleDynLibProc(s)
+      s.loc.snippet = mangleDynLibProc(s)
+    if value != "":
+      internalError(p.config, n.info, ".dynlib variables cannot have a value")
     return
   useHeader(p.module, s)
   if lfNoDecl in s.loc.flags: return
-  if sfThread in s.flags:
-    declareThreadVar(p.module, s, sfImportc in s.flags)
-  else:
-    var decl: Rope = nil
-    var td = getTypeDesc(p.module, s.loc.t)
-    if s.constraint.isNil:
-      if sfImportc in s.flags: add(decl, "extern ")
-      add(decl, td)
-      if sfRegister in s.flags: add(decl, " register")
-      if sfVolatile in s.flags: add(decl, " volatile")
-      addf(decl, " $1;$n", [s.loc.r])
+  if not containsOrIncl(p.module.declaredThings, s.id):
+    if sfThread in s.flags:
+      declareThreadVar(p.module, s, sfImportc in s.flags)
+      if value != "":
+        internalError(p.config, n.info, ".threadvar variables cannot have a value")
     else:
-      decl = (s.cgDeclFrmt & ";$n") % [td, s.loc.r]
-    add(p.module.s[cfsVars], decl)
-  if p.withinLoop > 0:
+      var decl: Rope = ""
+      let td = getTypeDesc(p.module, s.loc.t, dkVar)
+      genGlobalVarDecl(p, n, td, value, decl)
+      if s.constraint.isNil:
+        if value != "":
+          if p.module.compileToCpp and value.startsWith "{{}":
+            # TODO: taking this branch, re"\{\{\}(,\s\{\})*\}" might be emitted, resulting in
+            # either warnings (GCC 12.2+) or errors (Clang 15, MSVC 19.3+) of C++11+ compilers **when
+            # explicit constructors are around** due to overload resolution rules in place [^0][^1][^2]
+            # *Workaround* here: have C++'s static initialization mechanism do the default init work,
+            # for us lacking a deeper knowledge of an imported object's constructors' ex-/implicitness
+            # (so far) *and yet* trying to achieve default initialization.
+            # Still, generating {}s in genConstObjConstr() just to omit them here is faaaar from ideal;
+            # need to figure out a better way, possibly by keeping around more data about the
+            # imported objects' contructors?
+            #
+            # [^0]: https://en.cppreference.com/w/cpp/language/aggregate_initialization
+            # [^1]: https://cplusplus.github.io/CWG/issues/1518.html
+            # [^2]: https://eel.is/c++draft/over.match.ctor
+            decl.addf(" $1;$n", [s.loc.snippet])
+          else:
+            decl.addf(" $1 = $2;$n", [s.loc.snippet, value])
+        else:
+          decl.addf(" $1;$n", [s.loc.snippet])
+
+      p.module.s[cfsVars].add(decl)
+  if p.withinLoop > 0 and value == "":
     # fixes tests/run/tzeroarray:
     resetLoc(p, s.loc)
-  if p.module.module.options * {optStackTrace, optEndb} ==
-                               {optStackTrace, optEndb}:
-    appcg(p.module, p.module.s[cfsDebugInit],
-          "#dbgRegisterGlobal($1, &$2, $3);$n",
-         [makeCString(normalize(s.owner.name.s & '.' & s.name.s)),
-          s.loc.r, genTypeInfo(p.module, s.typ)])
-
-proc assignParam(p: BProc, s: PSym) =
-  assert(s.loc.r != nil)
+
+proc callGlobalVarCppCtor(p: BProc; v: PSym; vn, value: PNode; didGenTemp: var bool) =
+  let s = vn.sym
+  fillBackendName(p.module, s)
+  fillLoc(s.loc, locGlobalVar, vn, OnHeap)
+  var decl: Rope = ""
+  let td = getTypeDesc(p.module, vn.sym.typ, dkVar)
+  genGlobalVarDecl(p, vn, td, "", decl)
+  decl.add " " & $s.loc.snippet
+  genCppVarForCtor(p, value, decl, didGenTemp)
+  if didGenTemp:  return # generated in the caller
+  p.module.s[cfsVars].add decl
+
+proc assignParam(p: BProc, s: PSym, retType: PType) =
+  assert(s.loc.snippet != "")
   scopeMangledParam(p, s)
-  localDebugInfo(p, s)
 
 proc fillProcLoc(m: BModule; n: PNode) =
   let sym = n.sym
   if sym.loc.k == locNone:
-    fillLoc(sym.loc, locProc, n, mangleName(m, sym), OnStack)
+    fillBackendName(m, sym)
+    fillLoc(sym.loc, locProc, n, OnStack)
 
 proc getLabel(p: BProc): TLabel =
   inc(p.labels)
   result = "LA" & rope(p.labels) & "_"
 
 proc fixLabel(p: BProc, labl: TLabel) =
-  lineF(p, cpsStmts, "$1: ;$n", [labl])
+  p.s(cpsStmts).add("$1: ;$n" % [labl])
 
 proc genVarPrototype(m: BModule, n: PNode)
 proc requestConstImpl(p: BProc, sym: PSym)
 proc genStmts(p: BProc, t: PNode)
 proc expr(p: BProc, n: PNode, d: var TLoc)
-proc genProcPrototype(m: BModule, sym: PSym)
-proc putLocIntoDest(p: BProc, d: var TLoc, s: TLoc)
-proc genAssignment(p: BProc, dest, src: TLoc, flags: TAssignmentFlags)
-proc intLiteral(i: BiggestInt): Rope
-proc genLiteral(p: BProc, n: PNode): Rope
-proc genOtherArg(p: BProc; ri: PNode; i: int; typ: PType): Rope
 
-proc initLocExpr(p: BProc, e: PNode, result: var TLoc) =
-  initLoc(result, locNone, e, OnUnknown)
+proc putLocIntoDest(p: BProc, d: var TLoc, s: TLoc)
+proc intLiteral(i: BiggestInt; result: var Rope)
+proc genLiteral(p: BProc, n: PNode; result: var Rope)
+proc genOtherArg(p: BProc; ri: PNode; i: int; typ: PType; result: var Rope; argsCounter: var int)
+proc raiseExit(p: BProc)
+proc raiseExitCleanup(p: BProc, destroy: string)
+
+proc initLocExpr(p: BProc, e: PNode, flags: TLocFlags = {}): TLoc =
+  result = initLoc(locNone, e, OnUnknown, flags)
   expr(p, e, result)
 
-proc initLocExprSingleUse(p: BProc, e: PNode, result: var TLoc) =
-  initLoc(result, locNone, e, OnUnknown)
-  result.flags.incl lfSingleUse
+proc initLocExprSingleUse(p: BProc, e: PNode): TLoc =
+  result = initLoc(locNone, e, OnUnknown)
+  if e.kind in nkCallKinds and (e[0].kind != nkSym or e[0].sym.magic == mNone):
+    # We cannot check for tfNoSideEffect here because of mutable parameters.
+    discard "bug #8202; enforce evaluation order for nested calls for C++ too"
+    # We may need to consider that 'f(g())' cannot be rewritten to 'tmp = g(); f(tmp)'
+    # if 'tmp' lacks a move/assignment operator.
+    if e[0].kind == nkSym and sfCompileToCpp in e[0].sym.flags:
+      result.flags.incl lfSingleUse
+  else:
+    result.flags.incl lfSingleUse
   expr(p, e, result)
 
-proc lenField(p: BProc): Rope =
-  result = rope(if p.module.compileToCpp: "len" else: "Sup.len")
+include ccgcalls, "ccgstmts.nim"
+
+proc initFrame(p: BProc, procname, filename: Rope): Rope =
+  const frameDefines = """
+$1define nimfr_(proc, file) \
+  TFrame FR_; \
+  FR_.procname = proc; FR_.filename = file; FR_.line = 0; FR_.len = 0; #nimFrame(&FR_);
+
+$1define nimln_(n) \
+  FR_.line = n;
+
+$1define nimlf_(n, file) \
+  FR_.line = n; FR_.filename = file;
+
+"""
+  if p.module.s[cfsFrameDefines].len == 0:
+    appcg(p.module, p.module.s[cfsFrameDefines], frameDefines, ["#"])
 
-include ccgcalls, "ccgstmts.nim", "ccgexprs.nim"
+  cgsym(p.module, "nimFrame")
+  result = ropecg(p.module, "\tnimfr_($1, $2);$n", [procname, filename])
+
+proc initFrameNoDebug(p: BProc; frame, procname, filename: Rope; line: int): Rope =
+  cgsym(p.module, "nimFrame")
+  p.blocks[0].sections[cpsLocals].addf("TFrame $1;$n", [frame])
+  result = ropecg(p.module, "\t$1.procname = $2; $1.filename = $3; " &
+                      " $1.line = $4; $1.len = -1; nimFrame(&$1);$n",
+                      [frame, procname, filename, line])
+
+proc deinitFrameNoDebug(p: BProc; frame: Rope): Rope =
+  result = ropecg(p.module, "\t#popFrameOfAddr(&$1);$n", [frame])
+
+proc deinitFrame(p: BProc): Rope =
+  result = ropecg(p.module, "\t#popFrame();$n", [])
+
+include ccgexprs
 
 # ----------------------------- dynamic library handling -----------------
 # We don't finalize dynamic libs as the OS does this for us.
@@ -508,177 +825,213 @@ proc loadDynamicLib(m: BModule, lib: PLib) =
   if not lib.generated:
     lib.generated = true
     var tmp = getTempName(m)
-    assert(lib.name == nil)
+    assert(lib.name == "")
     lib.name = tmp # BUGFIX: cgsym has awful side-effects
-    addf(m.s[cfsVars], "static void* $1;$n", [tmp])
+    m.s[cfsVars].addf("static void* $1;$n", [tmp])
     if lib.path.kind in {nkStrLit..nkTripleStrLit}:
       var s: TStringSeq = @[]
       libCandidates(lib.path.strVal, s)
-      rawMessage(hintDependency, lib.path.strVal)
-      var loadlib: Rope = nil
-      for i in countup(0, high(s)):
+      rawMessage(m.config, hintDependency, lib.path.strVal)
+      var loadlib: Rope = ""
+      for i in 0..high(s):
         inc(m.labels)
-        if i > 0: add(loadlib, "||")
-        appcg(m, loadlib, "($1 = #nimLoadLibrary((#NimStringDesc*) &$2))$n",
-              [tmp, getStrLit(m, s[i])])
+        if i > 0: loadlib.add("||")
+        let n = newStrNode(nkStrLit, s[i])
+        n.info = lib.path.info
+        appcg(m, loadlib, "($1 = #nimLoadLibrary(", [tmp])
+        genStringLiteral(m, n, loadlib)
+        loadlib.addf "))$n", []
       appcg(m, m.s[cfsDynLibInit],
-            "if (!($1)) #nimLoadLibraryError((#NimStringDesc*) &$2);$n",
-            [loadlib, getStrLit(m, lib.path.strVal)])
+            "if (!($1)) #nimLoadLibraryError(",
+            [loadlib])
+      genStringLiteral(m, lib.path, m.s[cfsDynLibInit])
+      m.s[cfsDynLibInit].addf ");$n", []
+
     else:
       var p = newProc(nil, m)
-      p.options = p.options - {optStackTrace, optEndb}
-      var dest: TLoc
-      initLocExpr(p, lib.path, dest)
-      add(m.s[cfsVars], p.s(cpsLocals))
-      add(m.s[cfsDynLibInit], p.s(cpsInit))
-      add(m.s[cfsDynLibInit], p.s(cpsStmts))
+      p.options.excl optStackTrace
+      p.flags.incl nimErrorFlagDisabled
+      var dest: TLoc = initLoc(locTemp, lib.path, OnStack)
+      dest.snippet = getTempName(m)
+      appcg(m, m.s[cfsDynLibInit],"$1 $2;$n",
+           [getTypeDesc(m, lib.path.typ, dkVar), rdLoc(dest)])
+      expr(p, lib.path, dest)
+
+      m.s[cfsVars].add(p.s(cpsLocals))
+      m.s[cfsDynLibInit].add(p.s(cpsInit))
+      m.s[cfsDynLibInit].add(p.s(cpsStmts))
       appcg(m, m.s[cfsDynLibInit],
            "if (!($1 = #nimLoadLibrary($2))) #nimLoadLibraryError($2);$n",
            [tmp, rdLoc(dest)])
 
-  if lib.name == nil: internalError("loadDynamicLib")
+  if lib.name == "": internalError(m.config, "loadDynamicLib")
 
 proc mangleDynLibProc(sym: PSym): Rope =
+  # we have to build this as a single rope in order not to trip the
+  # optimization in genInfixCall, see test tests/cpp/t8241.nim
   if sfCompilerProc in sym.flags:
-    # NOTE: sym.loc.r is the external name!
+    # NOTE: sym.loc.snippet is the external name!
     result = rope(sym.name.s)
   else:
-    result = "Dl_$1_" % [rope(sym.id)]
+    result = rope(strutils.`%`("Dl_$1_", $sym.id))
 
 proc symInDynamicLib(m: BModule, sym: PSym) =
   var lib = sym.annex
   let isCall = isGetProcAddr(lib)
-  var extname = sym.loc.r
+  var extname = sym.loc.snippet
   if not isCall: loadDynamicLib(m, lib)
   var tmp = mangleDynLibProc(sym)
-  sym.loc.r = tmp             # from now on we only need the internal name
+  sym.loc.snippet = tmp             # from now on we only need the internal name
   sym.typ.sym = nil           # generate a new name
   inc(m.labels, 2)
   if isCall:
     let n = lib.path
-    var a: TLoc
-    initLocExpr(m.initProc, n[0], a)
+    var a: TLoc = initLocExpr(m.initProc, n[0])
     var params = rdLoc(a) & "("
-    for i in 1 .. n.len-2:
-      initLocExpr(m.initProc, n[i], a)
+    for i in 1..<n.len-1:
+      a = initLocExpr(m.initProc, n[i])
       params.add(rdLoc(a))
       params.add(", ")
     let load = "\t$1 = ($2) ($3$4));$n" %
-        [tmp, getTypeDesc(m, sym.typ), params, makeCString($extname)]
+        [tmp, getTypeDesc(m, sym.typ, dkVar), params, makeCString($extname)]
     var last = lastSon(n)
-    if last.kind == nkHiddenStdConv: last = last.sons[1]
-    internalAssert(last.kind == nkStrLit)
+    if last.kind == nkHiddenStdConv: last = last[1]
+    internalAssert(m.config, last.kind == nkStrLit)
     let idx = last.strVal
     if idx.len == 0:
-      add(m.initProc.s(cpsStmts), load)
+      m.initProc.s(cpsStmts).add(load)
     elif idx.len == 1 and idx[0] in {'0'..'9'}:
-      add(m.extensionLoaders[idx[0]], load)
+      m.extensionLoaders[idx[0]].add(load)
     else:
-      internalError(sym.info, "wrong index: " & idx)
+      internalError(m.config, sym.info, "wrong index: " & idx)
   else:
     appcg(m, m.s[cfsDynLibInit],
         "\t$1 = ($2) #nimGetProcAddr($3, $4);$n",
-        [tmp, getTypeDesc(m, sym.typ), lib.name, makeCString($extname)])
-  addf(m.s[cfsVars], "$2 $1;$n", [sym.loc.r, getTypeDesc(m, sym.loc.t)])
+        [tmp, getTypeDesc(m, sym.typ, dkVar), lib.name, makeCString($extname)])
+  m.s[cfsVars].addf("$2 $1;$n", [sym.loc.snippet, getTypeDesc(m, sym.loc.t, dkVar)])
 
 proc varInDynamicLib(m: BModule, sym: PSym) =
   var lib = sym.annex
-  var extname = sym.loc.r
+  var extname = sym.loc.snippet
   loadDynamicLib(m, lib)
   incl(sym.loc.flags, lfIndirect)
   var tmp = mangleDynLibProc(sym)
-  sym.loc.r = tmp             # from now on we only need the internal name
+  sym.loc.snippet = tmp             # from now on we only need the internal name
   inc(m.labels, 2)
   appcg(m, m.s[cfsDynLibInit],
       "$1 = ($2*) #nimGetProcAddr($3, $4);$n",
-      [tmp, getTypeDesc(m, sym.typ), lib.name, makeCString($extname)])
-  addf(m.s[cfsVars], "$2* $1;$n",
-      [sym.loc.r, getTypeDesc(m, sym.loc.t)])
+      [tmp, getTypeDesc(m, sym.typ, dkVar), lib.name, makeCString($extname)])
+  m.s[cfsVars].addf("$2* $1;$n",
+      [sym.loc.snippet, getTypeDesc(m, sym.loc.t, dkVar)])
 
 proc symInDynamicLibPartial(m: BModule, sym: PSym) =
-  sym.loc.r = mangleDynLibProc(sym)
+  sym.loc.snippet = mangleDynLibProc(sym)
   sym.typ.sym = nil           # generate a new name
 
-proc cgsym(m: BModule, name: string): Rope =
-  var sym = magicsys.getCompilerProc(name)
+proc cgsymImpl(m: BModule; sym: PSym) {.inline.} =
+  case sym.kind
+  of skProc, skFunc, skMethod, skConverter, skIterator: genProc(m, sym)
+  of skVar, skResult, skLet: genVarPrototype(m, newSymNode sym)
+  of skType: discard getTypeDesc(m, sym.typ)
+  else: internalError(m.config, "cgsym: " & $sym.kind)
+
+proc cgsym(m: BModule, name: string) =
+  let sym = magicsys.getCompilerProc(m.g.graph, name)
+  if sym != nil:
+    cgsymImpl m, sym
+  else:
+    rawMessage(m.config, errGenerated, "system module needs: " & name)
+
+proc cgsymValue(m: BModule, name: string): Rope =
+  let sym = magicsys.getCompilerProc(m.g.graph, name)
   if sym != nil:
-    case sym.kind
-    of skProc, skFunc, skMethod, skConverter, skIterator: genProc(m, sym)
-    of skVar, skResult, skLet: genVarPrototype(m, newSymNode sym)
-    of skType: discard getTypeDesc(m, sym.typ)
-    else: internalError("cgsym: " & name & ": " & $sym.kind)
+    cgsymImpl m, sym
   else:
-    # we used to exclude the system module from this check, but for DLL
-    # generation support this sloppyness leads to hard to detect bugs, so
-    # we're picky here for the system module too:
-    rawMessage(errSystemNeeds, name)
-  result = sym.loc.r
+    rawMessage(m.config, errGenerated, "system module needs: " & name)
+  result = sym.loc.snippet
+  if m.hcrOn and sym != nil and sym.kind in {skProc..skIterator}:
+    result.addActualSuffixForHCR(m.module, sym)
 
 proc generateHeaders(m: BModule) =
-  add(m.s[cfsHeaders], tnl & "#include \"nimbase.h\"" & tnl)
+  var nimbase = m.config.nimbasePattern
+  if nimbase == "": nimbase = "nimbase.h"
+  m.s[cfsHeaders].addf("\L#include \"$1\"\L", [nimbase])
 
   for it in m.headerFiles:
     if it[0] == '#':
-      add(m.s[cfsHeaders], rope(it.replace('`', '"') & tnl))
-    elif it[0] notin {'\"', '<'}:
-      addf(m.s[cfsHeaders], "#include \"$1\"$N", [rope(it)])
+      m.s[cfsHeaders].add(rope(it.replace('`', '"') & "\L"))
+    elif it[0] notin {'"', '<'}:
+      m.s[cfsHeaders].addf("#include \"$1\"$N", [rope(it)])
     else:
-      addf(m.s[cfsHeaders], "#include $1$N", [rope(it)])
-  add(m.s[cfsHeaders], "#undef LANGUAGE_C" & tnl)
-  add(m.s[cfsHeaders], "#undef MIPSEB" & tnl)
-  add(m.s[cfsHeaders], "#undef MIPSEL" & tnl)
-  add(m.s[cfsHeaders], "#undef PPC" & tnl)
-  add(m.s[cfsHeaders], "#undef R3000" & tnl)
-  add(m.s[cfsHeaders], "#undef R4000" & tnl)
-  add(m.s[cfsHeaders], "#undef i386" & tnl)
-  add(m.s[cfsHeaders], "#undef linux" & tnl)
-  add(m.s[cfsHeaders], "#undef mips" & tnl)
-  add(m.s[cfsHeaders], "#undef near" & tnl)
-  add(m.s[cfsHeaders], "#undef powerpc" & tnl)
-  add(m.s[cfsHeaders], "#undef unix" & tnl)
-
-proc initFrame(p: BProc, procname, filename: Rope): Rope =
-  discard cgsym(p.module, "nimFrame")
-  if p.maxFrameLen > 0:
-    discard cgsym(p.module, "VarSlot")
-    result = rfmt(nil, "\tnimfrs_($1, $2, $3, $4);$n",
-                  procname, filename, p.maxFrameLen.rope,
-                  p.blocks[0].frameLen.rope)
-  else:
-    result = rfmt(nil, "\tnimfr_($1, $2);$n", procname, filename)
-
-proc deinitFrame(p: BProc): Rope =
-  result = rfmt(p.module, "\t#popFrame();$n")
+      m.s[cfsHeaders].addf("#include $1$N", [rope(it)])
+  m.s[cfsHeaders].add("""#undef LANGUAGE_C
+#undef MIPSEB
+#undef MIPSEL
+#undef PPC
+#undef R3000
+#undef R4000
+#undef i386
+#undef linux
+#undef mips
+#undef near
+#undef far
+#undef powerpc
+#undef unix
+""")
+
+proc openNamespaceNim(namespace: string; result: var Rope) =
+  result.add("namespace ")
+  result.add(namespace)
+  result.add(" {\L")
+
+proc closeNamespaceNim(result: var Rope) =
+  result.add("}\L")
 
 proc closureSetup(p: BProc, prc: PSym) =
   if tfCapturesEnv notin prc.typ.flags: return
   # prc.ast[paramsPos].last contains the type we're after:
   var ls = lastSon(prc.ast[paramsPos])
   if ls.kind != nkSym:
-    internalError(prc.info, "closure generation failed")
+    internalError(p.config, prc.info, "closure generation failed")
   var env = ls.sym
   #echo "created environment: ", env.id, " for ", prc.name.s
   assignLocalVar(p, ls)
   # generate cast assignment:
-  linefmt(p, cpsStmts, "$1 = ($2) ClE_0;$n",
-          rdLoc(env.loc), getTypeDesc(p.module, env.typ))
+  if p.config.selectedGC == gcGo:
+    linefmt(p, cpsStmts, "#unsureAsgnRef((void**) $1, ($2) ClE_0);$n",
+            [addrLoc(p.config, env.loc), getTypeDesc(p.module, env.typ)])
+  else:
+    linefmt(p, cpsStmts, "$1 = ($2) ClE_0;$n",
+            [rdLoc(env.loc), getTypeDesc(p.module, env.typ)])
+
+const harmless = {nkConstSection, nkTypeSection, nkEmpty, nkCommentStmt, nkTemplateDef,
+                  nkMacroDef, nkMixinStmt, nkBindStmt, nkFormalParams} +
+                  declarativeDefs
 
 proc containsResult(n: PNode): bool =
-  if n.kind == nkSym and n.sym.kind == skResult:
-    result = true
+  result = false
+  case n.kind
+  of succ(nkEmpty)..pred(nkSym), succ(nkSym)..nkNilLit, harmless:
+    discard
+  of nkReturnStmt:
+    for i in 0..<n.len:
+      if containsResult(n[i]): return true
+    result = n.len > 0 and n[0].kind == nkEmpty
+  of nkSym:
+    if n.sym.kind == skResult:
+      result = true
   else:
-    for i in 0..<n.safeLen:
+    for i in 0..<n.len:
       if containsResult(n[i]): return true
 
 proc easyResultAsgn(n: PNode): PNode =
-  const harmless = {nkConstSection, nkTypeSection, nkEmpty, nkCommentStmt} +
-                    declarativeDefs
+  result = nil
   case n.kind
   of nkStmtList, nkStmtListExpr:
     var i = 0
     while i < n.len and n[i].kind in harmless: inc i
     if i < n.len: result = easyResultAsgn(n[i])
-  of nkAsgn, nkFastAsgn:
+  of nkAsgn, nkFastAsgn, nkSinkAsgn:
     if n[0].kind == nkSym and n[0].sym.kind == skResult and not containsResult(n[1]):
       incl n.flags, nfPreventCg
       return n[1]
@@ -688,142 +1041,354 @@ proc easyResultAsgn(n: PNode): PNode =
       if result != nil: incl n.flags, nfPreventCg
   else: discard
 
-proc genProcAux(m: BModule, prc: PSym) =
+type
+  InitResultEnum = enum Unknown, InitSkippable, InitRequired
+
+proc allPathsAsgnResult(p: BProc; n: PNode): InitResultEnum =
+  # Exceptions coming from calls don't have not be considered here:
+  #
+  # proc bar(): string = raise newException(...)
+  #
+  # proc foo(): string =
+  #   # optimized out: 'reset(result)'
+  #   result = bar()
+  #
+  # try:
+  #   a = foo()
+  # except:
+  #   echo "a was not written to"
+  #
+  template allPathsInBranch(it) =
+    let a = allPathsAsgnResult(p, it)
+    case a
+    of InitRequired: return InitRequired
+    of InitSkippable: discard
+    of Unknown:
+      # sticky, but can be overwritten by InitRequired:
+      result = Unknown
+
+  result = Unknown
+  case n.kind
+  of nkStmtList, nkStmtListExpr:
+    for it in n:
+      result = allPathsAsgnResult(p, it)
+      if result != Unknown: return result
+  of nkAsgn, nkFastAsgn, nkSinkAsgn:
+    if n[0].kind == nkSym and n[0].sym.kind == skResult:
+      if not containsResult(n[1]):
+        if allPathsAsgnResult(p, n[1]) == InitRequired:
+          result = InitRequired
+        else:
+          result = InitSkippable
+      else: result = InitRequired
+    elif containsResult(n):
+      result = InitRequired
+    else:
+      result = allPathsAsgnResult(p, n[1])
+  of nkReturnStmt:
+    if n.len > 0:
+      if n[0].kind == nkEmpty and result != InitSkippable:
+        # This is a bare `return` statement, if `result` was not initialized
+        # anywhere else (or if we're not sure about this) let's require it to be
+        # initialized. This avoids cases like #9286 where this heuristic lead to
+        # wrong code being generated.
+        result = InitRequired
+      else: result = allPathsAsgnResult(p, n[0])
+  of nkIfStmt, nkIfExpr:
+    var exhaustive = false
+    result = InitSkippable
+    for it in n:
+      # Every condition must not use 'result':
+      if it.len == 2 and containsResult(it[0]):
+        return InitRequired
+      if it.len == 1: exhaustive = true
+      allPathsInBranch(it.lastSon)
+    # if the 'if' statement is not exhaustive and yet it touched 'result'
+    # in some way, say Unknown.
+    if not exhaustive: result = Unknown
+  of nkCaseStmt:
+    if containsResult(n[0]): return InitRequired
+    result = InitSkippable
+    var exhaustive = skipTypes(n[0].typ,
+        abstractVarRange-{tyTypeDesc}).kind notin {tyFloat..tyFloat128, tyString, tyCstring}
+    for i in 1..<n.len:
+      let it = n[i]
+      allPathsInBranch(it.lastSon)
+      if it.kind == nkElse: exhaustive = true
+    if not exhaustive: result = Unknown
+  of nkWhileStmt:
+    # some dubious code can assign the result in the 'while'
+    # condition and that would be fine. Everything else isn't:
+    result = allPathsAsgnResult(p, n[0])
+    if result == Unknown:
+      result = allPathsAsgnResult(p, n[1])
+      # we cannot assume that the 'while' loop is really executed at least once:
+      if result == InitSkippable: result = Unknown
+  of harmless:
+    result = Unknown
+  of nkGotoState, nkBreakState:
+    # give up for now.
+    result = InitRequired
+  of nkSym:
+    # some path reads from 'result' before it was written to!
+    if n.sym.kind == skResult: result = InitRequired
+  of nkTryStmt, nkHiddenTryStmt:
+    # We need to watch out for the following problem:
+    # try:
+    #   result = stuffThatRaises()
+    # except:
+    #   discard "result was not set"
+    #
+    # So ... even if the assignment to 'result' is the very first
+    # assignment this is not good enough! The only pattern we allow for
+    # is 'finally: result = x'
+    result = InitSkippable
+    allPathsInBranch(n[0])
+    for i in 1..<n.len:
+      if n[i].kind == nkFinally:
+        result = allPathsAsgnResult(p, n[i].lastSon)
+      else:
+        allPathsInBranch(n[i].lastSon)
+  of nkCallKinds:
+    if canRaiseDisp(p, n[0]):
+      result = InitRequired
+    else:
+      for i in 0..<n.safeLen:
+        allPathsInBranch(n[i])
+  of nkRaiseStmt:
+    result = InitRequired
+  of nkChckRangeF, nkChckRange64, nkChckRange:
+    # TODO: more checks might need to be covered like overflow, indexDefect etc.
+    # bug #22852
+    result = InitRequired
+  else:
+    for i in 0..<n.safeLen:
+      allPathsInBranch(n[i])
+
+proc getProcTypeCast(m: BModule, prc: PSym): Rope =
+  result = getTypeDesc(m, prc.loc.t)
+  if prc.typ.callConv == ccClosure:
+    var rettype, params: Rope = ""
+    var check = initIntSet()
+    genProcParams(m, prc.typ, rettype, params, check)
+    result = "$1(*)$2" % [rettype, params]
+
+proc genProcBody(p: BProc; procBody: PNode) =
+  genStmts(p, procBody) # modifies p.locals, p.init, etc.
+  if {nimErrorFlagAccessed, nimErrorFlagDeclared, nimErrorFlagDisabled} * p.flags == {nimErrorFlagAccessed}:
+    p.flags.incl nimErrorFlagDeclared
+    p.blocks[0].sections[cpsLocals].add(ropecg(p.module, "NIM_BOOL* nimErr_;$n", []))
+    p.blocks[0].sections[cpsInit].add(ropecg(p.module, "nimErr_ = #nimErrorFlag();$n", []))
+
+proc isNoReturn(m: BModule; s: PSym): bool {.inline.} =
+  sfNoReturn in s.flags and m.config.exc != excGoto
+
+proc genProcAux*(m: BModule, prc: PSym) =
   var p = newProc(prc, m)
-  var header = genProcHeader(m, prc)
-  var returnStmt: Rope = nil
+  var header = newRopeAppender()
+  let isCppMember = m.config.backend == backendCpp and sfCppMember * prc.flags != {}
+  if isCppMember:
+    genMemberProcHeader(m, prc, header)
+  else:
+    genProcHeader(m, prc, header)
+  var returnStmt: Rope = ""
   assert(prc.ast != nil)
-  if sfPure notin prc.flags and prc.typ.sons[0] != nil:
+
+  var procBody = transformBody(m.g.graph, m.idgen, prc, {})
+  if sfInjectDestructors in prc.flags:
+    procBody = injectDestructorCalls(m.g.graph, m.idgen, prc, procBody)
+
+  let tmpInfo = prc.info
+  discard freshLineInfo(p, prc.info)
+
+  if sfPure notin prc.flags and prc.typ.returnType != nil:
     if resultPos >= prc.ast.len:
-      internalError(prc.info, "proc has no result symbol")
-    let resNode = prc.ast.sons[resultPos]
+      internalError(m.config, prc.info, "proc has no result symbol")
+    let resNode = prc.ast[resultPos]
     let res = resNode.sym # get result symbol
-    if not isInvalidReturnType(prc.typ.sons[0]):
+    if not isInvalidReturnType(m.config, prc.typ) and sfConstructor notin prc.flags:
       if sfNoInit in prc.flags: incl(res.flags, sfNoInit)
-      if sfNoInit in prc.flags and p.module.compileToCpp and (let val = easyResultAsgn(prc.getBody); val != nil):
+      if sfNoInit in prc.flags and p.module.compileToCpp and (let val = easyResultAsgn(procBody); val != nil):
         var decl = localVarDecl(p, resNode)
-        var a: TLoc
-        initLocExprSingleUse(p, val, a)
-        linefmt(p, cpsStmts, "$1 = $2;$n", decl, rdLoc(a))
+        var a: TLoc = initLocExprSingleUse(p, val)
+        linefmt(p, cpsStmts, "$1 = $2;$n", [decl, rdLoc(a)])
       else:
         # declare the result symbol:
         assignLocalVar(p, resNode)
-        assert(res.loc.r != nil)
-        initLocalVar(p, res, immediateAsgn=false)
-      returnStmt = rfmt(nil, "\treturn $1;$n", rdLoc(res.loc))
+        assert(res.loc.snippet != "")
+        if p.config.selectedGC in {gcArc, gcAtomicArc, gcOrc} and
+            allPathsAsgnResult(p, procBody) == InitSkippable:
+          # In an ideal world the codegen could rely on injectdestructors doing its job properly
+          # and then the analysis step would not be required.
+          discard "result init optimized out"
+        else:
+          initLocalVar(p, res, immediateAsgn=false)
+      returnStmt = ropecg(p.module, "\treturn $1;$n", [rdLoc(res.loc)])
+    elif sfConstructor in prc.flags:
+      resNode.sym.loc.flags.incl lfIndirect
+      fillLoc(resNode.sym.loc, locParam, resNode, "this", OnHeap)
+      prc.loc.snippet = getTypeDesc(m, resNode.sym.loc.t, dkVar)
     else:
-      fillResult(resNode)
-      assignParam(p, res)
+      fillResult(p.config, resNode, prc.typ)
+      assignParam(p, res, prc.typ.returnType)
+      # We simplify 'unsureAsgn(result, nil); unsureAsgn(result, x)'
+      # to 'unsureAsgn(result, x)'
+      # Sketch why this is correct: If 'result' points to a stack location
+      # the 'unsureAsgn' is a nop. If it points to a global variable the
+      # global is either 'nil' or points to valid memory and so the RC operation
+      # succeeds without touching not-initialized memory.
+      if sfNoInit in prc.flags: discard
+      elif allPathsAsgnResult(p, procBody) == InitSkippable: discard
+      else:
+        resetLoc(p, res.loc)
       if skipTypes(res.typ, abstractInst).kind == tyArray:
         #incl(res.loc.flags, lfIndirect)
         res.loc.storage = OnUnknown
 
-  for i in countup(1, sonsLen(prc.typ.n) - 1):
-    let param = prc.typ.n.sons[i].sym
+  for i in 1..<prc.typ.n.len:
+    let param = prc.typ.n[i].sym
     if param.typ.isCompileTimeOnly: continue
-    assignParam(p, param)
+    assignParam(p, param, prc.typ.returnType)
   closureSetup(p, prc)
-  genStmts(p, prc.getBody) # modifies p.locals, p.init, etc.
-  var generatedProc: Rope
-  if sfNoReturn in prc.flags:
-    if hasDeclspec in extccomp.CC[extccomp.cCompiler].props:
+  genProcBody(p, procBody)
+
+  prc.info = tmpInfo
+
+  var generatedProc: Rope = ""
+  generatedProc.genCLineDir prc.info, m.config
+  if isNoReturn(p.module, prc):
+    if hasDeclspec in extccomp.CC[p.config.cCompiler].props and not isCppMember:
       header = "__declspec(noreturn) " & header
   if sfPure in prc.flags:
-    if hasDeclspec in extccomp.CC[extccomp.cCompiler].props:
+    if hasDeclspec in extccomp.CC[p.config.cCompiler].props and not isCppMember:
       header = "__declspec(naked) " & header
-    generatedProc = rfmt(nil, "$N$1 {$n$2$3$4}$N$N",
-                         header, p.s(cpsLocals), p.s(cpsInit), p.s(cpsStmts))
+    generatedProc.add ropecg(p.module, "$1 {$n$2$3$4}$N$N",
+                         [header, p.s(cpsLocals), p.s(cpsInit), p.s(cpsStmts)])
   else:
-    generatedProc = rfmt(nil, "$N$1 {$N", header)
-    add(generatedProc, initGCFrame(p))
+    if m.hcrOn and isReloadable(m, prc):
+      # Add forward declaration for "_actual"-suffixed functions defined in the same module (or inline).
+      # This fixes the use of methods and also the case when 2 functions within the same module
+      # call each other using directly the "_actual" versions (an optimization) - see issue #11608
+      m.s[cfsProcHeaders].addf("$1;\n", [header])
+    generatedProc.add ropecg(p.module, "$1 {$n", [header])
     if optStackTrace in prc.options:
-      add(generatedProc, p.s(cpsLocals))
+      generatedProc.add(p.s(cpsLocals))
       var procname = makeCString(prc.name.s)
-      add(generatedProc, initFrame(p, procname, prc.info.quotedFilename))
+      generatedProc.add(initFrame(p, procname, quotedFilename(p.config, prc.info)))
     else:
-      add(generatedProc, p.s(cpsLocals))
+      generatedProc.add(p.s(cpsLocals))
     if optProfiler in prc.options:
       # invoke at proc entry for recursion:
       appcg(p, cpsInit, "\t#nimProfile();$n", [])
-    if p.beforeRetNeeded: add(generatedProc, "{")
-    add(generatedProc, p.s(cpsInit))
-    add(generatedProc, p.s(cpsStmts))
-    if p.beforeRetNeeded: add(generatedProc, ~"\t}BeforeRet_: ;$n")
-    add(generatedProc, deinitGCFrame(p))
-    if optStackTrace in prc.options: add(generatedProc, deinitFrame(p))
-    add(generatedProc, returnStmt)
-    add(generatedProc, ~"}$N")
-  add(m.s[cfsProcs], generatedProc)
+    # this pair of {} is required for C++ (C++ is weird with its
+    # control flow integrity checks):
+    if beforeRetNeeded in p.flags: generatedProc.add("{")
+    generatedProc.add(p.s(cpsInit))
+    generatedProc.add(p.s(cpsStmts))
+    if beforeRetNeeded in p.flags: generatedProc.add("\t}BeforeRet_: ;\n")
+    if optStackTrace in prc.options: generatedProc.add(deinitFrame(p))
+    generatedProc.add(returnStmt)
+    generatedProc.add("}\n")
+  m.s[cfsProcs].add(generatedProc)
+  if isReloadable(m, prc):
+    m.s[cfsDynLibInit].addf("\t$1 = ($3) hcrRegisterProc($4, \"$1\", (void*)$2);$n",
+         [prc.loc.snippet, prc.loc.snippet & "_actual", getProcTypeCast(m, prc), getModuleDllPath(m, prc)])
 
 proc requiresExternC(m: BModule; sym: PSym): bool {.inline.} =
   result = (sfCompileToCpp in m.module.flags and
            sfCompileToCpp notin sym.getModule().flags and
-           gCmd != cmdCompileToCpp) or (
-           sym.flags * {sfImportc, sfInfixCall, sfCompilerProc} == {sfImportc} and
+           m.config.backend != backendCpp) or (
+           sym.flags * {sfInfixCall, sfCompilerProc, sfMangleCpp} == {} and
+           sym.flags * {sfImportc, sfExportc} != {} and
            sym.magic == mNone and
-           gCmd == cmdCompileToCpp)
+           m.config.backend == backendCpp)
 
 proc genProcPrototype(m: BModule, sym: PSym) =
   useHeader(m, sym)
-  if lfNoDecl in sym.loc.flags: return
+  if lfNoDecl in sym.loc.flags or sfCppMember * sym.flags != {}: return
   if lfDynamicLib in sym.loc.flags:
-    if getModule(sym).id != m.module.id and
+    if sym.itemId.module != m.module.position and
         not containsOrIncl(m.declaredThings, sym.id):
-      add(m.s[cfsVars], rfmt(nil, "extern $1 $2;$n",
-                        getTypeDesc(m, sym.loc.t), mangleDynLibProc(sym)))
+      m.s[cfsVars].add(ropecg(m, "$1 $2 $3;$n",
+                        [(if isReloadable(m, sym): "static" else: "extern"),
+                        getTypeDesc(m, sym.loc.t), mangleDynLibProc(sym)]))
+      if isReloadable(m, sym):
+        m.s[cfsDynLibInit].addf("\t$1 = ($2) hcrGetProc($3, \"$1\");$n",
+             [mangleDynLibProc(sym), getTypeDesc(m, sym.loc.t), getModuleDllPath(m, sym)])
   elif not containsOrIncl(m.declaredProtos, sym.id):
-    var header = genProcHeader(m, sym)
-    if sfNoReturn in sym.flags and hasDeclspec in extccomp.CC[cCompiler].props:
-      header = "__declspec(noreturn) " & header
-    if sym.typ.callConv != ccInline and requiresExternC(m, sym):
-      header = "extern \"C\" " & header
-    if sfPure in sym.flags and hasAttribute in CC[cCompiler].props:
-      header.add(" __attribute__((naked))")
-    if sfNoReturn in sym.flags and hasAttribute in CC[cCompiler].props:
-      header.add(" __attribute__((noreturn))")
-    add(m.s[cfsProcHeaders], rfmt(nil, "$1;$n", header))
-
+    let asPtr = isReloadable(m, sym)
+    var header = newRopeAppender()
+    genProcHeader(m, sym, header, asPtr)
+    if not asPtr:
+      if isNoReturn(m, sym) and hasDeclspec in extccomp.CC[m.config.cCompiler].props:
+        header = "__declspec(noreturn) " & header
+      if sym.typ.callConv != ccInline and requiresExternC(m, sym):
+        header = "extern \"C\" " & header
+      if sfPure in sym.flags and hasAttribute in CC[m.config.cCompiler].props:
+        header.add(" __attribute__((naked))")
+      if isNoReturn(m, sym) and hasAttribute in CC[m.config.cCompiler].props:
+        header.add(" __attribute__((noreturn))")
+    m.s[cfsProcHeaders].add(ropecg(m, "$1;$N", [header]))
+
+# TODO: figure out how to rename this - it DOES generate a forward declaration
 proc genProcNoForward(m: BModule, prc: PSym) =
   if lfImportCompilerProc in prc.loc.flags:
     fillProcLoc(m, prc.ast[namePos])
     useHeader(m, prc)
     # dependency to a compilerproc:
-    discard cgsym(m, prc.name.s)
+    cgsym(m, prc.name.s)
     return
   if lfNoDecl in prc.loc.flags:
     fillProcLoc(m, prc.ast[namePos])
-    useHeader(m, prc)
     genProcPrototype(m, prc)
+  elif lfDynamicLib in prc.loc.flags:
+    var q = findPendingModule(m, prc)
+    fillProcLoc(q, prc.ast[namePos])
+    genProcPrototype(m, prc)
+    if q != nil and not containsOrIncl(q.declaredThings, prc.id):
+      symInDynamicLib(q, prc)
+      # register the procedure even though it is in a different dynamic library and will not be
+      # reloadable (and has no _actual suffix) - other modules will need to be able to get it through
+      # the hcr dynlib (also put it in the DynLibInit section - right after it gets loaded)
+      if isReloadable(q, prc):
+        q.s[cfsDynLibInit].addf("\t$1 = ($2) hcrRegisterProc($3, \"$1\", (void*)$1);$n",
+            [prc.loc.snippet, getTypeDesc(q, prc.loc.t), getModuleDllPath(m, q.module)])
+    else:
+      symInDynamicLibPartial(m, prc)
   elif prc.typ.callConv == ccInline:
     # We add inline procs to the calling module to enable C based inlining.
     # This also means that a check with ``q.declaredThings`` is wrong, we need
     # a check for ``m.declaredThings``.
     if not containsOrIncl(m.declaredThings, prc.id):
       #if prc.loc.k == locNone:
-      fillProcLoc(m, prc.ast[namePos])
+      # mangle the inline proc based on the module where it is defined -
+      # not on the first module that uses it
+      let m2 = if m.config.symbolFiles != disabledSf: m
+               else: findPendingModule(m, prc)
+      fillProcLoc(m2, prc.ast[namePos])
       #elif {sfExportc, sfImportc} * prc.flags == {}:
       #  # reset name to restore consistency in case of hashing collisions:
       #  echo "resetting ", prc.id, " by ", m.module.name.s
-      #  prc.loc.r = nil
-      #  prc.loc.r = mangleName(m, prc)
-      useHeader(m, prc)
+      #  prc.loc.snippet = nil
+      #  prc.loc.snippet = mangleName(m, prc)
       genProcPrototype(m, prc)
       genProcAux(m, prc)
-  elif lfDynamicLib in prc.loc.flags:
-    var q = findPendingModule(m, prc)
-    fillProcLoc(q, prc.ast[namePos])
-    useHeader(m, prc)
-    genProcPrototype(m, prc)
-    if q != nil and not containsOrIncl(q.declaredThings, prc.id):
-      symInDynamicLib(q, prc)
-    else:
-      symInDynamicLibPartial(m, prc)
   elif sfImportc notin prc.flags:
     var q = findPendingModule(m, prc)
     fillProcLoc(q, prc.ast[namePos])
-    useHeader(m, prc)
+    # generate a getProc call to initialize the pointer for this
+    # externally-to-the-current-module defined proc, also important
+    # to do the declaredProtos check before the call to genProcPrototype
+    if isReloadable(m, prc) and prc.id notin m.declaredProtos and
+      q != nil and q.module.id != m.module.id:
+      m.s[cfsDynLibInit].addf("\t$1 = ($2) hcrGetProc($3, \"$1\");$n",
+           [prc.loc.snippet, getProcTypeCast(m, prc), getModuleDllPath(m, prc)])
     genProcPrototype(m, prc)
     if q != nil and not containsOrIncl(q.declaredThings, prc.id):
+      # make sure there is a "prototype" in the external module
+      # which will actually become a function pointer
+      if isReloadable(m, prc):
+        genProcPrototype(q, prc)
       genProcAux(q, prc)
   else:
     fillProcLoc(m, prc.ast[namePos])
@@ -831,25 +1396,16 @@ proc genProcNoForward(m: BModule, prc: PSym) =
     if sfInfixCall notin prc.flags: genProcPrototype(m, prc)
 
 proc requestConstImpl(p: BProc, sym: PSym) =
-  var m = p.module
-  useHeader(m, sym)
-  if sym.loc.k == locNone:
-    fillLoc(sym.loc, locData, sym.ast, mangleName(p.module, sym), OnStatic)
-  if lfNoDecl in sym.loc.flags: return
-  # declare implementation:
-  var q = findPendingModule(m, sym)
-  if q != nil and not containsOrIncl(q.declaredThings, sym.id):
-    assert q.initProc.module == q
-    addf(q.s[cfsData], "NIM_CONST $1 $2 = $3;$n",
-        [getTypeDesc(q, sym.typ), sym.loc.r, genConstExpr(q.initProc, sym.ast)])
-  # declare header:
-  if q != m and not containsOrIncl(m.declaredThings, sym.id):
-    assert(sym.loc.r != nil)
-    let headerDecl = "extern NIM_CONST $1 $2;$n" %
-        [getTypeDesc(m, sym.loc.t), sym.loc.r]
-    add(m.s[cfsData], headerDecl)
-    if sfExportc in sym.flags and p.module.g.generatedHeader != nil:
-      add(p.module.g.generatedHeader.s[cfsData], headerDecl)
+  if genConstSetup(p, sym):
+    let m = p.module
+    # declare implementation:
+    var q = findPendingModule(m, sym)
+    if q != nil and not containsOrIncl(q.declaredThings, sym.id):
+      assert q.initProc.module == q
+      genConstDefinition(q, p, sym)
+    # declare header:
+    if q != m and not containsOrIncl(m.declaredThings, sym.id):
+      genConstHeader(m, q, p, sym)
 
 proc isActivated(prc: PSym): bool = prc.typ != nil
 
@@ -867,109 +1423,180 @@ proc genProc(m: BModule, prc: PSym) =
         if not containsOrIncl(m.g.generatedHeader.declaredThings, prc.id):
           genProcAux(m.g.generatedHeader, prc)
 
-proc genVarPrototypeAux(m: BModule, n: PNode) =
+proc genVarPrototype(m: BModule, n: PNode) =
   #assert(sfGlobal in sym.flags)
   let sym = n.sym
   useHeader(m, sym)
-  fillLoc(sym.loc, locGlobalVar, n, mangleName(m, sym), OnHeap)
-  if (lfNoDecl in sym.loc.flags) or containsOrIncl(m.declaredThings, sym.id):
+  fillBackendName(m, sym)
+  fillLoc(sym.loc, locGlobalVar, n, OnHeap)
+  if treatGlobalDifferentlyForHCR(m, sym): incl(sym.loc.flags, lfIndirect)
+
+  if (lfNoDecl in sym.loc.flags) or contains(m.declaredThings, sym.id):
     return
   if sym.owner.id != m.module.id:
     # else we already have the symbol generated!
-    assert(sym.loc.r != nil)
+    assert(sym.loc.snippet != "")
+    incl(m.declaredThings, sym.id)
     if sfThread in sym.flags:
       declareThreadVar(m, sym, true)
     else:
-      add(m.s[cfsVars], "extern ")
-      add(m.s[cfsVars], getTypeDesc(m, sym.loc.t))
-      if lfDynamicLib in sym.loc.flags: add(m.s[cfsVars], "*")
-      if sfRegister in sym.flags: add(m.s[cfsVars], " register")
-      if sfVolatile in sym.flags: add(m.s[cfsVars], " volatile")
-      addf(m.s[cfsVars], " $1;$n", [sym.loc.r])
+      if sym.kind in {skLet, skVar, skField, skForVar} and sym.alignment > 0:
+        m.s[cfsVars].addf "NIM_ALIGN($1) ", [rope(sym.alignment)]
+      m.s[cfsVars].add(if m.hcrOn: "static " else: "extern ")
+      m.s[cfsVars].add(getTypeDesc(m, sym.loc.t, dkVar))
+      if m.hcrOn: m.s[cfsVars].add("*")
+      if lfDynamicLib in sym.loc.flags: m.s[cfsVars].add("*")
+      if sfRegister in sym.flags: m.s[cfsVars].add(" register")
+      if sfVolatile in sym.flags: m.s[cfsVars].add(" volatile")
+      if sfNoalias in sym.flags: m.s[cfsVars].add(" NIM_NOALIAS")
+      m.s[cfsVars].addf(" $1;$n", [sym.loc.snippet])
+      if m.hcrOn: m.initProc.procSec(cpsLocals).addf(
+        "\t$1 = ($2*)hcrGetGlobal($3, \"$1\");$n", [sym.loc.snippet,
+        getTypeDesc(m, sym.loc.t, dkVar), getModuleDllPath(m, sym)])
+
+proc addNimDefines(result: var Rope; conf: ConfigRef) {.inline.} =
+  result.addf("#define NIM_INTBITS $1\L", [
+    platform.CPU[conf.target.targetCPU].intSize.rope])
+  if conf.cppCustomNamespace.len > 0:
+    result.add("#define USE_NIM_NAMESPACE ")
+    result.add(conf.cppCustomNamespace)
+    result.add("\L")
+  if conf.isDefined("nimEmulateOverflowChecks"):
+    result.add("#define NIM_EmulateOverflowChecks\L")
+
+proc headerTop(): Rope =
+  result = "/* Generated by Nim Compiler v$1 */$N" % [rope(VersionAsString)]
+
+proc getCopyright(conf: ConfigRef; cfile: Cfile): Rope =
+  result = headerTop()
+  if optCompileOnly notin conf.globalOptions:
+    result.add ("/* Compiled for: $1, $2, $3 */$N" &
+        "/* Command for C compiler:$n   $4 */$N") %
+        [rope(platform.OS[conf.target.targetOS].name),
+        rope(platform.CPU[conf.target.targetCPU].name),
+        rope(extccomp.CC[conf.cCompiler].name),
+        rope(getCompileCFileCmd(conf, cfile))]
+
+proc getFileHeader(conf: ConfigRef; cfile: Cfile): Rope =
+  result = getCopyright(conf, cfile)
+  if conf.hcrOn: result.add("#define NIM_HOT_CODE_RELOADING\L")
+  addNimDefines(result, conf)
+
+proc getSomeNameForModule(conf: ConfigRef, filename: AbsoluteFile): Rope =
+  ## Returns a mangled module name.
+  result = mangleModuleName(conf, filename).mangle
+
+proc getSomeNameForModule(m: BModule): Rope =
+  ## Returns a mangled module name.
+  assert m.module.kind == skModule
+  assert m.module.owner.kind == skPackage
+  result = mangleModuleName(m.g.config, m.filename).mangle
+
+proc getSomeInitName(m: BModule, suffix: string): Rope =
+  if not m.hcrOn:
+    result = getSomeNameForModule(m)
+  else:
+    result = ""
+  result.add suffix
 
-proc genVarPrototype(m: BModule, n: PNode) =
-  genVarPrototypeAux(m, n)
-
-proc addIntTypes(result: var Rope) {.inline.} =
-  addf(result, "#define NIM_NEW_MANGLING_RULES" & tnl &
-               "#define NIM_INTBITS $1" & tnl, [
-    platform.CPU[targetCPU].intSize.rope])
-
-proc getCopyright(cfile: Cfile): Rope =
-  if optCompileOnly in gGlobalOptions:
-    result = ("/* Generated by Nim Compiler v$1 */$N" &
-        "/*   (c) " & CompileDate.substr(0, 3) & " Andreas Rumpf */$N" &
-        "/* The generated code is subject to the original license. */$N") %
-        [rope(VersionAsString)]
+proc getInitName(m: BModule): Rope =
+  if sfMainModule in m.module.flags:
+    # generate constant name for main module, for "easy" debugging.
+    result = rope(m.config.nimMainPrefix) & rope"NimMainModule"
   else:
-    result = ("/* Generated by Nim Compiler v$1 */$N" &
-        "/*   (c) " & CompileDate.substr(0, 3) & " Andreas Rumpf */$N" &
-        "/* The generated code is subject to the original license. */$N" &
-        "/* Compiled for: $2, $3, $4 */$N" &
-        "/* Command for C compiler:$n   $5 */$N") %
-        [rope(VersionAsString),
-        rope(platform.OS[targetOS].name),
-        rope(platform.CPU[targetCPU].name),
-        rope(extccomp.CC[extccomp.cCompiler].name),
-        rope(getCompileCFileCmd(cfile))]
-
-proc getFileHeader(cfile: Cfile): Rope =
-  result = getCopyright(cfile)
-  addIntTypes(result)
-
-proc genFilenames(m: BModule): Rope =
-  discard cgsym(m, "dbgRegisterFilename")
-  result = nil
-  for i in 0.. <fileInfos.len:
-    result.addf("dbgRegisterFilename($1);$N", [fileInfos[i].projPath.makeCString])
+    result = getSomeInitName(m, "Init000")
+
+proc getDatInitName(m: BModule): Rope = getSomeInitName(m, "DatInit000")
+proc getHcrInitName(m: BModule): Rope = getSomeInitName(m, "HcrInit000")
+
+proc hcrGetProcLoadCode(m: BModule, sym, prefix, handle, getProcFunc: string): Rope
 
 proc genMainProc(m: BModule) =
+  ## this function is called in cgenWriteModules after all modules are closed,
+  ## it means raising dependency on the symbols is too late as it will not propagate
+  ## into other modules, only simple rope manipulations are allowed
+  var preMainCode: Rope = ""
+  if m.hcrOn:
+    proc loadLib(handle: string, name: string): Rope =
+      result = ""
+      let prc = magicsys.getCompilerProc(m.g.graph, name)
+      assert prc != nil
+      let n = newStrNode(nkStrLit, prc.annex.path.strVal)
+      n.info = prc.annex.path.info
+      var strLit = newRopeAppender()
+      genStringLiteral(m, n, strLit)
+      appcg(m, result, "\tif (!($1 = #nimLoadLibrary($2)))$N" &
+                       "\t\t#nimLoadLibraryError($2);$N",
+                       [handle, strLit])
+
+    preMainCode.add(loadLib("hcr_handle", "hcrGetProc"))
+    if m.config.selectedGC in {gcArc, gcAtomicArc, gcOrc}:
+      preMainCode.add("\t$1PreMain();\L" % [rope m.config.nimMainPrefix])
+    else:
+      preMainCode.add("\tvoid* rtl_handle;\L")
+      preMainCode.add(loadLib("rtl_handle", "nimGC_setStackBottom"))
+      preMainCode.add(hcrGetProcLoadCode(m, "nimGC_setStackBottom", "nimrtl_", "rtl_handle", "nimGetProcAddr"))
+      preMainCode.add("\tinner = $1PreMain;\L" % [rope m.config.nimMainPrefix])
+      preMainCode.add("\tinitStackBottomWith_actual((void *)&inner);\L")
+      preMainCode.add("\t(*inner)();\L")
+  else:
+    preMainCode.add("\t$1PreMain();\L" % [rope m.config.nimMainPrefix])
+
+  var posixCmdLine: Rope = ""
+  if optNoMain notin m.config.globalOptions:
+    posixCmdLine.add "N_LIB_PRIVATE int cmdCount;\L"
+    posixCmdLine.add "N_LIB_PRIVATE char** cmdLine;\L"
+    posixCmdLine.add "N_LIB_PRIVATE char** gEnv;\L"
+
   const
     # The use of a volatile function pointer to call Pre/NimMainInner
     # prevents inlining of the NimMainInner function and dependent
     # functions, which might otherwise merge their stack frames.
-    PreMainBody =
-      "void PreMainInner(void) {$N" &
-      "\tsystemInit000();$N" &
-      "$1" &
+
+    PreMainBody = "$N" &
+      "N_LIB_PRIVATE void $3PreMainInner(void) {$N" &
       "$2" &
-      "$3" &
       "}$N$N" &
-      "void PreMain(void) {$N" &
+      "$4" &
+      "N_LIB_PRIVATE void $3PreMain(void) {$N" &
+      "##if $5$N" & # 1 for volatile call, 0 for non-volatile
       "\tvoid (*volatile inner)(void);$N" &
-      "\tsystemDatInit000();$N" &
-      "\tinner = PreMainInner;$N" &
-      "$4$5" &
+      "\tinner = $3PreMainInner;$N" &
+      "$1" &
       "\t(*inner)();$N" &
+      "##else$N" &
+      "$1" &
+      "\t$3PreMainInner();$N" &
+      "##endif$N" &
       "}$N$N"
 
     MainProcs =
-      "\tNimMain();$N"
+      "\t$^NimMain();$N"
 
     MainProcsWithResult =
-      MainProcs & "\treturn nim_program_result;$N"
+      MainProcs & ("\treturn $1nim_program_result;$N")
 
-    NimMainInner = "N_CDECL(void, NimMainInner)(void) {$N" &
+    NimMainInner = "N_LIB_PRIVATE N_CDECL(void, $5NimMainInner)(void) {$N" &
         "$1" &
       "}$N$N"
 
     NimMainProc =
-      "N_CDECL(void, NimMain)(void) {$N" &
-        "\tvoid (*volatile inner)(void);$N" &
-        "\tPreMain();$N" &
-        "\tinner = NimMainInner;$N" &
-        "$2" &
-        "\t(*inner)();$N" &
+      "N_CDECL(void, $5NimMain)(void) {$N" &
+      "##if $6$N" & # 1 for volatile call, 0 for non-volatile
+      "\tvoid (*volatile inner)(void);$N" &
+      "$4" &
+      "\tinner = $5NimMainInner;$N" &
+      "$2" &
+      "\t(*inner)();$N" &
+      "##else$N" &
+      "$4" &
+      "$2" &
+      "\t$5NimMainInner();$N" &
+      "##endif$N" &
       "}$N$N"
 
     NimMainBody = NimMainInner & NimMainProc
 
-    PosixNimMain =
-      "int cmdCount;$N" &
-      "char** cmdLine;$N" &
-      "char** gEnv;$N" &
-      NimMainBody
-
     PosixCMain =
       "int main(int argc, char** args, char** env) {$N" &
         "\tcmdLine = args;$N" &
@@ -996,194 +1623,442 @@ proc genMainProc(m: BModule) =
     WinCDllMain =
       "BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fwdreason, $N" &
       "                    LPVOID lpvReserved) {$N" &
-      "\tif(fwdreason == DLL_PROCESS_ATTACH) {$N" & MainProcs & "}$N" &
+      "\tif (fwdreason == DLL_PROCESS_ATTACH) {$N" & MainProcs & "\t}$N" &
       "\treturn 1;$N}$N$N"
 
     PosixNimDllMain = WinNimDllMain
 
     PosixCDllMain =
-      "void NIM_POSIX_INIT NimMainInit(void) {$N" &
+      "N_LIB_PRIVATE void NIM_POSIX_INIT NimMainInit(void) {$N" &
         MainProcs &
       "}$N$N"
 
     GenodeNimMain =
-      "Libc::Env *genodeEnv;$N" &
+      "extern Genode::Env *nim_runtime_env;$N" &
+      "extern \"C\" void nim_component_construct(Genode::Env*);$N$N" &
       NimMainBody
 
     ComponentConstruct =
       "void Libc::Component::construct(Libc::Env &env) {$N" &
-      "\tgenodeEnv = &env;$N" &
+      "\t// Set Env used during runtime initialization$N" &
+      "\tnim_runtime_env = &env;$N" &
       "\tLibc::with_libc([&] () {$N\t" &
+      "\t// Initialize runtime and globals$N" &
       MainProcs &
+      "\t// Call application construct$N" &
+      "\t\tnim_component_construct(&env);$N" &
       "\t});$N" &
       "}$N$N"
 
-  var nimMain, otherMain: FormatStr
-  if platform.targetOS == osWindows and
-      gGlobalOptions * {optGenGuiApp, optGenDynLib} != {}:
-    if optGenGuiApp in gGlobalOptions:
-      nimMain = WinNimMain
-      otherMain = WinCMain
-    else:
-      nimMain = WinNimDllMain
-      otherMain = WinCDllMain
+  if m.config.target.targetOS == osWindows and
+      m.config.globalOptions * {optGenGuiApp, optGenDynLib} != {}:
     m.includeHeader("<windows.h>")
-  elif platform.targetOS == osGenode:
-    nimMain = GenodeNimMain
-    otherMain = ComponentConstruct
-  elif optGenDynLib in gGlobalOptions:
-    nimMain = PosixNimDllMain
-    otherMain = PosixCDllMain
-  elif platform.targetOS == osStandalone:
-    nimMain = PosixNimMain
-    otherMain = StandaloneCMain
-  else:
-    nimMain = PosixNimMain
-    otherMain = PosixCMain
-  if m.g.breakpoints != nil: discard cgsym(m, "dbgRegisterBreakpoint")
-  if optEndb in gOptions:
-    m.g.breakpoints.add(m.genFilenames)
+  elif m.config.target.targetOS == osGenode:
+    m.includeHeader("<libc/component.h>")
 
   let initStackBottomCall =
-    if platform.targetOS == osStandalone or gSelectedGC == gcNone: "".rope
-    else: ropecg(m, "\t#initStackBottomWith((void *)&inner);$N")
+    if m.config.target.targetOS == osStandalone or m.config.selectedGC in {gcNone, gcArc, gcAtomicArc, gcOrc}: "".rope
+    else: ropecg(m, "\t#initStackBottomWith((void *)&inner);$N", [])
   inc(m.labels)
-  appcg(m, m.s[cfsProcs], PreMainBody, [
-    m.g.mainDatInit, m.g.breakpoints, m.g.otherModsInit,
-     if emulatedThreadVars() and platform.targetOS != osStandalone:
-       ropecg(m, "\t#initThreadVarsEmulation();$N")
-     else:
-       "".rope,
-     initStackBottomCall])
-
-  appcg(m, m.s[cfsProcs], nimMain,
-        [m.g.mainModInit, initStackBottomCall, rope(m.labels)])
-  if optNoMain notin gGlobalOptions:
-    appcg(m, m.s[cfsProcs], otherMain, [])
-
-proc getSomeInitName(m: PSym, suffix: string): Rope =
-  assert m.kind == skModule
-  assert m.owner.kind == skPackage
-  if {sfSystemModule, sfMainModule} * m.flags == {}:
-    result = m.owner.name.s.mangle.rope
-    result.add "_"
-  result.add m.name.s
-  result.add suffix
 
-proc getInitName(m: PSym): Rope =
-  if sfMainModule in m.flags:
-    # generate constant name for main module, for "easy" debugging.
-    result = rope"NimMainModule"
+  let isVolatile = if m.config.selectedGC notin {gcNone, gcArc, gcAtomicArc, gcOrc}: "1" else: "0"
+  appcg(m, m.s[cfsProcs], PreMainBody, [m.g.mainDatInit, m.g.otherModsInit, m.config.nimMainPrefix, posixCmdLine, isVolatile])
+
+  if m.config.target.targetOS == osWindows and
+      m.config.globalOptions * {optGenGuiApp, optGenDynLib} != {}:
+    if optGenGuiApp in m.config.globalOptions:
+      const nimMain = WinNimMain
+      appcg(m, m.s[cfsProcs], nimMain,
+        [m.g.mainModInit, initStackBottomCall, m.labels, preMainCode, m.config.nimMainPrefix, isVolatile])
+    else:
+      const nimMain = WinNimDllMain
+      appcg(m, m.s[cfsProcs], nimMain,
+        [m.g.mainModInit, initStackBottomCall, m.labels, preMainCode, m.config.nimMainPrefix, isVolatile])
+  elif m.config.target.targetOS == osGenode:
+    const nimMain = GenodeNimMain
+    appcg(m, m.s[cfsProcs], nimMain,
+        [m.g.mainModInit, initStackBottomCall, m.labels, preMainCode, m.config.nimMainPrefix, isVolatile])
+  elif optGenDynLib in m.config.globalOptions:
+    const nimMain = PosixNimDllMain
+    appcg(m, m.s[cfsProcs], nimMain,
+        [m.g.mainModInit, initStackBottomCall, m.labels, preMainCode, m.config.nimMainPrefix, isVolatile])
   else:
-    result = getSomeInitName(m, "Init000")
+    const nimMain = NimMainBody
+    appcg(m, m.s[cfsProcs], nimMain,
+        [m.g.mainModInit, initStackBottomCall, m.labels, preMainCode, m.config.nimMainPrefix, isVolatile])
+
+  if optNoMain notin m.config.globalOptions:
+    if m.config.cppCustomNamespace.len > 0:
+      closeNamespaceNim(m.s[cfsProcs])
+      m.s[cfsProcs].add "using namespace " & m.config.cppCustomNamespace & ";\L"
+
+    if m.config.target.targetOS == osWindows and
+        m.config.globalOptions * {optGenGuiApp, optGenDynLib} != {}:
+      if optGenGuiApp in m.config.globalOptions:
+        const otherMain = WinCMain
+        appcg(m, m.s[cfsProcs], otherMain, [if m.hcrOn: "*" else: "", m.config.nimMainPrefix])
+      else:
+        const otherMain = WinCDllMain
+        appcg(m, m.s[cfsProcs], otherMain, [m.config.nimMainPrefix])
+    elif m.config.target.targetOS == osGenode:
+      const otherMain = ComponentConstruct
+      appcg(m, m.s[cfsProcs], otherMain, [m.config.nimMainPrefix])
+    elif optGenDynLib in m.config.globalOptions:
+      const otherMain = PosixCDllMain
+      appcg(m, m.s[cfsProcs], otherMain, [m.config.nimMainPrefix])
+    elif m.config.target.targetOS == osStandalone:
+      const otherMain = StandaloneCMain
+      appcg(m, m.s[cfsProcs], otherMain, [m.config.nimMainPrefix])
+    else:
+      const otherMain = PosixCMain
+      appcg(m, m.s[cfsProcs], otherMain, [if m.hcrOn: "*" else: "", m.config.nimMainPrefix])
+
+    if m.config.cppCustomNamespace.len > 0:
+      openNamespaceNim(m.config.cppCustomNamespace, m.s[cfsProcs])
+
+proc registerInitProcs*(g: BModuleList; m: PSym; flags: set[ModuleBackendFlag]) =
+  ## Called from the IC backend.
+  if HasDatInitProc in flags:
+    let datInit = getSomeNameForModule(g.config, g.config.toFullPath(m.info.fileIndex).AbsoluteFile) & "DatInit000"
+    g.mainModProcs.addf("N_LIB_PRIVATE N_NIMCALL(void, $1)(void);$N", [datInit])
+    g.mainDatInit.addf("\t$1();$N", [datInit])
+  if HasModuleInitProc in flags:
+    let init = getSomeNameForModule(g.config, g.config.toFullPath(m.info.fileIndex).AbsoluteFile) & "Init000"
+    g.mainModProcs.addf("N_LIB_PRIVATE N_NIMCALL(void, $1)(void);$N", [init])
+    let initCall = "\t$1();$N" % [init]
+    if sfMainModule in m.flags:
+      g.mainModInit.add(initCall)
+    elif sfSystemModule in m.flags:
+      g.mainDatInit.add(initCall) # systemInit must called right after systemDatInit if any
+    else:
+      g.otherModsInit.add(initCall)
 
-proc getDatInitName(m: PSym): Rope = getSomeInitName(m, "DatInit000")
+proc whichInitProcs*(m: BModule): set[ModuleBackendFlag] =
+  # called from IC.
+  result = {}
+  if m.hcrOn or m.preInitProc.s(cpsInit).len > 0 or m.preInitProc.s(cpsStmts).len > 0:
+    result.incl HasModuleInitProc
+  for i in cfsTypeInit1..cfsDynLibInit:
+    if m.s[i].len != 0:
+      result.incl HasDatInitProc
+      break
 
-proc registerModuleToMain(g: BModuleList; m: PSym) =
-  var
+proc registerModuleToMain(g: BModuleList; m: BModule) =
+  let
     init = m.getInitName
     datInit = m.getDatInitName
-  addf(g.mainModProcs, "NIM_EXTERNC N_NOINLINE(void, $1)(void);$N", [init])
-  addf(g.mainModProcs, "NIM_EXTERNC N_NOINLINE(void, $1)(void);$N", [datInit])
-  if sfSystemModule notin m.flags:
-    addf(g.mainDatInit, "\t$1();$N", [datInit])
+
+  if m.hcrOn:
+    var hcrModuleMeta = "$nN_LIB_PRIVATE const char* hcr_module_list[] = {$n" % []
+    let systemModulePath = getModuleDllPath(m, g.modules[g.graph.config.m.systemFileIdx.int].module)
+    let mainModulePath = getModuleDllPath(m, m.module)
+    if sfMainModule in m.module.flags:
+      hcrModuleMeta.addf("\t$1,$n", [systemModulePath])
+    g.graph.importDeps.withValue(FileIndex(m.module.position), deps):
+      for curr in deps[]:
+        hcrModuleMeta.addf("\t$1,$n", [getModuleDllPath(m, g.modules[curr.int].module)])
+    hcrModuleMeta.addf("\t\"\"};$n", [])
+    hcrModuleMeta.addf("$nN_LIB_EXPORT N_NIMCALL(void**, HcrGetImportedModules)() { return (void**)hcr_module_list; }$n", [])
+    hcrModuleMeta.addf("$nN_LIB_EXPORT N_NIMCALL(char*, HcrGetSigHash)() { return \"$1\"; }$n$n",
+                          [($sigHash(m.module, m.config)).rope])
+    if sfMainModule in m.module.flags:
+      g.mainModProcs.add(hcrModuleMeta)
+      g.mainModProcs.addf("static void* hcr_handle;$N", [])
+      g.mainModProcs.addf("N_LIB_EXPORT N_NIMCALL(void, $1)(void);$N", [init])
+      g.mainModProcs.addf("N_LIB_EXPORT N_NIMCALL(void, $1)(void);$N", [datInit])
+      g.mainModProcs.addf("N_LIB_EXPORT N_NIMCALL(void, $1)(void*, N_NIMCALL_PTR(void*, getProcAddr)(void*, char*));$N", [m.getHcrInitName])
+      g.mainModProcs.addf("N_LIB_EXPORT N_NIMCALL(void, HcrCreateTypeInfos)(void);$N", [])
+      g.mainModInit.addf("\t$1();$N", [init])
+      g.otherModsInit.addf("\thcrInit((void**)hcr_module_list, $1, $2, $3, hcr_handle, nimGetProcAddr);$n",
+                            [mainModulePath, systemModulePath, datInit])
+      g.mainDatInit.addf("\t$1(hcr_handle, nimGetProcAddr);$N", [m.getHcrInitName])
+      g.mainDatInit.addf("\thcrAddModule($1);\n", [mainModulePath])
+      g.mainDatInit.addf("\tHcrCreateTypeInfos();$N", [])
+      # nasty nasty hack to get the command line functionality working with HCR
+      # register the 2 variables on behalf of the os module which might not even
+      # be loaded (in which case it will get collected but that is not a problem)
+      # EDIT: indeed, this hack, in combination with another un-necessary one
+      # (`makeCString` was doing line wrap of string litterals) was root cause for
+      # bug #16265.
+      let osModulePath = ($systemModulePath).replace("stdlib_system", "stdlib_os").rope
+      g.mainDatInit.addf("\thcrAddModule($1);\n", [osModulePath])
+      g.mainDatInit.add("\tint* cmd_count;\n")
+      g.mainDatInit.add("\tchar*** cmd_line;\n")
+      g.mainDatInit.addf("\thcrRegisterGlobal($1, \"cmdCount\", sizeof(cmd_count), NULL, (void**)&cmd_count);$N", [osModulePath])
+      g.mainDatInit.addf("\thcrRegisterGlobal($1, \"cmdLine\", sizeof(cmd_line), NULL, (void**)&cmd_line);$N", [osModulePath])
+      g.mainDatInit.add("\t*cmd_count = cmdCount;\n")
+      g.mainDatInit.add("\t*cmd_line = cmdLine;\n")
+    else:
+      m.s[cfsInitProc].add(hcrModuleMeta)
+    return
+
+  if m.s[cfsDatInitProc].len > 0:
+    g.mainModProcs.addf("N_LIB_PRIVATE N_NIMCALL(void, $1)(void);$N", [datInit])
+    g.mainDatInit.addf("\t$1();$N", [datInit])
+
+  # Initialization of TLS and GC should be done in between
+  # systemDatInit and systemInit calls if any
+  if sfSystemModule in m.module.flags:
+    if emulatedThreadVars(m.config) and m.config.target.targetOS != osStandalone:
+      g.mainDatInit.add(ropecg(m, "\t#initThreadVarsEmulation();$N", []))
+    if m.config.target.targetOS != osStandalone and m.config.selectedGC notin {gcNone, gcArc, gcAtomicArc, gcOrc}:
+      g.mainDatInit.add(ropecg(m, "\t#initStackBottomWith((void *)&inner);$N", []))
+
+  if m.s[cfsInitProc].len > 0:
+    g.mainModProcs.addf("N_LIB_PRIVATE N_NIMCALL(void, $1)(void);$N", [init])
     let initCall = "\t$1();$N" % [init]
-    if sfMainModule in m.flags:
-      add(g.mainModInit, initCall)
+    if sfMainModule in m.module.flags:
+      g.mainModInit.add(initCall)
+    elif sfSystemModule in m.module.flags:
+      g.mainDatInit.add(initCall) # systemInit must called right after systemDatInit if any
     else:
-      add(g.otherModsInit, initCall)
+      g.otherModsInit.add(initCall)
+
+proc genDatInitCode(m: BModule) =
+  ## this function is called in cgenWriteModules after all modules are closed,
+  ## it means raising dependency on the symbols is too late as it will not propagate
+  ## into other modules, only simple rope manipulations are allowed
+
+  var moduleDatInitRequired = m.hcrOn
+
+  var prc = "$1 N_NIMCALL(void, $2)(void) {$N" %
+    [rope(if m.hcrOn: "N_LIB_EXPORT" else: "N_LIB_PRIVATE"), getDatInitName(m)]
+
+  # we don't want to break into such init code - could happen if a line
+  # directive from a function written by the user spills after itself
+  genCLineDir(prc, InvalidFileIdx, 999999, m.config)
+
+  for i in cfsTypeInit1..cfsDynLibInit:
+    if m.s[i].len != 0:
+      moduleDatInitRequired = true
+      prc.add(m.s[i])
+
+  prc.addf("}$N$N", [])
+
+  if moduleDatInitRequired:
+    m.s[cfsDatInitProc].add(prc)
+    #rememberFlag(m.g.graph, m.module, HasDatInitProc)
+
+# Very similar to the contents of symInDynamicLib - basically only the
+# things needed for the hot code reloading runtime procs to be loaded
+proc hcrGetProcLoadCode(m: BModule, sym, prefix, handle, getProcFunc: string): Rope =
+  let prc = magicsys.getCompilerProc(m.g.graph, sym)
+  assert prc != nil
+  fillProcLoc(m, prc.ast[namePos])
+
+  var extname = prefix & sym
+  var tmp = mangleDynLibProc(prc)
+  prc.loc.snippet = tmp
+  prc.typ.sym = nil
+
+  if not containsOrIncl(m.declaredThings, prc.id):
+    m.s[cfsVars].addf("static $2 $1;$n", [prc.loc.snippet, getTypeDesc(m, prc.loc.t, dkVar)])
+
+  result = "\t$1 = ($2) $3($4, $5);$n" %
+      [tmp, getTypeDesc(m, prc.typ, dkVar), getProcFunc.rope, handle.rope, makeCString(prefix & sym)]
 
 proc genInitCode(m: BModule) =
-  var initname = getInitName(m.module)
-  var prc = "NIM_EXTERNC N_NOINLINE(void, $1)(void) {$N" % [initname]
+  ## this function is called in cgenWriteModules after all modules are closed,
+  ## it means raising dependency on the symbols is too late as it will not propagate
+  ## into other modules, only simple rope manipulations are allowed
+  var moduleInitRequired = m.hcrOn
+  let initname = getInitName(m)
+  var prc = "$1 N_NIMCALL(void, $2)(void) {$N" %
+    [rope(if m.hcrOn: "N_LIB_EXPORT" else: "N_LIB_PRIVATE"), initname]
+  # we don't want to break into such init code - could happen if a line
+  # directive from a function written by the user spills after itself
+  genCLineDir(prc, InvalidFileIdx, 999999, m.config)
   if m.typeNodes > 0:
-    appcg(m, m.s[cfsTypeInit1], "static #TNimNode $1[$2];$n",
-          [m.typeNodesName, rope(m.typeNodes)])
+    if m.hcrOn:
+      appcg(m, m.s[cfsTypeInit1], "\t#TNimNode* $1;$N", [m.typeNodesName])
+      appcg(m, m.s[cfsTypeInit1], "\thcrRegisterGlobal($3, \"$1_$2\", sizeof(TNimNode) * $2, NULL, (void**)&$1);$N",
+            [m.typeNodesName, m.typeNodes, getModuleDllPath(m, m.module)])
+    else:
+      appcg(m, m.s[cfsTypeInit1], "static #TNimNode $1[$2];$n",
+            [m.typeNodesName, m.typeNodes])
   if m.nimTypes > 0:
     appcg(m, m.s[cfsTypeInit1], "static #TNimType $1[$2];$n",
-          [m.nimTypesName, rope(m.nimTypes)])
-
-  add(prc, initGCFrame(m.initProc))
-
-  add(prc, genSectionStart(cpsLocals))
-  add(prc, m.preInitProc.s(cpsLocals))
-  add(prc, m.initProc.s(cpsLocals))
-  add(prc, m.postInitProc.s(cpsLocals))
-  add(prc, genSectionEnd(cpsLocals))
-
-  if optStackTrace in m.initProc.options and frameDeclared notin m.flags:
-    # BUT: the generated init code might depend on a current frame, so
-    # declare it nevertheless:
-    incl m.flags, frameDeclared
-    if preventStackTrace notin m.flags:
-      var procname = makeCString(m.module.name.s)
-      add(prc, initFrame(m.initProc, procname, m.module.info.quotedFilename))
-    else:
-      add(prc, ~"\tTFrame FR_; FR_.len = 0;$N")
-
-  add(prc, genSectionStart(cpsInit))
-  add(prc, m.preInitProc.s(cpsInit))
-  add(prc, m.initProc.s(cpsInit))
-  add(prc, m.postInitProc.s(cpsInit))
-  add(prc, genSectionEnd(cpsInit))
-
-  add(prc, genSectionStart(cpsStmts))
-  add(prc, m.preInitProc.s(cpsStmts))
-  add(prc, m.initProc.s(cpsStmts))
-  add(prc, m.postInitProc.s(cpsStmts))
-  add(prc, genSectionEnd(cpsStmts))
-  if optStackTrace in m.initProc.options and preventStackTrace notin m.flags:
-    add(prc, deinitFrame(m.initProc))
-  add(prc, deinitGCFrame(m.initProc))
-  addf(prc, "}$N$N", [])
-
-  prc.addf("NIM_EXTERNC N_NOINLINE(void, $1)(void) {$N",
-           [getDatInitName(m.module)])
+          [m.nimTypesName, m.nimTypes])
+
+  if m.hcrOn:
+    prc.addf("\tint* nim_hcr_dummy_ = 0;$n" &
+              "\tNIM_BOOL nim_hcr_do_init_ = " &
+                  "hcrRegisterGlobal($1, \"module_initialized_\", 1, NULL, (void**)&nim_hcr_dummy_);$n",
+      [getModuleDllPath(m, m.module)])
+
+  template writeSection(thing: untyped, section: TCProcSection, addHcrGuards = false) =
+    if m.thing.s(section).len > 0:
+      moduleInitRequired = true
+      if addHcrGuards: prc.add("\tif (nim_hcr_do_init_) {\n\n")
+      prc.add(m.thing.s(section))
+      if addHcrGuards: prc.add("\n\t} // nim_hcr_do_init_\n")
+
+  if m.preInitProc.s(cpsInit).len > 0 or m.preInitProc.s(cpsStmts).len > 0:
+    # Give this small function its own scope
+    prc.addf("{$N", [])
+    # Keep a bogus frame in case the code needs one
+    prc.add("\tTFrame FR_; FR_.len = 0;\n")
+
+    writeSection(preInitProc, cpsLocals)
+    writeSection(preInitProc, cpsInit, m.hcrOn)
+    writeSection(preInitProc, cpsStmts)
+    prc.addf("}/* preInitProc end */$N", [])
+    when false:
+      m.initProc.blocks[0].sections[cpsLocals].add m.preInitProc.s(cpsLocals)
+      m.initProc.blocks[0].sections[cpsInit].prepend m.preInitProc.s(cpsInit)
+      m.initProc.blocks[0].sections[cpsStmts].prepend m.preInitProc.s(cpsStmts)
+
+  # add new scope for following code, because old vcc compiler need variable
+  # be defined at the top of the block
+  prc.addf("{$N", [])
+  writeSection(initProc, cpsLocals)
+
+  if m.initProc.s(cpsInit).len > 0 or m.initProc.s(cpsStmts).len > 0:
+    moduleInitRequired = true
+    if optStackTrace in m.initProc.options and frameDeclared notin m.flags:
+      # BUT: the generated init code might depend on a current frame, so
+      # declare it nevertheless:
+      incl m.flags, frameDeclared
+      if preventStackTrace notin m.flags:
+        var procname = makeCString(m.module.name.s)
+        prc.add(initFrame(m.initProc, procname, quotedFilename(m.config, m.module.info)))
+      else:
+        prc.add("\tTFrame FR_; FR_.len = 0;\n")
 
-  for i in cfsTypeInit1..cfsDynLibInit:
-    add(prc, genSectionStart(i))
-    add(prc, m.s[i])
-    add(prc, genSectionEnd(i))
+    writeSection(initProc, cpsInit, m.hcrOn)
+    writeSection(initProc, cpsStmts)
+
+    if beforeRetNeeded in m.initProc.flags:
+      prc.add("\tBeforeRet_: ;\n")
+
+    if m.config.exc == excGoto:
+      if getCompilerProc(m.g.graph, "nimTestErrorFlag") != nil:
+        m.appcg(prc, "\t#nimTestErrorFlag();$n", [])
+
+    if optStackTrace in m.initProc.options and preventStackTrace notin m.flags:
+      prc.add(deinitFrame(m.initProc))
+
+  prc.addf("}$N", [])
+
+  prc.addf("}$N$N", [])
 
-  addf(prc, "}$N$N", [])
   # we cannot simply add the init proc to ``m.s[cfsProcs]`` anymore because
   # that would lead to a *nesting* of merge sections which the merger does
   # not support. So we add it to another special section: ``cfsInitProc``
-  add(m.s[cfsInitProc], prc)
+
+  if m.hcrOn:
+    var procsToLoad = @["hcrRegisterProc", "hcrGetProc", "hcrRegisterGlobal", "hcrGetGlobal"]
+
+    m.s[cfsInitProc].addf("N_LIB_EXPORT N_NIMCALL(void, $1)(void* handle, N_NIMCALL_PTR(void*, getProcAddr)(void*, char*)) {$N", [getHcrInitName(m)])
+    if sfMainModule in m.module.flags:
+      # additional procs to load
+      procsToLoad.add("hcrInit")
+      procsToLoad.add("hcrAddModule")
+    # load procs
+    for curr in procsToLoad:
+      m.s[cfsInitProc].add(hcrGetProcLoadCode(m, curr, "", "handle", "getProcAddr"))
+    m.s[cfsInitProc].addf("}$N$N", [])
 
   for i, el in pairs(m.extensionLoaders):
-    if el != nil:
-      let ex = "N_NIMCALL(void, nimLoadProcs$1)(void) {$2}$N$N" %
+    if el != "":
+      let ex = "NIM_EXTERNC N_NIMCALL(void, nimLoadProcs$1)(void) {$2}$N$N" %
         [(i.ord - '0'.ord).rope, el]
-      add(m.s[cfsInitProc], ex)
+      moduleInitRequired = true
+      prc.add(ex)
+
+  if moduleInitRequired or sfMainModule in m.module.flags:
+    m.s[cfsInitProc].add(prc)
+    #rememberFlag(m.g.graph, m.module, HasModuleInitProc)
+
+  genDatInitCode(m)
+
+  if m.hcrOn:
+    m.s[cfsInitProc].addf("N_LIB_EXPORT N_NIMCALL(void, HcrCreateTypeInfos)(void) {$N", [])
+    m.s[cfsInitProc].add(m.hcrCreateTypeInfosProc)
+    m.s[cfsInitProc].addf("}$N$N", [])
+
+  registerModuleToMain(m.g, m)
+
+proc postprocessCode(conf: ConfigRef, r: var Rope) =
+  # find the first directive
+  var f = r.find(postprocessDirStart)
+  if f == -1:
+    return
+
+  var
+    nimlnDirLastF = ""
+
+  var res: Rope = r.substr(0, f - 1)
+  while f != -1:
+    var
+      e = r.find(postprocessDirEnd, f + 1)
+      dir = r.substr(f + 1, e - 1).split(postprocessDirSep)
+    case dir[0]
+    of "nimln":
+      if dir[2] == nimlnDirLastF:
+        res.add("nimln_(" & dir[1] & ");")
+      else:
+        res.add("nimlf_(" & dir[1] & ", " & quotedFilename(conf, dir[2].parseInt.FileIndex) & ");")
+        nimlnDirLastF = dir[2]
+    else:
+      raiseAssert "unexpected postprocess directive"
+
+    # find the next directive
+    f = r.find(postprocessDirStart, e + 1)
+    # copy the code until the next directive
+    if f != -1:
+      res.add(r.substr(e + 1, f - 1))
+    else:
+      res.add(r.substr(e + 1))
+
+  r = res
 
 proc genModule(m: BModule, cfile: Cfile): Rope =
-  result = getFileHeader(cfile)
-  result.add(genMergeInfo(m))
+  var moduleIsEmpty = true
+
+  result = getFileHeader(m.config, cfile)
 
   generateThreadLocalStorage(m)
   generateHeaders(m)
-  for i in countup(cfsHeaders, cfsProcs):
-    add(result, genSectionStart(i))
-    add(result, m.s[i])
-    add(result, genSectionEnd(i))
-  add(result, m.s[cfsInitProc])
-
-proc newPreInitProc(m: BModule): BProc =
-  result = newProc(nil, m)
-  # little hack so that unique temporaries are generated:
-  result.labels = 100_000
-
-proc newPostInitProc(m: BModule): BProc =
-  result = newProc(nil, m)
-  # little hack so that unique temporaries are generated:
-  result.labels = 200_000
+  result.add(m.s[cfsHeaders])
+  if m.config.cppCustomNamespace.len > 0:
+    openNamespaceNim(m.config.cppCustomNamespace, result)
+  if m.s[cfsFrameDefines].len > 0:
+    result.add(m.s[cfsFrameDefines])
+
+  for i in cfsForwardTypes..cfsProcs:
+    if m.s[i].len > 0:
+      moduleIsEmpty = false
+      result.add(m.s[i])
+
+  if m.s[cfsInitProc].len > 0:
+    moduleIsEmpty = false
+    result.add(m.s[cfsInitProc])
+  if m.s[cfsDatInitProc].len > 0 or m.hcrOn:
+    moduleIsEmpty = false
+    result.add(m.s[cfsDatInitProc])
+
+  if m.config.cppCustomNamespace.len > 0:
+    closeNamespaceNim(result)
+
+  if optLineDir in m.config.options:
+    var srcFileDefs = ""
+    for fi in 0..m.config.m.fileInfos.high:
+      srcFileDefs.add("#define FX_" & $fi & " " & makeSingleLineCString(toFullPath(m.config, fi.FileIndex)) & "\n")
+    result = srcFileDefs & result
+
+  if moduleIsEmpty:
+    result = ""
+
+  postprocessCode(m.config, result)
 
 proc initProcOptions(m: BModule): TOptions =
-  if sfSystemModule in m.module.flags: gOptions-{optStackTrace} else: gOptions
+  let opts = m.config.options
+  if sfSystemModule in m.module.flags: opts-{optStackTrace} else: opts
 
-proc rawNewModule(g: BModuleList; module: PSym, filename: string): BModule =
+proc rawNewModule(g: BModuleList; module: PSym, filename: AbsoluteFile): BModule =
   new(result)
+  result.g = g
   result.tmpBase = rope("TM" & $hashOwner(module) & "_")
   result.headerFiles = @[]
   result.declaredThings = initIntSet()
@@ -1196,176 +2071,144 @@ proc rawNewModule(g: BModuleList; module: PSym, filename: string): BModule =
   result.typeInfoMarker = initTable[SigHash, Rope]()
   result.sigConflicts = initCountTable[SigHash]()
   result.initProc = newProc(nil, result)
+  for i in low(result.s)..high(result.s): result.s[i] = newRopeAppender()
   result.initProc.options = initProcOptions(result)
-  result.preInitProc = newPreInitProc(result)
-  result.postInitProc = newPostInitProc(result)
-  initNodeTable(result.dataCache)
+  result.preInitProc = newProc(nil, result)
+  result.preInitProc.flags.incl nimErrorFlagDisabled
+  result.preInitProc.labels = 100_000 # little hack so that unique temporaries are generated
+  result.dataCache = initNodeTable()
   result.typeStack = @[]
-  result.forwardedProcs = @[]
   result.typeNodesName = getTempName(result)
   result.nimTypesName = getTempName(result)
-  result.g = g
   # no line tracing for the init sections of the system module so that we
-  # don't generate a TFrame which can confuse the stack botton initialization:
+  # don't generate a TFrame which can confuse the stack bottom initialization:
   if sfSystemModule in module.flags:
     incl result.flags, preventStackTrace
     excl(result.preInitProc.options, optStackTrace)
-    excl(result.postInitProc.options, optStackTrace)
-  let ndiName = if optCDebug in gGlobalOptions: changeFileExt(completeCFilePath(filename), "ndi")
-                else: ""
-  open(result.ndi, ndiName)
-
-proc nullify[T](arr: var T) =
-  for i in low(arr)..high(arr):
-    arr[i] = Rope(nil)
-
-proc resetModule*(m: BModule) =
-  # between two compilations in CAAS mode, we can throw
-  # away all the data that was written to disk
-  m.headerFiles = @[]
-  m.declaredProtos = initIntSet()
-  m.forwTypeCache = initTable[SigHash, Rope]()
-  m.initProc = newProc(nil, m)
-  m.initProc.options = initProcOptions(m)
-  m.preInitProc = newPreInitProc(m)
-  m.postInitProc = newPostInitProc(m)
-  initNodeTable(m.dataCache)
-  m.typeStack = @[]
-  m.forwardedProcs = @[]
-  m.typeNodesName = getTempName(m)
-  m.nimTypesName = getTempName(m)
-  if sfSystemModule in m.module.flags:
-    incl m.flags, preventStackTrace
-  else:
-    excl m.flags, preventStackTrace
-  nullify m.s
-  m.typeNodes = 0
-  m.nimTypes = 0
-  nullify m.extensionLoaders
-
-  # indicate that this is now cached module
-  # the cache will be invalidated by nullifying gModules
-  m.fromCache = true
-  m.g = nil
-
-  # we keep only the "merge info" information for the module
-  # and the properties that can't change:
-  # m.filename
-  # m.cfilename
-  # m.isHeaderFile
-  # m.module ?
-  # m.typeCache
-  # m.declaredThings
-  # m.typeInfoMarker
-  # m.labels
-  # m.FrameDeclared
-
-proc resetCgenModules*(g: BModuleList) =
-  for m in cgenModules(g): resetModule(m)
-
-proc rawNewModule(g: BModuleList; module: PSym): BModule =
-  result = rawNewModule(g, module, module.position.int32.toFullPath)
-
-proc newModule(g: BModuleList; module: PSym): BModule =
+  let ndiName = if optCDebug in g.config.globalOptions: changeFileExt(completeCfilePath(g.config, filename), "ndi")
+                else: AbsoluteFile""
+  open(result.ndi, ndiName, g.config)
+
+proc rawNewModule(g: BModuleList; module: PSym; conf: ConfigRef): BModule =
+  result = rawNewModule(g, module, AbsoluteFile toFullPath(conf, module.position.FileIndex))
+
+proc newModule*(g: BModuleList; module: PSym; conf: ConfigRef): BModule =
   # we should create only one cgen module for each module sym
-  result = rawNewModule(g, module)
-  growCache g.modules, module.position
+  result = rawNewModule(g, module, conf)
+  if module.position >= g.modules.len:
+    setLen(g.modules, module.position + 1)
+  #growCache g.modules, module.position
   g.modules[module.position] = result
 
-  if (optDeadCodeElim in gGlobalOptions):
-    if (sfDeadCodeElim in module.flags):
-      internalError("added pending module twice: " & module.filename)
-
-template injectG(config) {.dirty.} =
+template injectG() {.dirty.} =
   if graph.backend == nil:
-    graph.backend = newModuleList(config)
+    graph.backend = newModuleList(graph)
   let g = BModuleList(graph.backend)
 
-proc myOpen(graph: ModuleGraph; module: PSym; cache: IdentCache): PPassContext =
-  injectG(graph.config)
-  result = newModule(g, module)
-  if optGenIndex in gGlobalOptions and g.generatedHeader == nil:
-    let f = if graph.config.headerFile.len > 0: graph.config.headerFile else: gProjectFull
+proc setupCgen*(graph: ModuleGraph; module: PSym; idgen: IdGenerator): PPassContext =
+  injectG()
+  result = newModule(g, module, graph.config)
+  result.idgen = idgen
+  if optGenIndex in graph.config.globalOptions and g.generatedHeader == nil:
+    let f = if graph.config.headerFile.len > 0: AbsoluteFile graph.config.headerFile
+            else: graph.config.projectFull
     g.generatedHeader = rawNewModule(g, module,
-      changeFileExt(completeCFilePath(f), hExt))
+      changeFileExt(completeCfilePath(graph.config, f), hExt))
     incl g.generatedHeader.flags, isHeaderFile
 
 proc writeHeader(m: BModule) =
-  var result = ("/* Generated by Nim Compiler v$1 */$N" &
-        "/*   (c) " & CompileDate.substr(0, 3) & " Andreas Rumpf */$N" &
-        "/* The generated code is subject to the original license. */$N") %
-        [rope(VersionAsString)]
-
+  var result = headerTop()
   var guard = "__$1__" % [m.filename.splitFile.name.rope]
   result.addf("#ifndef $1$n#define $1$n", [guard])
-  addIntTypes(result)
+  addNimDefines(result, m.config)
   generateHeaders(m)
 
   generateThreadLocalStorage(m)
-  for i in countup(cfsHeaders, cfsProcs):
-    add(result, genSectionStart(i))
-    add(result, m.s[i])
-    add(result, genSectionEnd(i))
-  add(result, m.s[cfsInitProc])
+  for i in cfsHeaders..cfsProcs:
+    result.add(m.s[i])
+    if m.config.cppCustomNamespace.len > 0 and i == cfsHeaders:
+      openNamespaceNim(m.config.cppCustomNamespace, result)
+  result.add(m.s[cfsInitProc])
 
-  if optGenDynLib in gGlobalOptions:
+  if optGenDynLib in m.config.globalOptions:
     result.add("N_LIB_IMPORT ")
-  result.addf("N_CDECL(void, NimMain)(void);$n", [])
+  result.addf("N_CDECL(void, $1NimMain)(void);$n", [rope m.config.nimMainPrefix])
+  if m.config.cppCustomNamespace.len > 0: closeNamespaceNim(result)
   result.addf("#endif /* $1 */$n", [guard])
-  writeRope(result, m.filename)
+  if not writeRope(result, m.filename):
+    rawMessage(m.config, errCannotOpenFile, m.filename.string)
 
-proc getCFile(m: BModule): string =
+proc getCFile(m: BModule): AbsoluteFile =
   let ext =
-      if m.compileToCpp: ".cpp"
-      elif gCmd == cmdCompileToOC or sfCompileToObjC in m.module.flags: ".m"
-      else: ".c"
-  result = changeFileExt(completeCFilePath(m.cfilename.withPackageName), ext)
-
-proc myOpenCached(graph: ModuleGraph; module: PSym, rd: PRodReader): PPassContext =
-  injectG(graph.config)
-  assert optSymbolFiles in gGlobalOptions
-  var m = newModule(g, module)
-  readMergeInfo(getCFile(m), m)
-  result = m
-
-proc myProcess(b: PPassContext, n: PNode): PNode =
-  result = n
-  if b == nil or passes.skipCodegen(n): return
-  var m = BModule(b)
+      if m.compileToCpp: ".nim.cpp"
+      elif m.config.backend == backendObjc or sfCompileToObjc in m.module.flags: ".nim.m"
+      else: ".nim.c"
+  result = changeFileExt(completeCfilePath(m.config, mangleModuleName(m.config, m.cfilename).AbsoluteFile), ext)
+
+when false:
+  proc myOpenCached(graph: ModuleGraph; module: PSym, rd: PRodReader): PPassContext =
+    injectG()
+    var m = newModule(g, module, graph.config)
+    readMergeInfo(getCFile(m), m)
+    result = m
+
+proc addHcrInitGuards(p: BProc, n: PNode, inInitGuard: var bool) =
+  if n.kind == nkStmtList:
+    for child in n:
+      addHcrInitGuards(p, child, inInitGuard)
+  else:
+    let stmtShouldExecute = n.kind in {nkVarSection, nkLetSection} or
+                            nfExecuteOnReload in n.flags
+    if inInitGuard:
+      if stmtShouldExecute:
+        endBlock(p)
+        inInitGuard = false
+    else:
+      if not stmtShouldExecute:
+        line(p, cpsStmts, "if (nim_hcr_do_init_)\n")
+        startBlock(p)
+        inInitGuard = true
+
+    genStmts(p, n)
+
+proc genTopLevelStmt*(m: BModule; n: PNode) =
+  ## Also called from `ic/cbackend.nim`.
+  if pipelineutils.skipCodegen(m.config, n): return
   m.initProc.options = initProcOptions(m)
-  softRnl = if optLineDir in gOptions: noRnl else: rnl
-  genStmts(m.initProc, n)
+  #softRnl = if optLineDir in m.config.options: noRnl else: rnl
+  # XXX replicate this logic!
+  var transformedN = transformStmt(m.g.graph, m.idgen, m.module, n)
+  if sfInjectDestructors in m.module.flags:
+    transformedN = injectDestructorCalls(m.g.graph, m.idgen, m.module, transformedN)
+
+  if m.hcrOn:
+    addHcrInitGuards(m.initProc, transformedN, m.inHcrInitGuard)
+  else:
+    genProcBody(m.initProc, transformedN)
 
-proc finishModule(m: BModule) =
-  var i = 0
-  while i <= high(m.forwardedProcs):
-    # Note: ``genProc`` may add to ``m.forwardedProcs``, so we cannot use
-    # a ``for`` loop here
-    var prc = m.forwardedProcs[i]
-    if sfForward in prc.flags:
-      internalError(prc.info, "still forwarded: " & prc.name.s)
-    genProcNoForward(m, prc)
-    inc(i)
-  assert(m.g.forwardedProcsCounter >= i)
-  dec(m.g.forwardedProcsCounter, i)
-  setLen(m.forwardedProcs, 0)
-
-proc shouldRecompile(code: Rope, cfile: Cfile): bool =
-  result = true
-  if optForceFullMake notin gGlobalOptions:
-    if not equalsFile(code, cfile.cname):
-      if isDefined("nimdiff"):
+proc shouldRecompile(m: BModule; code: Rope, cfile: Cfile): bool =
+  if optForceFullMake notin m.config.globalOptions:
+    if not moduleHasChanged(m.g.graph, m.module):
+      result = false
+    elif not equalsFile(code, cfile.cname):
+      when false:
+        #m.config.symbolFiles == readOnlySf: #isDefined(m.config, "nimdiff"):
         if fileExists(cfile.cname):
-          copyFile(cfile.cname, cfile.cname & ".backup")
-          echo "diff ", cfile.cname, ".backup ", cfile.cname
+          copyFile(cfile.cname.string, cfile.cname.string & ".backup")
+          echo "diff ", cfile.cname.string, ".backup ", cfile.cname.string
         else:
-          echo "new file ", cfile.cname
-      writeRope(code, cfile.cname)
-      return
-    if existsFile(cfile.obj) and os.fileNewer(cfile.obj, cfile.cname):
+          echo "new file ", cfile.cname.string
+      if not writeRope(code, cfile.cname):
+        rawMessage(m.config, errCannotOpenFile, cfile.cname.string)
+      result = true
+    elif fileExists(cfile.obj) and os.fileNewer(cfile.obj.string, cfile.cname.string):
       result = false
+    else:
+      result = true
   else:
-    writeRope(code, cfile.cname)
+    if not writeRope(code, cfile.cname):
+      rawMessage(m.config, errCannotOpenFile, cfile.cname.string)
+    result = true
 
 # We need 2 different logics here: pending modules (including
 # 'nim__dat') may require file merging for the combination of dead code
@@ -1374,92 +2217,144 @@ proc shouldRecompile(code: Rope, cfile: Cfile): bool =
 # it would generate multiple 'main' procs, for instance.
 
 proc writeModule(m: BModule, pending: bool) =
-  # generate code for the init statements of the module:
+  template onExit() = close(m.ndi, m.config)
   let cfile = getCFile(m)
-
-  if not m.fromCache or optForceFullMake in gGlobalOptions:
+  if moduleHasChanged(m.g.graph, m.module):
     genInitCode(m)
     finishTypeDescriptions(m)
     if sfMainModule in m.module.flags:
       # generate main file:
-      add(m.s[cfsProcHeaders], m.g.mainModProcs)
+      genMainProc(m)
+      m.s[cfsProcHeaders].add(m.g.mainModProcs)
       generateThreadVarsSize(m)
 
-    var cf = Cfile(cname: cfile, obj: completeCFilePath(toObjFile(cfile)), flags: {})
-    var code = genModule(m, cf)
+  var cf = Cfile(nimname: m.module.name.s, cname: cfile,
+                  obj: completeCfilePath(m.config, toObjFile(m.config, cfile)), flags: {})
+  var code = genModule(m, cf)
+  if code != "" or m.config.symbolFiles != disabledSf:
     when hasTinyCBackend:
-      if gCmd == cmdRun:
-        tccgen.compileCCode($code)
+      if m.config.cmd == cmdTcc:
+        tccgen.compileCCode($code, m.config)
+        onExit()
         return
 
-    if not shouldRecompile(code, cf): cf.flags = {CfileFlag.Cached}
-    addFileToCompile(cf)
-  elif pending and mergeRequired(m) and sfMainModule notin m.module.flags:
-    let cf = Cfile(cname: cfile, obj: completeCFilePath(toObjFile(cfile)), flags: {})
-    mergeFiles(cfile, m)
-    genInitCode(m)
-    finishTypeDescriptions(m)
-    var code = genModule(m, cf)
-    writeRope(code, cfile)
-    addFileToCompile(cf)
-  else:
-    # Consider: first compilation compiles ``system.nim`` and produces
-    # ``system.c`` but then compilation fails due to an error. This means
-    # that ``system.o`` is missing, so we need to call the C compiler for it:
-    var cf = Cfile(cname: cfile, obj: completeCFilePath(toObjFile(cfile)), flags: {})
-    if not existsFile(cf.obj): cf.flags = {CfileFlag.Cached}
-    addFileToCompile(cf)
-  close(m.ndi)
+    if not shouldRecompile(m, code, cf): cf.flags = {CfileFlag.Cached}
+    addFileToCompile(m.config, cf)
+  onExit()
 
 proc updateCachedModule(m: BModule) =
   let cfile = getCFile(m)
-  var cf = Cfile(cname: cfile, obj: completeCFilePath(toObjFile(cfile)), flags: {})
-
-  if mergeRequired(m) and sfMainModule notin m.module.flags:
-    mergeFiles(cfile, m)
-    genInitCode(m)
-    finishTypeDescriptions(m)
+  var cf = Cfile(nimname: m.module.name.s, cname: cfile,
+                 obj: completeCfilePath(m.config, toObjFile(m.config, cfile)), flags: {})
+  if sfMainModule notin m.module.flags:
+    genMainProc(m)
+  cf.flags = {CfileFlag.Cached}
+  addFileToCompile(m.config, cf)
+
+proc generateLibraryDestroyGlobals(graph: ModuleGraph; m: BModule; body: PNode; isDynlib: bool): PSym =
+  let procname = getIdent(graph.cache, "NimDestroyGlobals")
+  result = newSym(skProc, procname, m.idgen, m.module.owner, m.module.info)
+  result.typ = newProcType(m.module.info, m.idgen, m.module.owner)
+  result.typ.callConv = ccCDecl
+  incl result.flags, sfExportc
+  result.loc.snippet = "NimDestroyGlobals"
+  if isDynlib:
+    incl(result.loc.flags, lfExportLib)
+
+  let theProc = newNodeI(nkProcDef, m.module.info, bodyPos+1)
+  for i in 0..<theProc.len: theProc[i] = newNodeI(nkEmpty, m.module.info)
+  theProc[namePos] = newSymNode(result)
+  theProc[bodyPos] = body
+  result.ast = theProc
+
+proc finalCodegenActions*(graph: ModuleGraph; m: BModule; n: PNode) =
+  ## Also called from IC.
+  if sfMainModule in m.module.flags:
+    # phase ordering problem here: We need to announce this
+    # dependency to 'nimTestErrorFlag' before system.c has been written to disk.
+    if m.config.exc == excGoto and getCompilerProc(graph, "nimTestErrorFlag") != nil:
+      cgsym(m, "nimTestErrorFlag")
+
+    if {optGenStaticLib, optGenDynLib, optNoMain} * m.config.globalOptions == {}:
+      for i in countdown(high(graph.globalDestructors), 0):
+        n.add graph.globalDestructors[i]
+    else:
+      var body = newNodeI(nkStmtList, m.module.info)
+      for i in countdown(high(graph.globalDestructors), 0):
+        body.add graph.globalDestructors[i]
+      body.flags.incl nfTransf # should not be further transformed
+      let dtor = generateLibraryDestroyGlobals(graph, m, body, optGenDynLib in m.config.globalOptions)
+      genProcAux(m, dtor)
+  if pipelineutils.skipCodegen(m.config, n): return
+  if moduleHasChanged(graph, m.module):
+    # if the module is cached, we don't regenerate the main proc
+    # nor the dispatchers? But if the dispatchers changed?
+    # XXX emit the dispatchers into its own .c file?
+    if n != nil:
+      m.initProc.options = initProcOptions(m)
+      genProcBody(m.initProc, n)
+
+    if m.hcrOn:
+      # make sure this is pulled in (meaning hcrGetGlobal() is called for it during init)
+      let sym = magicsys.getCompilerProc(m.g.graph, "programResult")
+      # ignore when not available, could be a module imported early in `system`
+      if sym != nil:
+        cgsymImpl m, sym
+      if m.inHcrInitGuard:
+        endBlock(m.initProc)
 
-    var code = genModule(m, cf)
-    writeRope(code, cfile)
-  else:
-    cf.flags = {CfileFlag.Cached}
-  addFileToCompile(cf)
-
-proc myClose(graph: ModuleGraph; b: PPassContext, n: PNode): PNode =
-  result = n
-  if b == nil or passes.skipCodegen(n): return
-  var m = BModule(b)
-  if n != nil:
-    m.initProc.options = initProcOptions(m)
-    genStmts(m.initProc, n)
-  # cached modules need to registered too:
-  registerModuleToMain(m.g, m.module)
+    if sfMainModule in m.module.flags:
+      if m.hcrOn:
+        # pull ("define" since they are inline when HCR is on) these functions in the main file
+        # so it can load the HCR runtime and later pass the library handle to the HCR runtime which
+        # will in turn pass it to the other modules it initializes so they can initialize the
+        # register/get procs so they don't have to have the definitions of these functions as well
+        cgsym(m, "nimLoadLibrary")
+        cgsym(m, "nimLoadLibraryError")
+        cgsym(m, "nimGetProcAddr")
+        cgsym(m, "procAddrError")
+        cgsym(m, "rawWrite")
+
+      # raise dependencies on behalf of genMainProc
+      if m.config.target.targetOS != osStandalone and m.config.selectedGC notin {gcNone, gcArc, gcAtomicArc, gcOrc}:
+        cgsym(m, "initStackBottomWith")
+      if emulatedThreadVars(m.config) and m.config.target.targetOS != osStandalone:
+        cgsym(m, "initThreadVarsEmulation")
+
+      if m.g.forwardedProcs.len == 0:
+        incl m.flags, objHasKidsValid
+      if optMultiMethods in m.g.config.globalOptions or
+          m.g.config.selectedGC notin {gcArc, gcOrc, gcAtomicArc} or
+          vtables notin m.g.config.features:
+        generateIfMethodDispatchers(graph, m.idgen)
+
+
+  let mm = m
+  m.g.modulesClosed.add mm
+
+proc genForwardedProcs(g: BModuleList) =
+  # Forward declared proc:s lack bodies when first encountered, so they're given
+  # a second pass here
+  # Note: ``genProcNoForward`` may add to ``forwardedProcs``
+  while g.forwardedProcs.len > 0:
+    let
+      prc = g.forwardedProcs.pop()
+      m = g.modules[prc.itemId.module]
+    if sfForward in prc.flags:
+      internalError(m.config, prc.info, "still forwarded: " & prc.name.s)
 
-  if sfMainModule in m.module.flags:
-    if m.g.forwardedProcsCounter == 0:
-      incl m.flags, objHasKidsValid
-    let disp = generateMethodDispatchers(graph)
-    for x in disp: genProcAux(m, x.sym)
-    genMainProc(m)
+    genProcNoForward(m, prc)
 
 proc cgenWriteModules*(backend: RootRef, config: ConfigRef) =
   let g = BModuleList(backend)
+  g.config = config
+
   # we need to process the transitive closure because recursive module
   # deps are allowed (and the system module is processed in the wrong
   # order anyway)
-  g.config = config
-  if g.generatedHeader != nil: finishModule(g.generatedHeader)
-  while g.forwardedProcsCounter > 0:
-    for m in cgenModules(g):
-      if not m.fromCache:
-        finishModule(m)
+  genForwardedProcs(g)
+
   for m in cgenModules(g):
-    if m.fromCache:
-      m.updateCachedModule
-    else:
-      m.writeModule(pending=true)
-  writeMapping(g.mapping)
+    m.writeModule(pending=true)
+  writeMapping(config, g.mapping)
   if g.generatedHeader != nil: writeHeader(g.generatedHeader)
-
-const cgenPass* = makePass(myOpen, myOpenCached, myProcess, myClose)