WIP: strings/seqs based on destructors

This commit is contained in:
Andreas Rumpf
2018-07-17 13:19:42 +02:00
parent 74bf316619
commit 32afdc09c6
13 changed files with 240 additions and 141 deletions

View File

@@ -7,133 +7,157 @@
# distribution, for details about the copyright.
#
import allocators, typetraits
import typetraits
# strs already imported allocators for us.
## Default seq implementation used by Nim's core.
type
seq*[T] = object
len, cap: int
data: ptr UncheckedArray[T]
NimSeqPayload {.core.}[T] = object
cap: int
region: Allocator
data: UncheckedArray[T]
NimSeqV2*[T] = object
len: int
p: ptr NimSeqPayload[T]
const nimSeqVersion {.core.} = 2
template frees(s) = dealloc(s.data, s.cap * sizeof(T))
template payloadSize(cap): int = cap * sizeof(T) + sizeof(int) + sizeof(Allocator)
# XXX make code memory safe for overflows in '*'
when defined(nimHasTrace):
proc `=trace`[T](s: seq[T]; a: Allocator) =
for i in 0 ..< s.len: `=trace`(s.data[i], a)
when false:
# this is currently not part of Nim's type bound operators and so it's
# built into the tracing proc generation just like before.
proc `=trace`[T](s: NimSeqV2[T]) =
for i in 0 ..< s.len: `=trace`(s.data[i])
proc `=destroy`[T](x: var seq[T]) =
if x.data != nil:
proc `=destroy`[T](x: var NimSeqV2[T]) =
var p = x.p
if p != nil:
when not supportsCopyMem(T):
for i in 0..<x.len: `=destroy`(x[i])
frees(x)
x.data = nil
for i in 0..<x.len: `=destroy`(p.data[i])
p.region.dealloc(p.region, p, payloadSize(p.cap))
x.p = nil
x.len = 0
x.cap = 0
proc `=`[T](a: var seq[T]; b: seq[T]) =
if a.data == b.data: return
if a.data != nil:
frees(a)
a.data = nil
proc `=`[T](a: var NimSeqV2[T]; b: NimSeqV2[T]) =
if a.p == b.p: return
`=destroy`(a)
a.len = b.len
a.cap = b.cap
if b.data != nil:
a.data = cast[type(a.data)](alloc(a.cap * sizeof(T)))
if b.p != nil:
a.p = cast[type(a.p)](alloc(payloadSize(a.len)))
when supportsCopyMem(T):
copyMem(a.data, b.data, a.cap * sizeof(T))
if a.len > 0:
copyMem(unsafeAddr a.p.data[0], unsafeAddr b.p.data[0], a.len * sizeof(T))
else:
for i in 0..<a.len:
a.data[i] = b.data[i]
a.p.data[i] = b.p.data[i]
proc `=sink`[T](a: var seq[T]; b: seq[T]) =
if a.data != nil and a.data != b.data:
frees(a)
proc `=sink`[T](a: var NimSeqV2[T]; b: NimSeqV2[T]) =
if a.p != nil and a.p != b.p:
`=destroy`(a)
a.len = b.len
a.cap = b.cap
a.data = b.data
a.p = b.p
proc resize[T](s: var seq[T]) =
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 * sizeof(T), s.cap * sizeof(T)))
when false:
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 reserveSlot[T](x: var seq[T]): ptr T =
if x.len >= x.cap: resize(x)
result = addr(x.data[x.len])
inc x.len
template add*[T](x: var seq[T]; y: T) =
reserveSlot(x)[] = y
type
PayloadBase = object
cap: int
region: Allocator
proc shrink*[T](x: var seq[T]; newLen: int) =
assert newLen <= x.len
assert newLen >= 0
proc newSeqPayload(cap, elemSize: int): pointer {.compilerRtl.} =
# 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 region = getLocalAllocator()
var p = cast[ptr PayloadBase](region.alloc(region, cap * elemSize + sizeof(int) + sizeof(Allocator)))
p.region = region
p.cap = cap
result = p
else:
result = nil
proc prepareSeqAdd(len: int; p: pointer; addlen, elemSize: int): pointer {.compilerRtl.} =
if len+addlen <= len:
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 region = if p.region == nil: getLocalAllocator() else: p.region
let cap = max(resize(p.cap), len+addlen)
var q = cast[ptr PayloadBase](region.realloc(region, p,
sizeof(int) + sizeof(Allocator) + elemSize * p.cap,
sizeof(int) + sizeof(Allocator) + elemSize * cap))
q.region = region
q.cap = cap
result = q
proc shrink*[T](x: var seq[T]; newLen: Natural) =
sysAssert newLen <= x.len, "invalid newLen parameter for 'shrink'"
when not supportsCopyMem(T):
for i in countdown(x.len - 1, newLen - 1):
`=destroy`(x.data[i])
x.len = newLen
`=destroy`(x[i])
proc grow*[T](x: var seq[T]; newLen: int; value: T) =
if newLen <= x.len: return
assert newLen >= 0
if x.cap == 0: x.cap = newLen
else: x.cap = max(newLen, (x.cap * 3) shr 1)
x.data = cast[type(x.data)](realloc(x.data, x.cap * sizeof(T)))
for i in x.len..<newLen:
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)
xu.p = prepareSeqAdd(oldLen, xu.p, newLen - oldLen, sizeof(T))
xu.len = newLen
for i in oldLen .. newLen-1:
x.data[i] = value
x.len = newLen
template default[T](t: typedesc[T]): T =
var v: T
v
proc setLen*[T](x: var seq[T]; newLen: int) {.deprecated.} =
if newlen < x.len: shrink(x, newLen)
else: grow(x, newLen, default(T))
template `[]`*[T](x: seq[T]; i: Natural): T =
assert i < x.len
x.data[i]
template `[]=`*[T](x: seq[T]; i: Natural; y: T) =
assert i < x.len
x.data[i] = y
proc `@`*[T](elems: openArray[T]): seq[T] =
result.cap = elems.len
result.len = elems.len
result.data = cast[type(result.data)](alloc(result.cap * sizeof(T)))
when supportsCopyMem(T):
copyMem(result.data, unsafeAddr(elems[0]), result.cap * sizeof(T))
proc setLen[T](s: var seq[T], newlen: Natural) =
if newlen < s.len:
shrink(s, newLen)
else:
for i in 0..<result.len:
result.data[i] = elems[i]
var v: T # get the default value of 'v'
grow(s, newLen, v)
proc len*[T](x: seq[T]): int {.inline.} = x.len
when false:
proc resize[T](s: var NimSeqV2[T]) =
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 * sizeof(T), s.cap * sizeof(T)))
proc `$`*[T](x: seq[T]): string =
result = "@["
var firstElement = true
for i in 0..<x.len:
let
value = x.data[i]
if firstElement:
firstElement = false
proc reserveSlot[T](x: var NimSeqV2[T]): ptr T =
if x.len >= x.cap: resize(x)
result = addr(x.data[x.len])
inc x.len
template add*[T](x: var NimSeqV2[T]; y: T) =
reserveSlot(x)[] = y
template `[]`*[T](x: NimSeqV2[T]; i: Natural): T =
assert i < x.len
x.data[i]
template `[]=`*[T](x: NimSeqV2[T]; i: Natural; y: T) =
assert i < x.len
x.data[i] = y
proc `@`*[T](elems: openArray[T]): NimSeqV2[T] =
result.cap = elems.len
result.len = elems.len
result.data = cast[type(result.data)](alloc(result.cap * sizeof(T)))
when supportsCopyMem(T):
copyMem(result.data, unsafeAddr(elems[0]), result.cap * sizeof(T))
else:
result.add(", ")
when compiles(value.isNil):
# this branch should not be necessary
if value.isNil:
result.add "nil"
else:
result.addQuoted(value)
else:
result.addQuoted(value)
result.add("]")
for i in 0..<result.len:
result.data[i] = elems[i]

