use bitops2 from shims (#77)

This commit is contained in:
Jacek Sieka 2019-05-09 13:46:40 -06:00 committed by Mamy Ratsimbazafy
parent 6853ebe97c
commit 9c51f9e7d5
13 changed files with 96 additions and 171 deletions

View File

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

View File

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

View File

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

40
stint/private/bitops2.nim Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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