mirror of https://github.com/vacp2p/nim-stint.git
use bitops2 from shims (#77)
This commit is contained in:
parent
6853ebe97c
commit
9c51f9e7d5
|
@ -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":
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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: "<intrin.h>", nosideeffect.}
|
||||
proc bitScanReverse64*(index: ptr culong, mask: uint64): cuchar {.importc: "_BitScanReverse64", header: "<intrin.h>", 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: "<immintrin.h>", nosideeffect.}
|
||||
proc bitScanReverse64*(p: ptr uint32, b: uint64): cuchar {.importc: "_BitScanReverse64", header: "<immintrin.h>", 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)
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue