summary refs log tree commit diff stats
path: root/compiler/rodimpl.nim
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rodimpl.nim')
-rw-r--r--compiler/rodimpl.nim947
1 files changed, 947 insertions, 0 deletions
diff --git a/compiler/rodimpl.nim b/compiler/rodimpl.nim
new file mode 100644
index 000000000..aff4f6909
--- /dev/null
+++ b/compiler/rodimpl.nim
@@ -0,0 +1,947 @@
+#
+#
+#           The Nim Compiler
+#        (c) Copyright 2018 Andreas Rumpf
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+## This module implements the new compilation cache.
+
+import strutils, os, intsets, tables, ropes, db_sqlite, msgs, options, types,
+  renderer, rodutils, std / sha1, idents, astalgo, magicsys
+
+## Todo:
+## - Implement the 'import' replay logic so that the codegen runs over
+##   dependent modules.
+## - Make conditional symbols and the configuration part of a module's
+##   dependencies.
+## - Test multi methods.
+## - Implement the limited VM support based on sets.
+## - Depencency computation should use signature hashes in order to
+##   avoid recompiling dependent modules.
+
+var db: DbConn
+
+proc hashFileCached(fileIdx: int32; fullpath: string): string =
+  result = msgs.getHash(fileIdx)
+  if result.len == 0:
+    result = $secureHashFile(fullpath)
+    msgs.setHash(fileIdx, result)
+
+proc needsRecompile(fileIdx: int32; fullpath: string; cycleCheck: var IntSet): bool =
+  let root = db.getRow(sql"select id, fullhash from filenames where fullpath = ?",
+    fullpath)
+  if root[0].len == 0: return true
+  if root[1] != hashFileCached(fileIdx, fullpath):
+    return true
+  # cycle detection: assume "not changed" is correct.
+  if cycleCheck.containsOrIncl(fileIdx):
+    return false
+  # check dependencies (recursively):
+  for row in db.fastRows(sql"select fullpath from filenames where id in (select dependency from deps where module = ?)",
+                         root[0]):
+    let dep = row[0]
+    if needsRecompile(dep.fileInfoIdx, dep, cycleCheck):
+      return true
+  return false
+
+proc getModuleId*(fileIdx: int32; fullpath: string): int =
+  if gSymbolFiles != v2Sf: return getID()
+  let module = db.getRow(
+    sql"select id, fullHash from modules where fullpath = ?", fullpath)
+  let currentFullhash = hashFileCached(fileIdx, fullpath)
+  if module[0].len == 0:
+    result = int db.insertID(sql"insert into modules(fullpath, interfHash, fullHash) values (?, ?, ?)",
+      fullpath, "", currentFullhash)
+  else:
+    result = parseInt(module[0])
+    if currentFullhash == module[1]:
+      # not changed, so use the cached AST (even if it might be wrong
+      # due to its dependencies):
+      doAssert(result != 0)
+      var cycleCheck = initIntSet()
+      if not needsRecompile(fileIdx, fullpath, cycleCheck):
+        return -result
+    db.exec(sql"update modules set fullHash = ? where id = ?", currentFullhash, module[0])
+    db.exec(sql"delete from deps where module = ?", module[0])
+    db.exec(sql"delete from types where module = ?", module[0])
+    db.exec(sql"delete from syms where module = ?", module[0])
+    db.exec(sql"delete from toplevelstmts where module = ?", module[0])
+    db.exec(sql"delete from statics where module = ?", module[0])
+
+type
+  TRodWriter = object
+    module: PSym
+    sstack: seq[PSym]          # a stack of symbols to process
+    tstack: seq[PType]         # a stack of types to process
+    tmarks, smarks: IntSet
+    forwardedSyms: seq[PSym]
+
+  PRodWriter = var TRodWriter
+
+proc initRodWriter(module: PSym): TRodWriter =
+  result = TRodWriter(module: module, sstack: @[], tstack: @[],
+    tmarks: initIntSet(), smarks: initIntSet(), forwardedSyms: @[])
+
+when false:
+  proc getDefines(): string =
+    result = ""
+    for d in definedSymbolNames():
+      if result.len != 0: add(result, " ")
+      add(result, d)
+
+const
+  rodNL = "\L"
+
+proc pushType(w: PRodWriter, t: PType) =
+  if not containsOrIncl(w.tmarks, t.id):
+    w.tstack.add(t)
+
+proc pushSym(w: PRodWriter, s: PSym) =
+  if not containsOrIncl(w.smarks, s.id):
+    w.sstack.add(s)
+
+proc toDbFileId(fileIdx: int32): int =
+  if fileIdx == -1: return -1
+  let fullpath = fileIdx.toFullPath
+  let row = db.getRow(sql"select id, fullhash from filenames where fullpath = ?",
+    fullpath)
+  let id = row[0]
+  let fullhash = hashFileCached(fileIdx, fullpath)
+  if id.len == 0:
+    result = int db.insertID(sql"insert into filenames(fullpath, fullhash) values (?, ?)",
+      fullpath, fullhash)
+  else:
+    if row[1] != fullhash:
+      db.exec(sql"update filenames set fullhash = ? where fullpath = ?", fullhash, fullpath)
+    result = parseInt(id)
+
+proc fromDbFileId(dbId: int): int32 =
+  if dbId == -1: return -1
+  let fullpath = db.getValue(sql"select fullpath from filenames where id = ?", dbId)
+  doAssert fullpath.len > 0, "cannot find file name for DB ID " & $dbId
+  result = fileInfoIdx(fullpath)
+
+proc encodeNode(w: PRodWriter, fInfo: TLineInfo, n: PNode,
+                result: var string) =
+  if n == nil:
+    # nil nodes have to be stored too:
+    result.add("()")
+    return
+  result.add('(')
+  encodeVInt(ord(n.kind), result)
+  # we do not write comments for now
+  # Line information takes easily 20% or more of the filesize! Therefore we
+  # omit line information if it is the same as the parent's line information:
+  if fInfo.fileIndex != n.info.fileIndex:
+    result.add('?')
+    encodeVInt(n.info.col, result)
+    result.add(',')
+    encodeVInt(n.info.line, result)
+    result.add(',')
+    encodeVInt(toDbFileId(n.info.fileIndex), result)
+  elif fInfo.line != n.info.line:
+    result.add('?')
+    encodeVInt(n.info.col, result)
+    result.add(',')
+    encodeVInt(n.info.line, result)
+  elif fInfo.col != n.info.col:
+    result.add('?')
+    encodeVInt(n.info.col, result)
+  # No need to output the file index, as this is the serialization of one
+  # file.
+  let f = n.flags * PersistentNodeFlags
+  if f != {}:
+    result.add('$')
+    encodeVInt(cast[int32](f), result)
+  if n.typ != nil:
+    result.add('^')
+    encodeVInt(n.typ.id, result)
+    pushType(w, n.typ)
+  case n.kind
+  of nkCharLit..nkUInt64Lit:
+    if n.intVal != 0:
+      result.add('!')
+      encodeVBiggestInt(n.intVal, result)
+  of nkFloatLit..nkFloat64Lit:
+    if n.floatVal != 0.0:
+      result.add('!')
+      encodeStr($n.floatVal, result)
+  of nkStrLit..nkTripleStrLit:
+    if n.strVal != "":
+      result.add('!')
+      encodeStr(n.strVal, result)
+  of nkIdent:
+    result.add('!')
+    encodeStr(n.ident.s, result)
+  of nkSym:
+    result.add('!')
+    encodeVInt(n.sym.id, result)
+    pushSym(w, n.sym)
+  else:
+    for i in countup(0, sonsLen(n) - 1):
+      encodeNode(w, n.info, n.sons[i], result)
+  add(result, ')')
+
+proc encodeLoc(w: PRodWriter, loc: TLoc, result: var string) =
+  var oldLen = result.len
+  result.add('<')
+  if loc.k != low(loc.k): encodeVInt(ord(loc.k), result)
+  if loc.storage != low(loc.storage):
+    add(result, '*')
+    encodeVInt(ord(loc.storage), result)
+  if loc.flags != {}:
+    add(result, '$')
+    encodeVInt(cast[int32](loc.flags), result)
+  if loc.lode != nil:
+    add(result, '^')
+    encodeNode(w, unknownLineInfo(), loc.lode, result)
+    #encodeVInt(cast[int32](loc.t.id), result)
+    #pushType(w, loc.t)
+  if loc.r != nil:
+    add(result, '!')
+    encodeStr($loc.r, result)
+  if oldLen + 1 == result.len:
+    # no data was necessary, so remove the '<' again:
+    setLen(result, oldLen)
+  else:
+    add(result, '>')
+
+proc encodeType(w: PRodWriter, t: PType, result: var string) =
+  if t == nil:
+    # nil nodes have to be stored too:
+    result.add("[]")
+    return
+  # we need no surrounding [] here because the type is in a line of its own
+  if t.kind == tyForward: internalError("encodeType: tyForward")
+  # for the new rodfile viewer we use a preceding [ so that the data section
+  # can easily be disambiguated:
+  add(result, '[')
+  encodeVInt(ord(t.kind), result)
+  add(result, '+')
+  encodeVInt(t.id, result)
+  if t.n != nil:
+    encodeNode(w, w.module.info, t.n, result)
+  if t.flags != {}:
+    add(result, '$')
+    encodeVInt(cast[int32](t.flags), result)
+  if t.callConv != low(t.callConv):
+    add(result, '?')
+    encodeVInt(ord(t.callConv), result)
+  if t.owner != nil:
+    add(result, '*')
+    encodeVInt(t.owner.id, result)
+    pushSym(w, t.owner)
+  if t.sym != nil:
+    add(result, '&')
+    encodeVInt(t.sym.id, result)
+    pushSym(w, t.sym)
+  if t.size != - 1:
+    add(result, '/')
+    encodeVBiggestInt(t.size, result)
+  if t.align != 2:
+    add(result, '=')
+    encodeVInt(t.align, result)
+  if t.lockLevel.ord != UnspecifiedLockLevel.ord:
+    add(result, '\14')
+    encodeVInt(t.lockLevel.int16, result)
+  if t.destructor != nil and t.destructor.id != 0:
+    add(result, '\15')
+    encodeVInt(t.destructor.id, result)
+    pushSym(w, t.destructor)
+  if t.deepCopy != nil:
+    add(result, '\16')
+    encodeVInt(t.deepcopy.id, result)
+    pushSym(w, t.deepcopy)
+  if t.assignment != nil:
+    add(result, '\17')
+    encodeVInt(t.assignment.id, result)
+    pushSym(w, t.assignment)
+  if t.sink != nil:
+    add(result, '\18')
+    encodeVInt(t.sink.id, result)
+    pushSym(w, t.sink)
+  for i, s in items(t.methods):
+    add(result, '\19')
+    encodeVInt(i, result)
+    add(result, '\20')
+    encodeVInt(s.id, result)
+    pushSym(w, s)
+  encodeLoc(w, t.loc, result)
+  for i in countup(0, sonsLen(t) - 1):
+    if t.sons[i] == nil:
+      add(result, "^()")
+    else:
+      add(result, '^')
+      encodeVInt(t.sons[i].id, result)
+      pushType(w, t.sons[i])
+
+proc encodeLib(w: PRodWriter, lib: PLib, info: TLineInfo, result: var string) =
+  add(result, '|')
+  encodeVInt(ord(lib.kind), result)
+  add(result, '|')
+  encodeStr($lib.name, result)
+  add(result, '|')
+  encodeNode(w, info, lib.path, result)
+
+proc encodeInstantiations(w: PRodWriter; s: seq[PInstantiation];
+                          result: var string) =
+  for t in s:
+    result.add('\15')
+    encodeVInt(t.sym.id, result)
+    pushSym(w, t.sym)
+    for tt in t.concreteTypes:
+      result.add('\17')
+      encodeVInt(tt.id, result)
+      pushType(w, tt)
+    result.add('\20')
+    encodeVInt(t.compilesId, result)
+
+proc encodeSym(w: PRodWriter, s: PSym, result: var string) =
+  if s == nil:
+    # nil nodes have to be stored too:
+    result.add("{}")
+    return
+  # we need no surrounding {} here because the symbol is in a line of its own
+  encodeVInt(ord(s.kind), result)
+  result.add('+')
+  encodeVInt(s.id, result)
+  result.add('&')
+  encodeStr(s.name.s, result)
+  if s.typ != nil:
+    result.add('^')
+    encodeVInt(s.typ.id, result)
+    pushType(w, s.typ)
+  result.add('?')
+  if s.info.col != -1'i16: encodeVInt(s.info.col, result)
+  result.add(',')
+  if s.info.line != -1'i16: encodeVInt(s.info.line, result)
+  result.add(',')
+  encodeVInt(toDbFileId(s.info.fileIndex), result)
+  if s.owner != nil:
+    result.add('*')
+    encodeVInt(s.owner.id, result)
+    pushSym(w, s.owner)
+  if s.flags != {}:
+    result.add('$')
+    encodeVInt(cast[int32](s.flags), result)
+  if s.magic != mNone:
+    result.add('@')
+    encodeVInt(ord(s.magic), result)
+  result.add('!')
+  encodeVInt(cast[int32](s.options), result)
+  if s.position != 0:
+    result.add('%')
+    encodeVInt(s.position, result)
+  if s.offset != - 1:
+    result.add('`')
+    encodeVInt(s.offset, result)
+  encodeLoc(w, s.loc, result)
+  if s.annex != nil: encodeLib(w, s.annex, s.info, result)
+  if s.constraint != nil:
+    add(result, '#')
+    encodeNode(w, unknownLineInfo(), s.constraint, result)
+  case s.kind
+  of skType, skGenericParam:
+    for t in s.typeInstCache:
+      result.add('\14')
+      encodeVInt(t.id, result)
+      pushType(w, t)
+  of routineKinds:
+    encodeInstantiations(w, s.procInstCache, result)
+    if s.gcUnsafetyReason != nil:
+      result.add('\16')
+      encodeVInt(s.gcUnsafetyReason.id, result)
+      pushSym(w, s.gcUnsafetyReason)
+  of skModule, skPackage:
+    encodeInstantiations(w, s.usedGenerics, result)
+    # we don't serialize:
+    #tab*: TStrTable         # interface table for modules
+  of skLet, skVar, skField, skForVar:
+    if s.guard != nil:
+      result.add('\18')
+      encodeVInt(s.guard.id, result)
+      pushSym(w, s.guard)
+    if s.bitsize != 0:
+      result.add('\19')
+      encodeVInt(s.bitsize, result)
+  else: discard
+  # lazy loading will soon reload the ast lazily, so the ast needs to be
+  # the last entry of a symbol:
+  if s.ast != nil:
+    # we used to attempt to save space here by only storing a dummy AST if
+    # it is not necessary, but Nim's heavy compile-time evaluation features
+    # make that unfeasible nowadays:
+    encodeNode(w, s.info, s.ast, result)
+
+proc storeSym(w: PRodWriter; s: PSym) =
+  if sfForward in s.flags and s.kind != skModule:
+    w.forwardedSyms.add s
+    return
+  var buf = newStringOfCap(160)
+  encodeSym(w, s, buf)
+  # XXX only store the name for exported symbols in order to speed up lookup
+  # times once we enable the skStub logic.
+  db.exec(sql"insert into syms(nimid, module, name, data, exported) values (?, ?, ?, ?, ?)",
+    s.id, abs(w.module.id), s.name.s, buf, ord(sfExported in s.flags))
+
+proc storeType(w: PRodWriter; t: PType) =
+  var buf = newStringOfCap(160)
+  encodeType(w, t, buf)
+  db.exec(sql"insert into types(nimid, module, data) values (?, ?, ?)",
+    t.id, abs(w.module.id), buf)
+
+var w = initRodWriter(nil)
+
+proc storeNode*(module: PSym; n: PNode) =
+  if gSymbolFiles != v2Sf: return
+  w.module = module
+  var buf = newStringOfCap(160)
+  encodeNode(w, module.info, n, buf)
+  db.exec(sql"insert into toplevelstmts(module, position, data) values (?, ?, ?)",
+    abs(module.id), module.offset, buf)
+  inc module.offset
+  var i = 0
+  while true:
+    if i > 10_000:
+      quit "loop never ends!"
+    if w.sstack.len > 0:
+      let s = w.sstack.pop()
+      when false:
+        echo "popped ", s.name.s, " ", s.id
+      storeSym(w, s)
+    elif w.tstack.len > 0:
+      let t = w.tstack.pop()
+      storeType(w, t)
+      when false:
+        echo "popped type ", typeToString(t), " ", t.id
+    else:
+      break
+    inc i
+
+proc storeRemaining*(module: PSym) =
+  if gSymbolFiles != v2Sf: return
+  w.module = module
+  for s in w.forwardedSyms:
+    assert sfForward notin s.flags
+    storeSym(w, s)
+  w.forwardedSyms.setLen 0
+
+# ---------------- decoder -----------------------------------
+type
+  TRodReader = object
+    module: PSym
+    #sstack: seq[(PSym, ptr PSym)]       # a stack of symbols to process
+    #tstack: seq[(PType, ptr PType)]     # a stack of types to process
+
+    #tmarks, smarks: IntSet
+    syms: Table[int, PSym] ## XXX make this more efficients
+    types: Table[int, PType]
+    cache: IdentCache
+
+  BlobReader = object
+    s: string
+    pos: int
+
+  PRodReader = var TRodReader
+
+proc initRodReader(cache: IdentCache): TRodReader =
+  TRodReader(module: nil,
+    syms: initTable[int, PSym](), types: initTable[int, PType](),
+    cache: cache)
+
+var gr = initRodReader(newIdentCache())
+
+using
+  r: PRodReader
+  b: var BlobReader
+
+proc loadSym(r; id: int, info: TLineInfo): PSym
+proc loadType(r; id: int, info: TLineInfo): PType
+
+proc decodeLineInfo(r; b; info: var TLineInfo) =
+  if b.s[b.pos] == '?':
+    inc(b.pos)
+    if b.s[b.pos] == ',': info.col = -1'i16
+    else: info.col = int16(decodeVInt(b.s, b.pos))
+    if b.s[b.pos] == ',':
+      inc(b.pos)
+      if b.s[b.pos] == ',': info.line = -1'i16
+      else: info.line = int16(decodeVInt(b.s, b.pos))
+      if b.s[b.pos] == ',':
+        inc(b.pos)
+        info.fileIndex = fromDbFileId(decodeVInt(b.s, b.pos))
+
+proc skipNode(b) =
+  assert b.s[b.pos] == '('
+  var par = 0
+  var pos = b.pos+1
+  while true:
+    case b.s[pos]
+    of ')':
+      if par == 0: break
+      dec par
+    of '(': inc par
+    else: discard
+    inc pos
+  b.pos = pos+1 # skip ')'
+
+proc decodeNodeLazyBody(r; b; fInfo: TLineInfo,
+                        belongsTo: PSym): PNode =
+  result = nil
+  if b.s[b.pos] == '(':
+    inc(b.pos)
+    if b.s[b.pos] == ')':
+      inc(b.pos)
+      return                  # nil node
+    result = newNodeI(TNodeKind(decodeVInt(b.s, b.pos)), fInfo)
+    decodeLineInfo(r, b, result.info)
+    if b.s[b.pos] == '$':
+      inc(b.pos)
+      result.flags = cast[TNodeFlags](int32(decodeVInt(b.s, b.pos)))
+    if b.s[b.pos] == '^':
+      inc(b.pos)
+      var id = decodeVInt(b.s, b.pos)
+      result.typ = loadType(r, id, result.info)
+    case result.kind
+    of nkCharLit..nkUInt64Lit:
+      if b.s[b.pos] == '!':
+        inc(b.pos)
+        result.intVal = decodeVBiggestInt(b.s, b.pos)
+    of nkFloatLit..nkFloat64Lit:
+      if b.s[b.pos] == '!':
+        inc(b.pos)
+        var fl = decodeStr(b.s, b.pos)
+        result.floatVal = parseFloat(fl)
+    of nkStrLit..nkTripleStrLit:
+      if b.s[b.pos] == '!':
+        inc(b.pos)
+        result.strVal = decodeStr(b.s, b.pos)
+      else:
+        result.strVal = ""
+    of nkIdent:
+      if b.s[b.pos] == '!':
+        inc(b.pos)
+        var fl = decodeStr(b.s, b.pos)
+        result.ident = r.cache.getIdent(fl)
+      else:
+        internalError(result.info, "decodeNode: nkIdent")
+    of nkSym:
+      if b.s[b.pos] == '!':
+        inc(b.pos)
+        var id = decodeVInt(b.s, b.pos)
+        result.sym = loadSym(r, id, result.info)
+      else:
+        internalError(result.info, "decodeNode: nkSym")
+    else:
+      var i = 0
+      while b.s[b.pos] != ')':
+        when false:
+          if belongsTo != nil and i == bodyPos:
+            addSonNilAllowed(result, nil)
+            belongsTo.offset = b.pos
+            skipNode(b)
+          else:
+            discard
+        addSonNilAllowed(result, decodeNodeLazyBody(r, b, result.info, nil))
+        inc i
+    if b.s[b.pos] == ')': inc(b.pos)
+    else: internalError(result.info, "decodeNode: ')' missing")
+  else:
+    internalError(fInfo, "decodeNode: '(' missing " & $b.pos)
+
+proc decodeNode(r; b; fInfo: TLineInfo): PNode =
+  result = decodeNodeLazyBody(r, b, fInfo, nil)
+
+proc decodeLoc(r; b; loc: var TLoc, info: TLineInfo) =
+  if b.s[b.pos] == '<':
+    inc(b.pos)
+    if b.s[b.pos] in {'0'..'9', 'a'..'z', 'A'..'Z'}:
+      loc.k = TLocKind(decodeVInt(b.s, b.pos))
+    else:
+      loc.k = low(loc.k)
+    if b.s[b.pos] == '*':
+      inc(b.pos)
+      loc.storage = TStorageLoc(decodeVInt(b.s, b.pos))
+    else:
+      loc.storage = low(loc.storage)
+    if b.s[b.pos] == '$':
+      inc(b.pos)
+      loc.flags = cast[TLocFlags](int32(decodeVInt(b.s, b.pos)))
+    else:
+      loc.flags = {}
+    if b.s[b.pos] == '^':
+      inc(b.pos)
+      loc.lode = decodeNode(r, b, info)
+      # rrGetType(b, decodeVInt(b.s, b.pos), info)
+    else:
+      loc.lode = nil
+    if b.s[b.pos] == '!':
+      inc(b.pos)
+      loc.r = rope(decodeStr(b.s, b.pos))
+    else:
+      loc.r = nil
+    if b.s[b.pos] == '>': inc(b.pos)
+    else: internalError(info, "decodeLoc " & b.s[b.pos])
+
+proc loadBlob(query: SqlQuery; id: int): BlobReader =
+  let blob = db.getValue(query, id)
+  if blob.len == 0:
+    internalError("symbolfiles: cannot find ID " & $ id)
+  result = BlobReader(pos: 0)
+  shallowCopy(result.s, blob)
+
+proc loadType(r; id: int; info: TLineInfo): PType =
+  result = r.types.getOrDefault(id)
+  if result != nil: return result
+  var b = loadBlob(sql"select data from types where nimid = ?", id)
+
+  if b.s[b.pos] == '[':
+    inc(b.pos)
+    if b.s[b.pos] == ']':
+      inc(b.pos)
+      return                  # nil type
+  new(result)
+  result.kind = TTypeKind(decodeVInt(b.s, b.pos))
+  if b.s[b.pos] == '+':
+    inc(b.pos)
+    result.id = decodeVInt(b.s, b.pos)
+    setId(result.id)
+    #if debugIds: registerID(result)
+  else:
+    internalError(info, "decodeType: no id")
+  # here this also avoids endless recursion for recursive type
+  r.types[result.id] = result
+  if b.s[b.pos] == '(': result.n = decodeNode(r, b, unknownLineInfo())
+  if b.s[b.pos] == '$':
+    inc(b.pos)
+    result.flags = cast[TTypeFlags](int32(decodeVInt(b.s, b.pos)))
+  if b.s[b.pos] == '?':
+    inc(b.pos)
+    result.callConv = TCallingConvention(decodeVInt(b.s, b.pos))
+  if b.s[b.pos] == '*':
+    inc(b.pos)
+    result.owner = loadSym(r, decodeVInt(b.s, b.pos), info)
+  if b.s[b.pos] == '&':
+    inc(b.pos)
+    result.sym = loadSym(r, decodeVInt(b.s, b.pos), info)
+  if b.s[b.pos] == '/':
+    inc(b.pos)
+    result.size = decodeVInt(b.s, b.pos)
+  else:
+    result.size = -1
+  if b.s[b.pos] == '=':
+    inc(b.pos)
+    result.align = decodeVInt(b.s, b.pos).int16
+  else:
+    result.align = 2
+
+  if b.s[b.pos] == '\14':
+    inc(b.pos)
+    result.lockLevel = decodeVInt(b.s, b.pos).TLockLevel
+  else:
+    result.lockLevel = UnspecifiedLockLevel
+
+  if b.s[b.pos] == '\15':
+    inc(b.pos)
+    result.destructor = loadSym(r, decodeVInt(b.s, b.pos), info)
+  if b.s[b.pos] == '\16':
+    inc(b.pos)
+    result.deepCopy = loadSym(r, decodeVInt(b.s, b.pos), info)
+  if b.s[b.pos] == '\17':
+    inc(b.pos)
+    result.assignment = loadSym(r, decodeVInt(b.s, b.pos), info)
+  if b.s[b.pos] == '\18':
+    inc(b.pos)
+    result.sink = loadSym(r, decodeVInt(b.s, b.pos), info)
+  while b.s[b.pos] == '\19':
+    inc(b.pos)
+    let x = decodeVInt(b.s, b.pos)
+    doAssert b.s[b.pos] == '\20'
+    inc(b.pos)
+    let y = loadSym(r, decodeVInt(b.s, b.pos), info)
+    result.methods.safeAdd((x, y))
+  decodeLoc(r, b, result.loc, info)
+  while b.s[b.pos] == '^':
+    inc(b.pos)
+    if b.s[b.pos] == '(':
+      inc(b.pos)
+      if b.s[b.pos] == ')': inc(b.pos)
+      else: internalError(info, "decodeType ^(" & b.s[b.pos])
+      rawAddSon(result, nil)
+    else:
+      var d = decodeVInt(b.s, b.pos)
+      rawAddSon(result, loadType(r, d, info))
+
+proc decodeLib(r; b; info: TLineInfo): PLib =
+  result = nil
+  if b.s[b.pos] == '|':
+    new(result)
+    inc(b.pos)
+    result.kind = TLibKind(decodeVInt(b.s, b.pos))
+    if b.s[b.pos] != '|': internalError("decodeLib: 1")
+    inc(b.pos)
+    result.name = rope(decodeStr(b.s, b.pos))
+    if b.s[b.pos] != '|': internalError("decodeLib: 2")
+    inc(b.pos)
+    result.path = decodeNode(r, b, info)
+
+proc decodeInstantiations(r; b; info: TLineInfo;
+                          s: var seq[PInstantiation]) =
+  while b.s[b.pos] == '\15':
+    inc(b.pos)
+    var ii: PInstantiation
+    new ii
+    ii.sym = loadSym(r, decodeVInt(b.s, b.pos), info)
+    ii.concreteTypes = @[]
+    while b.s[b.pos] == '\17':
+      inc(b.pos)
+      ii.concreteTypes.add loadType(r, decodeVInt(b.s, b.pos), info)
+    if b.s[b.pos] == '\20':
+      inc(b.pos)
+      ii.compilesId = decodeVInt(b.s, b.pos)
+    s.safeAdd ii
+
+proc loadSymFromBlob(r; b; info: TLineInfo): PSym =
+  if b.s[b.pos] == '{':
+    inc(b.pos)
+    if b.s[b.pos] == '}':
+      inc(b.pos)
+      return                  # nil sym
+  var k = TSymKind(decodeVInt(b.s, b.pos))
+  var id: int
+  if b.s[b.pos] == '+':
+    inc(b.pos)
+    id = decodeVInt(b.s, b.pos)
+    setId(id)
+  else:
+    internalError(info, "decodeSym: no id")
+  var ident: PIdent
+  if b.s[b.pos] == '&':
+    inc(b.pos)
+    ident = r.cache.getIdent(decodeStr(b.s, b.pos))
+  else:
+    internalError(info, "decodeSym: no ident")
+  #echo "decoding: {", ident.s
+  new(result)
+  result.id = id
+  result.kind = k
+  result.name = ident         # read the rest of the symbol description:
+  r.syms[result.id] = result
+  if b.s[b.pos] == '^':
+    inc(b.pos)
+    result.typ = loadType(r, decodeVInt(b.s, b.pos), info)
+  decodeLineInfo(r, b, result.info)
+  if b.s[b.pos] == '*':
+    inc(b.pos)
+    result.owner = loadSym(r, decodeVInt(b.s, b.pos), result.info)
+  if b.s[b.pos] == '$':
+    inc(b.pos)
+    result.flags = cast[TSymFlags](int32(decodeVInt(b.s, b.pos)))
+  if b.s[b.pos] == '@':
+    inc(b.pos)
+    result.magic = TMagic(decodeVInt(b.s, b.pos))
+  if b.s[b.pos] == '!':
+    inc(b.pos)
+    result.options = cast[TOptions](int32(decodeVInt(b.s, b.pos)))
+  else:
+    result.options = r.module.options
+  if b.s[b.pos] == '%':
+    inc(b.pos)
+    result.position = decodeVInt(b.s, b.pos)
+  if b.s[b.pos] == '`':
+    inc(b.pos)
+    result.offset = decodeVInt(b.s, b.pos)
+  else:
+    result.offset = - 1
+  decodeLoc(r, b, result.loc, result.info)
+  result.annex = decodeLib(r, b, info)
+  if b.s[b.pos] == '#':
+    inc(b.pos)
+    result.constraint = decodeNode(r, b, unknownLineInfo())
+  case result.kind
+  of skType, skGenericParam:
+    while b.s[b.pos] == '\14':
+      inc(b.pos)
+      result.typeInstCache.safeAdd loadType(r, decodeVInt(b.s, b.pos), result.info)
+  of routineKinds:
+    decodeInstantiations(r, b, result.info, result.procInstCache)
+    if b.s[b.pos] == '\16':
+      inc(b.pos)
+      result.gcUnsafetyReason = loadSym(r, decodeVInt(b.s, b.pos), result.info)
+  of skModule, skPackage:
+    decodeInstantiations(r, b, result.info, result.usedGenerics)
+  of skLet, skVar, skField, skForVar:
+    if b.s[b.pos] == '\18':
+      inc(b.pos)
+      result.guard = loadSym(r, decodeVInt(b.s, b.pos), result.info)
+    if b.s[b.pos] == '\19':
+      inc(b.pos)
+      result.bitsize = decodeVInt(b.s, b.pos).int16
+  else: discard
+
+  if b.s[b.pos] == '(':
+    #if result.kind in routineKinds:
+    #  result.ast = decodeNodeLazyBody(b, result.info, result)
+    #else:
+    result.ast = decodeNode(r, b, result.info)
+  if sfCompilerProc in result.flags:
+    registerCompilerProc(result)
+    #echo "loading ", result.name.s
+
+proc loadSym(r; id: int; info: TLineInfo): PSym =
+  result = r.syms.getOrDefault(id)
+  if result != nil: return result
+  var b = loadBlob(sql"select data from syms where nimid = ?", id)
+  result = loadSymFromBlob(r, b, info)
+  doAssert id == result.id, "symbol ID is not consistent!"
+
+proc loadModuleSymTab(r; module: PSym) =
+  ## goal: fill  module.tab
+  gr.syms[module.id] = module
+  for row in db.fastRows(sql"select nimid, data from syms where module = ? and exported = 1", abs(module.id)):
+    let id = parseInt(row[0])
+    var s = r.syms.getOrDefault(id)
+    if s == nil:
+      var b = BlobReader(pos: 0)
+      shallowCopy(b.s, row[1])
+      s = loadSymFromBlob(r, b, module.info)
+    assert s != nil
+    strTableAdd(module.tab, s)
+  if sfSystemModule in module.flags:
+    magicsys.systemModule = module
+
+proc loadNode*(module: PSym; index: int): PNode =
+  assert gSymbolFiles == v2Sf
+  if index == 0:
+    loadModuleSymTab(gr, module)
+    #index = parseInt db.getValue(
+    #  sql"select min(id) from toplevelstmts where module = ?", abs module.id)
+  var b = BlobReader(pos: 0)
+  b.s = db.getValue(sql"select data from toplevelstmts where position = ? and module = ?",
+                    index, abs module.id)
+  if b.s.len == 0:
+    db.exec(sql"insert into controlblock(idgen) values (?)", gFrontEndId)
+    return nil # end marker
+  gr.module = module
+  result = decodeNode(gr, b, module.info)
+
+proc addModuleDep*(module, fileIdx: int32; isIncludeFile: bool) =
+  if gSymbolFiles != v2Sf: return
+
+  let a = toDbFileId(module)
+  let b = toDbFileId(fileIdx)
+
+  db.exec(sql"insert into deps(module, dependency, isIncludeFile) values (?, ?, ?)",
+    a, b, ord(isIncludeFile))
+
+# --------------- Database model ---------------------------------------------
+
+proc createDb() =
+  db.exec(sql"""
+    create table if not exists controlblock(
+      idgen integer not null
+    );
+  """)
+
+  db.exec(sql"""
+    create table if not exists filenames(
+      id integer primary key,
+      fullpath varchar(8000) not null,
+      fullHash varchar(256) not null
+    );
+  """)
+  db.exec sql"create index if not exists FilenameIx on filenames(fullpath);"
+
+  db.exec(sql"""
+    create table if not exists modules(
+      id integer primary key,
+      fullpath varchar(8000) not null,
+      interfHash varchar(256) not null,
+      fullHash varchar(256) not null,
+
+      created timestamp not null default (DATETIME('now'))
+    );""")
+  db.exec(sql"""create unique index if not exists SymNameIx on modules(fullpath);""")
+
+  db.exec(sql"""
+    create table if not exists deps(
+      id integer primary key,
+      module integer not null,
+      dependency integer not null,
+      isIncludeFile integer not null,
+      foreign key (module) references filenames(id),
+      foreign key (dependency) references filenames(id)
+    );""")
+  db.exec(sql"""create index if not exists DepsIx on deps(module);""")
+
+  db.exec(sql"""
+    create table if not exists types(
+      id integer primary key,
+      nimid integer not null,
+      module integer not null,
+      data blob not null,
+      foreign key (module) references module(id)
+    );
+  """)
+  db.exec sql"create index TypeByModuleIdx on types(module);"
+  db.exec sql"create index TypeByNimIdIdx on types(nimid);"
+
+  db.exec(sql"""
+    create table if not exists syms(
+      id integer primary key,
+      nimid integer not null,
+      module integer not null,
+      name varchar(256) not null,
+      data blob not null,
+      exported int not null,
+      foreign key (module) references module(id)
+    );
+  """)
+  db.exec sql"create index if not exists SymNameIx on syms(name);"
+  db.exec sql"create index SymByNameAndModuleIdx on syms(name, module);"
+  db.exec sql"create index SymByModuleIdx on syms(module);"
+  db.exec sql"create index SymByNimIdIdx on syms(nimid);"
+
+
+  db.exec(sql"""
+    create table if not exists toplevelstmts(
+      id integer primary key,
+      position integer not null,
+      module integer not null,
+      data blob not null,
+      foreign key (module) references module(id)
+    );
+  """)
+  db.exec sql"create index TopLevelStmtByModuleIdx on toplevelstmts(module);"
+  db.exec sql"create index TopLevelStmtByPositionIdx on toplevelstmts(position);"
+
+  db.exec(sql"""
+    create table if not exists statics(
+      id integer primary key,
+      module integer not null,
+      data blob not null,
+      foreign key (module) references module(id)
+    );
+  """)
+  db.exec sql"create index StaticsByModuleIdx on toplevelstmts(module);"
+  db.exec sql"insert into controlblock(idgen) values (0)"
+
+proc setupModuleCache* =
+  if gSymbolFiles != v2Sf: return
+  let dbfile = getNimcacheDir() / "rodfiles.db"
+  if not fileExists(dbfile):
+    db = open(connection=dbfile, user="nim", password="",
+              database="nim")
+    createDb()
+  else:
+    db = open(connection=dbfile, user="nim", password="",
+              database="nim")
+  db.exec(sql"pragma journal_mode=off")
+  db.exec(sql"pragma SYNCHRONOUS=off")
+  db.exec(sql"pragma LOCKING_MODE=exclusive")
+  let lastId = db.getValue(sql"select max(idgen) from controlblock")
+  if lastId.len > 0:
+    idgen.setId(parseInt lastId)