From 1d08c4e241fb3a7731070a1aca979073ae78d9d8 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Thu, 6 Nov 2025 23:41:13 +0800 Subject: [PATCH 1/5] fixes #25263; provides a new switch `mangle:nim/cpp` for debug name mangling (#25264) fixes #25263 - [x] documentation and changelogs --- changelog.md | 2 + compiler/ccgtypes.nim | 2 +- compiler/commands.nim | 9 ++ compiler/options.nim | 1 + doc/advopt.txt | 1 + tests/codegen/titaniummangle.nim | 4 +- tests/codegen/titaniummangle_nim.nim | 199 +++++++++++++++++++++++++++ 7 files changed, 215 insertions(+), 3 deletions(-) create mode 100644 tests/codegen/titaniummangle_nim.nim diff --git a/changelog.md b/changelog.md index 959f105669..bf7d343d1b 100644 --- a/changelog.md +++ b/changelog.md @@ -27,6 +27,8 @@ errors. - With `-d:nimPreviewDuplicateModuleError`, importing two modules that share the same name becomes a compile-time error. This includes importing the same module more than once. Use `import foo as foo1` (or other aliases) to avoid collisions. +- Adds the switch `--mangle:nim|cpp`, which selects `nim` or `cpp` style name mangling when used with `debuginfo` on, defaults to `nim`. The default is changed from `cpp` to `nim`. + ## Standard library additions and changes [//]: # "Additions:" diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim index a83857206b..cdfa46cdd2 100644 --- a/compiler/ccgtypes.nim +++ b/compiler/ccgtypes.nim @@ -75,7 +75,7 @@ proc mangleProc(m: BModule; s: PSym; makeUnique: bool): string = proc fillBackendName(m: BModule; s: PSym) = if s.loc.snippet == "": var result: Rope - if not m.compileToCpp and s.kind in routineKinds and optCDebug in m.g.config.globalOptions and + if s.kind in routineKinds and {optCDebug, optItaniumMangle} * m.g.config.globalOptions == {optCDebug, optItaniumMangle} and m.g.config.symbolFiles == disabledSf: result = mangleProc(m, s, false).rope else: diff --git a/compiler/commands.nim b/compiler/commands.nim index 7da15ca05b..e206a37300 100644 --- a/compiler/commands.nim +++ b/compiler/commands.nim @@ -364,6 +364,7 @@ proc testCompileOption*(conf: ConfigRef; switch: string, info: TLineInfo): bool result = false of "panics": result = contains(conf.globalOptions, optPanics) of "jsbigint64": result = contains(conf.globalOptions, optJsBigInt64) + of "mangle": result = contains(conf.globalOptions, optItaniumMangle) else: result = false invalidCmdLineOption(conf, passCmd1, switch, info) @@ -762,6 +763,14 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo; conf.globalOptions.excl optCDebug else: localError(conf, info, "expected native|gdb|on|off but found " & arg) + of "mangle": + case arg.normalize + of "nim": + conf.globalOptions.excl optItaniumMangle + of "cpp": + conf.globalOptions.incl optItaniumMangle + else: + localError(conf, info, "expected nim|cpp but found " & arg) of "g": # alias for --debugger:native conf.globalOptions.incl optCDebug conf.options.incl optLineDir diff --git a/compiler/options.nim b/compiler/options.nim index fa2c2069b3..7bc7d403c8 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -110,6 +110,7 @@ type # please make sure we have under 32 options optEnableDeepCopy # ORC specific: enable 'deepcopy' for all types. optShowNonExportedFields # for documentation: show fields that are not exported optJsBigInt64 # use bigints for 64-bit integers in JS + optItaniumMangle # mangling follows the Itanium spec TGlobalOptions* = set[TGlobalOption] diff --git a/doc/advopt.txt b/doc/advopt.txt index 647c1b4d67..85da350a80 100644 --- a/doc/advopt.txt +++ b/doc/advopt.txt @@ -91,6 +91,7 @@ Advanced options: --os:SYMBOL set the target operating system (cross-compilation) --cpu:SYMBOL set the target processor (cross-compilation) --debuginfo:on|off enables debug information + --mangle:nim|cpp selects `nim` or `cpp` style name mangling, defaults to `nim` -t, --passC:OPTION pass an option to the C compiler -l, --passL:OPTION pass an option to the linker --cc:SYMBOL specify the C compiler diff --git a/tests/codegen/titaniummangle.nim b/tests/codegen/titaniummangle.nim index d566900b19..ccca9ce2f0 100644 --- a/tests/codegen/titaniummangle.nim +++ b/tests/codegen/titaniummangle.nim @@ -1,6 +1,6 @@ discard """ - targets: "c" - matrix: "--debugger:native" + targets: "c cpp" + matrix: "--debugger:native --mangle:cpp" ccodecheck: "'_ZN14titaniummangle8testFuncE'" ccodecheck: "'_ZN14titaniummangle8testFuncE6stringN14titaniummangle3FooE'" ccodecheck: "'_ZN14titaniummangle8testFuncE3int7varargsI6stringE'" diff --git a/tests/codegen/titaniummangle_nim.nim b/tests/codegen/titaniummangle_nim.nim new file mode 100644 index 0000000000..72afdaf8a6 --- /dev/null +++ b/tests/codegen/titaniummangle_nim.nim @@ -0,0 +1,199 @@ +discard """ + targets: "c" + matrix: "--debugger:native --mangle:nim; --debugger:native" + ccodecheck: "'testFunc__titaniummangle95nim_u1316'" + ccodecheck: "'testFunc__titaniummangle95nim_u156'" + ccodecheck: "'testFunc__titaniummangle95nim_u1305'" + ccodecheck: "'testFunc__titaniummangle95nim_u241'" + ccodecheck: "'testFunc__titaniummangle95nim_u1357'" + ccodecheck: "'testFunc__titaniummangle95nim_u292'" + ccodecheck: "'testFunc__titaniummangle95nim_u38'" + ccodecheck: "'testFunc__titaniummangle95nim_u175'" + ccodecheck: "'testFunc__titaniummangle95nim_u1302'" + ccodecheck: "'testFunc__titaniummangle95nim_u1305'" + ccodecheck: "'testFunc__titaniummangle95nim_u535'" + ccodecheck: "'testFunc__titaniummangle95nim_u1294'" + ccodecheck: "'testFunc__titaniummangle95nim_u336'" + ccodecheck: "'testFunc__titaniummangle95nim_u425'" + ccodecheck: "'testFunc__titaniummangle95nim_u308'" + ccodecheck: "'testFunc__titaniummangle95nim_u129'" + ccodecheck: "'testFunc__titaniummangle95nim_u320'" + ccodecheck: "'testFunc__titaniummangle95nim_u223'" + ccodecheck: "'testFunc__titaniummangle95nim_u545'" + ccodecheck: "'testFunc__titaniummangle95nim_u543'" + ccodecheck: "'testFunc__titaniummangle95nim_u895'" + ccodecheck: "'testFunc__titaniummangle95nim_u1104'" + ccodecheck: "'testFunc__titaniummangle95nim_u1155'" + ccodecheck: "'testFunc__titaniummangle95nim_u636'" + ccodecheck: "'testFunc__titaniummangle95nim_u705'" + ccodecheck: "'testFunc__titaniummangle95nim_u800'" + ccodecheck: "'new__titaniummangle95nim_u1320'" + ccodecheck: "'xxx__titaniummangle95nim_u1391'" + ccodecheck: "'xxx__titaniummangle95nim_u1394'" +""" + +#When debugging this notice that if one check fails, it can be due to any of the above. + +type + Comparable = concept x, y + (x < y) is bool + + Foo = object + a: int32 + b: int32 + + FooTuple = tuple + a: int + b: int + + Container[T] = object + data: T + + Container2[T, T2] = object + data: T + data2: T2 + + Boo = distinct Foo + + Coo = Foo + + Doo = Boo | Foo + + TestProc = proc(a:string): string + +type EnumSample = enum + a, b, c + +type EnumAnotherSample = enum + a, b, c + +proc testFunc(a: set[EnumSample]) = + echo $a + +proc testFunc(a: typedesc) = + echo $a + +proc testFunc(a: ptr Foo) = + echo repr a + +proc testFunc(s: string, a: Coo) = + echo repr a + +proc testFunc(s: int, a: Comparable) = + echo repr a + +proc testFunc(a: TestProc) = + let b = "" + echo repr a("") + +proc testFunc(a: ref Foo) = + echo repr a + +proc testFunc(b: Boo) = + echo repr b + +proc testFunc(a: ptr UncheckedArray[int]) = + echo repr a + +proc testFunc(a: ptr int) = + echo repr a + +proc testFunc(a: ptr ptr int) = + echo repr a + +proc testFunc(e: FooTuple, str: cstring) = + echo e + +proc testFunc(e: (float, float)) = + echo e + +proc testFunc(e: EnumSample) = + echo e + +proc testFunc(e: var int) = + echo e + +proc testFunc(e: var Foo, a, b: int32, refFoo: ref Foo) = + echo e + +proc testFunc(xs: Container[int]) = + let a = 2 + echo xs + +proc testFunc(xs: Container2[int32, int32]) = + let a = 2 + echo xs + +proc testFunc(xs: Container[Container2[int32, int32]]) = + let a = 2 + echo xs + +proc testFunc(xs: seq[int]) = + let a = 2 + echo xs + +proc testFunc(xs: openArray[string]) = + let a = 2 + echo xs + +proc testFunc(xs: array[2, int]) = + let a = 2 + echo xs + +proc testFunc(e: EnumAnotherSample) = + echo e + +proc testFunc(a, b: int) = + echo "hola" + discard + +proc testFunc(a: int, xs: varargs[string]) = + let a = 10 + for x in xs: + echo x + +proc xxx(v: static int) = + echo v + +proc testFunc() = + var a = 2 + var aPtr = a.addr + var foo = Foo() + let refFoo : ref Foo = new(Foo) + let b = Foo().Boo() + let d: Doo = Foo() + testFunc("", Coo()) + testFunc(1, ) + testFunc(b) + testFunc(EnumAnotherSample) + var t = [1, 2] + let uArr = cast[ptr UncheckedArray[int]](t.addr) + testFunc(uArr) + testFunc({}) + testFunc(proc(s:string): string = "test") + testFunc(20, a.int32) + testFunc(20, 2) + testFunc(EnumSample.c) + testFunc(EnumAnotherSample.c) + testFunc((2, 1), "adios") + testFunc((22.1, 1.2)) + testFunc(a.addr) + testFunc(foo.addr) + testFunc(aPtr.addr) + testFunc(refFoo) + testFunc(foo, 2, 1, refFoo) + testFunc(a) + testFunc(@[2, 1, 2]) + testFunc(@["hola"]) + testFunc(2, "hola", "adios") + let arr: array[2, int] = [2, 1] + testFunc(arr) + testFunc(Container[int](data: 10)) + let c2 = Container2[int32, int32](data: 10, data2: 20) + testFunc(c2) + testFunc(Container[Container2[int32, int32]](data: c2)) + xxx(10) + xxx(20) + + +testFunc() \ No newline at end of file From 861ebc0f19c0a3848f6cf94b53a6c91be0eab06b Mon Sep 17 00:00:00 2001 From: Jacek Sieka Date: Thu, 6 Nov 2025 17:33:52 +0100 Subject: [PATCH 2/5] Add `heaptrack` support (#25257) This PR, courtesy of @NagyZoltanPeter (https://github.com/waku-org/nwaku/pull/3522) adds the ability to track memory allocations in a program suitable for use with [heaptrack](https://github.com/KDE/heaptrack). By passing `-d:heaptrack --debugger:native` to compilation, calls to heaptrack will be injected when memory is being allocated and released - unlike `-d:useMalloc` this strategy also works with `refc` and the default memory pool. See https://github.com/KDE/heaptrack for usage examples. The resulting binary needs to be run with `heaptrack` and with the shared `libheaptrack_preload.so` in the `LD_LIBRARY_PATH`. --- doc/nimc.md | 2 ++ lib/system/alloc.nim | 13 +++++++++++++ 2 files changed, 15 insertions(+) diff --git a/doc/nimc.md b/doc/nimc.md index 24a2b3afe1..bb48af1370 100644 --- a/doc/nimc.md +++ b/doc/nimc.md @@ -580,6 +580,8 @@ Define Effect Currently only clang and vcc. `strip` Strip debug symbols added by the backend compiler from the executable. +`heaptrack` Track memory allocations using + [heaptrack](https://github.com/KDE/heaptrack) ====================== ========================================================= diff --git a/lib/system/alloc.nim b/lib/system/alloc.nim index fcb7ccb0c8..4109348fc2 100644 --- a/lib/system/alloc.nim +++ b/lib/system/alloc.nim @@ -837,6 +837,15 @@ when defined(gcDestructors): dec maxIters if it == nil: break +when defined(heaptrack): + const heaptrackLib = + when defined(heaptrack_inject): + "libheaptrack_inject.so" + else: + "libheaptrack_preload.so" + proc heaptrack_malloc(a: pointer, size: int) {.cdecl, importc, dynlib: heaptrackLib.} + proc heaptrack_free(a: pointer) {.cdecl, importc, dynlib: heaptrackLib.} + proc rawAlloc(a: var MemRegion, requestedSize: int): pointer = when defined(nimTypeNames): inc(a.allocCounter) @@ -959,6 +968,8 @@ proc rawAlloc(a: var MemRegion, requestedSize: int): pointer = sysAssert(isAccessible(a, result), "rawAlloc 14") sysAssert(allocInv(a), "rawAlloc: end") when logAlloc: cprintf("var pointer_%p = alloc(%ld) # %p\n", result, requestedSize, addr a) + when defined(heaptrack): + heaptrack_malloc(result, requestedSize) proc rawAlloc0(a: var MemRegion, requestedSize: int): pointer = result = rawAlloc(a, requestedSize) @@ -967,6 +978,8 @@ proc rawAlloc0(a: var MemRegion, requestedSize: int): pointer = proc rawDealloc(a: var MemRegion, p: pointer) = when defined(nimTypeNames): inc(a.deallocCounter) + when defined(heaptrack): + heaptrack_free(p) #sysAssert(isAllocatedPtr(a, p), "rawDealloc: no allocated pointer") sysAssert(allocInv(a), "rawDealloc: begin") var c = pageAddr(p) From 6f73094263ee83753aa8c3f25f13739c34d78c96 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Fri, 7 Nov 2025 17:06:05 +0800 Subject: [PATCH 3/5] fixes #25251; SIGBUS with iterator over const Table lookup - premature temporary destruction (#25255) fixes #25251 enforce a copy if the arg is a deref of a lent pointer since the arg could be a temporary that will go out of scope --- compiler/transf.nim | 5 ++++- tests/iter/titer_issues.nim | 25 +++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/compiler/transf.nim b/compiler/transf.nim index 066be57f87..5d80bf9328 100644 --- a/compiler/transf.nim +++ b/compiler/transf.nim @@ -828,8 +828,11 @@ proc transformFor(c: PTransf, n: PNode): PNode = elif t.destructor == nil and arg.typ.destructor != nil: t = arg.typ - if arg.kind in {nkDerefExpr, nkHiddenDeref}: + if arg.kind in {nkDerefExpr, nkHiddenDeref} and + arg[0].typ.skipTypes(abstractInst).kind != tyLent: # optimizes for `[]` # bug #24093 + # bug #25251: enforce a copy if the arg is a deref of a lent pointer + # since the arg could be a temporary that will go out of scope var temp = newTemp(c, arg[0].typ, formal.info) addVar(v, temp) stmtList.add(newAsgnStmt(c, nkFastAsgn, temp, arg[0], true)) diff --git a/tests/iter/titer_issues.nim b/tests/iter/titer_issues.nim index ff0b8eb49f..5070a54713 100644 --- a/tests/iter/titer_issues.nim +++ b/tests/iter/titer_issues.nim @@ -432,3 +432,28 @@ block: let x = cast[typeof(aaa)](aaa) # not even var for _ in x[]: discard + +import std/[tables, unicode, sequtils] + +const + myTable = { + "en": "abcdefghijklmnopqrstuvwxyz", + }.toTable + +proc buggyVersion(locale: string): seq[Rune] = + result = toSeq(runes(myTable[locale])) + +proc workingVersion(locale: string): seq[Rune] = + # string lifetime is extended + let str = myTable[locale] + result = toSeq(runes(str)) + +# echo "Testing working version..." +let runes2 = workingVersion("en") +# echo "Got ", runes2.len, " runes" + +# echo "Testing buggy version..." +let runes1 = buggyVersion("en") # <-- CRASHES HERE + +doAssert runes1.len == runes2.len +# echo "Got ", runes1.len, " runes" From 809662a22830e35c55d64f1d0859bc81f23bddb3 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Fri, 7 Nov 2025 11:05:13 +0100 Subject: [PATCH 4/5] =?UTF-8?q?VM:=20optimize=20'return'=20slots;=20saves?= =?UTF-8?q?=20millions=20of=20node=20allocations=20for=20N=E2=80=A6=20(#25?= =?UTF-8?q?266)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …imbus --- compiler/vm.nim | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/compiler/vm.nim b/compiler/vm.nim index 2a97140d64..258c233345 100644 --- a/compiler/vm.nim +++ b/compiler/vm.nim @@ -1442,8 +1442,16 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = #echo "new pc ", newPc, " calling: ", prc.name.s var newFrame = PStackFrame(prc: prc, comesFrom: pc, next: tos) newSeq(newFrame.slots, prc.offset+ord(isClosure)) - if not isEmptyType(prc.typ.returnType): - putIntoReg(newFrame.slots[0], getNullValue(c, prc.typ.returnType, prc.info, c.config)) + # setup slot for proc result: + let ret {.cursor.} = prc.typ.returnType + # hot spot ahead! + if ret != nil: + if fitsRegister(ret): + # same logic as opcLdNullReg here: + ensureKind(newFrame.slots[0], rkInt) + newFrame.slots[0].intVal = 0 + elif not isEmptyType(ret): + putIntoReg(newFrame.slots[0], getNullValue(c, ret, prc.info, c.config)) for i in 1..rc-1: newFrame.slots[i] = regs[rb+i] if isClosure: From 839cbeb371e9a219662925bd0bb923ba7bd66941 Mon Sep 17 00:00:00 2001 From: metagn Date: Fri, 7 Nov 2025 15:19:50 +0300 Subject: [PATCH 5/5] js: replace `push.apply` with for loop for string add [backport] (#25267) While `a.push.apply(a, b)` is better for performance than the previous `a = a.concat(b)` due to the fact that it doesn't create a new array, there is a pretty big problem with it: depending on the JS engine, if the second array is too long, it can [cause a crash](https://tanaikech.github.io/2020/04/20/limitation-of-array.prototype.push.apply-under-v8-for-google-apps-script/) due to the function `push` taking too many arguments. This has unfortunately been what the codegen produces since 1.4.0 (commit https://github.com/nim-lang/Nim/commit/707367e1ca231d964ba82a92b642eb5efdc1aa7c). So string addition is now moved to a compilerproc that just uses a `for` loop. From what I can tell this is the most compatible and the fastest. Only potential problem compared to `concat` etc is with aliasing, i.e. adding an array to itself, but I'm guessing it's enough that the length from before the iteration is used, since it can only grow. The test checks for aliased nim strings but I don't know if there's an extra protection for them. --- compiler/jsgen.nim | 4 ++-- lib/system/jssys.nim | 8 ++++++++ tests/system/tconcat.nim | 32 +++++++++++++++++++++++++++----- 3 files changed, 37 insertions(+), 7 deletions(-) diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim index 8abb830bd2..fd8ef583d0 100644 --- a/compiler/jsgen.nim +++ b/compiler/jsgen.nim @@ -2333,8 +2333,8 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) = r.res = "if (null != $1) { if (null == $2) $2 = $3; else $2 += $3; }" % [b, lhs.rdLoc, tmp] else: - let (a, tmp) = maybeMakeTemp(p, n[1], lhs) - r.res = "$1.push.apply($3, $2);" % [a, rhs.rdLoc, tmp] + useMagic(p, "nimAddStrStr") + r.res = "nimAddStrStr($1, $2);" % [lhs.rdLoc, rhs.rdLoc] r.kind = resExpr of mAppendSeqElem: var x, y: TCompRes = default(TCompRes) diff --git a/lib/system/jssys.nim b/lib/system/jssys.nim index 6934e67ee4..b469c4695f 100644 --- a/lib/system/jssys.nim +++ b/lib/system/jssys.nim @@ -687,6 +687,14 @@ proc isObj(obj, subclass: PNimType): bool {.compilerproc.} = proc addChar(x: string, c: char) {.compilerproc, asmNoStackFrame.} = {.emit: "`x`.push(`c`);".} +proc nimAddStrStr(x, y: string) {.compilerproc, asmNoStackFrame.} = + {.emit: """ + var L = `y`.length; + for (var i = 0; i < L; ++i) { + `x`.push(`y`[i]); + } + """.} + {.pop.} proc tenToThePowerOf(b: int): BiggestFloat = diff --git a/tests/system/tconcat.nim b/tests/system/tconcat.nim index fdce3ea00d..8cf995c938 100644 --- a/tests/system/tconcat.nim +++ b/tests/system/tconcat.nim @@ -1,11 +1,33 @@ discard """ - output: "DabcD" + targets: "c cpp js" + output: ''' +DabcD +(8192, 8, 1024) +''' """ -const - x = "abc" +import std/assertions -var v = "D" & x & "D" +block: + const + x = "abc" -echo v + var v = "D" & x & "D" + doAssert v == "DabcD" + echo v + +block: # test large additions + var a = "abcdefgh" + let initialLen = a.len + let times = 10 + for i in 1..times: + let start = a.len + a.add(a) + doAssert a.len == 2 * start + let multiplier = 1 shl times + doAssert a.len == initialLen * multiplier + echo (a.len, initialLen, multiplier) + for i in 1 ..< multiplier: + for j in 0 ..< initialLen: + doAssert a[j] == a[i * initialLen + j]