mirror of
https://github.com/status-im/nim-stint.git
synced 2025-02-21 03:18:13 +00:00
Uint - allow compile-time evaluation for all procs (#54)
* Initial commit - allow all ops at compile-time on uint. * Update benchmark * Delete commented ideal version of asWordsiterate, it is obvious from the workaround (and is tracked in the PR)
This commit is contained in:
parent
184e22b659
commit
a46c62bc83
@ -1,4 +1,4 @@
|
|||||||
import ../stint/mpint, times
|
import ../stint, times
|
||||||
|
|
||||||
|
|
||||||
# Warmup on normal int
|
# Warmup on normal int
|
||||||
@ -18,10 +18,10 @@ echo "Warmup: " & $(stop - start) & "s"
|
|||||||
|
|
||||||
start = cpuTime()
|
start = cpuTime()
|
||||||
block:
|
block:
|
||||||
var foo = 123.u(256)
|
var foo = 123.u256
|
||||||
for i in 0 ..< 10_000_000:
|
for i in 0 ..< 10_000_000:
|
||||||
foo += i.u(256) * i.u(256) mod 456.u(256)
|
foo += i.u256 * i.u256 mod 456.u256
|
||||||
foo = foo mod 789.u(256)
|
foo = foo mod 789.u256
|
||||||
|
|
||||||
stop = cpuTime()
|
stop = cpuTime()
|
||||||
echo "Library: " & $(stop - start) & "s"
|
echo "Library: " & $(stop - start) & "s"
|
||||||
@ -47,3 +47,9 @@ when defined(bench_ttmath):
|
|||||||
# Warmup: 0.04060799999999999s
|
# Warmup: 0.04060799999999999s
|
||||||
# Library: 0.9576759999999999s
|
# Library: 0.9576759999999999s
|
||||||
# TTMath: 0.758443s
|
# TTMath: 0.758443s
|
||||||
|
|
||||||
|
|
||||||
|
# After PR #54 for compile-time evaluation
|
||||||
|
# which includes loop unrolling but may bloat the code
|
||||||
|
# Warmup: 0.03993500000000001s
|
||||||
|
# Library: 0.848464s
|
||||||
|
@ -39,7 +39,7 @@ proc isInt*(x: NimNode): static[bool] =
|
|||||||
elif eqIdent(x, "int8"): true
|
elif eqIdent(x, "int8"): true
|
||||||
else: false
|
else: false
|
||||||
|
|
||||||
macro most_significant_word*(x: IntImpl): untyped =
|
macro most_significant_word_signed*(x: IntImpl): untyped =
|
||||||
|
|
||||||
let optim_type = optimInt(x)
|
let optim_type = optimInt(x)
|
||||||
if optim_type.isInt:
|
if optim_type.isInt:
|
||||||
@ -87,8 +87,8 @@ macro asSignedWordsZip*[T](
|
|||||||
first_y = quote do:
|
first_y = quote do:
|
||||||
cast[`optim_type`](`y`)
|
cast[`optim_type`](`y`)
|
||||||
else:
|
else:
|
||||||
first_x = getAST(most_significant_word(x))
|
first_x = getAST(most_significant_word_signed(x))
|
||||||
first_y = getAST(most_significant_word(y))
|
first_y = getAST(most_significant_word_signed(y))
|
||||||
|
|
||||||
replacing.add first_x
|
replacing.add first_x
|
||||||
replacing.add first_y
|
replacing.add first_y
|
||||||
|
@ -9,33 +9,52 @@
|
|||||||
|
|
||||||
import ./datatypes, macros
|
import ./datatypes, macros
|
||||||
|
|
||||||
proc optimUint(x: NimNode): NimNode =
|
# #########################################################################
|
||||||
let size = getSize(x)
|
# Multi-precision ints to compile-time array of words
|
||||||
|
|
||||||
if size > 64:
|
proc asWordsImpl(x: NimNode, current_path: NimNode, result: var NimNode) =
|
||||||
result = quote do:
|
## Transforms an UintImpl/IntImpl into an array of words
|
||||||
array[`size` div 64, uint64]
|
## at compile-time. Recursive implementation.
|
||||||
elif size == 64:
|
## Result is from most significant word to least significant
|
||||||
result = quote do:
|
|
||||||
uint64
|
let node = x.getTypeInst
|
||||||
elif size == 32:
|
|
||||||
result = quote do:
|
if node.kind == nnkBracketExpr:
|
||||||
uint32
|
assert eqIdent(node[0], "UintImpl") or eqIdent(node[0], "IntImpl")
|
||||||
elif size == 16:
|
|
||||||
result = quote do:
|
let hi = nnkDotExpr.newTree(current_path, newIdentNode("hi"))
|
||||||
uint16
|
let lo = nnkDotExpr.newTree(current_path, newIdentNode("lo"))
|
||||||
elif size == 8:
|
asWordsImpl(node[1], hi, result)
|
||||||
result = quote do:
|
asWordsImpl(node[1], lo, result)
|
||||||
uint8
|
|
||||||
else:
|
else:
|
||||||
error "Unreachable path reached"
|
result.add current_path
|
||||||
|
|
||||||
proc isUint(x: NimNode): static[bool] =
|
# #########################################################################
|
||||||
if eqIdent(x, "uint64"): true
|
# Accessors
|
||||||
elif eqIdent(x, "uint32"): true
|
|
||||||
elif eqIdent(x, "uint16"): true
|
macro asWords(x: UintImpl or IntImpl, idx: static[int]): untyped =
|
||||||
elif eqIdent(x, "uint8"): true
|
## Access a single element from a multiprecision ints
|
||||||
else: false
|
## 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 least_significant_word*(x: UintImpl or IntImpl): untyped =
|
||||||
|
var words = nnkBracket.newTree()
|
||||||
|
asWordsImpl(x, x, words)
|
||||||
|
result = words[words.len - 1]
|
||||||
|
|
||||||
|
macro second_least_significant_word*(x: UintImpl or IntImpl): untyped =
|
||||||
|
var words = nnkBracket.newTree()
|
||||||
|
asWordsImpl(x, x, words)
|
||||||
|
result = words[words.len - 2]
|
||||||
|
|
||||||
|
# #########################################################################
|
||||||
|
# Iteration macros
|
||||||
|
|
||||||
proc replaceNodes*(ast: NimNode, replacing: NimNode, to_replace: NimNode): NimNode =
|
proc replaceNodes*(ast: NimNode, replacing: NimNode, to_replace: NimNode): NimNode =
|
||||||
# Args:
|
# Args:
|
||||||
@ -60,263 +79,85 @@ proc replaceNodes*(ast: NimNode, replacing: NimNode, to_replace: NimNode): NimNo
|
|||||||
return rTree
|
return rTree
|
||||||
result = inspect(ast)
|
result = inspect(ast)
|
||||||
|
|
||||||
proc least_significant_two_words*(x: NimNode): tuple[lo, hi: NimNode] =
|
macro asWordsIterate(wordsIdents: untyped, sid0, sid1, sid2: typed, loopBody: untyped): untyped =
|
||||||
var node = x.getTypeInst
|
# TODO: We can't use varargs[typed] without losing type info - https://github.com/nim-lang/Nim/issues/7737
|
||||||
var result_lo = x
|
# 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
|
||||||
|
|
||||||
while node.kind == nnkBracketExpr:
|
# 1. Get the words of each stint
|
||||||
assert eqIdent(node[0], "UintImpl") or eqIdent(node[0], "IntImpl"), (
|
# + Workaround varargs[typed] losing type info https://github.com/nim-lang/Nim/issues/7737
|
||||||
"least_significant_word only supports primitive integers, Stint and Stuint")
|
var words = nnkBracket.newTree
|
||||||
result_lo = quote do: `result_lo`.lo
|
block:
|
||||||
node = node[1]
|
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
|
||||||
|
|
||||||
var result_hi = result_lo.copyNimTree # ⚠ Aliasing: NimNodes are ref objects
|
# 2. Construct an unrolled loop
|
||||||
result_hi[1] = newIdentNode("hi") # replace the last lo by hi
|
# We replace each occurence of each words
|
||||||
result = (result_lo, result_hi)
|
# in the original loop by how to access it.
|
||||||
|
let NbWords = words[0].len
|
||||||
|
|
||||||
macro second_least_significant_word*(x: UintImpl or IntImpl): untyped =
|
for currDepth in 0 ..< NbWords:
|
||||||
result = least_significant_two_words(x).hi
|
var replacing = nnkBracket.newTree
|
||||||
|
for currStint in 0 ..< NbStints:
|
||||||
|
replacing.add words[currStint][currDepth]
|
||||||
|
|
||||||
macro least_significant_word*(x: UintImpl or IntImpl): untyped =
|
let body = replaceNodes(loopBody, replacing, to_replace = wordsIdents)
|
||||||
result = least_significant_two_words(x).lo
|
result.add quote do:
|
||||||
|
block: `body`
|
||||||
|
|
||||||
macro asWords*(n: UintImpl or IntImpl, ignoreEndianness: static[bool], loopBody: untyped): untyped =
|
macro asWords*(x: ForLoopStmt): untyped =
|
||||||
## Iterates over n, as an array of words.
|
## This unrolls the body of the for loop
|
||||||
## Input:
|
## and applies it for each word.
|
||||||
## - n: The Multiprecision int
|
##
|
||||||
## - If endianness should be taken into account for iteratio order.
|
## TODO: allow an ignoreEndianness and signed parameter
|
||||||
## If yes, iteration is done from most significant word to least significant.
|
|
||||||
## Otherwise it is done in memory layout order.
|
|
||||||
## - loopBody: the operation you want to do on each word of n
|
|
||||||
let
|
|
||||||
optim_type = optimUint(n)
|
|
||||||
var
|
|
||||||
inner_n: NimNode
|
|
||||||
to_replace = nnkBracket.newTree
|
|
||||||
replacing = nnkBracket.newTree
|
|
||||||
|
|
||||||
if optim_type.isUint:
|
# ##### Tree representation
|
||||||
# We directly cast n
|
# for word_a, word_b in asWords(a, b):
|
||||||
inner_n = quote do:
|
# discard
|
||||||
cast[`optim_type`](`n`)
|
|
||||||
else:
|
|
||||||
# If we have an array of words, inner_n is a loop intermediate variable
|
|
||||||
inner_n = ident("n_asWordsRaw")
|
|
||||||
|
|
||||||
to_replace.add n
|
# ForStmt
|
||||||
replacing.add inner_n
|
# Ident "word_a"
|
||||||
|
# Ident "word_b"
|
||||||
|
# Call
|
||||||
|
# Ident "asWords"
|
||||||
|
# Ident "a"
|
||||||
|
# Ident "b"
|
||||||
|
# StmtList
|
||||||
|
# DiscardStmt
|
||||||
|
# Empty
|
||||||
|
|
||||||
let replacedAST = replaceNodes(loopBody, replacing, to_replace)
|
# 1. Get the words variable idents
|
||||||
|
var wordsIdents = nnkBracket.newTree
|
||||||
|
var idx = 0
|
||||||
|
while x[idx].kind == nnkIdent:
|
||||||
|
wordsIdents.add x[idx]
|
||||||
|
inc idx
|
||||||
|
|
||||||
if optim_type.isUint:
|
# 2. Get the multiprecision ints idents
|
||||||
result = replacedAST
|
var stintsIdents = nnkArgList.newTree # nnkArgList allows to keep the type when passing to varargs[typed]
|
||||||
else:
|
# but varargs[typed] has further issues ¯\_(ツ)_/¯
|
||||||
if ignoreEndianness or system.cpuEndian == bigEndian:
|
idx = 1
|
||||||
result = quote do:
|
while idx < x[wordsIdents.len].len and x[wordsIdents.len][idx].kind == nnkIdent:
|
||||||
for `inner_n` in cast[`optim_type`](`n`):
|
stintsIdents.add x[wordsIdents.len][idx]
|
||||||
`replacedAST`
|
inc idx
|
||||||
else:
|
|
||||||
assert false, "Not implemented"
|
|
||||||
|
|
||||||
macro asWordsZip*(x, y: UintImpl or IntImpl, ignoreEndianness: static[bool], loopBody: untyped): untyped =
|
assert wordsIdents.len == stintsIdents.len, "The number of loop variables and multiprecision integers t iterate on must be the same"
|
||||||
## Iterates over x and y, as an array of words.
|
|
||||||
## Input:
|
|
||||||
## - x, y: The multiprecision ints
|
|
||||||
## - If endianness should be taken into account for iteratio order.
|
|
||||||
## If yes, iteration is done from most significant word to least significant.
|
|
||||||
## Otherwise it is done in memory layout order.
|
|
||||||
## - loopBody: the operation you want to do on each word of n
|
|
||||||
let
|
|
||||||
optim_type = optimUint(x)
|
|
||||||
idx = ident("idx_asWordsRawZip")
|
|
||||||
var
|
|
||||||
inner_x, inner_y: NimNode
|
|
||||||
to_replace = nnkBracket.newTree
|
|
||||||
replacing = nnkBracket.newTree
|
|
||||||
|
|
||||||
to_replace.add x
|
# 3. Get the body and pass the bucket to a typed macro
|
||||||
to_replace.add y
|
# + 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()
|
||||||
|
|
||||||
if optim_type.isUint:
|
result = quote do: asWordsIterate(`wordsIdents`, `sid0`, `sid1`, `sid2`, `body`)
|
||||||
# We directly castx and y
|
|
||||||
inner_x = quote do:
|
|
||||||
cast[`optim_type`](`x`)
|
|
||||||
inner_y = quote do:
|
|
||||||
cast[`optim_type`](`y`)
|
|
||||||
|
|
||||||
replacing.add inner_x
|
|
||||||
replacing.add inner_y
|
|
||||||
else:
|
|
||||||
# If we have an array of words, inner_x and inner_y is are loop intermediate variable
|
|
||||||
inner_x = ident("x_asWordsRawZip")
|
|
||||||
inner_y = ident("y_asWordsRawZip")
|
|
||||||
|
|
||||||
# We replace the inner loop with the inner_x[idx]
|
|
||||||
replacing.add quote do:
|
|
||||||
`inner_x`[`idx`]
|
|
||||||
replacing.add quote do:
|
|
||||||
`inner_y`[`idx`]
|
|
||||||
|
|
||||||
let replacedAST = replaceNodes(loopBody, replacing, to_replace)
|
|
||||||
|
|
||||||
if optim_type.isUint:
|
|
||||||
result = replacedAST
|
|
||||||
else:
|
|
||||||
if ignoreEndianness or system.cpuEndian == bigEndian:
|
|
||||||
result = quote do:
|
|
||||||
{.pragma: restrict, codegenDecl: "$# __restrict $#".}
|
|
||||||
let
|
|
||||||
`inner_x`{.restrict.} = cast[ptr `optim_type`](`x`.unsafeaddr)
|
|
||||||
`inner_y`{.restrict.} = cast[ptr `optim_type`](`y`.unsafeaddr)
|
|
||||||
for `idx` in 0 ..< `inner_x`[].len:
|
|
||||||
`replacedAST`
|
|
||||||
else:
|
|
||||||
# Little-Endian, iteration in reverse
|
|
||||||
result = quote do:
|
|
||||||
{.pragma: restrict, codegenDecl: "$# __restrict $#".}
|
|
||||||
let
|
|
||||||
`inner_x`{.restrict.} = cast[ptr `optim_type`](`x`.unsafeaddr)
|
|
||||||
`inner_y`{.restrict.} = cast[ptr `optim_type`](`y`.unsafeaddr)
|
|
||||||
for `idx` in countdown(`inner_x`[].len - 1, 0):
|
|
||||||
`replacedAST`
|
|
||||||
|
|
||||||
macro m_asWordsZip*[T: UintImpl or IntImpl](m: var T, x: T,
|
|
||||||
ignoreEndianness: static[bool], loopBody: untyped): untyped =
|
|
||||||
## Iterates over a mutable int m and x as an array of words.
|
|
||||||
## returning a !! Pointer !! of the proper type to m.
|
|
||||||
## Input:
|
|
||||||
## - m: A mutable array
|
|
||||||
## - x: The multiprecision ints
|
|
||||||
## - If endianness should be taken into account for iteratio order.
|
|
||||||
## If yes, iteration is done from most significant word to least significant.
|
|
||||||
## Otherwise it is done in memory layout order.
|
|
||||||
## - loopBody: the operation you want to do on each word of n
|
|
||||||
let
|
|
||||||
optim_type = optimUint(x)
|
|
||||||
idx = ident("idx_asWordsRawZip")
|
|
||||||
var
|
|
||||||
inner_m, inner_x: NimNode
|
|
||||||
to_replace = nnkBracket.newTree
|
|
||||||
replacing = nnkBracket.newTree
|
|
||||||
|
|
||||||
to_replace.add m
|
|
||||||
to_replace.add x
|
|
||||||
|
|
||||||
if optim_type.isUint:
|
|
||||||
# We directly cast m and x
|
|
||||||
inner_m = quote do:
|
|
||||||
cast[var `optim_type`](`m`.addr)
|
|
||||||
inner_x = quote do:
|
|
||||||
cast[`optim_type`](`x`)
|
|
||||||
|
|
||||||
replacing.add inner_m
|
|
||||||
replacing.add inner_x
|
|
||||||
else:
|
|
||||||
# If we have an array of words, inner_x and inner_y is are loop intermediate variable
|
|
||||||
inner_m = ident("m_asWordsRawZip")
|
|
||||||
inner_x = ident("x_asWordsRawZip")
|
|
||||||
|
|
||||||
# We replace the inner loop with the inner_x[idx]
|
|
||||||
replacing.add quote do:
|
|
||||||
`inner_m`[`idx`]
|
|
||||||
replacing.add quote do:
|
|
||||||
`inner_x`[`idx`]
|
|
||||||
|
|
||||||
let replacedAST = replaceNodes(loopBody, replacing, to_replace)
|
|
||||||
|
|
||||||
if optim_type.isUint:
|
|
||||||
result = replacedAST
|
|
||||||
else:
|
|
||||||
if ignoreEndianness or system.cpuEndian == bigEndian:
|
|
||||||
result = quote do:
|
|
||||||
{.pragma: restrict, codegenDecl: "$# __restrict $#".}
|
|
||||||
let
|
|
||||||
`inner_m`{.restrict.} = cast[ptr `optim_type`](`m`.addr)
|
|
||||||
`inner_x`{.restrict.} = cast[ptr `optim_type`](`x`.unsafeaddr)
|
|
||||||
for `idx` in 0 ..< `inner_x`[].len:
|
|
||||||
`replacedAST`
|
|
||||||
else:
|
|
||||||
# Little-Endian, iteration in reverse
|
|
||||||
result = quote do:
|
|
||||||
{.pragma: restrict, codegenDecl: "$# __restrict $#".}
|
|
||||||
let
|
|
||||||
`inner_m`{.restrict.} = cast[ptr `optim_type`](`m`.addr)
|
|
||||||
`inner_x`{.restrict.} = cast[ptr `optim_type`](`x`.unsafeaddr)
|
|
||||||
for `idx` in countdown(`inner_x`[].len - 1, 0):
|
|
||||||
`replacedAST`
|
|
||||||
|
|
||||||
|
|
||||||
macro m_asWordsZip*[T: UintImpl or IntImpl](m: var T, x, y: T,
|
|
||||||
ignoreEndianness: static[bool], loopBody: untyped): untyped =
|
|
||||||
## Iterates over a mutable int m and x as an array of words.
|
|
||||||
## returning a !! Pointer !! of the proper type to m.
|
|
||||||
## Input:
|
|
||||||
## - m: A mutable array
|
|
||||||
## - x: The multiprecision ints
|
|
||||||
## - If endianness should be taken into account for iteratio order.
|
|
||||||
## If yes, iteration is done from most significant word to least significant.
|
|
||||||
## Otherwise it is done in memory layout order.
|
|
||||||
## - loopBody: the operation you want to do on each word of n
|
|
||||||
let
|
|
||||||
optim_type = optimUint(x)
|
|
||||||
idx = ident("idx_asWordsRawZip")
|
|
||||||
var
|
|
||||||
inner_m, inner_x, inner_y: NimNode
|
|
||||||
to_replace = nnkBracket.newTree
|
|
||||||
replacing = nnkBracket.newTree
|
|
||||||
|
|
||||||
to_replace.add m
|
|
||||||
to_replace.add x
|
|
||||||
to_replace.add y
|
|
||||||
|
|
||||||
if optim_type.isUint:
|
|
||||||
# We directly cast m, x and y
|
|
||||||
inner_m = quote do:
|
|
||||||
cast[var `optim_type`](`m`.addr)
|
|
||||||
inner_x = quote do:
|
|
||||||
cast[`optim_type`](`x`)
|
|
||||||
inner_y = quote do:
|
|
||||||
cast[`optim_type`](`y`)
|
|
||||||
|
|
||||||
replacing.add inner_m
|
|
||||||
replacing.add inner_x
|
|
||||||
replacing.add inner_y
|
|
||||||
else:
|
|
||||||
# If we have an array of words, inner_x and inner_y is are loop intermediate variable
|
|
||||||
inner_m = ident("m_asWordsRawZip")
|
|
||||||
inner_x = ident("x_asWordsRawZip")
|
|
||||||
inner_y = ident("y_asWordsRawZip")
|
|
||||||
|
|
||||||
# We replace the inner loop with the inner_x[idx]
|
|
||||||
replacing.add quote do:
|
|
||||||
`inner_m`[`idx`]
|
|
||||||
replacing.add quote do:
|
|
||||||
`inner_x`[`idx`]
|
|
||||||
replacing.add quote do:
|
|
||||||
`inner_y`[`idx`]
|
|
||||||
|
|
||||||
let replacedAST = replaceNodes(loopBody, replacing, to_replace)
|
|
||||||
|
|
||||||
# Arrays are in the form (`[]`, array, type)
|
|
||||||
if optim_type.isUint:
|
|
||||||
result = replacedAST
|
|
||||||
else:
|
|
||||||
if ignoreEndianness or system.cpuEndian == bigEndian:
|
|
||||||
result = quote do:
|
|
||||||
{.pragma: restrict, codegenDecl: "$# __restrict $#".}
|
|
||||||
let
|
|
||||||
`inner_m`{.restrict.} = cast[ptr `optim_type`](`m`.addr)
|
|
||||||
`inner_x`{.restrict.} = cast[ptr `optim_type`](`x`.unsafeaddr)
|
|
||||||
`inner_y`{.restrict.} = cast[ptr `optim_type`](`y`.unsafeaddr)
|
|
||||||
for `idx` in 0 ..< `inner_x`[].len:
|
|
||||||
`replacedAST`
|
|
||||||
else:
|
|
||||||
# Little-Endian, iteration in reverse
|
|
||||||
result = quote do:
|
|
||||||
{.pragma: restrict, codegenDecl: "$# __restrict $#".}
|
|
||||||
let
|
|
||||||
`inner_m`{.restrict.} = cast[ptr `optim_type`](`m`.addr)
|
|
||||||
`inner_x`{.restrict.} = cast[ptr `optim_type`](`x`.unsafeaddr)
|
|
||||||
`inner_y`{.restrict.} = cast[ptr `optim_type`](`y`.unsafeaddr)
|
|
||||||
for `idx` in countdown(`inner_x`[].len - 1, 0):
|
|
||||||
`replacedAST`
|
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
#
|
#
|
||||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||||
|
|
||||||
import ./datatypes, stdlib_bitops, as_signed_words
|
import ./datatypes, stdlib_bitops, as_words
|
||||||
export stdlib_bitops
|
export stdlib_bitops
|
||||||
|
|
||||||
# We reuse bitops from Nim standard lib, and expand it for multi-precision int.
|
# We reuse bitops from Nim standard lib, and expand it for multi-precision int.
|
||||||
@ -25,7 +25,7 @@ func countLeadingZeroBits*(n: UintImpl): int {.inline.} =
|
|||||||
n.lo.countLeadingZeroBits + maxHalfRepr
|
n.lo.countLeadingZeroBits + maxHalfRepr
|
||||||
else: hi_clz
|
else: hi_clz
|
||||||
|
|
||||||
func msb*[T: SomeInteger](n: T): T {.inline.}=
|
func isMsbSet*[T: SomeInteger](n: T): bool {.inline.}=
|
||||||
## Returns the most significant bit of an integer.
|
## Returns the most significant bit of an integer.
|
||||||
|
|
||||||
when T is int64 or (T is int and sizeof(int) == 8):
|
when T is int64 or (T is int and sizeof(int) == 8):
|
||||||
@ -40,9 +40,8 @@ func msb*[T: SomeInteger](n: T): T {.inline.}=
|
|||||||
type Uint = T
|
type Uint = T
|
||||||
|
|
||||||
const msb_pos = sizeof(T) * 8 - 1
|
const msb_pos = sizeof(T) * 8 - 1
|
||||||
result = T(cast[Uint](n) shr msb_pos)
|
result = bool(cast[Uint](n) shr msb_pos)
|
||||||
|
|
||||||
func msb*(n: IntImpl): auto {.inline.}=
|
func isMsbSet*(n: UintImpl or IntImpl): bool {.inline.}=
|
||||||
## Returns the most significant bit of an arbitrary precision integer.
|
## Returns the most significant bit of an arbitrary precision integer.
|
||||||
|
result = isMsbSet most_significant_word(n)
|
||||||
result = msb most_significant_word(n)
|
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
#
|
#
|
||||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||||
|
|
||||||
import ./datatypes, ./conversion, ./as_signed_words
|
import ./datatypes, ./conversion, ./int_comparison
|
||||||
|
|
||||||
func `+`*(x, y: IntImpl): IntImpl {.inline.}=
|
func `+`*(x, y: IntImpl): IntImpl {.inline.}=
|
||||||
# Addition for multi-precision signed int.
|
# Addition for multi-precision signed int.
|
||||||
@ -17,8 +17,8 @@ func `+`*(x, y: IntImpl): IntImpl {.inline.}=
|
|||||||
|
|
||||||
when compileOption("boundChecks"):
|
when compileOption("boundChecks"):
|
||||||
if unlikely(
|
if unlikely(
|
||||||
((result.most_significant_word xor x.most_significant_word) >= 0) or
|
not(result.isNegative xor x.isNegative) or
|
||||||
((result.most_significant_word xor y.most_significant_word) >= 0)
|
not(result.isNegative xor y.isNegative)
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
raise newException(OverflowError, "Addition overflow")
|
raise newException(OverflowError, "Addition overflow")
|
||||||
@ -36,8 +36,8 @@ func `-`*(x, y: IntImpl): IntImpl {.inline.}=
|
|||||||
|
|
||||||
when compileOption("boundChecks"):
|
when compileOption("boundChecks"):
|
||||||
if unlikely(
|
if unlikely(
|
||||||
((result.most_significant_word xor x.most_significant_word) >= 0) or
|
not(result.isNegative xor x.isNegative) or
|
||||||
((result.most_significant_word xor (not y).most_significant_word) >= 0)
|
not(result.isNegative xor y.isNegative.not)
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
raise newException(OverflowError, "Substraction underflow")
|
raise newException(OverflowError, "Substraction underflow")
|
||||||
|
@ -11,20 +11,20 @@ import ./datatypes, ./as_words
|
|||||||
|
|
||||||
func `not`*(x: IntImpl): IntImpl {.inline.}=
|
func `not`*(x: IntImpl): IntImpl {.inline.}=
|
||||||
## Bitwise complement of unsigned integer x
|
## Bitwise complement of unsigned integer x
|
||||||
m_asWordsZip(result, x, ignoreEndianness = true):
|
for wr, wx in asWords(result, x):
|
||||||
result = not x
|
wr = not wx
|
||||||
|
|
||||||
func `or`*(x, y: IntImpl): IntImpl {.inline.}=
|
func `or`*(x, y: IntImpl): IntImpl {.inline.}=
|
||||||
## `Bitwise or` of numbers x and y
|
## `Bitwise or` of numbers x and y
|
||||||
m_asWordsZip(result, x, y, ignoreEndianness = true):
|
for wr, wx, wy in asWords(result, x, y):
|
||||||
result = x or y
|
wr = wx or wy
|
||||||
|
|
||||||
func `and`*(x, y: IntImpl): IntImpl {.inline.}=
|
func `and`*(x, y: IntImpl): IntImpl {.inline.}=
|
||||||
## `Bitwise and` of numbers x and y
|
## `Bitwise and` of numbers x and y
|
||||||
m_asWordsZip(result, x, y, ignoreEndianness = true):
|
for wr, wx, wy in asWords(result, x, y):
|
||||||
result = x and y
|
wr = wx and wy
|
||||||
|
|
||||||
func `xor`*(x, y: IntImpl): IntImpl {.inline.}=
|
func `xor`*(x, y: IntImpl): IntImpl {.inline.}=
|
||||||
## `Bitwise xor` of numbers x and y
|
## `Bitwise xor` of numbers x and y
|
||||||
m_asWordsZip(result, x, y, ignoreEndianness = true):
|
for wr, wx, wy in asWords(result, x, y):
|
||||||
result = x xor y
|
wr = wx xor wy
|
||||||
|
@ -14,14 +14,14 @@ func isZero*(n: SomeSignedInt): bool {.inline.} =
|
|||||||
n == 0
|
n == 0
|
||||||
|
|
||||||
func isZero*(n: IntImpl): bool {.inline.} =
|
func isZero*(n: IntImpl): bool {.inline.} =
|
||||||
asWords(n, ignoreEndianness = true):
|
for word in asWords(n):
|
||||||
if n != 0:
|
if word != 0:
|
||||||
return false
|
return false
|
||||||
return true
|
return true
|
||||||
|
|
||||||
func isNegative*(n: IntImpl): bool {.inline.} =
|
func isNegative*(n: IntImpl): bool {.inline.} =
|
||||||
## Returns true if a number is negative:
|
## Returns true if a number is negative:
|
||||||
n.msb.bool
|
n.isMsbSet
|
||||||
|
|
||||||
func `<`*(x, y: IntImpl): bool {.inline.}=
|
func `<`*(x, y: IntImpl): bool {.inline.}=
|
||||||
# Lower comparison for multi-precision integers
|
# Lower comparison for multi-precision integers
|
||||||
@ -32,8 +32,8 @@ func `<`*(x, y: IntImpl): bool {.inline.}=
|
|||||||
|
|
||||||
func `==`*(x, y: IntImpl): bool {.inline.}=
|
func `==`*(x, y: IntImpl): bool {.inline.}=
|
||||||
# Equal comparison for multi-precision integers
|
# Equal comparison for multi-precision integers
|
||||||
asWordsZip(x, y, ignoreEndianness = true):
|
for wx, wy in asWords(x, y):
|
||||||
if x != y:
|
if wx != wy:
|
||||||
return false
|
return false
|
||||||
return true # they're equal
|
return true # they're equal
|
||||||
|
|
||||||
|
@ -12,23 +12,23 @@ import ./datatypes, ./as_words
|
|||||||
|
|
||||||
func `not`*(x: UintImpl): UintImpl {.inline.}=
|
func `not`*(x: UintImpl): UintImpl {.inline.}=
|
||||||
## Bitwise complement of unsigned integer x
|
## Bitwise complement of unsigned integer x
|
||||||
m_asWordsZip(result, x, ignoreEndianness = true):
|
for wr, wx in asWords(result, x):
|
||||||
result = not x
|
wr = not wx
|
||||||
|
|
||||||
func `or`*(x, y: UintImpl): UintImpl {.inline.}=
|
func `or`*(x, y: UintImpl): UintImpl {.inline.}=
|
||||||
## `Bitwise or` of numbers x and y
|
## `Bitwise or` of numbers x and y
|
||||||
m_asWordsZip(result, x, y, ignoreEndianness = true):
|
for wr, wx, wy in asWords(result, x, y):
|
||||||
result = x or y
|
wr = wx or wy
|
||||||
|
|
||||||
func `and`*(x, y: UintImpl): UintImpl {.inline.}=
|
func `and`*(x, y: UintImpl): UintImpl {.inline.}=
|
||||||
## `Bitwise and` of numbers x and y
|
## `Bitwise and` of numbers x and y
|
||||||
m_asWordsZip(result, x, y, ignoreEndianness = true):
|
for wr, wx, wy in asWords(result, x, y):
|
||||||
result = x and y
|
wr = wx and wy
|
||||||
|
|
||||||
func `xor`*(x, y: UintImpl): UintImpl {.inline.}=
|
func `xor`*(x, y: UintImpl): UintImpl {.inline.}=
|
||||||
## `Bitwise xor` of numbers x and y
|
## `Bitwise xor` of numbers x and y
|
||||||
m_asWordsZip(result, x, y, ignoreEndianness = true):
|
for wr, wx, wy in asWords(result, x, y):
|
||||||
result = x xor y
|
wr = wx xor wy
|
||||||
|
|
||||||
func `shr`*(x: UintImpl, y: SomeInteger): UintImpl {.inline.}
|
func `shr`*(x: UintImpl, y: SomeInteger): UintImpl {.inline.}
|
||||||
# Forward declaration
|
# Forward declaration
|
||||||
@ -37,8 +37,7 @@ func `shl`*(x: UintImpl, y: SomeInteger): UintImpl {.inline.}=
|
|||||||
## Compute the `shift left` operation of x and y
|
## Compute the `shift left` operation of x and y
|
||||||
# Note: inlining this poses codegen/aliasing issue when doing `x = x shl 1`
|
# Note: inlining this poses codegen/aliasing issue when doing `x = x shl 1`
|
||||||
|
|
||||||
# TODO: would it be better to reimplement this using an array of bytes/uint64
|
# TODO: would it be better to reimplement this with words iteration?
|
||||||
# That opens up to endianness issues.
|
|
||||||
const halfSize: type(y) = getSize(x) div 2
|
const halfSize: type(y) = getSize(x) div 2
|
||||||
|
|
||||||
if y == 0:
|
if y == 0:
|
||||||
@ -64,4 +63,3 @@ func `shr`*(x: UintImpl, y: SomeInteger): UintImpl {.inline.}=
|
|||||||
result.hi = x.hi shr y
|
result.hi = x.hi shr y
|
||||||
else:
|
else:
|
||||||
result.lo = x.hi shr (y - halfSize)
|
result.lo = x.hi shr (y - halfSize)
|
||||||
|
|
||||||
|
@ -13,30 +13,30 @@ func isZero*(n: SomeUnsignedInt): bool {.inline.} =
|
|||||||
n == 0
|
n == 0
|
||||||
|
|
||||||
func isZero*(n: UintImpl): bool {.inline.} =
|
func isZero*(n: UintImpl): bool {.inline.} =
|
||||||
asWords(n, ignoreEndianness = true):
|
for word in asWords(n):
|
||||||
if n != 0:
|
if word != 0:
|
||||||
return false
|
return false
|
||||||
return true
|
return true
|
||||||
|
|
||||||
func `<`*(x, y: UintImpl): bool {.inline.}=
|
func `<`*(x, y: UintImpl): bool {.inline.}=
|
||||||
# Lower comparison for multi-precision integers
|
# Lower comparison for multi-precision integers
|
||||||
asWordsZip(x, y, ignoreEndianness = false):
|
for wx, wy in asWords(x, y):
|
||||||
if x != y:
|
if wx != wy:
|
||||||
return x < y
|
return wx < wy
|
||||||
return false # they're equal
|
return false # they're equal
|
||||||
|
|
||||||
func `==`*(x, y: UintImpl): bool {.inline.}=
|
func `==`*(x, y: UintImpl): bool {.inline.}=
|
||||||
# Equal comparison for multi-precision integers
|
# Equal comparison for multi-precision integers
|
||||||
asWordsZip(x, y, ignoreEndianness = true):
|
for wx, wy in asWords(x, y):
|
||||||
if x != y:
|
if wx != wy:
|
||||||
return false
|
return false
|
||||||
return true # they're equal
|
return true # they're equal
|
||||||
|
|
||||||
func `<=`*(x, y: UintImpl): bool {.inline.}=
|
func `<=`*(x, y: UintImpl): bool {.inline.}=
|
||||||
# Lower or equal comparison for multi-precision integers
|
# Lower or equal comparison for multi-precision integers
|
||||||
asWordsZip(x, y, ignoreEndianness = false):
|
for wx, wy in asWords(x, y):
|
||||||
if x != y:
|
if wx != wy:
|
||||||
return x < y
|
return wx < wy
|
||||||
return true # they're equal
|
return true # they're equal
|
||||||
|
|
||||||
func isOdd*(x: UintImpl): bool {.inline.}=
|
func isOdd*(x: UintImpl): bool {.inline.}=
|
||||||
|
Loading…
x
Reference in New Issue
Block a user