Add SliceMem, an object to safely move slices of memory around without copies.
This commit is contained in:
parent
49d919f43d
commit
8eda3e5969
|
@ -1,5 +1,7 @@
|
|||
import std/strformat
|
||||
import std/hashes
|
||||
import ./slice_mem
|
||||
export slice_mem
|
||||
|
||||
const header_size = when not defined(release):
|
||||
sizeof(pointer)*2 + sizeof(Natural)
|
||||
|
@ -7,10 +9,9 @@ else:
|
|||
sizeof(pointer)
|
||||
|
||||
type ArrRef*[T] = ref object
|
||||
endp: pointer
|
||||
byte_len: int
|
||||
when not defined(release):
|
||||
# to see in the debugger
|
||||
size_bytes: Natural
|
||||
arr_ptr: ptr T
|
||||
arr: UncheckedArray[T]
|
||||
|
||||
|
@ -21,35 +22,31 @@ proc newArrRef*[T](size: Natural): ArrRef[T] =
|
|||
result = cast[ArrRef[T]](r)
|
||||
else:
|
||||
unsafeNew(result, size * sizeof(T) + header_size)
|
||||
result.endp = addr(result.arr[size])
|
||||
result.byte_len = size * sizeof(T)
|
||||
when not defined(release):
|
||||
result.size_bytes = size * sizeof(T)
|
||||
result.arr_ptr = result.arr[0].addr
|
||||
|
||||
template len*[T](a: ArrRef[T]): Natural =
|
||||
((cast[int](a.endp) -% cast[int](a)) -% header_size) div sizeof(T)
|
||||
a.byte_len div sizeof(T)
|
||||
|
||||
template byteLen*[T](a: ArrRef[T]): Natural =
|
||||
((cast[int](a.endp) -% cast[int](a)) -% header_size)
|
||||
a.byte_len
|
||||
|
||||
template low*[T](a: ArrRef[T]): Natural = 0
|
||||
|
||||
template high*[T](a: ArrRef[T]): int = a.len - 1
|
||||
|
||||
template rangeError[T](a: ArrRef[T], i: Natural) =
|
||||
raise newException(RangeDefect, &"index out of range: {i} >= {a.len}")
|
||||
|
||||
proc `[]`*[T](a: ArrRef[T], i: Natural): var T =
|
||||
let p = cast[int](a) +% header_size +% sizeof(T) * i
|
||||
when compileOption("rangechecks"):
|
||||
if p +% sizeof(T) > cast[int](a.endp): rangeError(a, i)
|
||||
cast[ptr T](p)[]
|
||||
if i > a.len:
|
||||
raise newException(RangeDefect, &"index out of range: {i} >= {a.len}")
|
||||
a.arr[i]
|
||||
|
||||
proc `[]=`*[T](a: ArrRef[T], i: Natural, v: T) =
|
||||
let p = cast[int](a) +% header_size +% sizeof(T) * i
|
||||
when compileOption("rangechecks"):
|
||||
if p +% sizeof(T) > cast[int](a.endp): rangeError(a, i)
|
||||
cast[ptr T](p)[] = v
|
||||
if i > a.len:
|
||||
raise newException(RangeDefect, &"index out of range: {i} >= {a.len}")
|
||||
a.arr[i] = v
|
||||
|
||||
template toPointer*[T](a: ArrRef[T]): pointer = a.arr[0].addr
|
||||
|
||||
|
@ -70,11 +67,13 @@ iterator mpairs*[T](a: ArrRef[T]): tuple[key: int, val: var T] =
|
|||
yield (i, a[i])
|
||||
|
||||
proc `$`*[T](a: ArrRef[T]): string =
|
||||
result = "["
|
||||
let hi = a.high
|
||||
for i in 0 ..< hi:
|
||||
result &= $a[i] & ", "
|
||||
result &= $a[hi] & "]"
|
||||
result = "ArrRef(["
|
||||
if a.endp != a.arr:
|
||||
let hi = a.high
|
||||
for i in 0 ..< hi:
|
||||
result &= $a[i] & ", "
|
||||
result &= $a[hi]
|
||||
result &= "])"
|
||||
|
||||
template to*[T](a: ArrRef[T], U: untyped): untyped =
|
||||
cast[ArrRef[U]](a)
|
||||
|
@ -104,9 +103,8 @@ proc newArrRefWith*[T](size: Natural, v: T): ArrRef[T] =
|
|||
result = cast[ArrRef[T]](r)
|
||||
else:
|
||||
unsafeNew(result, size * sizeof(T) + header_size)
|
||||
result.endp = addr(result.arr[size])
|
||||
result.byte_len = size * sizeof(T)
|
||||
when not defined(release):
|
||||
result.size_bytes = size * sizeof(T)
|
||||
result.arr_ptr = result.arr[0].addr
|
||||
for i in result.low .. result.high: result[i] = v
|
||||
|
||||
|
@ -137,3 +135,7 @@ proc hash*[T](arr: ArrRef[T]): Hash =
|
|||
# just in case the actual size is bigger than that.
|
||||
hash(cast[ptr UncheckedArray[byte]](arr.arr.addr).toOpenArray(0, arr.len * sizeof(T) - 1))
|
||||
# TODO: for bigger elements, would a different algorithm be faster?
|
||||
|
||||
template `[]`*[T](a: ArrRef[T], s: Slice[int]): var SliceMem[T] =
|
||||
toSliceMem(a, s)
|
||||
|
||||
|
|
148
libs/arr_ref/slice_mem.nim
Normal file
148
libs/arr_ref/slice_mem.nim
Normal file
|
@ -0,0 +1,148 @@
|
|||
|
||||
## SliceMem represents a slice of memory, but it includes a reference to the
|
||||
## original container (of any type) or even a custom destructor, so memory is
|
||||
## properly freed when it's no longer in use.
|
||||
##
|
||||
## It's called "SliceMem" because "MemSlice" is taken by std/memfiles.
|
||||
|
||||
import std/strformat
|
||||
import std/hashes
|
||||
import std/macros
|
||||
|
||||
type
|
||||
SliceMem*[T] = object
|
||||
data*: ptr UncheckedArray[T]
|
||||
byte_len*: int
|
||||
destroy_ref: ref CustomDestructor
|
||||
|
||||
CustomDestructor = object
|
||||
destroy: proc() {.closure, raises: [].}
|
||||
|
||||
proc `=destroy`(s: var CustomDestructor) =
|
||||
if s.destroy != nil:
|
||||
s.destroy()
|
||||
s.destroy = nil
|
||||
|
||||
template toInt(p: pointer): int = cast[int](p)
|
||||
# template toPointer(i: int): pointer = cast[pointer](p)
|
||||
|
||||
proc newSliceMem*[T, U](container: sink U; p: pointer, byte_len: int): SliceMem[T] =
|
||||
## Create a SliceMem from a container, a pointer, and a length in bytes.
|
||||
result = SliceMem[T](
|
||||
data: cast[ptr UncheckedArray[T]](p),
|
||||
byte_len: byte_len,
|
||||
destroy_ref: (ref CustomDestructor)(destroy: proc()=
|
||||
# we don't actually need to destroy the container here, destroying
|
||||
# the closure will do it for us. Calling it allows us to use custom
|
||||
# destructors though.
|
||||
discard container
|
||||
),
|
||||
)
|
||||
|
||||
proc newSliceMem*[T](p: ptr T, byte_len: int, destructor: proc() {.closure, raises: [].}): SliceMem[T] =
|
||||
## Create a SliceMem from a pointer to a type, a length in bytes, and a
|
||||
## destructor closure.
|
||||
result = SliceMem[T](
|
||||
data: cast[ptr UncheckedArray[T]](p),
|
||||
byte_len: byte_len,
|
||||
destroy_ref: (ref CustomDestructor)(destroy: destructor),
|
||||
)
|
||||
|
||||
proc newSliceMem*(p: pointer, byte_len: int, destructor: proc() {.closure, raises: [].}): SliceMem[byte] {.inline.} =
|
||||
newSliceMem(cast[ptr byte](p), byte_len, destructor)
|
||||
|
||||
template newSliceMem*(container: not pointer, p, byte_len: untyped): untyped =
|
||||
## Template to automatically determine the type for `newSliceMem[T,U]`
|
||||
newSliceMem[typeof(container.items)](container, p, byte_len)
|
||||
|
||||
proc newSliceMem*[T, U](container: sink U; first, last: pointer): SliceMem[T] =
|
||||
## Create a SliceMem from a container and the two pointers of the first and
|
||||
## last elements in the container. Usually you will want to use `toSliceMem`
|
||||
## instead.
|
||||
result = SliceMem[T](
|
||||
data: cast[ptr UncheckedArray[T]](first),
|
||||
byte_len: (last.toInt -% first.toInt) + sizeof(T),
|
||||
destroy_ref: (ref CustomDestructor)(destroy: proc()=
|
||||
discard container
|
||||
),
|
||||
)
|
||||
|
||||
macro noMove(e: untyped): untyped =
|
||||
if e.kind == nnkCommand and e[0].repr == "move": e[1]
|
||||
else: e
|
||||
|
||||
template toSliceMem*(container, slice: untyped): untyped =
|
||||
## Create a SliceMem from a container and a slice that indicates the range.
|
||||
## The container needs to have `[]` and `items()` (like `seq`). If it doesn't
|
||||
## you need to pass pointers directly to `newSliceMem` instead.
|
||||
if slice.len != 0:
|
||||
# TODO: check that with boundChecks:off it doesn't try to read the value
|
||||
let first = noMove(container)[slice.a].addr
|
||||
let last = noMove(container)[slice.b].addr
|
||||
newSliceMem[typeof(noMove(container).items)](container, first, last)
|
||||
else:
|
||||
SliceMem[typeof(container.items)]()
|
||||
|
||||
template toSliceMem*(container): untyped =
|
||||
## Create a SliceMem from a container, with all its contents. The container
|
||||
## needs to have `[]` and `items()` (like `seq`). If it doesn't you need to
|
||||
## pass pointers directly to `newSliceMem` instead.
|
||||
let s = noMove(container).low .. noMove(container).high
|
||||
toSliceMem(container, s)
|
||||
|
||||
template len*[T](s: SliceMem[T]): Natural =
|
||||
s.byte_len div sizeof(T)
|
||||
|
||||
template low*[T](s: SliceMem[T]): Natural = 0
|
||||
|
||||
template high*[T](s: SliceMem[T]): int = s.len - 1
|
||||
|
||||
template `[]`*[T](s: SliceMem[T], i: Natural): var T =
|
||||
when compileOption("rangechecks"):
|
||||
if i >= s.len:
|
||||
raise newException(RangeDefect, &"index out of range: {i} >= {s.len}")
|
||||
s.data[i]
|
||||
|
||||
proc `[]=`*[T](s: SliceMem[T], i: Natural, v: T) =
|
||||
when compileOption("rangechecks"):
|
||||
if i >= s.len:
|
||||
raise newException(RangeDefect, &"index out of range: {i} >= {s.len}")
|
||||
s.data[i] = v
|
||||
|
||||
iterator items*[T](s: SliceMem[T]): T =
|
||||
for i in 0 ..< s.len:
|
||||
yield s.data[i]
|
||||
|
||||
iterator pairs*[T](s: SliceMem[T]): tuple[key: int, val: T] =
|
||||
for i in 0 ..< s.len:
|
||||
yield (i, s.data[i])
|
||||
|
||||
iterator mitems*[T](s: SliceMem[T]): var T =
|
||||
for i in 0 ..< s.len:
|
||||
yield s.data[i]
|
||||
|
||||
iterator mpairs*[T](s: SliceMem[T]): tuple[key: int, val: var T] =
|
||||
for i in 0 ..< s.len:
|
||||
yield (i, s.data[i])
|
||||
|
||||
proc `$`*[T](s: SliceMem[T]): string =
|
||||
result = "SliceMem(["
|
||||
if s.byte_len != 0:
|
||||
let hi = s.high
|
||||
for i in 0 ..< hi:
|
||||
result &= $s.data[i] & ", "
|
||||
result &= $s.data[hi]
|
||||
result &= "])"
|
||||
|
||||
proc hash*[T](s: SliceMem[T]): Hash =
|
||||
# Make use of stdlib's murmur3
|
||||
hash(cast[ptr UncheckedArray[byte]](s.data).toOpenArray(0, s.byte_len - 1))
|
||||
|
||||
template toOpenArray*(s: SliceMem): untyped =
|
||||
s.data.toOpenArray(0, s.byte_len - 1)
|
||||
|
||||
template toOpenArrayByte*(s: SliceMem): untyped =
|
||||
cast[ptr UncheckedArray[byte]](s.data).toOpenArray(0, s.byte_len - 1)
|
||||
|
||||
template to*[T](s: SliceMem, typ: typedesc[T]): SliceMem[T] =
|
||||
cast[SliceMem[T]](s)
|
Loading…
Reference in a new issue