summary refs log tree commit diff stats
path: root/lib/core/strs.nim
diff options
context:
space:
mode:
Diffstat (limited to 'lib/core/strs.nim')
-rw-r--r--lib/core/strs.nim233
1 files changed, 147 insertions, 86 deletions
diff --git a/lib/core/strs.nim b/lib/core/strs.nim
index 1958f4974..186add52a 100644
--- a/lib/core/strs.nim
+++ b/lib/core/strs.nim
@@ -7,105 +7,166 @@
 #    distribution, for details about the copyright.
 #
 
-## Default string implementation used by Nim's core.
+## Default new string implementation used by Nim's core.
+
+when false:
+  # these are to be implemented or changed in the code generator.
+
+  #proc rawNewStringNoInit(space: int): NimString {.compilerProc.}
+  # seems to be unused.
+  proc copyDeepString(src: NimString): NimString {.inline.}
+  # ----------------- sequences ----------------------------------------------
+
+  proc incrSeqV3(s: PGenericSeq, typ: PNimType): PGenericSeq {.compilerProc.}
+  proc setLengthSeqV2(s: PGenericSeq, typ: PNimType, newLen: int): PGenericSeq {.
+      compilerRtl.}
+  proc newSeq(typ: PNimType, len: int): pointer {.compilerRtl.}
+  proc newSeqRC1(typ: PNimType, len: int): pointer {.compilerRtl.}
 
 import allocators
 
 type
-  string {.core.} = object
-    len, cap: int
-    data: ptr UncheckedArray[char]
+  NimStrPayload {.core.} = object
+    cap: int
+    region: Allocator
+    data: UncheckedArray[char]
+
+  NimStringV2 {.core.} = object
+    len: int
+    p: ptr NimStrPayload ## can be nil if len == 0.
+
+const nimStrVersion {.core.} = 2
 
-proc nimStringLiteral(x: cstring; len: int): string {.core.} =
-  string(len: len, cap: len, data: x)
+template isLiteral(s): bool = s.p == nil or s.p.region == nil
 
-template frees(s) = dealloc(s.data, s.cap + 1)
+template contentSize(cap): int = cap + 1 + sizeof(int) + sizeof(Allocator)
+
+template frees(s) =
+  if not isLiteral(s):
+    s.p.region.dealloc(s.p.region, s.p, contentSize(s.p.cap))
 
 proc `=destroy`(s: var string) =
-  if s.data != nil:
-    frees(s)
-    s.data = nil
-    s.len = 0
-    s.cap = 0
+  var a = cast[ptr NimStringV2](addr s)
+  frees(a)
+  a.len = 0
+  a.p = nil
+
+template lose(a) =
+  frees(a)
 
-proc `=sink`(a: var string, b: string) =
+proc `=sink`(x: var string, y: string) =
+  var a = cast[ptr NimStringV2](addr x)
+  var b = cast[ptr NimStringV2](unsafeAddr y)
   # we hope this is optimized away for not yet alive objects:
-  if a.data != nil and a.data != b.data:
-    frees(a)
+  if unlikely(a.p == b.p): return
+  lose(a)
   a.len = b.len
-  a.cap = b.cap
-  a.data = b.data
+  a.p = b.p
 
-proc `=`(a: var string; b: string) =
-  if a.data != nil and a.data != b.data:
-    frees(a)
-    a.data = nil
+proc `=`(x: var string, y: string) =
+  var a = cast[ptr NimStringV2](addr x)
+  var b = cast[ptr NimStringV2](unsafeAddr y)
+  if unlikely(a.p == b.p): return
+  lose(a)
   a.len = b.len
-  a.cap = b.cap
-  if b.data != nil:
-    a.data = cast[type(a.data)](alloc(a.cap + 1))
-    copyMem(a.data, b.data, a.cap+1)
-
-proc resize(s: var string) =
-  let old = s.cap
-  if old == 0: s.cap = 8
-  else: s.cap = (s.cap * 3) shr 1
-  s.data = cast[type(s.data)](realloc(s.data, old + 1, s.cap + 1))
-
-proc add*(s: var string; c: char) =
-  if s.len >= s.cap: resize(s)
-  s.data[s.len] = c
-  s.data[s.len+1] = '\0'
+  if isLiteral(b):
+    # we can shallow copy literals:
+    a.p = b.p
+  else:
+    let region = if a.p.region != nil: a.p.region else: getLocalAllocator()
+    # we have to allocate the 'cap' here, consider
+    # 'let y = newStringOfCap(); var x = y'
+    # on the other hand... These get turned into moves now.
+    a.p = cast[ptr NimStrPayload](region.alloc(region, contentSize(b.len)))
+    a.p.region = region
+    a.p.cap = b.len
+    copyMem(unsafeAddr a.p.data[0], unsafeAddr b.p.data[0], b.len+1)
+
+proc resize(old: int): int {.inline.} =
+  if old <= 0: result = 4
+  elif old < 65536: result = old * 2
+  else: result = old * 3 div 2 # for large arrays * 3/2 is better
+
+proc prepareAdd(s: var NimStringV2; addlen: int) {.compilerRtl.} =
+  if isLiteral(s):
+    let oldP = s.p
+    # can't mutate a literal, so we need a fresh copy here:
+    let region = getLocalAllocator()
+    s.p = cast[ptr NimStrPayload](region.alloc(region, contentSize(s.len + addlen)))
+    s.p.region = region
+    s.p.cap = s.len + addlen
+    if s.len > 0:
+      # we are about to append, so there is no need to copy the \0 terminator:
+      copyMem(unsafeAddr s.p.data[0], unsafeAddr oldP.data[0], s.len)
+  elif s.len + addlen > s.p.cap:
+    let cap = max(s.len + addlen, resize(s.p.cap))
+    s.p = cast[ptr NimStrPayload](s.p.region.realloc(s.p.region, s.p,
+      oldSize = contentSize(s.p.cap),
+      newSize = contentSize(cap)))
+    s.p.cap = cap
+
+proc nimAddCharV1(s: var NimStringV2; c: char) {.compilerRtl.} =
+  prepareAdd(s, 1)
+  s.p.data[s.len] = c
+  s.p.data[s.len+1] = '\0'
   inc s.len
 
