summary refs log blame commit diff stats
path: root/compiler/rodread.nim
blob: 4461641db1de18de360ea857670493c38b390ed7 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11


                               
                                         






                                                      

                                                                             
                                                     

                         









                                                                    

                                        




                      
                                        


















                                                                               




                                                                           






                                                                               

                                     


                                                                            


                                                                            











                                                              

                                                                              
                                
 


                                                                           
                                                      

     

                                                                               














                                                                                

                                       

                                         
    


                                                         
                                                


                                                







                                                                    
                                                         







                                                                             
                     


                              


                                                                  
                                           
                                           
 

                

                         




                                                              



                                                          
                                           
                                                  

                          
                                              
                                                     

                            
                                                                                
 















                                                         





                                        
                                                               


                                  
                                                                    

                          
                                     




                                                
                                                     


                                 
                                      



                                        
                                             




                                      
                                      





                                                         
                                       


                                                       
         
               
                               






                                                                           
                                    


                                                               


                                                         




                                                                
                                              



                          
                                                 



                          
                                                                



                          
                                                        



                          
                                           



                          
                                    





                                                         






                                        
                                                 

                        
                                      



                                            

                                                              


                                                                   
                                                                  

                        
                                                                

                        
                                                            

                        
                                                          

                        
                                        



                        
                                         



                        
                                               


                                                        






                                                             
                            
          
                                    
                                              
 
                                                       



                        
                                                  

                                                       
                                               

                                                       
                                     


                                                       







                                       
                                          

                        
                               




                                           
                                           

                                              
                              











                                                                        
                                                           


                                
                                                                   

                        
                                                                 

                        
                                                 

                        
                                                                  



                              
                                            
                                                   
                       
                                                              
                                                        

                        
                                          


                                       
                                   


                                                             


                                                                   

                                             
                                 
 
                                  


                                         
                                              


                     
                            
                    





                    

                                           

                
                                           
















                                                           


                                                       
                                 
              
                                    
                                   
                              




                                                         


                                                                   
                                 
              
                                    
                                   
                                         










                                                       
                                


                                
                                                   











                                                                    
















                                                                     

                                                  
             
                   
                            
                           

                                                                    

                 
                                        
                                                                   
             
                                        
                                         
                       
                  
                                        
                                                               
                                                            






                                                                   
                                                                        
                 


                                        
                                 




                                                                            
                

                                           
                              

                                                  
                                                                             

                                        
                                      
                   

                                           
                               
                                           
                                        
                                        






                                                                    
              
                                        
                                 
                                                     
                                        
                 

                             
                        

                                    
                
                              
                  
                                
                     

                                 


                              
               
                                    
                                                                           

                                                                             
               

                                      




                                                            




                             





                                                             

                                                    
             



                                               

                      
                      
                





                             
                                                                           
            
                                    



                                                      
                    
                                          


                                       
                                  
                                                                           



                            
       


                                                                 


                                            

                                       













                                                        
                               


                                                                            
                                                         
                     



                              







                                                       
                                                               


                                       
                                       
                        
                                 
                                                  
                      

                         
                                                               
                                                  
                                        
                            

                                      



                                                    


                               
                                           

                                                                     






                                                                      
                                                                 

                                             
                                                                       
                    

                              
                                                   
                                  
                                   
                 





                                                    

                                                                           
                                             

                                     
                             
                                  

                                             








                                                    


                                           
                                              


                           
                                                      
                              
                                  


                                                       
                                         



                                                                      

                                                 
















                                                                           

                                                               
                                                            
                                 
                                          




                                                                    
                                          

                       
                                                               
 
                            
                                  







                                                                    

                       

                       
                                         
                                    
                                                         



                                   
 
                           
                                                


                                                        
                                                            
                                                     


                                                    

                                                 
  










                                                                         















                                                                            
                       
                              
#
#
#           The Nimrod Compiler
#        (c) Copyright 2012 Andreas Rumpf
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.
#

