From f53d420648323fa0086ad6154467c18d6be4c014 Mon Sep 17 00:00:00 2001 From: mratsim Date: Sun, 17 Jun 2018 15:48:57 +0200 Subject: [PATCH 1/3] Initial commit - allow all ops at compile-time on uint. --- stint/private/as_signed_words.nim | 6 +- stint/private/as_words.nim | 438 ++++++++++------------------- stint/private/bithacks.nim | 11 +- stint/private/int_addsub.nim | 10 +- stint/private/int_bitwise_ops.nim | 16 +- stint/private/int_comparison.nim | 10 +- stint/private/uint_bitwise_ops.nim | 20 +- stint/private/uint_comparison.nim | 20 +- 8 files changed, 200 insertions(+), 331 deletions(-) diff --git a/stint/private/as_signed_words.nim b/stint/private/as_signed_words.nim index 3a225dc..51fae81 100644 --- a/stint/private/as_signed_words.nim +++ b/stint/private/as_signed_words.nim @@ -39,7 +39,7 @@ proc isInt*(x: NimNode): static[bool] = elif eqIdent(x, "int8"): true else: false -macro most_significant_word*(x: IntImpl): untyped = +macro most_significant_word_signed*(x: IntImpl): untyped = let optim_type = optimInt(x) if optim_type.isInt: @@ -87,8 +87,8 @@ macro asSignedWordsZip*[T]( first_y = quote do: cast[`optim_type`](`y`) else: - first_x = getAST(most_significant_word(x)) - first_y = getAST(most_significant_word(y)) + first_x = getAST(most_significant_word_signed(x)) + first_y = getAST(most_significant_word_signed(y)) replacing.add first_x replacing.add first_y diff --git a/stint/private/as_words.nim b/stint/private/as_words.nim index 2181fda..97dfad6 100644 --- a/stint/private/as_words.nim +++ b/stint/private/as_words.nim @@ -9,33 +9,52 @@ import ./datatypes, macros -proc optimUint(x: NimNode): NimNode = - let size = getSize(x) +# ######################################################################### +# Multi-precision ints to compile-time array of words - if size > 64: - result = quote do: - array[`size` div 64, uint64] - elif size == 64: - result = quote do: - uint64 - elif size == 32: - result = quote do: - uint32 - elif size == 16: - result = quote do: - uint16 - elif size == 8: - result = quote do: - uint8 +proc asWordsImpl(x: NimNode, current_path: NimNode, result: var NimNode) = + ## Transforms an UintImpl/IntImpl into an array of words + ## at compile-time. Recursve 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: - error "Unreachable path reached" + result.add current_path + +# ######################################################################### +# Accessors -proc isUint(x: NimNode): static[bool] = - if eqIdent(x, "uint64"): true - elif eqIdent(x, "uint32"): true - elif eqIdent(x, "uint16"): true - elif eqIdent(x, "uint8"): true - else: false +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 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 = # Args: @@ -60,263 +79,116 @@ proc replaceNodes*(ast: NimNode, replacing: NimNode, to_replace: NimNode): NimNo return rTree result = inspect(ast) -proc least_significant_two_words*(x: NimNode): tuple[lo, hi: NimNode] = - var node = x.getTypeInst - var result_lo = x - - while node.kind == nnkBracketExpr: - assert eqIdent(node[0], "UintImpl") or eqIdent(node[0], "IntImpl"), ( - "least_significant_word only supports primitive integers, Stint and Stuint") - result_lo = quote do: `result_lo`.lo - node = node[1] - - var result_hi = result_lo.copyNimTree # ⚠ Aliasing: NimNodes are ref objects - result_hi[1] = newIdentNode("hi") # replace the last lo by hi - result = (result_lo, result_hi) - -macro second_least_significant_word*(x: UintImpl or IntImpl): untyped = - result = least_significant_two_words(x).hi - -macro least_significant_word*(x: UintImpl or IntImpl): untyped = - result = least_significant_two_words(x).lo - -macro asWords*(n: UintImpl or IntImpl, ignoreEndianness: static[bool], loopBody: untyped): untyped = - ## Iterates over n, as an array of words. - ## Input: - ## - n: The Multiprecision int - ## - 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(n) - var - inner_n: NimNode - to_replace = nnkBracket.newTree - replacing = nnkBracket.newTree - - if optim_type.isUint: - # We directly cast n - inner_n = quote do: - 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 - replacing.add inner_n - - let replacedAST = replaceNodes(loopBody, replacing, to_replace) - - if optim_type.isUint: - result = replacedAST - else: - if ignoreEndianness or system.cpuEndian == bigEndian: - result = quote do: - for `inner_n` in cast[`optim_type`](`n`): - `replacedAST` - else: - assert false, "Not implemented" - -macro asWordsZip*(x, y: UintImpl or IntImpl, ignoreEndianness: static[bool], loopBody: untyped): untyped = - ## 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 - to_replace.add y - - if optim_type.isUint: - # 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`] +### 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 define it for a fixed 3 args and will just ignore what is not used - 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` +# macro asWordsIterate(wordsIdents: untyped, stintsIdents: varargs[typed], loopBody: untyped): untyped = +# +# assert wordsIdents.len == stintsIdents.len +# +# result = newStmtList() +# +# # 1. Get the words of each stint +# var words = nnkBracket.newTree +# for ident in stintsIdents: +# var wordList = nnkBracket.newTree +# asWordsImpl(ident, ident, 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 +# NbStints = wordsIdents.len +# NbWords = words[0].len +# +# for currDepth in 0 ..< NbWords: +# var replacing = nnkBracket.newTree +# for currStint in 0 ..< NbStints: +# replacing.add words[currStint][currDepth] +# +# let body = replaceNodes(loopBody, replacing, to_replace = wordsIdents) +# result.add quote do: +# block: `body` + +macro asWordsIterate(wordsIdents: untyped, sid0, sid1, sid2: typed, loopBody: untyped): untyped = + + 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: + replacing.add words[currStint][currDepth] + + let body = replaceNodes(loopBody, replacing, to_replace = wordsIdents) + result.add quote do: + block: `body` + +macro asWords*(x: ForLoopStmt): untyped = + ## This unrolls the body of the for loop + ## and applies it for each word. + ## + ## TODO: allow an ignoreEndianness and signed parameter + + # ##### 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 + # + Workaround 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() + + result = quote do: asWordsIterate(`wordsIdents`, `sid0`, `sid1`, `sid2`, `body`) diff --git a/stint/private/bithacks.nim b/stint/private/bithacks.nim index 11694db..0285a65 100644 --- a/stint/private/bithacks.nim +++ b/stint/private/bithacks.nim @@ -7,7 +7,7 @@ # # 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 # 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 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. 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 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. - - result = msb most_significant_word(n) + result = isMsbSet most_significant_word(n) diff --git a/stint/private/int_addsub.nim b/stint/private/int_addsub.nim index 4a0df5b..acbf5b0 100644 --- a/stint/private/int_addsub.nim +++ b/stint/private/int_addsub.nim @@ -7,7 +7,7 @@ # # 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.}= # Addition for multi-precision signed int. @@ -17,8 +17,8 @@ func `+`*(x, y: IntImpl): IntImpl {.inline.}= when compileOption("boundChecks"): if unlikely( - ((result.most_significant_word xor x.most_significant_word) >= 0) or - ((result.most_significant_word xor y.most_significant_word) >= 0) + not(result.isNegative xor x.isNegative) or + not(result.isNegative xor y.isNegative) ): return raise newException(OverflowError, "Addition overflow") @@ -36,8 +36,8 @@ func `-`*(x, y: IntImpl): IntImpl {.inline.}= when compileOption("boundChecks"): if unlikely( - ((result.most_significant_word xor x.most_significant_word) >= 0) or - ((result.most_significant_word xor (not y).most_significant_word) >= 0) + not(result.isNegative xor x.isNegative) or + not(result.isNegative xor y.isNegative.not) ): return raise newException(OverflowError, "Substraction underflow") diff --git a/stint/private/int_bitwise_ops.nim b/stint/private/int_bitwise_ops.nim index 8fd047e..5adbea0 100644 --- a/stint/private/int_bitwise_ops.nim +++ b/stint/private/int_bitwise_ops.nim @@ -11,20 +11,20 @@ import ./datatypes, ./as_words func `not`*(x: IntImpl): IntImpl {.inline.}= ## Bitwise complement of unsigned integer x - m_asWordsZip(result, x, ignoreEndianness = true): - result = not x + for wr, wx in asWords(result, x): + wr = not wx func `or`*(x, y: IntImpl): IntImpl {.inline.}= ## `Bitwise or` of numbers x and y - m_asWordsZip(result, x, y, ignoreEndianness = true): - result = x or y + for wr, wx, wy in asWords(result, x, y): + wr = wx or wy func `and`*(x, y: IntImpl): IntImpl {.inline.}= ## `Bitwise and` of numbers x and y - m_asWordsZip(result, x, y, ignoreEndianness = true): - result = x and y + for wr, wx, wy in asWords(result, x, y): + wr = wx and wy func `xor`*(x, y: IntImpl): IntImpl {.inline.}= ## `Bitwise xor` of numbers x and y - m_asWordsZip(result, x, y, ignoreEndianness = true): - result = x xor y + for wr, wx, wy in asWords(result, x, y): + wr = wx xor wy diff --git a/stint/private/int_comparison.nim b/stint/private/int_comparison.nim index 0f1bdb7..165b806 100644 --- a/stint/private/int_comparison.nim +++ b/stint/private/int_comparison.nim @@ -14,14 +14,14 @@ func isZero*(n: SomeSignedInt): bool {.inline.} = n == 0 func isZero*(n: IntImpl): bool {.inline.} = - asWords(n, ignoreEndianness = true): - if n != 0: + for word in asWords(n): + if word != 0: return false return true func isNegative*(n: IntImpl): bool {.inline.} = ## Returns true if a number is negative: - n.msb.bool + n.isMsbSet func `<`*(x, y: IntImpl): bool {.inline.}= # Lower comparison for multi-precision integers @@ -32,8 +32,8 @@ func `<`*(x, y: IntImpl): bool {.inline.}= func `==`*(x, y: IntImpl): bool {.inline.}= # Equal comparison for multi-precision integers - asWordsZip(x, y, ignoreEndianness = true): - if x != y: + for wx, wy in asWords(x, y): + if wx != wy: return false return true # they're equal diff --git a/stint/private/uint_bitwise_ops.nim b/stint/private/uint_bitwise_ops.nim index b7db906..23684ea 100644 --- a/stint/private/uint_bitwise_ops.nim +++ b/stint/private/uint_bitwise_ops.nim @@ -12,23 +12,23 @@ import ./datatypes, ./as_words func `not`*(x: UintImpl): UintImpl {.inline.}= ## Bitwise complement of unsigned integer x - m_asWordsZip(result, x, ignoreEndianness = true): - result = not x + for wr, wx in asWords(result, x): + wr = not wx func `or`*(x, y: UintImpl): UintImpl {.inline.}= ## `Bitwise or` of numbers x and y - m_asWordsZip(result, x, y, ignoreEndianness = true): - result = x or y + for wr, wx, wy in asWords(result, x, y): + wr = wx or wy func `and`*(x, y: UintImpl): UintImpl {.inline.}= ## `Bitwise and` of numbers x and y - m_asWordsZip(result, x, y, ignoreEndianness = true): - result = x and y + for wr, wx, wy in asWords(result, x, y): + wr = wx and wy func `xor`*(x, y: UintImpl): UintImpl {.inline.}= ## `Bitwise xor` of numbers x and y - m_asWordsZip(result, x, y, ignoreEndianness = true): - result = x xor y + for wr, wx, wy in asWords(result, x, y): + wr = wx xor wy func `shr`*(x: UintImpl, y: SomeInteger): UintImpl {.inline.} # Forward declaration @@ -37,8 +37,7 @@ func `shl`*(x: UintImpl, y: SomeInteger): UintImpl {.inline.}= ## Compute the `shift left` operation of x and y # 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 - # That opens up to endianness issues. + # TODO: would it be better to reimplement this with words iteration? const halfSize: type(y) = getSize(x) div 2 if y == 0: @@ -64,4 +63,3 @@ func `shr`*(x: UintImpl, y: SomeInteger): UintImpl {.inline.}= result.hi = x.hi shr y else: result.lo = x.hi shr (y - halfSize) - diff --git a/stint/private/uint_comparison.nim b/stint/private/uint_comparison.nim index 6bb5d69..cfbc5b5 100644 --- a/stint/private/uint_comparison.nim +++ b/stint/private/uint_comparison.nim @@ -13,30 +13,30 @@ func isZero*(n: SomeUnsignedInt): bool {.inline.} = n == 0 func isZero*(n: UintImpl): bool {.inline.} = - asWords(n, ignoreEndianness = true): - if n != 0: + for word in asWords(n): + if word != 0: return false return true func `<`*(x, y: UintImpl): bool {.inline.}= # Lower comparison for multi-precision integers - asWordsZip(x, y, ignoreEndianness = false): - if x != y: - return x < y + for wx, wy in asWords(x, y): + if wx != wy: + return wx < wy return false # they're equal func `==`*(x, y: UintImpl): bool {.inline.}= # Equal comparison for multi-precision integers - asWordsZip(x, y, ignoreEndianness = true): - if x != y: + for wx, wy in asWords(x, y): + if wx != wy: return false return true # they're equal func `<=`*(x, y: UintImpl): bool {.inline.}= # Lower or equal comparison for multi-precision integers - asWordsZip(x, y, ignoreEndianness = false): - if x != y: - return x < y + for wx, wy in asWords(x, y): + if wx != wy: + return wx < wy return true # they're equal func isOdd*(x: UintImpl): bool {.inline.}= From 10fc21f943be7e525b7f4c7d4e3b5bea05090df8 Mon Sep 17 00:00:00 2001 From: mratsim Date: Sun, 17 Jun 2018 16:51:01 +0200 Subject: [PATCH 2/3] Update benchmark --- benchmarks/bench_mod.nim | 14 ++++++++++---- stint/private/as_words.nim | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/benchmarks/bench_mod.nim b/benchmarks/bench_mod.nim index 95c0d07..0f79b68 100644 --- a/benchmarks/bench_mod.nim +++ b/benchmarks/bench_mod.nim @@ -1,4 +1,4 @@ -import ../stint/mpint, times +import ../stint, times # Warmup on normal int @@ -18,10 +18,10 @@ echo "Warmup: " & $(stop - start) & "s" start = cpuTime() block: - var foo = 123.u(256) + var foo = 123.u256 for i in 0 ..< 10_000_000: - foo += i.u(256) * i.u(256) mod 456.u(256) - foo = foo mod 789.u(256) + foo += i.u256 * i.u256 mod 456.u256 + foo = foo mod 789.u256 stop = cpuTime() echo "Library: " & $(stop - start) & "s" @@ -47,3 +47,9 @@ when defined(bench_ttmath): # Warmup: 0.04060799999999999s # Library: 0.9576759999999999s # 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 diff --git a/stint/private/as_words.nim b/stint/private/as_words.nim index 97dfad6..d3f3f1a 100644 --- a/stint/private/as_words.nim +++ b/stint/private/as_words.nim @@ -14,7 +14,7 @@ import ./datatypes, macros proc asWordsImpl(x: NimNode, current_path: NimNode, result: var NimNode) = ## Transforms an UintImpl/IntImpl into an array of words - ## at compile-time. Recursve implementation. + ## at compile-time. Recursive implementation. ## Result is from most significant word to least significant let node = x.getTypeInst From 3d1cb3f058c0ccfe5e1b8f0727e884cb6089ab4d Mon Sep 17 00:00:00 2001 From: mratsim Date: Sun, 17 Jun 2018 17:02:21 +0200 Subject: [PATCH 3/3] Delete commented ideal version of asWordsiterate, it is obvious from the workaround (and is tracked in the PR) --- stint/private/as_words.nim | 37 +++---------------------------------- 1 file changed, 3 insertions(+), 34 deletions(-) diff --git a/stint/private/as_words.nim b/stint/private/as_words.nim index d3f3f1a..78a5145 100644 --- a/stint/private/as_words.nim +++ b/stint/private/as_words.nim @@ -79,40 +79,9 @@ proc replaceNodes*(ast: NimNode, replacing: NimNode, to_replace: NimNode): NimNo return rTree result = inspect(ast) -### 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 define it for a fixed 3 args and will just ignore what is not used - -# macro asWordsIterate(wordsIdents: untyped, stintsIdents: varargs[typed], loopBody: untyped): untyped = -# -# assert wordsIdents.len == stintsIdents.len -# -# result = newStmtList() -# -# # 1. Get the words of each stint -# var words = nnkBracket.newTree -# for ident in stintsIdents: -# var wordList = nnkBracket.newTree -# asWordsImpl(ident, ident, 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 -# NbStints = wordsIdents.len -# NbWords = words[0].len -# -# for currDepth in 0 ..< NbWords: -# var replacing = nnkBracket.newTree -# for currStint in 0 ..< NbStints: -# replacing.add words[currStint][currDepth] -# -# let body = replaceNodes(loopBody, replacing, to_replace = wordsIdents) -# result.add quote do: -# block: `body` - macro asWordsIterate(wordsIdents: untyped, sid0, sid1, sid2: typed, 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 @@ -185,7 +154,7 @@ macro asWords*(x: ForLoopStmt): untyped = 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 - # + Workaround https://github.com/nim-lang/Nim/issues/7737 + # + 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()