diff --git a/src/hastur.nim b/src/hastur.nim index d3890412e..17c101a9b 100644 --- a/src/hastur.nim +++ b/src/hastur.nim @@ -1838,6 +1838,91 @@ proc runNativeCodegenTests*(dir: string; overwrite: bool) = else: echo "SUCCESS." +proc extractDebugMetadata(llText, testName: string): string = + ## Keep only the debug metadata lines of a `.ll` file, in order. Everything + ## else (instructions, types, mangled temporaries) churns with unrelated + ## codegen changes and would make the golden useless as a debug-info guard. + ## + ## Two things are normalized so the golden is portable across checkouts: + ## `directory:` holds an absolute path, and module-init/`main` symbols carry + ## the module-hash suffix, which depends on the absolute source path. + result = "" + for line in llText.splitLines: + var s = line.strip + if not s.startsWith("!"): continue + if not ("DILocation" in s or "DISubprogram" in s or + "DILocalVariable" in s or "DIFile" in s): continue + # `directory: ""` -> `directory: ""` + let dirPos = s.find("directory: \"") + if dirPos >= 0: + let valStart = dirPos + "directory: \"".len + let valEnd = s.find('"', valStart) + if valEnd > valStart: + s = s[0 ..< valStart] & "" & s[valEnd .. ^1] + # `X60Qini_0_` -> `X60Qini_0_` + let sufPos = s.find("X60Qini_0_") + if sufPos >= 0: + let valStart = sufPos + "X60Qini_0_".len + var valEnd = valStart + while valEnd < s.len and s[valEnd] notin {'"'}: inc valEnd + s = s[0 ..< valStart] & "" & s[valEnd .. ^1] + result.add s + result.add "\n" + +proc runLLVMDebugTests*(dir: string; overwrite: bool) = + ## Golden suite over the LLVM backend's *debug metadata*. For each `.nim`, + ## compile with `nimony l` and diff the DWARF-relevant metadata lines of the + ## emitted `.ll` against a checked-in `.ll.expected`. + ## + ## Only `DIFile`/`DISubprogram`/`DILocation`/`DILocalVariable` lines are + ## compared: those carry the inlined-frame structure (#1987) and are stable, + ## whereas the surrounding IR churns with every unrelated codegen change. + ## + ## `hastur.mode = skip` — the LLVM backend cannot build the full stdlib yet, + ## so these are opt-in via `hastur tests/llvmdebug`. Add `--overwrite` to + ## regenerate the goldens after an intended debug-info change. + if not skipBuild: + buildNimonyToolchain() + buildLengc() + let t0 = epochTime() + var c = TestCounters(total: 0, failures: 0) + var files: seq[string] = @[] + for x in walkDir(dir): + if x.kind == pcFile and x.path.endsWith(".nim") and + x.path.extractFilename != "setup.nim": + files.add x.path + sort files + for file in files: + inc c.total + let cacheArg = + if nimcacheDir != "nimcache": "--nimcache:" & quoteShell(nimcacheDir) & " " + else: "" + # The LLVM backend cannot link the full stdlib yet, so the exit code is not + # meaningful here; the `.ll` is written before linking and is what we check. + discard execLocal("nimony", "l --silentMake --isMain " & cacheArg & + quoteShell(file)) + let llFile = generatedFile(file, ".ll") + if not llFile.fileExists(): + failure c, file, "lengc .ll", "missing: " & llFile + continue + let actual = extractDebugMetadata(readFile(llFile), file.splitFile.name) + let expectedFile = file.changeFileExt(".ll.expected") + if overwrite: + writeFile(expectedFile, actual) + elif not expectedFile.fileExists(): + failure c, file, "golden debug metadata", "missing: " & expectedFile + else: + let expected = readFile(expectedFile) + if expected.strip != actual.strip: + failure c, file, expected, actual + echo c.total - c.failures, " / ", c.total, + " llvm-debug tests successful in ", + formatFloat(epochTime() - t0, ffDecimal, precision=2), "s." + if c.failures > 0: + quit "FAILURE: Some llvm-debug tests failed." + else: + echo "SUCCESS." + # ---- deterministic self-host bootstrap ------------------------------------ # `bin0/` is a fresh copy of the host-Nim-built toolchain in `bin/`; `binN/` # (N >= 1) is `binN-1/`'s nimony recompiling all three self-tools from diff --git a/src/lengc/codegen.nim b/src/lengc/codegen.nim index b8849fea8..d0ca12c48 100644 --- a/src/lengc/codegen.nim +++ b/src/lengc/codegen.nim @@ -196,7 +196,7 @@ proc errorAt(m: MainModule; msg: string; n: Cursor) {.noreturn.} = ## (mangled symbol plus embedded line info), which says nothing a reader wants. let info = rawLineInfo(n) if info.isValid: - write stdout, m.pool.filenames[info.file] + write stdout, realFile(m.pool.filenames[info.file]) write stdout, "(" & $info.line & ", " & $(info.col+1) & ") " # `Error: `, not the `[Error] ` of the rendering `error` above: this is a # user-facing diagnostic, and that is the spelling every other user-facing @@ -210,7 +210,7 @@ proc errorAt(m: MainModule; msg: string; n: Cursor) {.noreturn.} = proc error(m: MainModule; msg: string; n: Cursor) {.noreturn.} = let info = rawLineInfo(n) if info.isValid: - write stdout, m.pool.filenames[info.file] + write stdout, realFile(m.pool.filenames[info.file]) write stdout, "(" & $info.line & ", " & $(info.col+1) & ") " write stdout, "[Error] " write stdout, msg diff --git a/src/lengc/llvmcodegen.nim b/src/lengc/llvmcodegen.nim index 652fa03e9..524d6da2f 100644 --- a/src/lengc/llvmcodegen.nim +++ b/src/lengc/llvmcodegen.nim @@ -71,6 +71,9 @@ type diBasicTypeCache*: Table[string, int] # "i32"/"float"/… -> DIBasicType id compositeTypeDone*: HashSet[SymId] # symIds with fully built DICompositeType globalExprs*: seq[int] # DIGlobalVariableExpression IDs for DICompileUnit globals + fileIdsByName*: Table[string, int] # source path -> DIFile meta + inlineSpCache*: Table[string, int] # expanded routine (mangled) -> spId + nullSigId*: int # shared `!DISubroutineType(types: !{null})` for template SPs type PrimTypes* = object @@ -124,7 +127,7 @@ proc errorAt(m: MainModule; msg: string; n: Cursor) {.noreturn.} = ## what is wrong in prose (the render would append the raw mangled symbol). let info = rawLineInfo(n) if info.isValid: - write stdout, m.pool.filenames[info.file] + write stdout, realFile(m.pool.filenames[info.file]) write stdout, "(" & $info.line & ", " & $(info.col+1) & ") " # `Error: `, not the `[Error] ` of the rendering `error` above: this is a # user-facing diagnostic, and that is the spelling every other user-facing @@ -138,7 +141,7 @@ proc errorAt(m: MainModule; msg: string; n: Cursor) {.noreturn.} = proc error(m: MainModule; msg: string; n: Cursor) {.noreturn.} = let info = rawLineInfo(n) if info.isValid: - write stdout, m.pool.filenames[info.file] + write stdout, realFile(m.pool.filenames[info.file]) write stdout, "(" & $info.line & ", " & $(info.col+1) & ") " write stdout, "[Error] " write stdout, msg diff --git a/src/lengc/llvmdebug.nim b/src/lengc/llvmdebug.nim index d79aedd0a..837e3389b 100644 --- a/src/lengc/llvmdebug.nim +++ b/src/lengc/llvmdebug.nim @@ -56,43 +56,140 @@ proc genDIBasicType(c: var LLVMCode; name: string; sizeBits, ", encoding: " & $encoding & ")") c.debug.diBasicTypeCache[key] = result -proc getOrCreateDIFile(c: var LLVMCode; fid: FileId): int = - ## Get or create a DIFile metadata node for the given FileId. - let key = int(fid) - # `getOrDefault` rather than `[]`: the latter is `.raises` (KeyError) under - # Nimony. Metadata ids are positive, so -1 is an unambiguous "not cached". - let cached = c.debug.fileIds.getOrDefault(key, -1) +proc getOrCreateDIFileByName(c: var LLVMCode; path: string): int = + ## Get or create a DIFile metadata node for a plain source path. Keyed on the + ## path itself, since the declaration sites carried in a forged filename have + ## no `FileId` of their own. + let cached = c.debug.fileIdsByName.getOrDefault(path, -1) if cached >= 0: return cached - let path = c.m.pool.filenames[fid] let (dir, name, ext) = splitFile(path) let fullName = name & ext let directory = absoluteDirOrQuit(dir) result = c.addMetadata("!DIFile(filename: \"" & fullName & "\", directory: \"" & directory & "\")") - c.debug.fileIds[key] = result + c.debug.fileIdsByName[path] = result # First real source file → create the compile unit using this file if c.debug.cuId == 0: c.debug.cuId = c.addMetadata("distinct !DICompileUnit(language: DW_LANG_C99, file: !" & $result & ", producer: \"lengc\", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)") -proc dbgLocation(c: var LLVMCode; info: NifLineInfo): string = - ## Return a `, !dbg !N` suffix for the given source location, or "" if invalid. - if not info.isValid: return "" +proc getOrCreateDIFile(c: var LLVMCode; fid: FileId): int = + ## Get or create a DIFile metadata node for the given FileId. + let key = int(fid) + # `getOrDefault` rather than `[]`: the latter is `.raises` (KeyError) under + # Nimony. Metadata ids are positive, so -1 is an unambiguous "not cached". + let cached = c.debug.fileIds.getOrDefault(key, -1) + if cached >= 0: + return cached + # A forged filename carries template-expansion provenance (`__crucial\0…`); + # the DIFile must name the real source, and `dbgLocationId` reads the chain + # separately to build the inlined frames. + result = getOrCreateDIFileByName(c, realFile(c.m.pool.filenames[fid])) + c.debug.fileIds[key] = result + +proc getOrCreateInlineSP(c: var LLVMCode; origin: CrucialOrigin; + fallbackFileId, fallbackLine: int): int + +proc dbgLocationId(c: var LLVMCode; info: NifLineInfo): int = + ## Build a DILocation metadata node for the given source location and return + ## its id, or 0 if the location is invalid. + ## + ## When the location's filename carries expansion provenance + ## (`__crucial\0\0\0real.nim`, see `comesfrom`), the chain is + ## turned into DWARF inlined frames: one synthetic DISubprogram per expanded + ## routine, each location scoped to the innermost and chained outwards through + ## `inlinedAt` until it reaches the enclosing proc. That is what makes a + ## debugger show a template as an inlined frame instead of jumping into its + ## definition file (#1987). + if not info.isValid: return 0 let rawInfo = info - if not rawInfo.file.isValid: return "" + if not rawInfo.file.isValid: return 0 let fileId = getOrCreateDIFile(c, rawInfo.file) - let scopeId = + + # The call site: the location this expansion was written at, in the enclosing + # proc. Everything the chain builds hangs off it. + proc scopeInProc(c: var LLVMCode; fileId: int): int = if fileId == c.currentProc.subprogramFileId: c.currentProc.subprogramId else: c.addMetadata("!DILexicalBlockFile(scope: !" & $c.currentProc.subprogramId & ", file: !" & $fileId & ", discriminator: 0)") - let locId = c.addMetadata("!DILocation(line: " & $rawInfo.line & + + let fname = c.m.pool.filenames[rawInfo.file] + if not isCrucialFile(fname): + result = c.addMetadata("!DILocation(line: " & $rawInfo.line & + ", column: " & $(rawInfo.col + 1) & + ", scope: !" & $scopeInProc(c, fileId) & ")") + return + + # Outermost first, so each iteration nests one level deeper. The call-site + # location is the same for every level: expansions carry no separate location + # for where each nested template was invoked, so the whole chain attributes + # back to the statement the outermost expansion replaced. + var inlinedAt = c.addMetadata("!DILocation(line: " & $rawInfo.line & ", column: " & $(rawInfo.col + 1) & - ", scope: !" & $scopeId & ")") - result = ", !dbg !" & $locId + ", scope: !" & $scopeInProc(c, fileId) & ")") + var scopeId = 0 + for origin in crucialOrigins(fname): + let spId = getOrCreateInlineSP(c, origin, fileId, rawInfo.line) + if spId == 0: continue + if scopeId != 0: + # The previous level's location becomes this one's call site. + inlinedAt = c.addMetadata("!DILocation(line: " & $rawInfo.line & + ", column: " & $(rawInfo.col + 1) & + ", scope: !" & $scopeId & + ", inlinedAt: !" & $inlinedAt & ")") + scopeId = spId + if scopeId == 0: + result = inlinedAt + else: + result = c.addMetadata("!DILocation(line: " & $rawInfo.line & + ", column: " & $(rawInfo.col + 1) & + ", scope: !" & $scopeId & + ", inlinedAt: !" & $inlinedAt & ")") + +proc dbgLocation(c: var LLVMCode; info: NifLineInfo): string = + ## Return a `, !dbg !N` suffix for the given source location, or "" if invalid. + let locId = dbgLocationId(c, info) + result = if locId == 0: "" else: ", !dbg !" & $locId + +proc getOrCreateInlineSP(c: var LLVMCode; origin: CrucialOrigin; + fallbackFileId, fallbackLine: int): int = + ## Get or create the synthetic DISubprogram for an expanded routine, shared by + ## every call site of it. Returns 0 when none can be built. + ## + ## The frame is placed at the routine's *declaration* site, which the forged + ## filename carries for exactly this reason: a template declaration does not + ## survive into Leng, and the expanded code's own line info points at whatever + ## file the body came from. The fallbacks cover a chain written before the + ## declaration site was encoded. + let cached = c.debug.inlineSpCache.getOrDefault(origin.sym, -1) + if cached >= 0: return cached + var fileId = fallbackFileId + var line = fallbackLine + if origin.declFile.len > 0: + fileId = getOrCreateDIFileByName(c, origin.declFile) + line = int(origin.declLine) + if fileId == 0: return 0 + # A subprogram definition needs a type and a unit; the signature is opaque + # (`!{null}`: unknown return, no formals) because a template has neither a + # calling convention nor materialized parameters. + if c.debug.nullSigId == 0: + c.debug.nullSigId = c.addMetadata("!DISubroutineType(types: !{null})") + var isGlobal = false + var shortName = extractBasename(origin.sym, isGlobal) + if shortName.len == 0: shortName = origin.sym + result = c.addMetadata("distinct !DISubprogram(name: \"" & + shortName & + "\", scope: !" & $fileId & + ", file: !" & $fileId & + ", line: " & $line & + ", type: !" & $c.debug.nullSigId & + ", scopeLine: " & $line & + ", spFlags: DISPFlagDefinition, unit: !" & $c.debug.cuId & ")") + c.debug.inlineSpCache[origin.sym] = result proc createSubprogram(c: var LLVMCode; name: string; info: NifLineInfo): int = ## Create a DISubprogram metadata node for a function. @@ -147,18 +244,35 @@ proc emitDbgDeclare(c: var LLVMCode; localName: string; symId: SymId; useType = genDIBasicType(c, "int " & $bits, bits, DW_ATE_signed) let debugName = if wasName.len > 0: wasName else: nifSymBaseName(c, symId) let fileId = getOrCreateDIFile(c, rawInfo.file) + # A variable declared inside a template expansion belongs to that template's + # synthetic subprogram, and its declare location must carry the matching + # `inlinedAt` chain: LLVM's verifier rejects a #dbg_declare whose variable + # scope and location scope disagree. `template t = (var x = ...)` is ordinary + # code, so this is required, not polish. + # + # Both come from the same place - the forged filename - so they cannot drift: + # the innermost origin names the SP that `dbgLocationId` will scope to. + let fname = c.m.pool.filenames[rawInfo.file] + let inFrame = isCrucialFile(fname) + var varScopeId = c.currentProc.subprogramId + if inFrame: + for origin in crucialOrigins(fname): + let spId = getOrCreateInlineSP(c, origin, fileId, rawInfo.line) + if spId != 0: varScopeId = spId var varMetadata = "!DILocalVariable(name: \"" & debugName & "\"" if argNo > 0: varMetadata.add ", arg: " & $argNo - varMetadata.add ", scope: !" & $c.currentProc.subprogramId & + varMetadata.add ", scope: !" & $varScopeId & ", file: !" & $fileId & ", line: " & $rawInfo.line & ", type: !" & $useType & ")" let varId = c.addMetadata(varMetadata) - c.currentProc.retainedNodes.add varId - let locId = c.addMetadata("!DILocation(line: " & $rawInfo.line & - ", column: " & $(rawInfo.col + 1) & - ", scope: !" & $c.currentProc.subprogramId & ")") + if not inFrame: + # `retainedNodes` lists the variables scoped to *this* subprogram; one + # scoped to a template's synthetic SP does not belong in the proc's list. + c.currentProc.retainedNodes.add varId + let locId = dbgLocationId(c, info) + if locId == 0: return c.emitRaw "#dbg_declare(ptr " & localName & ", !" & $varId & ", !DIExpression(), !" & $locId & ")" diff --git a/src/lib/comesfrom.nim b/src/lib/comesfrom.nim new file mode 100644 index 000000000..4aa163b81 --- /dev/null +++ b/src/lib/comesfrom.nim @@ -0,0 +1,114 @@ +# Nimony +# (c) Copyright 2026 Andreas Rumpf +# +# See the file "license.txt", included in this +# distribution, for details about the copyright. + +## Expansion provenance encoded in a line-info filename. +## +## Code produced by expanding a template does not get its own wrapper node. +## Instead the tokens carry a *forged* filename that records what they came +## from, so a debug backend can emit them as DWARF inlined frames: +## +## __crucial\0setElem.0.foo\1foo.nim\116\0[]=.0.system\1system.nim\134\0system.nim +## ^prefix ^-------- outermost -------^ ^--------- innermost --------^ ^real file +## +## The chain runs outermost-first, so its length is the inlining depth. Each +## entry is `\1\1`: the symbol names the expanded +## routine, and the declaration site is carried because it cannot be recovered +## later - a template declaration does not survive into the backend, and the +## expanded code's own line info points at wherever the body came from, not at +## the template. Everything after the last NUL is the real filename, which is +## what a consumer that does not care about frames should use. +## +## A filename cannot otherwise contain a NUL or a `\1`, which is what makes the +## encoding unambiguous - note that `|` would not do, since Nim lets an operator +## be named `|`. `nifbuilder.needsEscape` covers `c < ' '`, so both control +## characters survive text NIF as `\00` / `\01`; `bif` writes filenames +## length-prefixed, so binary is fine. +## +## Deliberately free of any NIF dependency: it is plain string handling, so the +## front end, the C backend and the LLVM backend can all reach it without +## pulling in a cursor API. + +const + CrucialPrefix* = "__crucial\0" + ## Marks a forged filename that carries template-expansion provenance. + CrucialFieldSep* = '\1' + ## Separates ``, `` and `` inside one chain entry. + +type + CrucialOrigin* = object ## One level of an expansion chain. + sym*: string ## the expanded routine, mangled (`setElem.0.foo`) + declFile*: string ## where it was declared; "" when unknown + declLine*: int32 ## its declaration line; 0 when unknown + +proc isCrucialFile*(fname: string): bool {.noSideEffect.} = + ## True when `fname` carries expansion provenance rather than being a plain + ## source path. Cheap enough to call per token: the `\0` at the end of the + ## prefix is checked first, and a real path never has one. + if fname.len <= CrucialPrefix.len: return false + if fname[CrucialPrefix.len - 1] != '\0': return false + for i in 0 ..< CrucialPrefix.len: + if fname[i] != CrucialPrefix[i]: return false + result = true + +proc forgeCrucialFile*(origins: openArray[CrucialOrigin]; + realFile: string): string {.noSideEffect.} = + ## Build the forged filename for code expanded from `origins` (outermost + ## first) that physically lives in `realFile`. + result = CrucialPrefix + for o in origins: + result.add o.sym + result.add CrucialFieldSep + result.add o.declFile + result.add CrucialFieldSep + result.add $o.declLine + result.add '\0' + result.add realFile + +proc realFile*(fname: string): string {.noSideEffect.} = + ## The actual source file, with any expansion provenance stripped. Returns + ## `fname` unchanged when it carries none, so every consumer can call it. + if not isCrucialFile(fname): return fname + var last = -1 + for i in 0 ..< fname.len: + if fname[i] == '\0': last = i + if last < 0: fname else: fname.substr(last + 1) + +proc parseCrucialOrigin(entry: string): CrucialOrigin {.noSideEffect.} = + ## Split one `\1\1` entry. Tolerates a bare symbol + ## with no declaration site, so a partially-known chain still names its + ## frames instead of being discarded. + result = CrucialOrigin(sym: entry, declFile: "", declLine: 0'i32) + var first = -1 + var second = -1 + for i in 0 ..< entry.len: + if entry[i] == CrucialFieldSep: + if first < 0: first = i + elif second < 0: second = i + if first < 0: return + result.sym = entry.substr(0, first - 1) + if second < 0: + result.declFile = entry.substr(first + 1) + return + result.declFile = entry.substr(first + 1, second - 1) + var line = 0'i32 + for i in second + 1 ..< entry.len: + let ch = entry[i] + if ch < '0' or ch > '9': return + line = line * 10'i32 + int32(ord(ch) - ord('0')) + result.declLine = line + +iterator crucialOrigins*(fname: string): CrucialOrigin {.noSideEffect.} = + ## The expanded routines `fname` came from, outermost first. Yields nothing + ## for a plain filename. + if isCrucialFile(fname): + var start = CrucialPrefix.len + var i = start + while i < fname.len: + if fname[i] == '\0': + yield parseCrucialOrigin(fname.substr(start, i - 1)) + start = i + 1 + inc i + # the tail after the last NUL is the real filename, not an origin diff --git a/src/lib/nifcoreparse.nim b/src/lib/nifcoreparse.nim index fcee8e096..12f02cb34 100644 --- a/src/lib/nifcoreparse.nim +++ b/src/lib/nifcoreparse.nim @@ -25,6 +25,11 @@ import stringviews import lineinfos # for `==`(FileId) used by NifLineInfo's structural `==` export nifcore +import comesfrom +# Travels with the reader for the same reason it travels with the pool API: +# any consumer of a line-info filename may need `realFile` to strip the +# template-expansion provenance the front end forged into it. +export comesfrom type Parent = tuple[file: FileId; line, col: int32] diff --git a/src/lib/nifpools.nim b/src/lib/nifpools.nim index 1b3e06381..5743d7da4 100644 --- a/src/lib/nifpools.nim +++ b/src/lib/nifpools.nim @@ -31,6 +31,12 @@ import std / assertions import nifcore +import comesfrom +# Template-expansion provenance rides in the line-info filename, so every +# consumer of a filename may need to strip it (`realFile`). Plain string +# handling with no NIF dependency of its own; re-exported here so it travels +# with the pool API rather than being imported separately everywhere. +export comesfrom # Re-export nifcore verbatim except the two helpers whose shim versions below # thread the global pool. `kind`/`NifKind`/its members are re-exported AS-IS: # the sem port uses nifcore's own kind model directly (`TagLit`, `StrLit`, diff --git a/src/nimony/derefs.nim b/src/nimony/derefs.nim index eec4cd2f0..7cbacc9a7 100644 --- a/src/nimony/derefs.nim +++ b/src/nimony/derefs.nim @@ -777,7 +777,7 @@ proc trLocal(c: var Context; n: var Cursor) = let u = n.info echo "LOCAL LEFTOVER kind=", n.kind, (if n.isTagLit: " tag=" & globalTags.tags[n.tagId] else: ""), - " at ", pool.filenames[u.file], ":", u.line, ":", u.col + " at ", realFile(pool.filenames[u.file]), ":", u.line, ":", u.col proc trStmtListExpr(c: var Context; n: var Cursor; outerE: Expects) = takeInto c.dest, n: diff --git a/src/nimony/expreval.nim b/src/nimony/expreval.nim index e21df633e..f0926d0c8 100644 --- a/src/nimony/expreval.nim +++ b/src/nimony/expreval.nim @@ -163,7 +163,7 @@ proc constSourceDir(info: NifLineInfo): string = let fid = info.file try: if fid.isValid: - result = pool.filenames[fid].absolutePath().parentDir() + result = realFile(pool.filenames[fid]).absolutePath().parentDir() else: result = getCurrentDir() except: diff --git a/src/nimony/idetools.nim b/src/nimony/idetools.nim index 23b6683b8..3e7a87a0f 100644 --- a/src/nimony/idetools.nim +++ b/src/nimony/idetools.nim @@ -34,7 +34,7 @@ proc foundSymbol(n: Cursor; mode: TrackMode) = r.add "\t" # filename: r.add "\t" - r.add pool.filenames[info.file] + r.add realFile(pool.filenames[info.file]) r.add "\t" r.addInt info.line r.add "\t" diff --git a/src/nimony/indexgen.nim b/src/nimony/indexgen.nim index 6ec710231..a9e9db0fc 100644 --- a/src/nimony/indexgen.nim +++ b/src/nimony/indexgen.nim @@ -46,7 +46,7 @@ proc buildIndexExports(exports: Table[string, HashSet[SymId]]; infile: string): if mn.isTagLit: inc mn # into the stmts; first child carries the info let fileId = mn.info.file assert fileId.isValid - let path = pool.filenames[fileId].toAbsolutePath + let path = realFile(pool.filenames[fileId]).toAbsolutePath result.addParLe(TagId(FromexportIdx)) result.addStrLit(path, NoLineInfo) for s in syms: diff --git a/src/nimony/reporters.nim b/src/nimony/reporters.nim index 3f8d92eac..869679efa 100644 --- a/src/nimony/reporters.nim +++ b/src/nimony/reporters.nim @@ -109,7 +109,10 @@ proc infoToStr*(info: NifLineInfo): string = if not info.isValid: result = "???" else: - result = pool.filenames[info.file].shortenDir() + # `realFile`: expanded code carries a forged filename recording where it came + # from (see `comesfrom`'s `CrucialPrefix`). A user-facing message wants the + # actual source path, not the provenance chain. + result = realFile(pool.filenames[info.file]).shortenDir() result.add "(" & $info.line & ", " & $(info.col+1) & ")" proc reportErrorsRec(r: var Reporter; n: var Cursor; errTag: TagId; count: var int) = diff --git a/src/nimony/sem.nim b/src/nimony/sem.nim index b21ec2b50..3743662d6 100644 --- a/src/nimony/sem.nim +++ b/src/nimony/sem.nim @@ -1002,8 +1002,17 @@ proc visibilityModule(c: SemContext; info: NifLineInfo): string = ## expanded routine's module; an argument is the caller's own code and stays ## judged against the module being compiled. Walking outwards handles ## expansions nested inside expansions. + ## + ## The comparison is on the *real* file: an expansion's tokens carry a forged + ## filename recording where they came from (see `comesfrom`'s `CrucialPrefix`), + ## and a forged name has its own `FileId`, so matching raw ids would miss. + if c.visOwner.len == 0: return c.thisModuleSuffix + if not info.file.isValid: return c.thisModuleSuffix + let infoFile = realFile(pool.filenames[info.file]) for i in countdown(c.visOwner.len-1, 0): - if c.visOwner[i].file == info.file.uint32: + let ownerId = FileId(c.visOwner[i].file) + if not ownerId.isValid: continue + if realFile(pool.filenames[ownerId]) == infoFile: return c.visOwner[i].module result = c.thisModuleSuffix diff --git a/src/nimony/semcall.nim b/src/nimony/semcall.nim index 9258f1f5d..a295e7df1 100644 --- a/src/nimony/semcall.nim +++ b/src/nimony/semcall.nim @@ -218,6 +218,14 @@ proc semTemplateCall(c: var SemContext; dest: var TokenBuf; it: var Item; fnId: expandedInto.addDotToken() # sentinel so the final `inc` stays in bounds var a = Item(n: cursorAt(expandedInto, 0), typ: c.types.autoType) let aInfo = a.n.info + # make sure template body expression matches return type, mirrored with `semProcBody`: + # Hoisted above `semExpr` so the void case can be known before emitting: + # both `m.returnType` and `m.inferred` are fixed by `sigmatch` before we run. + let returnType = + if m.inferred.len == 0 or m.returnType.isDotToken: + m.returnType + else: + instantiateType(c, m.returnType, m.inferred) inc c.routine.inInst # An `untyped` template's body is published unresolved, so its field # accesses resolve HERE for the first time and must be judged against the @@ -228,12 +236,6 @@ proc semTemplateCall(c: var SemContext; dest: var TokenBuf; it: var Item; fnId: c.visOwner.add VisOwner(module: extractModule(pool.syms[fnId]), file: res.decl.info.file.uint32) semExpr c, dest, a, flags - # make sure template body expression matches return type, mirrored with `semProcBody`: - let returnType = - if m.inferred.len == 0 or m.returnType.isDotToken: - m.returnType - else: - instantiateType(c, m.returnType, m.inferred) case returnType.typeKind of UntypedT: # untyped return type ignored, maybe could be handled in commonType @@ -244,6 +246,19 @@ proc semTemplateCall(c: var SemContext; dest: var TokenBuf; it: var Item; fnId: commonType c, dest, a, beforeCall, returnType discard c.visOwner.pop() dec c.routine.inInst + # Record where this expansion came from (#1987). The provenance rides in the + # line-info filename of the emitted tokens - `__crucial\0\0` - + # so the debug backend can emit them as a DWARF inlined frame while every + # other consumer just sees `realFile()`. + # + # After `semExpr`, not before: a template called *inside* this body has + # already expanded and forged its own name by now, so this pass prepends the + # outer level onto the existing chain and the order comes out outermost + # first, which is what nesting `inlinedAt` needs. + # + # Tokens substituted in from the call site keep their own file: they belong + # to the caller's frame, and are recognised by already carrying `callInfo`'s. + forgeExpansionInfo(c, dest, beforeCall, fnId, res.decl.info, callInfo) # now match to expected type: it.kind = a.kind typeofCallIs c, dest, it, beforeCall, a.typ diff --git a/src/nimony/semos.nim b/src/nimony/semos.nim index d12925eb7..9be01357b 100644 --- a/src/nimony/semos.nim +++ b/src/nimony/semos.nim @@ -340,7 +340,7 @@ proc parseFile*(nimFile: string; paths: openArray[string], nifcachePath: string) proc getFile*(info: NifLineInfo): string = let fid = info.file if fid.isValid: - result = pool.filenames[fid] + result = realFile(pool.filenames[fid]) else: result = "" diff --git a/src/nimony/templates.nim b/src/nimony/templates.nim index 73f74a09c..3cf270a3a 100644 --- a/src/nimony/templates.nim +++ b/src/nimony/templates.nim @@ -98,6 +98,110 @@ proc expandTemplateImpl(c: var SemContext; dest: var TokenBuf; else: discard "ParRi/close (classic) or stray suffix (nifcore)" +type + ForgeCtx = object + ## State for one `forgeExpansionInfo` pass. A plain object rather than + ## captured locals: nimony has no closures, and this code must self-host. + originEntry: CrucialOrigin + callInfo: NifLineInfo + forgedOf: Table[FileId, FileId] + +proc forgeMapInfo(f: var ForgeCtx; li: NifLineInfo): NifLineInfo = + ## The provenance-carrying twin of `li`, or `li` itself when it is the call + ## site's own position or has no file to forge. + if not li.isValid or not li.file.isValid: return li + if li.file == f.callInfo.file and li.line == f.callInfo.line and + li.col == f.callInfo.col: + return li + var forged = f.forgedOf.getOrDefault(li.file) + if forged == FileId(0): + let fname = pool.filenames[li.file] + # Prepend, not append: a template's body is sem-checked (and so any template + # *it* calls is expanded) before this outer expansion is forged, so the + # existing chain is always the inner levels. Outermost first is what the + # debug backend needs to nest `inlinedAt` correctly. + var origins = @[f.originEntry] + for o in crucialOrigins(fname): origins.add o + forged = pool.filenames.getOrIncl(forgeCrucialFile(origins, realFile(fname))) + f.forgedOf[li.file] = forged + result = NifLineInfo(file: forged, line: li.line, col: li.col, + comment: li.comment) + +proc forgeReemit(f: var ForgeCtx; dest: var TokenBuf; src: var Cursor) = + ## Rebuild the subtree at `src` into `dest` with forged line info. Info rides + ## as a trailing `LineInfoLit` on a head token, so it cannot be patched in + ## place - the tokens have to be re-emitted. + let info = forgeMapInfo(f, src.info) + case src.kind + of TagLit: + if cursorTagId(src) == nifpools.ErrT: + # An `(err )` is already-reported + # diagnostic state, not code: its dots are the error contexts + # `reporters` prints as `Trace: instantiation from here`. Rewriting them + # duplicates the trace for a template that errors inside another + # expansion (`tests/nimony/templates/tinvalidrecursion.nim`). + dest.addSubtree src + skip src + else: + dest.addParLe(cursorTagId(src), info) + src.into: + while src.hasMore: forgeReemit(f, dest, src) + dest.addParRi() + of IntLit: dest.addIntLit(intVal(src), info); inc src + of UIntLit: dest.addUIntLit(uintVal(src), info); inc src + of FloatLit: dest.addFloatLit(floatVal(src), info); inc src + of CharLit: dest.addCharLit(charLit(src), info); inc src + of StrLit: dest.addStrLit(strVal(src), info); inc src + of Symbol: dest.addSymUse(src.symId, info); inc src + of SymbolDef: dest.addSymDef(src.symId, info); inc src + of DotToken: + # Info-carrying: `buildErr` records each instantiation context as a dot + # token whose line info is the call site, and `reporters` turns those into + # the `Trace: instantiation from here` lines. Dropping it loses them. + dest.addDotToken(info); inc src + else: + # Idents and anything unstructured: copy verbatim, info and all. + dest.addSubtree src + skip src + +proc forgeExpansionInfo*(c: var SemContext; dest: var TokenBuf; start: int; + origin: SymId; declInfo: NifLineInfo; + callInfo: NifLineInfo) = + ## Rewrite the line info of everything `dest` gained from `start` onward so + ## it records that the code came from expanding `origin` (#1987). + ## + ## The provenance rides in the filename rather than in a wrapper node: a token + ## whose file is `foo.nim` becomes + ## `__crucial\0\1\1\0foo.nim`, so a debug backend + ## can emit a DWARF inlined frame for it while every other consumer sees + ## `realFile()` and is unaffected. Nesting composes: expanding a template whose + ## body already carries a forged name prepends onto the existing chain, so the + ## chain runs outermost-first and its length is the inlining depth. + ## + ## Tokens substituted in from the *call site* keep their own info: they were + ## written by the caller and belong to the caller's frame. They are recognised + ## by sitting at exactly the call's position, which is why `callInfo` is passed + ## in rather than derived here. Comparing only the *file* would be wrong for a + ## template declared in the file it is called from - the common case, and the + ## one `tests/llvmdebug/ttemplate_locals.nim` covers. + # The declaration site travels in the chain because it cannot be recovered + # downstream: a template decl does not survive into Leng, and the expanded + # code's own info points at whatever file the body came from. + var f = ForgeCtx( + originEntry: CrucialOrigin( + sym: pool.syms[origin], + declFile: (if declInfo.file.isValid: realFile(pool.filenames[declInfo.file]) else: ""), + declLine: declInfo.line), + callInfo: callInfo, + forgedOf: initTable[FileId, FileId]()) + + var src = createTokenBuf(dest.len - start) + for i in start ..< dest.len: src.add dest[i] + shrink dest, start + + var n = beginRead(src) + while n.hasMore: forgeReemit(f, dest, n) + type PluginOutcome* = enum NoPluginRan ## the template carries no `.plugin` pragma diff --git a/tests/llvmdebug/hastur.mode b/tests/llvmdebug/hastur.mode new file mode 100644 index 000000000..e32e76dd5 --- /dev/null +++ b/tests/llvmdebug/hastur.mode @@ -0,0 +1 @@ +skip diff --git a/tests/llvmdebug/setup.nim b/tests/llvmdebug/setup.nim new file mode 100644 index 000000000..72d1b5e6b --- /dev/null +++ b/tests/llvmdebug/setup.nim @@ -0,0 +1,25 @@ +## Custom runner for the LLVM debug-info golden tests: compile each `.nim` +## with the LLVM backend (`nimony l`) and diff the DWARF-relevant metadata of +## the emitted `.ll` against the checked-in `.ll.expected`. +## +## This directory is `hastur.mode = skip`, so the default `hastur all` sweep +## leaves it out (the LLVM backend cannot build the full stdlib yet); run it +## explicitly with `hastur tests/llvmdebug` (add `--overwrite` to regenerate +## the goldens after an intended debug-info change). +import std / [os, strutils] +import "../../src/hastur" + +proc arg(name: string): string = + let prefix = "--" & name & ":" + for p in commandLineParams(): + if p.startsWith(prefix): return p[prefix.len .. ^1] + result = "" + +if arg("bindir").len > 0: + toolchainDir = arg("bindir") + skipBuild = true +if arg("cachedir").len > 0: nimcacheDir = arg("cachedir") +let overwrite = "--overwrite" in commandLineParams() +let dir = if arg("dir").len > 0: arg("dir") else: getCurrentDir() + +runLLVMDebugTests(dir, overwrite) diff --git a/tests/llvmdebug/ttemplate_frames.ll.expected b/tests/llvmdebug/ttemplate_frames.ll.expected new file mode 100644 index 000000000..05f79d63a --- /dev/null +++ b/tests/llvmdebug/ttemplate_frames.ll.expected @@ -0,0 +1,35 @@ +!2 = !DIFile(filename: "ttemplate_frames.nim", directory: "") +!4 = distinct !DISubprogram(name: "run", scope: !2, file: !2, line: 18, type: !19, scopeLine: 18, spFlags: DISPFlagDefinition, unit: !3, retainedNodes: !20) +!5 = !DILocalVariable(name: "p", arg: 1, scope: !4, file: !2, line: 18, type: !1) +!6 = !DILocation(line: 18, column: 1, scope: !4) +!7 = !DIFile(filename: "system.nim", directory: "") +!9 = !DILocation(line: 34, column: 10, scope: !8) +!11 = distinct !DISubprogram(name: "setElem", scope: !2, file: !2, line: 15, type: !10, scopeLine: 15, spFlags: DISPFlagDefinition, unit: !3) +!12 = distinct !DISubprogram(name: "[]=", scope: !7, file: !7, line: 33, type: !10, scopeLine: 33, spFlags: DISPFlagDefinition, unit: !3) +!13 = !DILocation(line: 34, column: 10, scope: !11, inlinedAt: !9) +!14 = !DILocation(line: 34, column: 10, scope: !12, inlinedAt: !13) +!16 = !DILocation(line: 34, column: 10, scope: !15) +!17 = !DILocation(line: 34, column: 10, scope: !11, inlinedAt: !16) +!18 = !DILocation(line: 34, column: 10, scope: !12, inlinedAt: !17) +!21 = !DILocation(line: 18, column: 1, scope: !4) +!25 = distinct !DISubprogram(name: "X60Qini_0_", scope: !2, file: !2, line: 15, type: !32, scopeLine: 15, spFlags: DISPFlagDefinition, unit: !3, retainedNodes: !33) +!26 = !DILocation(line: 15, column: 1, scope: !25) +!27 = !DILocation(line: 15, column: 1, scope: !25) +!28 = !DILocation(line: 15, column: 1, scope: !25) +!29 = !DILocation(line: 15, column: 1, scope: !25) +!30 = !DILocation(line: 15, column: 1, scope: !25) +!31 = !DILocation(line: 15, column: 1, scope: !25) +!34 = !DILocation(line: 15, column: 1, scope: !25) +!53 = distinct !DISubprogram(name: "main", scope: !2, file: !2, line: 15, type: !66, scopeLine: 15, spFlags: DISPFlagDefinition, unit: !3, retainedNodes: !67) +!54 = !DILocalVariable(name: "`argc", arg: 1, scope: !53, file: !2, line: 15, type: !35) +!55 = !DILocation(line: 15, column: 1, scope: !53) +!56 = !DILocalVariable(name: "`argv", arg: 2, scope: !53, file: !2, line: 15, type: !50) +!57 = !DILocation(line: 15, column: 1, scope: !53) +!58 = !DILocalVariable(name: "`envp", arg: 3, scope: !53, file: !2, line: 15, type: !52) +!59 = !DILocation(line: 15, column: 1, scope: !53) +!60 = !DILocation(line: 15, column: 1, scope: !53) +!61 = !DILocation(line: 15, column: 1, scope: !53) +!62 = !DILocation(line: 15, column: 1, scope: !53) +!63 = !DILocation(line: 15, column: 1, scope: !53) +!64 = !DILocation(line: 15, column: 1, scope: !53) +!65 = !DILocation(line: 15, column: 1, scope: !53) diff --git a/tests/llvmdebug/ttemplate_frames.nim b/tests/llvmdebug/ttemplate_frames.nim new file mode 100644 index 000000000..9ffcf87e5 --- /dev/null +++ b/tests/llvmdebug/ttemplate_frames.nim @@ -0,0 +1,20 @@ +# Regression guard for nim-lang/nimony#1987: a template call must appear as an +# inlined stack frame, not as a jump into the template's definition. +# +# Before the fix, stepping over `setElem(p, 0, 'H')` in lldb landed on +# `system.nim:34` (the body of `[]=`) with a bare +# !DILocation(line: 34, column: 10, scope: !) +# and no `inlinedAt`, so the debugger had no frame to attribute it to. +# +# The golden below pins the shape that fixes it: one DISubprogram per expanded +# template, and every location inside it chained back to its call site through +# `inlinedAt`. `setElem` expands `[]=`, so the chain is two levels deep — that +# nesting is the part most likely to regress (building the call-site location +# after pushing the frame would silently flatten it). + +template setElem(x: ptr UncheckedArray[char]; i: int; elem: char) = + x[i] = elem + +proc run(p: ptr UncheckedArray[char]) {.exportc.} = + setElem(p, 0, 'H') + setElem(p, 1, 'i') diff --git a/tests/llvmdebug/ttemplate_locals.ll.expected b/tests/llvmdebug/ttemplate_locals.ll.expected new file mode 100644 index 000000000..0badbc0ac --- /dev/null +++ b/tests/llvmdebug/ttemplate_locals.ll.expected @@ -0,0 +1,38 @@ +!3 = !DIFile(filename: "ttemplate_locals.nim", directory: "") +!5 = distinct !DISubprogram(name: "run2", scope: !3, file: !3, line: 15, type: !21, scopeLine: 15, spFlags: DISPFlagDefinition, unit: !4, retainedNodes: !22) +!6 = !DILocalVariable(name: "x", arg: 1, scope: !5, file: !3, line: 15, type: !1) +!7 = !DILocation(line: 15, column: 1, scope: !5) +!8 = !DILocalVariable(name: "y", arg: 2, scope: !5, file: !3, line: 15, type: !2) +!9 = !DILocation(line: 15, column: 1, scope: !5) +!11 = distinct !DISubprogram(name: "withTemp", scope: !3, file: !3, line: 10, type: !10, scopeLine: 10, spFlags: DISPFlagDefinition, unit: !4) +!12 = !DILocalVariable(name: "t", scope: !11, file: !3, line: 11, type: !2) +!13 = !DILocation(line: 11, column: 7, scope: !5) +!14 = !DILocation(line: 11, column: 7, scope: !11, inlinedAt: !13) +!15 = !DILocation(line: 11, column: 7, scope: !5) +!16 = !DILocation(line: 11, column: 7, scope: !11, inlinedAt: !15) +!17 = !DILocation(line: 12, column: 5, scope: !5) +!18 = !DILocation(line: 12, column: 5, scope: !11, inlinedAt: !17) +!19 = !DILocation(line: 13, column: 5, scope: !5) +!20 = !DILocation(line: 13, column: 5, scope: !11, inlinedAt: !19) +!23 = !DILocation(line: 15, column: 1, scope: !5) +!27 = distinct !DISubprogram(name: "X60Qini_0_", scope: !3, file: !3, line: 10, type: !34, scopeLine: 10, spFlags: DISPFlagDefinition, unit: !4, retainedNodes: !35) +!28 = !DILocation(line: 10, column: 1, scope: !27) +!29 = !DILocation(line: 10, column: 1, scope: !27) +!30 = !DILocation(line: 10, column: 1, scope: !27) +!31 = !DILocation(line: 10, column: 1, scope: !27) +!32 = !DILocation(line: 10, column: 1, scope: !27) +!33 = !DILocation(line: 10, column: 1, scope: !27) +!36 = !DILocation(line: 10, column: 1, scope: !27) +!55 = distinct !DISubprogram(name: "main", scope: !3, file: !3, line: 10, type: !68, scopeLine: 10, spFlags: DISPFlagDefinition, unit: !4, retainedNodes: !69) +!56 = !DILocalVariable(name: "`argc", arg: 1, scope: !55, file: !3, line: 10, type: !37) +!57 = !DILocation(line: 10, column: 1, scope: !55) +!58 = !DILocalVariable(name: "`argv", arg: 2, scope: !55, file: !3, line: 10, type: !52) +!59 = !DILocation(line: 10, column: 1, scope: !55) +!60 = !DILocalVariable(name: "`envp", arg: 3, scope: !55, file: !3, line: 10, type: !54) +!61 = !DILocation(line: 10, column: 1, scope: !55) +!62 = !DILocation(line: 10, column: 1, scope: !55) +!63 = !DILocation(line: 10, column: 1, scope: !55) +!64 = !DILocation(line: 10, column: 1, scope: !55) +!65 = !DILocation(line: 10, column: 1, scope: !55) +!66 = !DILocation(line: 10, column: 1, scope: !55) +!67 = !DILocation(line: 10, column: 1, scope: !55) diff --git a/tests/llvmdebug/ttemplate_locals.nim b/tests/llvmdebug/ttemplate_locals.nim new file mode 100644 index 000000000..f11244f92 --- /dev/null +++ b/tests/llvmdebug/ttemplate_locals.nim @@ -0,0 +1,16 @@ +# Companion to ttemplate_frames.nim for nim-lang/nimony#1987: a variable +# *declared inside* a template expansion. +# +# `#dbg_declare` names both a DILocalVariable scope and a DILocation scope. +# Under an active inlined frame both must be the template's synthetic +# DISubprogram and the location must carry the same `inlinedAt` chain — LLVM's +# verifier rejects the module when they disagree. `template t = (var x = ...)` +# is ordinary code, so this is a correctness requirement, not polish. + +template withTemp(a: int; b: int) = + var t: int = a + t = t + b + a = t + +proc run2(x: var int; y: int) {.exportc.} = + withTemp(x, y) diff --git a/tests/nimony/nosystem/tresemtype.nif b/tests/nimony/nosystem/tresemtype.nif index 7b6da2692..0ea304fdb 100644 --- a/tests/nimony/nosystem/tresemtype.nif +++ b/tests/nimony/nosystem/tresemtype.nif @@ -1,5 +1,5 @@ (.nif27) -(.indexat 1858 ) +(.indexat 2092 ) (stmts@,3,tests/nimony/nosystem/tresemtype.nim (proc :foo.0.@5 . . (typevars@8 @@ -27,7 +27,7 @@ (var@4,2 :obj.1 . . Foo.1.@6 (oconstr@9 Foo.1.~3 (kv@4 val.0~3 x.1@2))))) - (stmts@2,9 + (stmts@2,C,__crucial\00fooTempl.0.treckpe1i1\01tests/nimony/nosystem/tresemtype.nim\0111\00tests/nimony/nosystem/tresemtype.nim (type@9 :Foo.2.~4 . . (pragmas) (object@2 . @@ -36,7 +36,7 @@ (gvar@4,2 :obj.2 . . Foo.2.@6 (oconstr@9 Foo.2.~3 (kv@4 val.0~3 123~A,2)))) - (stmts@2,9 + (stmts@2,C,__crucial\00fooTempl.0.treckpe1i1\01tests/nimony/nosystem/tresemtype.nim\0111\00tests/nimony/nosystem/tresemtype.nim (type@9 :Foo.3.~4 . . (pragmas) (object@2 . @@ -78,8 +78,8 @@ (h fooTempl.0. 232) (h@H,8 T.1. 47) (h@2,9 Foo.1. 92) - (h@2,9 Foo.2. 178) - (h@2,9 Foo.3. 189) + (h@2,C,__crucial\00fooTempl.0.treckpe1i1\01tests/nimony/nosystem/tresemtype.nim\0111\00tests/nimony/nosystem/tresemtype.nim Foo.2. 295) + (h@2,C,__crucial\00fooTempl.0.treckpe1i1\01tests/nimony/nosystem/tresemtype.nim\0111\00tests/nimony/nosystem/tresemtype.nim Foo.3. 306) (h foo.0.Imj9rno. 229) (h@2,1 Foo.4. 144) (h foo.0.Iwjalau. 183) diff --git a/tests/nimony/nosystem/ttemplate.nif b/tests/nimony/nosystem/ttemplate.nif index ae035ca32..2ceef0703 100644 --- a/tests/nimony/nosystem/ttemplate.nif +++ b/tests/nimony/nosystem/ttemplate.nif @@ -1,5 +1,5 @@ (.nif27) -(.indexat 3628 ) +(.indexat 4020 ) (stmts@,2,tests/nimony/nosystem/ttemplate.nim (type@2,1 :int.0. (i 64). @@ -105,10 +105,10 @@ (i@A,~5 64).@A,~5) (var@4 :x.5 . . string.0.sysvq0asl "abc"@4) (asgn@7,1 result.1~7 - (expr@N,~4 + (expr@W,Y,__crucial\00plus.0.tte5tld8n\01tests/nimony/nosystem/ttemplate.nim\0134\00tests/nimony/nosystem/ttemplate.nim (add@2 (i~I,~2 64)4~I,4 - (expr~2 + (expr@W,Y,__crucial\00plus.0.tte5tld8n\01tests/nimony/nosystem/ttemplate.nim\0134\00plus.0.tte5tld8n\01tests/nimony/nosystem/ttemplate.nim\0134\00tests/nimony/nosystem/ttemplate.nim (add@2 (i~I,~2 64)2~A,4 89~7,4))))) (asgn@2,2 x.5~2 "34"@2) @@ -139,7 +139,7 @@ (glet@4,l :val.0. . . (i 64)123@6) (discard@,m - (expr@U,~3 + (expr@U,l,__crucial\00conv.0.tte5tld8n\01tests/nimony/nosystem/ttemplate.nim\0147\00tests/nimony/nosystem/ttemplate.nim (conv@1 (u~I,3 64)val.0.~C,3))) (comment@F,E untyped.0. typedesc.0.@I int.0.@1,G int.0.@1,G int.0.~2,G int.0.@5,I int.0.@5,I int.0.~2,I int.0.~3,K int.0.@6,K int.0.@5,V int.0.@5,A float.0.@4,B plus.0.@8,M plus.0.@,M uint.0.~2,Y conv.0.@3,Y) @@ -179,10 +179,10 @@ (x \2B.0. 165) (h plus.0. 170) (h foo.0. 183) - (h overloaded.0. 434) + (h overloaded.0. 717) (x uint.0. 228) (h conv.0. 66) (h@D,j T.3. 43) (h val.0. 127) - (h MyGeneric.0.Iu9y0wv. 313) + (h MyGeneric.0.Iu9y0wv. 422) (h MyGeneric.0.I1hdb4b1. 136)) \ No newline at end of file diff --git a/tests/nimony/nosystem/tvarargs.nif b/tests/nimony/nosystem/tvarargs.nif index 16495178c..689b9ad3d 100644 --- a/tests/nimony/nosystem/tvarargs.nif +++ b/tests/nimony/nosystem/tvarargs.nif @@ -1,5 +1,5 @@ (.nif27) -(.indexat 3622 ) +(.indexat 4054 ) (stmts@,2,tests/nimony/nosystem/tvarargs.nim (type@2,1 :int.0. (i 64). @@ -130,7 +130,7 @@ (ochoice write.0. write.1.)stdout.0.@6 x.4@E))) (cmd@,2 write.0. stdout.0.@6 '\0A'@E))) (gvar@4,c :someVar.0. . . string.0.sysvq0asl ""@A) - (stmts@2,Y + (stmts@2,a,__crucial\00echo.0.tvah6culb\01tests/nimony/nosystem/tvarargs.nim\0135\00tests/nimony/nosystem/tvarargs.nim (stmts@2,1 (cmd write.1. (haddr@6 stdout.0.)"a"@1,5)) @@ -142,7 +142,7 @@ (haddr@6 stdout.0.)"b"@F,5)) (cmd@,2 write.0. (haddr@6 stdout.0.)'\0A'@E)) - (stmts@2,Y + (stmts@2,a,__crucial\00echo.0.tvah6culb\01tests/nimony/nosystem/tvarargs.nim\0135\00tests/nimony/nosystem/tvarargs.nim (stmts@2,1 (cmd write.1. (haddr@6 stdout.0.)"xzy"@1,7)) @@ -151,10 +151,10 @@ (haddr@6 stdout.0.)'c'@8,7)) (cmd@,2 write.0. (haddr@6 stdout.0.)'\0A'@E)) - (stmts@2,Y + (stmts@2,a,__crucial\00echo.0.tvah6culb\01tests/nimony/nosystem/tvarargs.nim\0135\00tests/nimony/nosystem/tvarargs.nim (cmd@,2 write.0. (haddr@6 stdout.0.)'\0A'@E)) - (stmts@2,Y + (stmts@2,a,__crucial\00echo.0.tvah6culb\01tests/nimony/nosystem/tvarargs.nim\0135\00tests/nimony/nosystem/tvarargs.nim (stmts@2,1 (cmd write.1. (haddr@6 stdout.0.)someVar.0.@1,B))