# This module is responsible for loading of rod files.
#
# Reading and writing binary files are really hard to debug. Therefore we use
# a "creative" text/binary hybrid format. ROD-files are more efficient
# to process because symbols can be loaded on demand.
# 
# A ROD file consists of:
#
#  - a header:
#    NIM:$fileversion\n
#  - the module's id (even if the module changed, its ID will not!):
#    ID:Ax3\n
#  - CRC value of this module:
#    CRC:CRC-val\n
#  - a section containing the compiler options and defines this
#    module has been compiled with:
#    OPTIONS:options\n
#    GOPTIONS:options\n # global options
#    CMD:command\n
#    DEFINES:defines\n
#  - FILES(
#    myfile.inc
#    lib/mymodA
#    )
#  - an include file dependency section:
#    INCLUDES(
#    <fileidx> <CRC of myfile.inc>\n # fileidx is the LINE in the file section!
#    )
#  - a module dependency section:
#    DEPS: <fileidx> <fileidx>\n
#  - an interface section:
#    INTERF(
#    identifier1 id\n # id is the symbol's id
#    identifier2 id\n
#    )
#  - a compiler proc section:
#    COMPILERPROCS(
#    identifier1 id\n # id is the symbol's id    
#    )
#  - an index consisting of (ID, linenumber)-pairs:
#    INDEX(
#    id-diff idx-diff\n
#    id-diff idx-diff\n
#    )
#
#    Since the whole index has to be read in advance, we compress it by 
#    storing the integer differences to the last entry instead of using the
#    real numbers.
#
#  - an import index consisting of (ID, moduleID)-pairs:
#    IMPORTS(
#    id-diff moduleID-diff\n
#    id-diff moduleID-diff\n
#    )
#  - a list of all exported type converters because they are needed for correct
#    semantic checking:
#    CONVERTERS:id id\n   # symbol ID
#
#    This is a misnomer now; it's really a "load unconditionally" section as
#    it is also used for pattern templates.
#
#  - a list of all (private or exported) methods because they are needed for
#    correct dispatcher generation:
#    METHODS: id id\n   # symbol ID
#  - an AST section that contains the module's AST:
#    INIT(
#    idx\n  # position of the node in the DATA section
#    idx\n
#    )
#  - a data section, where each type, symbol or AST is stored.
#    DATA(
#    type
#    (node)
#    sym
#    )
#
#    The data section MUST be the last section of the file, because processing
#    stops immediately after ``DATA(`` and the rest is only loaded on demand
#    by using a mem'mapped file.
#

import 
  os, options, strutils, nversion, ast, astalgo, msgs, platform, condsyms, 
  ropes, idents, crc, idgen, types, rodutils, memfiles

type 
  TReasonForRecompile* = enum ## all the reasons that can trigger recompilation
    rrEmpty,                  # dependencies not yet computed
    rrNone,                   # no need to recompile
    rrRodDoesNotExist,        # rod file does not exist
    rrRodInvalid,             # rod file is invalid
    rrCrcChange,              # file has been edited since last recompilation
    rrDefines,                # defines have changed
    rrOptions,                # options have changed
    rrInclDeps,               # an include has changed
    rrModDeps                 # a module this module depends on has been changed

const 
  reasonToFrmt*: array[TReasonForRecompile, string] = ["", 
    "no need to recompile: $1", "symbol file for $1 does not exist", 
    "symbol file for $1 has the wrong version", 
    "file edited since last compilation: $1", 
    "list of conditional symbols changed for: $1", 
    "list of options changed for: $1", 
    "an include file edited: $1", 
    "a module $1 depends on has changed"]

type
  TIndex*{.final.} = object   # an index with compression
    lastIdxKey*, lastIdxVal*: int
    tab*: TIITable
    r*: string                # writers use this
    offset*: int              # readers use this
  
  TRodReader* = object of TObject
    pos: int                 # position; used for parsing
    s: cstring               # mmap'ed file contents
    options: TOptions
    reason: TReasonForRecompile
    modDeps: TStringSeq
    files: TStringSeq
    dataIdx: int             # offset of start of data section
    convertersIdx: int       # offset of start of converters section
    initIdx, interfIdx, compilerProcsIdx, methodsIdx: int
    filename: string
    index, imports: TIndex
    readerIndex: int
    line: int            # only used for debugging, but is always in the code
    moduleID: int
    syms: TIdTable       # already processed symbols
    memfile: TMemFile    # unfortunately there is no point in time where we
                         # can close this! XXX
    methods*: TSymSeq
  
  PRodReader* = ref TRodReader

var rodCompilerprocs*: TStrTable

proc handleSymbolFile*(module: PSym, filename: string): PRodReader
# global because this is needed by magicsys
proc loadInitSection*(r: PRodReader): PNode

# implementation

proc rawLoadStub(s: PSym)

var gTypeTable: TIdTable

proc rrGetSym(r: PRodReader, id: int, info: TLineInfo): PSym
  # `info` is only used for debugging purposes
proc rrGetType(r: PRodReader, id: int, info: TLineInfo): PType

proc decodeLineInfo(r: PRodReader, info: var TLineInfo) = 
  if r.s[r.pos] == '?': 
    inc(r.pos)
    if r.s[r.pos] == ',': info.col = -1'i16
    else: info.col = int16(decodeVInt(r.s, r.pos))
    if r.s[r.pos] == ',': 
      inc(r.pos)
      if r.s[r.pos] == ',': info.line = -1'i16
      else: info.line = int16(decodeVInt(r.s, r.pos))
      if r.s[r.pos] == ',': 
        inc(r.pos)
        info = newLineInfo(r.files[decodeVInt(r.s, r.pos)], info.line, info.col)

