Experimental support for delayed instantiation of generics

This postpones the semantic pass over the generic's body until
the generic is instantiated. There are several pros and cons for
this method and the capabilities that it enables may still be possible
in the old framework if we teach it a few new trick. Such an attempt
will follow in the next commits.

pros:
1) It allows macros to be expanded during generic instantiation that
will provide the body of the generic. See ``tmacrogenerics``.
2) The instantiation code is dramatically simplified. Dealing with unknown
types in the generic's body pre-pass requires a lot of hacky code and error
silencing in semTypeNode. See ``tgenericshardcases``.

cons:
1) There is a performance penalty of roughly 5% when bootstrapping.
2) Certain errors that used to be detected in the previous pre-pass won't
be detected with the new scheme until instantiation.
This commit is contained in:
Zahary Karadjov 2013-08-22 19:22:28 +03:00
commit fee2a7ecfa
12 changed files with 161 additions and 38 deletions

View file

@ -626,6 +626,7 @@ type
case kind*: TSymKind case kind*: TSymKind
of skType: of skType:
typeInstCache*: seq[PType] typeInstCache*: seq[PType]
typScope*: PScope
of routineKinds: of routineKinds:
procInstCache*: seq[PInstantiation] procInstCache*: seq[PInstantiation]
scope*: PScope # the scope where the proc was defined scope*: PScope # the scope where the proc was defined
@ -799,9 +800,9 @@ const
# imported via 'importc: "fullname"' and no format string. # imported via 'importc: "fullname"' and no format string.
# creator procs: # creator procs:
proc NewSym*(symKind: TSymKind, Name: PIdent, owner: PSym, proc newSym*(symKind: TSymKind, Name: PIdent, owner: PSym,
info: TLineInfo): PSym info: TLineInfo): PSym
proc NewType*(kind: TTypeKind, owner: PSym): PType proc newType*(kind: TTypeKind, owner: PSym): PType
proc newNode*(kind: TNodeKind): PNode proc newNode*(kind: TNodeKind): PNode
proc newIntNode*(kind: TNodeKind, intVal: BiggestInt): PNode proc newIntNode*(kind: TNodeKind, intVal: BiggestInt): PNode
proc newIntTypeNode*(kind: TNodeKind, intVal: BiggestInt, typ: PType): PNode proc newIntTypeNode*(kind: TNodeKind, intVal: BiggestInt, typ: PType): PNode
@ -1111,7 +1112,7 @@ proc copySym(s: PSym, keepId: bool = false): PSym =
result.loc = s.loc result.loc = s.loc
result.annex = s.annex # BUGFIX result.annex = s.annex # BUGFIX
proc NewSym(symKind: TSymKind, Name: PIdent, owner: PSym, proc newSym(symKind: TSymKind, Name: PIdent, owner: PSym,
info: TLineInfo): PSym = info: TLineInfo): PSym =
# generates a symbol and initializes the hash field too # generates a symbol and initializes the hash field too
new(result) new(result)

View file