View File

@@ -15,7 +15,6 @@ when false:
#proc rawNewStringNoInit(space: int): NimString {.compilerProc.}
# seems to be unused.
proc copyDeepString(src: NimString): NimString {.inline.}
proc setLengthStr(s: NimString, newLen: int): NimString {.compilerRtl.}
# ----------------- sequences ----------------------------------------------
proc incrSeqV3(s: PGenericSeq, typ: PNimType): PGenericSeq {.compilerProc.}
@@ -27,14 +26,14 @@ when false:
import allocators
type
StrContent = object
NimStrPayload {.core.} = object
cap: int
region: Allocator
data: UncheckedArray[char]
NimStringV2 {.core.} = object
len: int
p: ptr StrContent ## can be nil if len == 0.
p: ptr NimStrPayload ## can be nil if len == 0.
const nimStrVersion {.core.} = 2
@@ -73,7 +72,7 @@ proc `=`(a: var NimStringV2; b: NimStringV2) =
# 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 StrContent](region.alloc(region, contentSize(b.len)))
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)
@@ -88,7 +87,7 @@ proc prepareAdd(s: var NimStringV2; addlen: int) {.compilerRtl.} =
let oldP = s.p
# can't mutate a literal, so we need a fresh copy here:
let region = getLocalAllocator()
s.p = cast[ptr StrContent](region.alloc(region, contentSize(s.len + addlen)))
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:
@@ -96,7 +95,7 @@ proc prepareAdd(s: var NimStringV2; addlen: int) {.compilerRtl.} =
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 StrContent](s.p.region.realloc(s.p.region, s.p,
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
@@ -112,7 +111,7 @@ proc toNimStr(str: cstring, len: int): NimStringV2 {.compilerProc.} =
result = NimStringV2(len: 0, p: nil)
else:
let region = getLocalAllocator()
var p = cast[ptr StrContent](region.alloc(region, contentSize(len)))
var p = cast[ptr NimStrPayload](region.alloc(region, contentSize(len)))
p.region = region
p.cap = len
if len > 0:
@@ -144,7 +143,7 @@ proc rawNewString(space: int): NimStringV2 {.compilerProc.} =
result = NimStringV2(len: 0, p: nil)
else:
let region = getLocalAllocator()
var p = cast[ptr StrContent](region.alloc(region, contentSize(space)))
var p = cast[ptr NimStrPayload](region.alloc(region, contentSize(space)))
p.region = region
p.cap = space
result = NimStringV2(len: 0, p: p)
@@ -154,7 +153,15 @@ proc mnewString(len: int): NimStringV2 {.compilerProc.} =
result = NimStringV2(len: 0, p: nil)
else:
let region = getLocalAllocator()
var p = cast[ptr StrContent](region.alloc(region, contentSize(len)))
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
# this also only works because the destructor
# looks at s.p and not s.len