diff --git a/changelog.md b/changelog.md index 959f1056697e..211521574dd8 100644 --- a/changelog.md +++ b/changelog.md @@ -52,6 +52,7 @@ errors. - `copyDirWithPermissions` to recursively preserve attributes - `system.setLenUninit` now supports refc, JS and VM backends. +- `system.setLenUninit` for the `string` type. Allows setting length without initializing new memory on growth. [//]: # "Changes:" diff --git a/lib/system.nim b/lib/system.nim index 76739e51f93e..8808c3bcfce1 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -2318,6 +2318,30 @@ when notJSnotNims and hasAlloc: when not defined(nimV2): include "system/repr" +func setLenUninit*(s: var string, newlen: Natural) {.nodestroy.} = + ## Sets the length of string `s` to `newlen`. + ## New slots will not be initialized. + ## + ## If the new length is smaller than the new length, + ## `s` will be truncated. + let n = max(newLen, 0) + when nimvm: + s.setLen(n) + else: + when notJSnotNims: + when defined(nimSeqsV2): + {.noSideEffect.}: + let str = unsafeAddr s + setLengthStrV2Uninit(cast[ptr NimStringV2](str)[], newlen) + else: + {.noSideEffect.}: + when hasAlloc: + setLengthStrUninit(s, newlen) + else: + s.setLen(n) + else: s.setLen(n) + + when notJSnotNims and hasThreadSupport and hostOS != "standalone": when not defined(nimPreviewSlimSystem): include "system/channels_builtin" diff --git a/lib/system/strs_v2.nim b/lib/system/strs_v2.nim index 95e76b1f8fae..e6c408fb1f99 100644 --- a/lib/system/strs_v2.nim +++ b/lib/system/strs_v2.nim @@ -166,6 +166,26 @@ proc setLengthStrV2(s: var NimStringV2, newLen: int) {.compilerRtl.} = s.p.data[newLen] = '\0' s.len = newLen +proc setLengthStrV2Uninit(s: var NimStringV2, newLen: int) = + if newLen == 0: + discard "do not free the buffer here, pattern 's.setLen 0' is common for avoiding allocations" + else: + if isLiteral(s): + let oldP = s.p + s.p = allocPayload(newLen) + s.p.cap = newLen + if s.len > 0: + copyMem(unsafeAddr s.p.data[0], unsafeAddr oldP.data[0], min(s.len, newLen)) + s.p.data[newLen] = '\0' + elif newLen > s.len: + let oldCap = s.p.cap and not strlitFlag + if newLen > oldCap: + let newCap = max(newLen, resize(oldCap)) + s.p = reallocPayload0(s.p, oldCap, newCap) + s.p.cap = newCap + s.p.data[newLen] = '\0' + s.len = newLen + proc nimAsgnStrV2(a: var NimStringV2, b: NimStringV2) {.compilerRtl.} = if a.p == b.p and a.len == b.len: return if isLiteral(b): diff --git a/lib/system/sysstr.nim b/lib/system/sysstr.nim index 4fee6600337b..b3f0514bfe07 100644 --- a/lib/system/sysstr.nim +++ b/lib/system/sysstr.nim @@ -236,6 +236,31 @@ proc setLengthStr(s: NimString, newLen: int): NimString {.compilerRtl.} = result.len = n result.data[n] = '\0' +proc setLengthStrUninit(s: var string, newlen: Natural) {.nodestroy.} = + ## Sets the `s` length to `newlen` without zeroing memory on growth. + ## Terminating zero for cstring compatibility is set. + var str = cast[NimString](s) + let n = max(newLen, 0) + if str == nil: + if n == 0: return + else: + str = rawNewStringNoInit(n) + str.data[n] = '\0' + str.len = n + s = cast[string](str) + else: + if n > str.space: + let sp = max(resize(str.space), n) + str = rawNewStringNoInit(sp) + copyMem(addr str.data[0], unsafeAddr s[0], s.len) + str.data[n] = '\0' + str.len = n + s = cast[string](str) + elif n < s.len: + str.data[n] = '\0' + str.len = n + else: return + # ----------------- sequences ---------------------------------------------- proc incrSeq(seq: PGenericSeq, elemSize, elemAlign: int): PGenericSeq {.compilerproc.} = diff --git a/tests/stdlib/tstring.nim b/tests/stdlib/tstring.nim index b9b3c78a3821..87a9c6b8e99a 100644 --- a/tests/stdlib/tstring.nim +++ b/tests/stdlib/tstring.nim @@ -120,5 +120,42 @@ proc main() = doAssert c.len == 0 doAssert c.high == -1 + block: # setLen #setLenUninit + proc checkStrInternals(s: string; expectedLen: int) = + doAssert s.len == expectedLen, "expected:" & $expectedLen & " s.len:" & $s.len + when nimvm: discard + else: + when defined(UncheckedArray): # skip JS + let cs = s.cstring # allows to get data address without IndexDefect + let arr = cast[ptr UncheckedArray[char]](unsafeAddr cs[0]) + doAssert arr[expectedLen] == '\0', "(no terminating zero)" + + const numbers = "1234567890" + block setLen: + var s = numbers + s.setLen(0) # trim + s.checkStrInternals(expectedLen = 0) + doAssert s == "" + + s = numbers + s.setLen(numbers.len+1) # growth + s.checkStrInternals(expectedLen = numbers.len+1) + doAssert s[0..9] == numbers[0..9], "(contents not copied)" + doAssert s[numbers.len] == '\0', "(new space not zeroed)" + + block setLenUninit: + var s = numbers + s.setLenUninit(numbers.len) # noop + s.checkStrInternals(expectedLen = numbers.len) + doAssert s == numbers + + s.setLenUninit(5) # trim + s.checkStrInternals(expectedLen = 5) + doAssert s == "12345" + + s.setLenUninit(11) # growth + s.checkStrInternals(expectedLen = 11) + doAssert s[0..4] == numbers[0..4] + static: main() main()