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 611b8bbf67)
This commit is contained in:
ringabout
2025-07-15 05:19:58 +08:00
committed by narimiran
parent fcf4f10c70
commit 6c28f0d688
11 changed files with 76 additions and 8 deletions

View File

@@ -500,6 +500,7 @@ type
mAppendStrCh, mAppendStrStr, mAppendSeqElem,
mInSet, mRepr, mExit,
mSetLengthStr, mSetLengthSeq,
mSetLengthSeqUninit,
mIsPartOf, mAstToStr, mParallel,
mSwap, mIsNil, mArrToSeq, mOpenArrayToSeq,
mNewString, mNewStringOfCap, mParseBiggestFloat,

View File

@@ -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)

View File

@@ -173,3 +173,4 @@ proc initDefines*(symbols: StringTableRef) =
defineSymbol("nimHasXorSet")
defineSymbol("nimHasPreviewDuplicateModuleError")
defineSymbol("nimHasSetLengthSeqUninitMagic")

View File

@@ -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)

View File

@@ -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) =

View File

@@ -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,

View File

@@ -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])

View File

@@ -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`.

View File

@@ -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.
##

View File

@@ -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"

View File

@@ -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()