refactoring away for loop macros
* remove experimental for loop macro usage * make implementation of several operations follow data structure (recursive data -> recursive implementation) * rename getSize -> bitsof to avoid bits vs bytes confusion * fix potential 32-bit issue where asSignedWords cast to `int` even when `uint64` was used as storage * `hi` for signed ints now is signed - this replaces `asSignedWords` and makes several int operations more natural * fix bit size assert
This commit is contained in:
parent
4fe901d33b
commit
9027fbea3e
41
stint/io.nim
41
stint/io.nim
|
@ -10,7 +10,6 @@
|
|||
import
|
||||
./private/datatypes,
|
||||
./private/int_negabs,
|
||||
./private/as_words,
|
||||
./int_public, ./uint_public,
|
||||
typetraits, algorithm
|
||||
|
||||
|
@ -24,22 +23,11 @@ template static_check_size(T: typedesc[SomeInteger], bits: static[int]) =
|
|||
"\nUse a smaller input type instead. This is a compile-time check" &
|
||||
" to avoid a costly run-time bit_length check at each StUint initialization."
|
||||
|
||||
template assign_leastSignificantWords[T: SomeInteger](result: var (Stuint|Stint), n: T) =
|
||||
template lsw_result: untyped = leastSignificantWord(result.data)
|
||||
template slsw_result: untyped = secondLeastSignificantWord(result.data)
|
||||
|
||||
const wordSize = lsw_result.getSize
|
||||
when sizeof(T) * 8 <= wordSize:
|
||||
lsw_result = (type lsw_result)(n)
|
||||
else: # We try to store an int64 in 2 x uint32 or 4 x uint16
|
||||
# For now we only support assignation from 64 to 2x32 bit
|
||||
const
|
||||
size = getSize(T)
|
||||
halfSize = size div 2
|
||||
halfMask = (1.T shl halfSize) - 1.T
|
||||
|
||||
lsw_result = (type lsw_result)(n and halfMask)
|
||||
slsw_result = (type slsw_result)(n shr halfSize)
|
||||
func assignLo(result: var (UintImpl | IntImpl), n: SomeInteger) {.inline.} =
|
||||
when result.lo is UintImpl:
|
||||
assignLo(result.lo, n)
|
||||
else:
|
||||
result.lo = (type result.lo)(n)
|
||||
|
||||
func stuint*[T: SomeInteger](n: T, bits: static[int]): StUint[bits] {.inline.}=
|
||||
## Converts an integer to an arbitrary precision integer.
|
||||
|
@ -47,7 +35,7 @@ func stuint*[T: SomeInteger](n: T, bits: static[int]): StUint[bits] {.inline.}=
|
|||
assert n >= 0.T
|
||||
when result.data is UintImpl:
|
||||
static_check_size(T, bits)
|
||||
assign_leastSignificantWords(result, n)
|
||||
assignLo(result.data, n)
|
||||
else:
|
||||
result.data = (type result.data)(n)
|
||||
|
||||
|
@ -58,12 +46,12 @@ func stint*[T: SomeInteger](n: T, bits: static[int]): StInt[bits] {.inline.}=
|
|||
static_check_size(T, bits)
|
||||
when T is SomeSignedInt:
|
||||
if n < 0:
|
||||
assign_leastSignificantWords(result, -n)
|
||||
assignLo(result.data, -n)
|
||||
result = -result
|
||||
else:
|
||||
assign_leastSignificantWords(result, n)
|
||||
assignLo(result.data, n)
|
||||
else:
|
||||
assign_leastSignificantWords(result, n)
|
||||
assignLo(result.data, n)
|
||||
else:
|
||||
result.data = (type result.data)(n)
|
||||
|
||||
|
@ -78,14 +66,7 @@ func truncate*(num: Stint or StUint, T: typedesc[int or uint or int64 or uint64]
|
|||
## 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.
|
||||
when T is int: cast[int](num.data.leastSignificantWord)
|
||||
elif T is uint: uint num.data.leastSignificantWord
|
||||
elif T is int64:
|
||||
when sizeof(uint) == 8: cast[int64](num.data.leastSignificantWord)
|
||||
else: cast[int64](num.data.leastSignificantTwoWords)
|
||||
elif T is uint64:
|
||||
when sizeof(uint) == 8: uint64 num.data.leastSignificantWord
|
||||
else: cast[uint64](num.data.leastSignificantTwoWords)
|
||||
cast[T](num.data.leastSignificantWord)
|
||||
|
||||
func toInt*(num: Stint or StUint): int {.inline, deprecated:"Use num.truncate(int) instead".}=
|
||||
num.truncate(int)
|
||||
|
@ -280,7 +261,7 @@ func dumpHex*(x: Stint or StUint, order: static[Endianness] = bigEndian): string
|
|||
|
||||
const
|
||||
hexChars = "0123456789abcdef"
|
||||
size = getSize(x.data) div 8
|
||||
size = bitsof(x.data) div 8
|
||||
|
||||
{.pragma: restrict, codegenDecl: "$# __restrict $#".}
|
||||
let bytes {.restrict.}= cast[ptr array[size, byte]](x.unsafeaddr)
|
||||
|
|
|
@ -1,182 +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, macros
|
||||
|
||||
# #########################################################################
|
||||
# Multi-precision ints to compile-time array of words
|
||||
|
||||
proc asWordsImpl(x: NimNode, current_path: NimNode, result: var NimNode) =
|
||||
## Transforms an UintImpl/IntImpl into an array of words
|
||||
## at compile-time. Recursive implementation.
|
||||
## Result is from most significant word to least significant
|
||||
|
||||
let node = x.getTypeInst
|
||||
|
||||
if node.kind == nnkBracketExpr:
|
||||
assert eqIdent(node[0], "UintImpl") or eqIdent(node[0], "IntImpl")
|
||||
|
||||
let hi = nnkDotExpr.newTree(current_path, newIdentNode("hi"))
|
||||
let lo = nnkDotExpr.newTree(current_path, newIdentNode("lo"))
|
||||
asWordsImpl(node[1], hi, result)
|
||||
asWordsImpl(node[1], lo, result)
|
||||
else:
|
||||
result.add current_path
|
||||
|
||||
# #########################################################################
|
||||
# Accessors
|
||||
|
||||
macro asWords(x: UintImpl or IntImpl, idx: static[int]): untyped =
|
||||
## Access a single element from a multiprecision ints
|
||||
## as if if was stored as an array
|
||||
## x.asWords[0] is the most significant word
|
||||
var words = nnkBracket.newTree()
|
||||
asWordsImpl(x, x, words)
|
||||
result = words[idx]
|
||||
|
||||
macro most_significant_word*(x: UintImpl or IntImpl): untyped =
|
||||
result = getAST(asWords(x, 0))
|
||||
|
||||
macro leastSignificantWord*(x: UintImpl or IntImpl): untyped =
|
||||
var words = nnkBracket.newTree()
|
||||
asWordsImpl(x, x, words)
|
||||
result = words[words.len - 1]
|
||||
|
||||
macro secondLeastSignificantWord*(x: UintImpl or IntImpl): untyped =
|
||||
var words = nnkBracket.newTree()
|
||||
asWordsImpl(x, x, words)
|
||||
result = words[words.len - 2]
|
||||
|
||||
macro leastSignificantTwoWords*(x: UintImpl or IntImpl): untyped =
|
||||
var words = nnkBracket.newTree()
|
||||
asWordsImpl(x, x, words)
|
||||
when system.cpuEndian == bigEndian:
|
||||
result = nnkBracket.newTree(words[words.len - 2], words[words.len - 1])
|
||||
else:
|
||||
result = nnkBracket.newTree(words[words.len - 1], words[words.len - 2])
|
||||
|
||||
# #########################################################################
|
||||
# Iteration macros
|
||||
|
||||
proc replaceNodes*(ast: NimNode, replacing: NimNode, to_replace: NimNode): NimNode =
|
||||
# Args:
|
||||
# - The full syntax tree
|
||||
# - an array of replacement value
|
||||
# - an array of identifiers to replace
|
||||
proc inspect(node: NimNode): NimNode =
|
||||
case node.kind:
|
||||
of {nnkIdent, nnkSym}:
|
||||
for i, c in to_replace:
|
||||
if node.eqIdent($c):
|
||||
return replacing[i]
|
||||
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 asWordsIterate(wordsIdents: untyped, sid0, sid1, sid2: typed, signed: static[bool], loopBody: untyped): untyped =
|
||||
# TODO: We can't use varargs[typed] without losing type info - https://github.com/nim-lang/Nim/issues/7737
|
||||
# So we need a workaround we accept fixed 3 args sid0, sid1, sid2 and will just ignore what is not used
|
||||
result = newStmtList()
|
||||
let NbStints = wordsIdents.len
|
||||
|
||||
# 1. Get the words of each stint
|
||||
# + Workaround varargs[typed] losing type info https://github.com/nim-lang/Nim/issues/7737
|
||||
var words = nnkBracket.newTree
|
||||
block:
|
||||
var wordList = nnkBracket.newTree
|
||||
asWordsImpl(sid0, sid0, wordList)
|
||||
words.add wordList
|
||||
if NbStints > 1:
|
||||
var wordList = nnkBracket.newTree
|
||||
asWordsImpl(sid1, sid1, wordList)
|
||||
words.add wordList
|
||||
if NbStints > 2:
|
||||
var wordList = nnkBracket.newTree
|
||||
asWordsImpl(sid2, sid2, wordList)
|
||||
words.add wordList
|
||||
|
||||
# 2. Construct an unrolled loop
|
||||
# We replace each occurence of each words
|
||||
# in the original loop by how to access it.
|
||||
let NbWords = words[0].len
|
||||
|
||||
for currDepth in 0 ..< NbWords:
|
||||
var replacing = nnkBracket.newTree
|
||||
for currStint in 0 ..< NbStints:
|
||||
var w = words[currStint][currDepth]
|
||||
|
||||
if currDepth == 0 and signed:
|
||||
w = quote do: toInt(`w`)
|
||||
|
||||
replacing.add w
|
||||
|
||||
let body = replaceNodes(loopBody, replacing, to_replace = wordsIdents)
|
||||
result.add quote do:
|
||||
block: `body`
|
||||
|
||||
template asWordsParse(): untyped {.dirty.}=
|
||||
# ##### Tree representation
|
||||
# for word_a, word_b in asWords(a, b):
|
||||
# discard
|
||||
|
||||
# ForStmt
|
||||
# Ident "word_a"
|
||||
# Ident "word_b"
|
||||
# Call
|
||||
# Ident "asWords"
|
||||
# Ident "a"
|
||||
# Ident "b"
|
||||
# StmtList
|
||||
# DiscardStmt
|
||||
# Empty
|
||||
|
||||
# 1. Get the words variable idents
|
||||
var wordsIdents = nnkBracket.newTree
|
||||
var idx = 0
|
||||
while x[idx].kind == nnkIdent:
|
||||
wordsIdents.add x[idx]
|
||||
inc idx
|
||||
|
||||
# 2. Get the multiprecision ints idents
|
||||
var stintsIdents = nnkArgList.newTree # nnkArgList allows to keep the type when passing to varargs[typed]
|
||||
# but varargs[typed] has further issues ¯\_(ツ)_/¯
|
||||
idx = 1
|
||||
while idx < x[wordsIdents.len].len and x[wordsIdents.len][idx].kind == nnkIdent:
|
||||
stintsIdents.add x[wordsIdents.len][idx]
|
||||
inc idx
|
||||
|
||||
assert wordsIdents.len == stintsIdents.len, "The number of loop variables and multiprecision integers t iterate on must be the same"
|
||||
|
||||
# 3. Get the body and pass the bucket to a typed macro
|
||||
# + unroll varargs[typed] manually as workaround for https://github.com/nim-lang/Nim/issues/7737
|
||||
var body = x[x.len - 1]
|
||||
let sid0 = stintsIdents[0]
|
||||
let sid1 = if stintsIdents.len > 1: stintsIdents[1] else: newEmptyNode()
|
||||
let sid2 = if stintsIdents.len > 2: stintsIdents[2] else: newEmptyNode()
|
||||
|
||||
macro asWords*(x: ForLoopStmt): untyped =
|
||||
## This unrolls the body of the for loop and applies it for each word.
|
||||
## Words are processed from most significant word to least significant.
|
||||
asWordsParse()
|
||||
result = quote do: asWordsIterate(`wordsIdents`, `sid0`, `sid1`, `sid2`, false, `body`)
|
||||
|
||||
macro asSignedWords*(x: ForLoopStmt): untyped =
|
||||
## This unrolls the body of the for loop and applies it for each word.
|
||||
## Words are processed from most significant word to least significant.
|
||||
## The most significant word is returned signed for proper comparison.
|
||||
asWordsParse()
|
||||
result = quote do: asWordsIterate(`wordsIdents`, `sid0`, `sid1`, `sid2`, true, `body`)
|
|
@ -7,7 +7,7 @@
|
|||
#
|
||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
import ./datatypes, ./conversion, stdlib_bitops, as_words
|
||||
import ./datatypes, ./conversion, stdlib_bitops
|
||||
export stdlib_bitops
|
||||
|
||||
# We reuse bitops from Nim standard lib, and expand it for multi-precision int.
|
||||
|
@ -17,19 +17,10 @@ export stdlib_bitops
|
|||
func countLeadingZeroBits*(n: UintImpl): int {.inline.} =
|
||||
## Returns the number of leading zero bits in integer.
|
||||
|
||||
const maxHalfRepr = getSize(n) div 2
|
||||
const maxHalfRepr = bitsof(n) div 2
|
||||
|
||||
let hi_clz = n.hi.countLeadingZeroBits
|
||||
|
||||
result = if hi_clz == maxHalfRepr:
|
||||
n.lo.countLeadingZeroBits + maxHalfRepr
|
||||
else: hi_clz
|
||||
|
||||
func isMsbSet*[T: SomeInteger](n: T): bool {.inline.}=
|
||||
## Returns the most significant bit of an integer.
|
||||
const msb_pos = sizeof(T) * 8 - 1
|
||||
result = bool(n.toUint shr msb_pos)
|
||||
|
||||
func isMsbSet*(n: UintImpl or IntImpl): bool {.inline.}=
|
||||
## Returns the most significant bit of an arbitrary precision integer.
|
||||
result = isMsbSet most_significant_word(n)
|
||||
|
|
|
@ -12,11 +12,11 @@ import ./datatypes
|
|||
func toSubtype*[T: SomeInteger](b: bool, _: typedesc[T]): T {.inline.}=
|
||||
b.T
|
||||
|
||||
func toSubtype*[T: UintImpl](b: bool, _: typedesc[T]): T {.inline.}=
|
||||
func toSubtype*[T: UintImpl | IntImpl](b: bool, _: typedesc[T]): T {.inline.}=
|
||||
type SubTy = type result.lo
|
||||
result.lo = toSubtype(b, SubTy)
|
||||
|
||||
func toUint*(n: UintImpl or IntImpl): auto {.inline.}=
|
||||
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:
|
||||
|
@ -34,7 +34,7 @@ func toUint*(n: SomeUnsignedInt): SomeUnsignedInt {.inline.}=
|
|||
## No-op overload of multi-precision int casting
|
||||
n
|
||||
|
||||
func asDoubleUint*(n: BaseUint): auto {.inline.} =
|
||||
func asDoubleUint*(n: UintImpl | SomeUnsignedInt): auto {.inline.} =
|
||||
## Convert an integer or StUint to an uint with double the size
|
||||
type Double = (
|
||||
when n.sizeof == 4: uint64
|
||||
|
|
|
@ -9,9 +9,6 @@
|
|||
|
||||
# TODO: test if GCC/Clang support uint128 natively
|
||||
|
||||
import macros
|
||||
# The macro uintImpl must be exported
|
||||
|
||||
# #### Overview
|
||||
#
|
||||
# Stint extends the default uint8, uint16, uint32, uint64 with power of 2 integers.
|
||||
|
@ -52,7 +49,7 @@ import macros
|
|||
# - a root + intermediate nodes + leaves (uint256)
|
||||
# should be enough to ensure they work at all sizes, edge cases included.
|
||||
# - Adding a new backend like uint128 (GCC/Clang) or uint256 (LLVM instrinsics only) is just adding
|
||||
# a new case in the `uintImpl` macro.
|
||||
# a new case in the `uintImpl` template.
|
||||
# - All math implementations of the operations have a straightforward translation
|
||||
# to a high-low structure, including the fastest Karatsuba multiplication
|
||||
# and co-recursive division algorithm by Burnikel and Ziegler.
|
||||
|
@ -94,127 +91,70 @@ import macros
|
|||
# If you have a lot of computations and intermediate variables it's probably worthwhile
|
||||
# to explore creating an object pool to reuse the memory buffers.
|
||||
|
||||
when not defined(stint_test):
|
||||
macro uintImpl*(bits: static[int]): untyped =
|
||||
# Release version, word size is uint64 (even on 32-bit arch).
|
||||
assert (bits and (bits-1)) == 0, $bits & " is not a power of 2"
|
||||
assert bits >= 8, "The number of bits in a should be greater or equal to 8"
|
||||
template checkDiv2(bits: static[int]): untyped =
|
||||
static:
|
||||
doAssert (bits and (bits-1)) == 0, $bits & " is not a power of 2"
|
||||
doAssert bits >= 8, "The number of bits in a should be greater or equal to 8"
|
||||
bits div 2
|
||||
|
||||
if bits >= 128:
|
||||
let inner = getAST(uintImpl(bits div 2))
|
||||
result = newTree(nnkBracketExpr, ident("UintImpl"), inner)
|
||||
elif bits == 64:
|
||||
result = ident("uint64")
|
||||
elif bits == 32:
|
||||
result = ident("uint32")
|
||||
elif bits == 16:
|
||||
result = ident("uint16")
|
||||
elif bits == 8:
|
||||
result = ident("uint8")
|
||||
else:
|
||||
error "Fatal: unreachable"
|
||||
when defined(mpint_test):
|
||||
template uintImpl*(bits: static[int]): untyped =
|
||||
# Test version, StUint[64] = 2 uint32. Test the logic of the library
|
||||
|
||||
macro intImpl*(bits: static[int]): untyped =
|
||||
# Release version, word size is uint64 (even on 32-bit arch).
|
||||
# Note that int of size 128+ are implemented in terms of unsigned ints
|
||||
# Signed operations are built on top of that.
|
||||
when bits >= 128: UintImpl[uintImpl(checkDiv2(bits))]
|
||||
elif bits == 64: UintImpl[uint32]
|
||||
elif bits == 32: UintImpl[uint16]
|
||||
elif bits == 16: UintImpl[uint8]
|
||||
else: {.fatal: "Only power-of-2 >=16 supported when testing" .}
|
||||
|
||||
template intImpl*(bits: static[int]): untyped =
|
||||
# Test version, StInt[64] = 2 uint32. Test the logic of the library
|
||||
# int is implemented using a signed hi part and an unsigned lo part, given
|
||||
# that the sign resides in hi
|
||||
|
||||
when bits >= 128: IntImpl[intImpl(checkDiv2(bits)), uintImpl(checkDiv2(bits))]
|
||||
elif bits == 64: IntImpl[int32, uint32]
|
||||
elif bits == 32: IntImpl[int16, uint16]
|
||||
elif bits == 16: IntImpl[int8, uint8]
|
||||
else: {.fatal: "Only power-of-2 >=16 supported when testing" .}
|
||||
|
||||
if bits >= 128:
|
||||
let inner = getAST(uintImpl(bits div 2))
|
||||
result = newTree(nnkBracketExpr, ident("IntImpl"), inner)
|
||||
elif bits == 64:
|
||||
result = ident("int64")
|
||||
elif bits == 32:
|
||||
result = ident("int32")
|
||||
elif bits == 16:
|
||||
result = ident("int16")
|
||||
elif bits == 8:
|
||||
result = ident("int8")
|
||||
else:
|
||||
error "Fatal: unreachable"
|
||||
else:
|
||||
macro uintImpl*(bits: static[int]): untyped =
|
||||
# Test version, word size is uint32. Test the logic of the library.
|
||||
assert (bits and (bits-1)) == 0, $bits & " is not a power of 2"
|
||||
assert bits >= 16, "The number of bits in a should be greater or equal to 16"
|
||||
template uintImpl*(bits: static[int]): untyped =
|
||||
when bits >= 128: UintImpl[uintImpl(checkDiv2(bits))]
|
||||
elif bits == 64: uint64
|
||||
elif bits == 32: uint32
|
||||
elif bits == 16: uint16
|
||||
elif bits == 8: uint8
|
||||
else: {.fatal: "Only power-of-2 >=8 supported" .}
|
||||
|
||||
if bits >= 128:
|
||||
let inner = getAST(uintImpl(bits div 2))
|
||||
result = newTree(nnkBracketExpr, ident("UintImpl"), inner)
|
||||
elif bits == 64:
|
||||
result = newTree(nnkBracketExpr, ident("UintImpl"), ident("uint32"))
|
||||
elif bits == 32:
|
||||
result = newTree(nnkBracketExpr, ident("UintImpl"), ident("uint16"))
|
||||
elif bits == 16:
|
||||
result = newTree(nnkBracketExpr, ident("UintImpl"), ident("uint8"))
|
||||
else:
|
||||
error "Fatal: unreachable"
|
||||
template intImpl*(bits: static[int]): untyped =
|
||||
# int is implemented using a signed hi part and an unsigned lo part, given
|
||||
# that the sign resides in hi
|
||||
|
||||
macro intImpl*(bits: static[int]): untyped =
|
||||
# Test version, word size is uint32. Test the logic of the library.
|
||||
# Note that ints are implemented in terms of unsigned ints
|
||||
# Signed operations will be built on top of that.
|
||||
assert (bits and (bits-1)) == 0, $bits & " is not a power of 2"
|
||||
assert bits >= 16, "The number of bits in a should be greater or equal to 16"
|
||||
|
||||
if bits >= 128:
|
||||
let inner = getAST(uintImpl(bits div 2)) # IntImpl is built on top of UintImpl
|
||||
result = newTree(nnkBracketExpr, ident("IntImpl"), inner)
|
||||
elif bits == 64:
|
||||
result = newTree(nnkBracketExpr, ident("IntImpl"), ident("uint32"))
|
||||
elif bits == 32:
|
||||
result = newTree(nnkBracketExpr, ident("IntImpl"), ident("uint16"))
|
||||
elif bits == 16:
|
||||
result = newTree(nnkBracketExpr, ident("IntImpl"), ident("uint8"))
|
||||
else:
|
||||
error "Fatal: unreachable"
|
||||
|
||||
proc getSize*(x: NimNode): static[int] =
|
||||
# Default Nim's `sizeof` doesn't always work at compile-time, pending PR https://github.com/nim-lang/Nim/pull/5664
|
||||
var multiplier = 1
|
||||
var node = x.getTypeInst
|
||||
|
||||
while node.kind == nnkBracketExpr:
|
||||
assert eqIdent(node[0], "UintImpl") or eqIdent(node[0], "IntImpl"), (
|
||||
"getSize only supports primitive integers, Stint and Stuint")
|
||||
multiplier *= 2
|
||||
node = node[1]
|
||||
|
||||
# node[1] has the type
|
||||
# size(node[1]) * multiplier is the size in byte
|
||||
|
||||
# For optimization we cast to the biggest possible uint
|
||||
result = if eqIdent(node, "uint64") or eqIdent(node, "int64"): multiplier * 64
|
||||
elif eqIdent(node, "uint32") or eqIdent(node, "int32"): multiplier * 32
|
||||
elif eqIdent(node, "uint16") or eqIdent(node, "int16"): multiplier * 16
|
||||
elif eqIdent(node, "uint8") or eqIdent(node, "int8"): multiplier * 8
|
||||
elif eqIdent(node, "int") or eqIdent(node, "uint"):
|
||||
multiplier * 8 * sizeof(int)
|
||||
else:
|
||||
assert false, "Error when computing the size. Found: " & $node
|
||||
0
|
||||
|
||||
macro getSize*(x: typed): untyped =
|
||||
let size = getSize(x)
|
||||
result = quote do:
|
||||
`size`
|
||||
when bits >= 128: IntImpl[intImpl(checkDiv2(bits)), uintImpl(checkDiv2(bits))]
|
||||
elif bits == 64: int64
|
||||
elif bits == 32: int32
|
||||
elif bits == 16: int16
|
||||
elif bits == 8: int8
|
||||
else: {.fatal: "Only power-of-2 >=8 supported" .}
|
||||
|
||||
type
|
||||
# ### Private ### #
|
||||
BaseUint* = UintImpl or SomeUnsignedInt
|
||||
|
||||
UintImpl*[Baseuint] = object
|
||||
UintImpl*[BaseUint] = object
|
||||
when system.cpuEndian == littleEndian:
|
||||
lo*, hi*: BaseUint
|
||||
else:
|
||||
hi*, lo*: BaseUint
|
||||
|
||||
IntImpl*[Baseuint] = object
|
||||
IntImpl*[BaseInt, BaseUint] = object
|
||||
# Ints are implemented in terms of uints
|
||||
when system.cpuEndian == littleEndian:
|
||||
lo*, hi*: BaseUint
|
||||
lo*: BaseUint
|
||||
hi*: BaseInt
|
||||
else:
|
||||
hi*, lo*: BaseUint
|
||||
hi*: BaseInt
|
||||
lo*: BaseUint
|
||||
|
||||
# ### Private ### #
|
||||
|
||||
StUint*[bits: static[int]] = object
|
||||
|
@ -222,3 +162,42 @@ type
|
|||
|
||||
StInt*[bits: static[int]] = object
|
||||
data*: intImpl(bits)
|
||||
|
||||
template bitsof*(x: SomeInteger): int = sizeof(x) * 8
|
||||
|
||||
# XXX: https://github.com/nim-lang/Nim/issues/9494 - the extra overloads
|
||||
# can eventually be collapsed to one..
|
||||
template bitsof*(x: UintImpl[SomeInteger]): untyped =
|
||||
2 * bitsof(x.lo)
|
||||
template bitsof*(x: UintImpl[UintImpl[SomeInteger]]): untyped =
|
||||
2 * bitsof(x.lo)
|
||||
template bitsof*(x: UintImpl[UintImpl[UintImpl[SomeInteger]]]): untyped =
|
||||
2 * bitsof(x.lo)
|
||||
template bitsof*(x: UintImpl[UintImpl[UintImpl[UintImpl[SomeInteger]]]]): untyped =
|
||||
2 * bitsof(x.lo)
|
||||
|
||||
template applyHiLo*(a: UintImpl | IntImpl, c: untyped): untyped =
|
||||
## Apply `c` to each of `hi` and `lo`
|
||||
var res: type a
|
||||
res.hi = c(a.hi)
|
||||
res.lo = c(a.lo)
|
||||
res
|
||||
|
||||
template applyHiLo*(a, b: UintImpl | IntImpl, c: untyped): untyped =
|
||||
## Apply `c` to each of `hi` and `lo`
|
||||
var res: type a
|
||||
res.hi = c(a.hi, b.hi)
|
||||
res.lo = c(a.lo, b.lo)
|
||||
res
|
||||
|
||||
func leastSignificantWord*(num: UintImpl | IntImpl): auto {.inline.} =
|
||||
when num.lo is UintImpl:
|
||||
num.lo.leastSignificantWord
|
||||
else:
|
||||
num.lo
|
||||
|
||||
func mostSignificantWord*(num: UintImpl | IntImpl): auto {.inline.} =
|
||||
when num.hi is (UintImpl | IntImpl):
|
||||
num.hi.mostSignificantWord
|
||||
else:
|
||||
num.hi
|
||||
|
|
|
@ -7,13 +7,13 @@
|
|||
#
|
||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
import ./datatypes, ./as_words, typetraits
|
||||
import ./datatypes
|
||||
|
||||
func zero*(T: typedesc): T {.inline.} =
|
||||
discard
|
||||
|
||||
func one*(T: typedesc[UintImpl or IntImpl]): T {.inline.} =
|
||||
leastSignificantWord(result) = 1
|
||||
|
||||
func one*(T: typedesc[SomeInteger]): T {.inline.} =
|
||||
1
|
||||
|
||||
func one*(T: typedesc[UintImpl or IntImpl]): T {.inline.} =
|
||||
result.lo = one(type result.lo)
|
||||
|
|
|
@ -13,7 +13,7 @@ import
|
|||
|
||||
func `+`*(x, y: IntImpl): IntImpl {.inline.}=
|
||||
# Addition for multi-precision signed int.
|
||||
type SubTy = type x.lo
|
||||
type SubTy = type x.hi
|
||||
result.lo = x.lo + y.lo
|
||||
result.hi = (result.lo < y.lo).toSubtype(SubTy) + x.hi + y.hi
|
||||
|
||||
|
@ -32,7 +32,7 @@ func `+=`*(x: var IntImpl, y: IntImpl) {.inline.}=
|
|||
func `-`*(x, y: IntImpl): IntImpl {.inline.}=
|
||||
# Substraction for multi-precision signed int.
|
||||
|
||||
type SubTy = type x.lo
|
||||
type SubTy = type x.hi
|
||||
result.lo = x.lo - y.lo
|
||||
result.hi = x.hi - y.hi - (x.lo < y.lo).toSubtype(SubTy)
|
||||
|
||||
|
|
|
@ -7,32 +7,20 @@
|
|||
#
|
||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
import ./datatypes, ./as_words
|
||||
import ./datatypes, ./uint_bitwise_ops
|
||||
|
||||
func `not`*(x: IntImpl): IntImpl {.inline.}=
|
||||
## Bitwise complement of unsigned integer x
|
||||
{.push experimental: "forLoopMacros".}
|
||||
for wr, wx in asWords(result, x):
|
||||
wr = not wx
|
||||
{.pop.}
|
||||
applyHiLo(x, `not`)
|
||||
|
||||
func `or`*(x, y: IntImpl): IntImpl {.inline.}=
|
||||
## `Bitwise or` of numbers x and y
|
||||
{.push experimental: "forLoopMacros".}
|
||||
for wr, wx, wy in asWords(result, x, y):
|
||||
wr = wx or wy
|
||||
{.pop.}
|
||||
applyHiLo(x, y, `or`)
|
||||
|
||||
func `and`*(x, y: IntImpl): IntImpl {.inline.}=
|
||||
## `Bitwise and` of numbers x and y
|
||||
{.push experimental: "forLoopMacros".}
|
||||
for wr, wx, wy in asWords(result, x, y):
|
||||
wr = wx and wy
|
||||
{.pop.}
|
||||
applyHiLo(x, y, `and`)
|
||||
|
||||
func `xor`*(x, y: IntImpl): IntImpl {.inline.}=
|
||||
## `Bitwise xor` of numbers x and y
|
||||
{.push experimental: "forLoopMacros".}
|
||||
for wr, wx, wy in asWords(result, x, y):
|
||||
wr = wx xor wy
|
||||
{.pop.}
|
||||
applyHiLo(x, y, `xor`)
|
||||
|
|
|
@ -7,53 +7,37 @@
|
|||
#
|
||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
import ./datatypes, ./bithacks, ./as_words,
|
||||
./bithacks
|
||||
import ./datatypes, ./bithacks, ./uint_comparison
|
||||
|
||||
func isZero*(n: SomeSignedInt): bool {.inline.} =
|
||||
n == 0
|
||||
|
||||
func isZero*(n: IntImpl): bool {.inline.} =
|
||||
{.push experimental: "forLoopMacros".}
|
||||
for word in asWords(n):
|
||||
if word != 0:
|
||||
return false
|
||||
{.pop.}
|
||||
return true
|
||||
n.hi.isZero and n.lo.isZero
|
||||
|
||||
func isNegative*(n: SomeSignedInt): bool {.inline.} =
|
||||
n < 0
|
||||
|
||||
func isNegative*(n: IntImpl): bool {.inline.} =
|
||||
## Returns true if a number is negative:
|
||||
n.isMsbSet
|
||||
n.hi.isNegative
|
||||
|
||||
func `<`*(x, y: IntImpl): bool {.inline.}=
|
||||
# Lower comparison for multi-precision integers
|
||||
{.push experimental: "forLoopMacros".}
|
||||
for wx, wy in asSignedWords(x, y):
|
||||
if wx != wy:
|
||||
return wx < wy
|
||||
{.pop.}
|
||||
return false # they're equal
|
||||
x.hi < y.hi or
|
||||
(x.hi == y.hi and x.lo < y.lo)
|
||||
|
||||
func `==`*(x, y: IntImpl): bool {.inline.}=
|
||||
# Equal comparison for multi-precision integers
|
||||
{.push experimental: "forLoopMacros".}
|
||||
for wx, wy in asWords(x, y):
|
||||
if wx != wy:
|
||||
return false
|
||||
{.pop.}
|
||||
return true # they're equal
|
||||
x.hi == y.hi and x.lo == y.lo
|
||||
|
||||
func `<=`*(x, y: IntImpl): bool {.inline.}=
|
||||
# Lower or equal comparison for multi-precision integers
|
||||
{.push experimental: "forLoopMacros".}
|
||||
for wx, wy in asSignedWords(x, y):
|
||||
if wx != wy:
|
||||
return wx < wy
|
||||
{.pop.}
|
||||
return true # they're equal
|
||||
|
||||
func isOdd*(x: IntImpl): bool {.inline.}=
|
||||
bool(x.leastSignificantWord and 1)
|
||||
x.hi < y.hi or
|
||||
(x.hi == y.hi and x.lo <= y.lo)
|
||||
|
||||
func isEven*(x: IntImpl): bool {.inline.}=
|
||||
not x.isOdd
|
||||
x.lo.isEven
|
||||
|
||||
func isOdd*(x: IntImpl): bool {.inline.}=
|
||||
not x.isEven
|
||||
|
|
|
@ -36,12 +36,12 @@ func divmod*(x, y: SomeSignedInt): tuple[quot, rem: SomeSignedInt] {.inline.}=
|
|||
# hopefully the compiler fuse that in a single op
|
||||
(x div y, x mod y)
|
||||
|
||||
proc divmod*[T](x, y: IntImpl[T]): tuple[quot, rem: IntImpl[T]] =
|
||||
proc divmod*[T, T2](x, y: IntImpl[T, T2]): tuple[quot, rem: IntImpl[T, T2]] =
|
||||
## Divmod operation for multi-precision signed integer
|
||||
|
||||
result = cast[type result](divmod(
|
||||
cast[UintImpl[T]](x.abs),
|
||||
cast[UintImpl[T]](y.abs)
|
||||
cast[UintImpl[T2]](x.abs),
|
||||
cast[UintImpl[T2]](y.abs)
|
||||
))
|
||||
|
||||
if (x.isNegative xor y.isNegative):
|
||||
|
|
|
@ -7,17 +7,21 @@
|
|||
#
|
||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
import ./datatypes, ./int_bitwise_ops, ./as_words, ./initialization
|
||||
import ./datatypes, ./int_bitwise_ops, ./initialization, ./uint_highlow
|
||||
|
||||
func high*[T](_: typedesc[IntImpl[T]]): IntImpl[T] {.inline.}=
|
||||
# XXX There's some Araq reason why this isn't part of the std lib..
|
||||
func high(_: typedesc[SomeUnsignedInt]): SomeUnsignedInt =
|
||||
not SomeUnsignedInt(0'u8)
|
||||
|
||||
func high*[T, T2](_: typedesc[IntImpl[T, T2]]): IntImpl[T, T2] {.inline.}=
|
||||
# The highest signed int has representation
|
||||
# 0b0111_1111_1111_1111 ....
|
||||
# so we only have to unset the most significant bit.
|
||||
result = not result
|
||||
most_significant_word(result) = most_significant_word(result) shr 1
|
||||
result.hi = high(type result.hi)
|
||||
result.lo = high(type result.lo)
|
||||
|
||||
func low*[T](_: typedesc[IntImpl[T]]): IntImpl[T] {.inline.}=
|
||||
func low*[T, T2](_: typedesc[IntImpl[T, T2]]): IntImpl[T, T2] {.inline.}=
|
||||
# The lowest signed int has representation
|
||||
# 0b1000_0000_0000_0000 ....
|
||||
# so we only have to set the most significant bit.
|
||||
not high(IntImpl[T])
|
||||
not high(IntImpl[T, T2])
|
||||
|
|
|
@ -9,9 +9,9 @@
|
|||
|
||||
import ./datatypes, ./uint_mul
|
||||
|
||||
func `*`*[T](x, y: IntImpl[T]): IntImpl[T] {.inline.}=
|
||||
func `*`*[T, T2](x, y: IntImpl[T, T2]): IntImpl[T, T2] {.inline.}=
|
||||
## Multiplication for multi-precision signed integers
|
||||
# For 2-complement representation this is the exact same
|
||||
# as unsigned multiplication. We don't need to deal with the sign
|
||||
# TODO: overflow detection.
|
||||
cast[type result](cast[UIntImpl[T]](x) * cast[UIntImpl[T]](y))
|
||||
cast[type result](cast[UIntImpl[T2]](x) * cast[UIntImpl[T2]](y))
|
||||
|
|
|
@ -12,15 +12,15 @@ import
|
|||
./initialization, ./int_highlow,
|
||||
./int_addsub, ./int_comparison
|
||||
|
||||
func `-`*[T: IntImpl](x: T): T {.inline.}=
|
||||
func `-`*(x: IntImpl): IntImpl {.inline.}=
|
||||
# Negate a multi-precision signed int.
|
||||
|
||||
when compileOption("boundChecks"):
|
||||
if unlikely(x == low(T)):
|
||||
if unlikely(x == low(type x)):
|
||||
raise newException(OverflowError, "The lowest negative number cannot be negated")
|
||||
|
||||
result = not x
|
||||
result += one(T)
|
||||
result += one(type x)
|
||||
|
||||
func abs*[T: IntImpl](x: T): T {.inline.}=
|
||||
## Returns the absolute value of a signed int.
|
||||
|
|
|
@ -7,35 +7,23 @@
|
|||
#
|
||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
import ./datatypes, ./as_words
|
||||
import ./datatypes
|
||||
|
||||
func `not`*(x: UintImpl): UintImpl {.inline.}=
|
||||
## Bitwise complement of unsigned integer x
|
||||
{.push experimental: "forLoopMacros".}
|
||||
for wr, wx in asWords(result, x):
|
||||
wr = not wx
|
||||
{.pop.}
|
||||
applyHiLo(x, `not`)
|
||||
|
||||
func `or`*(x, y: UintImpl): UintImpl {.inline.}=
|
||||
## `Bitwise or` of numbers x and y
|
||||
{.push experimental: "forLoopMacros".}
|
||||
for wr, wx, wy in asWords(result, x, y):
|
||||
wr = wx or wy
|
||||
{.pop.}
|
||||
applyHiLo(x, y, `or`)
|
||||
|
||||
func `and`*(x, y: UintImpl): UintImpl {.inline.}=
|
||||
## `Bitwise and` of numbers x and y
|
||||
{.push experimental: "forLoopMacros".}
|
||||
for wr, wx, wy in asWords(result, x, y):
|
||||
wr = wx and wy
|
||||
{.pop.}
|
||||
applyHiLo(x, y, `and`)
|
||||
|
||||
func `xor`*(x, y: UintImpl): UintImpl {.inline.}=
|
||||
## `Bitwise xor` of numbers x and y
|
||||
{.push experimental: "forLoopMacros".}
|
||||
for wr, wx, wy in asWords(result, x, y):
|
||||
wr = wx xor wy
|
||||
{.pop.}
|
||||
applyHiLo(x, y, `xor`)
|
||||
|
||||
func `shr`*(x: UintImpl, y: SomeInteger): UintImpl {.inline.}
|
||||
# Forward declaration
|
||||
|
@ -45,7 +33,7 @@ func `shl`*(x: UintImpl, y: SomeInteger): UintImpl {.inline.}=
|
|||
# Note: inlining this poses codegen/aliasing issue when doing `x = x shl 1`
|
||||
|
||||
# TODO: would it be better to reimplement this with words iteration?
|
||||
const halfSize: type(y) = getSize(x) div 2
|
||||
const halfSize: type(y) = bitsof(x) div 2
|
||||
|
||||
if y == 0:
|
||||
return x
|
||||
|
@ -61,7 +49,7 @@ func `shr`*(x: UintImpl, y: SomeInteger): UintImpl {.inline.}=
|
|||
## Compute the `shift right` operation of x and y
|
||||
## Similar to C standard, result is undefined if y is bigger
|
||||
## than the number of bits in x.
|
||||
const halfSize: type(y) = getSize(x) div 2
|
||||
const halfSize: type(y) = bitsof(x) div 2
|
||||
|
||||
if y == 0:
|
||||
return x
|
||||
|
|
|
@ -7,48 +7,36 @@
|
|||
#
|
||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
import ./datatypes, ./as_words
|
||||
import ./datatypes
|
||||
|
||||
func isZero*(n: SomeUnsignedInt): bool {.inline.} =
|
||||
n == 0
|
||||
|
||||
func isZero*(n: UintImpl): bool {.inline.} =
|
||||
{.push experimental: "forLoopMacros".}
|
||||
for word in asWords(n):
|
||||
if word != 0:
|
||||
return false
|
||||
{.pop.}
|
||||
return true
|
||||
n.hi.isZero and n.lo.isZero
|
||||
|
||||
func `<`*(x, y: UintImpl): bool {.inline.}=
|
||||
# Lower comparison for multi-precision integers
|
||||
{.push experimental: "forLoopMacros".}
|
||||
for wx, wy in asWords(x, y):
|
||||
if wx != wy:
|
||||
return wx < wy
|
||||
{.pop.}
|
||||
return false # they're equal
|
||||
x.hi < y.hi or
|
||||
(x.hi == y.hi and x.lo < y.lo)
|
||||
|
||||
func `==`*(x, y: UintImpl): bool {.inline.}=
|
||||
# Equal comparison for multi-precision integers
|
||||
{.push experimental: "forLoopMacros".}
|
||||
for wx, wy in asWords(x, y):
|
||||
if wx != wy:
|
||||
return false
|
||||
{.pop.}
|
||||
return true # they're equal
|
||||
x.hi == y.hi and x.lo == y.lo
|
||||
|
||||
func `<=`*(x, y: UintImpl): bool {.inline.}=
|
||||
# Lower or equal comparison for multi-precision integers
|
||||
{.push experimental: "forLoopMacros".}
|
||||
for wx, wy in asWords(x, y):
|
||||
if wx != wy:
|
||||
return wx < wy
|
||||
{.pop.}
|
||||
return true # they're equal
|
||||
x.hi < y.hi or
|
||||
(x.hi == y.hi and x.lo <= y.lo)
|
||||
|
||||
func isOdd*(x: UintImpl): bool {.inline.}=
|
||||
bool(x.leastSignificantWord and 1)
|
||||
func isEven*(x: SomeUnsignedInt): bool {.inline.} =
|
||||
(x and 1) == 0
|
||||
|
||||
func isEven*(x: UintImpl): bool {.inline.}=
|
||||
not x.isOdd
|
||||
x.lo.isEven
|
||||
|
||||
func isOdd*(x: SomeUnsignedInt): bool {.inline.} =
|
||||
not x.isEven
|
||||
|
||||
func isOdd*(x: UintImpl): bool {.inline.}=
|
||||
not x.isEven
|
||||
|
|
|
@ -133,7 +133,7 @@ func div2n1n[T: SomeunsignedInt](q, r: var T, n_hi, n_lo, d: T) =
|
|||
# assert countLeadingZeroBits(d) == 0, "Divisor was not normalized"
|
||||
|
||||
const
|
||||
size = getSize(q)
|
||||
size = bitsof(q)
|
||||
halfSize = size div 2
|
||||
halfMask = (1.T shl halfSize) - 1.T
|
||||
|
||||
|
@ -238,7 +238,7 @@ func divmod*[T](x, y: UintImpl[T]): tuple[quot, rem: UintImpl[T]]=
|
|||
# TODO: Constant-time division
|
||||
if unlikely(y.isZero):
|
||||
raise newException(DivByZeroError, "You attempted to divide by zero")
|
||||
elif y_clz == (getSize(y) - 1):
|
||||
elif y_clz == (bitsof(y) - 1):
|
||||
# y is one
|
||||
result.quot = x
|
||||
elif (x.hi or y.hi).isZero:
|
||||
|
@ -249,7 +249,7 @@ func divmod*[T](x, y: UintImpl[T]): tuple[quot, rem: UintImpl[T]]=
|
|||
# TODO. Would it be faster to use countTrailingZero (ctz) + clz == size(y) - 1?
|
||||
# Especially because we shift by ctz after.
|
||||
# It is a bit tricky with recursive types. An empty n.lo means 0 or sizeof(n.lo)
|
||||
let y_ctz = getSize(y) - y_clz - 1
|
||||
let y_ctz = bitsof(y) - y_clz - 1
|
||||
result.quot = x shr y_ctz
|
||||
result.rem = x and (y - one(type y))
|
||||
elif x == y:
|
||||
|
|
|
@ -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 StUint
|
||||
export UintImpl, uintImpl # TODO: remove the need to export those
|
||||
|
@ -67,22 +67,15 @@ func isZero*(x: Stuint): bool {.inline.} =
|
|||
## false otherwise
|
||||
x.data.isZero
|
||||
|
||||
func isOdd*(x: SomeUnSignedInt): bool {.inline.}=
|
||||
## Returns true if input is odd
|
||||
## false otherwise
|
||||
bool(x and 1)
|
||||
func isEven*(x: SomeUnSignedInt): bool {.inline.}=
|
||||
## Returns true if input is even
|
||||
## false otherwise
|
||||
not x.isOdd
|
||||
func isOdd*(x: Stuint): bool {.inline.}=
|
||||
## Returns true if input is odd
|
||||
## false otherwise
|
||||
x.data.isOdd
|
||||
export isEven, isOdd
|
||||
func isEven*(x: Stuint): bool {.inline.}=
|
||||
## Returns true if input is even
|
||||
## false otherwise
|
||||
x.data.isEven
|
||||
func isOdd*(x: Stuint): bool {.inline.}=
|
||||
## Returns true if input is odd
|
||||
## false otherwise
|
||||
not x.isEven
|
||||
|
||||
import ./private/uint_bitwise_ops
|
||||
|
||||
|
|
Loading…
Reference in New Issue