diff --git a/stint.nimble b/stint.nimble index e8d654f..ee1f8de 100644 --- a/stint.nimble +++ b/stint.nimble @@ -7,7 +7,9 @@ skipDirs = @["tests", "benchmarks"] ### Dependencies # TODO test only requirements don't work: https://github.com/nim-lang/nimble/issues/482 -requires "nim >= 0.18" #, "https://github.com/alehander42/nim-quicktest >= 0.18.0", "https://github.com/status-im/nim-ttmath" +requires "nim >= 0.19", + "std_shims" + #, "https://github.com/alehander42/nim-quicktest >= 0.18.0", "https://github.com/status-im/nim-ttmath" proc test(name: string, lang: string = "c") = if not dirExists "build": diff --git a/stint/int_public.nim b/stint/int_public.nim index 53a6a3b..45ca208 100644 --- a/stint/int_public.nim +++ b/stint/int_public.nim @@ -7,7 +7,7 @@ # # at your option. This file may not be copied, modified, or distributed except according to those terms. -import ./private/datatypes, macros +import ./private/datatypes export StInt export IntImpl, intImpl, bitsof # TODO remove the need to export these diff --git a/stint/private/bithacks.nim b/stint/private/bithacks.nim deleted file mode 100644 index 92700be..0000000 --- a/stint/private/bithacks.nim +++ /dev/null @@ -1,26 +0,0 @@ -# Stint -# Copyright 2018 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) -# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) -# -# at your option. This file may not be copied, modified, or distributed except according to those terms. - -import ./datatypes, ./conversion, stdlib_bitops -export stdlib_bitops - -# We reuse bitops from Nim standard lib, and expand it for multi-precision int. -# MpInt rely on no undefined behaviour as often we scan 0. (if 1 is stored in a uint128 for example) -# Also countLeadingZeroBits must return the size of the type and not 0 like in the stdlib - -func countLeadingZeroBits*(n: UintImpl): int {.inline.} = - ## Returns the number of leading zero bits in integer. - - const maxHalfRepr = bitsof(n) div 2 - - let hi_clz = n.hi.countLeadingZeroBits - - result = if hi_clz == maxHalfRepr: - n.lo.countLeadingZeroBits + maxHalfRepr - else: hi_clz diff --git a/stint/private/bitops2.nim b/stint/private/bitops2.nim new file mode 100644 index 0000000..705b9df --- /dev/null +++ b/stint/private/bitops2.nim @@ -0,0 +1,40 @@ +# Stint +# Copyright 2018 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) +# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) +# +# at your option. This file may not be copied, modified, or distributed except according to those terms. + +import ./datatypes, ./conversion, std_shims/support/bitops2 +export bitops2 + +# Bitops from support library + +func countOnes*(x: UintImpl): int {.inline.} = + countOnes(x.lo) + countOnes(x.hi) + +func parity*(x: UintImpl): int {.inline.} = + parity(x.lo) xor parity(x.hi) + +func leadingZeros*(x: UintImpl): int {.inline.} = + let tmp = x.hi.leadingZeros() + if tmp == bitsof(x.hi): + x.lo.leadingZeros() + bitsof(x.hi) + else: + tmp + +func trailingZeros*(x: UintImpl): int {.inline.} = + let tmp = x.lo.trailingZeros() + if tmp == bitsof(x.lo): + tmp + x.hi.trailingZeros() + else: + tmp + +func firstOne*(x: UintImpl): int {.inline.} = + let tmp = trailingZeros(x) + if tmp == bitsof(x): + 0 + else: + 1 + tmp diff --git a/stint/private/conversion.nim b/stint/private/conversion.nim index 198f94f..469e1b1 100644 --- a/stint/private/conversion.nim +++ b/stint/private/conversion.nim @@ -20,7 +20,7 @@ func toUint*(n: UintImpl or IntImpl or SomeSignedInt): auto {.inline.}= ## Casts an unsigned integer to an uint of the same size # TODO: uint128 support when n.sizeof > 8: - raise newException("Unreachable. You are trying to cast a StUint with more than 64-bit of precision") + {.fatal: "Unreachable. You are trying to cast a StUint with more than 64-bit of precision" .} elif n.sizeof == 8: cast[uint64](n) elif n.sizeof == 4: @@ -48,7 +48,7 @@ func toInt*(n: UintImpl or IntImpl or SomeInteger): auto {.inline.}= ## Casts an unsigned integer to an uint of the same size # TODO: uint128 support when n.sizeof > 8: - raise newException("Unreachable. You are trying to cast a StUint with more than 64-bit of precision") + {.fatal: "Unreachable. You are trying to cast a StUint with more than 64-bit of precision" .} elif n.sizeof == 8: cast[int64](n) elif n.sizeof == 4: diff --git a/stint/private/int_comparison.nim b/stint/private/int_comparison.nim index 8f0ee80..0b2e8f1 100644 --- a/stint/private/int_comparison.nim +++ b/stint/private/int_comparison.nim @@ -7,7 +7,7 @@ # # at your option. This file may not be copied, modified, or distributed except according to those terms. -import ./datatypes, ./bithacks, ./uint_comparison +import ./datatypes, ./uint_comparison func isZero*(n: SomeSignedInt): bool {.inline.} = n == 0 diff --git a/stint/private/stdlib_bitops.nim b/stint/private/stdlib_bitops.nim deleted file mode 100644 index e6040e2..0000000 --- a/stint/private/stdlib_bitops.nim +++ /dev/null @@ -1,117 +0,0 @@ -# -# -# Nim's Runtime Library -# (c) Copyright 2017 Nim Authors -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## This module implements a series of low level methods for bit manipulation. -## By default, this module use compiler intrinsics to improve performance -## on supported compilers: ``GCC``, ``LLVM_GCC``, ``CLANG``, ``VCC``, ``ICC``. -## -## The module will fallback to pure nim procs incase the backend is not supported. -## You can also use the flag `noIntrinsicsBitOpts` to disable compiler intrinsics. -## -## This module is also compatible with other backends: ``Javascript``, ``Nimscript`` -## as well as the ``compiletime VM``. -## -## As a result of using optimized function/intrinsics some functions can return -## undefined results if the input is invalid. You can use the flag `noUndefinedBitOpts` -## to force predictable behaviour for all input, causing a small performance hit. -## -## At this time only `fastLog2`, `firstSetBit, `countLeadingZeroBits`, `countTrailingZeroBits` -## may return undefined and/or platform dependant value if given invalid input. - - -# Bitops from the standard lib modified for MpInt use. -# - No undefined behaviour or flag needed -# - Note that for CountLeadingZero, it returns sizeof(input) * 8 -# instead of 0 - - -const useBuiltins* = not defined(noIntrinsicsBitOpts) -# const noUndefined* = defined(noUndefinedBitOpts) -const useGCC_builtins* = (defined(gcc) or defined(llvm_gcc) or defined(clang)) and useBuiltins -const useICC_builtins* = defined(icc) and useBuiltins -const useVCC_builtins* = defined(vcc) and useBuiltins -const arch64* = sizeof(int) == 8 - - -func fastlog2_nim(x: uint32): int {.inline.} = - ## Quickly find the log base 2 of a 32-bit or less integer. - # https://graphics.stanford.edu/%7Eseander/bithacks.html#IntegerLogDeBruijn - # https://stackoverflow.com/questions/11376288/fast-computing-of-log2-for-64-bit-integers - const lookup: array[32, uint8] = [0'u8, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, - 22, 25, 3, 30, 8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31] - var v = x.uint32 - v = v or v shr 1 # first round down to one less than a power of 2 - v = v or v shr 2 - v = v or v shr 4 - v = v or v shr 8 - v = v or v shr 16 - result = lookup[uint32(v * 0x07C4ACDD'u32) shr 27].int - -func fastlog2_nim(x: uint64): int {.inline.} = - ## Quickly find the log base 2 of a 64-bit integer. - # https://graphics.stanford.edu/%7Eseander/bithacks.html#IntegerLogDeBruijn - # https://stackoverflow.com/questions/11376288/fast-computing-of-log2-for-64-bit-integers - const lookup: array[64, uint8] = [0'u8, 58, 1, 59, 47, 53, 2, 60, 39, 48, 27, 54, - 33, 42, 3, 61, 51, 37, 40, 49, 18, 28, 20, 55, 30, 34, 11, 43, 14, 22, 4, 62, - 57, 46, 52, 38, 26, 32, 41, 50, 36, 17, 19, 29, 10, 13, 21, 56, 45, 25, 31, - 35, 16, 9, 12, 44, 24, 15, 8, 23, 7, 6, 5, 63] - var v = x.uint64 - v = v or v shr 1 # first round down to one less than a power of 2 - v = v or v shr 2 - v = v or v shr 4 - v = v or v shr 8 - v = v or v shr 16 - v = v or v shr 32 - result = lookup[(v * 0x03F6EAF2CD271461'u64) shr 58].int - - -when useGCC_builtins: - # Returns the number of leading 0-bits in x, starting at the most significant bit position. If x is 0, the result is undefined. - proc builtin_clz*(x: cuint): cint {.importc: "__builtin_clz", cdecl.} - proc builtin_clzll*(x: culonglong): cint {.importc: "__builtin_clzll", cdecl.} - -elif useVCC_builtins: - # Search the mask data from most significant bit (MSB) to least significant bit (LSB) for a set bit (1). - proc bitScanReverse*(index: ptr culong, mask: culong): cuchar {.importc: "_BitScanReverse", header: "", nosideeffect.} - proc bitScanReverse64*(index: ptr culong, mask: uint64): cuchar {.importc: "_BitScanReverse64", header: "", nosideeffect.} - - template vcc_scan_impl*(fnc: untyped; v: untyped): int = - var index: culong - discard fnc(index.addr, v) - index.int - -elif useICC_builtins: - # Returns the number of leading 0-bits in x, starting at the most significant bit position. If x is 0, the result is undefined. - proc bitScanReverse*(p: ptr uint32, b: uint32): cuchar {.importc: "_BitScanReverse", header: "", nosideeffect.} - proc bitScanReverse64*(p: ptr uint32, b: uint64): cuchar {.importc: "_BitScanReverse64", header: "", nosideeffect.} - - template icc_scan_impl*(fnc: untyped; v: untyped): int = - var index: uint32 - discard fnc(index.addr, v) - index.int - -func countLeadingZeroBits*(x: SomeInteger): int {.inline.} = - ## Returns the number of leading zero bits in integer. - ## If `x` is zero, when ``noUndefinedBitOpts`` is set, result is 0, - ## otherwise result is undefined. - - # when noUndefined: - if x == 0: - return sizeof(x) * 8 # Note this differes from the stdlib which returns 0 - - when nimvm: - when sizeof(x) <= 4: result = sizeof(x)*8 - 1 - fastlog2_nim(x.uint32) - else: result = sizeof(x)*8 - 1 - fastlog2_nim(x.uint64) - else: - when useGCC_builtins: - when sizeof(x) <= 4: result = builtin_clz(x.uint32).int - (32 - sizeof(x)*8) - else: result = builtin_clzll(x.uint64).int - else: - when sizeof(x) <= 4: result = sizeof(x)*8 - 1 - fastlog2_nim(x.uint32) - else: result = sizeof(x)*8 - 1 - fastlog2_nim(x.uint64) diff --git a/stint/private/uint_addsub.nim b/stint/private/uint_addsub.nim index 976c9b2..c4f11bc 100644 --- a/stint/private/uint_addsub.nim +++ b/stint/private/uint_addsub.nim @@ -7,7 +7,7 @@ # # at your option. This file may not be copied, modified, or distributed except according to those terms. -import ./bithacks, ./conversion, ./initialization, +import ./conversion, ./initialization, ./datatypes, ./uint_comparison, ./uint_bitwise_ops diff --git a/stint/private/uint_div.nim b/stint/private/uint_div.nim index 70b3ae8..814b8c1 100644 --- a/stint/private/uint_div.nim +++ b/stint/private/uint_div.nim @@ -7,7 +7,7 @@ # # at your option. This file may not be copied, modified, or distributed except according to those terms. -import ./bithacks, ./conversion, ./initialization, +import ./bitops2, ./conversion, ./initialization, ./datatypes, ./uint_comparison, ./uint_bitwise_ops, @@ -122,7 +122,7 @@ proc div3n2n[T: SomeUnsignedInt]( func div2n1n(q, r: var UintImpl, ah, al, b: UintImpl) = - # doAssert countLeadingZeroBits(b) == 0, "Divisor was not normalized" + # doAssert leadingZeros(b) == 0, "Divisor was not normalized" var s: UintImpl div3n2n(q.hi, s, ah.hi, ah.lo, al.hi, b) @@ -130,7 +130,7 @@ func div2n1n(q, r: var UintImpl, ah, al, b: UintImpl) = func div2n1n[T: SomeunsignedInt](q, r: var T, n_hi, n_lo, d: T) = - # doAssert countLeadingZeroBits(d) == 0, "Divisor was not normalized" + # doAssert leadingZeros(d) == 0, "Divisor was not normalized" const size = bitsof(q) @@ -177,7 +177,7 @@ func divmodBZ[T](x, y: UintImpl[T], q, r: var UintImpl[T])= if x.hi < y.lo: # Normalize let - clz = countLeadingZeroBits(y.lo) + clz = leadingZeros(y.lo) xx = x shl clz yy = y.lo shl clz @@ -191,7 +191,7 @@ func divmodBZ[T](x, y: UintImpl[T], q, r: var UintImpl[T])= # General case # Normalization - let clz = countLeadingZeroBits(y) + let clz = leadingZeros(y) let xx = UintImpl[type x](lo: x) shl clz @@ -212,7 +212,7 @@ func divmodBS(x, y: UintImpl, q, r: var UintImpl) = type SubTy = type x.lo var - shift = y.countLeadingZeroBits - x.countLeadingZeroBits + shift = y.leadingZeros - x.leadingZeros d = y shl shift r = x @@ -231,8 +231,8 @@ const BinaryShiftThreshold = 8 # If the difference in bit-length is below 8 func divmod*[T](x, y: UintImpl[T]): tuple[quot, rem: UintImpl[T]]= - let x_clz = x.countLeadingZeroBits - let y_clz = y.countLeadingZeroBits + let x_clz = x.leadingZeros + let y_clz = y.leadingZeros # We short-circuit division depending on special-cases. # TODO: Constant-time division diff --git a/stint/uint_public.nim b/stint/uint_public.nim index 1148649..0851360 100644 --- a/stint/uint_public.nim +++ b/stint/uint_public.nim @@ -111,14 +111,13 @@ func low*[bits: static[int]](_: typedesc[Stuint[bits]]): Stuint[bits] {.inline.} ## Returns the lowest unsigned int of this size. This is always 0. result.data = low(type result.data) -import ./private/bithacks +import ./private/bitops2 -func countLeadingZeroBits*(x: Stuint): int {.inline.} = - ## Logical count of leading zeros - ## This corresponds to the number of zero bits of the input in - ## its big endian representation. - ## If input is zero, the number of bits of the number is returned. - x.data.countLeadingZeroBits +func countOnes*(x: StUint): int {.inline.} = countOnes(x.data) +func parity*(x: StUint): int {.inline.} = parity(x.data) +func firstOne*(x: StUint): int {.inline.} = firstOne(x.data) +func leadingZeros*(x: StUint): int {.inline.} = leadingZeros(x.data) +func trailingZeros*(x: StUint): int {.inline.} = trailingZeros(x.data) import ./private/initialization diff --git a/tests/all_tests.nim b/tests/all_tests.nim index a7c3ef0..073c4ec 100644 --- a/tests/all_tests.nim +++ b/tests/all_tests.nim @@ -7,7 +7,8 @@ # # at your option. This file may not be copied, modified, or distributed except according to those terms. -import test_uint_endianness, +import test_uint_bitops2, + test_uint_endianness, test_uint_comparison, test_uint_bitwise, test_uint_addsub, diff --git a/tests/test_uint_bitops2.nim b/tests/test_uint_bitops2.nim new file mode 100644 index 0000000..c7297f7 --- /dev/null +++ b/tests/test_uint_bitops2.nim @@ -0,0 +1,32 @@ +# Stint +# Copyright 2018 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) +# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) +# +# at your option. This file may not be copied, modified, or distributed except according to those terms. + +import ../stint, unittest + +suite "Testing bitops2": + test "Bitops give sane results": + + check: + countOnes(0b01000100'u8.stuint(128)) == 2 + countOnes(0b01000100'u8.stuint(128) shl 100) == 2 + + parity(0b00000001'u8.stuint(128)) == 1 + parity(0b00000001'u8.stuint(128) shl 100) == 1 + + firstOne(0b00000010'u8.stuint(128)) == 2 + firstOne(0b00000010'u8.stuint(128) shl 100) == 102 + firstOne(0'u8.stuint(128)) == 0 + + leadingZeros(0'u8.stuint(128)) == 128 + leadingZeros(0b00100000'u8.stuint(128)) == 128 - 6 + leadingZeros(0b00100000'u8.stuint(128) shl 100) == 128 - 106 + + trailingZeros(0b00100000'u8.stuint(128)) == 5 + trailingZeros(0b00100000'u8.stuint(128) shl 100) == 105 + trailingZeros(0'u8.stuint(128)) == 128 diff --git a/tests/test_uint_bitwise.nim b/tests/test_uint_bitwise.nim index de94987..fdf1aab 100644 --- a/tests/test_uint_bitwise.nim +++ b/tests/test_uint_bitwise.nim @@ -52,9 +52,3 @@ suite "Testing unsigned int bitwise operations": test "Shift right - by half the size of the integer": check: cast[uint16](b) == z # Sanity check check: cast[uint16](b shr 8) == z shr 8 - - test "leading zero bits": - var x: StUint[2048] - check: x.countLeadingZeroBits() == 2048 - x = StUint[2048].one() - check: x.countLeadingZeroBits() == 2047