mirror of
https://github.com/status-im/nim-stint.git
synced 2025-02-19 18:38:13 +00:00
Refresh endians2 (#160)
* speed up conversions by unrolling bulk limb conversion * remove default from `toBytes`/`fromBytes` - the existing default did not match `stew/endians2` which is an unnecessary risk - rather than changing it to match, the safer option is to remove it for now so that code in the wild gets a compile error * deprecate redundant names like `toByteArrayXX` (`toBytesXX` etc do the same thing) * deprecate staticFor (now lives in stew)
This commit is contained in:
parent
5c5e01cef0
commit
b06bb210d9
@ -1,29 +0,0 @@
|
||||
import std/macros
|
||||
|
||||
proc replaceNodes(ast: NimNode, what: NimNode, by: NimNode): NimNode =
|
||||
# Replace "what" ident node by "by"
|
||||
proc inspect(node: NimNode): NimNode =
|
||||
case node.kind:
|
||||
of {nnkIdent, nnkSym}:
|
||||
if node.eqIdent(what):
|
||||
return by
|
||||
return node
|
||||
of nnkEmpty:
|
||||
return node
|
||||
of nnkLiterals:
|
||||
return node
|
||||
else:
|
||||
var rTree = node.kind.newTree()
|
||||
for child in node:
|
||||
rTree.add inspect(child)
|
||||
return rTree
|
||||
result = inspect(ast)
|
||||
|
||||
macro staticFor*(idx: untyped{nkIdent}, start, stopEx: static int, body: untyped): untyped =
|
||||
## staticFor [min inclusive, max exclusive)
|
||||
result = newStmtList()
|
||||
for i in start ..< stopEx:
|
||||
result.add nnkBlockStmt.newTree(
|
||||
ident("unrolledIter_" & $idx & $i),
|
||||
body.replaceNodes(idx, newLit i)
|
||||
)
|
@ -7,275 +7,296 @@
|
||||
#
|
||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
import private/datatypes
|
||||
import stew/[endians2, staticfor], private/datatypes
|
||||
|
||||
{.push raises: [], noinit, gcsafe.}
|
||||
{.push raises: [], noinit, gcsafe, inline.}
|
||||
|
||||
# Serialization
|
||||
# ------------------------------------------------------------------------------------------
|
||||
|
||||
template toByte(x: SomeUnsignedInt): byte =
|
||||
## At compile-time, conversion to bytes checks the range
|
||||
## we want to ensure this is done at the register level
|
||||
## at runtime in a single "mov byte" instruction
|
||||
template copyBytes(tgt, src, tstart, sstart, n: untyped) =
|
||||
when nimvm:
|
||||
byte(x and 0xFF)
|
||||
for i in 0..<n:
|
||||
tgt[tstart + i] = src[sstart + i]
|
||||
else:
|
||||
byte(x)
|
||||
moveMem(addr tgt[tstart], unsafeAddr src[sstart], n)
|
||||
|
||||
template blobFrom(dst: var openArray[byte], src: SomeUnsignedInt, startIdx: int, endian: static Endianness) =
|
||||
## Write an integer into a raw binary blob
|
||||
## Swapping endianness if needed
|
||||
when endian == cpuEndian:
|
||||
for i in 0 ..< sizeof(src):
|
||||
dst[startIdx+i] = toByte((src shr (i * 8)))
|
||||
else:
|
||||
for i in 0 ..< sizeof(src):
|
||||
dst[startIdx+sizeof(src)-1-i] = toByte((src shr (i * 8)))
|
||||
template toBytesCopy(src: StUint) =
|
||||
# Copy src to result maintaining native byte order
|
||||
const words = result.len div sizeof(Word)
|
||||
staticFor i, 0 ..< words:
|
||||
let limb = src.limbs[i].toBytes()
|
||||
const pos = i * sizeof(Word)
|
||||
copyBytes(result, limb, pos, 0, sizeof(Word))
|
||||
|
||||
const leftover = result.len - (words * sizeof(Word))
|
||||
when leftover > 0:
|
||||
# Handle partial limb when bits don't line up with word size
|
||||
let limb = src.limbs[^1].toBytes()
|
||||
copyBytes(result, limb, result.len - leftover, 0, leftover)
|
||||
|
||||
template toBytesSwap(src: StUint) =
|
||||
# Copy src to result swapping both limb and byte order
|
||||
const words = result.len div sizeof(Word)
|
||||
staticFor i, 0 ..< words:
|
||||
let limb = swapBytes(src.limbs[i]).toBytes()
|
||||
const pos = result.len - (i + 1) * sizeof(Word)
|
||||
copyBytes(result, limb, pos, 0, sizeof(Word))
|
||||
|
||||
const leftover = result.len - (words * sizeof(Word))
|
||||
when leftover > 0:
|
||||
let limb = swapBytes(src.limbs[^1]).toBytes()
|
||||
copyBytes(result, limb, 0, limb.len - leftover, leftover)
|
||||
|
||||
func toBytesLE*[bits: static int](src: StUint[bits]): array[bits div 8, byte] =
|
||||
var
|
||||
src_idx, dst_idx = 0
|
||||
acc: Word = 0
|
||||
acc_len = 0
|
||||
|
||||
when cpuEndian == bigEndian:
|
||||
srcIdx = src.limbs.len - 1
|
||||
|
||||
var tail = result.len
|
||||
while tail > 0:
|
||||
when cpuEndian == littleEndian:
|
||||
let w = if src_idx < src.limbs.len: src.limbs[src_idx]
|
||||
else: 0
|
||||
inc src_idx
|
||||
else:
|
||||
let w = if src_idx >= 0: src.limbs[src_idx]
|
||||
else: 0
|
||||
dec src_idx
|
||||
|
||||
if acc_len == 0:
|
||||
# We need to refill the buffer to output 64-bit
|
||||
acc = w
|
||||
acc_len = WordBitWidth
|
||||
else:
|
||||
let lo = acc
|
||||
acc = w
|
||||
|
||||
if tail >= sizeof(Word):
|
||||
# Unrolled copy
|
||||
result.blobFrom(src = lo, dst_idx, littleEndian)
|
||||
dst_idx += sizeof(Word)
|
||||
tail -= sizeof(Word)
|
||||
else:
|
||||
# Process the tail and exit
|
||||
when cpuEndian == littleEndian:
|
||||
# When requesting little-endian on little-endian platform
|
||||
# we can just copy each byte
|
||||
# tail is inclusive
|
||||
for i in 0 ..< tail:
|
||||
result[dst_idx+i] = toByte(lo shr (i*8))
|
||||
else: # TODO check this
|
||||
# We need to copy from the end
|
||||
for i in 0 ..< tail:
|
||||
result[dst_idx+i] = toByte(lo shr ((tail-i)*8))
|
||||
return
|
||||
|
||||
func toBytesBE*[bits: static int](src: StUint[bits]): array[bits div 8, byte] {.inline.} =
|
||||
var
|
||||
src_idx = 0
|
||||
acc: Word = 0
|
||||
acc_len = 0
|
||||
|
||||
when cpuEndian == bigEndian:
|
||||
srcIdx = src.limbs.len - 1
|
||||
|
||||
var tail = result.len
|
||||
while tail > 0:
|
||||
when cpuEndian == littleEndian:
|
||||
let w = if src_idx < src.limbs.len: src.limbs[src_idx]
|
||||
else: 0
|
||||
inc src_idx
|
||||
else:
|
||||
let w = if src_idx >= 0: src.limbs[src_idx]
|
||||
else: 0
|
||||
dec src_idx
|
||||
|
||||
if acc_len == 0:
|
||||
# We need to refill the buffer to output 64-bit
|
||||
acc = w
|
||||
acc_len = WordBitWidth
|
||||
else:
|
||||
let lo = acc
|
||||
acc = w
|
||||
|
||||
if tail >= sizeof(Word):
|
||||
# Unrolled copy
|
||||
tail -= sizeof(Word)
|
||||
result.blobFrom(src = lo, tail, bigEndian)
|
||||
else:
|
||||
# Process the tail and exit
|
||||
when cpuEndian == littleEndian:
|
||||
# When requesting little-endian on little-endian platform
|
||||
# we can just copy each byte
|
||||
# tail is inclusive
|
||||
for i in 0 ..< tail:
|
||||
result[tail-1-i] = toByte(lo shr (i*8))
|
||||
else:
|
||||
# We need to copy from the end
|
||||
for i in 0 ..< tail:
|
||||
result[tail-1-i] = toByte(lo shr ((tail-i)*8))
|
||||
return
|
||||
|
||||
func toBytes*[bits: static int](x: StUint[bits], endian: Endianness = bigEndian): array[bits div 8, byte] {.inline.} =
|
||||
## Default to bigEndian
|
||||
if endian == littleEndian:
|
||||
result = x.toBytesLE()
|
||||
## Encode `src` to a byte array using little-endian byte order
|
||||
when cpuEndian == littleEndian:
|
||||
toBytesCopy(src)
|
||||
else:
|
||||
result = x.toBytesBE()
|
||||
toBytesSwap(src)
|
||||
|
||||
func toBytesBE*[bits: static int](src: StUint[bits]): array[bits div 8, byte] =
|
||||
## Encode `src` to a byte array using big-endian byte order
|
||||
when cpuEndian == littleEndian:
|
||||
toBytesSwap(src)
|
||||
else:
|
||||
toBytesCopy(src)
|
||||
|
||||
func toBytes*[bits: static int](
|
||||
x: StUint[bits], endian: Endianness
|
||||
): array[bits div 8, byte] =
|
||||
## Encode `src` to a byte array using the given byte order
|
||||
## TODO Unlike the corresponding function in stew/endians that defaults to
|
||||
## native endian, this function used to default to big endian - the
|
||||
## default has been removed to avoid confusion and runtime surprises
|
||||
if endian == littleEndian:
|
||||
x.toBytesLE()
|
||||
else:
|
||||
x.toBytesBE()
|
||||
|
||||
# Deserialization
|
||||
# ------------------------------------------------------------------------------------------
|
||||
|
||||
when sizeof(Word) == 8:
|
||||
const
|
||||
wordMask = 0b111
|
||||
wordShift = 3
|
||||
elif sizeof(Word) == 4:
|
||||
const
|
||||
wordMask = 0b11
|
||||
wordShift = 2
|
||||
else:
|
||||
static:
|
||||
raiseAssert "unsupported"
|
||||
|
||||
template fromBytesSwapUnrolled(src: openArray[byte]) =
|
||||
# Copy src to result swapping limb and byte order - unrolled version for when
|
||||
# src doers not need padding
|
||||
const
|
||||
bytes = result.bits div 8
|
||||
words = bytes shr wordShift
|
||||
|
||||
staticFor i, 0 ..< words:
|
||||
const pos = bytes - (i + 1) * sizeof(Word)
|
||||
let limb = Word.fromBytes(src.toOpenArray(pos, pos + sizeof(Word) - 1))
|
||||
result.limbs[i] = swapBytes(limb)
|
||||
|
||||
# Handle partial limb in case the bit length doesn't line up with word size
|
||||
const leftover = bytes mod sizeof(Word)
|
||||
when leftover > 0:
|
||||
var limb: Word
|
||||
staticFor i, 0 ..< leftover:
|
||||
limb = limb or (Word(src[i]) shl ((leftover - i - 1) shl 3))
|
||||
|
||||
result.limbs[words] = limb
|
||||
|
||||
template fromBytesSwap(src: openArray[byte]) =
|
||||
# Copy src to result swapping limb and byte order
|
||||
if src.len >= result.bits div 8:
|
||||
# Fast path - there's enough bytes for the full stint meaning we don't have
|
||||
# to worry about out-of-bounds src access
|
||||
fromBytesSwapUnrolled(src)
|
||||
else:
|
||||
# Slow path - src is shorter than the integer so we must zero-pad
|
||||
let
|
||||
bytes = src.len
|
||||
words = bytes shr wordShift
|
||||
|
||||
block bulk: # Unroll this part to reduce in-loop arithmetic
|
||||
staticFor i, 0 ..< result.limbs.len:
|
||||
if i >= words:
|
||||
break bulk
|
||||
const reversePos = (i + 1) * sizeof(Word)
|
||||
let
|
||||
pos = bytes - reversePos
|
||||
limb = Word.fromBytes(src.toOpenArray(pos, pos + sizeof(Word) - 1))
|
||||
result.limbs[i] = swapBytes(limb)
|
||||
|
||||
# Handle partial limb in case the bit length doesn't line up with word size
|
||||
let leftover = bytes and wordMask
|
||||
var limb: Word
|
||||
for i in 0 ..< leftover:
|
||||
limb = limb or (Word(src[i]) shl ((leftover - i - 1) shl 3))
|
||||
|
||||
result.limbs[words] = limb
|
||||
|
||||
# noinit means we have to manually zero the limbs missing from src
|
||||
for i in words + 1 ..< result.limbs.len:
|
||||
result.limbs[i] = 0
|
||||
|
||||
template fromBytesCopyUnrolled(src: openArray[byte]) =
|
||||
# Copy src to result maintaining limb and byte order - unrolled version for when
|
||||
# src doers not need padding
|
||||
const
|
||||
bytes = result.bits div 8
|
||||
words = bytes shr wordShift
|
||||
|
||||
staticFor i, 0 ..< words:
|
||||
const pos = i * sizeof(Word)
|
||||
let limb = Word.fromBytes(src.toOpenArray(pos, pos + sizeof(Word) - 1))
|
||||
result.limbs[i] = limb
|
||||
|
||||
const leftover = bytes and wordMask
|
||||
when leftover > 0:
|
||||
var limb: Word
|
||||
staticFor i, 0 .. leftover - 1:
|
||||
limb = limb or (Word(src[bytes - i - 1]) shl ((leftover - i - 1) shl 3))
|
||||
|
||||
result.limbs[words] = limb
|
||||
|
||||
template fromBytesCopy(src: openArray[byte]) =
|
||||
if src.len >= result.bits div 8:
|
||||
fromBytesCopyUnrolled(src)
|
||||
else:
|
||||
let
|
||||
bytes = src.len
|
||||
words = bytes shr wordShift
|
||||
|
||||
block bulk: # Unroll this part to reduce in-loop arithmetic
|
||||
staticFor i, 0 ..< result.limbs.len:
|
||||
if i >= words:
|
||||
break bulk
|
||||
|
||||
const pos = i * sizeof(Word)
|
||||
let
|
||||
limb = Word.fromBytes(src.toOpenArray(pos, pos + sizeof(Word) - 1))
|
||||
result.limbs[i] = limb
|
||||
|
||||
let leftover = bytes and wordMask
|
||||
var limb: Word
|
||||
for i in 0 ..< leftover:
|
||||
limb = limb or (Word(src[bytes - i - 1]) shl ((leftover - i - 1) shl 3))
|
||||
|
||||
# No overflow because src.len < result.bits div 8
|
||||
result.limbs[words] = limb
|
||||
|
||||
for i in words + 1 ..< result.limbs.len:
|
||||
result.limbs[i] = 0
|
||||
|
||||
func fromBytesBE*[bits: static int](
|
||||
T: typedesc[StUint[bits]],
|
||||
x: openArray[byte]): T {.raises: [], noinit, gcsafe.} =
|
||||
## Read big endian bytes and convert to an integer. At runtime, v must contain
|
||||
## at least sizeof(T) bytes. Native endianess is used which is not
|
||||
## portable! (i.e. use fixed-endian byte array or hex for serialization)
|
||||
T: typedesc[StUint[bits]], src: openArray[byte]
|
||||
): T =
|
||||
## Read big endian bytes and convert to an integer. At runtime, src must contain
|
||||
## at least sizeof(T) bytes.
|
||||
## TODO Contrary to docs and the corresponding stew/endians2 function, src is
|
||||
## actually padded when short - this may change in the future to match
|
||||
## stew where it panics instead
|
||||
when cpuEndian == littleEndian:
|
||||
fromBytesSwap(src)
|
||||
else:
|
||||
fromBytesCopy(src)
|
||||
|
||||
var accum: Word = 0
|
||||
var accumBits: int = 0
|
||||
var dstIdx: int
|
||||
|
||||
when cpuEndian == littleEndian: # src is bigEndian, CPU is little-endian
|
||||
dstIdx = 0
|
||||
|
||||
for srcIdx in countdown(x.len-1, 0):
|
||||
let srcByte = Word(x[srcIdx])
|
||||
|
||||
accum = accum or (srcByte shl accumBits)
|
||||
accumBits += 8
|
||||
|
||||
if accumBits >= WordBitWidth:
|
||||
result.limbs[dstIdx] = accum
|
||||
inc dstIdx
|
||||
accumBits -= WordBitWidth
|
||||
accum = srcByte shr (8 - accumBits)
|
||||
|
||||
if dstIdx < result.limbs.len:
|
||||
result.limbs[dstIdx] = accum
|
||||
for fillIdx in dstIdx+1 ..< result.limbs.len:
|
||||
result.limbs[fillIdx] = 0
|
||||
else: # src and CPU are bigEndian
|
||||
dstIdx = result.limbs.len-1
|
||||
|
||||
for srcIdx in countdown(x.len-1, 0):
|
||||
let srcByte = Word(x[srcIdx])
|
||||
|
||||
accum = accum or (srcByte shl accumBits)
|
||||
accumBits += 8
|
||||
|
||||
if accumBits >= WordBitWidth:
|
||||
result.limbs[dstIdx] = accum
|
||||
dec dstIdx
|
||||
accumBits -= WordBitWidth
|
||||
accum = srcByte shr (8 - accumBits)
|
||||
|
||||
if dstIdx > 0:
|
||||
result.limbs[dstIdx] = accum
|
||||
for fillIdx in 0 ..< dstIdx:
|
||||
result.limbs[fillIdx] = 0
|
||||
func fromBytesBE*[bits: static int](
|
||||
T: typedesc[StUint[bits]], src: array[bits div 8, byte]
|
||||
): T =
|
||||
## Read big endian bytes and convert to an integer. At runtime, src must contain
|
||||
## at least sizeof(T) bytes.
|
||||
## TODO Contrary to docs and the corresponding stew/endians2 function, src is
|
||||
## actually padded when short - this may change in the future to match
|
||||
## stew where it panics instead
|
||||
when cpuEndian == littleEndian:
|
||||
fromBytesSwapUnrolled(src)
|
||||
else:
|
||||
fromBytesCopyUnrolled(src)
|
||||
|
||||
func fromBytesLE*[bits: static int](
|
||||
T: typedesc[StUint[bits]],
|
||||
x: openArray[byte]): T =
|
||||
## Read little endian bytes and convert to an integer. At runtime, v must
|
||||
## contain at least sizeof(T) bytes. By default, native endianess is used
|
||||
## which is not portable! (i.e. use fixed-endian byte array or hex for serialization)
|
||||
T: typedesc[StUint[bits]], src: openArray[byte]
|
||||
): T =
|
||||
## Read little endian bytes and convert to an integer. At runtime, src must
|
||||
## contain at least sizeof(T) bytes.
|
||||
## TODO Contrary to docs and the corresponding stew/endians2 function, src is
|
||||
## actually padded when short - this may change in the future to match
|
||||
## stew where it panics instead
|
||||
|
||||
var accum: Word = 0
|
||||
var accumBits: int = 0
|
||||
var dstIdx: int
|
||||
when cpuEndian == littleEndian:
|
||||
fromBytesCopy(src)
|
||||
else:
|
||||
fromBytesSwap(src)
|
||||
|
||||
when cpuEndian == littleEndian: # src and CPU are little-endian
|
||||
dstIdx = 0
|
||||
|
||||
for srcIdx in 0 ..< x.len:
|
||||
let srcByte = Word(x[srcIdx])
|
||||
|
||||
accum = accum or (srcByte shl accumBits)
|
||||
accumBits += 8
|
||||
|
||||
if accumBits >= WordBitWidth:
|
||||
result.limbs[dstIdx] = accum
|
||||
inc dstIdx
|
||||
accumBits -= WordBitWidth
|
||||
accum = srcByte shr (8 - accumBits)
|
||||
|
||||
if dstIdx < result.limbs.len:
|
||||
result.limbs[dstIdx] = accum
|
||||
for fillIdx in dstIdx+1 ..< result.limbs.len:
|
||||
result.limbs[fillIdx] = 0
|
||||
else: # src is little endian, CPU is bigEndian
|
||||
dstIdx = result.limbs.len-1
|
||||
|
||||
for srcIdx in 0 ..< x.len:
|
||||
let srcByte = Word(x[srcIdx])
|
||||
|
||||
accum = accum or (srcByte shl accumBits)
|
||||
accumBits += 8
|
||||
|
||||
if accumBits >= WordBitWidth:
|
||||
result.limbs[dstIdx] = accum
|
||||
dec dstIdx
|
||||
accumBits -= WordBitWidth
|
||||
accum = srcByte shr (8 - accumBits)
|
||||
|
||||
if dstIdx > 0:
|
||||
result.limbs[dstIdx] = accum
|
||||
for fillIdx in 0 ..< dstIdx:
|
||||
result.limbs[fillIdx] = 0
|
||||
func fromBytesLE*[bits: static int](
|
||||
T: typedesc[StUint[bits]], src: array[bits div 8, byte]
|
||||
): T =
|
||||
when cpuEndian == littleEndian:
|
||||
fromBytesCopyUnrolled(src)
|
||||
else:
|
||||
fromBytesSwapUnrolled(src)
|
||||
|
||||
func fromBytes*[bits: static int](
|
||||
T: typedesc[StUint[bits]],
|
||||
x: openArray[byte],
|
||||
srcEndian: Endianness = bigEndian): T {.inline.} =
|
||||
T: typedesc[StUint[bits]], src: openArray[byte], srcEndian: Endianness
|
||||
): T =
|
||||
## Read an source bytearray with the specified endianness and
|
||||
## convert it to an integer
|
||||
## Default to bigEndian
|
||||
## TODO Unlike the corresponding function in stew/endians that defaults to
|
||||
## native endian, this function used to default to big endian - the
|
||||
## default has been removed to avoid confusion and runtime surprises
|
||||
if srcEndian == littleEndian:
|
||||
result = fromBytesLE(T, x)
|
||||
fromBytesLE(T, src)
|
||||
else:
|
||||
result = fromBytesBE(T, x)
|
||||
fromBytesBE(T, src)
|
||||
|
||||
func fromBytes*[bits: static int](
|
||||
T: typedesc[StUint[bits]], src: array[bits div 8, byte], srcEndian: Endianness
|
||||
): T =
|
||||
## Read an source bytearray with the specified endianness and
|
||||
## convert it to an integer
|
||||
## TODO Unlike the corresponding function in stew/endians that defaults to
|
||||
## native endian, this function used to default to big endian - the
|
||||
## default has been removed to avoid confusion and runtime surprises
|
||||
if srcEndian == littleEndian:
|
||||
fromBytesLE(T, src)
|
||||
else:
|
||||
fromBytesBE(T, src)
|
||||
|
||||
# Signed integer version of above funcs
|
||||
# TODO these are not in stew/endians2 :/
|
||||
# ------------------------------------------------------------------------------------------
|
||||
|
||||
func toBytesLE*[bits: static int](src: StInt[bits]):
|
||||
array[bits div 8, byte] {.inline.} =
|
||||
func toBytesLE*[bits: static int](src: StInt[bits]): array[bits div 8, byte] =
|
||||
toBytesLE(src.impl)
|
||||
|
||||
func toBytesBE*[bits: static int](src: StInt[bits]):
|
||||
array[bits div 8, byte] {.inline.} =
|
||||
func toBytesBE*[bits: static int](src: StInt[bits]): array[bits div 8, byte] =
|
||||
toBytesBE(src.impl)
|
||||
|
||||
func toBytes*[bits: static int](x: StInt[bits], endian: Endianness = bigEndian):
|
||||
array[bits div 8, byte] {.inline.} =
|
||||
func toBytes*[bits: static int](
|
||||
x: StInt[bits], endian: Endianness
|
||||
): array[bits div 8, byte] =
|
||||
## TODO Unlike the corresponding function in stew/endians that defaults to
|
||||
## native endian, this function used to default to big endian - the
|
||||
## default has been removed to avoid confusion and runtime surprises
|
||||
toBytes(x.impl, endian)
|
||||
|
||||
func fromBytesBE*[bits: static int](
|
||||
T: typedesc[StInt[bits]],
|
||||
x: openArray[byte]): T {.raises: [], noinit, gcsafe, inline.} =
|
||||
T: typedesc[StInt[bits]], x: openArray[byte]
|
||||
): T {.raises: [], noinit, gcsafe, inline.} =
|
||||
fromBytesBE(type result.impl, x)
|
||||
|
||||
func fromBytesLE*[bits: static int](
|
||||
T: typedesc[StInt[bits]],
|
||||
x: openArray[byte]): T {.inline.} =
|
||||
func fromBytesLE*[bits: static int](T: typedesc[StInt[bits]], x: openArray[byte]): T =
|
||||
fromBytesLE(type result.impl, x)
|
||||
|
||||
func fromBytes*[bits: static int](
|
||||
T: typedesc[StInt[bits]],
|
||||
x: openArray[byte],
|
||||
srcEndian: Endianness = bigEndian): T {.inline.} =
|
||||
T: typedesc[StInt[bits]], x: openArray[byte], srcEndian: Endianness
|
||||
): T =
|
||||
## TODO Unlike the corresponding function in stew/endians that defaults to
|
||||
## native endian, this function used to default to big endian - the
|
||||
## default has been removed to avoid confusion and runtime surprises
|
||||
fromBytes(type result.impl, x, srcEndian)
|
||||
|
39
stint/io.nim
39
stint/io.nim
@ -11,9 +11,12 @@ import
|
||||
# Standard library
|
||||
std/[typetraits, algorithm, hashes],
|
||||
# Internal
|
||||
stew/staticfor,
|
||||
./private/datatypes,
|
||||
./uintops, ./endians2
|
||||
|
||||
export endians2
|
||||
|
||||
from stew/byteutils import toHex
|
||||
|
||||
# Helpers
|
||||
@ -38,6 +41,10 @@ template hash*(num: StUint|StInt): Hash =
|
||||
mixin hash, limbs
|
||||
hash(num.limbs)
|
||||
|
||||
func swap*(a, b: var (StUint|StInt)) =
|
||||
staticFor i, 0..<a.len:
|
||||
swap(a.limbs[i], b.limbs[i])
|
||||
|
||||
{.pop.}
|
||||
|
||||
# Constructors
|
||||
@ -385,9 +392,9 @@ func readUintBE*[bits: static[int]](ba: openArray[byte]): StUint[bits] {.noinit,
|
||||
## - a big-endian openArray of size (bits div 8) at least
|
||||
## Returns:
|
||||
## - A unsigned integer of the same size with `bits` bits
|
||||
result = (typeof result).fromBytesBE(ba)
|
||||
(typeof result).fromBytesBE(ba)
|
||||
|
||||
func toByteArrayBE*[bits: static[int]](n: StUint[bits]): array[bits div 8, byte] {.noinit, inline.}=
|
||||
func toByteArrayBE*[bits: static[int]](n: StUint[bits]): array[bits div 8, byte] {.noinit, inline, deprecated: "endians2.toBytesBE".}=
|
||||
## Convert a Uint[bits] to to a big-endian array of bits div 8 bytes
|
||||
## Input:
|
||||
## - an unsigned integer
|
||||
@ -395,11 +402,11 @@ func toByteArrayBE*[bits: static[int]](n: StUint[bits]): array[bits div 8, byte]
|
||||
## - a big-endian array of the same size
|
||||
result = n.toBytesBE()
|
||||
|
||||
func fromBytesBE*(T: type StUint, ba: openArray[byte]): T {.noinit, inline.}=
|
||||
result = readUintBE[T.bits](ba)
|
||||
template fromBytesBE*(T: type StUint, ba: openArray[byte]): T {.deprecated: "endians2.fromBytesBE".}=
|
||||
endians2.fromBytesBE(T, ba)
|
||||
|
||||
template initFromBytesBE*(x: var StUint, ba: openArray[byte]) =
|
||||
x = fromBytesBE(type x, ba)
|
||||
x = endians2.fromBytesBE(type x, ba)
|
||||
|
||||
func readUintLE*[bits: static[int]](ba: openArray[byte]): StUint[bits] {.noinit, inline.}=
|
||||
## Convert a lettle-endian array of (bits div 8) Bytes to an UInt[bits] (in native host endianness)
|
||||
@ -409,16 +416,16 @@ func readUintLE*[bits: static[int]](ba: openArray[byte]): StUint[bits] {.noinit,
|
||||
## - A unsigned integer of the same size with `bits` bits
|
||||
result = (typeof result).fromBytesLE(ba)
|
||||
|
||||
func toByteArrayLE*[bits: static[int]](n: StUint[bits]): array[bits div 8, byte] {.noinit, inline.}=
|
||||
template toByteArrayLE*[bits: static[int]](n: StUint[bits]): array[bits div 8, byte] {.deprecated: "endians2.toBytesLE".} =
|
||||
## Convert a Uint[bits] to to a little-endian array of bits div 8 bytes
|
||||
## Input:
|
||||
## - an unsigned integer
|
||||
## Returns:
|
||||
## - a little-endian array of the same size
|
||||
result = n.toBytesLE()
|
||||
n.toBytesLE()
|
||||
|
||||
func fromBytesLE*(T: type StUint, ba: openArray[byte]): T {.noinit, inline.}=
|
||||
result = readUintLE[T.bits](ba)
|
||||
func fromBytesLE*(T: type StUint, ba: openArray[byte]): T {.deprecated: "endians2.fromBytesLE".} =
|
||||
endians2.fromBytesLE(T, ba)
|
||||
|
||||
template initFromBytesLE*(x: var StUint, ba: openArray[byte]) =
|
||||
x = fromBytesLE(type x, ba)
|
||||
@ -447,7 +454,7 @@ func readIntLE*[bits: static[int]](ba: openArray[byte]): StInt[bits] {.noinit, i
|
||||
## - A signed integer of the same size with `bits` bits
|
||||
result.impl = (typeof result.impl).fromBytesLE(ba)
|
||||
|
||||
func toByteArrayLE*[bits: static[int]](n: StInt[bits]): array[bits div 8, byte] {.noinit, inline.}=
|
||||
template toByteArrayLE*[bits: static[int]](n: StInt[bits]): array[bits div 8, byte] {.deprecated: "endians2.toBytesLE".} =
|
||||
## Convert a Int[bits] to to a little-endian array of bits div 8 bytes
|
||||
## Input:
|
||||
## - an signed integer
|
||||
@ -455,17 +462,17 @@ func toByteArrayLE*[bits: static[int]](n: StInt[bits]): array[bits div 8, byte]
|
||||
## - a little-endian array of the same size
|
||||
result = n.impl.toBytesLE()
|
||||
|
||||
func fromBytesLE*(T: type StInt, ba: openArray[byte]): T {.noinit, inline.}=
|
||||
result = readIntLE[T.bits](ba)
|
||||
template fromBytesLE*(T: type StInt, ba: openArray[byte]): T {.deprecated: "endians2.fromBytesLE".} =
|
||||
endians2.fromBytesLE(T, ba)
|
||||
|
||||
template initFromBytesLE*(x: var StInt, ba: openArray[byte]) =
|
||||
x = fromBytesLE(type x, ba)
|
||||
|
||||
template toBytesLE*[bits: static[int]](n: StInt[bits]): array[bits div 8, byte] =
|
||||
result = n.impl.toBytesLE()
|
||||
template toBytesLE*[bits: static[int]](n: StInt[bits]): array[bits div 8, byte] {.deprecated: "endians2.toBytesLE".} =
|
||||
endians2.toBytesLE(n)
|
||||
|
||||
template toBytesBE*[bits: static[int]](n: StInt[bits]): array[bits div 8, byte] =
|
||||
result = n.impl.toBytesBE()
|
||||
template toBytesBE*[bits: static[int]](n: StInt[bits]): array[bits div 8, byte] {.deprecated: "endians2.toBytesBE".} =
|
||||
endians2.toBytesBE(n)
|
||||
|
||||
{.pop.}
|
||||
|
||||
|
@ -135,7 +135,7 @@ proc replaceNodes(ast: NimNode, what: NimNode, by: NimNode): NimNode =
|
||||
return rTree
|
||||
result = inspect(ast)
|
||||
|
||||
macro staticFor*(idx: untyped{nkIdent}, start, stopEx: static int, body: untyped): untyped =
|
||||
macro staticFor*(idx: untyped{nkIdent}, start, stopEx: static int, body: untyped): untyped {.deprecated: "stew/staticfor".} =
|
||||
## staticFor [min inclusive, max exclusive)
|
||||
result = newStmtList()
|
||||
for i in start ..< stopEx:
|
||||
|
@ -8,6 +8,7 @@
|
||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
import
|
||||
stew/staticfor,
|
||||
./datatypes,
|
||||
./primitives/extended_precision
|
||||
|
||||
@ -28,10 +29,10 @@ func prod*[rLen, aLen, bLen: static int](r: var Limbs[rLen], a: Limbs[aLen], b:
|
||||
var t, u, v = Word(0)
|
||||
var z: typeof(r) # zero-init, ensure on stack and removes in-place problems
|
||||
|
||||
staticFor i, 0, min(a.len+b.len, r.len):
|
||||
staticFor i, 0..<min(a.len+b.len, r.len):
|
||||
const ib = min(b.len-1, i)
|
||||
const ia = i - ib
|
||||
staticFor j, 0, min(a.len - ia, ib+1):
|
||||
staticFor j, 0..<min(a.len - ia, ib+1):
|
||||
mulAcc(t, u, v, a[ia+j], b[ib-j])
|
||||
|
||||
z[i] = v
|
||||
@ -70,10 +71,10 @@ func prod_high_words*[rLen, aLen, bLen: static int](
|
||||
# but not the ones before (we accumulate in 3 words (t, u, v))
|
||||
const w = lowestWordIndex - 2
|
||||
|
||||
staticFor i, max(0, w), min(a.len+b.len, r.len+lowestWordIndex):
|
||||
staticFor i, max(0, w)..<min(a.len+b.len, r.len+lowestWordIndex):
|
||||
const ib = min(b.len-1, i)
|
||||
const ia = i - ib
|
||||
staticFor j, 0, min(a.len - ia, ib+1):
|
||||
staticFor j, 0..<min(a.len - ia, ib+1):
|
||||
mulAcc(t, u, v, a[ia+j], b[ib-j])
|
||||
|
||||
when i >= lowestWordIndex:
|
||||
|
@ -10,11 +10,11 @@ import
|
||||
# Standard library
|
||||
std/[unittest2, times, strutils],
|
||||
# Third-party
|
||||
gmp, stew/byteutils,
|
||||
gmp, stew/[byteutils, staticfor],
|
||||
# Internal
|
||||
../stint,
|
||||
# Test utilities
|
||||
../helpers/[prng_unsafe, staticfor]
|
||||
../helpers/[prng_unsafe]
|
||||
|
||||
const
|
||||
Iters = 1000
|
||||
@ -191,7 +191,7 @@ template testMultiplication(bits: static int) =
|
||||
test_mul(bits, Iters, Long01Sequence)
|
||||
|
||||
suite "Randomized arithmetic tests vs GMP":
|
||||
staticFor i, 0, Bitwidths.len:
|
||||
staticFor i, 0..<Bitwidths.len:
|
||||
testAddition(Bitwidths[i])
|
||||
testSubstraction(Bitwidths[i])
|
||||
testMultiplication(Bitwidths[i])
|
@ -96,15 +96,15 @@ template chkRoundtripBE(str: string, bits: int) =
|
||||
const data = toByteArray(str)
|
||||
var x: StUint[bits]
|
||||
initFromBytesBE(x, data)
|
||||
let y = toByteArrayBE(x)
|
||||
let y = toBytesBE(x)
|
||||
check y == data
|
||||
|
||||
template chkCTvsRT(num: untyped, bits: int) =
|
||||
block:
|
||||
let x = stuint(num, bits)
|
||||
let y = toByteArrayBE(x)
|
||||
let y = toBytesBE(x)
|
||||
const xx = stuint(num, bits)
|
||||
const yy = toByteArrayBE(xx)
|
||||
const yy = toBytesBE(xx)
|
||||
check y == yy
|
||||
|
||||
template chkDumpHexStuint(BE, LE: string, bits: int) =
|
||||
@ -707,7 +707,7 @@ suite "Testing input and output procedures":
|
||||
chkRoundTripStint("-170141183460469231731687303715884105727", 128, 10)
|
||||
chkRoundTripStint("-170141183460469231731687303715884105728", 128, 10)
|
||||
|
||||
test "roundtrip initFromBytesBE and toByteArrayBE":
|
||||
test "roundtrip initFromBytesBE and toBytesBE":
|
||||
chkRoundtripBE("xyzwabcd", 64)
|
||||
chkRoundtripBE("xyzwabcd12345678", 128)
|
||||
chkRoundtripBE("xyzwabcd12345678kilimanjarohello", 256)
|
||||
@ -724,7 +724,7 @@ suite "Testing input and output procedures":
|
||||
|
||||
chkDumpHexStint("abcdef0012345678abcdef1122334455", "5544332211efcdab7856341200efcdab", 128)
|
||||
|
||||
test "toByteArrayBE CT vs RT":
|
||||
test "toBytesBE CT vs RT":
|
||||
chkCTvsRT(0xab'u64, 64)
|
||||
chkCTvsRT(0xabcd'u64, 64)
|
||||
chkCTvsRT(0xabcdef12'u64, 64)
|
||||
@ -974,14 +974,14 @@ suite "Testing conversion functions: Hex, Bytes, Endianness using secp256k1 curv
|
||||
for i in 2 .. 25:
|
||||
f = f * i.stuint(256)
|
||||
let
|
||||
bytes = f.toByteArrayBE
|
||||
bytes = f.toBytesBE
|
||||
nonZeroBytes = significantBytesBE(bytes)
|
||||
fRestored = UInt256.fromBytesBE(bytes.toOpenArray(bytes.len - nonZeroBytes,
|
||||
bytes.len - 1))
|
||||
check f == fRestored
|
||||
|
||||
test "uint256 -> big-endian array -> hex":
|
||||
check: SECPK1_N.toByteArrayBE == SECPK1_N_BYTES
|
||||
check: SECPK1_N.toBytesBE == SECPK1_N_BYTES
|
||||
|
||||
# This is a sample of signatures generated with a known-good implementation of the ECDSA
|
||||
# algorithm, which we use to test our ECC backends. If necessary, it can be generated from scratch
|
||||
|
@ -7,7 +7,7 @@
|
||||
#
|
||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
import ../stint, unittest2, stew/byteutils
|
||||
import ../stint, ../stint/endians2, unittest2, stew/byteutils, std/algorithm
|
||||
|
||||
template chkToBytesLE(bits: int, hex: string) =
|
||||
let x = fromHex(StUint[bits], hex)
|
||||
@ -19,13 +19,21 @@ template chkToBytesBE(bits: int, hex: string) =
|
||||
|
||||
template chkFromBytesBE(bits: int, hex: string) =
|
||||
let x = fromHex(StUint[bits], hex)
|
||||
let z = fromBytesBE(StUint[bits], toByteArrayBE(x))
|
||||
check z == x
|
||||
|
||||
check:
|
||||
x.toBytesBE().toHex() == hex
|
||||
|
||||
let z = fromBytesBE(StUint[bits], toBytesBE(x))
|
||||
check toHex(z) == hex
|
||||
|
||||
template chkFromBytesLE(bits: int, hex: string) =
|
||||
let x = fromHex(StUint[bits], hex)
|
||||
let z = fromBytesLE(StUint[bits], toByteArrayLE(x))
|
||||
check z == x
|
||||
|
||||
check:
|
||||
x.toBytesLE().toHex() == reversed(hexToSeqByte(hex)).toHex()
|
||||
|
||||
let z = fromBytesLE(StUint[bits], toBytesLE(x))
|
||||
check toHex(z) == hex
|
||||
|
||||
template chkEndians(name: untyped) =
|
||||
test astToStr(name).substr(3):
|
||||
@ -33,6 +41,14 @@ template chkEndians(name: untyped) =
|
||||
name(128, "abcdef1234567890abcdef1234567890")
|
||||
name(256, "abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890")
|
||||
|
||||
name(72, "a0a1a2a3a4a5a6a7a8")
|
||||
name(80, "a0a1a2a3a4a5a6a7a8a9")
|
||||
name(88, "a0a1a2a3a4a5a6a7a8a9aa")
|
||||
name(96, "a0a1a2a3a4a5a6a7a8a9aaab")
|
||||
name(104, "a0a1a2a3a4a5a6a7a8a9aaabac")
|
||||
|
||||
name(264, "abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890aa")
|
||||
|
||||
suite "Testing endians":
|
||||
chkEndians(chkToBytesLE)
|
||||
chkEndians(chkToBytesBE)
|
||||
@ -40,12 +56,11 @@ suite "Testing endians":
|
||||
chkEndians(chkFromBytesBE)
|
||||
|
||||
test "Endians give sane results":
|
||||
|
||||
check:
|
||||
1.u128.toByteArrayBE() ==
|
||||
1.u128.toBytesBE() ==
|
||||
[0'u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
|
||||
|
||||
1.u128.toByteArrayLE() ==
|
||||
1.u128.toBytesLE() ==
|
||||
[1'u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
|
||||
1.u128 == UInt128.fromBytesBE(
|
||||
@ -53,3 +68,22 @@ suite "Testing endians":
|
||||
|
||||
1.u128 == UInt128.fromBytesLE(
|
||||
[1'u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
|
||||
|
||||
1.u128 == UInt128.fromBytesBE(
|
||||
[0'u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 42])
|
||||
|
||||
1.u128 == UInt128.fromBytesLE(
|
||||
[1'u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42])
|
||||
|
||||
1.stuint(120) == StUint[120].fromBytesLE(
|
||||
[1'u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42])
|
||||
|
||||
|
||||
# TOOD the tests below are not valid according to docs by encode de-facto
|
||||
# usage in the wild
|
||||
|
||||
1.stuint(128) == StUint[128].fromBytesLE([1'u8, 0, 0, 0, 0, 0, 0, 0])
|
||||
1.stuint(128) == StUint[128].fromBytesBE([0'u8, 0, 0, 0, 0, 0, 0, 0, 1])
|
||||
|
||||
1.stuint(128) == StUint[128].fromBytesLE([1'u8])
|
||||
1.stuint(128) == StUint[128].fromBytesBE([1'u8])
|
||||
|
Loading…
x
Reference in New Issue
Block a user