compilation cache: mostly working; generics not yet

This commit is contained in:
Araq 2011-10-25 15:26:36 +02:00
commit 9fb36bd20c
17 changed files with 160 additions and 2949 deletions

View file

@ -568,7 +568,7 @@ const
ConstantDataTypes*: TTypeKinds = {tyArrayConstr, tyArray, tySet, ConstantDataTypes*: TTypeKinds = {tyArrayConstr, tyArray, tySet,
tyTuple, tySequence} tyTuple, tySequence}
ExportableSymKinds* = {skVar, skConst, skProc, skMethod, skType, skIterator, ExportableSymKinds* = {skVar, skConst, skProc, skMethod, skType, skIterator,
skMacro, skTemplate, skConverter, skStub} skMacro, skTemplate, skConverter, skEnumField, skStub}
PersistentNodeFlags*: TNodeFlags = {nfBase2, nfBase8, nfBase16, nfAllConst} PersistentNodeFlags*: TNodeFlags = {nfBase2, nfBase8, nfBase16, nfAllConst}
namePos* = 0 namePos* = 0
genericParamsPos* = 1 genericParamsPos* = 1

View file

@ -142,7 +142,7 @@ type
PRodReader* = ref TRodReader PRodReader* = ref TRodReader
const const
FileVersion* = "1024" # modify this if the rod-format changes! FileVersion* = "1026" # modify this if the rod-format changes!
var rodCompilerprocs*: TStrTable var rodCompilerprocs*: TStrTable
@ -657,12 +657,21 @@ proc decodeSymSafePos(rd: PRodReader, offset: int, info: TLineInfo): PSym =
result = decodeSym(rd, info) result = decodeSym(rd, info)
rd.pos = oldPos 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 = proc rrGetSym(r: PRodReader, id: int, info: TLineInfo): PSym =
result = PSym(IdTableGet(r.syms, id)) result = PSym(IdTableGet(r.syms, id))
if result == nil: if result == nil:
# load the symbol: # load the symbol:
var d = IITableGet(r.index.tab, id) var d = IITableGet(r.index.tab, id)
if d == invalidKey: if d == invalidKey:
# import from other module:
var moduleID = IiTableGet(r.imports.tab, id) var moduleID = IiTableGet(r.imports.tab, id)
if moduleID < 0: if moduleID < 0:
var x = "" var x = ""
@ -680,6 +689,7 @@ proc rrGetSym(r: PRodReader, id: int, info: TLineInfo): PSym =
else: else:
var x = "" var x = ""
encodeVInt(id, x) encodeVInt(id, x)
when false: findSomeWhere(id)
InternalError(info, "rrGetSym: no reader found: +" & x) InternalError(info, "rrGetSym: no reader found: +" & x)
else: else:
#if IiTableGet(rd.index.tab, id) <> invalidKey then #if IiTableGet(rd.index.tab, id) <> invalidKey then

View file