@ -904,18 +904,16 @@ proc evalParseStmt(c: PEvalContext, n: PNode): PNode =
code.info.line.int) code.info.line.int)
#result.typ = newType(tyStmt, c.module) #result.typ = newType(tyStmt, c.module)
proc evalTypeTrait*(n: PNode, context: PSym): PNode = proc evalTypeTrait*(trait, operand: PNode, context: PSym): PNode =
## XXX: This should be pretty much guaranteed to be true InternalAssert operand.kind == nkSym and
# by the type traits procs' signatures, but until the operand.sym.typ.kind == tyTypeDesc
# code is more mature it doesn't hurt to be extra safe
internalAssert n.sons.len >= 2 and n.sons[1].kind == nkSym
let typ = n.sons[1].sym.typ.skipTypes({tyTypeDesc}) let typ = operand.sym.typ.skipTypes({tyTypeDesc})
case n.sons[0].sym.name.s.normalize case trait.sym.name.s.normalize
of "name": of "name":
result = newStrNode(nkStrLit, typ.typeToString(preferExported)) result = newStrNode(nkStrLit, typ.typeToString(preferName))
result.typ = newType(tyString, context) result.typ = newType(tyString, context)
result.info = n.info result.info = trait.info
else: else:
internalAssert false internalAssert false
@ -1037,8 +1035,8 @@ proc evalMagicOrCall(c: PEvalContext, n: PNode): PNode =
of mParseStmtToAst: result = evalParseStmt(c, n) of mParseStmtToAst: result = evalParseStmt(c, n)
of mExpandToAst: result = evalExpandToAst(c, n) of mExpandToAst: result = evalExpandToAst(c, n)
of mTypeTrait: of mTypeTrait:
n.sons[1] = evalAux(c, n.sons[1], {}) let operand = evalAux(c, n.sons[1], {})
result = evalTypeTrait(n, c.module) result = evalTypeTrait(n[0], operand, c.module)
of mIs: of mIs:
n.sons[1] = evalAux(c, n.sons[1], {}) n.sons[1] = evalAux(c, n.sons[1], {})
result = evalIsOp(n) result = evalIsOp(n)

View file

@ -156,6 +156,8 @@ var
const oKeepVariableNames* = true const oKeepVariableNames* = true
const oUseLateInstantiation* = true
proc mainCommandArg*: string = proc mainCommandArg*: string =
## This is intended for commands like check or parse ## This is intended for commands like check or parse
## which will work on the main project file unless ## which will work on the main project file unless

View file

@ -558,9 +558,10 @@ proc getConstExpr(m: PSym, n: PNode): PNode =
case n.kind case n.kind
of nkSym: of nkSym:
var s = n.sym var s = n.sym
if s.kind == skEnumField: case s.kind
of skEnumField:
result = newIntNodeT(s.position, n) result = newIntNodeT(s.position, n)
elif s.kind == skConst: of skConst:
case s.magic case s.magic
of mIsMainModule: result = newIntNodeT(ord(sfMainModule in m.flags), n) of mIsMainModule: result = newIntNodeT(ord(sfMainModule in m.flags), n)
of mCompileDate: result = newStrNodeT(times.getDateStr(), n) of mCompileDate: result = newStrNodeT(times.getDateStr(), n)
@ -578,10 +579,17 @@ proc getConstExpr(m: PSym, n: PNode): PNode =
of mNegInf: result = newFloatNodeT(NegInf, n) of mNegInf: result = newFloatNodeT(NegInf, n)
else: else:
if sfFakeConst notin s.flags: result = copyTree(s.ast) if sfFakeConst notin s.flags: result = copyTree(s.ast)
elif s.kind in {skProc, skMethod}: # BUGFIX of {skProc, skMethod}:
result = n result = n
elif s.kind in {skType, skGenericParam}: of skType:
result = newSymNodeTypeDesc(s, n.info) result = newSymNodeTypeDesc(s, n.info)
of skGenericParam:
if s.typ.kind == tyExpr:
result = s.typ.n
result.typ = s.typ.sons[0]
else:
result = newSymNodeTypeDesc(s, n.info)
else: nil
of nkCharLit..nkNilLit: of nkCharLit..nkNilLit:
result = copyNode(n) result = copyNode(n)
of nkIfExpr: of nkIfExpr:

View file

