=hook and converter support

This commit is contained in:
araq
2025-12-04 21:44:51 +01:00
parent 12aa867245
commit 37962c1042
3 changed files with 157 additions and 6 deletions

View File

@@ -22,6 +22,66 @@ import "../dist/nimony/src/models" / nifindex_tags
import ic / [enum2nif]
# Re-export types needed for hook handling
export nifindexes.AttachedOp, nifindexes.HookIndexEntry, nifindexes.HooksPerType
proc toAttachedOp*(op: TTypeAttachedOp): AttachedOp =
## Maps Nim compiler's TTypeAttachedOp to nimony's AttachedOp.
## Returns attachedDestroy for attachedDeepCopy (caller should skip it).
case op
of attachedDestructor: attachedDestroy
of attachedAsgn: attachedCopy
of attachedWasMoved: nifindexes.attachedWasMoved
of attachedDup: nifindexes.attachedDup
of attachedSink: nifindexes.attachedSink
of attachedTrace: nifindexes.attachedTrace
of attachedDeepCopy: attachedDestroy # Not supported, caller should skip
proc toTTypeAttachedOp*(op: AttachedOp): TTypeAttachedOp =
## Maps nimony's AttachedOp back to Nim compiler's TTypeAttachedOp.
case op
of attachedDestroy: attachedDestructor
of attachedCopy: attachedAsgn
of nifindexes.attachedWasMoved: astdef.attachedWasMoved
of nifindexes.attachedDup: astdef.attachedDup
of nifindexes.attachedSink: astdef.attachedSink
of nifindexes.attachedTrace: astdef.attachedTrace
proc toHookIndexEntry*(config: ConfigRef; typeId: ItemId; hookSym: PSym): HookIndexEntry =
## Converts a type ItemId and hook symbol to a HookIndexEntry for the NIF index.
let typeSymName = "`t" & $typeId.item & "." & moduleSuffix(
toFullPath(config, typeId.module.FileIndex),
cast[seq[string]](config.searchPaths))
let hookSymName = hookSym.name.s & "." & $hookSym.disamb & "." & moduleSuffix(
toFullPath(config, hookSym.itemId.module.FileIndex),
cast[seq[string]](config.searchPaths))
let typSymId = pool.syms.getOrIncl(typeSymName)
let hookSymId = pool.syms.getOrIncl(hookSymName)
# Check if it's a generic hook (has non-empty generic params)
let isGeneric = hookSym.astImpl != nil and hookSym.astImpl.len > genericParamsPos and
hookSym.astImpl[genericParamsPos].kind != nkEmpty
result = HookIndexEntry(typ: typSymId, hook: hookSymId, isGeneric: isGeneric)
proc toConverterIndexEntry*(config: ConfigRef; converterSym: PSym): (nifstreams.SymId, nifstreams.SymId) =
## Converts a converter symbol to an index entry (destType, converterSym).
## Returns the destination type's SymId and the converter's SymId.
# Get the return type of the converter (destination type)
let retType = converterSym.typImpl
if retType != nil and retType.sonsImpl.len > 0:
let destType = retType.sonsImpl[0] # Return type is first son
if destType != nil:
let destTypeSymName = "`t" & $destType.itemId.item & "." & moduleSuffix(
toFullPath(config, destType.itemId.module.FileIndex),
cast[seq[string]](config.searchPaths))
let convSymName = converterSym.name.s & "." & $converterSym.disamb & "." & moduleSuffix(
toFullPath(config, converterSym.itemId.module.FileIndex),
cast[seq[string]](config.searchPaths))
result = (pool.syms.getOrIncl(destTypeSymName), pool.syms.getOrIncl(convSymName))
return
# Fallback: return empty entry
result = (nifstreams.SymId(0), nifstreams.SymId(0))
# ---------------- Line info handling -----------------------------------------
type
@@ -594,7 +654,9 @@ proc buildExportBuf(w: var Writer): TokenBuf =
result.add identToken(pool.strings.getOrIncl(name), NoLineInfo)
result.addParRi()
proc writeNifModule*(config: ConfigRef; thisModule: int32; n: PNode) =
proc writeNifModule*(config: ConfigRef; thisModule: int32; n: PNode;
hooks: array[AttachedOp, seq[HookIndexEntry]];
converters: seq[(nifstreams.SymId, nifstreams.SymId)]) =
var w = Writer(infos: LineInfoWriter(config: config), currentModule: thisModule)
var content = createTokenBuf(300)
@@ -617,10 +679,10 @@ proc writeNifModule*(config: ConfigRef; thisModule: int32; n: PNode) =
writeFile(dest, d)
# Build index with export information
# Build index with export, hook, and converter information
let exportBuf = buildExportBuf(w)
createIndex(d, dest[0].info, false,
IndexSections(exportBuf: exportBuf))
IndexSections(hooks: hooks, converters: converters, exportBuf: exportBuf))
# Don't unload symbols/types yet - they may be needed by other modules that haven't
# had their NIF files written. For recursive module dependencies (like system.nim),
@@ -1300,7 +1362,48 @@ proc toNifIndexFilename*(conf: ConfigRef; f: FileIndex): string =
let suffix = moduleSuffix(conf, f)
result = toGeneratedFile(conf, AbsoluteFile(suffix), ".s.idx.nif").string
proc loadNifModule*(c: var DecodeContext; f: FileIndex; interf, interfHidden: var TStrTable): PNode =
proc parseTypeSymIdToItemId*(c: var DecodeContext; symId: nifstreams.SymId): ItemId =
## Parses a type SymId (format: "`tN.modulesuffix") to extract ItemId.
let s = pool.syms[symId]
if not s.startsWith("`t"):
return ItemId(module: -1, item: 0)
var i = 2 # skip "`t"
var item = 0'i32
while i < s.len and s[i] in {'0'..'9'}:
item = item * 10 + int32(ord(s[i]) - ord('0'))
inc i
if i < s.len and s[i] == '.':
inc i
let suffix = s.substr(i)
let module = moduleId(c, suffix)
result = ItemId(module: int32(module), item: item)
else:
result = ItemId(module: -1, item: item)
proc resolveHookSym*(c: var DecodeContext; symId: nifstreams.SymId): PSym =
## Resolves a hook SymId to PSym.
let symAsStr = pool.syms[symId]
let sn = parseSymName(symAsStr)
if sn.module.len == 0:
return nil # Local symbols shouldn't be hooks
let module = moduleId(c, sn.module)
# Look up the symbol in the module's index
let offs = c.mods[module].index.public.getOrDefault(symAsStr)
if offs.offset == 0:
return nil
# Create a stub symbol
let val = addr c.mods[module].symCounter
inc val[]
let id = ItemId(module: int32(module), item: val[])
result = c.syms.getOrDefault(id)[0]
if result == nil:
result = PSym(itemId: id, kindImpl: skProc, name: c.cache.getIdent(sn.name),
disamb: sn.count.int32, state: Partial)
c.syms[id] = (result, offs)
proc loadNifModule*(c: var DecodeContext; f: FileIndex; interf, interfHidden: var TStrTable;
hooks: var Table[nifstreams.SymId, HooksPerType];
converters: var seq[(string, string)]): PNode =
let suffix = moduleSuffix(c.infos.config, f)
# Ensure module index is loaded - moduleId returns the FileIndex for this suffix
@@ -1310,6 +1413,11 @@ proc loadNifModule*(c: var DecodeContext; f: FileIndex; interf, interfHidden: va
# Symbols are created as stubs (Partial state) and will be loaded lazily via loadSym
populateInterfaceTablesFromIndex(c, module, interf, interfHidden, suffix)
# Return hooks from the index
hooks = move c.mods[module].index.hooks
# Return converters from the index
converters = move c.mods[module].index.converters
# Return empty statement list - actual content is loaded lazily via the index
result = newNode(nkStmtList)

View File

@@ -18,6 +18,7 @@ import ic / [packed_ast, ic]
when not defined(nimKochBootstrap):
import ast2nif
import "../dist/nimony/src/lib" / [nifstreams, bitabs]
when defined(nimPreviewSlimSystem):
import std/assertions
@@ -366,6 +367,10 @@ proc setAttachedOp*(g: ModuleGraph; module: int; t: PType; op: TTypeAttachedOp;
## we also need to record this to the packed module.
g.attachedOps[op][t.itemId] = LazySym(sym: value)
proc setAttachedOp*(g: ModuleGraph; module: int; typeId: ItemId; op: TTypeAttachedOp; value: PSym) =
## Overload that takes ItemId directly, useful for registering hooks from NIF index.
g.attachedOps[op][typeId] = LazySym(sym: value)
proc setAttachedOpPartial*(g: ModuleGraph; module: int; t: PType; op: TTypeAttachedOp; value: PSym) =
## we also need to record this to the packed module.
g.attachedOps[op][t.itemId] = LazySym(sym: value)
@@ -765,7 +770,26 @@ when not defined(nimKochBootstrap):
# Register module in graph
registerModule(g, result)
result.astImpl = loadNifModule(ast.program, fileIdx, g.ifaces[fileIdx.int].interf, g.ifaces[fileIdx.int].interfHidden)
var hooks = initTable[nifstreams.SymId, HooksPerType]()
var converters: seq[(string, string)] = @[]
result.astImpl = loadNifModule(ast.program, fileIdx, g.ifaces[fileIdx.int].interf,
g.ifaces[fileIdx.int].interfHidden, hooks, converters)
# Register hooks from NIF index with the module graph
for typSymId, hooksPerType in hooks:
let typeItemId = parseTypeSymIdToItemId(ast.program, typSymId)
if typeItemId.module >= 0:
for op in AttachedOp:
let (hookSymId, isGeneric) = hooksPerType.a[op]
if hookSymId != nifstreams.SymId(0):
let hookSym = resolveHookSym(ast.program, hookSymId)
if hookSym != nil:
setAttachedOp(g, int(fileIdx), typeItemId, toTTypeAttachedOp(op), hookSym)
# Register converters from NIF index with the module's interface
for (destType, convSym) in converters:
let symId = pool.syms.getOrIncl(convSym)
let convPSym = resolveHookSym(ast.program, symId) # reuse hook resolution
if convPSym != nil:
g.ifaces[fileIdx.int].converters.add LazySym(sym: convPSym)
cachedModules.add fileIdx
proc configComplete*(g: ModuleGraph) =

View File

@@ -5,6 +5,7 @@ import sem, cgen, modulegraphs, ast, llstream, parser, msgs,
when not defined(nimKochBootstrap):
import ast2nif
import "../dist/nimony/src/lib" / [nifstreams, bitabs]
import pipelineutils
@@ -243,7 +244,25 @@ proc processPipelineModule*(graph: ModuleGraph; module: PSym; idgen: IdGenerator
if (optCompress in graph.config.globalOptions or graph.config.cmd == cmdM) and
not graph.config.isDefined("nimscript"):
topLevelStmts.add finalNode
writeNifModule(graph.config, module.position.int32, topLevelStmts)
# Collect hooks from the module graph for the current module
var hooks = default array[AttachedOp, seq[HookIndexEntry]]
for op in TTypeAttachedOp:
if op == attachedDeepCopy: continue # Not supported in nimony
let nimonyOp = toAttachedOp(op)
for typeId, lazySym in graph.attachedOps[op]:
if typeId.module == module.position.int32:
let sym = lazySym.sym
if sym != nil:
hooks[nimonyOp].add toHookIndexEntry(graph.config, typeId, sym)
# Collect converters from the module's interface
var converters: seq[(nifstreams.SymId, nifstreams.SymId)] = @[]
for lazySym in graph.ifaces[module.position].converters:
let sym = lazySym.sym
if sym != nil:
let entry = toConverterIndexEntry(graph.config, sym)
if entry[0] != nifstreams.SymId(0):
converters.add entry
writeNifModule(graph.config, module.position.int32, topLevelStmts, hooks, converters)
if graph.config.backend notin {backendC, backendCpp, backendObjc} and graph.config.cmd != cmdM:
# We only write rod files here if no C-like backend is active.