proc skipNode(r: PRodReader) =
  assert r.s[r.pos] == '('
  var par = 0
  var pos = r.pos+1
  while true:
    case r.s[pos]
    of ')':
      if par == 0: break
      dec par
    of '(': inc par
    else: nil
    inc pos
  r.pos = pos+1 # skip ')'

proc decodeNodeLazyBody(r: PRodReader, fInfo: TLineInfo, 
                        belongsTo: PSym): PNode = 
  result = nil
  if r.s[r.pos] == '(': 
    inc(r.pos)
    if r.s[r.pos] == ')': 
      inc(r.pos)
      return                  # nil node
    result = newNodeI(TNodeKind(decodeVInt(r.s, r.pos)), fInfo)
    decodeLineInfo(r, result.info)
    if r.s[r.pos] == '$': 
      inc(r.pos)
      result.flags = cast[TNodeFlags](int32(decodeVInt(r.s, r.pos)))
    if r.s[r.pos] == '^': 
      inc(r.pos)
      var id = decodeVInt(r.s, r.pos)
      result.typ = rrGetType(r, id, result.info)
    case result.kind
    of nkCharLit..nkInt64Lit: 
      if r.s[r.pos] == '!': 
        inc(r.pos)
        result.intVal = decodeVBiggestInt(r.s, r.pos)
    of nkFloatLit..nkFloat64Lit: 
      if r.s[r.pos] == '!': 
        inc(r.pos)
        var fl = decodeStr(r.s, r.pos)
        result.floatVal = parseFloat(fl)
    of nkStrLit..nkTripleStrLit: 
      if r.s[r.pos] == '!': 
        inc(r.pos)
        result.strVal = decodeStr(r.s, r.pos)
      else: 
        result.strVal = ""    # BUGFIX
    of nkIdent: 
      if r.s[r.pos] == '!': 
        inc(r.pos)
        var fl = decodeStr(r.s, r.pos)
        result.ident = getIdent(fl)
      else: 
        internalError(result.info, "decodeNode: nkIdent")
    of nkSym: 
      if r.s[r.pos] == '!': 
        inc(r.pos)
        var id = decodeVInt(r.s, r.pos)
        result.sym = rrGetSym(r, id, result.info)
      else: 
        internalError(result.info, "decodeNode: nkSym")
    else:
      var i = 0
      while r.s[r.pos] != ')': 
        if belongsTo != nil and i == bodyPos:
          addSonNilAllowed(result, nil)
          belongsTo.offset = r.pos
          skipNode(r)
        else:
          addSonNilAllowed(result, decodeNodeLazyBody(r, result.info, nil))
        inc i
    if r.s[r.pos] == ')': inc(r.pos)
    else: internalError(result.info, "decodeNode: ')' missing")
  else:
    InternalError(fInfo, "decodeNode: '(' missing " & $r.pos)

proc decodeNode(r: PRodReader, fInfo: TLineInfo): PNode =
  result = decodeNodeLazyBody(r, fInfo, nil)
  
proc decodeLoc(r: PRodReader, loc: var TLoc, info: TLineInfo) = 
  if r.s[r.pos] == '<': 
    inc(r.pos)
    if r.s[r.pos] in {'0'..'9', 'a'..'z', 'A'..'Z'}: 
      loc.k = TLocKind(decodeVInt(r.s, r.pos))
    else: 
      loc.k = low(loc.k)
    if r.s[r.pos] == '*': 
      inc(r.pos)
      loc.s = TStorageLoc(decodeVInt(r.s, r.pos))
    else: 
      loc.s = low(loc.s)
    if r.s[r.pos] == '$': 
      inc(r.pos)
      loc.flags = cast[TLocFlags](int32(decodeVInt(r.s, r.pos)))
    else: 
      loc.flags = {}
    if r.s[r.pos] == '^': 
      inc(r.pos)
      loc.t = rrGetType(r, decodeVInt(r.s, r.pos), info)
    else: 
      loc.t = nil
    if r.s[r.pos] == '!': 
      inc(r.pos)
      loc.r = toRope(decodeStr(r.s, r.pos))
    else: 
      loc.r = nil
    if r.s[r.pos] == '?': 
      inc(r.pos)
      loc.a = decodeVInt(r.s, r.pos)
    else: 
      loc.a = 0
    if r.s[r.pos] == '>': inc(r.pos)
    else: InternalError(info, "decodeLoc " & r.s[r.pos])
  
