rework the vtable implementation embedding the vtable array directly with new strictions on methods (#22991)

**TODO**
- [x] fixes changelog
With the new option `nimPreviewVtables`, `methods` are confined in the
same module where the type of the first parameter is defined

- [x] make it opt in after CI checks its feasibility

## In the following-up PRs

- [ ] in the following PRs, refactor code into a more efficient one

- [ ] cpp needs special treatments since it cannot embed array in light
of the preceding limits: ref
https://github.com/nim-lang/Nim/pull/20977#discussion_r1035528927; we
can support cpp backends with vtable implementations later on the
comprise that uses indirect vtable access

---------

Co-authored-by: Andreas Rumpf <rumpf_a@web.de>
This commit is contained in:
ringabout
2023-11-28 22:11:43 +08:00
committed by GitHub
parent 8cad6ac048
commit 30cf33f04d
28 changed files with 555 additions and 197 deletions

View File

@@ -5,6 +5,7 @@
- `-d:nimStrictDelete` becomes the default. An index error is produced when the index passed to `system.delete` was out of bounds. Use `-d:nimAuditDelete` to mimic the old behavior for backwards compatibility.
- The default user-agent in `std/httpclient` has been changed to `Nim-httpclient/<version>` instead of `Nim httpclient/<version>` which was incorrect according to the HTTP spec.
- Methods now support implementations based on vtable by using `-d:nimPreviewVtables`. methods are confined in the same module where the type has been defined.
- With `-d:nimPreviewNonVarDestructor`, non-var destructors become the default.
## Standard library additions and changes

View File

@@ -13,6 +13,7 @@
import sighashes, modulegraphs, std/strscans
import ../dist/checksums/src/checksums/md5
import std/sequtils
type
TypeDescKind = enum
@@ -1718,6 +1719,13 @@ proc genTypeInfoV2OldImpl(m: BModule; t, origType: PType, name: Rope; info: TLin
if t.kind == tyObject and t.len > 0 and t[0] != nil and optEnableDeepCopy in m.config.globalOptions:
discard genTypeInfoV1(m, t, info)
proc genVTable(seqs: seq[PSym]): string =
result = "{"
for i in 0..<seqs.len:
if i > 0: result.add ", "
result.add "(void *) " & seqs[i].loc.r
result.add "}"
proc genTypeInfoV2Impl(m: BModule; t, origType: PType, name: Rope; info: TLineInfo) =
cgsym(m, "TNimTypeV2")
m.s[cfsStrData].addf("N_LIB_PRIVATE TNimTypeV2 $1;$n", [name])
@@ -1754,8 +1762,16 @@ proc genTypeInfoV2Impl(m: BModule; t, origType: PType, name: Rope; info: TLineIn
add(typeEntry, ", .traceImpl = (void*)")
genHook(m, t, info, attachedTrace, typeEntry)
addf(typeEntry, ", .flags = $1};$n", [rope(flags)])
m.s[cfsVars].add typeEntry
let dispatchMethods = toSeq(getMethodsPerType(m.g.graph, t))
if dispatchMethods.len > 0:
addf(typeEntry, ", .flags = $1", [rope(flags)])
for i in dispatchMethods:
genProcPrototype(m, i)
addf(typeEntry, ", .vTable = $1};$n", [genVTable(dispatchMethods)])
m.s[cfsVars].add typeEntry
else:
addf(typeEntry, ", .flags = $1};$n", [rope(flags)])
m.s[cfsVars].add typeEntry
if t.kind == tyObject and t.len > 0 and t[0] != nil and optEnableDeepCopy in m.config.globalOptions:
discard genTypeInfoV1(m, t, info)

View File

@@ -366,6 +366,8 @@ proc dataField(p: BProc): Rope =
else:
result = rope"->data"
proc genProcPrototype(m: BModule, sym: PSym)
include ccgliterals
include ccgtypes
@@ -734,7 +736,7 @@ proc genVarPrototype(m: BModule, n: PNode)
proc requestConstImpl(p: BProc, sym: PSym)
proc genStmts(p: BProc, t: PNode)
proc expr(p: BProc, n: PNode, d: var TLoc)
proc genProcPrototype(m: BModule, sym: PSym)
proc putLocIntoDest(p: BProc, d: var TLoc, s: TLoc)
proc intLiteral(i: BiggestInt; result: var Rope)
proc genLiteral(p: BProc, n: PNode; result: var Rope)
@@ -2181,9 +2183,8 @@ proc updateCachedModule(m: BModule) =
cf.flags = {CfileFlag.Cached}
addFileToCompile(m.config, cf)
proc finalCodegenActions*(graph: ModuleGraph; m: BModule; n: PNode): PNode =
proc finalCodegenActions*(graph: ModuleGraph; m: BModule; n: PNode) =
## Also called from IC.
result = nil
if sfMainModule in m.module.flags:
# phase ordering problem here: We need to announce this
# dependency to 'nimTestErrorFlag' before system.c has been written to disk.
@@ -2231,7 +2232,12 @@ proc finalCodegenActions*(graph: ModuleGraph; m: BModule; n: PNode): PNode =
if m.g.forwardedProcs.len == 0:
incl m.flags, objHasKidsValid
result = generateMethodDispatchers(graph, m.idgen)
if optMultiMethods in m.g.config.globalOptions or
m.g.config.selectedGC notin {gcArc, gcOrc, gcAtomicArc} or
not m.g.config.isDefined("nimPreviewVtables") or
m.g.config.backend == backendCpp or sfCompileToCpp in m.module.flags:
generateIfMethodDispatchers(graph, m.idgen)
let mm = m
m.g.modulesClosed.add mm

View File

@@ -11,14 +11,14 @@
import
options, ast, msgs, idents, renderer, types, magicsys,
sempass2, modulegraphs, lineinfos
sempass2, modulegraphs, lineinfos, astalgo
import std/intsets
when defined(nimPreviewSlimSystem):
import std/assertions
import std/[tables]
proc genConv(n: PNode, d: PType, downcast: bool; conf: ConfigRef): PNode =
var dest = skipTypes(d, abstractPtrs)
@@ -157,6 +157,9 @@ proc fixupDispatcher(meth, disp: PSym; conf: ConfigRef) =
proc methodDef*(g: ModuleGraph; idgen: IdGenerator; s: PSym) =
var witness: PSym = nil
if s.typ[1].owner.getModule != s.getModule and g.config.isDefined("nimPreviewVtables"):
localError(g.config, s.info, errGenerated, "method `" & s.name.s &
"` can be defined only in the same module with its type (" & s.typ[1].typeToString() & ")")
for i in 0..<g.methods.len:
let disp = g.methods[i].dispatcher
case sameMethodBucket(disp, s, multimethods = optMultiMethods in g.config.globalOptions)
@@ -175,6 +178,11 @@ proc methodDef*(g: ModuleGraph; idgen: IdGenerator; s: PSym) =
of Invalid:
if witness.isNil: witness = g.methods[i].methods[0]
# create a new dispatcher:
# stores the id and the position
if s.typ[1].skipTypes(skipPtrs).itemId notin g.bucketTable:
g.bucketTable[s.typ[1].skipTypes(skipPtrs).itemId] = 1
else:
g.bucketTable.inc(s.typ[1].skipTypes(skipPtrs).itemId)
g.methods.add((methods: @[s], dispatcher: createDispatcher(s, g, idgen)))
#echo "adding ", s.info
if witness != nil:
@@ -183,7 +191,7 @@ proc methodDef*(g: ModuleGraph; idgen: IdGenerator; s: PSym) =
elif sfBase notin s.flags:
message(g.config, s.info, warnUseBase)
proc relevantCol(methods: seq[PSym], col: int): bool =
proc relevantCol*(methods: seq[PSym], col: int): bool =
# returns true iff the position is relevant
result = false
var t = methods[0].typ[col].skipTypes(skipPtrs)
@@ -203,7 +211,7 @@ proc cmpSignatures(a, b: PSym, relevantCols: IntSet): int =
if (d != high(int)) and d != 0:
return d
proc sortBucket(a: var seq[PSym], relevantCols: IntSet) =
proc sortBucket*(a: var seq[PSym], relevantCols: IntSet) =
# we use shellsort here; fast and simple
var n = a.len
var h = 1
@@ -222,7 +230,7 @@ proc sortBucket(a: var seq[PSym], relevantCols: IntSet) =
a[j] = v
if h == 1: break
proc genDispatcher(g: ModuleGraph; methods: seq[PSym], relevantCols: IntSet; idgen: IdGenerator): PSym =
proc genIfDispatcher*(g: ModuleGraph; methods: seq[PSym], relevantCols: IntSet; idgen: IdGenerator): PSym =
var base = methods[0].ast[dispatcherPos].sym
result = base
var paramLen = base.typ.len
@@ -281,8 +289,7 @@ proc genDispatcher(g: ModuleGraph; methods: seq[PSym], relevantCols: IntSet; idg
nilchecks.flags.incl nfTransf # should not be further transformed
result.ast[bodyPos] = nilchecks
proc generateMethodDispatchers*(g: ModuleGraph, idgen: IdGenerator): PNode =
result = newNode(nkStmtList)
proc generateIfMethodDispatchers*(g: ModuleGraph, idgen: IdGenerator) =
for bucket in 0..<g.methods.len:
var relevantCols = initIntSet()
for col in 1..<g.methods[bucket].methods[0].typ.len:
@@ -291,4 +298,4 @@ proc generateMethodDispatchers*(g: ModuleGraph, idgen: IdGenerator): PNode =
# if multi-methods are not enabled, we are interested only in the first field
break
sortBucket(g.methods[bucket].methods, relevantCols)
result.add newSymNode(genDispatcher(g, g.methods[bucket].methods, relevantCols, idgen))
g.addDispatchers genIfDispatcher(g, g.methods[bucket].methods, relevantCols, idgen)

View File

@@ -50,10 +50,9 @@ proc generateCodeForModule(g: ModuleGraph; m: var LoadedModule; alive: var Alive
let n = unpackTree(g, m.module.position, m.fromDisk.topLevel, p)
cgen.genTopLevelStmt(bmod, n)
let disps = finalCodegenActions(g, bmod, newNodeI(nkStmtList, m.module.info))
if disps != nil:
for disp in disps:
genProcAux(bmod, disp.sym)
finalCodegenActions(g, bmod, newNodeI(nkStmtList, m.module.info))
for disp in getDispatchers(g):
genProcAux(bmod, disp)
m.fromDisk.backendFlags = cgen.whichInitProcs(bmod)
proc replayTypeInfo(g: ModuleGraph; m: var LoadedModule; origin: FileIndex) =

View File

@@ -51,8 +51,10 @@ type
typeInstCache*: seq[(PackedItemId, PackedItemId)]
procInstCache*: seq[PackedInstantiation]
attachedOps*: seq[(PackedItemId, TTypeAttachedOp, PackedItemId)]
methodsPerType*: seq[(PackedItemId, int, PackedItemId)]
methodsPerGenericType*: seq[(PackedItemId, int, PackedItemId)]
enumToStringProcs*: seq[(PackedItemId, PackedItemId)]
methodsPerType*: seq[(PackedItemId, PackedItemId)]
dispatchers*: seq[PackedItemId]
emittedTypeInfo*: seq[string]
backendFlags*: set[ModuleBackendFlag]
@@ -650,8 +652,10 @@ proc loadRodFile*(filename: AbsoluteFile; m: var PackedModule; config: ConfigRef
loadSeqSection typeInstCacheSection, m.typeInstCache
loadSeqSection procInstCacheSection, m.procInstCache
loadSeqSection attachedOpsSection, m.attachedOps
loadSeqSection methodsPerTypeSection, m.methodsPerType
loadSeqSection methodsPerGenericTypeSection, m.methodsPerGenericType
loadSeqSection enumToStringProcsSection, m.enumToStringProcs
loadSeqSection methodsPerTypeSection, m.methodsPerType
loadSeqSection dispatchersSection, m.dispatchers
loadSeqSection typeInfoSection, m.emittedTypeInfo
f.loadSection backendFlagsSection
@@ -718,8 +722,10 @@ proc saveRodFile*(filename: AbsoluteFile; encoder: var PackedEncoder; m: var Pac
storeSeqSection typeInstCacheSection, m.typeInstCache
storeSeqSection procInstCacheSection, m.procInstCache
storeSeqSection attachedOpsSection, m.attachedOps
storeSeqSection methodsPerTypeSection, m.methodsPerType
storeSeqSection methodsPerGenericTypeSection, m.methodsPerGenericType
storeSeqSection enumToStringProcsSection, m.enumToStringProcs
storeSeqSection methodsPerTypeSection, m.methodsPerType
storeSeqSection dispatchersSection, m.dispatchers
storeSeqSection typeInfoSection, m.emittedTypeInfo
f.storeSection backendFlagsSection

View File

@@ -135,8 +135,10 @@ proc checkModule(c: var CheckedContext; m: PackedModule) =
typeInstCache*: seq[(PackedItemId, PackedItemId)]
procInstCache*: seq[PackedInstantiation]
attachedOps*: seq[(TTypeAttachedOp, PackedItemId, PackedItemId)]
methodsPerType*: seq[(PackedItemId, int, PackedItemId)]
methodsPerGenericType*: seq[(PackedItemId, int, PackedItemId)]
enumToStringProcs*: seq[(PackedItemId, PackedItemId)]
methodsPerType*: seq[(PackedItemId, PackedItemId)]
dispatchers*: seq[PackedItemId]
]#
proc checkIntegrity*(g: ModuleGraph) =

View File

@@ -103,6 +103,17 @@ proc replayBackendProcs*(g: ModuleGraph; module: int) =
let symId = FullId(module: tmp.module, packed: it[1])
g.enumToStringProcs[key] = LazySym(id: symId, sym: nil)
for it in mitems(g.packed[module].fromDisk.methodsPerType):
let key = translateId(it[0], g.packed, module, g.config)
let tmp = translateId(it[1], g.packed, module, g.config)
let symId = FullId(module: tmp.module, packed: it[1])
g.methodsPerType.mgetOrPut(key, @[]).add LazySym(id: symId, sym: nil)
for it in mitems(g.packed[module].fromDisk.dispatchers):
let tmp = translateId(it, g.packed, module, g.config)
let symId = FullId(module: tmp.module, packed: it)
g.dispatchers.add LazySym(id: symId, sym: nil)
proc replayGenericCacheInformation*(g: ModuleGraph; module: int) =
## We remember the generic instantiations a module performed
## in order to to avoid the code bloat that generic code tends
@@ -127,12 +138,12 @@ proc replayGenericCacheInformation*(g: ModuleGraph; module: int) =
module: module, sym: FullId(module: sym.module, packed: it.sym),
concreteTypes: concreteTypes, inst: nil)
for it in mitems(g.packed[module].fromDisk.methodsPerType):
for it in mitems(g.packed[module].fromDisk.methodsPerGenericType):
let key = translateId(it[0], g.packed, module, g.config)
let col = it[1]
let tmp = translateId(it[2], g.packed, module, g.config)
let symId = FullId(module: tmp.module, packed: it[2])
g.methodsPerType.mgetOrPut(key, @[]).add (col, LazySym(id: symId, sym: nil))
g.methodsPerGenericType.mgetOrPut(key, @[]).add (col, LazySym(id: symId, sym: nil))
replayBackendProcs(g, module)

View File

@@ -92,8 +92,10 @@ type
typeInstCacheSection
procInstCacheSection
attachedOpsSection
methodsPerTypeSection
methodsPerGenericTypeSection
enumToStringProcsSection
methodsPerTypeSection
dispatchersSection
typeInfoSection # required by the backend
backendFlagsSection
aliveSymsSection # beware, this is stored in a `.alivesyms` file.

View File

@@ -3097,9 +3097,8 @@ proc wholeCode(graph: ModuleGraph; m: BModule): Rope =
var p = newInitProc(globals, m)
attachProc(p, prc)
var disp = generateMethodDispatchers(graph, m.idgen)
for i in 0..<disp.len:
let prc = disp[i].sym
generateIfMethodDispatchers(graph, m.idgen)
for prc in getDispatchers(graph):
if not globals.generatedSyms.containsOrIncl(prc.id):
var p = newInitProc(globals, m)
attachProc(p, prc)

View File

@@ -16,6 +16,7 @@ import ../dist/checksums/src/checksums/md5
import ast, astalgo, options, lineinfos,idents, btrees, ropes, msgs, pathutils, packages
import ic / [packed_ast, ic]
when defined(nimPreviewSlimSystem):
import std/assertions
@@ -81,7 +82,7 @@ type
typeInstCache*: Table[ItemId, seq[LazyType]] # A symbol's ItemId.
procInstCache*: Table[ItemId, seq[LazyInstantiation]] # A symbol's ItemId.
attachedOps*: array[TTypeAttachedOp, Table[ItemId, LazySym]] # Type ID, destructors, etc.
methodsPerType*: Table[ItemId, seq[(int, LazySym)]] # Type ID, attached methods
methodsPerGenericType*: Table[ItemId, seq[(int, LazySym)]] # Type ID, attached methods
memberProcsPerType*: Table[ItemId, seq[PSym]] # Type ID, attached member procs (only c++, virtual,member and ctor so far).
initializersPerType*: Table[ItemId, PNode] # Type ID, AST call to the default ctor (c++ only)
enumToStringProcs*: Table[ItemId, LazySym]
@@ -110,6 +111,11 @@ type
suggestSymbols*: Table[FileIndex, seq[SymInfoPair]]
suggestErrors*: Table[FileIndex, seq[Suggest]]
methods*: seq[tuple[methods: seq[PSym], dispatcher: PSym]] # needs serialization!
bucketTable*: CountTable[ItemId]
objectTree*: Table[ItemId, seq[tuple[depth: int, value: PType]]]
methodsPerType*: Table[ItemId, seq[LazySym]]
dispatchers*: seq[LazySym]
systemModule*: PSym
sysTypes*: array[TTypeKind, PType]
compilerprocs*: TStrTable
@@ -153,8 +159,10 @@ proc resetForBackend*(g: ModuleGraph) =
g.procInstCache.clear()
for a in mitems(g.attachedOps):
a.clear()
g.methodsPerType.clear()
g.methodsPerGenericType.clear()
g.enumToStringProcs.clear()
g.dispatchers.setLen(0)
g.methodsPerType.clear()
const
cb64 = [
@@ -325,6 +333,7 @@ iterator procInstCacheItems*(g: ModuleGraph; s: PSym): PInstantiation =
for t in mitems(x[]):
yield resolveInst(g, t)
proc getAttachedOp*(g: ModuleGraph; t: PType; op: TTypeAttachedOp): PSym =
## returns the requested attached operation for type `t`. Can return nil
## if no such operation exists.
@@ -348,6 +357,27 @@ proc completePartialOp*(g: ModuleGraph; module: int; t: PType; op: TTypeAttached
toPackedGeneratedProcDef(value, g.encoders[module], g.packed[module].fromDisk)
#storeAttachedProcDef(t, op, value, g.encoders[module], g.packed[module].fromDisk)
iterator getDispatchers*(g: ModuleGraph): PSym =
for i in g.dispatchers.mitems:
yield resolveSym(g, i)
proc addDispatchers*(g: ModuleGraph, value: PSym) =
# TODO: add it for packed modules
g.dispatchers.add LazySym(sym: value)
iterator resolveLazySymSeq(g: ModuleGraph, list: var seq[LazySym]): PSym =
for it in list.mitems:
yield resolveSym(g, it)
proc setMethodsPerType*(g: ModuleGraph; id: ItemId, methods: seq[LazySym]) =
# TODO: add it for packed modules
g.methodsPerType[id] = methods
iterator getMethodsPerType*(g: ModuleGraph; t: PType): PSym =
if g.methodsPerType.contains(t.itemId):
for it in mitems g.methodsPerType[t.itemId]:
yield resolveSym(g, it)
proc getToStringProc*(g: ModuleGraph; t: PType): PSym =
result = resolveSym(g, g.enumToStringProcs[t.itemId])
assert result != nil
@@ -356,12 +386,12 @@ proc setToStringProc*(g: ModuleGraph; t: PType; value: PSym) =
g.enumToStringProcs[t.itemId] = LazySym(sym: value)
iterator methodsForGeneric*(g: ModuleGraph; t: PType): (int, PSym) =
if g.methodsPerType.contains(t.itemId):
for it in mitems g.methodsPerType[t.itemId]:
if g.methodsPerGenericType.contains(t.itemId):
for it in mitems g.methodsPerGenericType[t.itemId]:
yield (it[0], resolveSym(g, it[1]))
proc addMethodToGeneric*(g: ModuleGraph; module: int; t: PType; col: int; m: PSym) =
g.methodsPerType.mgetOrPut(t.itemId, @[]).add (col, LazySym(sym: m))
g.methodsPerGenericType.mgetOrPut(t.itemId, @[]).add (col, LazySym(sym: m))
proc hasDisabledAsgn*(g: ModuleGraph; t: PType): bool =
let op = getAttachedOp(g, t, attachedAsgn)

View File

@@ -9,6 +9,7 @@ define:nimPreviewSlimSystem
define:nimPreviewCstringConversion
define:nimPreviewProcConversion
define:nimPreviewRangeDefault
define:nimPreviewVtables
define:nimPreviewNonVarDestructor
threads:off

View File

@@ -191,15 +191,16 @@ proc processPipelineModule*(graph: ModuleGraph; module: PSym; idgen: IdGenerator
case graph.pipelinePass
of CgenPass:
if bModule != nil:
let disps = finalCodegenActions(graph, BModule(bModule), finalNode)
if disps != nil:
let m = BModule(bModule)
finalCodegenActions(graph, m, finalNode)
if graph.dispatchers.len > 0:
let ctx = preparePContext(graph, module, idgen)
for disp in disps:
let retTyp = disp.sym.typ[0]
for disp in getDispatchers(graph):
let retTyp = disp.typ[0]
if retTyp != nil:
# todo properly semcheck the code of dispatcher?
createTypeBoundOps(graph, ctx, retTyp, disp.info, idgen)
genProcAux(BModule(bModule), disp.sym)
# TODO: properly semcheck the code of dispatcher?
createTypeBoundOps(graph, ctx, retTyp, disp.ast.info, idgen)
genProcAux(m, disp)
discard closePContext(graph, ctx, nil)
of JSgenPass:
when not defined(leanCompiler):

View File

@@ -20,6 +20,7 @@ import
isolation_check, typeallowed, modulegraphs, enumtostr, concepts, astmsgs,
extccomp
import vtables
import std/[strtabs, math, tables, intsets, strutils]
when not defined(leanCompiler):
@@ -839,6 +840,15 @@ proc semStmtAndGenerateGenerics(c: PContext, n: PNode): PNode =
if c.config.cmd == cmdIdeTools:
appendToModule(c.module, result)
trackStmt(c, c.module, result, isTopLevel = true)
if optMultiMethods notin c.config.globalOptions and
c.config.selectedGC in {gcArc, gcOrc, gcAtomicArc} and
c.config.isDefined("nimPreviewVtables") and
c.config.backend != backendCpp and
sfCompileToCpp notin c.module.flags:
sortVTableDispatchers(c.graph)
if sfMainModule in c.module.flags:
collectVTableDispatchers(c.graph)
proc recoverContext(c: PContext) =
# clean up in case of a semantic error: We clean up the stacks, etc. This is

View File

@@ -24,6 +24,9 @@ when defined(useDfa):
import liftdestructors
include sinkparameter_inference
import std/options as opt
#[ Second semantic checking pass over the AST. Necessary because the old
way had some inherent problems. Performs:
@@ -91,6 +94,37 @@ const
errXCannotBeAssignedTo = "'$1' cannot be assigned to"
errLetNeedsInit = "'let' symbol requires an initialization"
proc getObjDepth(t: PType): Option[tuple[depth: int, root: ItemId]] =
var x = t
var res: tuple[depth: int, root: ItemId]
res.depth = -1
var stack = newSeq[ItemId]()
while x != nil:
x = skipTypes(x, skipPtrs)
if x.kind != tyObject:
return none(tuple[depth: int, root: ItemId])
stack.add x.itemId
x = x[0]
inc(res.depth)
res.root = stack[^2]
result = some(res)
proc collectObjectTree(graph: ModuleGraph, n: PNode) =
for section in n:
if section.kind == nkTypeDef and section[^1].kind in {nkObjectTy, nkRefTy, nkPtrTy}:
let typ = section[^1].typ.skipTypes(skipPtrs)
if typ.len > 0 and typ[0] != nil:
let depthItem = getObjDepth(typ)
if isSome(depthItem):
let (depthLevel, root) = depthItem.unsafeGet
if depthLevel == 1:
graph.objectTree[root] = @[]
else:
if root notin graph.objectTree:
graph.objectTree[root] = @[(depthLevel, typ)]
else:
graph.objectTree[root].add (depthLevel, typ)
proc createTypeBoundOps(tracked: PEffects, typ: PType; info: TLineInfo) =
if typ == nil or sfGeneratedOp in tracked.owner.flags:
# don't create type bound ops for anything in a function with a `nodestroy` pragma
@@ -1323,8 +1357,11 @@ proc track(tracked: PEffects, n: PNode) =
of nkProcDef, nkConverterDef, nkMethodDef, nkIteratorDef, nkLambda, nkFuncDef, nkDo:
if n[0].kind == nkSym and n[0].sym.ast != nil:
trackInnerProc(tracked, getBody(tracked.graph, n[0].sym))
of nkTypeSection, nkMacroDef, nkTemplateDef:
of nkMacroDef, nkTemplateDef:
discard
of nkTypeSection:
if tracked.isTopLevel:
collectObjectTree(tracked.graph, n)
of nkCast:
if n.len == 2:
track(tracked, n[1])
@@ -1637,13 +1674,18 @@ proc trackProc*(c: PContext; s: PSym, body: PNode) =
checkNil(s, body, g.config, c.idgen)
proc trackStmt*(c: PContext; module: PSym; n: PNode, isTopLevel: bool) =
if n.kind in {nkPragma, nkMacroDef, nkTemplateDef, nkProcDef, nkFuncDef,
nkTypeSection, nkConverterDef, nkMethodDef, nkIteratorDef}:
return
let g = c.graph
var effects = newNodeI(nkEffectList, n.info)
var t: TEffects = initEffects(g, effects, module, c)
t.isTopLevel = isTopLevel
track(t, n)
when defined(drnim):
if c.graph.strongSemCheck != nil: c.graph.strongSemCheck(c.graph, module, n)
case n.kind
of {nkPragma, nkMacroDef, nkTemplateDef, nkProcDef, nkFuncDef,
nkConverterDef, nkMethodDef, nkIteratorDef}:
discard
of nkTypeSection:
if isTopLevel:
collectObjectTree(c.graph, n)
else:
let g = c.graph
var effects = newNodeI(nkEffectList, n.info)
var t: TEffects = initEffects(g, effects, module, c)
t.isTopLevel = isTopLevel
track(t, n)
when defined(drnim):
if c.graph.strongSemCheck != nil: c.graph.strongSemCheck(c.graph, module, n)

167
compiler/vtables.nim Normal file
View File

@@ -0,0 +1,167 @@
import ast, modulegraphs, magicsys, lineinfos, options, cgmeth, types
import std/[algorithm, tables, intsets, assertions]
proc genVTableDispatcher(g: ModuleGraph; methods: seq[PSym]; index: int): PSym =
#[
proc dispatch(x: Base, params: ...) =
cast[proc bar(x: Base, params: ...)](x.vTable[index])(x, params)
]#
var base = methods[0].ast[dispatcherPos].sym
result = base
var paramLen = base.typ.len
var body = newNodeI(nkStmtList, base.info)
var disp = newNodeI(nkIfStmt, base.info)
var vTableAccess = newNodeIT(nkBracketExpr, base.info, base.typ)
let nimGetVTableSym = getCompilerProc(g, "nimGetVTable")
let ptrPNimType = nimGetVTableSym.typ.n[1].sym.typ
var nTyp = base.typ.n[1].sym.typ
var dispatchObject = newSymNode(base.typ.n[1].sym)
if nTyp.kind == tyObject:
dispatchObject = newTree(nkAddr, dispatchObject)
else:
if g.config.backend != backendCpp: # TODO: maybe handle ptr?
if nTyp.kind == tyVar and nTyp.skipTypes({tyVar}).kind != tyObject:
dispatchObject = newTree(nkDerefExpr, dispatchObject)
var getVTableCall = newTree(nkCall,
newSymNode(nimGetVTableSym),
dispatchObject,
newIntNode(nkIntLit, index)
)
getVTableCall.typ = base.typ
var vTableCall = newNodeIT(nkCall, base.info, base.typ[0])
var castNode = newTree(nkCast,
newNodeIT(nkType, base.info, base.typ),
getVTableCall)
castNode.typ = base.typ
vTableCall.add castNode
for col in 1..<paramLen:
let param = base.typ.n[col].sym
vTableCall.add newSymNode(param)
var ret: PNode
if base.typ[0] != nil:
var a = newNodeI(nkFastAsgn, base.info)
a.add newSymNode(base.ast[resultPos].sym)
a.add vTableCall
ret = newNodeI(nkReturnStmt, base.info)
ret.add a
else:
ret = vTableCall
if base.typ.n[1].sym.typ.skipTypes(abstractInst).kind in {tyRef, tyPtr}:
let ifBranch = newNodeI(nkElifBranch, base.info)
let boolType = getSysType(g, unknownLineInfo, tyBool)
var isNil = getSysMagic(g, unknownLineInfo, "isNil", mIsNil)
let checkSelf = newNodeIT(nkCall, base.info, boolType)
checkSelf.add newSymNode(isNil)
checkSelf.add newSymNode(base.typ.n[1].sym)
ifBranch.add checkSelf
ifBranch.add newTree(nkCall,
newSymNode(getCompilerProc(g, "chckNilDisp")), newSymNode(base.typ.n[1].sym))
let elseBranch = newTree(nkElifBranch, ret)
disp.add ifBranch
disp.add elseBranch
else:
disp = ret
body.add disp
body.flags.incl nfTransf # should not be further transformed
result.ast[bodyPos] = body
proc containGenerics(base: PType, s: seq[tuple[depth: int, value: PType]]): bool =
result = tfHasMeta in base.flags
for i in s:
if tfHasMeta in i.value.flags:
result = true
break
proc collectVTableDispatchers*(g: ModuleGraph) =
var itemTable = initTable[ItemId, seq[LazySym]]()
var rootTypeSeq = newSeq[PType]()
var rootItemIdCount = initCountTable[ItemId]()
for bucket in 0..<g.methods.len:
var relevantCols = initIntSet()
if relevantCol(g.methods[bucket].methods, 1): incl(relevantCols, 1)
sortBucket(g.methods[bucket].methods, relevantCols)
let base = g.methods[bucket].methods[^1]
let baseType = base.typ[1].skipTypes(skipPtrs-{tyTypeDesc})
if baseType.itemId in g.objectTree and not containGenerics(baseType, g.objectTree[baseType.itemId]):
let methodIndexLen = g.bucketTable[baseType.itemId]
if baseType.itemId notin itemTable: # once is enough
rootTypeSeq.add baseType
itemTable[baseType.itemId] = newSeq[LazySym](methodIndexLen)
sort(g.objectTree[baseType.itemId], cmp = proc (x, y: tuple[depth: int, value: PType]): int =
if x.depth >= y.depth: 1
else: -1
)
for item in g.objectTree[baseType.itemId]:
if item.value.itemId notin itemTable:
itemTable[item.value.itemId] = newSeq[LazySym](methodIndexLen)
var mIndex = 0 # here is the correpsonding index
if baseType.itemId notin rootItemIdCount:
rootItemIdCount[baseType.itemId] = 1
else:
mIndex = rootItemIdCount[baseType.itemId]
rootItemIdCount.inc(baseType.itemId)
for idx in 0..<g.methods[bucket].methods.len:
let obj = g.methods[bucket].methods[idx].typ[1].skipTypes(skipPtrs)
itemTable[obj.itemId][mIndex] = LazySym(sym: g.methods[bucket].methods[idx])
g.addDispatchers genVTableDispatcher(g, g.methods[bucket].methods, mIndex)
else: # if the base object doesn't have this method
g.addDispatchers genIfDispatcher(g, g.methods[bucket].methods, relevantCols, g.idgen)
proc sortVTableDispatchers*(g: ModuleGraph) =
var itemTable = initTable[ItemId, seq[LazySym]]()
var rootTypeSeq = newSeq[ItemId]()
var rootItemIdCount = initCountTable[ItemId]()
for bucket in 0..<g.methods.len:
var relevantCols = initIntSet()
if relevantCol(g.methods[bucket].methods, 1): incl(relevantCols, 1)
sortBucket(g.methods[bucket].methods, relevantCols)
let base = g.methods[bucket].methods[^1]
let baseType = base.typ[1].skipTypes(skipPtrs-{tyTypeDesc})
if baseType.itemId in g.objectTree and not containGenerics(baseType, g.objectTree[baseType.itemId]):
let methodIndexLen = g.bucketTable[baseType.itemId]
if baseType.itemId notin itemTable: # once is enough
rootTypeSeq.add baseType.itemId
itemTable[baseType.itemId] = newSeq[LazySym](methodIndexLen)
sort(g.objectTree[baseType.itemId], cmp = proc (x, y: tuple[depth: int, value: PType]): int =
if x.depth >= y.depth: 1
else: -1
)
for item in g.objectTree[baseType.itemId]:
if item.value.itemId notin itemTable:
itemTable[item.value.itemId] = newSeq[LazySym](methodIndexLen)
var mIndex = 0 # here is the correpsonding index
if baseType.itemId notin rootItemIdCount:
rootItemIdCount[baseType.itemId] = 1
else:
mIndex = rootItemIdCount[baseType.itemId]
rootItemIdCount.inc(baseType.itemId)
for idx in 0..<g.methods[bucket].methods.len:
let obj = g.methods[bucket].methods[idx].typ[1].skipTypes(skipPtrs)
itemTable[obj.itemId][mIndex] = LazySym(sym: g.methods[bucket].methods[idx])
for baseType in rootTypeSeq:
g.setMethodsPerType(baseType, itemTable[baseType])
for item in g.objectTree[baseType]:
let typ = item.value.skipTypes(skipPtrs)
let idx = typ.itemId
for mIndex in 0..<itemTable[idx].len:
if itemTable[idx][mIndex].sym == nil:
let parentIndex = typ[0].skipTypes(skipPtrs).itemId
itemTable[idx][mIndex] = itemTable[parentIndex][mIndex]
g.setMethodsPerType(idx, itemTable[idx])

View File

@@ -21,4 +21,3 @@ when defined(nimStrictMode):
# future work: XDeclaredButNotUsed
switch("define", "nimVersion:" & NimVersion)

View File

@@ -1606,6 +1606,8 @@ when not defined(js) and defined(nimV2):
traceImpl: pointer
typeInfoV1: pointer # for backwards compat, usually nil
flags: int
when defined(nimPreviewVtables) and not defined(cpp):
vTable: UncheckedArray[pointer] # vtable for types
PNimTypeV2 = ptr TNimTypeV2
proc supportsCopyMem(t: typedesc): bool {.magic: "TypeTrait".}

View File

@@ -243,3 +243,8 @@ template tearDownForeignThreadGc* =
proc isObjDisplayCheck(source: PNimTypeV2, targetDepth: int16, token: uint32): bool {.compilerRtl, inl.} =
result = targetDepth <= source.depth and source.display[targetDepth] == token
when defined(nimPreviewVtables) and not defined(cpp):
proc nimGetVTable(p: pointer, index: int): pointer
{.compilerRtl, inline, raises: [].} =
result = cast[ptr PNimTypeV2](p).vTable[index]

View File

@@ -44,4 +44,5 @@ switch("define", "nimPreviewNonVarDestructor")
switch("warningAserror", "UnnamedBreak")
switch("legacy", "verboseTypeMismatch")
switch("define", "nimPreviewVtables")

View File

@@ -1,4 +1,5 @@
discard """
matrix: "-u:nimPreviewVtables"
output: '''(peel: 0, color: 15)
(color: 15)
17

View File

@@ -1 +0,0 @@
multimethods:on

View File

@@ -0,0 +1,24 @@
discard """
matrix: "--mm:arc; --mm:refc"
output: '''
newDNode base
'''
"""
type
SNodeAny = ref object of RootObj
SNode[T] = ref object of SNodeAny
m: T
DNode[T] = ref object
method getStr(s: SNode[float]): string {.base.} = "blahblah"
method newDNode(s: SNodeAny) {.base.} =
echo "newDNode base"
method newDNode[T](s: SNode[T]) =
echo "newDNode generic"
let m = SNode[float]()
let s = SNodeAny(m)
newDnode(s)

View File

@@ -1,42 +1,42 @@
discard """
matrix: "--mm:arc; --mm:refc"
output: '''wow2
X 1
X 3'''
"""
type
First[T] = ref object of RootObj
value: T
Second[T] = ref object of First[T]
value2: T
method wow[T](y: int; x: First[T]) {.base.} =
echo "wow1"
method wow[T](y: int; x: Second[T]) =
echo "wow2"
var
x: Second[int]
new(x)
proc takeFirst(x: First[int]) =
wow(2, x)
takeFirst(x)
# bug #5479
type
Base[T: static[int]] = ref object of RootObj
method test[T](t: Base[T]) {.base.} =
echo "X ", t.T
let ab = Base[1]()
ab.test()
let ac = Base[3]()
ac.test()
discard """
matrix: "--mm:arc --multimethods:on -u:nimPreviewVtables; --mm:refc --multimethods:on -u:nimPreviewVtables"
output: '''wow2
X 1
X 3'''
"""
type
First[T] = ref object of RootObj
value: T
Second[T] = ref object of First[T]
value2: T
method wow[T](y: int; x: First[T]) {.base.} =
echo "wow1"
method wow[T](y: int; x: Second[T]) =
echo "wow2"
var
x: Second[int]
new(x)
proc takeFirst(x: First[int]) =
wow(2, x)
takeFirst(x)
# bug #5479
type
Base[T: static[int]] = ref object of RootObj
method test[T](t: Base[T]) {.base.} =
echo "X ", t.T
let ab = Base[1]()
ab.test()
let ac = Base[3]()
ac.test()

View File

@@ -1,15 +1,12 @@
discard """
matrix: "--mm:arc; --mm:refc"
output: '''
do nothing
HELLO WORLD!
'''
"""
# tmethods1
method somethin(obj: RootObj) {.base.} =
echo "do nothing"
type
TNode* {.inheritable.} = object
@@ -23,8 +20,6 @@ type
method foo(a: PNode, b: PSomethingElse) {.base.} = discard
method foo(a: PNodeFoo, b: PSomethingElse) = discard
var o: RootObj
o.somethin()

View File

@@ -0,0 +1,12 @@
discard """
matrix: "--mm:arc -u:nimPreviewVtables"
output: '''
do nothing
'''
"""
# tmethods1
method somethin(obj: RootObj) {.base.} =
echo "do nothing"
var o: RootObj
o.somethin()

View File

@@ -1,96 +1,97 @@
discard """
output: '''
collide: unit, thing
collide: unit, thing
collide: thing, unit
collide: thing, thing
collide: unit, thing |
collide: unit, thing |
collide: thing, unit |
do nothing
'''
joinable: false
disabled: true
"""
# tmultim2
type
TThing {.inheritable.} = object
TUnit = object of TThing
x: int
TParticle = object of TThing
a, b: int
method collide(a, b: TThing) {.base, inline.} =
echo "collide: thing, thing"
method collide(a: TThing, b: TUnit) {.inline.} =
echo "collide: thing, unit"
method collide(a: TUnit, b: TThing) {.inline.} =
echo "collide: unit, thing"
proc test(a, b: TThing) {.inline.} =
collide(a, b)
proc staticCollide(a, b: TThing) {.inline.} =
procCall collide(a, b)
var
a: TThing
b, c: TUnit
collide(b, c) # ambiguous (unit, thing) or (thing, unit)? -> prefer unit, thing!
test(b, c)
collide(a, b)
staticCollide(a, b)
# tmultim6
type
Thing {.inheritable.} = object
Unit[T] = object of Thing
x: T
Particle = object of Thing
a, b: int
method collide(a, b: Thing) {.base, inline.} =
quit "to override!"
method collide[T](a: Thing, b: Unit[T]) {.inline.} =
echo "collide: thing, unit |"
method collide[T](a: Unit[T], b: Thing) {.inline.} =
echo "collide: unit, thing |"
proc test(a, b: Thing) {.inline.} =
collide(a, b)
var
aaa: Thing
bbb, ccc: Unit[string]
collide(bbb, Thing(ccc))
test(bbb, ccc)
collide(aaa, bbb)
# tmethods1
method somethin(obj: RootObj) {.base.} =
echo "do nothing"
type
TNode* {.inheritable.} = object
PNode* = ref TNode
PNodeFoo* = ref object of TNode
TSomethingElse = object
PSomethingElse = ref TSomethingElse
method foo(a: PNode, b: PSomethingElse) {.base.} = discard
method foo(a: PNodeFoo, b: PSomethingElse) = discard
var o: RootObj
o.somethin()
discard """
matrix: "--multimethods:on"
output: '''
collide: unit, thing
collide: unit, thing
collide: thing, unit
collide: thing, thing
collide: unit, thing |
collide: unit, thing |
collide: thing, unit |
do nothing
'''
joinable: false
disabled: true
"""
# tmultim2
type
TThing {.inheritable.} = object
TUnit = object of TThing
x: int
TParticle = object of TThing
a, b: int
method collide(a, b: TThing) {.base, inline.} =
echo "collide: thing, thing"
method collide(a: TThing, b: TUnit) {.inline.} =
echo "collide: thing, unit"
method collide(a: TUnit, b: TThing) {.inline.} =
echo "collide: unit, thing"
proc test(a, b: TThing) {.inline.} =
collide(a, b)
proc staticCollide(a, b: TThing) {.inline.} =
procCall collide(a, b)
var
a: TThing
b, c: TUnit
collide(b, c) # ambiguous (unit, thing) or (thing, unit)? -> prefer unit, thing!
test(b, c)
collide(a, b)
staticCollide(a, b)
# tmultim6
type
Thing {.inheritable.} = object
Unit[T] = object of Thing
x: T
Particle = object of Thing
a, b: int
method collide(a, b: Thing) {.base, inline.} =
quit "to override!"
method collide[T](a: Thing, b: Unit[T]) {.inline.} =
echo "collide: thing, unit |"
method collide[T](a: Unit[T], b: Thing) {.inline.} =
echo "collide: unit, thing |"
proc test(a, b: Thing) {.inline.} =
collide(a, b)
var
aaa: Thing
bbb, ccc: Unit[string]
collide(bbb, Thing(ccc))
test(bbb, ccc)
collide(aaa, bbb)
# tmethods1
method somethin(obj: RootObj) {.base.} =
echo "do nothing"
type
TNode* {.inheritable.} = object
PNode* = ref TNode
PNodeFoo* = ref object of TNode
TSomethingElse = object
PSomethingElse = ref TSomethingElse
method foo(a: PNode, b: PSomethingElse) {.base.} = discard
method foo(a: PNodeFoo, b: PSomethingElse) = discard
var o: RootObj
o.somethin()

19
tests/method/tvtable.nim Normal file
View File

@@ -0,0 +1,19 @@
type FooBase = ref object of RootObj
dummy: int
type Foo = ref object of FooBase
value : float32
type Foo2 = ref object of Foo
change : float32
method bar(x: FooBase, a: float32) {.base.} =
discard
method bar(x: Foo, a: float32) =
x.value += a
method bar(x: Foo2, a: float32) =
x.value += a
proc test() =
var x = new Foo2
x.bar(2.3)
test()