IC: bugfixes

This commit is contained in:
Araq
2026-06-15 16:13:49 +02:00
parent df9d5d0b2f
commit a77dcb16fc
5 changed files with 108 additions and 2 deletions

View File

@@ -149,6 +149,17 @@ proc hashType(c: var MD5Context, t: PType; flags: set[ConsiderFlag]; conf: Confi
# as properties are accessed and trigger lazy loading.
backendEnsureMutable(t)
# Bare type-class keywords used as a typedesc without arguments (e.g. `array`,
# `range`, `distinct` passed to `signatureHash`) have no children, so the
# structural branches below would index a non-existent `elementType`. Hash them
# by kind (+ sym for an extra, stable distinction) — enough for a stable,
# distinct identity. (`seq`/`openArray`/`tuple` already fall through the empty
# `else` loop unharmed; this covers the branches that index `elementType`.)
if t.kind in {tyArray, tyRange, tyDistinct} and not t.hasElementType:
c &= char(t.kind)
if t.sym != nil: c.hashSym(t.sym)
return
case t.kind
of tyGenericInvocation:
for a in t.kids:

View File

@@ -1955,7 +1955,21 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
if regs[rb].node.kind != nkSym:
stackTrace(c, tos, pc, "node is not a symbol")
else:
regs[ra].node.strVal = $sigHash(regs[rb].node.sym, c.config)
let shSym = regs[rb].node.sym
# When `signatureHash` is applied to a type (e.g. a `T: typedesc`/generic
# param), hash the *type* it denotes, not the parameter symbol. Hashing the
# symbol routes through `hashNonProc`, which mixes in `s.disamb` — a
# per-module instantiation counter. Under incremental compilation the
# registering module and a consuming module instantiate the surrounding
# generic separately, get different `disamb`s, and produce different
# hashes for the same type (nim-serialization's auto-serialization lookup
# missed because of this). Hashing the underlying type via `hashType` is
# type-identity based and stable across the NIF boundary.
let shTyp = shSym.typ
if shTyp != nil and shTyp.kind == tyTypeDesc and shTyp.hasElementType:
regs[ra].node.strVal = $hashType(shTyp.elementType, c.config)
else:
regs[ra].node.strVal = $sigHash(shSym, c.config)
of opcSlurp:
decodeB(rkNode)
createStr regs[ra]

View File

@@ -617,7 +617,7 @@ proc runIcTestFile(inp: string) =
# on a sibling helper (`timp` -> `myimp`, `tcompiletimeglobal` -> `mctglobal`),
# which exercises the NIF import/load path the single-file tests do not.
const icSuite = ["thallo", "tconverter", "timp", "tmiscs", "tparseutils",
"tcompiletimeglobal"]
"tcompiletimeglobal", "tsighashstable"]
proc icTest(args: string) =
temp("")

View File

@@ -0,0 +1,48 @@
# Helper module for tsighashstable.nim (not a test itself; no `discard`).
#
# Models nim-serialization's auto-serialization registry: a flavor records which
# types it auto-serializes in a `std/macrocache` keyed by `signatureHash(T)`,
# computed through a generic `{.compileTime.}` func. The registration happens
# here (at this module's compile time); the lookup happens in the importer.
#
# Under `nim ic` the two modules are compiled separately, so the generic
# `getSig[T]` is instantiated independently on each side. `signatureHash` must
# therefore hash the *type* `T` denotes, not the generic parameter symbol — the
# latter mixes in a per-module `disamb` counter and diverges across the NIF
# boundary, making the lookup miss.
import std/[macrocache, macros, typetraits]
type DefaultFlavor* = object
macro calcSig*(T: typed): untyped =
doAssert(T.typeKind == ntyTypeDesc)
result = newLit(signatureHash(T))
func getSig*(F: type DefaultFlavor, T: distinct type): string {.compileTime.} =
calcSig(T)
func getTable*(F: type DefaultFlavor): CacheTable {.compileTime.} =
CacheTable("nsrzStableTable" & typetraits.name(F))
func setAuto*(F: type DefaultFlavor, T: distinct type) {.compileTime.} =
let sig = F.getSig(T)
let table = F.getTable()
if not table.hasKey(sig):
table[sig] = newLit(1)
func getAuto*(F: type DefaultFlavor, T: distinct type): bool {.compileTime.} =
let sig = F.getSig(T)
let table = F.getTable()
table.hasKey(sig)
template autoCheck*(F: distinct type, T: distinct type, body) =
when not F.getAuto(T):
{.error: "auto serialization not enabled for `" & typetraits.name(T) & "`".}
else:
body
static:
setAuto(DefaultFlavor, string)
setAuto(DefaultFlavor, SomeInteger)
setAuto(DefaultFlavor, seq)

View File

@@ -0,0 +1,33 @@
discard """
output: '''ok string
ok int
ok seq'''
"""
# Regression test: `signatureHash(T)` must be stable across the NIF boundary so
# that a macrocache keyed by it (nim-serialization's auto-serialization registry)
# can be populated in one module and queried from another under `nim ic`.
#
# Before the fix, `signatureHash` hashed the generic *parameter symbol* (whose
# `disamb` is a per-module instantiation counter) instead of the type it denotes.
# The registering module (msighashstable) and this importer instantiated the
# generic `getSig[T]` separately, got different `disamb`s, and the lookups for
# `string`/`SomeInteger` missed -> `{.error: auto serialization not enabled.}`.
import msighashstable
proc writeStr(F: type DefaultFlavor, v: string) =
autoCheck(F, string):
echo "ok string"
proc writeInt[T: SomeInteger](F: type DefaultFlavor, v: T) =
autoCheck(F, SomeInteger):
echo "ok int"
proc writeSeq[T](F: type DefaultFlavor, v: seq[T]) =
autoCheck(F, seq):
echo "ok seq"
writeStr(DefaultFlavor, "hi")
writeInt(DefaultFlavor, 42)
writeSeq(DefaultFlavor, @[1, 2, 3])