mirror of
https://github.com/nim-lang/Nim.git
synced 2026-06-14 15:43:45 +00:00
progress
This commit is contained in:
@@ -801,7 +801,10 @@ proc writeOp(w: var Writer; content: var TokenBuf; op: LogEntry) =
|
||||
content.add symToken(pool.syms.getOrIncl(w.toNifSymName(op.sym)), NoLineInfo)
|
||||
content.addParRi()
|
||||
of MethodEntry:
|
||||
discard "to implement"
|
||||
content.addParLe repMethodTag, NoLineInfo
|
||||
content.add strToken(pool.strings.getOrIncl(op.key), NoLineInfo)
|
||||
content.add symToken(pool.syms.getOrIncl(w.toNifSymName(op.sym)), NoLineInfo)
|
||||
content.addParRi()
|
||||
of EnumToStrEntry:
|
||||
content.addParLe repEnumToStrTag, NoLineInfo
|
||||
content.add strToken(pool.strings.getOrIncl(op.key), NoLineInfo)
|
||||
|
||||
@@ -1368,26 +1368,18 @@ proc genProcBody(p: BProc; procBody: PNode) =
|
||||
p.blocks[0].sections[cpsInit].addAssignmentWithValue("nimErr_"):
|
||||
p.blocks[0].sections[cpsInit].addCall(cgsymValue(p.module, "nimErrorFlag"))
|
||||
|
||||
proc findMainBModule(g: BModuleList): BModule =
|
||||
result = nil
|
||||
for cand in g.mods:
|
||||
if cand != nil and cand.module != nil and sfMainModule in cand.module.flags:
|
||||
return cand
|
||||
|
||||
proc genProcLvl3*(m: BModule, prc: PSym) =
|
||||
if m.config.cmd == cmdNifC:
|
||||
fillBackendName(m, prc)
|
||||
if sfDispatcher in prc.flags and sfMainModule notin m.module.flags:
|
||||
# A method dispatcher enumerates the whole program's method set; a
|
||||
# definition inside a reusable TU goes stale as soon as a method is
|
||||
# added in an unrelated module. Route every dispatcher definition
|
||||
# into the main TU, which is regenerated on every run.
|
||||
# A method dispatcher enumerates the whole program's method set: its
|
||||
# body is synthesized by `generateIfMethodDispatchers` only after all
|
||||
# modules have been generated, and its single definition is emitted
|
||||
# into the main TU by `finishModule` (main is finished last and never
|
||||
# reused, so the definition can never go stale inside a cached TU).
|
||||
# Any demand before that point yields a prototype.
|
||||
genProcPrototype(m, prc)
|
||||
let mainMod = findMainBModule(m.g)
|
||||
if mainMod != nil:
|
||||
if not containsOrIncl(mainMod.declaredThings, prc.id):
|
||||
genProcLvl3(mainMod, prc)
|
||||
return
|
||||
return
|
||||
# inline procs are emitted into every using TU; they are never shared
|
||||
# across translation units, so cached/cross-TU dedup must not touch
|
||||
# them. Dispatchers always (re)define in main, never from the cache.
|
||||
@@ -1398,18 +1390,19 @@ proc genProcLvl3*(m: BModule, prc: PSym) =
|
||||
# already defined inside a reused TU from the previous run
|
||||
genProcPrototype(m, prc)
|
||||
return
|
||||
if isSharedInstanceCName(m, prc) or
|
||||
prc.itemId.module != m.module.position:
|
||||
# one definition program-wide: shared instances by design; otherwise a
|
||||
# definition redirected away from a reused TU — the first claimant's
|
||||
# TU embeds it, everyone else declares it. The claim records the TU
|
||||
# as well: with redirects the same symbol can be demanded into
|
||||
# several TUs.
|
||||
let claim = (sym: prc.itemId, tu: m.module.position)
|
||||
if m.g.graph.icSharedDefOwner.hasKeyOrPut(key, claim) and
|
||||
m.g.graph.icSharedDefOwner[key] != claim:
|
||||
genProcPrototype(m, prc)
|
||||
return
|
||||
# one definition program-wide: the first claimant's TU embeds it,
|
||||
# everyone else declares it. The claim records the TU as well: with
|
||||
# redirects the same symbol can be demanded into several TUs. Home
|
||||
# emissions must claim too — a hook's demand routing goes through the
|
||||
# type-owner module (`findPendingModule` walks `s.owner`) while its
|
||||
# eager emission uses the announcing module's TU; when the owner TU
|
||||
# is reused, the very same symbol reaches this point through both
|
||||
# paths and only the registry serializes them.
|
||||
let claim = (sym: prc.itemId, tu: m.module.position)
|
||||
if m.g.graph.icSharedDefOwner.hasKeyOrPut(key, claim) and
|
||||
m.g.graph.icSharedDefOwner[key] != claim:
|
||||
genProcPrototype(m, prc)
|
||||
return
|
||||
if prc.itemId.module != m.module.position and
|
||||
not isBackendMinted(prc.itemId) and
|
||||
(prc.typ == nil or prc.typ.callConv != ccInline) and
|
||||
@@ -2886,7 +2879,12 @@ proc finalCodegenActions*(graph: ModuleGraph; m: BModule; n: PNode) =
|
||||
|
||||
if m.g.forwardedProcs.len == 0:
|
||||
incl m.flags, objHasKidsValid
|
||||
if optMultiMethods in m.g.config.globalOptions or
|
||||
if m.config.cmd == cmdNifC:
|
||||
# nifbackend synthesizes the dispatchers between the module loop
|
||||
# and the finish loop (emitMethodDispatchers): TUs demand-created
|
||||
# by the dispatcher bodies must still reach `modulesClosed`
|
||||
discard
|
||||
elif optMultiMethods in m.g.config.globalOptions or
|
||||
m.g.config.selectedGC notin {gcArc, gcOrc, gcAtomicArc, gcYrc} or
|
||||
vtables notin m.g.config.features:
|
||||
generateIfMethodDispatchers(graph, m.idgen)
|
||||
|
||||
@@ -180,6 +180,7 @@ proc methodDef*(g: ModuleGraph; idgen: IdGenerator; s: PSym) =
|
||||
g.methods[i].methods[0] != s:
|
||||
# already exists due to forwarding definition?
|
||||
localError(g.config, s.info, "method is not a base")
|
||||
logMethodDef(g, s)
|
||||
return
|
||||
of No: discard
|
||||
of Invalid:
|
||||
@@ -191,6 +192,7 @@ proc methodDef*(g: ModuleGraph; idgen: IdGenerator; s: PSym) =
|
||||
else:
|
||||
g.bucketTable.inc(s.typ.firstParamType.skipTypes(skipPtrs).itemId)
|
||||
g.methods.add((methods: @[s], dispatcher: createDispatcher(s, g, idgen)))
|
||||
logMethodDef(g, s)
|
||||
#echo "adding ", s.info
|
||||
if witness != nil:
|
||||
localError(g.config, s.info, "invalid declaration order; cannot attach '" & s.name.s &
|
||||
|
||||
@@ -629,6 +629,11 @@ proc generateBuildFile(c: DepContext): string =
|
||||
# them — phantom outputs that re-fire the build on every rerun).
|
||||
if c.config.selectedGC != gcUnselected:
|
||||
forwardedArgs.add "--mm:" & $c.config.selectedGC
|
||||
# method dispatch semantics must match across the child processes:
|
||||
# a child compiled without --multimethods:on builds different dispatch
|
||||
# buckets (and rejects calls as ambiguous that multi-dispatch accepts)
|
||||
if optMultiMethods in c.config.globalOptions:
|
||||
forwardedArgs.add "--multimethods:on"
|
||||
|
||||
# Define nifler command
|
||||
b.addTree "cmd"
|
||||
|
||||
@@ -419,9 +419,14 @@ proc mainCommand*(graph: ModuleGraph) =
|
||||
# cmdM uses NIF files, not ROD files
|
||||
graph.config.symbolFiles = disabledSf
|
||||
setUseIc(true)
|
||||
# vtable dispatch needs a whole-program vtable layout, which the
|
||||
# per-module compilation model cannot provide (yet); methods dispatch
|
||||
# through the classic if-chain dispatchers instead
|
||||
excl conf.features, Feature.vtables
|
||||
commandCheck(graph)
|
||||
of cmdNifC:
|
||||
setUseIc(true)
|
||||
excl conf.features, Feature.vtables
|
||||
# Generate C code from NIF files
|
||||
wantMainModule(conf)
|
||||
setOutFile(conf)
|
||||
|
||||
@@ -97,6 +97,9 @@ type
|
||||
# loads (reached only through system or demand-driven codegen)
|
||||
icFileReusedCnames*: HashSet[string] # their .c paths, so demand-created
|
||||
# BModules for them never write anything
|
||||
pendingMethodReplays*: seq[PSym] # method registrations loaded under
|
||||
# `nim nifc`, bucketed only after every
|
||||
# module is loaded (`flushMethodReplays`)
|
||||
icPreserveDefs*: Table[int, seq[PSym]] # module position -> symbols whose
|
||||
# definitions the TU must keep emitting:
|
||||
# they were in its previous artifact and a
|
||||
@@ -443,6 +446,49 @@ proc addMethodToGeneric*(g: ModuleGraph; module: int; t: PType; col: int; m: PSy
|
||||
let ownerModule = if t.sym != nil: t.sym.itemId.module.int else: module
|
||||
g.opsLog.add LogEntry(kind: MethodEntry, module: ownerModule, key: key, sym: m)
|
||||
|
||||
proc logMethodDef*(g: ModuleGraph; s: PSym) =
|
||||
## Log a method registration (`cgmeth.methodDef`) so that importers and
|
||||
## the backend can rebuild the dispatch buckets (`g.methods`) from the
|
||||
## NIF replay log — the serialized method ast carries its dispatcher sym
|
||||
## at `dispatcherPos`, so replay reuses the original dispatcher that all
|
||||
## call sites reference by name (see `registerLoadedMethod`).
|
||||
if g.config.cmd in {cmdNifC, cmdM}:
|
||||
g.opsLog.add LogEntry(kind: MethodEntry, module: s.itemId.module.int,
|
||||
key: "", sym: s)
|
||||
|
||||
proc registerLoadedMethod*(g: ModuleGraph; m: PSym) =
|
||||
## Rebuild the dispatch buckets from a serialized method registration.
|
||||
## Buckets group the methods sharing a dispatcher; the dispatcher's BODY
|
||||
## does not exist in serialized form — `generateIfMethodDispatchers`
|
||||
## synthesizes it in the backend from the complete bucket.
|
||||
template dbg(msg: string) =
|
||||
when defined(icDbgMeth):
|
||||
echo "[icMeth] replay ", (if m != nil: m.name.s else: "nil"), ": ", msg
|
||||
if m == nil or sfDispatcher in m.flags: dbg "skip self/nil"; return
|
||||
if m.ast == nil or dispatcherPos >= m.ast.len:
|
||||
dbg "no dispatcherPos (len " & $(if m.ast != nil: m.ast.len else: -1) & ")"
|
||||
return
|
||||
let dn = m.ast[dispatcherPos]
|
||||
if dn == nil or dn.kind != nkSym or dn.sym == nil: dbg "empty dispatcher slot"; return
|
||||
let disp = dn.sym
|
||||
if sfDispatcher notin disp.flags: dbg "slot sym not a dispatcher"; return
|
||||
dbg "ok -> bucket of " & disp.name.s & "." & $disp.disamb
|
||||
for i in 0..<g.methods.len:
|
||||
if g.methods[i].dispatcher.itemId == disp.itemId:
|
||||
for existing in g.methods[i].methods:
|
||||
if existing.itemId == m.itemId: return
|
||||
g.methods[i].methods.add m
|
||||
return
|
||||
g.methods.add (methods: @[m], dispatcher: disp)
|
||||
|
||||
proc flushMethodReplays*(g: ModuleGraph) =
|
||||
## Builds the dispatch buckets from the method registrations collected
|
||||
## during module loading; called once every module of the program is
|
||||
## loaded (`nifbackend.generateCode`).
|
||||
for s in g.pendingMethodReplays:
|
||||
registerLoadedMethod(g, s)
|
||||
g.pendingMethodReplays.setLen 0
|
||||
|
||||
proc logGenericInstance*(g: ModuleGraph; inst: PSym) =
|
||||
## Log a generic instance so it gets written to the NIF file.
|
||||
## This is needed when generic instances are created during compile-time
|
||||
@@ -861,6 +907,20 @@ when not defined(nimKochBootstrap):
|
||||
g.loadedOps[x.op][x.key] = x.sym
|
||||
of EnumToStrEntry:
|
||||
g.loadedEnumToStringProcs[x.key] = x.sym
|
||||
of MethodEntry:
|
||||
# only `methodDef` registrations (empty key) rebuild dispatch
|
||||
# buckets; the `addMethodToGeneric` flavor (typeKey key) announces
|
||||
# the uninstantiated generic method, which must never enter a
|
||||
# bucket (methodsPerGenericType replay is still a todo).
|
||||
# Under `nim nifc` the replay is deferred: building a bucket forces
|
||||
# the method's body, and a body loaded mid `loadModuleDependencies`
|
||||
# registers modules it references in a different path context than
|
||||
# the lazy loads during codegen do (`flushMethodReplays`).
|
||||
if x.key.len == 0:
|
||||
if g.config.cmd == cmdNifC:
|
||||
g.pendingMethodReplays.add x.sym
|
||||
else:
|
||||
registerLoadedMethod(g, x.sym)
|
||||
else:
|
||||
discard
|
||||
|
||||
@@ -918,7 +978,7 @@ when not defined(nimKochBootstrap):
|
||||
of ConverterEntry:
|
||||
g.ifaces[fileIdx.int].converters.add x.sym
|
||||
of MethodEntry:
|
||||
discard "todo"
|
||||
discard "dispatch buckets already rebuilt by registerLoadedHooks"
|
||||
of GenericInstEntry:
|
||||
raiseAssert "GenericInstEntry should not be in the NIF index"
|
||||
of HookEntry, EnumToStrEntry:
|
||||
|
||||
@@ -25,6 +25,7 @@ when defined(nimPreviewSlimSystem):
|
||||
import ast, options, lineinfos, modulegraphs, cgendata, cgen,
|
||||
pathutils, extccomp, msgs, modulepaths, idents, types, ast2nif, typekeys, dce,
|
||||
cnif
|
||||
from cgmeth import generateIfMethodDispatchers
|
||||
import ic / replayer
|
||||
|
||||
proc loadModuleDependencies(g: ModuleGraph; mainFileIdx: FileIndex;
|
||||
@@ -159,7 +160,13 @@ proc enforceDefRetention(g: ModuleGraph; mainPos: int;
|
||||
# NB: reading `sym.kind` forces the lazy stub, so the kind is real
|
||||
var action = 0 # un-reuse any visibly referencing TUs
|
||||
if sym != nil:
|
||||
if sym.kind in routineKinds or sym.kind == skConst:
|
||||
if sfDispatcher in sym.flags:
|
||||
# method dispatchers are synthesized from the whole
|
||||
# program's method set and emitted into the main TU on
|
||||
# every run; re-demanding the serialized sym would emit
|
||||
# its (empty) serialized body
|
||||
action = 1
|
||||
elif sym.kind in routineKinds or sym.kind == skConst:
|
||||
# routine and const definitions exist only by demand (the
|
||||
# serialized top level holds just the eager init statements,
|
||||
# not a proc listing!), and reused TUs never demand — so
|
||||
@@ -407,15 +414,29 @@ proc finishModule(g: ModuleGraph; bmod: BModule) =
|
||||
let initStmt = newNode(nkStmtList)
|
||||
finalCodegenActions(g, bmod, initStmt)
|
||||
|
||||
# Dispatcher definitions live in the main TU only: their bodies enumerate
|
||||
# the whole program's method set, so a copy inside a reusable TU would go
|
||||
# stale when a method is added in an unrelated module (genProcLvl3 routes
|
||||
# demand-driven dispatcher definitions to main the same way). Main is
|
||||
# finished last, after all method registrations have been replayed.
|
||||
if sfMainModule in bmod.module.flags:
|
||||
for disp in getDispatchers(g):
|
||||
if not containsOrIncl(bmod.declaredThings, disp.id):
|
||||
genProcLvl3(bmod, disp)
|
||||
# NB: the method dispatchers are emitted in `emitMethodDispatchers`,
|
||||
# between the module loop and this finish loop: their bodies demand the
|
||||
# method definitions, which can in turn demand definitions from modules
|
||||
# the backend never loaded — and a TU demand-created during the LAST
|
||||
# finishModule call would miss `modulesClosed` and never be written.
|
||||
|
||||
proc emitMethodDispatchers(g: ModuleGraph) =
|
||||
## Synthesizes the method dispatcher bodies from the replayed dispatch
|
||||
## buckets (`registerLoadedMethod`) and emits their definitions into the
|
||||
## main TU. Main is regenerated on every run, so a dispatcher — whose
|
||||
## body enumerates the whole program's method set — can never go stale
|
||||
## inside a cached TU; cross-TU callers prototype it (see genProcLvl3).
|
||||
let bl = BModuleList(g.backend)
|
||||
var mainMod: BModule = nil
|
||||
for m in bl.mods:
|
||||
if m != nil and m.module != nil and sfMainModule in m.module.flags:
|
||||
mainMod = m
|
||||
break
|
||||
if mainMod == nil: return
|
||||
generateIfMethodDispatchers(g, mainMod.idgen)
|
||||
for disp in getDispatchers(g):
|
||||
if not containsOrIncl(mainMod.declaredThings, disp.id):
|
||||
genProcLvl3(mainMod, disp)
|
||||
|
||||
proc generateCodeForModule(g: ModuleGraph; precomp: PrecompiledModule) =
|
||||
## Generate C code for a single module.
|
||||
@@ -484,6 +505,8 @@ proc generateCode*(g: ModuleGraph; mainFileIdx: FileIndex) =
|
||||
# This must happen BEFORE any code generation so that hooks are loaded into loadedOps
|
||||
var nifFiles: seq[string] = @[toNifFilename(g.config, systemFileIdx)]
|
||||
let modules = loadModuleDependencies(g, mainFileIdx, nifFiles)
|
||||
# build the method dispatch buckets now that every module is loaded
|
||||
flushMethodReplays(g)
|
||||
phaseDone "load (" & $ (modules.len + 1) & " modules)"
|
||||
if modules.len == 0:
|
||||
rawMessage(g.config, errGenerated,
|
||||
@@ -531,6 +554,8 @@ proc generateCode*(g: ModuleGraph; mainFileIdx: FileIndex) =
|
||||
for m in modules:
|
||||
if not processed.containsOrIncl(m.module.position):
|
||||
generateOrReuse(m)
|
||||
|
||||
emitMethodDispatchers(g)
|
||||
phaseDone "cgen"
|
||||
|
||||
# during code generation of `main.nim` we can trigger the code generation
|
||||
|
||||
@@ -197,6 +197,29 @@ proc semOpenSym(c: PContext, n: PNode, flags: TExprFlags, expectedType: PType,
|
||||
# set symchoice node type back to None
|
||||
n.typ = newTypeS(tyNone, c)
|
||||
|
||||
proc resolveOpenSymDotRhs(c: PContext, n: PNode): PNode =
|
||||
## Resolves an `nkOpenSym` in the field position of a dot expression.
|
||||
## The dot handling (`builtinFieldAccess`, `dotTransformation`) matches on
|
||||
## the node kind of the RHS directly, so the wrapper cannot be left for
|
||||
## `semExpr` to unwrap; without this the captured symbol degrades to a
|
||||
## plain identifier that is then only looked up in the instantiation
|
||||
## context. Mirrors `semOpenSym`: a symbol injected during instantiation
|
||||
## under the current proc replaces the captured symbol, otherwise the
|
||||
## captured node is used.
|
||||
let inner = n[0]
|
||||
result = inner
|
||||
if inner.kind != nkSym: return
|
||||
let id = newIdentNode(inner.sym.name, n.info)
|
||||
c.isAmbiguous = false
|
||||
let s2 = qualifiedLookUp(c, id, {})
|
||||
if s2 != nil and not c.isAmbiguous and s2 != inner.sym:
|
||||
# only consider symbols defined under the current proc:
|
||||
var o = s2.owner
|
||||
while o != nil:
|
||||
if o == c.p.owner:
|
||||
return id
|
||||
o = o.owner
|
||||
|
||||
proc semSymChoice(c: PContext, n: PNode, flags: TExprFlags = {}, expectedType: PType = nil): PNode =
|
||||
if n.kind == nkOpenSymChoice:
|
||||
result = semOpenSym(c, n, flags, expectedType,
|
||||
@@ -1524,6 +1547,9 @@ proc builtinFieldAccess(c: PContext; n: PNode; flags: var TExprFlags): PNode =
|
||||
suggestExpr(c, n)
|
||||
if exactEquals(c.config.m.trackPos, n[1].info): suggestExprNoCheck(c, n)
|
||||
|
||||
if n[1].kind == nkOpenSym:
|
||||
n[1] = resolveOpenSymDotRhs(c, n[1])
|
||||
|
||||
var s = qualifiedLookUp(c, n, {checkAmbiguity, checkUndeclared, checkModule})
|
||||
if s != nil:
|
||||
if s.kind in OverloadableSyms:
|
||||
|
||||
Reference in New Issue
Block a user