From 37962c104258c68e377c716657a8d48009e6da98 Mon Sep 17 00:00:00 2001 From: araq Date: Thu, 4 Dec 2025 21:44:51 +0100 Subject: [PATCH] =hook and converter support --- compiler/ast2nif.nim | 116 ++++++++++++++++++++++++++++++++++++-- compiler/modulegraphs.nim | 26 ++++++++- compiler/pipelines.nim | 21 ++++++- 3 files changed, 157 insertions(+), 6 deletions(-) diff --git a/compiler/ast2nif.nim b/compiler/ast2nif.nim index a1b7ca6e67..623e5eab8f 100644 --- a/compiler/ast2nif.nim +++ b/compiler/ast2nif.nim @@ -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) diff --git a/compiler/modulegraphs.nim b/compiler/modulegraphs.nim index 408acd3ed3..ff9ac4359c 100644 --- a/compiler/modulegraphs.nim +++ b/compiler/modulegraphs.nim @@ -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) = diff --git a/compiler/pipelines.nim b/compiler/pipelines.nim index 358d0be5f0..de0ab42cea 100644 --- a/compiler/pipelines.nim +++ b/compiler/pipelines.nim @@ -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.