ARC: cycle detector (#12823)

* first implementation of the =trace and =dispose hooks for the cycle collector
* a cycle collector for ARC: progress
* manual: the .acyclic pragma is a thing once again
* gcbench: adaptations for --gc:arc
* enable valgrind tests for the strutils tests
* testament: better valgrind support
* ARC refactoring: growable jumpstacks
* ARC cycle detector: non-recursive algorithm
* moved and renamed core/ files back to system/
* refactoring: --gc:arc vs --gc:orc since 'orc' is even more experimental and we want to ship --gc:arc soonish
This commit is contained in:
Andreas Rumpf 2019-12-17 17:37:50 +01:00 • committed by GitHub
commit 83a736a34a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
45 changed files with 939 additions and 229 deletions

80
lib/system/allocators.nim Normal file
View file

@ -0,0 +1,80 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2017 Nim contributors
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## Unstable API.
type
AllocatorFlag* {.pure.} = enum ## flags describing the properties of the allocator
ThreadLocal ## the allocator is thread local only.
ZerosMem ## the allocator always zeros the memory on an allocation
Allocator* = ptr AllocatorObj
AllocatorObj* {.inheritable, compilerproc.} = object
alloc*: proc (a: Allocator; size: int; alignment: int = 8): pointer {.nimcall, raises: [], tags: [], gcsafe.}
dealloc*: proc (a: Allocator; p: pointer; size: int) {.nimcall, raises: [], tags: [], gcsafe.}
realloc*: proc (a: Allocator; p: pointer; oldSize, newSize: int): pointer {.nimcall, raises: [], tags: [], gcsafe.}
deallocAll*: proc (a: Allocator) {.nimcall, raises: [], tags: [], gcsafe.}
flags*: set[AllocatorFlag]
name*: cstring
allocCount: int
deallocCount: int
var
localAllocator {.threadvar.}: Allocator
sharedAllocator: Allocator
allocatorStorage {.threadvar.}: AllocatorObj
when defined(useMalloc) and not defined(nimscript):
import "system/ansi_c"
import "system/memory"
template `+!`(p: pointer, s: int): pointer =
cast[pointer](cast[int](p) +% s)
proc getLocalAllocator*(): Allocator =
result = localAllocator
if result == nil:
result = addr allocatorStorage
result.alloc = proc (a: Allocator; size: int; alignment: int = 8): pointer {.nimcall, raises: [].} =
when defined(useMalloc) and not defined(nimscript):
result = c_malloc(cuint size)
# XXX do we need this?
nimZeroMem(result, size)
else:
result = system.alloc0(size)
inc a.allocCount
result.dealloc = proc (a: Allocator; p: pointer; size: int) {.nimcall, raises: [].} =
when defined(useMalloc) and not defined(nimscript):
c_free(p)
else:
system.dealloc(p)
inc a.deallocCount
result.realloc = proc (a: Allocator; p: pointer; oldSize, newSize: int): pointer {.nimcall, raises: [].} =
when defined(useMalloc) and not defined(nimscript):
result = c_realloc(p, cuint newSize)
else:
result = system.realloc(p, newSize)
nimZeroMem(result +! oldSize, newSize - oldSize)
result.deallocAll = nil
result.flags = {ThreadLocal, ZerosMem}
result.name = "nim_local"
localAllocator = result
proc setLocalAllocator*(a: Allocator) =
localAllocator = a
proc getSharedAllocator*(): Allocator =
result = sharedAllocator
proc setSharedAllocator*(a: Allocator) =
sharedAllocator = a
proc allocCounters*(): (int, int) =
let a = getLocalAllocator()
result = (a.allocCount, a.deallocCount)

View file

@ -0,0 +1,232 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2019 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
# Cycle collector based on Lins' Jump Stack and other ideas,
# see for example:
# https://pdfs.semanticscholar.org/f2b2/0d168acf38ff86305809a55ef2c5d6ebc787.pdf
# Further refinement in 2008 by the notion of "critical links", see
# "Cyclic reference counting" by Rafael Dueire Lins
# R.D. Lins / Information Processing Letters 109 (2008) 71–78
const
colGreen = 0b000
colYellow = 0b001
colRed = 0b010
jumpStackFlag = 0b100 # stored in jumpstack
rcShift = 3 # shift by rcShift to get the reference counter
colorMask = 0b011
type
TraceProc = proc (p, env: pointer) {.nimcall, benign.}
DisposeProc = proc (p: pointer) {.nimcall, benign.}
template color(c): untyped = c.rc and colorMask
template setColor(c, col) =
when col == colGreen:
c.rc = c.rc and not colorMask
else:
c.rc = c.rc and not colorMask or col
proc nimIncRefCyclic(p: pointer) {.compilerRtl, inl.} =
let h = head(p)
inc h.rc, rcIncrement
h.setColor colYellow # mark as potential cycle!
proc markCyclic*[T](x: ref T) {.inline.} =
## Mark the underlying object as a candidate for cycle collections.
## Experimental API. Do not use!
let h = head(cast[pointer](x))
h.setColor colYellow
type
CellTuple = (Cell, PNimType)
CellArray = ptr UncheckedArray[CellTuple]
CellSeq = object
len, cap: int
d: CellArray
GcEnv = object
traceStack: CellSeq
jumpStack: CellSeq
# ------------------- cell seq handling --------------------------------------
proc add(s: var CellSeq, c: Cell; t: PNimType) {.inline.} =
if s.len >= s.cap:
s.cap = s.cap * 3 div 2
when defined(useMalloc):
var d = cast[CellArray](c_malloc(uint(s.cap * sizeof(CellTuple))))
else:
var d = cast[CellArray](alloc(s.cap * sizeof(CellTuple)))
copyMem(d, s.d, s.len * sizeof(CellTuple))
when defined(useMalloc):
c_free(s.d)
else:
dealloc(s.d)
s.d = d
# XXX: realloc?
s.d[s.len] = (c, t)
inc(s.len)
proc init(s: var CellSeq, cap: int = 1024) =
s.len = 0
s.cap = cap
when defined(useMalloc):
s.d = cast[CellArray](c_malloc(uint(s.cap * sizeof(CellTuple))))
else:
s.d = cast[CellArray](alloc(s.cap * sizeof(CellTuple)))
proc deinit(s: var CellSeq) =
when defined(useMalloc):
c_free(s.d)
else:
dealloc(s.d)
s.d = nil
s.len = 0
s.cap = 0
proc pop(s: var CellSeq): (Cell, PNimType) =
result = s.d[s.len-1]
dec s.len
# ----------------------------------------------------------------------------
proc trace(s: Cell; desc: PNimType; j: var GcEnv) {.inline.} =
if desc.traceImpl != nil:
var p = s +! sizeof(RefHeader)
cast[TraceProc](desc.traceImpl)(p, addr(j))
proc free(s: Cell; desc: PNimType) {.inline.} =
when traceCollector:
cprintf("[From ] %p rc %ld color %ld in jumpstack %ld\n", s, s.rc shr rcShift,
s.color, s.rc and jumpStackFlag)
var p = s +! sizeof(RefHeader)
if desc.disposeImpl != nil:
cast[DisposeProc](desc.disposeImpl)(p)
nimRawDispose(p)
proc collect(s: Cell; desc: PNimType; j: var GcEnv) =
if s.color == colRed:
s.setColor colGreen
trace(s, desc, j)
while j.traceStack.len > 0:
let (t, desc) = j.traceStack.pop()
if t.color == colRed:
t.setColor colGreen
trace(t, desc, j)
free(t, desc)
free(s, desc)
#cprintf("[Cycle free] %p %ld\n", s, s.rc shr rcShift)
proc markRed(s: Cell; desc: PNimType; j: var GcEnv) =
if s.color != colRed:
s.setColor colRed
trace(s, desc, j)
while j.traceStack.len > 0:
let (t, desc) = j.traceStack.pop()
when traceCollector:
cprintf("[Cycle dec] %p %ld color %ld in jumpstack %ld\n", t, t.rc shr rcShift, t.color, t.rc and jumpStackFlag)
dec t.rc, rcIncrement
if (t.rc and not rcMask) >= 0 and (t.rc and jumpStackFlag) == 0:
t.rc = t.rc or jumpStackFlag
when traceCollector:
cprintf("[Now in jumpstack] %p %ld color %ld in jumpstack %ld\n", t, t.rc shr rcShift, t.color, t.rc and jumpStackFlag)
j.jumpStack.add(t, desc)
if t.color != colRed:
t.setColor colRed
trace(t, desc, j)
proc scanGreen(s: Cell; desc: PNimType; j: var GcEnv) =
s.setColor colGreen
trace(s, desc, j)
while j.traceStack.len > 0:
let (t, desc) = j.traceStack.pop()
if t.color != colGreen:
t.setColor colGreen
trace(t, desc, j)
inc t.rc, rcIncrement
when traceCollector:
cprintf("[Cycle inc] %p %ld color %ld\n", t, t.rc shr rcShift, t.color)
proc nimTraceRef(p: pointer; desc: PNimType; env: pointer) {.compilerRtl.} =
if p != nil:
var t = head(p)
var j = cast[ptr GcEnv](env)
j.traceStack.add(t, desc)
proc nimTraceRefDyn(p: pointer; env: pointer) {.compilerRtl.} =
if p != nil:
let desc = cast[ptr PNimType](p)[]
var t = head(p)
var j = cast[ptr GcEnv](env)
j.traceStack.add(t, desc)
proc scan(s: Cell; desc: PNimType; j: var GcEnv) =
when traceCollector:
cprintf("[doScanGreen] %p %ld\n", s, s.rc shr rcShift)
# even after trial deletion, `s` is still alive, so undo
# the decrefs by calling `scanGreen`:
if (s.rc and not rcMask) >= 0:
scanGreen(s, desc, j)
s.setColor colYellow
else:
# first we have to repair all the nodes we have seen
# that are still alive; we also need to mark what they
# refer to as alive:
while j.jumpStack.len > 0:
let (t, desc) = j.jumpStack.pop
# not in jump stack anymore!
t.rc = t.rc and not jumpStackFlag
if t.color == colRed and (t.rc and not rcMask) >= 0:
scanGreen(t, desc, j)
t.setColor colYellow
when traceCollector:
cprintf("[jump stack] %p %ld\n", t, t.rc shr rcShift)
# we have proven that `s` and its subgraph are dead, so we can
# collect these nodes:
collect(s, desc, j)
proc traceCycle(s: Cell; desc: PNimType) {.noinline.} =
when traceCollector:
cprintf("[traceCycle] %p %ld\n", s, s.rc shr rcShift)
var j: GcEnv
init j.jumpStack
init j.traceStack
markRed(s, desc, j)
scan(s, desc, j)
while j.jumpStack.len > 0:
let (t, desc) = j.jumpStack.pop
# not in jump stack anymore!
t.rc = t.rc and not jumpStackFlag
deinit j.jumpStack
deinit j.traceStack
proc nimDecRefIsLastCyclicDyn(p: pointer): bool {.compilerRtl, inl.} =
if p != nil:
var cell = head(p)
if (cell.rc and not rcMask) == 0:
result = true
#cprintf("[DESTROY] %p\n", p)
else:
dec cell.rc, rcIncrement
if cell.color == colYellow:
let desc = cast[ptr PNimType](p)[]
traceCycle(cell, desc)
# According to Lins it's correct to do nothing else here.
#cprintf("[DeCREF] %p\n", p)
proc nimDecRefIsLastCyclicStatic(p: pointer; desc: PNimType): bool {.compilerRtl, inl.} =
if p != nil:
var cell = head(p)
if (cell.rc and not rcMask) == 0:
result = true
#cprintf("[DESTROY] %p %s\n", p, desc.name)
else:
dec cell.rc, rcIncrement
if cell.color == colYellow: traceCycle(cell, desc)
#cprintf("[DeCREF] %p %s %ld\n", p, desc.name, cell.rc)

View file

@ -499,7 +499,8 @@ else:
when not defined(gcRegions):
include "system/alloc"
include "system/cellsets"
when not usesDestructors:
include "system/cellsets"
when not leakDetector and not useCellIds:
sysAssert(sizeof(Cell) == sizeof(FreeCell), "sizeof FreeCell")
when compileOption("gc", "v2"):

166
lib/system/refs_v2.nim Normal file
View file

@ -0,0 +1,166 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2019 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
#[
In this new runtime we simplify the object layouts a bit: The runtime type
information is only accessed for the objects that have it and it's always
at offset 0 then. The ``ref`` object header is independent from the
runtime type and only contains a reference count.
Object subtyping is checked via the generated 'name'. This should have
comparable overhead to the old pointer chasing approach but has the benefit
that it works across DLL boundaries.
The generated name is a concatenation of the object names in the hierarchy
so that a subtype check becomes a substring check. For example::
type
ObjectA = object of RootObj
ObjectB = object of ObjectA
ObjectA's ``name`` is "|ObjectA|RootObj|".
ObjectB's ``name`` is "|ObjectB|ObjectA|RootObj|".
Now to check for ``x of ObjectB`` we need to check
for ``x.typ.name.hasSubstring("|ObjectB|")``. In the actual implementation,
however, we could also use a
hash of ``package & "." & module & "." & name`` to save space.
]#
const
rcIncrement = 0b1000 # so that lowest 3 bits are not touched
rcMask = 0b111
type
RefHeader = object
rc: int # the object header is now a single RC field.
# we could remove it in non-debug builds for the 'owned ref'
# design but this seems unwise.
Cell = ptr RefHeader
template `+!`(p: pointer, s: int): pointer =
cast[pointer](cast[int](p) +% s)
template `-!`(p: pointer, s: int): pointer =
cast[pointer](cast[int](p) -% s)
template head(p: pointer): Cell =
cast[Cell](cast[int](p) -% sizeof(RefHeader))
const
traceCollector = defined(traceArc)
var allocs*: int
proc nimNewObj(size: int): pointer {.compilerRtl.} =
let s = size + sizeof(RefHeader)
when defined(nimscript):
discard
elif defined(useMalloc):
var orig = c_malloc(cuint s)
nimZeroMem(orig, s)
result = orig +! sizeof(RefHeader)
else:
result = alloc0(s) +! sizeof(RefHeader)
when hasThreadSupport:
atomicInc allocs
else:
inc allocs
when traceCollector:
cprintf("[Allocated] %p\n", result -! sizeof(RefHeader))
proc nimDecWeakRef(p: pointer) {.compilerRtl, inl.} =
dec head(p).rc, rcIncrement
proc nimIncRef(p: pointer) {.compilerRtl, inl.} =
inc head(p).rc, rcIncrement
#cprintf("[INCREF] %p\n", p)
proc nimRawDispose(p: pointer) {.compilerRtl.} =
when not defined(nimscript):
when traceCollector:
cprintf("[Freed] %p\n", p -! sizeof(RefHeader))
when defined(nimOwnedEnabled):
if head(p).rc >= rcIncrement:
cstderr.rawWrite "[FATAL] dangling references exist\n"
quit 1
when defined(useMalloc):
c_free(p -! sizeof(RefHeader))
else:
dealloc(p -! sizeof(RefHeader))
if allocs > 0:
when hasThreadSupport:
discard atomicDec(allocs)
else:
dec allocs
else:
cstderr.rawWrite "[FATAL] unpaired dealloc\n"
quit 1
template dispose*[T](x: owned(ref T)) = nimRawDispose(cast[pointer](x))
#proc dispose*(x: pointer) = nimRawDispose(x)
proc nimDestroyAndDispose(p: pointer) {.compilerRtl.} =
let d = cast[ptr PNimType](p)[].destructor
if d != nil: cast[DestructorProc](d)(p)
when false:
cstderr.rawWrite cast[ptr PNimType](p)[].name
cstderr.rawWrite "\n"
if d == nil:
cstderr.rawWrite "bah, nil\n"
else:
cstderr.rawWrite "has destructor!\n"
nimRawDispose(p)
when defined(gcOrc):
include cyclicrefs_v2
proc nimDecRefIsLast(p: pointer): bool {.compilerRtl, inl.} =
if p != nil:
var cell = head(p)
if (cell.rc and not rcMask) == 0:
result = true
#cprintf("[DESTROY] %p\n", p)
else:
dec cell.rc, rcIncrement
# According to Lins it's correct to do nothing else here.
#cprintf("[DeCREF] %p\n", p)
proc GC_unref*[T](x: ref T) =
## New runtime only supports this operation for 'ref T'.
if nimDecRefIsLast(cast[pointer](x)):
# XXX this does NOT work for virtual destructors!
`=destroy`(x[])
nimRawDispose(cast[pointer](x))
proc GC_ref*[T](x: ref T) =
## New runtime only supports this operation for 'ref T'.
if x != nil: nimIncRef(cast[pointer](x))
template GC_fullCollect* =
## Forces a full garbage collection pass. With ``--gc:arc`` a nop.
discard
template setupForeignThreadGc* =
## With ``--gc:arc`` a nop.
discard
template tearDownForeignThreadGc* =
## With ``--gc:arc`` a nop.
discard
proc isObj(obj: PNimType, subclass: cstring): bool {.compilerRtl, inl.} =
proc strstr(s, sub: cstring): cstring {.header: "<string.h>", importc.}
result = strstr(obj.name, subclass) != nil
proc chckObj(obj: PNimType, subclass: cstring) {.compilerRtl.} =
# checks if obj is of type subclass:
if not isObj(obj, subclass): sysFatal(ObjectConversionError, "invalid object conversion")

