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:
Jacek Sieka 2024-12-03 13:16:02 +01:00 committed by GitHub
parent 5c5e01cef0
commit b06bb210d9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 329 additions and 295 deletions

View File

@ -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)
)

View File

@ -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)

View File

@ -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.}

View File

@ -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:

View File

@ -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:

View File

@ -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])

View File

@ -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

View File

@ -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])