proc decodeType(r: PRodReader, info: TLineInfo): PType = 
  result = nil
  if r.s[r.pos] == '[': 
    inc(r.pos)
    if r.s[r.pos] == ']': 
      inc(r.pos)
      return                  # nil type
  new(result)
  result.kind = TTypeKind(decodeVInt(r.s, r.pos))
  if r.s[r.pos] == '+': 
    inc(r.pos)
    result.id = decodeVInt(r.s, r.pos)
    setId(result.id)
    if debugIds: registerID(result)
  else: 
    InternalError(info, "decodeType: no id")
  # here this also avoids endless recursion for recursive type
  IdTablePut(gTypeTable, result, result) 
  if r.s[r.pos] == '(': result.n = decodeNode(r, UnknownLineInfo())
  if r.s[r.pos] == '$': 
    inc(r.pos)
    result.flags = cast[TTypeFlags](int32(decodeVInt(r.s, r.pos)))
  if r.s[r.pos] == '?': 
    inc(r.pos)
    result.callConv = TCallingConvention(decodeVInt(r.s, r.pos))
  if r.s[r.pos] == '*': 
    inc(r.pos)
    result.owner = rrGetSym(r, decodeVInt(r.s, r.pos), info)
  if r.s[r.pos] == '&': 
    inc(r.pos)
    result.sym = rrGetSym(r, decodeVInt(r.s, r.pos), info)
  if r.s[r.pos] == '/': 
    inc(r.pos)
    result.size = decodeVInt(r.s, r.pos)
  else: 
    result.size = - 1
  if r.s[r.pos] == '=': 
    inc(r.pos)
    result.align = decodeVInt(r.s, r.pos)
  else: 
    result.align = 2
  if r.s[r.pos] == '@': 
    inc(r.pos)
    result.containerID = decodeVInt(r.s, r.pos)
  if r.s[r.pos] == '`':
    inc(r.pos)
    result.constraint = decodeNode(r, UnknownLineInfo())
  decodeLoc(r, result.loc, info)
  while r.s[r.pos] == '^': 
    inc(r.pos)
    if r.s[r.pos] == '(': 
      inc(r.pos)
      if r.s[r.pos] == ')': inc(r.pos)
      else: InternalError(info, "decodeType ^(" & r.s[r.pos])
      rawAddSon(result, nil)
    else: 
      var d = decodeVInt(r.s, r.pos)
      rawAddSon(result, rrGetType(r, d, info))

proc decodeLib(r: PRodReader, info: TLineInfo): PLib = 
  result = nil
  if r.s[r.pos] == '|': 
    new(result)
    inc(r.pos)
    result.kind = TLibKind(decodeVInt(r.s, r.pos))
    if r.s[r.pos] != '|': InternalError("decodeLib: 1")
    inc(r.pos)
    result.name = toRope(decodeStr(r.s, r.pos))
    if r.s[r.pos] != '|': InternalError("decodeLib: 2")
    inc(r.pos)
    result.path = decodeNode(r, info)

proc decodeSym(r: PRodReader, info: TLineInfo): PSym = 
  var 
    id: int
    ident: PIdent
  result = nil
  if r.s[r.pos] == '{': 
    inc(r.pos)
    if r.s[r.pos] == '}': 
      inc(r.pos)
      return                  # nil sym
  var k = TSymKind(decodeVInt(r.s, r.pos))
  if r.s[r.pos] == '+': 
    inc(r.pos)
    id = decodeVInt(r.s, r.pos)
    setId(id)
  else: 
    InternalError(info, "decodeSym: no id")
  if r.s[r.pos] == '&': 
    inc(r.pos)
    ident = getIdent(decodeStr(r.s, r.pos))
  else: 
    InternalError(info, "decodeSym: no ident")
  #echo "decoding: {", ident.s
  result = PSym(IdTableGet(r.syms, id))
  if result == nil: 
    new(result)
    result.id = id
    IdTablePut(r.syms, result, result)
    if debugIds: registerID(result)
  elif (result.id != id): 
    InternalError(info, "decodeSym: wrong id")
  result.kind = k
  result.name = ident         # read the rest of the symbol description:
  if r.s[r.pos] == '^': 
    inc(r.pos)
    result.typ = rrGetType(r, decodeVInt(r.s, r.pos), info)
  decodeLineInfo(r, result.info)
  if r.s[r.pos] == '*': 
    inc(r.pos)
    result.owner = rrGetSym(r, decodeVInt(r.s, r.pos), result.info)
  if r.s[r.pos] == '$': 
    inc(r.pos)
    result.flags = cast[TSymFlags](int32(decodeVInt(r.s, r.pos)))
  if r.s[r.pos] == '@': 
    inc(r.pos)
    result.magic = TMagic(decodeVInt(r.s, r.pos))
  if r.s[r.pos] == '!': 
    inc(r.pos)
    result.options = cast[TOptions](int32(decodeVInt(r.s, r.pos)))
  else: 
    result.options = r.options
  if r.s[r.pos] == '%': 
    inc(r.pos)
    result.position = decodeVInt(r.s, r.pos)
  elif result.kind notin routineKinds + {skModule}:
    result.position = 0
    # this may have been misused as reader index! But we still
    # need it for routines as the body is loaded lazily.
  if r.s[r.pos] == '`': 
    inc(r.pos)
    result.offset = decodeVInt(r.s, r.pos)
  else: 
    result.offset = - 1
  decodeLoc(r, result.loc, result.info)
  result.annex = decodeLib(r, info)
  if r.s[r.pos] == '(':
    if result.kind in routineKinds:
      result.ast = decodeNodeLazyBody(r, result.info, result)
      # since we load the body lazily, we need to set the reader to
      # be able to reload:
      result.position = r.readerIndex
    else:
      result.ast = decodeNode(r, result.info)
  #echo "decoded: ", ident.s, "}"