@ -322,7 +322,10 @@ proc addToIndex(w: var TIndex, key, val: int) =
w.lastIdxVal = val w.lastIdxVal = val
IiTablePut(w.tab, key, val) IiTablePut(w.tab, key, val)
#var debugWritten = initIntSet() const debugWrittenIds = false
when debugWrittenIds:
var debugWritten = initIntSet()
proc symStack(w: PRodWriter) = proc symStack(w: PRodWriter) =
var i = 0 var i = 0
@ -335,10 +338,12 @@ proc symStack(w: PRodWriter) =
# put definition in here # put definition in here
var L = w.data.len var L = w.data.len
addToIndex(w.index, s.id, L) addToIndex(w.index, s.id, L)
#intSetIncl(debugWritten, s.id) when debugWrittenIds: incl(debugWritten, s.id)
encodeSym(w, s, w.data) encodeSym(w, s, w.data)
add(w.data, rodNL) add(w.data, rodNL)
if sfExported in s.flags and s.kind in ExportableSymKinds: # put into interface section if appropriate:
if {sfExported, sfFromGeneric} * s.flags == {sfExported} and
s.kind in ExportableSymKinds:
encodeStr(s.name.s, w.interf) encodeStr(s.name.s, w.interf)
add(w.interf, ' ') add(w.interf, ' ')
encodeVInt(s.id, w.interf) encodeVInt(s.id, w.interf)
@ -355,12 +360,14 @@ proc symStack(w: PRodWriter) =
if w.methods.len != 0: add(w.methods, ' ') if w.methods.len != 0: add(w.methods, ' ')
encodeVInt(s.id, w.methods) encodeVInt(s.id, w.methods)
elif IiTableGet(w.imports.tab, s.id) == invalidKey: elif IiTableGet(w.imports.tab, s.id) == invalidKey:
addToIndex(w.imports, s.id, m.id) #if not Contains(debugWritten, s.id): addToIndex(w.imports, s.id, m.id)
# MessageOut(w.filename); when debugWrittenIds:
# debug(s.owner); if not Contains(debugWritten, s.id):
# debug(s); echo(w.filename)
# InternalError('BUG!!!!'); debug(s)
#end debug(s.owner)
debug(m)
InternalError("BUG!!!!")
inc(i) inc(i)
setlen(w.sstack, 0) setlen(w.sstack, 0)

View file