@ -130,13 +130,50 @@ proc sideEffectsCheck(c: PContext, s: PSym) =
s.ast.sons[genericParamsPos].kind == nkEmpty: s.ast.sons[genericParamsPos].kind == nkEmpty:
c.threadEntries.add(s) c.threadEntries.add(s)
proc lateInstantiateGeneric(c: PContext, invocation: PType, info: TLineInfo): PType =
InternalAssert invocation.kind == tyGenericInvokation
let cacheHit = searchInstTypes(invocation)
if cacheHit != nil:
result = cacheHit
else:
let s = invocation.sons[0].sym
let oldScope = c.currentScope
c.currentScope = s.typScope
openScope(c)
pushInfoContext(info)
for i in 0 .. <s.typ.n.sons.len:
let genericParam = s.typ.n[i].sym
let symKind = if genericParam.typ.kind == tyExpr: skConst
else: skType
var boundSym = newSym(symKind, s.typ.n[i].sym.name, s, info)
boundSym.typ = invocation.sons[i+1].skipTypes({tyExpr})
boundSym.ast = invocation.sons[i+1].n
addDecl(c, boundSym)
# XXX: copyTree would have been unnecessary here if semTypeNode
# didn't modify its input parameters. Currently, it does modify
# at least the record lists of the passed object and tuple types
var instantiated = semTypeNode(c, copyTree(s.ast[2]), nil)
popInfoContext()
closeScope(c)
c.currentScope = oldScope
if instantiated != nil:
result = invocation
result.kind = tyGenericInst
result.sons.add instantiated
cacheTypeInst result
proc instGenericContainer(c: PContext, info: TLineInfo, header: PType): PType = proc instGenericContainer(c: PContext, info: TLineInfo, header: PType): PType =
var cl: TReplTypeVars when oUseLateInstantiation:
InitIdTable(cl.symMap) lateInstantiateGeneric(c, header, info)
InitIdTable(cl.typeMap) else:
cl.info = info var cl: TReplTypeVars
cl.c = c InitIdTable(cl.symMap)
result = ReplaceTypeVarsT(cl, header) InitIdTable(cl.typeMap)
cl.info = info
cl.c = c
result = ReplaceTypeVarsT(cl, header)
proc instGenericContainer(c: PContext, n: PNode, header: PType): PType = proc instGenericContainer(c: PContext, n: PNode, header: PType): PType =
result = instGenericContainer(c, n.info, header) result = instGenericContainer(c, n.info, header)

View file

@ -40,7 +40,7 @@ proc semTypeTraits(c: PContext, n: PNode): PNode =
(typArg.kind == skParam and typArg.typ.sonsLen > 0): (typArg.kind == skParam and typArg.typ.sonsLen > 0):
# This is either a type known to sem or a typedesc # This is either a type known to sem or a typedesc
# param to a regular proc (again, known at instantiation) # param to a regular proc (again, known at instantiation)
result = evalTypeTrait(n, GetCurrOwner()) result = evalTypeTrait(n[0], n[1], GetCurrOwner())
else: else:
# a typedesc variable, pass unmodified to evals # a typedesc variable, pass unmodified to evals
result = n result = n

View file

@ -729,12 +729,16 @@ proc typeSectionRightSidePass(c: PContext, n: PNode) =
# like: mydata.seq # like: mydata.seq
rawAddSon(s.typ, newTypeS(tyEmpty, c)) rawAddSon(s.typ, newTypeS(tyEmpty, c))
s.ast = a s.ast = a
inc c.InGenericContext when oUseLateInstantiation:
var body = semTypeNode(c, a.sons[2], nil) var body: PType = nil
dec c.InGenericContext s.typScope = c.currentScope.parent
if body != nil: else:
body.sym = s inc c.InGenericContext
body.size = -1 # could not be computed properly var body = semTypeNode(c, a.sons[2], nil)
dec c.InGenericContext
if body != nil:
body.sym = s
body.size = -1 # could not be computed properly
s.typ.sons[sonsLen(s.typ) - 1] = body s.typ.sons[sonsLen(s.typ) - 1] = body
popOwner() popOwner()
closeScope(c) closeScope(c)

View file

@ -846,7 +846,10 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType =
LocalError(n.info, errCannotInstantiateX, s.name.s) LocalError(n.info, errCannotInstantiateX, s.name.s)
result = newOrPrevType(tyError, prev, c) result = newOrPrevType(tyError, prev, c)
else: else:
result = instGenericContainer(c, n, result) when oUseLateInstantiation:
result = lateInstantiateGeneric(c, result, n.info)
else:
result = instGenericContainer(c, n, result)
proc semTypeExpr(c: PContext, n: PNode): PType = proc semTypeExpr(c: PContext, n: PNode): PType =
var n = semExprWithType(c, n, {efDetermineType}) var n = semExprWithType(c, n, {efDetermineType})