-proc ensure(s: var string; newLen: int) =
-  let old = s.cap
-  if newLen >= old:
-    s.cap = max((old * 3) shr 1, newLen)
-    if s.cap > 0:
-      s.data = cast[type(s.data)](realloc(s.data, old + 1, s.cap + 1))
-
-proc add*(s: var string; y: string) =
-  if y.len != 0:
-    let newLen = s.len + y.len
-    ensure(s, newLen)
-    copyMem(addr s.data[len], y.data, y.data.len + 1)
+proc toNimStr(str: cstring, len: int): NimStringV2 {.compilerProc.} =
+  if len <= 0:
+    result = NimStringV2(len: 0, p: nil)
+  else:
+    let region = getLocalAllocator()
+    var p = cast[ptr NimStrPayload](region.alloc(region, contentSize(len)))
+    p.region = region
+    p.cap = len
+    if len > 0:
+      # we are about to append, so there is no need to copy the \0 terminator:
+      copyMem(unsafeAddr p.data[0], str, len)
+    result = NimStringV2(len: 0, p: p)
+
+proc cstrToNimstr(str: cstring): NimStringV2 {.compilerRtl.} =
+  if str == nil: toNimStr(str, 0)
+  else: toNimStr(str, str.len)
+
+proc nimToCStringConv(s: NimStringV2): cstring {.compilerProc, inline.} =
+  if s.len == 0: result = cstring""
+  else: result = cstring(unsafeAddr s.p.data)
+
+proc appendString(dest: var NimStringV2; src: NimStringV2) {.compilerproc, inline.} =
+  if src.len > 0:
+    # also copy the \0 terminator:
+    copyMem(unsafeAddr dest.p.data[dest.len], unsafeAddr src.p.data[0], src.len+1)
+
+proc appendChar(dest: var NimStringV2; c: char) {.compilerproc, inline.} =
+  dest.p.data[dest.len] = c
+  dest.p.data[dest.len+1] = '\0'
+  inc dest.len
+
+proc rawNewString(space: int): NimStringV2 {.compilerProc.} =
+  # this is also 'system.newStringOfCap'.
+  if space <= 0:
+    result = NimStringV2(len: 0, p: nil)
+  else:
+    let region = getLocalAllocator()
+    var p = cast[ptr NimStrPayload](region.alloc(region, contentSize(space)))
+    p.region = region
+    p.cap = space
+    result = NimStringV2(len: 0, p: p)
+
+proc mnewString(len: int): NimStringV2 {.compilerProc.} =
+  if len <= 0:
+    result = NimStringV2(len: 0, p: nil)
+  else:
+    let region = getLocalAllocator()
+    var p = cast[ptr NimStrPayload](region.alloc(region, contentSize(len)))
+    p.region = region
+    p.cap = len
+    result = NimStringV2(len: len, p: p)
+
+proc setLengthStrV2(s: var NimStringV2, newLen: int) {.compilerRtl.} =
+  if newLen > s.len:
+    prepareAdd(s, newLen - s.len)
+  else:
     s.len = newLen
-
-proc len*(s: string): int {.inline.} = s.len
-
-proc newString*(len: int): string =
-  result.len = len
-  result.cap = len
-  if len > 0:
-    result.data = alloc0(len+1)
-
-converter toCString(x: string): cstring {.core.} =
-  if x.len == 0: cstring"" else: cast[cstring](x.data)
-
-proc newStringOfCap*(cap: int): string =
-  result.len = 0
-  result.cap = cap
-  if cap > 0:
-    result.data = alloc(cap+1)
-
-proc `&`*(a, b: string): string =
-  let sum = a.len + b.len
-  result = newStringOfCap(sum)
-  result.len = sum
-  copyMem(addr result.data[0], a.data, a.len)
-  copyMem(addr result.data[a.len], b.data, b.len)
-  if sum > 0:
-    result.data[sum] = '\0'
-
-proc concat(x: openArray[string]): string {.core.} =
-  ## used be the code generator to optimize 'x & y & z ...'
-  var sum = 0
-  for i in 0 ..< x.len: inc(sum, x[i].len)
-  result = newStringOfCap(sum)
-  sum = 0
-  for i in 0 ..< x.len:
-    let L = x[i].len
-    copyMem(addr result.data[sum], x[i].data, L)
-    inc(sum, L)
-
+    # this also only works because the destructor
+    # looks at s.p and not s.len