review changes:

* the length is stored as int32
* negative indexing is now supported with arr[^idx]
* toOpenArray supports slicing
* raise operations extracted in procs in order to reduce code bloat

A large disclaimer has been added warning about the possible risks
of using the StackArray type.
This commit is contained in:
Zahary Karadjov 2018-04-18 14:46:03 +03:00 committed by zah
parent 51121411a0
commit 5145020c30
2 changed files with 58 additions and 14 deletions

View File

@ -1,27 +1,61 @@
proc alloca(n: int): pointer {.importc, header: "<alloca.h>".} ## 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)
##
type type
StackArray*[T] = ptr object StackArray*[T] = ptr object
bufferLen: int bufferLen: int32
buffer: UncheckedArray[T] buffer: UncheckedArray[T]
template `[]`*(a: StackArray, i: int): auto = proc alloca(n: int): pointer {.importc, header: "<alloca.h>".}
if i < 0 or i >= a.len: raise newException(RangeError, "index out of range")
a.buffer[i]
proc `[]=`*(a: StackArray, i: int, val: a.T) = proc raiseRangeError(s: string) =
if i < 0 or i >= a.len: raise newException(RangeError, "index out of range") raise newException(RangeError, s)
a.buffer[i] = val
proc len*(a: StackArray): int {.inline.} = proc raiseOutOfRange =
a.bufferLen raiseRangeError "index out of range"
template len*(a: StackArray): int =
int(a.bufferLen)
template high*(a: StackArray): int = template high*(a: StackArray): int =
a.bufferLen - 1 int(a.bufferLen) - 1
template low*(a: StackArray): int = template low*(a: StackArray): int =
0 0
template `[]`*(a: StackArray, i: int): auto =
if i < 0 or i >= a.len: raiseOutOfRange()
a.buffer[i]
proc `[]=`*(a: StackArray, i: int, val: a.T) =
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)]
proc `[]=`*(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 = iterator items*(a: StackArray): a.T =
for i in 0 .. a.high: for i in 0 .. a.high:
yield a.buffer[i] yield a.buffer[i]
@ -39,14 +73,14 @@ iterator mpairs*(a: var StackArray): (int, var a.T) =
yield (i, a.buffer[i]) yield (i, a.buffer[i])
template allocStackArray*(T: typedesc, size: int): auto = template allocStackArray*(T: typedesc, size: int): auto =
if size < 0: raise newException(RangeError, "allocation with a negative size") if size < 0: raiseRangeError "allocation with a negative size"
# XXX: is it possible to perform a stack size check before calling `alloca`? # 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, # 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. # so in theory we can verify that we still have enough room for the allocation.
# Research this. # Research this.
var var
bufferSize = size * sizeof(T) bufferSize = size * sizeof(T)
totalSize = sizeof(int) + bufferSize totalSize = sizeof(int32) + bufferSize
arr = cast[StackArray[T]](alloca(totalSize)) arr = cast[StackArray[T]](alloca(totalSize))
zeroMem(addr arr.buffer[0], bufferSize) zeroMem(addr arr.buffer[0], bufferSize)
arr.bufferLen = size arr.bufferLen = size
@ -55,3 +89,11 @@ template allocStackArray*(T: typedesc, size: int): auto =
template toOpenArray*(a: StackArray): auto = template toOpenArray*(a: StackArray): auto =
toOpenArray(a.buffer, 0, a.high) toOpenArray(a.buffer, 0, a.high)
template toOpenArray*(a: StackArray, first: int): auto =
if first < 0 or first >= a.len: raiseOutOfRange()
toOpenArray(a.buffer, first, a.high)
template toOpenArray*(a: StackArray, first, last: int): auto =
if first < 0 or first >= last or last <= a.len: raiseOutOfRange()
toOpenArray(a.buffer, first, last)

View File

@ -1,6 +1,6 @@
import import
unittest, math, unittest, math,
../ranges/stackarrays ../ranges/[stackarrays, ptr_arith]
suite "Stack arrays": suite "Stack arrays":
test "Basic operations work as expected": test "Basic operations work as expected":
@ -20,6 +20,8 @@ suite "Stack arrays":
check: check:
sum(arr.toOpenArray) == 19 sum(arr.toOpenArray) == 19
arr[5] == 10 arr[5] == 10
arr[^1] == 6
cast[ptr int](shift(addr arr[0], 5))[] == 10
test "Allocating with a negative size throws a RangeError": test "Allocating with a negative size throws a RangeError":
expect RangeError: expect RangeError: