rebase and try to make it works with clients

This commit is contained in:
jangko 2023-06-09 15:46:21 +07:00
parent bff69b3f98
commit 63a32129c8
No known key found for this signature in database
GPG Key ID: 31702AE10541E6B9
11 changed files with 116 additions and 231 deletions

View File

@ -1,5 +1,5 @@
# Stint
# Copyright 2018 Status Research & Development GmbH
# Copyright 2018-2023 Status Research & Development GmbH
# Licensed under either of
#
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
@ -10,12 +10,12 @@
# import stint/[bitops2, endians2, intops, io, modular_arithmetic, literals_stint]
# export bitops2, endians2, intops, io, modular_arithmetic, literals_stint
import stint/[io, uintops]
export io, uintops
import stint/[io, uintops, intops, literals_stint, modular_arithmetic]
export io, uintops, intops, literals_stint, modular_arithmetic
type
# Int128* = Stint[128]
# Int256* = Stint[256]
Int128* = Stint[128]
Int256* = Stint[256]
UInt128* = StUint[128]
UInt256* = StUint[256]
@ -25,8 +25,8 @@ func u128*(s: string): UInt128 {.inline.} = s.parse(UInt128)
func u256*(n: SomeInteger): UInt256 {.inline.} = n.stuint(256)
func u256*(s: string): UInt256 {.inline.} = s.parse(UInt256)
# func i128*(n: SomeInteger): Int128 {.inline.} = n.stint(128)
func i128*(n: SomeInteger): Int128 {.inline.} = n.stint(128)
# func i128*(s: string): Int128 {.inline.} = s.parse(Int128)
# func i256*(n: SomeInteger): Int256 {.inline.} = n.stint(256)
func i256*(n: SomeInteger): Int256 {.inline.} = n.stint(256)
# func i256*(s: string): Int256 {.inline.} = s.parse(Int256)

View File

@ -7,10 +7,10 @@ skipDirs = @["tests", "benchmarks"]
### Dependencies
# TODO test only requirements don't work: https://github.com/nim-lang/nimble/issues/482
requires "nim >= 1.6.0",
requires "nim >= 1.6.12",
"stew"
proc test(args, path: string) =
proc test(name: string, lang: string = "c") =
if not dirExists "build":
mkDir "build"

View File

@ -9,7 +9,7 @@
import private/datatypes
{.push raises: [IndexError], noInit, gcsafe.}
{.push raises: [IndexDefect], noInit, gcsafe.}
# Serialization
# ------------------------------------------------------------------------------------------
@ -138,7 +138,7 @@ func toBytes*[bits: static int](x: StUint[bits], endian: Endianness = bigEndian)
func fromBytesBE*[bits: static int](
T: typedesc[StUint[bits]],
x: openArray[byte]): T =
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)

View File

@ -7,19 +7,18 @@
#
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import ./private/[bitops2_priv, datatypes]
import ./private/[datatypes]
export StInt, StUint
export IntImpl, intImpl, UintImpl, uintImpl, bitsof # TODO: remove the need to export those
export StInt
#export IntImpl, intImpl, UintImpl, uintImpl, bitsof # TODO: remove the need to export those
type SomeBigInteger = StUint|StInt
#import ./private/initialization
import ./private/initialization
func zero*[bits: static[int]](T: typedesc[StUint[bits] or StInt[bits]]): T {.inline.} =
func zero*[bits: static[int]](T: typedesc[StInt[bits]]): T {.inline.} =
## Returns the zero of the input type
discard
#[
func one*[bits: static[int]](T: typedesc[StUint[bits]]): T {.inline.} =
## Returns the one of the input type
result.data = one(type result.data)
@ -159,3 +158,4 @@ func pow*(x: StUint, y: StUint): StUint {.inline.} =
result.data = x.data.pow(y.data)
else:
result.data = x.data ^ y.data
]#

View File

@ -1,5 +1,5 @@
# Stint
# Copyright 2018 Status Research & Development GmbH
# Copyright 2018-2023 Status Research & Development GmbH
# Licensed under either of
#
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
@ -21,6 +21,18 @@ import
from stew/byteutils import toHex # Why are we exporting readHexChar in byteutils?
template leastSignificantWord*(a: SomeBigInteger): Word =
a.limbs[0]
template mostSignificantWord*(a: SomeBigInteger): Word =
a.limbs[^1]
template signedWordType*(_: type SomeBigInteger): type =
SignedWord
template wordType*(_: type SomeBigInteger): type =
Word
template static_check_size(T: typedesc[SomeInteger], bits: static[int]) =
# To avoid a costly runtime check, we refuse storing into StUint types smaller
# than the input type.
@ -62,14 +74,14 @@ func stuint*[T: SomeInteger](n: T, bits: static[int]): StUint[bits] {.inline.}=
func to*(a: SomeUnsignedInt, T: typedesc[StUint]): T =
stuint(a, result.bits)
func truncate*(num: StInt or StUint, T: typedesc[SomeInteger]): T {.inline.}=
func truncate*(num: Stint or StUint, T: typedesc[SomeInteger]): T {.inline.}=
## Extract the int, uint, int8-int64 or uint8-uint64 portion of a multi-precision integer.
## Note that int and uint are 32-bit on 32-bit platform.
## For unsigned result type, result is modulo 2^(sizeof T in bit)
## For signed result type, result is undefined if input does not fit in the target type.
result = T(num.leastSignificantWord())
func toInt*(num: StInt or StUint): int {.inline, deprecated:"Use num.truncate(int) instead".}=
func toInt*(num: Stint or StUint): int {.inline, deprecated:"Use num.truncate(int) instead".}=
num.truncate(int)
func stuint*(a: StUint, bits: static[int]): StUint[bits] {.inline.} =
@ -79,7 +91,7 @@ func stuint*(a: StUint, bits: static[int]): StUint[bits] {.inline.} =
for i in 0 ..< result.len:
result[i] = a[i]
# func stuint*(a: StInt, bits: static[int]): StUint[bits] {.inline.} =
# func StUint*(a: StInt, bits: static[int]): StUint[bits] {.inline.} =
# ## signed int to unsigned int conversion
# ## current behavior is cast-like, copying bit pattern
# ## or truncating if input does not fit into destination
@ -87,12 +99,12 @@ func stuint*(a: StUint, bits: static[int]): StUint[bits] {.inline.} =
# when N < bits:
# when N <= 64:
# type T = StUint[N]
# result = stuint(convert[T](a).data, bits)
# result = StUint(convert[T](a).data, bits)
# else:
# smallToBig(result.data, a.data)
# elif N > bits:
# when bits <= 64:
# result = stuint(x.truncate(type(result.data)), bits)
# result = StUint(x.truncate(type(result.data)), bits)
# else:
# bigToSmall(result.data, a.data)
# else:
@ -143,7 +155,7 @@ func stuint*(a: StUint, bits: static[int]): StUint[bits] {.inline.} =
# func stint*(a: StUint, bits: static[int]): StInt[bits] {.inline.} =
# const N = bitsof(a.data)
# const dmax = stuint((type result).high, N)
# const dmax = StUint((type result).high, N)
# if a > dmax: raise newException(RangeError, "value out of range")
# when N < bits:
# when N <= 64:
@ -170,8 +182,6 @@ func readHexChar(c: char): int8 {.inline.}=
func skipPrefixes(current_idx: var int, str: string, radix: range[2..16]) {.inline.} =
## Returns the index of the first meaningful char in `hexStr` by skipping
## "0x" prefix
# Always called from a context where radix is known at compile-time
# and checked within 2..16 and so cannot throw a RangeDefect at runtime
if str.len < 2:
return
@ -179,20 +189,14 @@ func skipPrefixes(current_idx: var int, str: string, radix: range[2..16]) {.inli
doAssert current_idx == 0, "skipPrefixes only works for prefixes (position 0 and 1 of the string)"
if str[0] == '0':
if str[1] in {'x', 'X'}:
if radix == 16:
doAssert radix == 16, "Parsing mismatch, 0x prefix is only valid for a hexadecimal number (base 16)"
current_idx = 2
else:
raise newException(ValueError,"Parsing mismatch, 0x prefix is only valid for a hexadecimal number (base 16)")
elif str[1] in {'o', 'O'}:
if radix == 8:
doAssert radix == 8, "Parsing mismatch, 0o prefix is only valid for an octal number (base 8)"
current_idx = 2
else:
raise newException(ValueError, "Parsing mismatch, 0o prefix is only valid for an octal number (base 8)")
elif str[1] in {'b', 'B'}:
if radix == 2:
doAssert radix == 2, "Parsing mismatch, 0b prefix is only valid for a binary number (base 2)"
current_idx = 2
elif radix != 16:
raise newException(ValueError, "Parsing mismatch, 0b prefix is only valid for a binary number (base 2) or as first byte of a hexadecimal number (base 16)")
func nextNonBlank(current_idx: var int, s: string) {.inline.} =
## Move the current index, skipping white spaces and "_" characters.
@ -203,15 +207,13 @@ func nextNonBlank(current_idx: var int, s: string) {.inline.} =
while current_idx < s.len and s[current_idx] in blanks:
inc current_idx
func readDecChar(c: char): int {.inline.}=
func readDecChar(c: range['0'..'9']): int {.inline.}=
## Converts a decimal char to an int
# specialization without branching for base <= 10.
if c notin {'0'..'9'}:
raise newException(ValueError, "Character out of '0'..'9' range")
ord(c) - ord('0')
func parse*[bits: static[int]](input: string, T: typedesc[StUint[bits]], radix: static[uint8] = 10): T =
## Parse a string and store the result in a StInt[bits] or StUint[bits].
## Parse a string and store the result in a Stint[bits] or StUint[bits].
static: doAssert (radix >= 2) and radix <= 16, "Only base from 2..16 are supported"
# TODO: use static[range[2 .. 16]], not supported at the moment (2018-04-26)
@ -232,7 +234,7 @@ func parse*[bits: static[int]](input: string, T: typedesc[StUint[bits]], radix:
nextNonBlank(curr, input)
# func parse*[bits: static[int]](input: string, T: typedesc[Stint[bits]], radix: static[int8] = 10): T =
# ## Parse a string and store the result in a Stint[bits] or Stuint[bits].
# ## Parse a string and store the result in a Stint[bits] or StUint[bits].
# static: doAssert (radix >= 2) and radix <= 16, "Only base from 2..16 are supported"
# # TODO: use static[range[2 .. 16]], not supported at the moment (2018-04-26)
@ -241,12 +243,12 @@ func parse*[bits: static[int]](input: string, T: typedesc[StUint[bits]], radix:
# # and be much faster
# # For conversion we require overflowing operations (for example for negative hex numbers)
# const base = radix.int8.stuint(bits)
# const base = radix.int8.StUint(bits)
# var
# curr = 0 # Current index in the string
# isNeg = false
# no_overflow: Stuint[bits]
# no_overflow: StUint[bits]
# if input[curr] == '-':
# doAssert radix == 10, "Negative numbers are only supported with base 10 input."
@ -258,9 +260,9 @@ func parse*[bits: static[int]](input: string, T: typedesc[StUint[bits]], radix:
# while curr < input.len:
# # TODO: overflow detection
# when radix <= 10:
# no_overflow = no_overflow * base + input[curr].readDecChar.stuint(bits)
# no_overflow = no_overflow * base + input[curr].readDecChar.StUint(bits)
# else:
# no_overflow = no_overflow * base + input[curr].readHexChar.stuint(bits)
# no_overflow = no_overflow * base + input[curr].readHexChar.StUint(bits)
# nextNonBlank(curr, input)
# # TODO: we can't create the lowest int this way
@ -269,7 +271,7 @@ func parse*[bits: static[int]](input: string, T: typedesc[StUint[bits]], radix:
# else:
# result = convert[T](no_overflow)
func fromHex*(T: typedesc[StUint|StInt], s: string): T {.inline.} =
func fromHex*(T: typedesc[StUint|Stint], s: string): T {.inline.} =
## Convert an hex string to the corresponding unsigned integer
parse(s, type result, radix = 16)
@ -277,34 +279,34 @@ func hexToUint*[bits: static[int]](hexString: string): StUint[bits] {.inline.} =
## Convert an hex string to the corresponding unsigned integer
parse(hexString, type result, radix = 16)
# func toString*[bits: static[int]](num: StUint[bits], radix: static[uint8] = 10): string =
# ## Convert a Stint or Stuint to string.
# ## In case of negative numbers:
# ## - they are prefixed with "-" for base 10.
# ## - if not base 10, they are returned raw in two-complement form.
func toString*[bits: static[int]](num: StUint[bits], radix: static[uint8] = 10): string =
## Convert a Stint or StUint to string.
## In case of negative numbers:
## - they are prefixed with "-" for base 10.
## - if not base 10, they are returned raw in two-complement form.
# static: doAssert (radix >= 2) and radix <= 16, "Only base from 2..16 are supported"
# # TODO: use static[range[2 .. 16]], not supported at the moment (2018-04-26)
static: doAssert (radix >= 2) and radix <= 16, "Only base from 2..16 are supported"
# TODO: use static[range[2 .. 16]], not supported at the moment (2018-04-26)
# const hexChars = "0123456789abcdef"
# const base = radix.uint8.stuint(bits)
const hexChars = "0123456789abcdef"
const base = radix.uint8.stuint(bits)
# result = ""
# var (q, r) = divmod(num, base)
result = ""
var (q, r) = divmod(num, base)
# while true:
# when bitsof(r.data) <= 64:
# result.add hexChars[r.data.int]
# else:
# result.add hexChars[r.truncate(int)]
# if q.isZero:
# break
# (q, r) = divmod(q, base)
while true:
when bits <= 64:
result.add hexChars[r.leastSignificantWord()]
else:
result.add hexChars[r.truncate(int)]
if q.isZero:
break
(q, r) = divmod(q, base)
# reverse(result)
reverse(result)
# func toString*[bits: static[int]](num: Stint[bits], radix: static[int8] = 10): string =
# ## Convert a Stint or Stuint to string.
# ## Convert a Stint or StUint to string.
# ## In case of negative numbers:
# ## - they are prefixed with "-" for base 10.
# ## - if not base 10, they are returned raw in two-complement form.
@ -313,11 +315,11 @@ func hexToUint*[bits: static[int]](hexString: string): StUint[bits] {.inline.} =
# # TODO: use static[range[2 .. 16]], not supported at the moment (2018-04-26)
# const hexChars = "0123456789abcdef"
# const base = radix.int8.stuint(bits)
# const base = radix.int8.StUint(bits)
# result = ""
# type T = Stuint[bits]
# type T = StUint[bits]
# let isNeg = num.isNegative
# let num = convert[T](if radix == 10 and isNeg: -num
# else: num)
@ -344,11 +346,11 @@ func hexToUint*[bits: static[int]](hexString: string): StUint[bits] {.inline.} =
# else:
# toString(num, 10)
# func toHex*[bits: static[int]](num: Stint[bits] or StUint[bits]): string {.inline.}=
# ## Convert to a hex string.
# ## Output is considered a big-endian base 16 string.
# ## Leading zeros are stripped. Use dumpHex instead if you need the in-memory representation
# toString(num, 16)
func toHex*[bits: static[int]](num: Stint[bits] or StUint[bits]): string {.inline.}=
## Convert to a hex string.
## Output is considered a big-endian base 16 string.
## Leading zeros are stripped. Use dumpHex instead if you need the in-memory representation
toString(num, 16)
func dumpHex*(a: Stint or StUint, order: static[Endianness] = bigEndian): string =
## Stringify an int to hex.
@ -365,7 +367,9 @@ func dumpHex*(a: Stint or StUint, order: static[Endianness] = bigEndian): string
let bytes = a.toBytes(order)
result = bytes.toHex()
func readUintBE*[bits: static[int]](ba: openarray[byte]): Stuint[bits] {.noInit, inline.}=
export fromBytes, toBytes
func readUintBE*[bits: static[int]](ba: openArray[byte]): StUint[bits] {.noInit, inline.}=
## Convert a big-endian array of (bits div 8) Bytes to an UInt[bits] (in native host endianness)
## Input:
## - a big-endian openArray of size (bits div 8) at least
@ -386,3 +390,11 @@ template hash*(num: StUint|StInt): Hash =
# `hashData` is not particularly efficient.
# Explore better hashing solutions in nim-stew.
hashData(unsafeAddr num, sizeof num)
func fromBytesBE*(T: type StUint, ba: openArray[byte], allowPadding: static[bool] = true): T {.noInit, inline.}=
result = readUintBE[T.bits](ba)
when allowPadding:
result = result shl ((sizeof(T) - ba.len) * 8)
template initFromBytesBE*(x: var StUint, ba: openArray[byte], allowPadding: static[bool] = true) =
x = fromBytesBE(type x, ba, allowPadding)

View File

@ -9,7 +9,7 @@
## This file provides syntactic sugar to work with literals
import ./intops, macros
import ./intops, ./uintops, macros
type Signedness = enum
BothSigned, IntOnly, UintOnly

View File

@ -7,7 +7,7 @@
#
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import ./intops, private/datatypes
import ./uintops, private/datatypes
func addmod_internal(a, b, m: StUint): StUint {.inline.}=
## Modular addition

View File

@ -12,9 +12,13 @@ import
stew/bitops2
when sizeof(int) == 8 and not defined(Stint32):
type Word* = uint64
type
Word* = uint64
SignedWord* = int64
else:
type Word* = uint32
type
Word* = uint32
SignedWord* = int32
const WordBitWidth* = sizeof(Word) * 8
@ -33,10 +37,19 @@ type
limbs*: array[bits.wordsRequired, Word]
# Limbs-Endianess is little-endian
when (NimMajor, NimMinor) < (1,9):
type
StInt*[bits: static[int]] = object
## Stack-based integer
## Signed
limbs*: array[bits.wordsRequired, Word]
else:
type
StInt*[bits: static[int]] {.borrow: `.`.} = distinct StUint[bits]
## Stack-based integer
## Signed
type
Carry* = uint8 # distinct range[0'u8 .. 1]
Borrow* = uint8 # distinct range[0'u8 .. 1]

View File

@ -20,7 +20,7 @@ import
func bitnot*(r: var StUint, a: Stuint) =
## Bitwise complement of unsigned integer a
## i.e. flips all bits of the input
for i in 0 ..< r.len:
for i in 0 ..< r.limbs.len:
r[i] = not a[i]
r.clearExtraBitsOverMSB()
@ -56,7 +56,7 @@ func leadingZeros*(a: Stuint): int =
# Adjust when we use only part of the word size
var extraBits = WordBitWidth * a.limbs.len - a.bits
for i in countdown(a.len-1, 0):
for i in countdown(a.limbs.len-1, 0):
let zeroCount = a.limbs[i].leadingZeros()
if extraBits > 0:
result += zeroCount - min(extraBits, WordBitWidth)

View File

@ -54,146 +54,6 @@ func shlAddMod_multi(a: var openArray[Word], c: Word,
## Does a <- a * W + c (mod M)
## and returns q = (a * W + c ) / M
##
<<<<<<< HEAD
## For now only LittleEndian is implemented
#
# Resources at the bottom of the file
const
qLen = q.limbs.len
rLen = r.limbs.len
uLen = u.limbs.len
vLen = v.limbs.len
template `[]`(a: Stuint, i: int): Word = a.limbs[i]
template `[]=`(a: Stuint, i: int, val: Word) = a.limbs[i] = val
# Find the most significant word with actual set bits
# and get the leading zero count there
var divisorLen = vLen
var clz: int
for w in mostToLeastSig(v):
if w != 0:
clz = leadingZeros(w)
break
else:
divisorLen -= 1
doAssert divisorLen != 0, "Division by zero. Abandon ship!"
# Divisor is a single word.
if divisorLen == 1:
q.copyFrom(u)
r.leastSignificantWord() = q.limbs.shortDiv(v.leastSignificantWord())
# zero all but the least significant word
var lsw = true
for w in leastToMostSig(r):
if lsw:
lsw = false
else:
w = 0
return
var un {.noInit.}: Limbs[uLen+1]
var vn {.noInit.}: Limbs[vLen] # [mswLen .. vLen] range is unused
# Normalize so that the divisor MSB is set,
# vn cannot overflow, un can overflowed by 1 word at most, hence uLen+1
un.shlSmallOverflowing(u.limbs, clz)
vn.shlSmall(v.limbs, clz)
static: doAssert cpuEndian == littleEndian, "Currently the division algorithm requires little endian ordering of the limbs"
# TODO: is it worth it to have the uint be the exact same extended precision representation
# as a wide int (say uint128 or uint256)?
# in big-endian, the following loop must go the other way and the -1 must be +1
let vhi = vn[divisorLen-1]
let vlo = vn[divisorLen-2]
for j in countdown(uLen - divisorLen, 0, 1):
# Compute qhat estimate of q[j] (off by 0, 1 and rarely 2)
var qhat, rhat: Word
let uhi = un[j+divisorLen]
let ulo = un[j+divisorLen-1]
div2n1n(qhat, rhat, uhi, ulo, vhi)
var mhi, mlo: Word
var rhi, rlo: Word
mul(mhi, mlo, qhat, vlo)
rhi = rhat
rlo = ulo
# if r < m, adjust approximation, up to twice
while rhi < mhi or (rhi == mhi and rlo < mlo):
qhat -= 1
rhi += vhi
# Found the quotient
q[j] = qhat
# un -= qhat * v
var borrow = Borrow(0)
var qvhi, qvlo: Word
for i in 0 ..< divisorLen-1:
mul(qvhi, qvlo, qhat, v[i])
subB(borrow, un[j+i], un[j+i], qvlo, borrow)
subB(borrow, un[j+i+1], un[j+i+1], qvhi, borrow)
# Last step
mul(qvhi, qvlo, qhat, v[divisorLen-1])
subB(borrow, un[j+divisorLen-1], un[j+divisorLen-1], qvlo, borrow)
qvhi += Word(borrow)
let isNeg = un[j+divisorLen] < qvhi
un[j+divisorLen] -= qvhi
if isNeg:
# oops, too big by one, add back
q[j] -= 1
var carry = Carry(0)
for i in 0 ..< divisorLen:
addC(carry, un[j+i], un[j+i], v[i], carry)
# Quotient is found, if remainder is needed we need to un-normalize un
if needRemainder:
# r.limbs.shrSmall(un, clz) - TODO
when cpuEndian == littleEndian:
# rLen+1 == un.len
for i in 0 ..< rLen:
r[i] = (un[i] shr clz) or (un[i+1] shl (WordBitWidth - clz))
else:
{.error: "Not Implemented for bigEndian".}
const BinaryShiftThreshold = 8 # If the difference in bit-length is below 8
# binary shift is probably faster
func divmod(q, r: var Stuint,
x, y: Stuint, needRemainder: bool) =
let x_clz = x.leadingZeros()
let y_clz = y.leadingZeros()
# We short-circuit division depending on special-cases.
if unlikely(y.isZero()):
raise newException(DivByZeroError, "You attempted to divide by zero")
elif y_clz == (y.bits - 1):
# y is one
q = x
# elif (x.hi or y.hi).isZero:
# # If computing just on the low part is enough
# (result.quot.lo, result.rem.lo) = divmod(x.lo, y.lo, needRemainder)
# elif (y and (y - one(type y))).isZero:
# # y is a power of 2. (this also matches 0 but it was eliminated earlier)
# # TODO. Would it be faster to use countTrailingZero (ctz) + clz == size(y) - 1?
# # Especially because we shift by ctz after.
# let y_ctz = bitsof(y) - y_clz - 1
# result.quot = x shr y_ctz
# if needRemainder:
# result.rem = x and (y - one(type y))
elif x == y:
q.setOne()
elif x < y:
r = x
# elif (y_clz - x_clz) < BinaryShiftThreshold:
# binaryShiftDiv(x, y, result.quot, result.rem)
## The modulus `M` most-significant bit at `mBits` MUST be set.
# Assuming 64-bit words

View File

@ -46,7 +46,7 @@ func one*[bits: static[int]](T: typedesc[Stuint[bits]]): T {.inline.} =
result.setOne()
func high*[bits](_: typedesc[Stuint[bits]]): Stuint[bits] {.inline.} =
for i in 0 ..< result.len:
for i in 0 ..< result.limbs.len:
result[i] = high(Word)
func low*[bits](_: typedesc[Stuint[bits]]): Stuint[bits] {.inline.} =