diff --git a/changelog.md b/changelog.md index ab121f5a13..fb70a9a6b0 100644 --- a/changelog.md +++ b/changelog.md @@ -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/` instead of `Nim httpclient/` 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 diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim index 855ec4cd9b..2f304852b1 100644 --- a/compiler/ccgtypes.nim +++ b/compiler/ccgtypes.nim @@ -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.. 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) diff --git a/compiler/cgen.nim b/compiler/cgen.nim index 0011cb90e3..5a331ae7c8 100644 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -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 diff --git a/compiler/cgmeth.nim b/compiler/cgmeth.nim index 2cd5f36722..3bbe7b3f4a 100644 --- a/compiler/cgmeth.nim +++ b/compiler/cgmeth.nim @@ -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.. 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): diff --git a/compiler/sem.nim b/compiler/sem.nim index 960fc9165b..0dcc66432a 100644 --- a/compiler/sem.nim +++ b/compiler/sem.nim @@ -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 diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim index 54f46fa73c..e010bf1799 100644 --- a/compiler/sempass2.nim +++ b/compiler/sempass2.nim @@ -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) diff --git a/compiler/vtables.nim b/compiler/vtables.nim new file mode 100644 index 0000000000..f57b59eaef --- /dev/null +++ b/compiler/vtables.nim @@ -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..= 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..= 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.. 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() diff --git a/tests/method/tvtable.nim b/tests/method/tvtable.nim new file mode 100644 index 0000000000..8d98dd42c3 --- /dev/null +++ b/tests/method/tvtable.nim @@ -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() \ No newline at end of file