Skip to content

Commit 6ca4735

Browse files
committed
Feat: stdlib: adds system.string.setLenUninit
- Required for a followup to #15951 - Accompanies #19727 but for strings + `sysstr` housekeeping: - Removed redundant `len` and `reserved` sets already performed by prior `rawNewStringNoInit`calls.
1 parent 4352fa2 commit 6ca4735

File tree

5 files changed

+99
-8
lines changed

5 files changed

+99
-8
lines changed

changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ errors.
2828
to more efficiently calculate the symmetric difference of bitsets.
2929
- `strutils.multiReplace` overload for character set replacements in a single pass.
3030
Useful for string sanitation. Follows existing multiReplace semantics.
31+
- `system.setLenUninit` for the `string` type. Allows setting length without initializing new memory on growth.
3132

3233
[//]: # "Changes:"
3334

lib/system.nim

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2287,6 +2287,27 @@ when notJSnotNims and hasAlloc:
22872287
when not defined(nimV2):
22882288
include "system/repr"
22892289

2290+
func setLenUninit*(s: var string, newlen: Natural) {.nodestroy.} =
2291+
## Sets the length of string `s` to `newlen`.
2292+
## New slots will not be initialized.
2293+
##
2294+
## If the new length is smaller than the new length,
2295+
## `s` will be truncated.
2296+
let n = max(newLen, 0)
2297+
when nimvm:
2298+
s.setLen(n)
2299+
else:
2300+
when notJSnotNims:
2301+
when defined(nimSeqsV2):
2302+
{.noSideEffect.}:
2303+
let str = unsafeAddr s
2304+
setLengthStrV2Uninit(cast[ptr NimStringV2](str)[], newlen)
2305+
else:
2306+
{.noSideEffect.}:
2307+
setLengthStrUninit(s, newlen)
2308+
else: s.setLen(n)
2309+
2310+
22902311
when notJSnotNims and hasThreadSupport and hostOS != "standalone":
22912312
when not defined(nimPreviewSlimSystem):
22922313
include "system/channels_builtin"

lib/system/strs_v2.nim

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,26 @@ proc setLengthStrV2(s: var NimStringV2, newLen: int) {.compilerRtl.} =
166166
s.p.data[newLen] = '\0'
167167
s.len = newLen
168168

169+
proc setLengthStrV2Uninit(s: var NimStringV2, newLen: int) =
170+
if newLen == 0:
171+
discard "do not free the buffer here, pattern 's.setLen 0' is common for avoiding allocations"
172+
else:
173+
if isLiteral(s):
174+
let oldP = s.p
175+
s.p = allocPayload(newLen)
176+
s.p.cap = newLen
177+
if s.len > 0:
178+
copyMem(unsafeAddr s.p.data[0], unsafeAddr oldP.data[0], min(s.len, newLen))
179+
s.p.data[newLen] = '\0'
180+
elif newLen > s.len:
181+
let oldCap = s.p.cap and not strlitFlag
182+
if newLen > oldCap:
183+
let newCap = max(newLen, resize(oldCap))
184+
s.p = reallocPayload0(s.p, oldCap, newCap)
185+
s.p.cap = newCap
186+
s.p.data[newLen] = '\0'
187+
s.len = newLen
188+
169189
proc nimAsgnStrV2(a: var NimStringV2, b: NimStringV2) {.compilerRtl.} =
170190
if a.p == b.p and a.len == b.len: return
171191
if isLiteral(b):

lib/system/sysstr.nim

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,6 @@ proc addChar(s: NimString, c: char): NimString =
156156
result = rawNewStringNoInit(r)
157157
result.len = s.len
158158
copyMem(addr result.data[0], unsafeAddr(s.data[0]), s.len+1)
159-
result.reserved = r
160159
result.data[result.len] = c
161160
result.data[result.len+1] = '\0'
162161
inc(result.len)
@@ -202,7 +201,6 @@ proc resizeString(dest: NimString, addlen: int): NimString {.compilerRtl.} =
202201
result = rawNewStringNoInit(sp)
203202
result.len = dest.len
204203
copyMem(addr result.data[0], unsafeAddr(dest.data[0]), dest.len+1)
205-
result.reserved = sp
206204
#result = rawNewString(sp)
207205
#copyMem(result, dest, dest.len + sizeof(TGenericSeq))
208206
# DO NOT UPDATE LEN YET: dest.len = newLen
@@ -220,22 +218,42 @@ proc appendChar(dest: NimString, c: char) {.compilerproc, inline.} =
220218
proc setLengthStr(s: NimString, newLen: int): NimString {.compilerRtl.} =
221219
let n = max(newLen, 0)
222220
if s == nil:
223-
if n == 0:
224-
return s
225-
else:
226-
result = mnewString(n)
221+
return if n == 0: s else: mnewString(n)
227222
elif n <= s.space:
228223
result = s
229224
else:
230225
let sp = max(resize(s.space), n)
231226
result = rawNewStringNoInit(sp)
232-
result.len = s.len
233227
copyMem(addr result.data[0], unsafeAddr(s.data[0]), s.len)
234228
zeroMem(addr result.data[s.len], n - s.len)
235-
result.reserved = sp
236229
result.len = n
237230
result.data[n] = '\0'
238231

232+
proc setLengthStrUninit(s: var string, newlen: Natural) {.nodestroy.} =
233+
## Sets the `s` length to `newlen` without zeroing memory on growth.
234+
## Terminating zero for cstring compatibility is set.
235+
var str = cast[NimString](s.unsafeAddr)
236+
let n = max(newLen, 0)
237+
if str == nil:
238+
if n == 0: return
239+
else:
240+
str = rawNewStringNoInit(n)
241+
str.data[n] = '\0'
242+
str.len = n
243+
s = cast[string](str)
244+
else:
245+
if n > str.space:
246+
let sp = max(resize(str.space), n)
247+
str = rawNewStringNoInit(sp)
248+
copyMem(addr str.data[0], unsafeAddr s[0], s.len)
249+
str.data[n] = '\0'
250+
str.len = n
251+
s = cast[string](str)
252+
elif n < s.len:
253+
str.data[n] = '\0'
254+
str.len = n
255+
else: return
256+
239257
# ----------------- sequences ----------------------------------------------
240258

241259
proc incrSeq(seq: PGenericSeq, elemSize, elemAlign: int): PGenericSeq {.compilerproc.} =

tests/stdlib/tstring.nim

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,5 +120,36 @@ proc main() =
120120
doAssert c.len == 0
121121
doAssert c.high == -1
122122

123+
block: # setLen #setLenUninit
124+
proc checkStrInternals(s: string; expectedLen: int) =
125+
doAssert s.len == expectedLen
126+
when nimvm: discard
127+
else:
128+
when defined(UncheckedArray): # skip JS
129+
let cs = s.cstring # allows to get data address without IndexDefect
130+
let arr = cast[ptr UncheckedArray[char]](unsafeAddr cs[0])
131+
doAssert arr[expectedLen] == '\0', "(no terminating zero)"
132+
133+
const numbers = "1234567890"
134+
block setLen:
135+
var s = numbers
136+
s.setLen(0)
137+
s.checkStrInternals(expectedLen = 0)
138+
doAssert s == ""
139+
140+
block setLenUninit:
141+
var s = numbers
142+
s.setLenUninit(numbers.len) # noop
143+
s.checkStrInternals(expectedLen = numbers.len)
144+
doAssert s == numbers
145+
146+
s.setLenUninit(5) # trim
147+
s.checkStrInternals(expectedLen = 5)
148+
doAssert s == "12345"
149+
150+
s.setLenUninit(11) # growth
151+
s.checkStrInternals(expectedLen = 11)
152+
doAssert s[0..4] == numbers[0..4]
153+
123154
static: main()
124155
main()

0 commit comments

Comments
 (0)