@ -126,13 +126,13 @@ proc semConstBoolExpr(c: PContext, n: PNode): PNode =
include semtypes, semexprs, semgnrc, semstmts include semtypes, semexprs, semgnrc, semstmts
proc addCodeForGenerics(c: PContext, n: PNode) = proc addCodeForGenerics(c: PContext, n: PNode) =
for i in countup(lastGenericIdx, Len(generics) - 1): for i in countup(c.generics.lastGenericIdx, Len(c.generics.generics) - 1):
var prc = generics[i].instSym var prc = c.generics.generics[i].instSym
if prc.kind in {skProc, skMethod, skConverter} and prc.magic == mNone: if prc.kind in {skProc, skMethod, skConverter} and prc.magic == mNone:
if prc.ast == nil or prc.ast.sons[codePos] == nil: if prc.ast == nil or prc.ast.sons[codePos] == nil:
InternalError(prc.info, "no code for " & prc.name.s) InternalError(prc.info, "no code for " & prc.name.s)
addSon(n, prc.ast) addSon(n, prc.ast)
lastGenericIdx = Len(generics) c.generics.lastGenericIdx = Len(c.generics.generics)
proc semExprNoFlags(c: PContext, n: PNode): PNode {.procvar.} = proc semExprNoFlags(c: PContext, n: PNode): PNode {.procvar.} =
result = semExpr(c, n, {}) result = semExpr(c, n, {})
@ -162,7 +162,7 @@ proc myOpenCached(module: PSym, filename: string,
proc SemStmtAndGenerateGenerics(c: PContext, n: PNode): PNode = proc SemStmtAndGenerateGenerics(c: PContext, n: PNode): PNode =
result = semStmt(c, n) result = semStmt(c, n)
# BUGFIX: process newly generated generics here, not at the end! # BUGFIX: process newly generated generics here, not at the end!
if lastGenericIdx < Len(generics): if c.generics.lastGenericIdx < Len(c.generics.generics):
var a = newNodeI(nkStmtList, n.info) var a = newNodeI(nkStmtList, n.info)
addCodeForGenerics(c, a) addCodeForGenerics(c, a)
if sonsLen(a) > 0: if sonsLen(a) > 0:

View file

@ -7,7 +7,7 @@
# distribution, for details about the copyright. # distribution, for details about the copyright.
# #
# This module contains the data structures for the semantic checking phase. ## This module contains the data structures for the semantic checking phase.
import import
strutils, lists, intsets, options, lexer, ast, astalgo, trees, treetab, strutils, lists, intsets, options, lexer, ast, astalgo, trees, treetab,
@ -37,10 +37,24 @@ type
genericSym*, instSym*: PSym genericSym*, instSym*: PSym
concreteTypes*: seq[PType] concreteTypes*: seq[PType]
# If we generate an instance of a generic, we'd like to re-use that
# instance if possible across module boundaries. However, this is not
# possible if the compilation cache is enabled. So we give up then and use
# the caching of generics only per module, not per project.
TGenericsCache* {.final.} = object
InstTypes*: TIdTable # map PType to PType
generics*: seq[TInstantiatedSymbol] # a list of the things to compile
lastGenericIdx*: int # used for the generics stack
PGenericsCache* = ref TGenericsCache
PContext* = ref TContext PContext* = ref TContext
TContext* = object of TPassContext # a context represents a module TContext* = object of TPassContext # a context represents a module
module*: PSym # the module sym belonging to the context module*: PSym # the module sym belonging to the context
p*: PProcCon # procedure context p*: PProcCon # procedure context
generics*: PGenericsCache # may point to a global or module-local structure
friendModule*: PSym # current friend module; may access private data;
# this is used so that generic instantiations can
# access private object fields
InstCounter*: int # to prevent endless instantiations InstCounter*: int # to prevent endless instantiations
threadEntries*: TSymSeq # list of thread entries to check threadEntries*: TSymSeq # list of thread entries to check
@ -56,10 +70,13 @@ type
filename*: string # the module's filename filename*: string # the module's filename
userPragmas*: TStrTable userPragmas*: TStrTable
var
gGenericsCache: PGenericsCache # save for modularity
var gInstTypes*: TIdTable # map PType to PType proc newGenericsCache: PGenericsCache =
var generics*: seq[TInstantiatedSymbol] = @[] # a list of the things to compile new(result)
var lastGenericIdx*: int # used for the generics stack initIdTable(result.InstTypes)
result.generics = @[]
proc newContext*(module: PSym, nimfile: string): PContext proc newContext*(module: PSym, nimfile: string): PContext
@ -126,11 +143,21 @@ proc newContext(module: PSym, nimfile: string): PContext =
initLinkedList(result.libs) initLinkedList(result.libs)
append(result.optionStack, newOptionEntry()) append(result.optionStack, newOptionEntry())
result.module = module result.module = module
result.friendModule = module
result.threadEntries = @[] result.threadEntries = @[]
result.converters = @[] result.converters = @[]
result.filename = nimfile result.filename = nimfile
result.includedFiles = initIntSet() result.includedFiles = initIntSet()
initStrTable(result.userPragmas) initStrTable(result.userPragmas)
if optSymbolFiles notin gGlobalOptions:
# re-usage of generic instantiations across module boundaries is
# very nice for code size:
if gGenericsCache == nil: gGenericsCache = newGenericsCache()
result.generics = gGenericsCache
else:
# we have to give up and use a per-module cache for generic instantiations:
result.generics = newGenericsCache()
assert gGenericsCache == nil
proc addConverter(c: PContext, conv: PSym) = proc addConverter(c: PContext, conv: PSym) =
var L = len(c.converters) var L = len(c.converters)
@ -190,4 +217,3 @@ proc checkSonsLen*(n: PNode, length: int) =
proc checkMinSonsLen*(n: PNode, length: int) = proc checkMinSonsLen*(n: PNode, length: int) =
if sonsLen(n) < length: illFormedAst(n) if sonsLen(n) < length: illFormedAst(n)
initIdTable(gInstTypes)

View file

@ -53,8 +53,9 @@ proc inlineConst(n: PNode, s: PSym): PNode {.inline.} =
proc semSym(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode = proc semSym(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode =
case s.kind case s.kind
of skProc, skMethod, skIterator, skConverter: of skProc, skMethod, skIterator, skConverter:
var smoduleId = getModule(s).id
if sfProcVar notin s.flags and s.typ.callConv == ccDefault and if sfProcVar notin s.flags and s.typ.callConv == ccDefault and
getModule(s).id != c.module.id: smoduleId != c.module.id and smoduleId != c.friendModule.id:
LocalError(n.info, errXCannotBePassedToProcVar, s.name.s) LocalError(n.info, errXCannotBePassedToProcVar, s.name.s)
result = symChoice(c, n, s) result = symChoice(c, n, s)
of skConst: of skConst:
@ -673,8 +674,10 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode =
if ty.sons[0] == nil: break if ty.sons[0] == nil: break
ty = skipTypes(ty.sons[0], {tyGenericInst}) ty = skipTypes(ty.sons[0], {tyGenericInst})
if f != nil: if f != nil:
if sfExported in f.flags or getModule(f).id == c.module.id: var fmoduleId = getModule(f).id
# is the access to a public field or in the same module? if sfExported in f.flags or fmoduleId == c.module.id or
fmoduleId == c.friendModule.id:
# is the access to a public field or in the same module or in a friend?
n.sons[0] = makeDeref(n.sons[0]) n.sons[0] = makeDeref(n.sons[0])
n.sons[1] = newSymNode(f) # we now have the correct field n.sons[1] = newSymNode(f) # we now have the correct field
n.typ = f.typ n.typ = f.typ

View file

@ -22,7 +22,7 @@ proc instantiateGenericParamList(c: PContext, n: PNode, pt: TIdTable,
if q.typ.kind notin {tyTypeDesc, tyGenericParam}: continue if q.typ.kind notin {tyTypeDesc, tyGenericParam}: continue
var s = newSym(skType, q.name, getCurrOwner()) var s = newSym(skType, q.name, getCurrOwner())
s.info = q.info s.info = q.info
incl(s.flags, sfUsed) s.flags = s.flags + {sfUsed, sfFromGeneric}
var t = PType(IdTableGet(pt, q.typ)) var t = PType(IdTableGet(pt, q.typ))
if t == nil: if t == nil:
LocalError(a.info, errCannotInstantiateX, s.name.s) LocalError(a.info, errCannotInstantiateX, s.name.s)
@ -45,9 +45,9 @@ proc sameInstantiation(a, b: TInstantiatedSymbol): bool =
result = true result = true
proc GenericCacheGet(c: PContext, entry: var TInstantiatedSymbol): PSym = proc GenericCacheGet(c: PContext, entry: var TInstantiatedSymbol): PSym =
for i in countup(0, Len(generics) - 1): for i in countup(0, Len(c.generics.generics) - 1):
if sameInstantiation(entry, generics[i]): if sameInstantiation(entry, c.generics.generics[i]):
result = generics[i].instSym result = c.generics.generics[i].instSym
# checking for the concrete parameter list is wrong and unnecessary! # checking for the concrete parameter list is wrong and unnecessary!
#if equalParams(b.typ.n, instSym.typ.n) == paramsEqual: #if equalParams(b.typ.n, instSym.typ.n) == paramsEqual:
#echo "found in cache: ", getProcHeader(result) #echo "found in cache: ", getProcHeader(result)
@ -86,9 +86,9 @@ proc instantiateBody(c: PContext, n: PNode, result: PSym) =
popProcCon(c) popProcCon(c)
proc fixupInstantiatedSymbols(c: PContext, s: PSym) = proc fixupInstantiatedSymbols(c: PContext, s: PSym) =
for i in countup(0, Len(generics) - 1): for i in countup(0, Len(c.generics.generics) - 1):
if generics[i].genericSym.id == s.id: if c.generics.generics[i].genericSym.id == s.id:
var oldPrc = generics[i].instSym var oldPrc = c.generics.generics[i].instSym
pushInfoContext(oldPrc.info) pushInfoContext(oldPrc.info)
openScope(c.tab) openScope(c.tab)
var n = oldPrc.ast var n = oldPrc.ast
@ -112,10 +112,9 @@ proc generateInstance(c: PContext, fn: PSym, pt: TIdTable,
if c.InstCounter > 1000: InternalError(fn.ast.info, "nesting too deep") if c.InstCounter > 1000: InternalError(fn.ast.info, "nesting too deep")
inc(c.InstCounter) inc(c.InstCounter)
# NOTE: for access of private fields within generics from a different module # NOTE: for access of private fields within generics from a different module
# and other identifiers we fake the current module temporarily! # we set the friend module:
# XXX bad hack! var oldFriend = c.friendModule
var oldMod = c.module c.friendModule = getModule(fn)
c.module = getModule(fn)
result = copySym(fn, false) result = copySym(fn, false)
incl(result.flags, sfFromGeneric) incl(result.flags, sfFromGeneric)
result.owner = getCurrOwner().owner result.owner = getCurrOwner().owner
@ -144,7 +143,7 @@ proc generateInstance(c: PContext, fn: PSym, pt: TIdTable,
ParamsTypeCheck(c, result.typ) ParamsTypeCheck(c, result.typ)
var oldPrc = GenericCacheGet(c, entry) var oldPrc = GenericCacheGet(c, entry)
if oldPrc == nil: if oldPrc == nil:
generics.add(entry) c.generics.generics.add(entry)
if n.sons[pragmasPos].kind != nkEmpty: if n.sons[pragmasPos].kind != nkEmpty:
pragma(c, result, n.sons[pragmasPos], allRoutinePragmas) pragma(c, result, n.sons[pragmasPos], allRoutinePragmas)
instantiateBody(c, n, result) instantiateBody(c, n, result)
@ -154,7 +153,7 @@ proc generateInstance(c: PContext, fn: PSym, pt: TIdTable,
popInfoContext() popInfoContext()
closeScope(c.tab) # close scope for parameters closeScope(c.tab) # close scope for parameters
popOwner() popOwner()
c.module = oldMod c.friendModule = oldFriend
dec(c.InstCounter) dec(c.InstCounter)
proc instGenericContainer(c: PContext, n: PNode, header: PType): PType = proc instGenericContainer(c: PContext, n: PNode, header: PType): PType =

View file

@ -527,7 +527,6 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode,
typ = paramType(c, a.sons[length-2], genericParams, cl) typ = paramType(c, a.sons[length-2], genericParams, cl)
#if matchType(typ, [(tyVar, 0)], tyGenericInvokation): #if matchType(typ, [(tyVar, 0)], tyGenericInvokation):
# debug a.sons[length-2][0][1] # debug a.sons[length-2][0][1]
else: else:
typ = nil typ = nil
if a.sons[length-1].kind != nkEmpty: if a.sons[length-1].kind != nkEmpty:

View file

@ -105,7 +105,7 @@ proc handleGenericInvokation(cl: var TReplTypeVars, t: PType): PType =
var header: PType = nil var header: PType = nil
when true: when true:
# search for some instantiation here: # search for some instantiation here:
result = searchInstTypes(gInstTypes, t) result = searchInstTypes(cl.c.generics.InstTypes, t)
if result != nil: return if result != nil: return
for i in countup(1, sonsLen(t) - 1): for i in countup(1, sonsLen(t) - 1):
var x = t.sons[i] var x = t.sons[i]
@ -116,7 +116,7 @@ proc handleGenericInvokation(cl: var TReplTypeVars, t: PType): PType =
#idTablePut(cl.typeMap, body.sons[i-1], x) #idTablePut(cl.typeMap, body.sons[i-1], x)
if header != nil: if header != nil:
# search again after first pass: # search again after first pass:
result = searchInstTypes(gInstTypes, header) result = searchInstTypes(cl.c.generics.InstTypes, header)
if result != nil: return if result != nil: return
else: else:
header = copyType(t, t.owner, false) header = copyType(t, t.owner, false)
@ -124,7 +124,7 @@ proc handleGenericInvokation(cl: var TReplTypeVars, t: PType): PType =
# we need to add the candidate here, before it's fully instantiated for # we need to add the candidate here, before it's fully instantiated for
# recursive instantions: # recursive instantions:
result = newType(tyGenericInst, t.sons[0].owner) result = newType(tyGenericInst, t.sons[0].owner)
idTablePut(gInstTypes, header, result) idTablePut(cl.c.generics.InstTypes, header, result)
for i in countup(1, sonsLen(t) - 1): for i in countup(1, sonsLen(t) - 1):
var x = replaceTypeVarsT(cl, t.sons[i]) var x = replaceTypeVarsT(cl, t.sons[i])
@ -154,14 +154,14 @@ proc handleGenericInvokation(cl: var TReplTypeVars, t: PType): PType =
assert x.kind != tyGenericInvokation assert x.kind != tyGenericInvokation
idTablePut(cl.typeMap, body.sons[i-1], x) idTablePut(cl.typeMap, body.sons[i-1], x)
if header == nil: header = t if header == nil: header = t
result = searchInstTypes(gInstTypes, header) result = searchInstTypes(cl.c.generics.InstTypes, header)
if result != nil: return if result != nil: return
result = newType(tyGenericInst, t.sons[0].owner) result = newType(tyGenericInst, t.sons[0].owner)
for i in countup(0, sonsLen(t) - 1): for i in countup(0, sonsLen(t) - 1):
# if one of the params is not concrete, we cannot do anything # if one of the params is not concrete, we cannot do anything
# but we already raised an error! # but we already raised an error!
addSon(result, header.sons[i]) addSon(result, header.sons[i])
idTablePut(gInstTypes, header, result) idTablePut(cl.c.generics.InstTypes, header, result)
var newbody = ReplaceTypeVarsT(cl, lastSon(body)) var newbody = ReplaceTypeVarsT(cl, lastSon(body))
newbody.flags = newbody.flags + t.flags + body.flags newbody.flags = newbody.flags + t.flags + body.flags
newbody.n = ReplaceTypeVarsN(cl, lastSon(body).n) newbody.n = ReplaceTypeVarsN(cl, lastSon(body).n)

View file

@ -7,8 +7,8 @@
# distribution, for details about the copyright. # distribution, for details about the copyright.
# #
# This module implements the signature matching for resolving ## This module implements the signature matching for resolving
# the call to overloaded procs, generic procs and operators. ## the call to overloaded procs, generic procs and operators.
import import
intsets, ast, astalgo, semdata, types, msgs, renderer, lookups, semtypinst, intsets, ast, astalgo, semdata, types, msgs, renderer, lookups, semtypinst,

View file

@ -160,6 +160,9 @@ program*.
Frontend issues Frontend issues
--------------- ---------------
Methods and type converters
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Nimrod contains language features that are *global*. The best example for that Nimrod contains language features that are *global*. The best example for that
are multi methods: Introducing a new method with the same name and some are multi methods: Introducing a new method with the same name and some
compatible object parameter means that the method's dispatcher needs to take compatible object parameter means that the method's dispatcher needs to take
@ -189,6 +192,17 @@ Both the multi method and the type converter problems are solved by storing
them in special sections in the ROD file that are loaded *unconditionally* them in special sections in the ROD file that are loaded *unconditionally*
when the ROD file is read. when the ROD file is read.
Generics
~~~~~~~~
If we generate an instance of a generic, we'd like to re-use that
instance if possible across module boundaries. However, this is not
possible if the compilation cache is enabled. So we give up then and use
the caching of generics only per module, not per project. This means that
``--symbolFiles:on`` hurts a bit for efficiency. A better solution would
be to persist the instantiations in a global cache per project. This might be
implemented in later versions.
Backend issues Backend issues
-------------- --------------

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,11 @@
type
TMyObj = object
x: int
proc gen*[T](): T =
var d: TMyObj
# access private field here
d.x = 3
result = d.x

View file

@ -0,0 +1,11 @@
discard """
output: "3"
"""
# Tests that a generic instantiation from a different module may access
# private object fields:
import mfriends
echo gen[int]()

View file

@ -16,11 +16,12 @@ Version 0.8.14
incremental compilation incremental compilation
----------------------- -----------------------
- object types need to be compared by container ID!
- adapt thread var implementation to care about the new merge operation - adapt thread var implementation to care about the new merge operation
- write test cases: needs test script support - write test cases: needs test script support
- test thread var
- test DLL interfacing!
- stress test with whole compiler - stress test with whole compiler
- test DLL interfacing!
- test thread var
- automate tests: - automate tests:
- test basic recompilation scheme - test basic recompilation scheme