View file

@ -31,7 +31,7 @@ proc checkConstructedType*(info: TLineInfo, typ: PType) =
if t.sons[0].kind != tyObject or tfFinal in t.sons[0].flags: if t.sons[0].kind != tyObject or tfFinal in t.sons[0].flags:
localError(info, errInheritanceOnlyWithNonFinalObjects) localError(info, errInheritanceOnlyWithNonFinalObjects)
proc searchInstTypes(key: PType): PType = proc searchInstTypes*(key: PType): PType =
let genericTyp = key.sons[0] let genericTyp = key.sons[0]
InternalAssert genericTyp.kind == tyGenericBody and InternalAssert genericTyp.kind == tyGenericBody and
key.sons[0] == genericTyp and key.sons[0] == genericTyp and
@ -55,7 +55,7 @@ proc searchInstTypes(key: PType): PType =
return inst return inst
proc cacheTypeInst(inst: PType) = proc cacheTypeInst*(inst: PType) =
# XXX: add to module's generics # XXX: add to module's generics
# update the refcount # update the refcount
let genericTyp = inst.sons[0] let genericTyp = inst.sons[0]

View file

@ -654,7 +654,7 @@ proc typeRel(c: var TCandidate, f, a: PType): TTypeRelation =
result = typeRel(c, x, a) # check if it fits result = typeRel(c, x, a) # check if it fits
of tyTypeDesc: of tyTypeDesc:
var prev = PType(idTableGet(c.bindings, f)) var prev = PType(idTableGet(c.bindings, f))
if prev == nil: if prev == nil or true:
if a.kind == tyTypeDesc: if a.kind == tyTypeDesc:
if f.sonsLen == 0: if f.sonsLen == 0:
result = isGeneric result = isGeneric
@ -768,6 +768,7 @@ proc ParamTypesMatchAux(c: PContext, m: var TCandidate, f, a: PType,
if evaluated != nil: if evaluated != nil:
r = isGeneric r = isGeneric
arg.typ = newTypeS(tyExpr, c) arg.typ = newTypeS(tyExpr, c)
arg.typ.sons = @[evaluated.typ]
arg.typ.n = evaluated arg.typ.n = evaluated
if r == isGeneric: if r == isGeneric:

View file

@ -0,0 +1,30 @@
discard """
file: "tgenericshardcases.nim"
output: "int\nfloat\nint\nstring"
"""
import typetraits
proc typeNameLen(x: typedesc): int {.compileTime.} =
result = x.name.len
macro selectType(a, b: typedesc): typedesc =
result = a
type
Foo[T] = object
data1: array[high(T), int]
data2: array[1..typeNameLen(T), selectType(float, string)]
MyEnum = enum A, B, C,D
var f1: Foo[MyEnum]
var f2: Foo[int8]
static:
assert high(f1.data1) == D
assert high(f1.data2) == 6 # length of MyEnum
assert high(f2.data1) == 127
assert high(f2.data2) == 4 # length of int8

View file

@ -0,0 +1,39 @@
discard """
file: "tmacrogenerics.nim"
msg: '''
instantiation 1 with int and float
instantiation 2 with float and string
instantiation 3 with string and string
counter: 3
'''
output: "int\nfloat\nint\nstring"
"""
import typetraits, macros
var counter {.compileTime.} = 0
macro makeBar(A, B: typedesc): typedesc =
inc counter
echo "instantiation ", counter, " with ", A.name, " and ", B.name
result = A
type
Bar[T, U] = makeBar(T, U)
var bb1: Bar[int, float]
var bb2: Bar[float, string]
var bb3: Bar[int, float]
var bb4: Bar[string, string]
proc match(a: int) = echo "int"
proc match(a: string) = echo "string"
proc match(a: float) = echo "float"
match(bb1)
match(bb2)
match(bb3)
match(bb4)
static:
echo "counter: ", counter