From fa4f9c9759fbb9c82021745425c76bc886d8c805 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Duval?= Date: Wed, 1 Jul 2026 13:51:23 +0200 Subject: [PATCH 1/7] haiku: add kqueue definitions (#25953) needs libbsd for kqueue --- config/nim.cfg | 10 +++++----- lib/posix/kqueue.nim | 9 +++++++++ 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/config/nim.cfg b/config/nim.cfg index 6a259321fb..26948ba928 100644 --- a/config/nim.cfg +++ b/config/nim.cfg @@ -134,11 +134,11 @@ nimblepath="$home/.nimble/pkgs/" # BSD got posix_spawn only recently, so we deactivate it for osproc: define:useFork @elif haiku: - gcc.options.linker = "-Wl,--as-needed -lnetwork" - gcc.cpp.options.linker = "-Wl,--as-needed -lnetwork" - clang.options.linker = "-Wl,--as-needed -lnetwork" - clang.cpp.options.linker = "-Wl,--as-needed -lnetwork" - tcc.options.linker = "-Wl,--as-needed -lnetwork" + gcc.options.linker = "-Wl,--as-needed -lnetwork -lbsd" + gcc.cpp.options.linker = "-Wl,--as-needed -lnetwork -lbsd" + clang.options.linker = "-Wl,--as-needed -lnetwork -lbsd" + clang.cpp.options.linker = "-Wl,--as-needed -lnetwork -lbsd" + tcc.options.linker = "-Wl,--as-needed -lnetwork -lbsd" @elif not genode: # -fopenmp gcc.options.linker = "-ldl" diff --git a/lib/posix/kqueue.nim b/lib/posix/kqueue.nim index 2450cdb424..f5edcaf2e0 100644 --- a/lib/posix/kqueue.nim +++ b/lib/posix/kqueue.nim @@ -28,6 +28,15 @@ elif defined(netbsd): EVFILT_PROC* = 4 ## attached to struct proc EVFILT_SIGNAL* = 5 ## attached to struct proc EVFILT_TIMER* = 6 ## timers (in ms) +elif defined(haiku): + const + EVFILT_READ* = -1 + EVFILT_WRITE* = -2 + EVFILT_AIO* = -3 ## attached to aio requests + EVFILT_VNODE* = -4 ## attached to vnodes + EVFILT_PROC* = -5 ## attached to struct proc + EVFILT_SIGNAL* = -6 ## attached to struct proc + EVFILT_TIMER* = -7 ## timers when defined(macosx): const EVFILT_MACHPORT* = -8 ## Mach portsets From 3b9100178eb98e0f6ae54a6615191660cfea2223 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Thu, 2 Jul 2026 18:02:00 +0800 Subject: [PATCH 2/7] fixes #25949; nimvm + staticRead code executed in runtime context (#25950) fixes #25949 track still walks both branches of preserved when nimvm, but it now sets a small inNimvmBranch flag while visiting the VM branch, and trackCall skips sfCompileTime marking only when that flag is set. --- compiler/sempass2.nim | 5 ++++- tests/vm/tvmmisc.nim | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim index 008f2317ba..73e3b4072a 100644 --- a/compiler/sempass2.nim +++ b/compiler/sempass2.nim @@ -93,6 +93,7 @@ type graph: ModuleGraph c: PContext escapingParams: IntSet + inNimvmBranch: int PEffects = var TEffects const @@ -1127,7 +1128,7 @@ proc trackCall(tracked: PEffects; n: PNode) = # sfCompileTime` path in `semProcAux`. if a.kind == nkSym and a.sym.magic in {mNLen..mNError, mSlurp..mQuoteAst} and tracked.owner != nil and tracked.owner.kind in routineKinds and - tracked.config.cmd != cmdNimscript: + tracked.config.cmd != cmdNimscript and tracked.inNimvmBranch == 0: # ...but NOT under `nim e`: nimscript has no codegen backend to protect, and # marking a routine `sfCompileTime` makes `semExpr` eagerly fold calls to it # at sem time (emConst), where module-level globals it reads have no VM slot @@ -1483,7 +1484,9 @@ proc track(tracked: PEffects, n: PNode) = of nkCaseStmt: trackCase(tracked, n) of nkWhen: # This should be a "when nimvm" node. let oldState = tracked.init.len + inc tracked.inNimvmBranch track(tracked, n[0][1]) + dec tracked.inNimvmBranch tracked.init.setLen(oldState) track(tracked, n[1][0]) of nkIfStmt, nkIfExpr: trackIf(tracked, n) diff --git a/tests/vm/tvmmisc.nim b/tests/vm/tvmmisc.nim index 906ced7b83..c8ed29f7ce 100644 --- a/tests/vm/tvmmisc.nim +++ b/tests/vm/tvmmisc.nim @@ -855,3 +855,17 @@ block: var r = Obj(x: 10) r.value = 42 doAssert r.x == 42 + +block: # bug #25949 + template loadFile(filename: string): auto = + when nimvm: + staticRead(filename) + else: + "something" + + proc roundTrip(): bool = + let content = loadFile("tests/tomls/case.toml") + content == "something" + + doAssert roundTrip() + From a0e44d7aca2023dcd6d6d7cf76cde7238ee6261e Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Thu, 2 Jul 2026 18:02:16 +0800 Subject: [PATCH 3/7] fixes #25945; cannot map the empty seq type to a C type (#25954) fixes #25945 When `@[]` appears inside a nested `if` expression that also contains statements, the AST wraps it in `nkStmtListExpr` nodes. The empty container's `tyEmpty` element type was never resolved to a concrete type, causing the C codegen to ICE with "cannot map the empty seq type to a C type". Walk through nested statement-list/block expressions in `fitNodePostMatch` to find the innermost value node and propagate the formal type to empty containers. --- compiler/sem.nim | 12 ++++++++++++ tests/ccgbugs2/tcodegen.nim | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/compiler/sem.nim b/compiler/sem.nim index a9f87cd006..7f777a7add 100644 --- a/compiler/sem.nim +++ b/compiler/sem.nim @@ -89,6 +89,18 @@ proc fitNodePostMatch(c: PContext, formal: PType, arg: PNode): PNode = changeType(c, x, formal, check=true) result = arg result = skipHiddenSubConv(result, c.graph, c.idgen) + # Walk through nested statement-list/block expressions to find the innermost + # value node. Empty containers (e.g. `@[]`) inside `nkStmtListExpr` wrappers + # need their type resolved to match the formal type, otherwise the C codegen + # cannot map `tyEmpty` to a concrete type (fixes #25945). + var tail = result + while tail.kind in {nkStmtList, nkStmtListExpr, nkBlockStmt, nkBlockExpr, nkPragmaBlock} and tail.len > 0: + tail = tail.lastSon + + if tail.typ != nil and tail.typ.isEmptyContainer and + formal.kind notin {tyUntyped, tyBuiltInTypeClass, tyAnything}: + changeType(c, tail, formal, check=true) + # mark inserted converter as used: var a = result if a.kind == nkHiddenDeref: a = a[0] diff --git a/tests/ccgbugs2/tcodegen.nim b/tests/ccgbugs2/tcodegen.nim index bca361e813..da5ab045cc 100644 --- a/tests/ccgbugs2/tcodegen.nim +++ b/tests/ccgbugs2/tcodegen.nim @@ -75,3 +75,15 @@ block: # importc type inheritance doAssert(cast[cint](b) == 123) var c = foo(b) doAssert(cast[cint](c) == 123) + +block: # bug #25945 + var stateRefund = 0 + let authCode = + if true: + if false: + stateRefund += 0 + @[] + else: + @([1.byte]) + +discard (if true: (discard; @[]) else: @[0]) From 8101c8d73bb704e1eb783ccc079700a30efc75e1 Mon Sep 17 00:00:00 2001 From: WyattBlue Date: Thu, 2 Jul 2026 06:03:34 -0400 Subject: [PATCH 4/7] fixes memory leak in the emscripten page allocator (#25901) The emscripten branch cast the descriptor address to the value type EmscriptenMMapBlock instead of the pointer alias PEmscriptenMMapBlock, so osAllocPages stored realSize/realPointer in a discarded local and osDeallocPages reinterpreted the address integer as the descriptor instead of dereferencing it -- calling munmap() with garbage that fails, so freed pages are never returned. Freed huge chunks are also dropped from the free list, leaking permanently. Affects wasm32 and wasm64. Cast to PEmscriptenMMapBlock so both accesses go through memory. --- lib/system/osalloc.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/system/osalloc.nim b/lib/system/osalloc.nim index 4177b47b1f..db7f4c1482 100644 --- a/lib/system/osalloc.nim +++ b/lib/system/osalloc.nim @@ -89,7 +89,7 @@ elif defined(emscripten) and not defined(StandaloneHeapSize): var mmapDescrPos = cast[int](result) -% sizeof(EmscriptenMMapBlock) - var mmapDescr = cast[EmscriptenMMapBlock](mmapDescrPos) + var mmapDescr = cast[PEmscriptenMMapBlock](mmapDescrPos) mmapDescr.realSize = realSize mmapDescr.realPointer = realPointer @@ -99,7 +99,7 @@ elif defined(emscripten) and not defined(StandaloneHeapSize): proc osDeallocPages(p: pointer, size: int) {.inline.} = var mmapDescrPos = cast[int](p) -% sizeof(EmscriptenMMapBlock) - var mmapDescr = cast[EmscriptenMMapBlock](mmapDescrPos) + var mmapDescr = cast[PEmscriptenMMapBlock](mmapDescrPos) munmap(mmapDescr.realPointer, mmapDescr.realSize) elif defined(genode) and not defined(StandaloneHeapSize): From d77f5bcd0fcecb89487501c7177e2d2f3c8f4be7 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Thu, 2 Jul 2026 18:04:43 +0800 Subject: [PATCH 5/7] fixes #25803: add genCppConstructorExpr for full type-prefixed expression (#25817) fixes #25803 This pull request introduces a new approach for generating C++ constructor expressions in the Nim compiler's C++ backend, ensuring that type-prefixed construction is used when needed (such as in assignments), rather than just braced initializer lists. It also adds a new test case (with corresponding C++ header) to verify correct behavior when assigning to types with overloaded assignment operators and constructors. **C++ code generation improvements:** * Added `genCppConstructorExpr` in `ccgtypes.nim`, which generates a full type-prefixed constructor expression (e.g., `Foo(a, b)`) for C++ code generation, as opposed to just a braced initializer list. This is important for contexts like assignments where the type must be explicit. * Updated `resetLoc` in `cgen.nim` to use `genCppConstructorExpr` instead of `genCppInitializer` when initializing imported C++ types, ensuring correct code generation for assignments. **Testing:** * Added a new C++ header file, `tcpp_default_ctor_assignment.h`, defining a struct `AmbiguousAssign` with overloaded assignment operators and constructors to test ambiguous assignment scenarios. * Added a corresponding Nim test, `tcpp_default_ctor_assignment.nim`, which exercises construction and assignment for the imported C++ type, ensuring the new code generation logic works as intended. --- compiler/ccgtypes.nim | 18 +++++++++++++ compiler/cgen.nim | 2 +- tests/cpp/tcpp_default_ctor_assignment.h | 30 ++++++++++++++++++++++ tests/cpp/tcpp_default_ctor_assignment.nim | 14 ++++++++++ 4 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 tests/cpp/tcpp_default_ctor_assignment.h create mode 100644 tests/cpp/tcpp_default_ctor_assignment.nim diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim index 58343f2c44..98a85b38ae 100644 --- a/compiler/ccgtypes.nim +++ b/compiler/ccgtypes.nim @@ -2279,3 +2279,21 @@ proc genTypeSection(m: BModule, n: PNode) = discard getTypeDescAux(m, s.typ, intSet, descKindFromSymKind(s.kind)) if m.g.generatedHeader != nil: discard getTypeDescAux(m.g.generatedHeader, s.typ, intSet, descKindFromSymKind(s.kind)) + +# Unlike genCppInitializer which returns just the braced value list (e.g. "{a, b}"), +# genCppConstructorExpr returns a full type-prefixed expression (e.g. "Foo(a, b)"). +# This is used when a standalone construction expression is needed — e.g. on the +# right-hand side of an assignment — whereas genCppInitializer is used in variable +# declarations where the type is already written separately before the initializer. +proc genCppConstructorExpr(m: BModule, prc: BProc; typ: PType; didGenTemp: var bool): Snippet = + var params = "" + if typ.itemId in m.g.graph.initializersPerType: + let call = m.g.graph.initializersPerType[typ.itemId] + if call != nil: + var p = prc + if p == nil: + p = BProc(module: m) + params = genCppParamsForCtor(p, call, didGenTemp) + if prc == nil: + assert p.blocks.len == 0, "BProc belongs to a struct doesnt have blocks" + result = getTypeDesc(m, typ, dkVar) & "(" & params & ")" diff --git a/compiler/cgen.nim b/compiler/cgen.nim index b54160edf8..b1f6f862c7 100644 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -613,7 +613,7 @@ proc resetLoc(p: BProc, loc: var TLoc) = if isImportedCppType(typ): var didGenTemp = false let rl = rdLoc(loc) - let init = genCppInitializer(p.module, p, typ, didGenTemp) + let init = genCppConstructorExpr(p.module, p, typ, didGenTemp) p.s(cpsStmts).addAssignment(rl, init) return if optSeqDestructors in p.config.globalOptions and typ.kind in {tyString, tySequence}: diff --git a/tests/cpp/tcpp_default_ctor_assignment.h b/tests/cpp/tcpp_default_ctor_assignment.h new file mode 100644 index 0000000000..356cbb9381 --- /dev/null +++ b/tests/cpp/tcpp_default_ctor_assignment.h @@ -0,0 +1,30 @@ +#ifndef TCPP_DEFAULT_CTOR_ASSIGNMENT_H +#define TCPP_DEFAULT_CTOR_ASSIGNMENT_H + +struct AmbiguousAssign { + int x; + const char* y; + + AmbiguousAssign(): x(0), y(nullptr) {} + AmbiguousAssign(int x, const char* y): x(x), y(y) {} + + AmbiguousAssign& operator=(int v) { + x = v; + y = nullptr; + return *this; + } + + AmbiguousAssign& operator=(const char* s) { + x = 0; + y = s; + return *this; + } + + AmbiguousAssign& operator=(const AmbiguousAssign& other) { + x = other.x; + y = other.y; + return *this; + } +}; + +#endif \ No newline at end of file diff --git a/tests/cpp/tcpp_default_ctor_assignment.nim b/tests/cpp/tcpp_default_ctor_assignment.nim new file mode 100644 index 0000000000..88d9671ae6 --- /dev/null +++ b/tests/cpp/tcpp_default_ctor_assignment.nim @@ -0,0 +1,14 @@ +discard """ + cmd: "nim cpp $file" +""" + +type + AmbiguousAssign {.importcpp, header: "tcpp_default_ctor_assignment.h".} = object + x: cint + y: cstring + +proc main = + var xs = newSeq[AmbiguousAssign](3) + doAssert xs.len == 3 + +main() \ No newline at end of file From 985b1125b1e55e74daba8ed7207d9f0b66e416aa Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Fri, 3 Jul 2026 01:51:08 +0800 Subject: [PATCH 6/7] fixes genMagicExpr: handle mAsgn for Isolated[T] with primitive types in tuple assignment (#25955) Explicit `=sink` calls such as `Isolated[T].=sink` can delegate to a field type like `float`, which has no attached sink op. In that case `replaceHookMagic` leaves the builtin `mAsgn` call in place. `genMagicExpr` did not lower that shape, which caused the regression. Mapping `=sink` to `nkSinkAsgn` and other builtin assignment hooks to `nkAsgn`. --- compiler/ccgexprs.nim | 7 +++++++ tests/arc/tisolated_primitive.nim | 12 ++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 tests/arc/tisolated_primitive.nim diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim index 171d656230..df50d93954 100644 --- a/compiler/ccgexprs.nim +++ b/compiler/ccgexprs.nim @@ -2940,6 +2940,13 @@ proc genEnumToStr(p: BProc, e: PNode, d: var TLoc) = proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) = case op + of mAsgn: + let kind = if e[0].sym.name.s == "=sink": nkSinkAsgn else: nkAsgn + let lhs = e[1].skipHiddenAddr + let n = newTreeI(kind, e.info, lhs, e[2]) + n.typ = e.typ + cow(p, e[2]) + genAsgn(p, n, fastAsgn = kind != nkAsgn) of mOr, mAnd: genAndOr(p, e, d, op) of mNot..mUnaryMinusF64: unaryArith(p, e, d, op) of mUnaryMinusI..mAbsI: unaryArithOverflow(p, e, d, op) diff --git a/tests/arc/tisolated_primitive.nim b/tests/arc/tisolated_primitive.nim new file mode 100644 index 0000000000..05e760b155 --- /dev/null +++ b/tests/arc/tisolated_primitive.nim @@ -0,0 +1,12 @@ +# Issue: genMagicExpr: mAsgn internal error when using Isolated[T] with primitive types +# in tuple assignment to pointer dereference + +import std/isolation + +proc main() = + var x: ptr Isolated[float] + x = cast[ptr Isolated[float]](alloc0(sizeof(Isolated[float]))) + x[] = isolate(42.0) + dealloc(x) + +main() From c7ea004ca9b472e7fb2eec88e5e4107638207e15 Mon Sep 17 00:00:00 2001 From: Savant Date: Fri, 3 Jul 2026 03:55:58 -0400 Subject: [PATCH 7/7] js: cursor inference to elide nimCopy for safe value aliases (#25948) --- compiler/jsgen.nim | 16 ++++++++++++---- compiler/varpartitions.nim | 21 +++++++++++++++++---- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim index fd82a127f7..a04c8e8f95 100644 --- a/compiler/jsgen.nim +++ b/compiler/jsgen.nim @@ -34,7 +34,7 @@ import ropes, wordrecg, renderer, cgmeth, lowerings, sighashes, modulegraphs, lineinfos, transf, injectdestructors, sourcemap, astmsgs, pushpoppragmas, - mangleutils + mangleutils, varpartitions import pipelineutils @@ -1298,14 +1298,16 @@ proc genAsgnAux(p: PProc, x, y: PNode, noCopyNeeded: bool) = xtyp = etySeq case xtyp of etySeq: - if x.typ.kind in {tyVar, tyLent} or (needsNoCopy(p, y) and needsNoCopy(p, x)) or noCopyNeeded: + if x.typ.kind in {tyVar, tyLent} or (needsNoCopy(p, y) and needsNoCopy(p, x)) or noCopyNeeded or + (x.kind == nkSym and sfCursor in x.sym.flags): lineF(p, "$1 = $2;$n", [a.rdLoc, b.rdLoc]) else: useMagic(p, "nimCopy") lineF(p, "$1 = nimCopy(null, $2, $3);$n", [a.rdLoc, b.res, genTypeInfo(p, y.typ)]) of etyObject: - if x.typ.kind in {tyVar, tyLent, tyOpenArray, tyVarargs} or (needsNoCopy(p, y) and needsNoCopy(p, x)) or noCopyNeeded: + if x.typ.kind in {tyVar, tyLent, tyOpenArray, tyVarargs} or (needsNoCopy(p, y) and needsNoCopy(p, x)) or noCopyNeeded or + (x.kind == nkSym and sfCursor in x.sym.flags): lineF(p, "$1 = $2;$n", [a.rdLoc, b.rdLoc]) else: useMagic(p, "nimCopy") @@ -2092,7 +2094,8 @@ proc genVarInit(p: PProc, v: PSym, n: PNode) = gen(p, n, a) case mapType(p, v.typ) of etyObject, etySeq: - if v.typ.kind in {tyOpenArray, tyVarargs} or needsNoCopy(p, n): + if v.typ.kind in {tyOpenArray, tyVarargs} or needsNoCopy(p, n) or + sfCursor in v.flags: s = a.res else: useMagic(p, "nimCopy") @@ -2798,6 +2801,11 @@ proc genProc(oldProc: PProc, prc: PSym): Rope = var transformedBody = transformBody(p.module.graph, p.module.idgen, prc, {}) if sfInjectDestructors in prc.flags: transformedBody = injectDestructorCalls(p.module.graph, p.module.idgen, prc, transformedBody) + else: + # JS has a GC, so the destructor pass is off; but the cursor (alias) analysis + # is independent of ownership and always memory-safe on a traced target. + # Running it lets last-use `var b = a` aliases skip the deep `nimCopy`. + computeCursors(prc, transformedBody, p.module.graph) p.nested: genStmt(p, transformedBody) diff --git a/compiler/varpartitions.nim b/compiler/varpartitions.nim index fea6cc540b..3a01eb0bbd 100644 --- a/compiler/varpartitions.nim +++ b/compiler/varpartitions.nim @@ -677,9 +677,13 @@ proc deps(c: var Partitions; dest, src: PNode) = else: let srcid = variableId(c, s) if srcid >= 0: - if s.kind notin {skResult, skParam} and ( - c.s[srcid].aliveEnd < c.s[vid].aliveEnd): - # you cannot borrow from a local that lives shorter than 'vid': + if s.kind notin {skResult, skParam} and + c.s[srcid].aliveEnd < c.s[vid].aliveEnd and + c.g.config.backend != backendJs: + # you cannot borrow from a local that lives shorter than 'vid'. + # On a traced (JS/GC) target the source object stays alive as long + # as the alias references it, so this lifetime rule does not apply; + # value-semantics safety is enforced by `dangerousMutation` instead. when explainCursors: echo "B not a cursor ", d.sym, " ", c.s[srcid].aliveEnd, " ", c.s[vid].aliveEnd c.s[vid].flags.incl preventCursor elif {isReassigned, preventCursor} * c.s[srcid].flags != {}: @@ -1003,13 +1007,22 @@ proc checkBorrowedLocations*(par: var Partitions; body: PNode; config: ConfigRef #if par.s[rid].con.kind == isRootOf and dangerousMutation(par.graphs[par.s[rid].con.graphIndex], par.s[i]): # cannotBorrow(config, s, par.graphs[par.s[rid].con.graphIndex]) +proc jsDeepCopied(t: PType): bool = + ## On the JS backend `nimCopy` deep-copies these type classes on every + ## assignment, so eliding the copy for a safe alias is worthwhile even when + ## the type has no C-style destructor. + t.skipTypes({tyGenericInst, tyAlias, tyDistinct, tyVar, tyLent}).kind in + {tyObject, tyTuple, tyArray, tySequence, tyString} + proc computeCursors*(s: PSym; n: PNode; g: ModuleGraph) = + let jsCursors = g.config.backend == backendJs var par = computeGraphPartitions(s, n, g, {cursorInference}) for i in 0 ..< par.s.len: let v = addr(par.s[i]) if v.flags * {ownsData, preventCursor, isConditionallyReassigned} == {} and v.sym.kind notin {skParam, skResult} and - v.sym.flags * {sfThread, sfGlobal} == {} and hasDestructor(v.sym.typ) and + v.sym.flags * {sfThread, sfGlobal} == {} and + (hasDestructor(v.sym.typ) or (jsCursors and jsDeepCopied(v.sym.typ))) and v.sym.typ.skipTypes({tyGenericInst, tyAlias}).kind != tyOwned and (getAttachedOp(g, v.sym.typ, attachedAsgn) == nil or sfError notin getAttachedOp(g, v.sym.typ, attachedAsgn).flags):