From ebfd1c50902816bfe761852044a0a4900fb0ef7c Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Tue, 11 Aug 2026 22:27:49 +0200 Subject: [PATCH 1/9] fixes #26025 (#26076) --- compiler/semdata.nim | 7 +++++++ compiler/semstmts.nim | 18 +++++++++++++++++ compiler/semtypes.nim | 19 ++++++++++++++++++ tests/arc/tarcmisc.nim | 44 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 88 insertions(+) diff --git a/compiler/semdata.nim b/compiler/semdata.nim index 89e8911975..50b33d48a1 100644 --- a/compiler/semdata.nim +++ b/compiler/semdata.nim @@ -186,6 +186,12 @@ type forwardFieldUpdates*: seq[(PType, PNode, PType)] # object/tuple field definitions whose default values mention forward # types and need delayed const checking + forwardFlagUpdates*: seq[(PType, PType)] + # (owner, son) pairs whose `propagateToOwner` ran on a not yet reified + # forward type and has to be redone in the final pass + staleTypeFlags*: IntSet + # ids of the owners in `forwardFlagUpdates`; their flags are provisional + # too, so reading them makes the reader provisional in turn inTypeofContext*: int semAsgnOpr*: proc (c: PContext; n: PNode; k: TNodeKind): PNode {.nimcall.} @@ -369,6 +375,7 @@ proc newContext*(graph: ModuleGraph; module: PSym): PContext = unknownIdents: initIntSet(), shadowDiscardedDefs: initIntSet(), realizedDefs: initIntSet(), + staleTypeFlags: initIntSet(), cache: graph.cache, graph: graph, signatures: initStrTable(), diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 4b9dd3e7a4..cee96cd9d9 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -1837,6 +1837,24 @@ proc typeSectionFinalPass(c: PContext, n: PNode) = for (owner, field, expectedType) in c.forwardFieldUpdates: semDelayedFieldDefault(c, owner, expectedType, field) c.forwardFieldUpdates = @[] + + # a son that still was a `tyForward` could not propagate `tfHasAsgn` and + # friends to its owner back then, see `rememberFlagUpdate`. Now that every + # forward declaration has a body, redo those propagations. They are recorded + # in declaration order rather than dependency order and an owner can itself + # be the son of another pair, so repeat until nothing changes; this + # terminates because flags are only ever added. + if c.forwardFlagUpdates.len > 0: + let updates = move c.forwardFlagUpdates + c.staleTypeFlags = initIntSet() + var changed = true + while changed: + changed = false + for (owner, elem) in updates: + let before = owner.flags + propagateToOwner(owner, elem) + if owner.flags != before: changed = true + for i in 0.. 0: c.getCurrOwner else: rectype.sym for i in 0.. Date: Fri, 14 Aug 2026 19:36:55 +0800 Subject: [PATCH 2/9] =?UTF-8?q?fixes=20#26104;=20prevent=20compile-time-on?= =?UTF-8?q?ly=20`typeof`=20from=20being=20treated=20a=E2=80=A6=20(#26105)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …s a runtime alias fixes #26104 Follows up https://github.com/nim-lang/Nim/pull/25994 --- compiler/aliases.nim | 13 +++++++++++++ tests/ccgbugs/t26104.nim | 16 ++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 tests/ccgbugs/t26104.nim diff --git a/compiler/aliases.nim b/compiler/aliases.nim index 6877028c3a..639367b9b6 100644 --- a/compiler/aliases.nim +++ b/compiler/aliases.nim @@ -25,6 +25,11 @@ type pfStructural ## use structural prefix-chain detection and tree-walk pfBidirectional ## also check reverse direction per field in nkObjConstr +proc isCompileTimeOnlyNode(n: PNode): bool {.inline.} = + ## `typeof` and typedesc/static values describe types at compile time; they + ## do not read the runtime location that alias analysis is protecting. + n.kind == nkTypeOfExpr or (n.typ != nil and n.typ.isCompileTimeOnly) + func sameLocation(a, b: PNode): bool = template sameConstIndex(a, b: PNode): bool = a.kind in nkLiterals and b.kind in nkLiterals and a.intVal == b.intVal @@ -157,6 +162,9 @@ proc isPartOf*(a, b: PNode; flags: set[PartFlag] = {}): TAnalysisResult = ## ## x[] ?<| y depending on type ## ``` + if a.isCompileTimeOnlyNode or b.isCompileTimeOnlyNode: + return arNo + if a.kind == b.kind: case a.kind of nkSym: @@ -271,6 +279,11 @@ proc isPartOf*(a, b: PNode; flags: set[PartFlag] = {}): TAnalysisResult = of nkCallKinds: result = arNo for i in 1.. Date: Sat, 15 Aug 2026 07:45:58 +0200 Subject: [PATCH 3/9] deprecate hotCodeReloading (#26107) See https://github.com/nim-lang/RFCs/issues/573 - deprecating for visibility in 2.4, in case a maintainer wants to step up - else it can be binned for 2.6 --- compiler/commands.nim | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/commands.nim b/compiler/commands.nim index 123e404141..597063a680 100644 --- a/compiler/commands.nim +++ b/compiler/commands.nim @@ -843,6 +843,7 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo; of "hotcodereloading": processOnOffSwitchG(conf, {optHotCodeReloading}, arg, pass, info) if conf.hcrOn: + warningDeprecated(conf, info, "hotCodeReloading is deprecated, see https://github.com/nim-lang/RFCs/issues/573 for further information") defineSymbol(conf.symbols, "hotcodereloading") defineSymbol(conf.symbols, "useNimRtl") # hardcoded linking with dynamic runtime for MSVC for smaller binaries From 10f0e5e9ac07ec4927517c9076f7a8bb53c53d92 Mon Sep 17 00:00:00 2001 From: Jacek Sieka Date: Sat, 15 Aug 2026 07:48:58 +0200 Subject: [PATCH 4/9] rm `genCaseObjDiscMapping` (#26097) No longer used --- compiler/ccgstmts.nim | 16 ----------- compiler/enumtostr.nim | 62 ------------------------------------------ 2 files changed, 78 deletions(-) diff --git a/compiler/ccgstmts.nim b/compiler/ccgstmts.nim index 928caeacd0..cffee9c77d 100644 --- a/compiler/ccgstmts.nim +++ b/compiler/ccgstmts.nim @@ -1918,22 +1918,6 @@ proc genDiscriminantCheck(p: BProc, a, tmp: TLoc, objtype: PType, if p.config.exc == excGoto: raiseExit(p) -when false: - proc genCaseObjDiscMapping(p: BProc, e: PNode, t: PType, field: PSym; d: var TLoc) = - const ObjDiscMappingProcSlot = -5 - var theProc: PSym = nil - for idx, p in items(t.methods): - if idx == ObjDiscMappingProcSlot: - theProc = p - break - if theProc == nil: - theProc = genCaseObjDiscMapping(t, field, e.info, p.module.g.graph, p.module.idgen) - t.methods.add((ObjDiscMappingProcSlot, theProc)) - var call = newNodeIT(nkCall, e.info, getSysType(p.module.g.graph, e.info, tyUInt8)) - call.add newSymNode(theProc) - call.add e - expr(p, call, d) - proc asgnFieldDiscriminant(p: BProc, e: PNode) = var dotExpr = e.firstSon if dotExpr.kind == nkCheckedFieldExpr: dotExpr = dotExpr.firstSon diff --git a/compiler/enumtostr.nim b/compiler/enumtostr.nim index a21d744bea..9210e8db2e 100644 --- a/compiler/enumtostr.nim +++ b/compiler/enumtostr.nim @@ -49,65 +49,3 @@ proc genEnumToStrProc*(t: PType; info: TLineInfo; g: ModuleGraph; idgen: IdGener result.ast = n incl result.flagsImpl, {sfFromGeneric, sfNeverRaises} setHookDisamb(g, result, "$enumtostr", t) - -proc searchObjCaseImpl(obj: PNode; field: PSym): PNode = - case obj.kind - of nkSym: - result = nil - of nkElse, nkOfBranch: - result = searchObjCaseImpl(obj.lastSon, field) - else: - if obj.kind == nkRecCase and obj[0].kind == nkSym and obj[0].sym == field: - result = obj - else: - result = nil - for x in obj: - result = searchObjCaseImpl(x, field) - if result != nil: break - -proc searchObjCase(t: PType; field: PSym): PNode = - result = searchObjCaseImpl(t.n, field) - if result == nil and t.baseClass != nil: - result = searchObjCase(t.baseClass.skipTypes({tyAlias, tyGenericInst, tyRef, tyPtr}), field) - doAssert result != nil - -proc genCaseObjDiscMapping*(t: PType; field: PSym; info: TLineInfo; g: ModuleGraph; idgen: IdGenerator): PSym = - result = newSym(skProc, getIdent(g.cache, "objDiscMapping"), idgen, t.owner, info) - - let dest = newSym(skParam, getIdent(g.cache, "e"), idgen, result, info) - dest.typ = field.typ - - let res = newSym(skResult, getIdent(g.cache, "result"), idgen, result, info) - res.typ = getSysType(g, info, tyUInt8) - - result.typ = newType(tyProc, idgen, t.owner) - result.typ.n = newNodeI(nkFormalParams, info) - rawAddSon(result.typ, res.typ) - result.typ.n.add newNodeI(nkEffectList, info) - - result.typ.addParam dest - - var body = newNodeI(nkStmtList, info) - var caseStmt = newNodeI(nkCaseStmt, info) - caseStmt.add(newSymNode dest) - - let subObj = searchObjCase(t, field) - for i in 1.. Date: Sat, 15 Aug 2026 13:51:13 +0800 Subject: [PATCH 5/9] fixes #25992; fix GC tracing of stale bytes in case objects during reset (#26003) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fixes #25992 ```nim type Foo = object case kind: bool of true: a: ref Bar # 8 bytes (pointer) of false: b: int # 4 bytes ``` specializeResetT for b emits accessor.b = 0 — writes 4 bytes But the union is 8 bytes wide (sized by the largest branch) The remaining 4 bytes where a used to live are untouched Those stale bytes could contain a heap pointer the GC traces → crash Add nimZeroMem after specializeResetN for case objects to clear the entire union including unused branch bytes. --- compiler/ccgreset.nim | 17 +++++++++++ tests/gc/tmove_case_object.nim | 56 ++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 tests/gc/tmove_case_object.nim diff --git a/compiler/ccgreset.nim b/compiler/ccgreset.nim index 84478dd07e..57ea5fc793 100644 --- a/compiler/ccgreset.nim +++ b/compiler/ccgreset.nim @@ -75,6 +75,23 @@ proc specializeResetT(p: BProc, accessor: Rope, typ: PType) = cSizeof(getTypeDesc(p.module, typ))) else: specializeResetN(p, accessor, typ.n, typ) + if isCaseObj(typ.n): + # The active branch was released above. Clear the complete object so + # stale bytes from overlapping branches cannot be traced by the GC. + # type + # Foo = object + # case kind: bool + # of true: + # a: ref Bar # 8 bytes (pointer) + # of false: + # b: int # 4 bytes + # specializeResetT for b emits accessor.b = 0 — writes 4 bytes + # But the union is 8 bytes wide (sized by the largest branch) + # The remaining 4 bytes where a used to live are untouched + # Those stale bytes could contain a heap pointer the GC traces → crash + p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimZeroMem"), + cCast(CPointer, cAddr(accessor)), + cSizeof(getTypeDesc(p.module, typ))) of tyTuple: let typ = getUniqueType(typ) for i, a in typ.ikids: diff --git a/tests/gc/tmove_case_object.nim b/tests/gc/tmove_case_object.nim new file mode 100644 index 0000000000..8f4d9ba450 --- /dev/null +++ b/tests/gc/tmove_case_object.nim @@ -0,0 +1,56 @@ +discard """ + matrix: "--mm:refc; --mm:orc" +""" + +type + A = object of RootObj + + V = object + case g: bool + of true: + v: A + of false: + e: string + +var r = V(g: true, v: A()) +discard move r +GC_fullCollect() + +type + Kind = enum nested, other + Nested = object + case kind: Kind + of nested: + case enabled: bool + of true: payload: A + of false: message: string + of other: + discard + +var n = Nested(kind: nested, enabled: true, payload: A()) +discard move n +GC_fullCollect() + +# Moving from the other branch must keep its value alive and leave the source +# in the default state. +var s = V(g: false, e: "hello") +let moved = move s +doAssert moved.e == "hello" +doAssert not s.g +doAssert s.e.len == 0 + +# Reinitializing the zeroed value must also restore embedded object type +# headers. +type W = object + a: A + value: V + text: string + +var w = W(a: A(), value: V(g: true, v: A()), text: "content") +let movedW = move w +doAssert movedW.text == "content" +doAssert cast[ptr pointer](addr w.a)[] != nil +doAssert not w.value.g +doAssert w.value.e.len == 0 +doAssert w.text.len == 0 +GC_fullCollect() From 43f7631b1c3eb6341b94a76230e52b0265274ec0 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Mon, 17 Aug 2026 12:22:53 +0200 Subject: [PATCH 6/9] Memregion pool no handle (#26110) Co-authored-by: SirOlaf <34164198+SirOlaf@users.noreply.github.com> --- lib/system.nim | 16 +- lib/system/alloc.nim | 203 +++++++++++++----- lib/system/arc.nim | 18 +- lib/system/threadimpl.nim | 11 + tests/threads/tthreadallocatorforeignpool.nim | 59 +++++ tests/threads/tthreadallocatorhandoffrace.nim | 64 ++++++ tests/threads/tthreadallocatorpool.nim | 92 ++++++++ tests/threads/tthreadallocatorpoolrace.nim | 56 +++++ 8 files changed, 460 insertions(+), 59 deletions(-) create mode 100644 tests/threads/tthreadallocatorforeignpool.nim create mode 100644 tests/threads/tthreadallocatorhandoffrace.nim create mode 100644 tests/threads/tthreadallocatorpool.nim create mode 100644 tests/threads/tthreadallocatorpoolrace.nim diff --git a/lib/system.nim b/lib/system.nim index 313f90969c..d4896bdc4a 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -1154,7 +1154,19 @@ template sysAssert(cond: bool, msg: string) = cstderr.rawWrite "\n" rawQuit 1 -const hasAlloc = (hostOS != "standalone" or not defined(nogc)) and not defined(nimscript) +const + hasAlloc = (hostOS != "standalone" or not defined(nogc)) and not defined(nimscript) + hasDefaultAllocator = + hasAlloc and + not (defined(useNimRtl) or defined(useMalloc) or defined(gcRegions) or + defined(nogc) or defined(boehmgc) or defined(gogc)) + hasThreadLocalAllocator = + hasDefaultAllocator and hasThreadSupport and defined(gcDestructors) + +when hasThreadLocalAllocator: + # threadimpl is included before mmdisp provides these implementations. + proc initThreadAllocator() {.gcsafe, raises: [].} + proc releaseThreadAllocator() {.gcsafe, raises: [].} when notJSnotNims and hasAlloc and not defined(nimSeqsV2): proc addChar(s: NimString, c: char): NimString {.compilerproc, gcsafe.} @@ -2425,6 +2437,8 @@ when notJSnotNims and hasAlloc: {.push profiler: off.} include "system/mmdisp" {.pop.} + when hasThreadLocalAllocator: + initThreadAllocator() {.push stackTrace: off, profiler: off.} when not defined(nimSeqsV2): include "system/sysstr" diff --git a/lib/system/alloc.nim b/lib/system/alloc.nim index 880fdeb388..2de1d9f7e5 100644 --- a/lib/system/alloc.nim +++ b/lib/system/alloc.nim @@ -40,7 +40,7 @@ template track(op, address, size) = # # A deallocation of a small pointer then looks like this #[ - dealloc -> rawDealloc -> chunk.owner == addr(a) --------------> This thread owns the chunk ------> The current chunk is active -> Chunk is completely unused -----> Chunk references no foreign cells + dealloc -> rawDealloc -> chunk.owner == regionOwner(a) -------> This thread owns the chunk ------> The current chunk is active -> Chunk is completely unused -----> Chunk references no foreign cells | | (Add cell into the current chunk) | Return the current chunk back to tlsf | | | | v v v v @@ -63,6 +63,11 @@ const # size of chunks in last matrix bin MaxBigChunkSize = int(1'i32 shl MaxFli - 1'i32 shl (MaxFli-MaxLog2Sli-1)) HugeChunkSize = MaxBigChunkSize + 1 + usesRegionHandles = hasThreadSupport and defined(gcDestructors) + # Deliberately *not* `hasThreadLocalAllocator`: this selects the chunk + # layout, which is ABI and must match between a `--useNimRtl` client and + # the RTL it links against. Whether this module owns a thread local region + # is the separate question that `hasThreadLocalAllocator` answers. type PTrunk = ptr Trunk @@ -112,11 +117,15 @@ type PChunk = ptr BaseChunk PBigChunk = ptr BigChunk PSmallChunk = ptr SmallChunk + SharedFreeLists = array[0..max(1, SmallChunkSize div MemAlign-1), ptr FreeCell] BaseChunk {.pure, inheritable.} = object prevSize: int # size of previous chunk; for coalescing # 0th bit == 1 if 'used size: int # if < PageSize it is a small chunk - owner: ptr MemRegion + when usesRegionHandles: + owner: ptr RegionHandle + else: + owner: ptr MemRegion SmallChunk = object of BaseChunk next, prev: PSmallChunk # chunks of the same size @@ -145,14 +154,16 @@ type next: ptr HeapLinks MemRegion = object + when usesRegionHandles: + regionHandle: ptr RegionHandle when not defined(gcDestructors): minLargeObj, maxLargeObj: int freeSmallChunks: array[0..max(1, SmallChunkSize div MemAlign-1), PSmallChunk] # List of available chunks per size class. Only one is expected to be active per class. when defined(gcDestructors): - sharedFreeLists: array[0..max(1, SmallChunkSize div MemAlign-1), ptr FreeCell] - # When a thread frees a pointer it did not create, it must not adjust the counters. - # Instead, the cell is placed here and deferred until the next allocation. + sharedFreeLists: SharedFreeLists + # Used directly without threads. Threaded builds use RegionHandle but + # retain this 2 KiB spacer: removing it regresses 2-4 KiB allocations. flBitmap: uint32 slBitmap: array[RealFli, uint32] matrix: array[RealFli, array[MaxSli, PBigChunk]] @@ -160,7 +171,7 @@ type currMem, maxMem, freeMem, occ: int # memory sizes (allocated from OS) lastSize: int # needed for the case that OS gives us pages linearly when defined(gcDestructors): - sharedFreeListBigChunks: PBigChunk # make no attempt at avoiding false sharing for now for this object field + sharedFreeListBigChunks: PBigChunk # private pending list with threads; shared queue otherwise chunkStarts: IntSet when not defined(gcDestructors): @@ -173,9 +184,24 @@ type when defined(nimTypeNames): allocCounter, deallocCounter: int + RegionHandle = object + # Permanent chunk-owner identity and home of the remote-free queues. + sharedFreeLists: SharedFreeLists + sharedFreeListBigChunks: PBigChunk + # Keep the movable allocator state with its permanent owner while the + # owning thread is retired. + region: MemRegion + next: ptr RegionHandle + template smallChunkOverhead(): untyped = sizeof(SmallChunk) template bigChunkOverhead(): untyped = sizeof(BigChunk) +template regionOwner(a: var MemRegion): untyped = + when usesRegionHandles: + a.regionHandle + else: + addr a + when hasThreadSupport: template loada(x: untyped): untyped = atomicLoadN(unsafeAddr x, ATOMIC_RELAXED) template storea(x, y: untyped) = atomicStoreN(unsafeAddr x, y, ATOMIC_RELAXED) @@ -502,6 +528,56 @@ proc pageAddr(p: pointer): PChunk {.inline.} = result = cast[PChunk](cast[int](p) and not PageMask) #sysAssert(Contains(allocator.chunkStarts, pageIndex(result))) +when hasThreadLocalAllocator: + var + regionPool: ptr RegionHandle + regionPoolLock: SysLock + initSysLock(regionPoolLock) + + proc moveMemRegion(dest, source: ptr MemRegion) {.inline.} = + # MemRegion owns only raw allocator state, so transfer it bitwise and + # clear the source to leave exactly one owner. + copyMem(dest, source, sizeof(MemRegion)) + zeroMem(source, sizeof(MemRegion)) + + proc acquireMemRegion(a: var MemRegion) {.raises: [], gcsafe.} = + if a.regionHandle != nil: + return + + acquireSys(regionPoolLock) + let handle = regionPool + if handle != nil: + regionPool = handle.next + releaseSys(regionPoolLock) + + if handle == nil: + # RegionHandle is larger than llAlloc's one-page metadata slabs and is + # retained independently of any checked-out MemRegion. + let handleSize = roundup(sizeof(RegionHandle), PageSize) + let newHandle = cast[ptr RegionHandle](osAllocPages(handleSize)) + zeroMem(newHandle, sizeof(RegionHandle)) + a.regionHandle = newHandle + else: + moveMemRegion(addr a, addr handle.region) + + proc releaseMemRegion(a: var MemRegion) {.raises: [], gcsafe.} = + # Zeroing `a` also clears `a.regionHandle`, which is what keeps a late + # `dealloc` on this thread correct: the ownership test can no longer match, + # so the cell is routed to its real owner's handle instead of to a region + # that is about to be reused. A late *alloc* on the other hand would mint + # chunks with a nil owner, so nothing may allocate after this point -- + # `afterThreadRuns` has already run by the time `threadProcWrapStackFrame` + # gets here. + if a.regionHandle == nil: + return + let handle = a.regionHandle + moveMemRegion(addr handle.region, addr a) + + acquireSys(regionPoolLock) + handle.next = regionPool + regionPool = handle + releaseSys(regionPoolLock) + when false: proc writeFreeList(a: MemRegion) = var it = a.freeChunksList @@ -618,7 +694,7 @@ proc splitChunk2(a: var MemRegion, c: PBigChunk, size: int): PBigChunk = result.prev = nil # size and not used: result.prevSize = size - result.owner = addr a + result.owner = regionOwner(a) sysAssert((size and 1) == 0, "splitChunk 2") sysAssert((size and PageMask) == 0, "splitChunk: size is not a multiple of the PageSize") @@ -686,7 +762,7 @@ proc getBigChunk(a: var MemRegion, size: int): PBigChunk = # if we over allocated split the chunk: if result.size > size: splitChunk(a, result, size) - result.owner = addr a + result.owner = regionOwner(a) else: removeChunkFromMatrix2(a, result, fl, sl) if result.size >= size + PageSize: @@ -694,7 +770,7 @@ proc getBigChunk(a: var MemRegion, size: int): PBigChunk = # set 'used' to true: result.prevSize = 1 track("setUsedToFalse", addr result.size, sizeof(int)) - sysAssert result.owner == addr a, "getBigChunk: No owner set!" + sysAssert result.owner == regionOwner(a), "getBigChunk: No owner set!" incl(a, a.chunkStarts, pageIndex(result)) dec(a.freeMem, size) @@ -710,7 +786,7 @@ proc getHugeChunk(a: var MemRegion; size: int): PBigChunk = result.size = size # set 'used' to true: result.prevSize = 1 - result.owner = addr a + result.owner = regionOwner(a) incl(a, a.chunkStarts, pageIndex(result)) proc freeHugeChunk(a: var MemRegion; c: PBigChunk) = @@ -791,7 +867,7 @@ proc deallocBigChunk(a: var MemRegion, c: PBigChunk) = when defined(gcDestructors): template atomicPrepend(head, elem: untyped) = # see also https://en.cppreference.com/w/cpp/atomic/atomic_compare_exchange - when hasThreadSupport: + when usesRegionHandles: while true: elem.next.storea head.loada if atomicCompareExchangeN(addr head, addr elem.next, elem, weak = true, ATOMIC_RELEASE, ATOMIC_RELAXED): @@ -800,30 +876,39 @@ when defined(gcDestructors): elem.next.storea head.loada head.storea elem - proc addToSharedFreeListBigChunks(a: var MemRegion; c: PBigChunk) {.inline.} = - sysAssert c.next == nil, "c.next pointer must be nil" - atomicPrepend a.sharedFreeListBigChunks, c + when usesRegionHandles: + proc addToSharedFreeListBigChunks(handle: ptr RegionHandle; + c: PBigChunk) {.inline.} = + sysAssert c.next == nil, "c.next pointer must be nil" + atomicPrepend handle.sharedFreeListBigChunks, c + else: + proc addToSharedFreeListBigChunks(a: var MemRegion; + c: PBigChunk) {.inline.} = + sysAssert c.next == nil, "c.next pointer must be nil" + atomicPrepend a.sharedFreeListBigChunks, c proc takeFromSharedFreeListBigChunks(a: var MemRegion): PBigChunk {.inline.} = - when hasThreadSupport: - while true: - result = atomicLoadN(addr a.sharedFreeListBigChunks, ATOMIC_ACQUIRE) - if result == nil: - break - let next = result.next.loada - var expected = result - if atomicCompareExchangeN(addr a.sharedFreeListBigChunks, addr expected, next, - weak = true, ATOMIC_ACQUIRE, ATOMIC_RELAXED): - result.next.storea nil - break - else: - result = a.sharedFreeListBigChunks - if result != nil: - a.sharedFreeListBigChunks = result.next - result.next = nil + when usesRegionHandles: + if a.sharedFreeListBigChunks == nil: + let sharedHead = addr a.regionHandle.sharedFreeListBigChunks + # Detach a batch from the stable remote inbox. The embedded MemRegion + # field is now a private pending list and moves with the region. + if atomicLoadN(sharedHead, ATOMIC_RELAXED) != nil: + a.sharedFreeListBigChunks = atomicExchangeN(sharedHead, nil, + ATOMIC_ACQUIRE) + result = a.sharedFreeListBigChunks + if result != nil: + a.sharedFreeListBigChunks = result.next + result.next = nil - proc addToSharedFreeList(c: PSmallChunk; f: ptr FreeCell; size: int) {.inline.} = - atomicPrepend c.owner.sharedFreeLists[size], f + when usesRegionHandles: + proc addToSharedFreeList(handle: ptr RegionHandle; f: ptr FreeCell; + size: int) {.inline.} = + atomicPrepend handle.sharedFreeLists[size], f + else: + proc addToSharedFreeList(c: PSmallChunk; f: ptr FreeCell; + size: int) {.inline.} = + atomicPrepend c.owner.sharedFreeLists[size], f const MaxSteps = 20 @@ -846,9 +931,8 @@ when defined(gcDestructors): dec(a.occ, total) proc freeDeferredObjects(a: var MemRegion) = - # Pop only as many nodes as we can process. Detaching the entire list and - # re-enqueuing its unprocessed tail through atomicPrepend would overwrite - # that tail's next pointer and lose the rest of the list. + # Bound the work per allocation. With threads, takeFromSharedFreeListBigChunks + # detaches the shared stack into the region's private pending list first. for _ in 0..MaxSteps: let it = takeFromSharedFreeListBigChunks(a) if it == nil: break @@ -892,17 +976,20 @@ proc rawAlloc(a: var MemRegion, requestedSize: int, alignment: int = 0): pointer if size + alignOff <= SmallChunkSize-smallChunkOverhead(): template fetchSharedCells(tc: PSmallChunk) = - # Consumes cells from (potentially) foreign threads from `a.sharedFreeLists[s]` + # Consume cells freed by potentially foreign threads. when defined(gcDestructors): if tc.freeList == nil: - when hasThreadSupport: - # Steal the entire list from `sharedFreeList`: - tc.freeList = atomicExchangeN(addr a.sharedFreeLists[s], nil, ATOMIC_RELAXED) + when usesRegionHandles: + let sharedHead = addr tc.owner.sharedFreeLists[s] + # The owner is the only consumer, so once it observes a non-empty + # stack no other thread can make it empty before the exchange. + if atomicLoadN(sharedHead, ATOMIC_RELAXED) != nil: + tc.freeList = atomicExchangeN(sharedHead, nil, ATOMIC_ACQUIRE) else: tc.freeList = a.sharedFreeLists[s] a.sharedFreeLists[s] = nil - # if `tc.freeList` isn't nil, `tc` will gain capacity. - # We must calculate how much it gained and how many foreign cells are included. + # If `tc.freeList` isn't nil, `tc` gains capacity. Calculate how + # much it gained and how many foreign cells are included. compensateCounters(a, tc, size) # allocate a small block: for small chunks, we use only its next pointer @@ -921,11 +1008,11 @@ proc rawAlloc(a: var MemRegion, requestedSize: int, alignment: int = 0): pointer c.size = size c.acc = (alignOff + size).uint32 c.free = SmallChunkSize - smallChunkOverhead() - alignOff.int32 - size.int32 - sysAssert c.owner == addr(a), "rawAlloc: No owner set!" + sysAssert c.owner == regionOwner(a), "rawAlloc: No owner set!" c.next = nil c.prev = nil - # Shared cells are fetched here in case `c.size * 2 >= SmallChunkSize - smallChunkOverhead()`. - # For those single cell chunks, we would otherwise have to allocate a new one almost every time. + # Fetch deferred cells here for single-cell chunks; otherwise every + # allocation of that size would tend to allocate a new chunk. fetchSharedCells(c) if c.free >= size: # Because removals from `a.freeSmallChunks[s]` only happen in the other alloc branch and during dealloc, @@ -963,9 +1050,8 @@ proc rawAlloc(a: var MemRegion, requestedSize: int, alignment: int = 0): pointer dec(c.free, size) sysAssert((cast[int](result) and (MemAlign-1)) == 0, "rawAlloc 9") sysAssert(allocInv(a), "rawAlloc: end c != nil") - # We fetch deferred cells *after* advancing `c.freeList`/`acc` to adjust `c.free`. - # If after the adjustment it turns out there's free cells available, - # the chunk stays in `a.freeSmallChunks[s]` and the need for a new chunk is delayed. + # Fetch after advancing `freeList`/`acc` so `c.free` can be adjusted. If + # cells arrived, keep this chunk active instead of allocating another. fetchSharedCells(c) sysAssert(allocInv(a), "rawAlloc: before c.free < size") if c.free < size: @@ -1030,7 +1116,8 @@ proc rawDealloc(a: var MemRegion, p: pointer) = # ^ We might access thread foreign storage here. # The other thread cannot possibly free this block as it's still alive. var f = cast[ptr FreeCell](p) - if c.owner == addr(a): + let owner = c.owner + if owner == regionOwner(a): # We own the block, there is no foreign thread involved. dec a.occ, s untrackSize(s) @@ -1093,7 +1180,10 @@ proc rawDealloc(a: var MemRegion, p: pointer) = when logAlloc: cprintf("dealloc(pointer_%p) # SMALL FROM %p CALLER %p\n", p, c.owner, addr(a)) when defined(gcDestructors): - addToSharedFreeList(c, f, s div MemAlign) + when usesRegionHandles: + addToSharedFreeList(owner, f, s div MemAlign) + else: + addToSharedFreeList(c, f, s div MemAlign) sysAssert(((cast[int](p) and PageMask) - smallChunkOverhead() - c.chunkAlignOff) %% s == 0, "rawDealloc 2") else: @@ -1101,10 +1191,14 @@ proc rawDealloc(a: var MemRegion, p: pointer) = when overwriteFree: nimSetMem(p, -1'i32, c.size -% bigChunkOverhead()) when logAlloc: cprintf("dealloc(pointer_%p) # BIG %p\n", p, c.owner) when defined(gcDestructors): - if c.owner == addr(a): + let owner = c.owner + if owner == regionOwner(a): deallocBigChunk(a, cast[PBigChunk](c)) else: - addToSharedFreeListBigChunks(c.owner[], cast[PBigChunk](c)) + when usesRegionHandles: + addToSharedFreeListBigChunks(owner, cast[PBigChunk](c)) + else: + addToSharedFreeListBigChunks(owner[], cast[PBigChunk](c)) else: deallocBigChunk(a, cast[PBigChunk](c)) @@ -1263,6 +1357,13 @@ when defined(nimTypeNames): template instantiateForRegion(allocator: untyped) {.dirty.} = {.push stackTrace: off.} + when hasThreadLocalAllocator: + proc initThreadAllocator() {.gcsafe, raises: [].} = + acquireMemRegion(allocator) + + proc releaseThreadAllocator() {.gcsafe, raises: [].} = + releaseMemRegion(allocator) + when defined(nimFulldebug): proc interiorAllocatedPtr*(p: pointer): pointer = result = interiorAllocatedPtr(allocator, p) diff --git a/lib/system/arc.nim b/lib/system/arc.nim index cd74e7f4fa..8c6efbd3f6 100644 --- a/lib/system/arc.nim +++ b/lib/system/arc.nim @@ -312,13 +312,17 @@ when not (defined(gcOrc) or defined(gcYrc)): ## Forces a full garbage collection pass. With `--mm:arc` a nop. discard -template setupForeignThreadGc* = - ## With `--mm:arc` a nop. - discard - -template tearDownForeignThreadGc* = - ## With `--mm:arc` a nop. - discard +when not hasThreadSupport: + template setupForeignThreadGc* = discard + template tearDownForeignThreadGc* = discard +elif emulatedThreadVars: + template setupForeignThreadGc* = + {.error: "setupForeignThreadGc is available only when ``--threads:on`` and ``--tlsEmulation:off`` are used".} + template tearDownForeignThreadGc* = + {.error: "tearDownForeignThreadGc is available only when ``--threads:on`` and ``--tlsEmulation:off`` are used".} +elif not hasThreadLocalAllocator: + template setupForeignThreadGc* = discard + template tearDownForeignThreadGc* = discard proc isObjDisplayCheck(source: PNimTypeV2, targetDepth: int16, token: uint32): bool {.compilerRtl, inl.} = result = targetDepth <= source.depth and source.display[targetDepth] == token diff --git a/lib/system/threadimpl.nim b/lib/system/threadimpl.nim index e35378db0e..62d54ef6d8 100644 --- a/lib/system/threadimpl.nim +++ b/lib/system/threadimpl.nim @@ -19,6 +19,13 @@ when not defined(useNimRtl): threadType = ThreadType.NimThread +when hasThreadLocalAllocator and not emulatedThreadVars: + proc setupForeignThreadGc*() {.gcsafe, raises: [].} = + initThreadAllocator() + + proc tearDownForeignThreadGc*() {.gcsafe, raises: [].} = + releaseThreadAllocator() + when defined(gcDestructors): proc deallocThreadStorage(p: pointer) = c_free(p) else: @@ -83,6 +90,8 @@ else: deallocThreadStorage(thrd.rawStack) proc threadProcWrapStackFrame[TArg](thrd: ptr Thread[TArg]) {.raises: [].} = + when hasThreadLocalAllocator: + initThreadAllocator() when defined(boehmgc): boehmGC_call_with_stack_base(threadProcWrapDispatch[TArg], thrd) elif not defined(nogc) and not defined(gogc) and not defined(gcRegions) and not usesDestructors: @@ -97,6 +106,8 @@ proc threadProcWrapStackFrame[TArg](thrd: ptr Thread[TArg]) {.raises: [].} = when declared(deallocOsPages): deallocOsPages() else: threadProcWrapDispatch(thrd) + when hasThreadLocalAllocator: + releaseThreadAllocator() template nimThreadProcWrapperBody*(closure: untyped): untyped = var thrd = cast[ptr Thread[TArg]](closure) diff --git a/tests/threads/tthreadallocatorforeignpool.nim b/tests/threads/tthreadallocatorforeignpool.nim new file mode 100644 index 0000000000..ad46c74fea --- /dev/null +++ b/tests/threads/tthreadallocatorforeignpool.nim @@ -0,0 +1,59 @@ +discard """ + matrix: "--mm:arc --threads:on --tlsEmulation:off; --mm:orc --threads:on --tlsEmulation:off" + disabled: "windows" + output: "ok" + timeout: "30" +""" + +import std/posix + +var + escaped: pointer + reused: pointer + +proc allocateOnForeignThread(_: pointer): pointer {.noconv.} = + setupForeignThreadGc() + escaped = allocShared(96) + cast[ptr int](escaped)[] = 73 + tearDownForeignThreadGc() + result = nil + +proc reuseOnForeignThread(_: pointer): pointer {.noconv.} = + setupForeignThreadGc() + doAssert cast[ptr int](escaped)[] == 73 + deallocShared(escaped) + reused = allocShared(96) + doAssert reused == escaped + deallocShared(reused) + tearDownForeignThreadGc() + result = nil + +proc consumeDeferredFree(_: pointer): pointer {.noconv.} = + setupForeignThreadGc() + let first = allocShared(96) + let second = allocShared(96) + # The first allocation advances the active chunk and collects its deferred + # foreign frees. The next allocation reuses the remotely returned cell. + doAssert second == escaped + deallocShared(first) + deallocShared(second) + tearDownForeignThreadGc() + result = nil + +proc run(worker: proc(_: pointer): pointer {.noconv.}) = + var thread: Pthread + doAssert pthread_create(addr thread, nil, worker, nil) == 0 + doAssert pthread_join(thread, nil) == 0 + +# setup/teardown is the checkout/return boundary. A distinct native thread can +# safely inherit the allocator even while one of its allocations is still live. +run(allocateOnForeignThread) +run(reuseOnForeignThread) + +# A free that arrives while the allocator is idle is queued on its handle and +# consumed after that allocator is handed to another foreign thread. +run(allocateOnForeignThread) +deallocShared(escaped) +run(consumeDeferredFree) + +echo "ok" diff --git a/tests/threads/tthreadallocatorhandoffrace.nim b/tests/threads/tthreadallocatorhandoffrace.nim new file mode 100644 index 0000000000..3a7803e8f7 --- /dev/null +++ b/tests/threads/tthreadallocatorhandoffrace.nim @@ -0,0 +1,64 @@ +discard """ + matrix: "--mm:arc --threads:on; --mm:orc --threads:on" + output: "ok" + timeout: "30" +""" + +import std/[atomics, typedthreads] + +const + pointerCount = 512 + drainCount = 2048 + iterations {.intdefine.} = 200 + sizes = [16, 64, 4000, 4096, 8192] + +var + pointers: array[pointerCount, pointer] + mayExit: Atomic[bool] + +proc owner() {.thread.} = + for i in 0.. Date: Mon, 17 Aug 2026 12:36:51 +0200 Subject: [PATCH 7/9] add web3 package to the test suite (#26108) --- testament/important_packages.nim | 1 + 1 file changed, 1 insertion(+) diff --git a/testament/important_packages.nim b/testament/important_packages.nim index 27774db70e..d8b7cfc74f 100644 --- a/testament/important_packages.nim +++ b/testament/important_packages.nim @@ -176,6 +176,7 @@ pkg "unittest2" pkg "unpack" when not defined(arm64): pkg "weave", "nimble install -y cligen@#HEAD; nimble test_gc_arc", useHead = true +pkg "web3", "nimble test_slim", useHead = true pkg "websock", "nim c -d:chronicles_log_level=INFO tests/all_tests.nim" pkg "websocket", "nim c websocket.nim" pkg "with" From 5f5cf8dd0376ab205703d6d05bd1780b9025d6c1 Mon Sep 17 00:00:00 2001 From: Jacek Sieka Date: Mon, 17 Aug 2026 15:02:49 +0200 Subject: [PATCH 8/9] rm some cruft (#26113) `XDeclaredButNotUsed` for years in most cases - there's more but this is the low-hanging fruit --- compiler/ccgexprs.nim | 18 ------- compiler/ccgliterals.nim | 35 -------------- compiler/ccgstmts.nim | 86 ---------------------------------- compiler/ccgtypes.nim | 2 - compiler/cgen.nim | 14 ------ compiler/concepts.nim | 5 -- compiler/docgen.nim | 6 --- compiler/docgen2.nim | 3 +- compiler/injectdestructors.nim | 1 - compiler/int128.nim | 3 -- compiler/jsgen.nim | 36 -------------- compiler/lambdalifting.nim | 6 --- compiler/lexer.nim | 16 ++----- compiler/liftdestructors.nim | 6 --- compiler/main.nim | 16 ------- compiler/modulegraphs.nim | 8 ---- compiler/msgs.nim | 7 +-- compiler/packages.nim | 1 - compiler/pipelineutils.nim | 1 - compiler/procfind.nim | 44 +---------------- compiler/renderer.nim | 28 ----------- compiler/rodutils.nim | 8 ---- compiler/sem.nim | 1 - compiler/semcall.nim | 11 +---- compiler/semexprs.nim | 40 ---------------- compiler/semfold.nim | 22 +-------- compiler/semgnrc.nim | 2 +- compiler/semobjconstr.nim | 4 +- compiler/sempass2.nim | 8 ---- compiler/semstmts.nim | 5 -- compiler/semtempl.nim | 2 +- compiler/semtypes.nim | 3 -- compiler/semtypinst.nim | 29 ------------ compiler/sigmatch.nim | 3 -- compiler/sourcemap.nim | 5 +- compiler/spawn.nim | 1 - compiler/types.nim | 9 +--- compiler/vm.nim | 19 -------- compiler/vmgen.nim | 5 -- compiler/vmops.nim | 3 -- compiler/vmprofiler.nim | 1 - compiler/vtables.nim | 1 - 42 files changed, 18 insertions(+), 506 deletions(-) diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim index dc62e7c818..ba7e1500e3 100644 --- a/compiler/ccgexprs.nim +++ b/compiler/ccgexprs.nim @@ -2901,22 +2901,6 @@ proc genDestroy(p: BProc; n: PNode) = internalError(p.config, n.info, "destructor turned out to be not trivial") discard "ignore calls to the default destructor" -proc genDispose(p: BProc; n: PNode) = - when false: - let elemType = n[1].typ.skipTypes(abstractVar).elementType - - var a: TLoc = initLocExpr(p, n[1].skipAddr) - - if isFinal(elemType): - if elemType.destructor != nil: - var destroyCall = newNodeI(nkCall, n.info) - genStmts(p, destroyCall) - lineFmt(p, cpsStmts, "#nimRawDispose($1, NIM_ALIGNOF($2))", [rdLoc(a), getTypeDesc(p.module, elemType)]) - else: - # ``nimRawDisposeVirtual`` calls the ``finalizer`` which is the same as the - # destructor, but it uses the runtime type. Afterwards the memory is freed: - lineCg(p, cpsStmts, ["#nimDestroyAndDispose($#)", rdLoc(a)]) - proc genSlice(p: BProc; e: PNode; d: var TLoc) = let (x, y) = genOpenArraySlice(p, e, e.typ, e.typ.elementType, prepareForMutation = e[1].kind == nkHiddenDeref and @@ -3490,7 +3474,6 @@ proc genConstHeader(m, q: BModule; p: BProc, sym: PSym) = m.initProc.procSec(cpsLocals).add('\t') m.initProc.procSec(cpsLocals).addAssignmentWithValue(sym.loc.snippet): m.initProc.procSec(cpsLocals).addCast(ptrType(getTypeDesc(m, sym.loc.t, dkVar))): - var getGlobalCall: CallBuilder m.initProc.procSec(cpsLocals).addCall("hcrGetGlobal", getModuleDllPath(q, sym), '"' & sym.loc.snippet & '"') @@ -3768,7 +3751,6 @@ proc expr(p: BProc, n: PNode, d: var TLoc) = if delayedCodegen(p.module): genConstStmt(p, n) else: # enforce addressable consts for exportc - let m = p.module for it in n: let symNode = skipPragmaExpr(it.firstSon) if symNode.kind == nkSym and sfExportc in symNode.sym.flags: diff --git a/compiler/ccgliterals.nim b/compiler/ccgliterals.nim index 0a1586ae29..ddedabf6ad 100644 --- a/compiler/ccgliterals.nim +++ b/compiler/ccgliterals.nim @@ -28,9 +28,6 @@ proc detectStrVersion(m: BModule): int = else: detectVersion(strVersion, "nimStrVersion") -proc detectSeqVersion(m: BModule): int = - detectVersion(seqVersion, "nimSeqVersion") - # ----- Version 1: GC'ed strings and seqs -------------------------------- proc genStringLiteralDataOnlyV1(m: BModule, s: string; result: var Rope) = @@ -132,25 +129,6 @@ proc genStringLiteralV2Const(m: BModule; n: PNode; isConst: bool; result: var Bu result.addField(strInit, name = "p"): result.add(cCast(ptrType("NimStrPayload"), cAddr(pureLit))) -proc ssoCharLit(ch: char): string = - ## Return a C char literal for ch, with proper escaping. - const hexDigits = "0123456789abcdef" - result = "'" - case ch - of '\'': result.add("\\'") - of '\\': result.add("\\\\") - of '\0': result.add("\\0") - of '\n': result.add("\\n") - of '\r': result.add("\\r") - of '\t': result.add("\\t") - elif ch.ord < 32 or ch.ord == 127: - result.add("\\x") - result.add(hexDigits[ch.ord shr 4]) - result.add(hexDigits[ch.ord and 0xf]) - else: - result.add(ch) - result.add('\'') - proc ssoBytesLit(m: BModule; s: string; slen: int): string = ## Compute the `bytes` field value for the new SmallString layout. ## byte 0 = slen, bytes 1-7 = inline chars 0-6 (zero-padded). @@ -320,19 +298,6 @@ proc genStringLiteralV3(m: BModule; n: PNode; isConst: bool; result: var Builder # ------ Version selector --------------------------------------------------- -proc genStringLiteralDataOnly(m: BModule; s: string; info: TLineInfo; - isConst: bool; result: var Rope) = - case detectStrVersion(m) - of 0, 1: genStringLiteralDataOnlyV1(m, s, result) - of 2: - let tmp = getTempName(m) - genStringLiteralDataOnlyV2(m, s, tmp, isConst) - result.add tmp - of 3: - localError(m.config, info, "genStringLiteralDataOnly not supported for SmallString (nimsso)") - else: - localError(m.config, info, "cannot determine how to produce code for string literal") - proc genNilStringLiteral(m: BModule; info: TLineInfo; result: var Builder) = result.add(cCast(ptrType(cgsymValue(m, "NimStringDesc")), NimNil)) diff --git a/compiler/ccgstmts.nim b/compiler/ccgstmts.nim index cffee9c77d..f4c6cfab9e 100644 --- a/compiler/ccgstmts.nim +++ b/compiler/ccgstmts.nim @@ -1354,92 +1354,6 @@ proc genTryCpp(p: BProc, t: PNode, d: var TLoc) = linefmt(p, cpsStmts, "if (T$1_) std::rethrow_exception(T$1_);$n", [etmp]) endSimpleBlock(p, scope) -proc genTryCppOld(p: BProc, t: PNode, d: var TLoc) = - # There are two versions we generate, depending on whether we - # catch C++ exceptions, imported via .importcpp or not. The - # code can be easier if there are no imported C++ exceptions - # to deal with. - - # code to generate: - # - # try - # { - # myDiv(4, 9); - # } catch (NimExceptionType1&) { - # body - # } catch (NimExceptionType2&) { - # finallyPart() - # raise; - # } - # catch(...) { - # general_handler_body - # } - # finallyPart(); - - template genExceptBranchBody(body: PNode) {.dirty.} = - genRestoreFrameAfterException(p) - expr(p, body, d) - - if not isEmptyType(t.typ) and d.k == locNone: - d = getTemp(p, t.typ) - genLineDir(p, t) - cgsym(p.module, "popCurrentExceptionEx") - let fin = if t[^1].kind == nkFinally: t[^1] else: nil - p.nestedTryStmts.add((fin, false, t.kind == nkHiddenTryStmt, 0.Natural)) - startBlockWith(p): - p.s(cpsStmts).add("try {\n") - expr(p, t.firstSon, d) - endBlockWith(p): - p.s(cpsStmts).add("}\n") - - var catchAllPresent = false - - p.nestedTryStmts[^1].inExcept = true - for i in 1.. 0: if optDocRaw in d.conf.globalOptions: diff --git a/compiler/docgen2.nim b/compiler/docgen2.nim index 7fb11a3bd7..1d5434879c 100644 --- a/compiler/docgen2.nim +++ b/compiler/docgen2.nim @@ -29,7 +29,6 @@ proc shouldProcess(g: PGen): bool = template closeImpl(body: untyped) {.dirty.} = var g = PGen(p) let useWarning = sfMainModule notin g.module.flags - let groupedToc = true if shouldProcess(g): finishGenerateDoc(g.doc) body @@ -41,7 +40,7 @@ template closeImpl(body: untyped) {.dirty.} = proc closeDoc*(graph: ModuleGraph; p: PPassContext, n: PNode): PNode = result = nil closeImpl: - writeOutput(g.doc, useWarning, groupedToc) + writeOutput(g.doc, useWarning, true) proc closeJson*(graph: ModuleGraph; p: PPassContext, n: PNode): PNode = result = nil diff --git a/compiler/injectdestructors.nim b/compiler/injectdestructors.nim index 37969dfd31..bc99008a0d 100644 --- a/compiler/injectdestructors.nim +++ b/compiler/injectdestructors.nim @@ -173,7 +173,6 @@ template hasDestructorOrAsgn(c: var Con, typ: PType): bool = proc isLastRead(n: PNode; c: var Con; s: var Scope): bool = if not hasDestructorOrAsgn(c, n.typ): return true - let m = skipConvDfa(n) result = isLastReadImpl(n, c, s) proc isFirstWrite(n: PNode; c: var Con): bool = diff --git a/compiler/int128.nim b/compiler/int128.nim index cc253fb682..4b1f7f3a7a 100644 --- a/compiler/int128.nim +++ b/compiler/int128.nim @@ -340,9 +340,6 @@ proc `*`*(a: Int128, b: int32): Int128 = if b < 0: result = -result -proc `*=`(a: var Int128, b: int32) = - a = a * b - proc makeInt128(high, low: uint64): Int128 = result = Zero result.udata[0] = cast[uint32](low) diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim index a04c8e8f95..963c147fac 100644 --- a/compiler/jsgen.nim +++ b/compiler/jsgen.nim @@ -148,11 +148,6 @@ proc newGlobals(): PGlobals = typeInfoGenerated: initIntSet() ) -proc initCompRes(): TCompRes = - result = TCompRes(address: "", res: "", - tmpLoc: "", typ: etyNone, kind: resNone - ) - proc rdLoc(a: TCompRes): Rope {.inline.} = if a.typ != etyBaseIndex: result = a.res @@ -594,15 +589,6 @@ proc binaryUintExpr(p: PProc, n: PNode, r: var TCompRes, op: string, r.res = "(($1 $2 $3) $4)" % [x.rdLoc, rope op, y.rdLoc, trimmer] r.kind = resExpr -template ternaryExpr(p: PProc, n: PNode, r: var TCompRes, magic, frmt: string) = - var x, y, z: TCompRes - useMagic(p, magic) - gen(p, n[1], x) - gen(p, n[2], y) - gen(p, n[3], z) - r.res = frmt % [x.rdLoc, y.rdLoc, z.rdLoc] - r.kind = resExpr - template unaryExpr(p: PProc, n: PNode, r: var TCompRes, magic, frmt: string) = # $1 binds to n[1], if $2 is present it will be substituted to a tmp of $1 useMagic(p, magic) @@ -1182,7 +1168,6 @@ proc genAsmOrEmitStmt(p: PProc, n: PNode; isAsmStmt = false) = of nkStrLit..nkTripleStrLit: p.body.add(it.strVal) of nkSym: - let v = it.sym # for backwards compatibility we don't deref syms here :-( if false: discard @@ -1255,17 +1240,6 @@ proc generateHeader(p: PProc, prc: PSym): Rope = result.add(name) result.add("_Idx") -proc countJsParams(typ: PType): int = - result = 0 - for i in 1.. 1: initList.add(", ") var it = n[i] diff --git a/compiler/lambdalifting.nim b/compiler/lambdalifting.nim index 21571de254..c1994a962d 100644 --- a/compiler/lambdalifting.nim +++ b/compiler/lambdalifting.nim @@ -126,11 +126,6 @@ const paramName* = ":envP" envName* = ":env" -proc newCall(a: PSym, b: PNode): PNode = - result = newNodeI(nkCall, a.info) - result.add newSymNode(a) - result.add b - proc createClosureIterStateType*(g: ModuleGraph; iter: PSym; idgen: IdGenerator): PType = var n = newNodeI(nkRange, iter.info) n.add newIntNode(nkIntLit, -1) @@ -288,7 +283,6 @@ proc liftIterSym*(g: ModuleGraph; n: PNode; idgen: IdGenerator; owner: PSym): PN addVar(v, env) result.add(v) # add 'new' statement: - #result.add newCall(getSysSym(g, n.info, "internalNew"), env) result.add genCreateEnv(env) createTypeBoundOpsLL(g, env.typ, n.info, idgen, owner) result.add makeClosure(g, idgen, iter, env, n.info) diff --git a/compiler/lexer.nim b/compiler/lexer.nim index bc94542cc2..b05041ef1a 100644 --- a/compiler/lexer.nim +++ b/compiler/lexer.nim @@ -735,17 +735,11 @@ proc getEscapedChar(L: var Lexer, tok: var Token) = else: lexMessage(L, errGenerated, "invalid character constant") proc handleCRLF(L: var Lexer, pos: int): int = - template registerLine = - let col = L.getColNumber(pos) - - case L.buf[pos] - of CR: - registerLine() - result = nimlexbase.handleCR(L, pos) - of LF: - registerLine() - result = nimlexbase.handleLF(L, pos) - else: result = pos + result = + case L.buf[pos] + of CR: nimlexbase.handleCR(L, pos) + of LF: nimlexbase.handleLF(L, pos) + else: pos type StringMode = enum diff --git a/compiler/liftdestructors.nim b/compiler/liftdestructors.nim index 032a4623f2..caba4b2600 100644 --- a/compiler/liftdestructors.nim +++ b/compiler/liftdestructors.nim @@ -596,12 +596,6 @@ proc newSeqCall(c: var TLiftCtx; x, y: PNode): PNode = lenCall.typ = getSysType(c.g, x.info, tyInt) result.add lenCall -proc setLenStrCall(c: var TLiftCtx; x, y: PNode): PNode = - let lenCall = genBuiltin(c, mLengthStr, "len", y) - lenCall.typ = getSysType(c.g, x.info, tyInt) - result = genBuiltin(c, mSetLengthStr, "setLen", x) # genAddr(g, x)) - result.add lenCall - proc setLenSeqCall(c: var TLiftCtx; t: PType; x, y: PNode; noinit = false): PNode = let lenCall = genBuiltin(c, mLengthSeq, "len", y) lenCall.typ = getSysType(c.g, x.info, tyInt) diff --git a/compiler/main.nim b/compiler/main.nim index e27960f589..0365eba486 100644 --- a/compiler/main.nim +++ b/compiler/main.nim @@ -209,22 +209,6 @@ proc commandInteractive(graph: ModuleGraph) = let s = llStreamOpenStdIn(onPrompt = proc() = flushDot(graph.config)) discard processPipelineModule(graph, m, idgen, s) -proc commandScan(cache: IdentCache, config: ConfigRef) = - var f = addFileExt(AbsoluteFile mainCommandArg(config), NimExt) - var stream = llStreamOpen(f, fmRead) - if stream != nil: - var - L: Lexer = default(Lexer) - tok: Token = default(Token) - openLexer(L, f, stream, cache, config) - while true: - rawGetTok(L, tok) - printTok(config, tok) - if tok.tokType == tkEof: break - closeLexer(L) - else: - rawMessage(config, errGenerated, "cannot open file: " & f.string) - const PrintRopeCacheStats = false diff --git a/compiler/modulegraphs.nim b/compiler/modulegraphs.nim index 7b975268cd..6e8715c837 100644 --- a/compiler/modulegraphs.nim +++ b/compiler/modulegraphs.nim @@ -432,10 +432,6 @@ proc addDispatchers*(g: ModuleGraph, value: PSym) = # TODO: add it for packed modules g.dispatchers.add value -iterator resolveLazySymSeq(g: ModuleGraph, list: var seq[PSym]): PSym = - for it in list.mitems: - yield it - proc setMethodsPerType*(g: ModuleGraph; id: ItemId, methods: seq[PSym]) = # TODO: add it for packed modules g.methodsPerType[id] = methods @@ -668,10 +664,6 @@ proc hash*(u: SigHash): Hash = proc hash*(x: FileIndex): Hash {.borrow.} -template getPContext(): untyped = - when c is PContext: c - else: c.c - when defined(nimsuggest): template onUse*(info: TLineInfo; s: PSym; isGenericInstance = false) = discard template onDefResolveForward*(info: TLineInfo; s: PSym) = discard diff --git a/compiler/msgs.nim b/compiler/msgs.nim index 8c3ef55423..cb9195bec8 100644 --- a/compiler/msgs.nim +++ b/compiler/msgs.nim @@ -24,10 +24,6 @@ template instLoc*(): InstantiationInfo = instantiationInfo(-2, fullPaths = true) template toStdOrrKind(stdOrr): untyped = if stdOrr == stdout: stdOrrStdout else: stdOrrStderr -proc toLowerAscii(a: var string) {.inline.} = - for c in mitems(a): - if isUpperAscii(c): c = char(uint8(c) xor 0b0010_0000'u8) - proc flushDot*(conf: ConfigRef) = ## safe to call multiple times let stdOrr = if optStdout in conf.globalOptions: stdout else: stderr @@ -83,7 +79,8 @@ proc canonicalCase(path: var string) {.inline.} = ## the idea is to only use this for checking whether a path is already in ## the table but otherwise keep the original case when FileSystemCaseSensitive: discard - else: toLowerAscii(path) + else: + for c in mitems(path): c = toLowerAscii(c) proc fileInfoKnown*(conf: ConfigRef; filename: AbsoluteFile): bool = var diff --git a/compiler/packages.nim b/compiler/packages.nim index 95c42151b0..ceb3b3ae32 100644 --- a/compiler/packages.nim +++ b/compiler/packages.nim @@ -27,7 +27,6 @@ proc getPackage*(conf: ConfigRef; cache: IdentCache; fileIdx: FileIndex): PSym = ## * `modulegraphs.getPackage` let filename = AbsoluteFile toFullPath(conf, fileIdx) - name = getIdent(cache, splitFile(filename).name) info = newLineInfo(fileIdx, 1, 1) pkgName = getPackageName(conf, filename.string) pkgIdent = getIdent(cache, pkgName) diff --git a/compiler/pipelineutils.nim b/compiler/pipelineutils.nim index b29d513060..eadd48467e 100644 --- a/compiler/pipelineutils.nim +++ b/compiler/pipelineutils.nim @@ -1,4 +1,3 @@ -import std/intsets import ast, options, lineinfos, pathutils, msgs, modulegraphs, packages proc skipCodegen*(config: ConfigRef; n: PNode): bool {.inline.} = diff --git a/compiler/procfind.nim b/compiler/procfind.nim index c2cc6e71fa..eedf1542cd 100644 --- a/compiler/procfind.nim +++ b/compiler/procfind.nim @@ -11,25 +11,10 @@ # This is needed for proper handling of forward declarations. import - ast, astalgo, msgs, semdata, types, trees, lookups + ast, astalgo, msgs, semdata, types, lookups import std/strutils -proc equalGenericParams(procA, procB: PNode): bool = - if procA.len != procB.len: return false - for i in 0..= 0 or (g.tokens.len > 0 and - g.tokens[^1].kind == tkSpaces) - proc putNL(g: var TSrcGen) = putNL(g, g.indent) @@ -646,28 +642,6 @@ proc maxLineLength(s: string): int = inc(lineLen) inc(i) -proc putRawStr(g: var TSrcGen, kind: TokType, s: string) = - var i = 0 - let hi = s.len - 1 - var str = "" - while i <= hi: - case s[i] - of '\r': - put(g, kind, str) - str = "" - inc(i) - if i <= hi and s[i] == '\n': inc(i) - optNL(g, 0) - of '\n': - put(g, kind, str) - str = "" - inc(i) - optNL(g, 0) - else: - str.add(s[i]) - inc(i) - put(g, kind, str) - proc containsNL(s: string): bool = for i in 0..", nodecl, varargs.} - - -when not declared(signbit): - proc c_signbit(x: SomeFloat): cint {.importc: "signbit", header: "".} - proc signbit*(x: SomeFloat): bool {.inline.} = - result = c_signbit(x) != 0 - import std/formatfloat proc toStrMaxPrecision*(f: BiggestFloat | float32): string = diff --git a/compiler/sem.nim b/compiler/sem.nim index a689e2626f..c3c2b82491 100644 --- a/compiler/sem.nim +++ b/compiler/sem.nim @@ -332,7 +332,6 @@ proc typeAllowedCheck(c: PContext; info: TLineInfo; typ: PType; kind: TSymKind; proc paramsTypeCheck(c: PContext, typ: PType) {.inline.} = typeAllowedCheck(c, typ.n.info, typ, skProc) -proc expectMacroOrTemplateCall(c: PContext, n: PNode): PSym proc semDirectOp(c: PContext, n: PNode, flags: TExprFlags; expectedType: PType = nil): PNode proc semWhen(c: PContext, n: PNode, semCheck: bool = true): PNode proc semTemplateExpr(c: PContext, n: PNode, s: PSym, diff --git a/compiler/semcall.nim b/compiler/semcall.nim index 38b88ee7d2..26c459f6d8 100644 --- a/compiler/semcall.nim +++ b/compiler/semcall.nim @@ -689,7 +689,7 @@ proc bracketNotFoundError(c: PContext; n: PNode; flags: TExprFlags) = baseFilter + {skIterator} else: baseFilter # this will add the errors: - var r = resolveOverloads(c, n, n, filter, flags, errors, true) + discard resolveOverloads(c, n, n, filter, flags, errors, true) if errors.len == 0: localError(c.config, n.info, "could not resolve: " & $n) else: @@ -926,15 +926,6 @@ proc semResolvedCall(c: PContext, x: var TCandidate, result.typ = finalCallee.typ.returnType updateDefaultParams(c, result) -proc canDeref(n: PNode): bool {.inline.} = - result = n.len >= 2 and (let t = n[1].typ; - t != nil and t.skipTypes({tyGenericInst, tyAlias, tySink}).kind in {tyPtr, tyRef}) - -proc tryDeref(n: PNode): PNode = - result = newNodeI(nkHiddenDeref, n.info) - result.typ = n.typ.skipTypes(abstractInst)[0] - result.add n - proc semOverloadedCall(c: PContext, n, nOrig: PNode, filter: TSymKinds, flags: TExprFlags; expectedType: PType = nil): PNode = diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index d4539f7fa2..5361f57724 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -22,7 +22,6 @@ const errNamedExprExpected = "named expression expected" errNamedExprNotAllowed = "named expression not allowed here" errFieldInitTwice = "field initialized twice: '$1'" - errUndeclaredFieldX = "undeclared field: '$1'" proc semTemplateExpr(c: PContext, n: PNode, s: PSym, flags: TExprFlags = {}; expectedType: PType = nil): PNode = @@ -770,17 +769,6 @@ proc changeType(c: PContext; n: PNode, newType: PType, check: bool) = n.typ = newType -proc arrayConstrType(c: PContext, n: PNode): PType = - var typ = newTypeS(tyArray, c) - rawAddSon(typ, nil) # index type - if n.len == 0: - rawAddSon(typ, newTypeS(tyEmpty, c)) # needs an empty basetype! - else: - var t = skipTypes(n[0].typ, {tyGenericInst, tyVar, tyLent, tyOrdinal, tyAlias, tySink}) - addSonSkipIntLit(typ, t, c.idgen) - typ.setIndexType makeRangeType(c, 0, n.len - 1, n.info) - result = typ - proc semArrayConstr(c: PContext, n: PNode, flags: TExprFlags; expectedType: PType = nil): PNode = result = newNodeI(nkBracket, n.info) # nkBracket nodes can also be produced by the VM as seq constant nodes @@ -1339,7 +1327,6 @@ proc lookupInRecordAndBuildCheck(c: PContext, n, r: PNode, field: PIdent, else: illFormedAst(n, c.config) const - tyTypeParamsHolders = {tyGenericInst, tyCompositeTypeClass} tyDotOpTransparent = {tyVar, tyLent, tyPtr, tyRef, tyOwned, tyAlias, tySink} proc readTypeParameter(c: PContext, typ: PType, @@ -2326,24 +2313,6 @@ proc semDeclared(c: PContext, n: PNode, onlyCurrentScope: bool): PNode = result.info = n.info result.typ = getSysType(c.graph, n.info, tyBool) -proc expectMacroOrTemplateCall(c: PContext, n: PNode): PSym = - ## The argument to the proc should be nkCall(...) or similar - ## Returns the macro/template symbol - if isCallExpr(n): - var expandedSym = qualifiedLookUp(c, n[0], {checkUndeclared}) - if expandedSym == nil: - errorUndeclaredIdentifier(c, n.info, n[0].renderTree) - return errorSym(c, n[0]) - - if expandedSym.kind notin {skMacro, skTemplate}: - localError(c.config, n.info, "'$1' is not a macro or template" % expandedSym.name.s) - return errorSym(c, n[0]) - - result = expandedSym - else: - localError(c.config, n.info, "'$1' is not a macro or template" % n.renderTree) - result = errorSym(c, n) - proc expectString(c: PContext, n: PNode): string = var n = semConstExpr(c, n) if n.kind in nkStrKinds: @@ -2358,14 +2327,6 @@ proc newAnonSym(c: PContext; kind: TSymKind, info: TLineInfo): PSym = proc semExpandToAst(c: PContext, n: PNode): PNode = let macroCall = n[1] - when false: - let expandedSym = expectMacroOrTemplateCall(c, macroCall) - if expandedSym.kind == skError: return n - - macroCall[0] = newSymNode(expandedSym, macroCall.info) - markUsed(c, n.info, expandedSym) - onUse(n.info, expandedSym) - if isCallExpr(macroCall): for i in 1.. lastFloat(n.typ): - localError(g.config, n.info, "cannot convert " & $value & - " to " & typeToString(n.typ)) - proc foldConv(n, a: PNode; idgen: IdGenerator; g: ModuleGraph; check = false): PNode = let dstTyp = skipTypes(n.typ, abstractRange - {tyTypeDesc}) let srcTyp = skipTypes(a.typ, abstractRange - {tyTypeDesc}) diff --git a/compiler/semgnrc.nim b/compiler/semgnrc.nim index 91a834078d..da9b1c187c 100644 --- a/compiler/semgnrc.nim +++ b/compiler/semgnrc.nim @@ -233,7 +233,7 @@ proc fuzzyLookup(c: PContext, n: PNode, flags: TSemGenericFlags, if s.kind == skType: # don't put types in sym choice var ambig = false if candidates.len > 1: - let s2 = searchInScopes(c, ident, ambig) + discard searchInScopes(c, ident, ambig) result = newDot(result, semGenericStmtSymbol(c, n, s, ctx, flags, isAmbiguous = ambig, fromDotExpr = true)) else: diff --git a/compiler/semobjconstr.nim b/compiler/semobjconstr.nim index 769f88b6f2..bb6dbe4144 100644 --- a/compiler/semobjconstr.nim +++ b/compiler/semobjconstr.nim @@ -440,7 +440,7 @@ proc initConstrContext(t: PType, initExpr: PNode): ObjConstrContext = proc computeRequiresInit(c: PContext, t: PType): bool = assert t.kind == tyObject var constrCtx = initConstrContext(t, newNode(nkObjConstr)) - let initResult = semConstructTypeAux(c, constrCtx, {efWantNoDefaults}) + discard semConstructTypeAux(c, constrCtx, {efWantNoDefaults}) constrCtx.missingFields.len > 0 proc defaultConstructionError(c: PContext, t: PType, info: TLineInfo) = @@ -450,7 +450,7 @@ proc defaultConstructionError(c: PContext, t: PType, info: TLineInfo) = assert objType != nil if objType.kind == tyObject: var constrCtx = initConstrContext(objType, newNodeI(nkObjConstr, info)) - let initResult = semConstructTypeAux(c, constrCtx, {efIgnoreDefaults}) + discard semConstructTypeAux(c, constrCtx, {efIgnoreDefaults}) if constrCtx.missingFields.len > 0: localError(c.config, info, "The $1 type doesn't have a default value. The following fields must be initialized: $2." % [typeToString(t), listSymbolNames(constrCtx.missingFields)]) diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim index 2d1a42cd86..1bc5bac628 100644 --- a/compiler/sempass2.nim +++ b/compiler/sempass2.nim @@ -1006,7 +1006,6 @@ proc trackIf(tracked: PEffects, n: PNode) = proc trackBlock(tracked: PEffects, n: PNode; typ: PType) = if n.kind in {nkStmtList, nkStmtListExpr}: - let myBlock = tracked.currentBlock var oldState = -1 for i in 0.. Date: Tue, 18 Aug 2026 05:37:46 +0800 Subject: [PATCH 9/9] fix #26112: update variable kinds in isPartOf to include skResult (#26114) fix #26112 --- compiler/aliases.nim | 2 +- tests/ccgbugs/t26112.nim | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 tests/ccgbugs/t26112.nim diff --git a/compiler/aliases.nim b/compiler/aliases.nim index 639367b9b6..8775355c92 100644 --- a/compiler/aliases.nim +++ b/compiler/aliases.nim @@ -168,7 +168,7 @@ proc isPartOf*(a, b: PNode; flags: set[PartFlag] = {}): TAnalysisResult = if a.kind == b.kind: case a.kind of nkSym: - const varKinds = {skVar, skTemp, skProc, skFunc} + const varKinds = {skVar, skTemp, skResult, skProc, skFunc} # same symbol: aliasing: if a.sym.id == b.sym.id: result = arYes elif a.sym.kind in varKinds or b.sym.kind in varKinds: diff --git a/tests/ccgbugs/t26112.nim b/tests/ccgbugs/t26112.nim new file mode 100644 index 0000000000..f10250b61f --- /dev/null +++ b/tests/ccgbugs/t26112.nim @@ -0,0 +1,35 @@ +discard """ + matrix: "--mm:refc; --mm:orc" + ccodeCheck: "'result.fromScalar = x_p0;'" + ccodeCheck: "'result.fromObject = x_p0.fromObject;'" + ccodeCheck: "'result.nested.fromNested = x_p0.fromNested;'" +""" + +# bug #26112: unrelated parameters were considered potential aliases of the +# result location when their types could be contained in the returned object. + +type + Inner = object + fromNested: int + P = object + fromScalar: int + fromObject: int + nested: Inner + +func fromScalar(x: int): P = + P(fromScalar: x) + +proc fromObject(x: P): P = + P(fromObject: x.fromObject) + +func fromNested(x: Inner): P = + P(nested: Inner(fromNested: x.fromNested)) + +proc selfAlias(): P = + result.fromScalar = 42 + result = P(fromScalar: result.fromScalar) + +doAssert fromScalar(1).fromScalar == 1 +doAssert fromObject(P(fromObject: 2)).fromObject == 2 +doAssert fromNested(Inner(fromNested: 3)).nested.fromNested == 3 +doAssert selfAlias().fromScalar == 42