Instantiates generics in the module that uses it (#22513)

Attempts to move the generic instantiation to the module that uses it.
This should decrease re-compilation times as the source module where the
generic lives doesnt need to be recompiled

---------

Co-authored-by: ringabout <43030857+ringabout@users.noreply.github.com>
Co-authored-by: Andreas Rumpf <rumpf_a@web.de>
This commit is contained in:
Juan M Gómez
2023-09-09 09:34:20 +01:00
committed by GitHub
parent 5f13e15e0a
commit e6ca13ec85
13 changed files with 52 additions and 8 deletions

View File

@@ -936,6 +936,7 @@ type
# it won't cause problems
# for skModule the string literal to output for
# deprecated modules.
instantiatedFrom*: PSym # for instances, the generic symbol where it came from.
when defined(nimsuggest):
allUsages*: seq[TLineInfo]
@@ -1936,7 +1937,7 @@ proc skipGenericOwner*(s: PSym): PSym =
## Generic instantiations are owned by their originating generic
## symbol. This proc skips such owners and goes straight to the owner
## of the generic itself (the module or the enclosing proc).
result = if s.kind in skProcKinds and sfFromGeneric in s.flags:
result = if s.kind in skProcKinds and sfFromGeneric in s.flags and s.owner.kind != skModule:
s.owner.owner
else:
s.owner

View File

@@ -783,7 +783,10 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
if conf.backend == backendJs or conf.cmd == cmdNimscript: discard
else: processOnOffSwitchG(conf, {optThreads}, arg, pass, info)
#if optThreads in conf.globalOptions: conf.setNote(warnGcUnsafe)
of "tlsemulation": processOnOffSwitchG(conf, {optTlsEmulation}, arg, pass, info)
of "tlsemulation":
processOnOffSwitchG(conf, {optTlsEmulation}, arg, pass, info)
if optTlsEmulation in conf.globalOptions:
conf.legacyFeatures.incl emitGenerics
of "implicitstatic":
processOnOffSwitch(conf, {optImplicitStatic}, arg, pass, info)
of "patterns", "trmacros":

View File

@@ -413,6 +413,7 @@ proc storeSym*(s: PSym; c: var PackedEncoder; m: var PackedModule): PackedItemId
p.annex = toPackedLib(s.annex, c, m)
when hasFFI:
p.cname = toLitId(s.cname, m)
p.instantiatedFrom = s.instantiatedFrom.safeItemId(c, m)
# fill the reserved slot, nothing else:
m.syms[s.itemId.item] = p
@@ -876,6 +877,7 @@ proc symBodyFromPacked(c: var PackedDecoder; g: var PackedModuleGraph;
if externalName != "":
result.loc.r = rope externalName
result.loc.flags = s.locFlags
result.instantiatedFrom = loadSym(c, g, si, s.instantiatedFrom)
proc loadSym(c: var PackedDecoder; g: var PackedModuleGraph; thisModule: int; s: PackedItemId): PSym =
if s == nilItemId:

View File

@@ -71,6 +71,7 @@ type
when hasFFI:
cname*: LitId
constraint*: NodeId
instantiatedFrom*: PackedItemId
PackedType* = object
kind*: TTypeKind

View File

@@ -235,6 +235,9 @@ type
laxEffects
## Lax effects system prior to Nim 2.0.
verboseTypeMismatch
emitGenerics
## generics are emitted in the module that contains them.
## Useful for libraries that rely on local passC
SymbolFilesOption* = enum
disabledSf, writeOnlySf, readOnlySf, v2Sf, stressTest

View File

@@ -1129,6 +1129,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
of wLocalPassc:
assert sym != nil and sym.kind == skModule
let s = expectStrLit(c, it)
appendToModule(sym, n)
extccomp.addLocalCompileOption(c.config, s, toFullPathConsiderDirty(c.config, sym.info.fileIndex))
recordPragma(c, it, "localpassl", s)
of wPush:

View File

@@ -19,7 +19,8 @@ import
intsets, transf, vmdef, vm, aliases, cgmeth, lambdalifting,
evaltempl, patterns, parampatterns, sempass2, linter, semmacrosanity,
lowerings, plugins/active, lineinfos, strtabs, int128,
isolation_check, typeallowed, modulegraphs, enumtostr, concepts, astmsgs
isolation_check, typeallowed, modulegraphs, enumtostr, concepts, astmsgs,
extccomp
when not defined(leanCompiler):

View File

@@ -2543,7 +2543,7 @@ proc semMagic(c: PContext, n: PNode, s: PSym, flags: TExprFlags; expectedType: P
if n[0].kind == nkSym and sfFromGeneric in n[0].sym.flags:
# may have been resolved to `@`[empty] at some point,
# reset to `@` to deal with this
n[0] = newSymNode(n[0].sym.owner, n[0].info)
n[0] = newSymNode(n[0].sym.instantiatedFrom, n[0].info)
n[1] = semExpr(c, n[1], flags, arrayType)
result = semDirectOp(c, n, flags, expectedType)
else:

View File

@@ -313,6 +313,17 @@ proc fillMixinScope(c: PContext) =
addSym(c.currentScope, n.sym)
p = p.next
proc getLocalPassC(c: PContext, s: PSym): string =
if s.ast == nil or s.ast.len == 0: return ""
result = ""
template extractPassc(p: PNode) =
if p.kind == nkPragma and p[0][0].ident == c.cache.getIdent"localpassc":
return p[0][1].strVal
extractPassc(s.ast[0]) #it is set via appendToModule in pragmas (fast access)
for n in s.ast:
for p in n:
extractPassc(p)
proc generateInstance(c: PContext, fn: PSym, pt: TIdTable,
info: TLineInfo): PSym =
## Generates a new instance of a generic procedure.
@@ -328,14 +339,22 @@ proc generateInstance(c: PContext, fn: PSym, pt: TIdTable,
var n = copyTree(fn.ast)
# NOTE: for access of private fields within generics from a different module
# we set the friend module:
c.friendModules.add(getModule(fn))
let producer = getModule(fn)
c.friendModules.add(producer)
let oldMatchedConcept = c.matchedConcept
c.matchedConcept = nil
let oldScope = c.currentScope
while not isTopLevel(c): c.currentScope = c.currentScope.parent
result = copySym(fn, c.idgen)
incl(result.flags, sfFromGeneric)
result.owner = fn
result.instantiatedFrom = fn
if sfGlobal in result.flags and c.config.symbolFiles != disabledSf:
let passc = getLocalPassC(c, producer)
if passc != "": #pass the local compiler options to the consumer module too
extccomp.addLocalCompileOption(c.config, passc, toFullPathConsiderDirty(c.config, c.module.info.fileIndex))
result.owner = c.module
else:
result.owner = fn
result.ast = n
pushOwner(c, result)

View File

@@ -502,6 +502,8 @@ proc semNewFinalize(c: PContext; n: PNode): PNode =
getAttachedOp(c.graph, t, attachedDestructor).owner == fin:
discard "already turned this one into a finalizer"
else:
if fin.instantiatedFrom != nil and fin.instantiatedFrom != fin.owner: #undo move
fin.owner = fin.instantiatedFrom
let wrapperSym = newSym(skProc, getIdent(c.graph.cache, fin.name.s & "FinalizerWrapper"), c.idgen, fin.owner, fin.info)
let selfSymNode = newSymNode(copySym(fin.ast[paramsPos][1][0].sym, c.idgen))
selfSymNode.typ = fin.typ[1]

View File

@@ -1313,7 +1313,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
if a.kind == nkSym and a.sym.kind in skProcKinds and
b.kind == nkSym and b.sym.kind in skProcKinds:
regs[ra].intVal =
if sfFromGeneric in a.sym.flags and a.sym.owner == b.sym: 1
if sfFromGeneric in a.sym.flags and a.sym.instantiatedFrom == b.sym: 1
else: 0
else:
stackTrace(c, tos, pc, "node is not a proc symbol")

View File

@@ -2098,7 +2098,7 @@ proc toKey(s: PSym): string =
result.add s.name.s
if s.owner != nil:
if sfFromGeneric in s.flags:
s = s.owner.owner
s = s.instantiatedFrom.owner
else:
s = s.owner
result.add "."

11
tests/ic/tgenericinst.nim Normal file
View File

@@ -0,0 +1,11 @@
discard """
cmd: "nim cpp --incremental:on $file"
"""
{.emit:"""/*TYPESECTION*/
#include <iostream>
struct Foo { };
""".}
type Foo {.importcpp.} = object
echo $Foo() #Notice the generic is instantiate in the this module if not, it wouldnt find Foo