proc skipSection(r: PRodReader) = 
  if r.s[r.pos] == ':': 
    while r.s[r.pos] > '\x0A': inc(r.pos)
  elif r.s[r.pos] == '(': 
    var c = 0                 # count () pairs
    inc(r.pos)
    while true: 
      case r.s[r.pos]
      of '\x0A': inc(r.line)
      of '(': inc(c)
      of ')': 
        if c == 0: 
          inc(r.pos)
          break 
        elif c > 0: 
          dec(c)
      of '\0': break          # end of file
      else: nil
      inc(r.pos)
  else: 
    InternalError("skipSection " & $r.line)
  
proc rdWord(r: PRodReader): string = 
  result = ""
  while r.s[r.pos] in {'A'..'Z', '_', 'a'..'z', '0'..'9'}: 
    add(result, r.s[r.pos])
    inc(r.pos)

proc newStub(r: PRodReader, name: string, id: int): PSym = 
  new(result)
  result.kind = skStub
  result.id = id
  result.name = getIdent(name)
  result.position = r.readerIndex
  setID(id)                   #MessageOut(result.name.s);
  if debugIds: registerID(result)
  
proc processInterf(r: PRodReader, module: PSym) = 
  if r.interfIdx == 0: InternalError("processInterf")
  r.pos = r.interfIdx
  while (r.s[r.pos] > '\x0A') and (r.s[r.pos] != ')'): 
    var w = decodeStr(r.s, r.pos)
    inc(r.pos)
    var key = decodeVInt(r.s, r.pos)
    inc(r.pos)                # #10
    var s = newStub(r, w, key)
    s.owner = module
    StrTableAdd(module.tab, s)
    IdTablePut(r.syms, s, s)

proc processCompilerProcs(r: PRodReader, module: PSym) = 
  if r.compilerProcsIdx == 0: InternalError("processCompilerProcs")
  r.pos = r.compilerProcsIdx
  while (r.s[r.pos] > '\x0A') and (r.s[r.pos] != ')'): 
    var w = decodeStr(r.s, r.pos)
    inc(r.pos)
    var key = decodeVInt(r.s, r.pos)
    inc(r.pos)                # #10
    var s = PSym(IdTableGet(r.syms, key))
    if s == nil: 
      s = newStub(r, w, key)
      s.owner = module
      IdTablePut(r.syms, s, s)
    StrTableAdd(rodCompilerProcs, s)

proc processIndex(r: PRodReader, idx: var TIndex) = 
  var key, val, tmp: int
  inc(r.pos, 2)               # skip "(\10"
  inc(r.line)
  while (r.s[r.pos] > '\x0A') and (r.s[r.pos] != ')'): 
    tmp = decodeVInt(r.s, r.pos)
    if r.s[r.pos] == ' ': 
      inc(r.pos)
      key = idx.lastIdxKey + tmp
      val = decodeVInt(r.s, r.pos) + idx.lastIdxVal
    else: 
      key = idx.lastIdxKey + 1
      val = tmp + idx.lastIdxVal
    IITablePut(idx.tab, key, val)
    idx.lastIdxKey = key
    idx.lastIdxVal = val
    setID(key)                # ensure that this id will not be used
    if r.s[r.pos] == '\x0A': 
      inc(r.pos)
      inc(r.line)
  if r.s[r.pos] == ')': inc(r.pos)
  
proc cmdChangeTriggersRecompilation(old, new: TCommands): bool =
  if old == new: return false
  # we use a 'case' statement without 'else' so that addition of a
  # new command forces us to consider it here :-)
  case old
  of cmdCompileToC, cmdCompileToCpp, cmdCompileToOC,
      cmdCompileToEcmaScript, cmdCompileToLLVM:
    if new in {cmdDoc, cmdCheck, cmdIdeTools, cmdPretty, cmdDef,
               cmdInteractive}:
      return false
  of cmdNone, cmdDoc, cmdInterpret, cmdPretty, cmdGenDepend, cmdDump,
      cmdCheck, cmdParse, cmdScan, cmdIdeTools, cmdDef, 
      cmdRst2html, cmdRst2tex, cmdInteractive, cmdRun:
    nil
  # else: trigger recompilation:
  result = true
  