129
lib/system/seqs_v2.nim Normal file
View file

@ -0,0 +1,129 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2017 Nim contributors
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
# import typetraits
# strs already imported allocators for us.
proc supportsCopyMem(t: typedesc): bool {.magic: "TypeTrait".}
## Default seq implementation used by Nim's core.
type
NimSeqPayload[T] = object
cap: int
allocator: Allocator
data: UncheckedArray[T]
NimSeqV2*[T] = object
len: int
p: ptr NimSeqPayload[T]
const nimSeqVersion {.core.} = 2
template payloadSize(cap): int = cap * sizeof(T) + sizeof(int) + sizeof(Allocator)
# XXX make code memory safe for overflows in '*'
type
PayloadBase = object
cap: int
allocator: Allocator
proc newSeqPayload(cap, elemSize: int): pointer {.compilerRtl, raises: [].} =
# we have to use type erasure here as Nim does not support generic
# compilerProcs. Oh well, this will all be inlined anyway.
if cap > 0:
let allocator = getLocalAllocator()
var p = cast[ptr PayloadBase](allocator.alloc(allocator, cap * elemSize + sizeof(int) + sizeof(Allocator)))
p.allocator = allocator
p.cap = cap
result = p
else:
result = nil
proc prepareSeqAdd(len: int; p: pointer; addlen, elemSize: int): pointer {.
compilerRtl, noSideEffect, raises: [].} =
{.noSideEffect.}:
template `+!`(p: pointer, s: int): pointer =
cast[pointer](cast[int](p) +% s)
const headerSize = sizeof(int) + sizeof(Allocator)
if addlen <= 0:
result = p
elif p == nil:
result = newSeqPayload(len+addlen, elemSize)
else:
# Note: this means we cannot support things that have internal pointers as
# they get reallocated here. This needs to be documented clearly.
var p = cast[ptr PayloadBase](p)
let cap = max(resize(p.cap), len+addlen)
if p.allocator == nil:
let allocator = getLocalAllocator()
var q = cast[ptr PayloadBase](allocator.alloc(allocator,
headerSize + elemSize * cap))
copyMem(q +! headerSize, p +! headerSize, len * elemSize)
q.allocator = allocator
q.cap = cap
result = q
else:
let allocator = p.allocator
var q = cast[ptr PayloadBase](allocator.realloc(allocator, p,
headerSize + elemSize * p.cap,
headerSize + elemSize * cap))
q.allocator = allocator
q.cap = cap
result = q
proc shrink*[T](x: var seq[T]; newLen: Natural) =
when nimvm:
setLen(x, newLen)
else:
mixin `=destroy`
sysAssert newLen <= x.len, "invalid newLen parameter for 'shrink'"
when not supportsCopyMem(T):
for i in countdown(x.len - 1, newLen):
`=destroy`(x[i])
# XXX This is wrong for const seqs that were moved into 'x'!
cast[ptr NimSeqV2[T]](addr x).len = newLen
proc grow*[T](x: var seq[T]; newLen: Natural; value: T) =
let oldLen = x.len
if newLen <= oldLen: return
var xu = cast[ptr NimSeqV2[T]](addr x)
if xu.p == nil or xu.p.cap < newLen:
xu.p = cast[typeof(xu.p)](prepareSeqAdd(oldLen, xu.p, newLen - oldLen, sizeof(T)))
xu.len = newLen
for i in oldLen .. newLen-1:
xu.p.data[i] = value
proc add*[T](x: var seq[T]; value: sink T) {.magic: "AppendSeqElem", noSideEffect.} =
## Generic proc for adding a data item `y` to a container `x`.
##
## For containers that have an order, `add` means *append*. New generic
## containers should also call their adding proc `add` for consistency.
## Generic code becomes much easier to write if the Nim naming scheme is
## respected.
let oldLen = x.len
var xu = cast[ptr NimSeqV2[T]](addr x)
if xu.p == nil or xu.p.cap < oldLen+1:
xu.p = cast[typeof(xu.p)](prepareSeqAdd(oldLen, xu.p, 1, sizeof(T)))
xu.len = oldLen+1
xu.p.data[oldLen] = value
proc setLen[T](s: var seq[T], newlen: Natural) =
{.noSideEffect.}:
if newlen < s.len:
shrink(s, newlen)
else:
let oldLen = s.len
if newlen <= oldLen: return
var xu = cast[ptr NimSeqV2[T]](addr s)
if xu.p == nil or xu.p.cap < newlen:
xu.p = cast[typeof(xu.p)](prepareSeqAdd(oldLen, xu.p, newlen - oldLen, sizeof(T)))
xu.len = newlen

