nim-stew/stew/ranges/stackarrays.nim

133 lines
4.3 KiB
Nim

## Stack-allocated arrays should be used with great care.
##
## They pose several major risks:
##
## 1. They should not be used inside resumable procs
## (i.e. closure iterators and async procs)
##
## Future versions of the library may automatically
## detect such usages and flag them as errors
##
## 2. The user code should be certain that enough stack space
## is available for the allocation and there will be enough
## room for additional calls after the allocation.
##
## Future versions of this library may provide checks
##
## Please note that the stack size on certain platforms
## may be very small (e.g. 8 to 32 kb on some Android versions)
##
## Before using alloca-backed arrays, consider using:
##
## 1. A regular stack array with a reasonable size
##
## 2. A global {.threadvar.} sequence that can be resized when
## needed (only in non-reentrant procs)
##
## Other possible future directions:
##
## Instead of `alloca`, we may start using a shadow stack that will be much
## harder to overflow. This will work by allocating a very large chunk of the
## address space at program init (e.g. 1TB on a 64-bit system) and then by
## gradually committing the individual pages to memory as they are requested.
##
## Such a scheme will even allow us to resize the stack array on demand
## in situations where the final size is not known upfront. With a resizing
## factor of 2, we'll never waste more than 50% of the memory which should
## be reasonable for short-lived allocations.
##
type
StackArray*[T] = object
bufferLen: int32
# TODO For some reason, this needs to be public with Nim 0.19.6
# in order to compile the test suite of nim-stew:
buffer*: ptr UncheckedArray[T]
when defined(windows):
proc alloca(n: int): pointer {.importc, header: "<malloc.h>".}
else:
proc alloca(n: int): pointer {.importc, header: "<alloca.h>".}
func raiseRangeError(s: string) =
raise newException(RangeDefect, s)
func raiseOutOfRange =
raiseRangeError "index out of range"
template len*(a: StackArray): int =
int(a.bufferLen)
template high*(a: StackArray): int =
int(a.bufferLen) - 1
template low*(a: StackArray): int =
0
template `[]`*(a: StackArray, i: int): auto =
if i < 0 or i >= a.len: raiseOutOfRange()
a.buffer[i]
func `[]=`*(a: StackArray, i: int, val: a.T) {.inline.} =
if i < 0 or i >= a.len: raiseOutOfRange()
a.buffer[i] = val
template `[]`*(a: StackArray, i: BackwardsIndex): auto =
if int(i) < 1 or int(i) > a.len: raiseOutOfRange()
a.buffer[a.len - int(i)]
func `[]=`*(a: StackArray, i: BackwardsIndex, val: a.T) =
if int(i) < 1 or int(i) > a.len: raiseOutOfRange()
a.buffer[a.len - int(i)] = val
iterator items*(a: StackArray): a.T =
for i in 0 .. a.high:
yield a.buffer[i]
iterator mitems*(a: var StackArray): var a.T =
for i in 0 .. a.high:
yield a.buffer[i]
iterator pairs*(a: StackArray): a.T =
for i in 0 .. a.high:
yield (i, a.buffer[i])
iterator mpairs*(a: var StackArray): (int, var a.T) =
for i in 0 .. a.high:
yield (i, a.buffer[i])
template allocaAux(sz: int, init: static[bool]): pointer =
let s = sz
let b = alloca(s)
when init: zeroMem(b, s)
b
template allocStackArrayAux(T: typedesc, size: int, init: static[bool]): StackArray[T] =
let sz = int(size) # Evaluate size only once
if sz < 0: raiseRangeError "allocation with a negative size"
# XXX: is it possible to perform a stack size check before calling `alloca`?
# On thread init, Nim may record the base address and the capacity of the stack,
# so in theory we can verify that we still have enough room for the allocation.
# Research this.
StackArray[T](bufferLen: int32(sz), buffer: cast[ptr UncheckedArray[T]](allocaAux(sz * sizeof(T), init)))
template allocStackArray*(T: typedesc, size: int): StackArray[T] =
allocStackArrayAux(T, size, true)
template allocStackArrayNoInit*(T: typedesc, size: int): StackArray[T] =
allocStackArrayAux(T, size, false)
template getBuffer*(a: StackArray): untyped =
a.buffer
template toOpenArray*(a: StackArray): auto =
toOpenArray(a.getBuffer, 0, a.high)
template toOpenArray*(a: StackArray, first: int): auto =
if first < 0 or first >= a.len: raiseOutOfRange()
toOpenArray(a.getBuffer, first, a.high)
template toOpenArray*(a: StackArray, first, last: int): auto =
if first < 0 or first >= last or last <= a.len: raiseOutOfRange()
toOpenArray(a.getBuffer, first, last)