proc processRodFile(r: PRodReader, crc: TCrc32) = 
  var 
    w: string
    d, inclCrc: int
  while r.s[r.pos] != '\0': 
    var section = rdWord(r)
    if r.reason != rrNone: 
      break                   # no need to process this file further
    case section 
    of "CRC": 
      inc(r.pos)              # skip ':'
      if int(crc) != decodeVInt(r.s, r.pos): r.reason = rrCrcChange
    of "ID": 
      inc(r.pos)              # skip ':'
      r.moduleID = decodeVInt(r.s, r.pos)
      setID(r.moduleID)
    of "OPTIONS": 
      inc(r.pos)              # skip ':'
      r.options = cast[TOptions](int32(decodeVInt(r.s, r.pos)))
      if options.gOptions != r.options: r.reason = rrOptions
    of "GOPTIONS":
      inc(r.pos)              # skip ':'
      var dep = cast[TGlobalOptions](int32(decodeVInt(r.s, r.pos)))
      if gGlobalOptions != dep: r.reason = rrOptions
    of "CMD":
      inc(r.pos)              # skip ':'
      var dep = cast[TCommands](int32(decodeVInt(r.s, r.pos)))
      if cmdChangeTriggersRecompilation(dep, gCmd): r.reason = rrOptions
    of "DEFINES":
      inc(r.pos)              # skip ':'
      d = 0
      while r.s[r.pos] > '\x0A': 
        w = decodeStr(r.s, r.pos)
        inc(d)
        if not condsyms.isDefined(getIdent(w)): 
          r.reason = rrDefines #MessageOut('not defined, but should: ' + w);
        if r.s[r.pos] == ' ': inc(r.pos)
      if (d != countDefinedSymbols()): r.reason = rrDefines
    of "FILES": 
      inc(r.pos, 2)           # skip "(\10"
      inc(r.line)
      while r.s[r.pos] != ')':
        var relativePath = decodeStr(r.s, r.pos)
        var resolvedPath = relativePath.findModule
        r.files.add(if resolvedPath.len > 0: resolvedPath else: relativePath)
        inc(r.pos)            # skip #10
        inc(r.line)
      if r.s[r.pos] == ')': inc(r.pos)
    of "INCLUDES": 
      inc(r.pos, 2)           # skip "(\10"
      inc(r.line)
      while r.s[r.pos] != ')': 
        w = r.files[decodeVInt(r.s, r.pos)]
        inc(r.pos)            # skip ' '
        inclCrc = decodeVInt(r.s, r.pos)
        if r.reason == rrNone: 
          if not ExistsFile(w) or (inclCrc != int(crcFromFile(w))): 
            r.reason = rrInclDeps
        if r.s[r.pos] == '\x0A': 
          inc(r.pos)
          inc(r.line)
      if r.s[r.pos] == ')': inc(r.pos)
    of "DEPS":
      inc(r.pos)              # skip ':'
      while r.s[r.pos] > '\x0A': 
        r.modDeps.add r.files[decodeVInt(r.s, r.pos)]
        if r.s[r.pos] == ' ': inc(r.pos)
    of "INTERF": 
      r.interfIdx = r.pos + 2
      skipSection(r)
    of "COMPILERPROCS": 
      r.compilerProcsIdx = r.pos + 2
      skipSection(r)
    of "INDEX": 
      processIndex(r, r.index)
    of "IMPORTS": 
      processIndex(r, r.imports)
    of "CONVERTERS": 
      r.convertersIdx = r.pos + 1
      skipSection(r)
    of "METHODS":
      r.methodsIdx = r.pos + 1
      skipSection(r)
    of "DATA": 
      r.dataIdx = r.pos + 2 # "(\10"
      # We do not read the DATA section here! We read the needed objects on
      # demand. And the DATA section comes last in the file, so we stop here:
      break
    of "INIT": 
      r.initIdx = r.pos + 2   # "(\10"
      skipSection(r)
    else:
      InternalError("invalid section: '" & section &
                    "' at " & $r.line & " in " & r.filename)
      #MsgWriteln("skipping section: " & section &
      #           " at " & $r.line & " in " & r.filename)
      skipSection(r)
    if r.s[r.pos] == '\x0A': 
      inc(r.pos)
      inc(r.line)


proc startsWith(buf: cstring, token: string, pos = 0): bool =
  var s = 0
  while s < token.len and buf[pos+s] == token[s]: inc s
  result = s == token.len

