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`.
This commit is contained in:
ringabout
2025-07-15 05:19:58 +08:00
committed by GitHub
parent 7e2df41850
commit 611b8bbf67
13 changed files with 79 additions and 7 deletions

View File

@@ -33,6 +33,8 @@ errors.
- `strutils.multiReplace` overload for character set replacements in a single pass.
Useful for string sanitation. Follows existing multiReplace semantics.
- `system.setLenUninit` now supports refc, JS and VM backends.
[//]: # "Changes:"
- `std/math` The `^` symbol now supports floating-point as exponent in addition to the Natural type.

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

@@ -2214,7 +2214,7 @@ proc isTrivialTypesToSnippet(t: PType): Snippet =
else:
result = NimTrue
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)
@@ -2236,7 +2236,9 @@ proc genSetLengthSeq(p: BProc, e: PNode, d: var TLoc) =
pExpr = cIfExpr(ra, cAddr(derefField(ra, "Sup")), NimNil)
else:
pExpr = ra
call.snippet = cCast(rt, cgCall(p, "setLengthSeqV2", pExpr, rti, rb,
let name = if noinit: "setLengthSeqUninit" else: "setLengthSeqV2"
call.snippet = cCast(rt, cgCall(p, name, pExpr, rti, rb,
isTrivialTypesToSnippet(t.skipTypes(abstractInst)[0])))
genAssignment(p, a, call, {})
@@ -2975,6 +2977,7 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimGCunref"), ra)
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

@@ -172,3 +172,5 @@ proc initDefines*(symbols: StringTableRef) =
defineSymbol("nimHasDefaultFloatRoundtrip")
defineSymbol("nimHasXorSet")
defineSymbol("nimHasSetLengthSeqUninitMagic")

View File

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

@@ -358,6 +358,7 @@ proc magicToNifTag(s: TMagic): (string, int) =
of mExit: ("exit", NoMagic)
of mSetLengthStr: ("setlenstr", NoMagic)
of mSetLengthSeq: ("setlenseq", NoMagic)
of mSetLengthSeqUninit: ("setlensequninit", NoMagic)
of mIsPartOf: ("ispartof", NoMagic)
of mAstToStr: ("asttostr", NoMagic)
of mParallel: ("parallel", NoMagic)

View File

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

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

@@ -202,7 +202,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,5 +236,12 @@ 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()