From 6c28f0d6889b15f2cb78de4728f71ef032f4a4c1 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Tue, 15 Jul 2025 05:19:58 +0800 Subject: [PATCH] fixes #25007; implements `setLenUninit` for refc (#25022) fixes #25007 ```nim proc setLengthSeqUninit(s: PGenericSeq, typ: PNimType, newLen: int, isTrivial: bool): PGenericSeq {. compilerRtl.} = ``` In this added function, only the line `zeroMem(dataPointer(result, elemAlign, elemSize, newLen), (result.len-%newLen) *% elemSize)` is removed from `proc setLengthSeqV2` when enlarging a sequence. JS and VM versions simply use `setLen`. (cherry picked from commit 611b8bbf67b7e4f51db60087d5e5b8f672fabf51) --- compiler/ast.nim | 1 + compiler/ccgexprs.nim | 9 ++++++--- compiler/condsyms.nim | 1 + compiler/jsgen.nim | 2 +- compiler/semdata.nim | 2 +- compiler/semmagic.nim | 2 +- compiler/vmgen.nim | 2 +- lib/system.nim | 16 ++++++++++++++++ lib/system/seqs_v2.nim | 2 +- lib/system/sysstr.nim | 40 ++++++++++++++++++++++++++++++++++++++++ tests/stdlib/tsystem.nim | 7 +++++++ 11 files changed, 76 insertions(+), 8 deletions(-) diff --git a/compiler/ast.nim b/compiler/ast.nim index a77e393720..0b319ef9ba 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -500,6 +500,7 @@ type mAppendStrCh, mAppendStrStr, mAppendSeqElem, mInSet, mRepr, mExit, mSetLengthStr, mSetLengthSeq, + mSetLengthSeqUninit, mIsPartOf, mAstToStr, mParallel, mSwap, mIsNil, mArrToSeq, mOpenArrayToSeq, mNewString, mNewStringOfCap, mParseBiggestFloat, diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim index 4e3d5491ab..29137cba98 100644 --- a/compiler/ccgexprs.nim +++ b/compiler/ccgexprs.nim @@ -1932,7 +1932,7 @@ proc isTrivialTypesToSnippet(t: PType): Rope = else: result = rope"NIM_TRUE" -proc genSetLengthSeq(p: BProc, e: PNode, d: var TLoc) = +proc genSetLengthSeq(p: BProc, e: PNode, d: var TLoc, noinit = false) = if optSeqDestructors in p.config.globalOptions: e[1] = makeAddr(e[1], p.module.idgen) genCall(p, e, d) @@ -1952,11 +1952,13 @@ proc genSetLengthSeq(p: BProc, e: PNode, d: var TLoc) = genTypeInfoV1(p.module, t.skipTypes(abstractInst), e.info)]) else: - const setLenPattern = "($3) #setLengthSeqV2($1, $4, $2, $5)" + const setLenPattern = "($3) #$6($1, $4, $2, $5)" + let name = if noinit: "setLengthSeqUninit" else: "setLengthSeqV2" call.snippet = ropecg(p.module, setLenPattern, [ rdLoc(a), rdLoc(b), getTypeDesc(p.module, t), genTypeInfoV1(p.module, t.skipTypes(abstractInst), e.info), - isTrivialTypesToSnippet(t.skipTypes(abstractInst)[0])]) + isTrivialTypesToSnippet(t.skipTypes(abstractInst)[0]), + name]) genAssignment(p, a, call, {}) gcUsage(p.config, e) @@ -2579,6 +2581,7 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) = unaryStmt(p, e, d, "if ($1) { #nimGCunref($1); }$n") of mSetLengthStr: genSetLengthStr(p, e, d) of mSetLengthSeq: genSetLengthSeq(p, e, d) + of mSetLengthSeqUninit: genSetLengthSeq(p, e, d, noinit = true) of mIncl, mExcl, mCard, mLtSet, mLeSet, mEqSet, mMulSet, mPlusSet, mMinusSet, mInSet, mXorSet: genSetOp(p, e, d, op) diff --git a/compiler/condsyms.nim b/compiler/condsyms.nim index 8bfcb2dac8..180ab49ffb 100644 --- a/compiler/condsyms.nim +++ b/compiler/condsyms.nim @@ -173,3 +173,4 @@ proc initDefines*(symbols: StringTableRef) = defineSymbol("nimHasXorSet") defineSymbol("nimHasPreviewDuplicateModuleError") + defineSymbol("nimHasSetLengthSeqUninitMagic") diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim index 1eff2a21d7..bfc15a6120 100644 --- a/compiler/jsgen.nim +++ b/compiler/jsgen.nim @@ -2439,7 +2439,7 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) = binaryExpr(p, n, r, "mnewString", """if ($1.length < $2) { for (var i = $3.length; i < $4; ++i) $3.push(0); } else {$3.length = $4; }""") - of mSetLengthSeq: + of mSetLengthSeq, mSetLengthSeqUninit: var x, y: TCompRes = default(TCompRes) gen(p, n[1], x) gen(p, n[2], y) diff --git a/compiler/semdata.nim b/compiler/semdata.nim index 251c5accea..20e21236e9 100644 --- a/compiler/semdata.nim +++ b/compiler/semdata.nim @@ -708,7 +708,7 @@ proc analyseIfAddressTakenInCall*(c: PContext, n: PNode, isConverter = false) = return const FakeVarParams = {mNew, mNewFinalize, mInc, ast.mDec, mIncl, mExcl, - mSetLengthStr, mSetLengthSeq, mAppendStrCh, mAppendStrStr, mSwap, + mSetLengthStr, mSetLengthSeq, mSetLengthSeqUninit, mAppendStrCh, mAppendStrStr, mSwap, mAppendSeqElem, mNewSeq, mShallowCopy, mDeepCopy, mMove, mWasMoved} template checkIfConverterCalled(c: PContext, n: PNode) = diff --git a/compiler/semmagic.nim b/compiler/semmagic.nim index 0b71783575..0ad6117813 100644 --- a/compiler/semmagic.nim +++ b/compiler/semmagic.nim @@ -667,7 +667,7 @@ proc magicsAfterOverloadResolution(c: PContext, n: PNode, result = semQuantifier(c, n) of mOld: result = semOld(c, n) - of mSetLengthSeq: + of mSetLengthSeq, mSetLengthSeqUninit: result = n let seqType = result[1].typ.skipTypes({tyPtr, tyRef, # in case we had auto-dereferencing tyVar, tyGenericInst, tyOwned, tySink, diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim index 3a7b498271..8d8eb9a25b 100644 --- a/compiler/vmgen.nim +++ b/compiler/vmgen.nim @@ -1230,7 +1230,7 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags = {}, m: TMag var tmp = c.genx(n[1]) c.gABC(n, opcQuit, tmp) c.freeTemp(tmp) - of mSetLengthStr, mSetLengthSeq: + of mSetLengthStr, mSetLengthSeq, mSetLengthSeqUninit: unused(c, n, dest) var d = c.genx(n[1]) var tmp = c.genx(n[2]) diff --git a/lib/system.nim b/lib/system.nim index c18d6ed9c3..0982276bdb 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -955,6 +955,22 @@ proc setLen*[T](s: var seq[T], newlen: Natural) {. ## assert x == @[10] ## ``` +when defined(nimHasSetLengthSeqUninitMagic): + func setLenUninit*[T](s: var seq[T], newlen: Natural) {.magic: "SetLengthSeqUninit", nodestroy.} = + ## Sets the length of seq `s` to `newlen`. `T` may be any sequence type. + ## New slots will not be initialized. + ## + ## If the current length is greater than the new length, + ## `s` will be truncated. + ## ```nim + ## var x = @[10, 20] + ## x.setLenUninit(5) + ## x[4] = 50 + ## assert x[4] == 50Add commentMore actions + ## x.setLenUninit(1) + ## assert x == @[10] + ## ``` + proc setLen*(s: var string, newlen: Natural) {. magic: "SetLengthStr", noSideEffect.} ## Sets the length of string `s` to `newlen`. diff --git a/lib/system/seqs_v2.nim b/lib/system/seqs_v2.nim index fbf4d5c49c..f0c880115c 100644 --- a/lib/system/seqs_v2.nim +++ b/lib/system/seqs_v2.nim @@ -196,7 +196,7 @@ func capacity*[T](self: seq[T]): int {.inline.} = let sek = cast[ptr NimSeqV2[T]](unsafeAddr self) result = if sek.p != nil: sek.p.cap and not strlitFlag else: 0 -func setLenUninit*[T](s: var seq[T], newlen: Natural) {.nodestroy.} = +func setLenUninit[T](s: var seq[T], newlen: Natural) {.nodestroy.} = ## Sets the length of seq `s` to `newlen`. `T` may be any sequence type. ## New slots will not be initialized. ## diff --git a/lib/system/sysstr.nim b/lib/system/sysstr.nim index b864da8531..4fee660033 100644 --- a/lib/system/sysstr.nim +++ b/lib/system/sysstr.nim @@ -300,6 +300,46 @@ proc setLengthSeq(seq: PGenericSeq, elemSize, elemAlign, newLen: int): PGenericS zeroMem(dataPointer(result, elemAlign, elemSize, newLen), (result.len-%newLen) *% elemSize) result.len = newLen +proc setLengthSeqUninit(s: PGenericSeq, typ: PNimType, newLen: int, isTrivial: bool): PGenericSeq {. + compilerRtl.} = + sysAssert typ.kind == tySequence, "setLengthSeqUninit: type is not a seq" + if s == nil: + if newLen == 0: + result = s + else: + result = cast[PGenericSeq](newSeq(typ, newLen)) + else: + let elemSize = typ.base.size + let elemAlign = typ.base.align + if s.space < newLen: + let r = max(resize(s.space), newLen) + result = cast[PGenericSeq](newSeq(typ, r)) + copyMem(dataPointer(result, elemAlign), dataPointer(s, elemAlign), s.len * elemSize) + # since we steal the content from 's', it's crucial to set s's len to 0. + s.len = 0 + elif newLen < s.len: + result = s + # we need to decref here, otherwise the GC leaks! + when not defined(boehmGC) and not defined(nogc) and + not defined(gcMarkAndSweep) and not defined(gogc) and + not defined(gcRegions): + if ntfNoRefs notin typ.base.flags: + for i in newLen..result.len-1: + forAllChildrenAux(dataPointer(result, elemAlign, elemSize, i), + extGetCellType(result).base, waZctDecRef) + + # XXX: zeroing out the memory can still result in crashes if a wiped-out + # cell is aliased by another pointer (ie proc parameter or a let variable). + # This is a tough problem, because even if we don't zeroMem here, in the + # presence of user defined destructors, the user will expect the cell to be + # "destroyed" thus creating the same problem. We can destroy the cell in the + # finalizer of the sequence, but this makes destruction non-deterministic. + if not isTrivial: # optimization for trivial types + zeroMem(dataPointer(result, elemAlign, elemSize, newLen), (result.len-%newLen) *% elemSize) + else: + result = s + result.len = newLen + proc setLengthSeqV2(s: PGenericSeq, typ: PNimType, newLen: int, isTrivial: bool): PGenericSeq {. compilerRtl.} = sysAssert typ.kind == tySequence, "setLengthSeqV2: type is not a seq" diff --git a/tests/stdlib/tsystem.nim b/tests/stdlib/tsystem.nim index dc2163f242..3385d1107b 100644 --- a/tests/stdlib/tsystem.nim +++ b/tests/stdlib/tsystem.nim @@ -236,6 +236,13 @@ proc bar2() = doAssert cstring(nil) <= cstring(nil) doAssert cstring("") <= cstring("") + var x = @[10, 20] + x.setLenUninit(5) + x[4] = 50 + doAssert x[4] == 50 + x.setLenUninit(1) + doAssert x == @[10] + static: bar2() bar2()