proc newRodReader(modfilename: string, crc: TCrc32, 
                  readerIndex: int): PRodReader = 
  new(result)
  try:
    result.memFile = memfiles.open(modfilename)
  except EOS:
    return nil
  result.files = @[]
  result.modDeps = @[]
  result.methods = @[]
  var r = result
  r.reason = rrNone
  r.pos = 0
  r.line = 1
  r.readerIndex = readerIndex
  r.filename = modfilename
  InitIdTable(r.syms)
  # we terminate the file explicitely with ``\0``, so the cast to `cstring`
  # is safe:
  r.s = cast[cstring](r.memFile.mem)
  if startsWith(r.s, "NIM:"): 
    initIITable(r.index.tab)
    initIITable(r.imports.tab) # looks like a ROD file
    inc(r.pos, 4)
    var version = ""
    while r.s[r.pos] notin {'\0', '\x0A'}:
      add(version, r.s[r.pos])
      inc(r.pos)
    if r.s[r.pos] == '\x0A': inc(r.pos)
    if version == RodFileVersion: 
      # since ROD files are only for caching, no backwards compatibility is
      # needed
      processRodFile(r, crc)
    else: 
      result = nil
  else:
    result = nil
  
proc rrGetType(r: PRodReader, id: int, info: TLineInfo): PType = 
  result = PType(IdTableGet(gTypeTable, id))
  if result == nil: 
    # load the type:
    var oldPos = r.pos
    var d = IITableGet(r.index.tab, id)
    if d == invalidKey: InternalError(info, "rrGetType")
    r.pos = d + r.dataIdx
    result = decodeType(r, info)
    r.pos = oldPos

type 
  TFileModuleRec{.final.} = object 
    filename*: string
    reason*: TReasonForRecompile
    rd*: PRodReader
    crc*: TCrc32

  TFileModuleMap = seq[TFileModuleRec]

var gMods: TFileModuleMap = @[]

proc decodeSymSafePos(rd: PRodReader, offset: int, info: TLineInfo): PSym = 
  # all compiled modules
  if rd.dataIdx == 0: InternalError(info, "dataIdx == 0")
  var oldPos = rd.pos
  rd.pos = offset + rd.dataIdx
  result = decodeSym(rd, info)
  rd.pos = oldPos

proc findSomeWhere(id: int) =
  for i in countup(0, high(gMods)): 
    var rd = gMods[i].rd
    if rd != nil: 
      var d = IITableGet(rd.index.tab, id)
      if d != invalidKey:
        echo "found id ", id, " in ", gMods[i].filename

proc rrGetSym(r: PRodReader, id: int, info: TLineInfo): PSym = 
  result = PSym(IdTableGet(r.syms, id))
  if result == nil: 
    # load the symbol:
    var d = IITableGet(r.index.tab, id)
    if d == invalidKey: 
      # import from other module:
      var moduleID = IiTableGet(r.imports.tab, id)
      if moduleID < 0:
        var x = ""
        encodeVInt(id, x)
        InternalError(info, "missing from both indexes: +" & x)
      # find the reader with the correct moduleID:
      for i in countup(0, high(gMods)): 
        var rd = gMods[i].rd
        if rd != nil: 
          if rd.moduleID == moduleID: 
            d = IITableGet(rd.index.tab, id)
            if d != invalidKey: 
              result = decodeSymSafePos(rd, d, info)
              break 
            else:
              var x = ""
              encodeVInt(id, x)
              when false: findSomeWhere(id)
              InternalError(info, "rrGetSym: no reader found: +" & x)
          else:
            #if IiTableGet(rd.index.tab, id) <> invalidKey then
            # XXX expensive check!
            #InternalError(info,
            #'id found in other module: +' + ropeToStr(encodeInt(id)))
    else: 
      # own symbol:
      result = decodeSymSafePos(r, d, info)
  if result != nil and result.kind == skStub: rawLoadStub(result)
  
proc loadInitSection(r: PRodReader): PNode = 
  if r.initIdx == 0 or r.dataIdx == 0: InternalError("loadInitSection")
  var oldPos = r.pos
  r.pos = r.initIdx
  result = newNode(nkStmtList)
  while r.s[r.pos] > '\x0A' and r.s[r.pos] != ')': 
    var d = decodeVInt(r.s, r.pos)
    inc(r.pos)                # #10
    var p = r.pos
    r.pos = d + r.dataIdx
    addSon(result, decodeNode(r, UnknownLineInfo()))
    r.pos = p
  r.pos = oldPos

proc loadConverters(r: PRodReader) = 
  # We have to ensure that no exported converter is a stub anymore, and the
  # import mechanism takes care of the rest.
  if r.convertersIdx == 0 or r.dataIdx == 0: 
    InternalError("importConverters")
  r.pos = r.convertersIdx
  while r.s[r.pos] > '\x0A': 
    var d = decodeVInt(r.s, r.pos)
    discard rrGetSym(r, d, UnknownLineInfo())
    if r.s[r.pos] == ' ': inc(r.pos)

