Skip to content

Commit d19e431

Browse files
authored
std/hashes: hash(ref|ptr|pointer) + other improvements (#17731)
1 parent 611b887 commit d19e431

File tree

5 files changed

+89
-29
lines changed

5 files changed

+89
-29
lines changed

lib/pure/hashes.nim

Lines changed: 49 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ proc hashData*(data: pointer, size: int): Hash =
182182
var h: Hash = 0
183183
when defined(js):
184184
var p: cstring
185-
asm """`p` = `Data`;"""
185+
asm """`p` = `Data`"""
186186
else:
187187
var p = cast[cstring](data)
188188
var i = 0
@@ -193,12 +193,22 @@ proc hashData*(data: pointer, size: int): Hash =
193193
dec(s)
194194
result = !$h
195195

196+
proc hashIdentity*[T: Ordinal|enum](x: T): Hash {.inline, since: (1, 3).} =
197+
## The identity hash, i.e. `hashIdentity(x) = x`.
198+
cast[Hash](ord(x))
199+
200+
when defined(nimIntHash1):
201+
proc hash*[T: Ordinal|enum](x: T): Hash {.inline.} =
202+
## Efficient hashing of integers.
203+
cast[Hash](ord(x))
204+
else:
205+
proc hash*[T: Ordinal|enum](x: T): Hash {.inline.} =
206+
## Efficient hashing of integers.
207+
hashWangYi1(uint64(ord(x)))
208+
196209
when defined(js):
197210
var objectID = 0
198-
199-
proc hash*(x: pointer): Hash {.inline.} =
200-
## Efficient hashing of pointers.
201-
when defined(js):
211+
proc getObjectId(x: pointer): int =
202212
asm """
203213
if (typeof `x` == "object") {
204214
if ("_NimID" in `x`)
@@ -209,29 +219,46 @@ proc hash*(x: pointer): Hash {.inline.} =
209219
}
210220
}
211221
"""
222+
223+
proc hash*(x: pointer): Hash {.inline.} =
224+
## Efficient `hash` overload.
225+
when defined(js):
226+
let y = getObjectId(x)
212227
else:
213-
result = cast[Hash](cast[uint](x) shr 3) # skip the alignment
228+
let y = cast[int](x)
229+
hash(y) # consistent with code expecting scrambled hashes depending on `nimIntHash1`.
230+
231+
proc hash*[T](x: ref[T] | ptr[T]): Hash {.inline.} =
232+
## Efficient `hash` overload.
233+
runnableExamples:
234+
var a: array[10, uint8]
235+
assert a[0].addr.hash != a[1].addr.hash
236+
assert cast[pointer](a[0].addr).hash == a[0].addr.hash
237+
runnableExamples:
238+
type A = ref object
239+
x: int
240+
let a = A(x: 3)
241+
let ha = a.hash
242+
assert ha != A(x: 3).hash # A(x: 3) is a different ref object from `a`.
243+
a.x = 4
244+
assert ha == a.hash # the hash only depends on the address
245+
runnableExamples:
246+
# you can overload `hash` if you want to customize semantics
247+
type A[T] = ref object
248+
x, y: T
249+
proc hash(a: A): Hash = hash(a.x)
250+
assert A[int](x: 3, y: 4).hash == A[int](x: 3, y: 5).hash
251+
# xxx pending bug #17733, merge as `proc hash*(pointer | ref | ptr): Hash`
252+
# or `proc hash*[T: ref | ptr](x: T): Hash`
253+
hash(cast[pointer](x))
214254

215255
proc hash*[T: proc](x: T): Hash {.inline.} =
216256
## Efficient hashing of proc vars. Closures are supported too.
217257
when T is "closure":
218-
result = hash(rawProc(x)) !& hash(rawEnv(x))
258+
result = hash((rawProc(x), rawEnv(x)))
219259
else:
220260
result = hash(pointer(x))
221261

222-
proc hashIdentity*[T: Ordinal|enum](x: T): Hash {.inline, since: (1, 3).} =
223-
## The identity hash, i.e. `hashIdentity(x) = x`.
224-
cast[Hash](ord(x))
225-
226-
when defined(nimIntHash1):
227-
proc hash*[T: Ordinal|enum](x: T): Hash {.inline.} =
228-
## Efficient hashing of integers.
229-
cast[Hash](ord(x))
230-
else:
231-
proc hash*[T: Ordinal|enum](x: T): Hash {.inline.} =
232-
## Efficient hashing of integers.
233-
hashWangYi1(uint64(ord(x)))
234-
235262
proc hash*(x: float): Hash {.inline.} =
236263
## Efficient hashing of floats.
237264
let y = x + 0.0 # for denormalization
@@ -484,10 +511,9 @@ proc hashIgnoreCase*(sBuf: string, sPos, ePos: int): Hash =
484511
h = h !& ord(c)
485512
result = !$h
486513

487-
488514
proc hash*[T: tuple | object](x: T): Hash =
489-
## Efficient hashing of tuples and objects.
490-
## There must be a `hash` proc defined for each of the field types.
515+
## Efficient `hash` overload.
516+
## `hash` must be defined for each component of `x`.
491517
runnableExamples:
492518
type Obj = object
493519
x: int

testament/lib/stdtest/testutils.nim

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,7 @@ template accept*(a) =
8585

8686
template reject*(a) =
8787
doAssert not compiles(a)
88+
89+
template disableVm*(body) =
90+
when nimvm: discard
91+
else: body

tests/collections/ttables.nim

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ And we get here
77
3
88
'''
99
joinable: false
10+
targets: "c cpp js"
1011
"""
12+
13+
# xxx wrap in a template to test in VM, see https://github.com/timotheecour/Nim/issues/534#issuecomment-769565033
14+
1115
import hashes, sequtils, tables, algorithm
1216

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

451+
block: # Table[ref, int]
452+
type A = ref object
453+
x: int
454+
var t: OrderedTable[A, int]
455+
let a1 = A(x: 3)
456+
let a2 = A(x: 3)
457+
t[a1] = 10
458+
t[a2] = 11
459+
doAssert t[a1] == 10
460+
doAssert t[a2] == 11

tests/stdlib/thashes.nim

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ discard """
33
"""
44

55
import std/hashes
6-
6+
from stdtest/testutils import disableVm, whenVMorJs
77

88
when not defined(js) and not defined(cpp):
99
block:
@@ -177,5 +177,25 @@ proc main() =
177177
doAssert hash(Obj5(t: false, x: 1)) == hash(Obj5(t: true, y: 1))
178178
doAssert hash(Obj5(t: false, x: 1)) != hash(Obj5(t: true, y: 2))
179179

180+
block: # hash(ref|ptr|pointer)
181+
var a: array[10, uint8]
182+
# disableVm:
183+
whenVMorJs:
184+
# pending fix proposed in https://github.com/nim-lang/Nim/issues/15952#issuecomment-786312417
185+
discard
186+
do:
187+
assert a[0].addr.hash != a[1].addr.hash
188+
assert cast[pointer](a[0].addr).hash == a[0].addr.hash
189+
190+
block: # hash(ref)
191+
type A = ref object
192+
x: int
193+
let a = A(x: 3)
194+
disableVm: # xxx Error: VM does not support 'cast' from tyRef to tyPointer
195+
let ha = a.hash
196+
assert ha != A(x: 3).hash # A(x: 3) is a different ref object from `a`.
197+
a.x = 4
198+
assert ha == a.hash # the hash only depends on the address
199+
180200
static: main()
181201
main()

tests/stdlib/tstrutils.nim

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ discard """
33
"""
44

55
import std/strutils
6-
6+
from stdtest/testutils import disableVm
77
# xxx each instance of `disableVm` and `when not defined js:` should eventually be fixed
88

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

15-
template disableVm(body) =
16-
when nimvm: discard
17-
else: body
18-
1915
template main() =
2016
block: # strip
2117
doAssert strip(" ha ") == "ha"

0 commit comments

Comments
 (0)