Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

std/hashes: hash(ref|ptr|pointer) + other improvements #17731

Merged
merged 5 commits into from
Apr 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 49 additions & 23 deletions lib/pure/hashes.nim
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ proc hashData*(data: pointer, size: int): Hash =
var h: Hash = 0
when defined(js):
var p: cstring
asm """`p` = `Data`;"""
asm """`p` = `Data`"""
else:
var p = cast[cstring](data)
var i = 0
Expand All @@ -193,12 +193,22 @@ proc hashData*(data: pointer, size: int): Hash =
dec(s)
result = !$h

proc hashIdentity*[T: Ordinal|enum](x: T): Hash {.inline, since: (1, 3).} =
## The identity hash, i.e. `hashIdentity(x) = x`.
cast[Hash](ord(x))

when defined(nimIntHash1):
proc hash*[T: Ordinal|enum](x: T): Hash {.inline.} =
## Efficient hashing of integers.
cast[Hash](ord(x))
else:
proc hash*[T: Ordinal|enum](x: T): Hash {.inline.} =
## Efficient hashing of integers.
hashWangYi1(uint64(ord(x)))

when defined(js):
var objectID = 0

proc hash*(x: pointer): Hash {.inline.} =
## Efficient hashing of pointers.
when defined(js):
proc getObjectId(x: pointer): int =
asm """
if (typeof `x` == "object") {
if ("_NimID" in `x`)
Expand All @@ -209,29 +219,46 @@ proc hash*(x: pointer): Hash {.inline.} =
}
}
"""

proc hash*(x: pointer): Hash {.inline.} =
## Efficient `hash` overload.
when defined(js):
let y = getObjectId(x)
else:
result = cast[Hash](cast[uint](x) shr 3) # skip the alignment
let y = cast[int](x)
hash(y) # consistent with code expecting scrambled hashes depending on `nimIntHash1`.

proc hash*[T](x: ref[T] | ptr[T]): Hash {.inline.} =
## Efficient `hash` overload.
runnableExamples:
var a: array[10, uint8]
assert a[0].addr.hash != a[1].addr.hash
assert cast[pointer](a[0].addr).hash == a[0].addr.hash
runnableExamples:
type A = ref object
x: int
let a = A(x: 3)
let ha = a.hash
assert ha != A(x: 3).hash # A(x: 3) is a different ref object from `a`.
a.x = 4
assert ha == a.hash # the hash only depends on the address
runnableExamples:
# you can overload `hash` if you want to customize semantics
type A[T] = ref object
x, y: T
proc hash(a: A): Hash = hash(a.x)
assert A[int](x: 3, y: 4).hash == A[int](x: 3, y: 5).hash
# xxx pending bug #17733, merge as `proc hash*(pointer | ref | ptr): Hash`
# or `proc hash*[T: ref | ptr](x: T): Hash`
hash(cast[pointer](x))

proc hash*[T: proc](x: T): Hash {.inline.} =
## Efficient hashing of proc vars. Closures are supported too.
when T is "closure":
result = hash(rawProc(x)) !& hash(rawEnv(x))
result = hash((rawProc(x), rawEnv(x)))
else:
result = hash(pointer(x))

proc hashIdentity*[T: Ordinal|enum](x: T): Hash {.inline, since: (1, 3).} =
## The identity hash, i.e. `hashIdentity(x) = x`.
cast[Hash](ord(x))

when defined(nimIntHash1):
proc hash*[T: Ordinal|enum](x: T): Hash {.inline.} =
## Efficient hashing of integers.
cast[Hash](ord(x))
else:
proc hash*[T: Ordinal|enum](x: T): Hash {.inline.} =
## Efficient hashing of integers.
hashWangYi1(uint64(ord(x)))

proc hash*(x: float): Hash {.inline.} =
## Efficient hashing of floats.
let y = x + 0.0 # for denormalization
Expand Down Expand Up @@ -484,10 +511,9 @@ proc hashIgnoreCase*(sBuf: string, sPos, ePos: int): Hash =
h = h !& ord(c)
result = !$h


proc hash*[T: tuple | object](x: T): Hash =
## Efficient hashing of tuples and objects.
## There must be a `hash` proc defined for each of the field types.
## Efficient `hash` overload.
## `hash` must be defined for each component of `x`.
runnableExamples:
type Obj = object
x: int
Expand Down
4 changes: 4 additions & 0 deletions testament/lib/stdtest/testutils.nim
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,7 @@ template accept*(a) =

template reject*(a) =
doAssert not compiles(a)

template disableVm*(body) =
when nimvm: discard
else: body
14 changes: 14 additions & 0 deletions tests/collections/ttables.nim
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ And we get here
3
'''
joinable: false
targets: "c cpp js"
"""

# xxx wrap in a template to test in VM, see https://github.com/timotheecour/Nim/issues/534#issuecomment-769565033

import hashes, sequtils, tables, algorithm

proc sortedPairs[T](t: T): auto = toSeq(t.pairs).sorted
Expand Down Expand Up @@ -444,3 +448,13 @@ block emptyOrdered:
var t2: OrderedTable[int, string]
doAssert t1 == t2

block: # Table[ref, int]
type A = ref object
x: int
var t: OrderedTable[A, int]
let a1 = A(x: 3)
let a2 = A(x: 3)
t[a1] = 10
t[a2] = 11
doAssert t[a1] == 10
doAssert t[a2] == 11
22 changes: 21 additions & 1 deletion tests/stdlib/thashes.nim
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ discard """
"""

import std/hashes

from stdtest/testutils import disableVm, whenVMorJs

when not defined(js) and not defined(cpp):
block:
Expand Down Expand Up @@ -177,5 +177,25 @@ proc main() =
doAssert hash(Obj5(t: false, x: 1)) == hash(Obj5(t: true, y: 1))
doAssert hash(Obj5(t: false, x: 1)) != hash(Obj5(t: true, y: 2))

block: # hash(ref|ptr|pointer)
var a: array[10, uint8]
# disableVm:
whenVMorJs:
# pending fix proposed in https://github.com/nim-lang/Nim/issues/15952#issuecomment-786312417
discard
do:
assert a[0].addr.hash != a[1].addr.hash
assert cast[pointer](a[0].addr).hash == a[0].addr.hash

block: # hash(ref)
type A = ref object
x: int
let a = A(x: 3)
disableVm: # xxx Error: VM does not support 'cast' from tyRef to tyPointer
let ha = a.hash
assert ha != A(x: 3).hash # A(x: 3) is a different ref object from `a`.
a.x = 4
assert ha == a.hash # the hash only depends on the address

static: main()
main()
6 changes: 1 addition & 5 deletions tests/stdlib/tstrutils.nim
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ discard """
"""

import std/strutils

from stdtest/testutils import disableVm
# xxx each instance of `disableVm` and `when not defined js:` should eventually be fixed

template rejectParse(e) =
Expand All @@ -12,10 +12,6 @@ template rejectParse(e) =
raise newException(AssertionDefect, "This was supposed to fail: $#!" % astToStr(e))
except ValueError: discard

template disableVm(body) =
when nimvm: discard
else: body

template main() =
block: # strip
doAssert strip(" ha ") == "ha"
Expand Down