diff --git a/libs/arr_ref/arr_ref.nim b/libs/arr_ref/arr_ref.nim index e2683a8..ab554f3 100644 --- a/libs/arr_ref/arr_ref.nim +++ b/libs/arr_ref/arr_ref.nim @@ -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) + diff --git a/libs/arr_ref/slice_mem.nim b/libs/arr_ref/slice_mem.nim new file mode 100644 index 0000000..2771ddf --- /dev/null +++ b/libs/arr_ref/slice_mem.nim @@ -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)