proc loadMethods(r: PRodReader) =
  if r.methodsIdx == 0 or r.dataIdx == 0:
    InternalError("loadMethods")
  r.pos = r.methodsIdx
  while r.s[r.pos] > '\x0A':
    var d = decodeVInt(r.s, r.pos)
    r.methods.add(rrGetSym(r, d, UnknownLineInfo()))
    if r.s[r.pos] == ' ': inc(r.pos)
  
proc getModuleIdx(filename: string): int = 
  for i in countup(0, high(gMods)): 
    if gMods[i].filename == filename: return i
  result = len(gMods)
  setlen(gMods, result + 1)

proc checkDep(filename: string): TReasonForRecompile =
  assert(not isNil(filename)) 
  var idx = getModuleIdx(filename)
  if gMods[idx].reason != rrEmpty: 
    # reason has already been computed for this module:
    return gMods[idx].reason
  var crc: TCrc32 = crcFromFile(filename)
  gMods[idx].reason = rrNone  # we need to set it here to avoid cycles
  gMods[idx].filename = filename
  gMods[idx].crc = crc
  result = rrNone
  var r: PRodReader = nil
  var rodfile = toGeneratedFile(filename, RodExt)
  r = newRodReader(rodfile, crc, idx)
  if r == nil: 
    result = (if ExistsFile(rodfile): rrRodInvalid else: rrRodDoesNotExist)
  else:
    result = r.reason
    if result == rrNone: 
      # check modules it depends on
      # NOTE: we need to process the entire module graph so that no ID will
      # be used twice! However, compilation speed does not suffer much from
      # this, since results are cached.
      var res = checkDep(options.libpath / addFileExt("system", nimExt))
      if res != rrNone: result = rrModDeps
      for i in countup(0, high(r.modDeps)): 
        res = checkDep(r.modDeps[i])
        if res != rrNone: 
          result = rrModDeps 
          # we cannot break here, because of side-effects of `checkDep`
  if result != rrNone and gVerbosity > 0:
    rawMessage(hintProcessing, reasonToFrmt[result] % filename)
  if result != rrNone or optForceFullMake in gGlobalOptions:
    # recompilation is necessary:
    if r != nil: memfiles.close(r.memFile)
    r = nil
  gMods[idx].rd = r
  gMods[idx].reason = result  # now we know better
  
proc handleSymbolFile(module: PSym, filename: string): PRodReader = 
  if optSymbolFiles notin gGlobalOptions: 
    module.id = getID()
    return nil
  idgen.loadMaxIds(options.gProjectPath / options.gProjectName)

  discard checkDep(filename)
  var idx = getModuleIdx(filename)
  if gMods[idx].reason == rrEmpty: InternalError("handleSymbolFile")
  result = gMods[idx].rd
  if result != nil: 
    module.id = result.moduleID
    IdTablePut(result.syms, module, module)
    processInterf(result, module)
    processCompilerProcs(result, module)
    loadConverters(result)
    loadMethods(result)
  else:
    module.id = getID()
  
proc GetCRC*(filename: string): TCrc32 = 
  for i in countup(0, high(gMods)): 
    if gMods[i].filename == filename: return gMods[i].crc
  
  result = crcFromFile(filename)
  #var idx = getModuleIdx(filename)
  #result = gMods[idx].crc

proc rawLoadStub(s: PSym) =
  if s.kind != skStub: InternalError("loadStub")
  var rd = gMods[s.position].rd
  var theId = s.id                # used for later check
  var d = IITableGet(rd.index.tab, s.id)
  if d == invalidKey: InternalError("loadStub: invalid key")
  var rs = decodeSymSafePos(rd, d, UnknownLineInfo())
  if rs != s: 
    InternalError(rs.info, "loadStub: wrong symbol")
  elif rs.id != theId: 
    InternalError(rs.info, "loadStub: wrong ID") 
  #MessageOut('loaded stub: ' + s.name.s);
  
proc LoadStub*(s: PSym) =
  ## loads the stub symbol `s`.
  
  # deactivate the GC here because we do a deep recursion and generate no
  # garbage when restoring parts of the object graph anyway.
  # Since we die with internal errors if this fails, so no try-finally is
  # necessary.
  GC_disable()
  rawLoadStub(s)
  GC_enable()
  
proc getBody*(s: PSym): PNode =
  ## retrieves the AST's body of `s`. If `s` has been loaded from a rod-file
  ## it may perform an expensive reload operation. Otherwise it's a simple
  ## accessor.
  assert s.kind in routineKinds
  result = s.ast.sons[bodyPos]
  if result == nil:
    assert s.offset != 0
    var r = gMods[s.position].rd
    var oldPos = r.pos
    r.pos = s.offset
    result = decodeNode(r, s.info)
    r.pos = oldPos
    s.ast.sons[bodyPos] = result
    s.offset = 0
  
InitIdTable(gTypeTable)
InitStrTable(rodCompilerProcs)