mirror of
https://github.com/nim-lang/Nim.git
synced 2026-06-14 23:53:47 +00:00
342 lines
12 KiB
Nim
342 lines
12 KiB
Nim
#
|
||
#
|
||
# The Nim Compiler
|
||
# (c) Copyright 2025 Andreas Rumpf
|
||
#
|
||
# See the file "copying.txt", included in this
|
||
# distribution, for details about the copyright.
|
||
#
|
||
|
||
## Based on sighashes.nim but works on astdef directly as we need it in ast2nif.nim.
|
||
## Also produces more readable names thanks to treemangler.
|
||
|
||
import std/[assertions, sets]
|
||
|
||
import "../dist/nimony/src/lib" / [treemangler]
|
||
import "../dist/nimony/src/gear2" / modnames
|
||
|
||
import astdef, idents, options, lineinfos, msgs
|
||
import ic / [enum2nif]
|
||
|
||
# -------------- Module name handling --------------------------------------------
|
||
|
||
proc cachedModuleSuffix*(config: ConfigRef; fileIdx: FileIndex): string =
|
||
## Gets or computes the module suffix for a FileIndex.
|
||
## For NIF modules, the suffix is already stored in the file info.
|
||
## For source files, computes it from the path.
|
||
let fullPath = toFullPath(config, fileIdx)
|
||
if fileInfoKind(config, fileIdx) == fikNifModule:
|
||
result = fullPath # Already a suffix
|
||
else:
|
||
result = moduleSuffix(fullPath, cast[seq[string]](config.searchPaths))
|
||
|
||
proc modname*(module: int; conf: ConfigRef): string =
|
||
cachedModuleSuffix(conf, module.FileIndex)
|
||
|
||
proc modname*(module: PSym; conf: ConfigRef): string =
|
||
assert module.kindImpl == skModule
|
||
modname(module.positionImpl, conf)
|
||
|
||
# --------------- Type key generation --------------------------------------------
|
||
|
||
type
|
||
ConsiderFlag = enum
|
||
CoProc
|
||
CoType
|
||
CoIgnoreRange
|
||
CoConsiderOwned
|
||
CoDistinct
|
||
CoHashTypeInsideNode
|
||
|
||
TypeLoader* = proc (t: PType) {.nimcall.}
|
||
SymLoader* = proc (s: PSym) {.nimcall.}
|
||
Context = object
|
||
m: Mangler
|
||
tl: TypeLoader
|
||
sl: SymLoader
|
||
visited: HashSet[ItemId] # anonymous object types whose fields are currently
|
||
# being hashed — a non-mutating guard against endless
|
||
# recursion when a field references the type itself.
|
||
|
||
proc typeKey(c: var Context; t: PType; flags: set[ConsiderFlag]; conf: ConfigRef)
|
||
proc symKey(c: var Context; s: PSym; conf: ConfigRef) =
|
||
if s.state == Partial:
|
||
assert c.sl != nil
|
||
c.sl(s)
|
||
if sfAnon in s.flagsImpl or s.kindImpl == skGenericParam:
|
||
c.m.addIdent("´anon")
|
||
else:
|
||
var name = s.name.s
|
||
name.add '.'
|
||
name.addInt s.disamb
|
||
|
||
let it =
|
||
if s.kindImpl == skModule:
|
||
s
|
||
elif s.kindImpl in skProcKinds and sfFromGeneric in s.flagsImpl and s.ownerFieldImpl.kindImpl != skModule:
|
||
s.ownerFieldImpl.ownerFieldImpl
|
||
else:
|
||
s.ownerFieldImpl
|
||
if it.kindImpl == skModule:
|
||
name.add '.'
|
||
name.add modname(it, conf)
|
||
c.m.addSymbol(name)
|
||
|
||
proc treeKey(c: var Context; n: PNode; flags: set[ConsiderFlag]; conf: ConfigRef) =
|
||
if n == nil:
|
||
c.m.addEmpty()
|
||
return
|
||
|
||
let k = n.kind
|
||
case k
|
||
of nkEmpty, nkNilLit, nkType: discard
|
||
of nkIdent:
|
||
c.m.addIdent(n.ident.s)
|
||
of nkSym:
|
||
symKey(c, n.sym, conf)
|
||
if CoHashTypeInsideNode in flags and n.sym.typImpl != nil:
|
||
typeKey(c, n.sym.typImpl, flags, conf)
|
||
of nkCharLit..nkUInt64Lit:
|
||
let v = n.intVal
|
||
c.m.addIntLit v
|
||
of nkFloatLit..nkFloat64Lit:
|
||
let v = n.floatVal
|
||
c.m.addFloatLit v
|
||
of nkStrLit..nkTripleStrLit:
|
||
c.m.addStrLit n.strVal
|
||
else:
|
||
withTree c.m, toNifTag(k):
|
||
for i in 0..<n.len: treeKey(c, n[i], flags, conf)
|
||
|
||
proc skipModifierB(n: PType): PType {.inline.} =
|
||
n.sonsImpl[^1]
|
||
|
||
proc skipTypesB(t: PType, kinds: TTypeKinds): PType =
|
||
result = t
|
||
while result.kind in kinds: result = result.sonsImpl[^1]
|
||
|
||
proc isGenericAlias(t: PType): bool =
|
||
result = t.kind == tyGenericInst and t.skipModifierB.skipTypesB({tyAlias}).kind == tyGenericInst
|
||
|
||
proc skipGenericAlias(t: PType): PType =
|
||
result = t.skipTypesB({tyAlias})
|
||
if result.isGenericAlias:
|
||
result = result.skipModifierB.skipTypesB({tyAlias})
|
||
|
||
proc maybeImported(c: var Context; s: PSym; conf: ConfigRef) {.inline.} =
|
||
if s != nil and {sfImportc, sfExportc} * s.flagsImpl != {}:
|
||
c.symKey(s, conf)
|
||
|
||
proc typeKey(c: var Context; t: PType; flags: set[ConsiderFlag]; conf: ConfigRef) =
|
||
if t == nil:
|
||
c.m.addEmpty()
|
||
return
|
||
|
||
if t.state == Partial:
|
||
assert c.tl != nil
|
||
c.tl(t)
|
||
|
||
case t.kind
|
||
of tyGenericInvocation:
|
||
for a in t.sonsImpl:
|
||
c.typeKey a, flags, conf
|
||
of tyDistinct:
|
||
if CoDistinct in flags:
|
||
if t.symImpl != nil: symKey(c, t.symImpl, conf)
|
||
if t.symImpl == nil or tfFromGeneric in t.flagsImpl:
|
||
c.typeKey t.sonsImpl[^1], flags, conf
|
||
elif CoType in flags or t.symImpl == nil:
|
||
c.typeKey t.sonsImpl[^1], flags, conf
|
||
else:
|
||
symKey(c, t.symImpl, conf)
|
||
of tyGenericInst:
|
||
# The generic head (son[0]) may be a lazily-loaded stub under IC; ensure it
|
||
# is materialised before peeking at its symbol. A nil sym means this is not
|
||
# an imported C++ generic, so fall through to the normal `skipModifierB`.
|
||
var base = t.sonsImpl[0]
|
||
if base.state == Partial:
|
||
assert c.tl != nil
|
||
c.tl(base)
|
||
if base.symImpl != nil and sfInfixCall in base.symImpl.flagsImpl:
|
||
# This is an imported C++ generic type.
|
||
# We cannot trust the `lastSon` to hold a properly populated and unique
|
||
# value for each instantiation, so we hash the generic parameters here:
|
||
let normalizedType = t.skipGenericAlias
|
||
c.typeKey normalizedType.sonsImpl[0], flags, conf
|
||
for i in 1..<t.sonsImpl.len-1:
|
||
c.typeKey t.sonsImpl[i], flags, conf
|
||
else:
|
||
c.typeKey t.skipModifierB, flags, conf
|
||
of tyAlias, tySink, tyUserTypeClasses, tyInferred:
|
||
c.typeKey t.skipModifierB, flags, conf
|
||
of tyOwned:
|
||
if CoConsiderOwned in flags:
|
||
withTree c.m, toNifTag(t.kind):
|
||
c.typeKey t.skipModifierB, flags, conf
|
||
else:
|
||
c.typeKey t.skipModifierB, flags, conf
|
||
of tyBool:
|
||
withTree c.m, "bool":
|
||
maybeImported(c, t.symImpl, conf)
|
||
of tyChar:
|
||
withTree c.m, "c":
|
||
c.m.addIntLit 8 # char is always 8 bits
|
||
maybeImported(c, t.symImpl, conf)
|
||
of tyInt:
|
||
withTree c.m, "i":
|
||
c.m.addIntLit -1
|
||
maybeImported(c, t.symImpl, conf)
|
||
of tyInt8:
|
||
withTree c.m, "i":
|
||
c.m.addIntLit 8
|
||
maybeImported(c, t.symImpl, conf)
|
||
of tyInt16:
|
||
withTree c.m, "i":
|
||
c.m.addIntLit 16
|
||
maybeImported(c, t.symImpl, conf)
|
||
of tyInt32:
|
||
withTree c.m, "i":
|
||
c.m.addIntLit 32
|
||
maybeImported(c, t.symImpl, conf)
|
||
of tyInt64:
|
||
withTree c.m, "i":
|
||
c.m.addIntLit 64
|
||
maybeImported(c, t.symImpl, conf)
|
||
of tyUInt:
|
||
withTree c.m, "u":
|
||
c.m.addIntLit -1
|
||
maybeImported(c, t.symImpl, conf)
|
||
of tyUInt8:
|
||
withTree c.m, "u":
|
||
c.m.addIntLit 8
|
||
maybeImported(c, t.symImpl, conf)
|
||
of tyUInt16:
|
||
withTree c.m, "u":
|
||
c.m.addIntLit 16
|
||
maybeImported(c, t.symImpl, conf)
|
||
of tyUInt32:
|
||
withTree c.m, "u":
|
||
c.m.addIntLit 32
|
||
maybeImported(c, t.symImpl, conf)
|
||
of tyUInt64:
|
||
withTree c.m, "u":
|
||
c.m.addIntLit 64
|
||
maybeImported(c, t.symImpl, conf)
|
||
of tyObject, tyEnum:
|
||
if t.typeInstImpl != nil:
|
||
# prevent against infinite recursions here, see bug #8883:
|
||
let inst = t.typeInstImpl
|
||
t.typeInstImpl = nil # IC: spurious writes are ok since we set it back immediately
|
||
assert inst.kind == tyGenericInst
|
||
c.typeKey inst.sonsImpl[0], flags, conf
|
||
for i in 1..<inst.sonsImpl.len-1:
|
||
# Match sighashes: generic-instantiation arguments are keyed with
|
||
# `CoDistinct` so distinct args are not collapsed to their base.
|
||
c.typeKey inst.sonsImpl[i], flags+{CoDistinct}, conf
|
||
t.typeInstImpl = inst
|
||
elif t.symImpl != nil:
|
||
c.symKey(t.symImpl, conf)
|
||
# Anonymous / gensym'd object types (e.g. closure environments and
|
||
# `ref object` ObjectTypes) share the placeholder name `´anon`, so `symKey`
|
||
# alone collapses every one of them onto the same key — which made distinct
|
||
# closure-env `=destroy`/`=sink` hooks collide. Mirror sighashes: when the
|
||
# type symbol is anonymous/gensym'd, disambiguate further by keying the
|
||
# field types and names (or `.empty` when there are none).
|
||
template hasFlag(sym: PSym): bool =
|
||
{sfAnon, sfGenSym} * sym.flagsImpl != {}
|
||
if hasFlag(t.symImpl) or
|
||
(t.kind == tyObject and t.ownerFieldImpl != nil and t.ownerFieldImpl.kindImpl == skType and
|
||
t.ownerFieldImpl.typImpl != nil and t.ownerFieldImpl.typImpl.kind == tyRef and hasFlag(t.ownerFieldImpl)):
|
||
if t.nImpl != nil and t.nImpl.len > 0:
|
||
# Guard against endless recursion when a field references this type
|
||
# itself. Unlike sighashes (which temporarily clears `sfAnon`/`sfGenSym`
|
||
# on the symbol), do NOT mutate: `typeKey` runs during sem — it is
|
||
# called unconditionally from `modulegraphs.setAttachedOp` — so a
|
||
# mutation that an assertion deeper in `treeKey` left unrestored would
|
||
# corrupt the type. `symKey` above already emitted the type's identity,
|
||
# so on a back-reference we simply stop.
|
||
if not containsOrIncl(c.visited, t.itemId):
|
||
c.treeKey(t.nImpl, flags + {CoHashTypeInsideNode}, conf)
|
||
c.visited.excl t.itemId
|
||
else:
|
||
c.m.addIdent "´empty"
|
||
# Object inheritance is part of identity: key the base class too.
|
||
if t.kind == tyObject and t.sonsImpl.len > 0 and t.sonsImpl[0] != nil:
|
||
c.typeKey t.sonsImpl[0], flags, conf
|
||
else:
|
||
c.m.addIdent "`bug"
|
||
of tyFromExpr:
|
||
withTree c.m, toNifTag(t.kind):
|
||
c.treeKey(t.nImpl, flags, conf)
|
||
of tyTuple:
|
||
withTree c.m, toNifTag(t.kind):
|
||
if t.nImpl != nil and CoType notin flags:
|
||
for i in 0..<t.nImpl.len:
|
||
withTree c.m, "kv":
|
||
assert(t.nImpl[i].kind == nkSym)
|
||
c.symKey(t.nImpl[i].sym, conf)
|
||
c.typeKey(t.nImpl[i].sym.typImpl, flags+{CoIgnoreRange}, conf)
|
||
else:
|
||
for i in 1..<t.sonsImpl.len:
|
||
c.typeKey t.sonsImpl[i], flags+{CoIgnoreRange}, conf
|
||
of tyRange:
|
||
if CoIgnoreRange notin flags:
|
||
withTree c.m, toNifTag(t.kind):
|
||
c.treeKey(t.nImpl, {}, conf)
|
||
c.typeKey(t.sonsImpl[^1], flags, conf)
|
||
else:
|
||
c.typeKey(t.sonsImpl[^1], flags, conf)
|
||
of tyStatic:
|
||
withTree c.m, toNifTag(t.kind):
|
||
c.treeKey(t.nImpl, {}, conf)
|
||
if t.sonsImpl.len > 0:
|
||
c.typeKey(t.skipModifierB, flags, conf)
|
||
of tyProc:
|
||
withTree c.m, (if tfIterator in t.flagsImpl: "itertype" else: "proctype"):
|
||
# Proc parameter *types* are part of the type's identity. Under IC the
|
||
# parameters live in `nImpl` (`sonsImpl` holds only the return type), so a
|
||
# loaded proc type has an empty `sonsImpl[1..]`; reading params from there
|
||
# would silently drop them and collide every same-return/same-callconv
|
||
# closure onto one key (e.g. `proc(cb: proc())` onto bare `proc()`),
|
||
# which made hook lookup resolve to the wrong `=copy`. Prefer `nImpl`
|
||
# (consistent in-memory and after load); hash param types only, not their
|
||
# symbols — parameter names do not affect type identity.
|
||
if t.nImpl != nil and t.nImpl.kind == nkFormalParams:
|
||
let params = t.nImpl
|
||
for i in 1..<params.len:
|
||
if params[i].kind == nkSym:
|
||
# The param sym may be a lazily-loaded stub: force it in (as `symKey`
|
||
# does) so its type is available, then hash the param *type* only —
|
||
# parameter names are not part of the type's identity. Without the
|
||
# load the type reads back nil at codegen and the key silently loses
|
||
# its parameters (collapsing distinct closure types onto one key).
|
||
let ps = params[i].sym
|
||
if ps.state == Partial and c.sl != nil: c.sl(ps)
|
||
c.typeKey(ps.typImpl, flags, conf)
|
||
else:
|
||
c.typeKey(params[i].typField, flags, conf)
|
||
else:
|
||
for i in 1..<t.sonsImpl.len:
|
||
c.typeKey(t.sonsImpl[i], flags, conf)
|
||
if t.sonsImpl.len > 0:
|
||
c.typeKey(t.sonsImpl[0], flags, conf)
|
||
|
||
c.m.addIdent toNifTag(t.callConvImpl)
|
||
if tfVarargs in t.flagsImpl: c.m.addIdent "´varargs"
|
||
of tyArray:
|
||
withTree c.m, toNifTag(t.kind):
|
||
c.typeKey(t.sonsImpl[^1], flags-{CoIgnoreRange}, conf)
|
||
c.typeKey(t.sonsImpl[0], flags-{CoIgnoreRange}, conf)
|
||
else:
|
||
withTree c.m, toNifTag(t.kind):
|
||
for i in 0..<t.sonsImpl.len:
|
||
c.typeKey t.sonsImpl[i], flags, conf
|
||
if tfNotNil in t.flagsImpl and CoType notin flags:
|
||
c.m.addIdent "´notnil"
|
||
|
||
proc typeKey*(t: PType; conf: ConfigRef; tl: TypeLoader; sl: SymLoader): string =
|
||
var c: Context = Context(m: createMangler(30, -1), tl: tl, sl: sl,
|
||
visited: initHashSet[ItemId]())
|
||
typeKey(c, t, {}, conf)
|
||
result = c.m.extract()
|