Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:"

Expand Down
24 changes: 24 additions & 0 deletions lib/system.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
20 changes: 20 additions & 0 deletions lib/system/strs_v2.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
25 changes: 25 additions & 0 deletions lib/system/sysstr.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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.} =
Expand Down
37 changes: 37 additions & 0 deletions tests/stdlib/tstring.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Loading