Refactor the bitranges module

* The bit procs defined over number values and openarray are now part of
  the bitops2 module and use the more traditional LittleEndian indexing.

* Added BitSeq and BitArray types as defined in the ETH2 spec.
This commit is contained in:
Zahary Karadjov 2019-07-22 02:48:03 +03:00
parent f782f0378f
commit 3fce87f0f5
No known key found for this signature in database
GPG Key ID: C8936F8A3073D609
4 changed files with 294 additions and 115 deletions

View File

@ -25,6 +25,8 @@ const
template bitsof*(T: typedesc[SomeInteger]): int = 8 * sizeof(T)
template bitsof*(x: SomeInteger): int = 8 * sizeof(x)
type BitIndexable = SomeUnsignedInt
# #### Pure Nim version ####
func nextPow2Nim(x: SomeUnsignedInt): SomeUnsignedInt =
@ -394,3 +396,147 @@ func rotateRight*(v: SomeUnsignedInt, amount: SomeInteger):
const mask = bitsof(v) - 1
let amount = int(amount and mask)
(v shr amount) or (v shl ( (-amount) and mask))
template mostSignificantBit(T: type): auto =
const res = 1 shl (sizeof(T) * 8 - 1)
T(res)
template getBit*(x: BitIndexable, bit: Natural): bool =
## reads a bit from `x`, assuming 0 to be the position of the
## least significant bit
type T = type(x)
(x and T(0b1 shl bit)) != 0
template getBitLE*(x: BitIndexable, bit: Natural): bool =
getBit(x, bit)
template getBitBE*(x: BitIndexable, bit: Natural): bool =
## Reads a bit from `x`, assuming 0 to be the position of
## the most significant bit.
##
## This indexing may be natural when you are considering the
## string representation of a bit field. For example, 72 can
## be written in binary as 0b01001000. The first bit here is
## zero, while the second bit is one.
##
## Since the string representation will depend on the size of
## the operand, using `getBitBE` with the same numeric value
## and a bit position may produce different results depending
## on the machine type used to store the value. For this reason,
## this indexing scheme is considered more error-prone and
## `getBitLE` is considering the default indexing scheme.
(x and mostSignificantBit(x.type) shr bit) != 0
proc setBit*(x: var BitIndexable, bit: Natural, val: bool) =
## writes a bit in `x`, assuming 0 to be the position of the
## least significant bit
type T = type(x)
let mask = T(0b1 shl bit)
if val:
x = x or mask
else:
x = x and not mask
proc setBitLE*(x: var BitIndexable, bit: Natural, val: bool) =
setBit(x, bit, val)
proc setBitBE*(x: var BitIndexable, bit: Natural, val: bool) =
## writes a bit in `x`, assuming 0 to be the position of the
## most significant bit
let mask = mostSignificantBit(x.type) shr bit
if val:
x = x or mask
else:
x = x and not mask
proc raiseBit*(x: var BitIndexable, bit: Natural) =
## raises bit in `x`, assuming 0 to be the position of the
## least significant bit
type T = type(x)
let mask = T(0b1 shl bit)
x = x or mask
proc raiseBitLE*(x: var BitIndexable, bit: Natural) =
raiseBit(x, bit)
proc raiseBitBE*(x: var BitIndexable, bit: Natural) =
## raises a bit in `x`, assuming 0 to be the position of the
## most significant bit
type T = type(x)
let mask = mostSignificantBit(x.type) shr bit
x = x or mask
proc lowerBit*(x: var BitIndexable, bit: Natural) =
## raises bit in a byte, assuming 0 to be the position of the
## least significant bit
type T = type(x)
let mask = T(0b1 shl bit)
x = x and not mask
proc lowerBitLE*(x: var BitIndexable, bit: Natural) =
lowerBit(x, bit)
proc lowerBitBE*(x: var BitIndexable, bit: Natural) =
## raises a bit in `x`, assuming 0 to be the position of the
## most significant bit
type T = type(x)
let mask = mostSignificantBit(x.type) shr bit
x = x and not mask
template byteIndex(pos: Natural): int =
pos shr 3 # same as pos div 8
template bitIndex(pos: Natural): int =
pos and 0b111 # same as pos mod 8
proc getBit*(bytes: openarray[byte], pos: Natural): bool {.inline.} =
getBit(bytes[byteIndex pos], bitIndex pos)
proc getBitBE*(bytes: openarray[byte], pos: Natural): bool {.inline.} =
getBitBE(bytes[byteIndex pos], bitIndex pos)
template getBitLE*(bytes: openarray[byte], pos: Natural): bool =
getBit(bytes, pos)
proc setBit*(bytes: var openarray[byte], pos: Natural, value: bool) {.inline.} =
setBit(bytes[byteIndex pos], bitIndex pos, value)
proc setBitBE*(bytes: var openarray[byte], pos: Natural, value: bool) {.inline.} =
setBitBE(bytes[byteIndex pos], bitIndex pos, value)
template getBitLE*(bytes: var openarray[byte], pos: Natural, value: bool) =
setBitLE(bytes, pos)
proc lowerBit*(bytes: var openarray[byte], pos: Natural) {.inline.} =
lowerBit(bytes[byteIndex pos], bitIndex pos)
proc lowerBitBE*(bytes: var openarray[byte], pos: Natural) {.inline.} =
lowerBitBE(bytes[byteIndex pos], bitIndex pos)
template lowerBitLE*(bytes: var openarray[byte], pos: Natural) =
lowerBit(bytes, pos)
proc raiseBit*(bytes: var openarray[byte], pos: Natural) {.inline.} =
raiseBit(bytes[byteIndex pos], bitIndex pos)
proc raiseBitBE*(bytes: var openarray[byte], pos: Natural) {.inline.} =
raiseBitBE(bytes[byteIndex pos], bitIndex pos)
template raiseBitLE*(bytes: var openarray[byte], pos: Natural) =
raiseBit(bytes, pos)
when isMainModule:
template test() =
doAssert countOnes(0b01000100'u8) == 2
doAssert parity(0b00000001'u8) == 1
doAssert firstOne(0b00000010'u8) == 2
doAssert firstOne(0'u8) == 0
doAssert log2trunc(0b01000000'u8) == 6
doAssert leadingZeros(0b00100000'u8) == 2
doAssert trailingZeros(0b00100000'u8) == 5
doAssert leadingZeros(0'u8) == 8
doAssert trailingZeros(0'u8) == 8
test()
static: test()

90
stew/bitseqs.nim Normal file
View File

@ -0,0 +1,90 @@
import
bitops2
type
Bytes = seq[byte]
BitSeq* = distinct Bytes
BitArray*[bits: static int] = object
bytes*: array[(bits + 7) div 8, byte]
proc len*(s: BitSeq): int =
let
bytesCount = s.Bytes.len
lastByte = s.Bytes[bytesCount - 1]
markerPos = log2trunc(lastByte)
Bytes(s).len * 8 - (8 - markerPos)
template bytes*(s: BitSeq): untyped =
Bytes(s)
proc add*(s: var BitSeq, value: bool) =
let
lastBytePos = s.Bytes.len - 1
lastByte = s.Bytes[lastBytePos]
if (lastByte and byte(128)) == 0:
# There is at least one leading zero, so we have enough
# room to store the new bit
let markerPos = log2trunc(lastByte)
s.Bytes[lastBytePos].setBit markerPos, value
s.Bytes[lastBytePos].raiseBit markerPos + 1
else:
s.Bytes[lastBytePos].setBit 7, value
s.Bytes.add byte(1)
proc `[]`*(s: BitSeq, pos: Natural): bool {.inline.} =
doAssert pos < s.len
s.Bytes.getBit pos
proc `[]=`*(s: var BitSeq, pos: Natural, value: bool) {.inline.} =
doAssert pos < s.len
s.Bytes.setBit pos, value
proc raiseBit*(s: var BitSeq, pos: Natural) {.inline.} =
doAssert pos < s.len
raiseBit s.Bytes, pos
proc lowerBit*(s: var BitSeq, pos: Natural) {.inline.} =
doAssert pos < s.len
lowerBit s.Bytes, pos
proc init*(T: type BitSeq, len: int): T =
result = BitSeq newSeq[byte](1 + len div 8)
Bytes(result).raiseBit len
proc init*(T: type BitArray): T =
# The default zero-initializatio is fine
discard
template `[]`*(a: BitArray, pos: Natural): bool =
getBit a.bytes, pos
template `[]=`*(a: var BitArray, pos: Natural, value: bool) =
setBit a.bytes, pos, value
template raiseBit*(a: var BitArray, pos: Natural) =
raiseBit a.bytes, pos
template lowerBit*(a: var BitArray, pos: Natural) =
lowerBit a.bytes, pos
# TODO: Submit this to the standard library as `cmp`
# At the moment, it doesn't work quite well because Nim selects
# the generic cmp[T] from the system module instead of choosing
# the openarray overload
proc compareArrays[T](a, b: openarray[T]): int =
result = cmp(a.len, b.len)
if result != 0: return
for i in 0 ..< a.len:
result = cmp(a[i], b[i])
if result != 0: return
template cmp*(a, b: BitSeq): int =
compareArrays(Bytes a, Bytes b)
template `==`*(a, b: BitSeq): bool =
cmp(a, b) == 0

View File

@ -1,5 +1,5 @@
import
typedranges, ptr_arith
../bitops2, typedranges, ptr_arith
type
BitRange* = object
@ -7,8 +7,6 @@ type
start: int
mLen: int
BitIndexable = SomeUnsignedInt
template `@`(s, idx: untyped): untyped =
(when idx is BackwardsIndex: s.len - int(idx) else: int(idx))
@ -39,103 +37,17 @@ template bits*(bytes: MutByteRange, slice: HSlice): BitRange =
template bits*(x: BitRange): BitRange = x
template mostSignificantBit(T: typedesc): auto =
const res = 1 shl (sizeof(T) * 8 - 1)
T(res)
template getBit*(x: BitIndexable, bit: Natural): bool =
## reads a bit from `x`, assuming 0 to be the position of the
## most significant bit
(x and mostSignificantBit(x.type) shr bit) != 0
template getBitLE*(x: BitIndexable, bit: Natural): bool =
## reads a bit from `x`, assuming 0 to be the position of the
## least significant bit
type T = type(x)
(x and T(0b1 shl bit)) != 0
proc setBit*(x: var BitIndexable, bit: Natural, val: bool) =
## writes a bit in `x`, assuming 0 to be the position of the
## most significant bit
let mask = mostSignificantBit(x.type) shr bit
if val:
x = x or mask
else:
x = x and not mask
proc setBitLE*(x: var BitIndexable, bit: Natural, val: bool) =
## writes a bit in `x`, assuming 0 to be the position of the
## least significant bit
type T = type(x)
let mask = 0b1 shl bit
if val:
x = x or mask
else:
x = x and not mask
proc raiseBit*(x: var BitIndexable, bit: Natural) =
## raises a bit in `x`, assuming 0 to be the position of the
## most significant bit
type T = type(x)
let mask = mostSignificantBit(x.type) shr bit
x = x or mask
proc lowerBit*(x: var BitIndexable, bit: Natural) =
## raises a bit in `x`, assuming 0 to be the position of the
## most significant bit
type T = type(x)
let mask = mostSignificantBit(x.type) shr bit
x = x and not mask
proc raiseBitLE*(x: var BitIndexable, bit: Natural) =
## raises bit in `x`, assuming 0 to be the position of the
## least significant bit
type T = type(x)
let mask = 0b1 shl bit
x = x or mask
proc lowerBitLE*(x: var BitIndexable, bit: Natural) =
## raises bit in a byte, assuming 0 to be the position of the
## least significant bit
type T = type(x)
let mask = 0b1 shl bit
x = x and not mask
proc len*(r: BitRange): int {.inline.} = r.mLen
template getAbsoluteBit(bytes, absIdx: untyped): bool =
## Returns a bit with a position relative to the start of
## the underlying range. Not to be confused with a position
## relative to the start of the BitRange (i.e. the two would
## match only when range.start == 0).
let
byteToCheck = absIdx shr 3 # the same as absIdx / 8
bitToCheck = (absIdx and 0b111)
getBit(bytes[byteToCheck], bitToCheck)
template setAbsoluteBit(bytes, absIdx, value) =
let
byteToWrite = absIdx shr 3 # the same as absIdx / 8
bitToWrite = (absIdx and 0b111)
setBit(bytes[byteToWrite], bitToWrite, value)
iterator enumerateBits(x: BitRange): (int, bool) =
var p = x.start
var i = 0
let e = x.len
while i != e:
yield (i, getAbsoluteBit(x.data, p))
yield (i, getBitBE(x.data.toOpenArray, p))
inc p
inc i
proc getBit*(bytes: openarray[byte], pos: Natural): bool =
getAbsoluteBit(bytes, pos)
proc setBit*(bytes: var openarray[byte], pos: Natural, value: bool) =
setAbsoluteBit(bytes, pos, value)
iterator items*(x: BitRange): bool =
for _, v in enumerateBits(x): yield v
@ -145,7 +57,7 @@ iterator pairs*(x: BitRange): (int, bool) =
proc `[]`*(x: BitRange, idx: int): bool {.inline.} =
doAssert idx < x.len
let p = x.start + idx
result = getAbsoluteBit(x.data, p)
result = getBitBE(x.data.toOpenArray, p)
proc sliceNormalized(x: BitRange, ibegin, iend: int): BitRange =
doAssert ibegin >= 0 and
@ -170,18 +82,7 @@ proc `==`*(a, b: BitRange): bool =
proc `[]=`*(r: var BitRange, idx: Natural, val: bool) {.inline.} =
doAssert idx < r.len
let absIdx = r.start + idx
setAbsoluteBit(r.data, absIdx, val)
proc setAbsoluteBit(x: BitRange, absIdx: int, val: bool) {.inline.} =
## Assumes the destination bit is already zeroed.
## Works with absolute positions similar to `getAbsoluteBit`
doAssert absIdx < x.len
let
byteToWrite = absIdx shr 3 # the same as absIdx / 8
bitToWrite = (absIdx and 0b111)
if val:
raiseBit x.data[byteToWrite], bitToWrite
setBitBE(r.data.toOpenArray, absIdx, val)
proc pushFront*(x: var BitRange, val: bool) =
doAssert x.start > 0
@ -203,15 +104,15 @@ proc `&`*(a, b: BitRange): BitRange =
var bytes = newSeq[byte](totalLen.neededBytes)
result = bits(bytes, 0, totalLen)
for i in 0 ..< a.len: result.setAbsoluteBit(i, a[i])
for i in 0 ..< b.len: result.setAbsoluteBit(i + a.len, b[i])
for i in 0 ..< a.len: result.data.toOpenArray.setBitBE(i, a[i])
for i in 0 ..< b.len: result.data.toOpenArray.setBitBE(i + a.len, b[i])
proc `$`*(r: BitRange): string =
result = newStringOfCap(r.len)
for b in r:
result.add(if b: '1' else: '0')
for bit in r:
result.add(if bit: '1' else: '0')
proc fromBits*(T: typedesc, r: BitRange, offset, num: Natural): T =
proc fromBits*(T: type, r: BitRange, offset, num: Natural): T =
doAssert(num <= sizeof(T) * 8)
# XXX: Nim has a bug that a typedesc parameter cannot be used
# in a type coercion, so we must define an alias here:
@ -219,12 +120,12 @@ proc fromBits*(T: typedesc, r: BitRange, offset, num: Natural): T =
for i in 0 ..< num:
result = (result shl 1) or TT(r[offset + i])
proc parse*(T: typedesc[BitRange], s: string): BitRange =
proc parse*(T: type BitRange, s: string): BitRange =
var bytes = newSeq[byte](s.len.neededBytes)
for i, c in s:
case c
of '0': discard
of '1': raiseBit(bytes[i shr 3], i and 0b111)
of '1': raiseBitBE(bytes, i)
else: doAssert false
result = bits(bytes, 0, s.len)

View File

@ -1,12 +1,54 @@
import
random, unittest,
../../stew/ranges/bitranges
../../stew/ranges/bitranges, ../../stew/bitseqs
proc randomBytes(n: int): seq[byte] =
result = newSeq[byte](n)
for i in 0 ..< result.len:
result[i] = byte(rand(256))
suite "bit sequences":
test "growing and indexing":
var b0 = BitSeq.init(0)
check b0.len == 0
var b10 = BitSeq.init(10)
check b10.len == 10
var b100 = BitSeq.init(100)
check b100.len == 100
var bytes = randomBytes(50)
var bitArr: BitArray[50]
for i in 0 ..< bytes.len:
let bit = bytes[i] < 128
b0.add bit
if i < b10.len:
b10[i] = bit
else:
b10.add bit
b100[i] = bit
bitArr[i] = bit
check:
b0.len == i + 1
b10.len == max(i + 1, 10)
b100.len == 100
for i in 0 ..< bytes.len:
let bit = bytes[i] < 128
check:
b0[i] == bit
b10[i] == bit
b100[i] == bit
bitArr[i] == bit
suite "bit ranges":
test "basic":