Skip to content

Commit bcab01b

Browse files
committed
Feat: stdlib: adds system.string.setLenUninit
- Required for a follow-up to #15951 - Accompanies #19727 but for strings - Expands `stdlib/tstring` with tests for `setLen` and `setLenUninit`
1 parent 3f48576 commit bcab01b

File tree

5 files changed

+107
-0
lines changed

5 files changed

+107
-0
lines changed

changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ errors.
5252
- `copyDirWithPermissions` to recursively preserve attributes
5353

5454
- `system.setLenUninit` now supports refc, JS and VM backends.
55+
- `system.setLenUninit` for the `string` type. Allows setting length without initializing new memory on growth.
5556

5657
[//]: # "Changes:"
5758

lib/system.nim

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2318,6 +2318,30 @@ when notJSnotNims and hasAlloc:
23182318
when not defined(nimV2):
23192319
include "system/repr"
23202320

2321+
func setLenUninit*(s: var string, newlen: Natural) {.nodestroy.} =
2322+
## Sets the length of string `s` to `newlen`.
2323+
## New slots will not be initialized.
2324+
##
2325+
## If the new length is smaller than the new length,
2326+
## `s` will be truncated.
2327+
let n = max(newLen, 0)
2328+
when nimvm:
2329+
s.setLen(n)
2330+
else:
2331+
when notJSnotNims:
2332+
when defined(nimSeqsV2):
2333+
{.noSideEffect.}:
2334+
let str = unsafeAddr s
2335+
setLengthStrV2Uninit(cast[ptr NimStringV2](str)[], newlen)
2336+
else:
2337+
{.noSideEffect.}:
2338+
when hasAlloc:
2339+
setLengthStrUninit(s, newlen)
2340+
else:
2341+
s.setLen(n)
2342+
else: s.setLen(n)
2343+
2344+
23212345
when notJSnotNims and hasThreadSupport and hostOS != "standalone":
23222346
when not defined(nimPreviewSlimSystem):
23232347
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: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,31 @@ proc setLengthStr(s: NimString, newLen: int): NimString {.compilerRtl.} =
236236
result.len = n
237237
result.data[n] = '\0'
238238

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

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

tests/stdlib/tstring.nim

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,5 +120,42 @@ 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, "expected:" & $expectedLen & " s.len:" & $s.len
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) # trim
137+
s.checkStrInternals(expectedLen = 0)
138+
doAssert s == ""
139+
140+
s = numbers
141+
s.setLen(numbers.len+1) # growth
142+
s.checkStrInternals(expectedLen = numbers.len+1)
143+
doAssert s[0..9] == numbers[0..9], "(contents not copied)"
144+
doAssert s[numbers.len] == '\0', "(new space not zeroed)"
145+
146+
block setLenUninit:
147+
var s = numbers
148+
s.setLenUninit(numbers.len) # noop
149+
s.checkStrInternals(expectedLen = numbers.len)
150+
doAssert s == numbers
151+
152+
s.setLenUninit(5) # trim
153+
s.checkStrInternals(expectedLen = 5)
154+
doAssert s == "12345"
155+
156+
s.setLenUninit(11) # growth
157+
s.checkStrInternals(expectedLen = 11)
158+
doAssert s[0..4] == numbers[0..4]
159+
123160
static: main()
124161
main()

0 commit comments

Comments
 (0)