152
lib/system/strs_v2.nim Normal file
View file

@ -0,0 +1,152 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2017 Nim contributors
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## Default new string implementation used by Nim's core.
import allocators
type
NimStrPayload {.core.} = object
cap: int
allocator: Allocator
data: UncheckedArray[char]
NimStringV2 {.core.} = object
len: int
p: ptr NimStrPayload ## can be nil if len == 0.
const nimStrVersion {.core.} = 2
template isLiteral(s): bool = s.p == nil or s.p.allocator == nil
template contentSize(cap): int = cap + 1 + sizeof(int) + sizeof(Allocator)
template frees(s) =
if not isLiteral(s):
s.p.allocator.dealloc(s.p.allocator, s.p, contentSize(s.p.cap))
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) and addlen > 0:
let oldP = s.p
# can't mutate a literal, so we need a fresh copy here:
let allocator = getLocalAllocator()
s.p = cast[ptr NimStrPayload](allocator.alloc(allocator, contentSize(s.len + addlen)))
s.p.allocator = allocator
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.allocator.realloc(s.p.allocator, 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 toNimStr(str: cstring, len: int): NimStringV2 {.compilerproc.} =
if len <= 0:
result = NimStringV2(len: 0, p: nil)
else:
let allocator = getLocalAllocator()
var p = cast[ptr NimStrPayload](allocator.alloc(allocator, contentSize(len)))
p.allocator = allocator
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: len, 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, nonReloadable, 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)
inc dest.len, src.len
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 allocator = getLocalAllocator()
var p = cast[ptr NimStrPayload](allocator.alloc(allocator, contentSize(space)))
p.allocator = allocator
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 allocator = getLocalAllocator()
var p = cast[ptr NimStrPayload](allocator.alloc(allocator, contentSize(len)))
p.allocator = allocator
p.cap = len
result = NimStringV2(len: len, p: p)
proc setLengthStrV2(s: var NimStringV2, newLen: int) {.compilerRtl.} =
if newLen == 0:
frees(s)
s.p = nil
elif newLen > s.len or isLiteral(s):
prepareAdd(s, newLen - s.len)
s.len = newLen
proc nimAsgnStrV2(a: var NimStringV2, b: NimStringV2) {.compilerRtl.} =
if a.p == b.p: return
if isLiteral(b):
# we can shallow copy literals:
frees(a)
a.len = b.len
a.p = b.p
else:
if isLiteral(a) or a.p.cap < b.len:
let allocator = if a.p != nil and a.p.allocator != nil: a.p.allocator 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.
frees(a)
a.p = cast[ptr NimStrPayload](allocator.alloc(allocator, contentSize(b.len)))
a.p.allocator = allocator
a.p.cap = b.len
a.len = b.len
copyMem(unsafeAddr a.p.data[0], unsafeAddr b.p.data[0], b.len+1)
proc nimPrepareStrMutationV2(s: var NimStringV2) {.compilerRtl.} =
if s.p != nil and s.p.allocator == nil:
let oldP = s.p
# can't mutate a literal, so we need a fresh copy here:
let allocator = getLocalAllocator()
s.p = cast[ptr NimStrPayload](allocator.alloc(allocator, contentSize(s.len)))
s.p.allocator = allocator
s.p.cap = s.len
copyMem(unsafeAddr s.p.data[0], unsafeAddr oldP.data[0], s.len+1)

View file

@ -18,7 +18,7 @@ type
when defined(nimv2):
import core / allocators
import system / allocators
type
WideCString* = ptr UncheckedArray[Utf16Char]