Skip to content

Commit 37ae02d

Browse files
committed
Port and use 64-bit CityHash for tasty uuid.
1 parent 83604b6 commit 37ae02d

File tree

3 files changed

+189
-264
lines changed

3 files changed

+189
-264
lines changed

compiler/src/dotty/tools/dotc/core/tasty/TastyPickler.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ package tasty
66
import TastyFormat._
77
import collection.mutable
88
import TastyBuffer._
9-
import util.MurmurLongHash3
9+
import util.CityHash
1010
import core.Symbols.Symbol
1111
import ast.tpd
1212
import Decorators._
@@ -26,8 +26,8 @@ class TastyPickler {
2626
buf.length + natSize(buf.length)
2727
}
2828

29-
val uuidLow: Long = MurmurLongHash3.bytesHash(nameBuffer.bytes)
30-
val uuidHi: Long = sections.iterator.map(x => MurmurLongHash3.bytesHash(x._2.bytes)).fold(0L)(_ ^ _)
29+
val uuidLow: Long = CityHash.bytesHash(nameBuffer.bytes)
30+
val uuidHi: Long = sections.iterator.map(x => CityHash.bytesHash(x._2.bytes)).fold(0L)(_ ^ _)
3131

3232
val headerBuffer = {
3333
val buf = new TastyBuffer(header.length + 24)
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
package dotty.tools.dotc.util
2+
3+
/* Ported from https://github.com/google/cityhash/blob/master/src/city.cc */
4+
private[util] class CityHash {
5+
6+
// Some primes between 2^63 and 2^64 for various uses.
7+
final val k0 = 0xc3a5c85c97cb3127L
8+
final val k1 = 0xb492b66fbe98f273L
9+
final val k2 = 0x9ae16a3b2f90404fL
10+
11+
protected final def cityHash64(data: Array[Byte]): Long = {
12+
implicit val implicitData: Array[Byte] = data
13+
var s = 0
14+
var len = data.length
15+
if (len <= 32) {
16+
if (len <= 16) hashLen0to16(len)
17+
else hashLen17to32(len)
18+
} else if (len <= 64) {
19+
hashLen33to64(len)
20+
} else {
21+
// For strings over 64 bytes we hash the end first, and then as we
22+
// loop we keep 56 bytes of state: v, w, x, y, and z.
23+
var x: Long = fetch64(s + len - 40)
24+
var y: Long = fetch64(s + len - 16) + fetch64(s + len - 56)
25+
var z: Long = hashLen16(fetch64(s + len - 48) + len, fetch64(s + len - 24))
26+
var v: (Long, Long) = weakHashLen32WithSeeds(s + len - 64, len, z)
27+
var w: (Long, Long) = weakHashLen32WithSeeds(s + len - 32, y + k1, x)
28+
x = x * k1 + fetch64(s)
29+
30+
// Decrease len to the nearest multiple of 64, and operate on 64-byte chunks.
31+
len = (len - 1) & 63
32+
do {
33+
x = rotate(x + y + v._1 + fetch64(s + 8), 37) * k1
34+
y = rotate(y + v._2 + fetch64(s + 48), 42) * k1
35+
x ^= w._2
36+
y += v._1 + fetch64(s + 40)
37+
z = rotate(z + w._1, 33) * k1
38+
v = weakHashLen32WithSeeds(s, v._2 * k1, x + w._1)
39+
w = weakHashLen32WithSeeds(s + 32, z + w._2, y + fetch64(s + 16))
40+
val tmp = z
41+
z = x
42+
x = tmp
43+
s += 64
44+
len -= 64
45+
} while (len != 0)
46+
47+
hashLen16(hashLen16(v._1, w._1) + shiftMix(y) * k1 + z,hashLen16(v._2, w._2) + x)
48+
}
49+
}
50+
51+
private final def hashLen0to16(len: Int)(implicit data: Array[Byte]): Long = {
52+
if (len >= 8) {
53+
val mul: Long = k2 + len * 2
54+
val a: Long = fetch64(0) + k2
55+
val b: Long = fetch64(len - 8)
56+
val c: Long = rotate(b, 37) * mul + a
57+
val d: Long = (rotate(a, 25) + b) * mul
58+
hashLen16(c, d, mul)
59+
} else if (len >= 4) {
60+
val mul = k2 + len * 2
61+
val a = fetch32(0)
62+
hashLen16(len + (a << 3), fetch32(len - 4), mul)
63+
} else if (len > 0) {
64+
val a: Byte = data(0)
65+
val b: Byte = data(len >> 1)
66+
val c: Byte = data(len - 1)
67+
val y: Int = a.toInt + (b.toInt << 8)
68+
val z: Int = len + (c.toInt << 2)
69+
shiftMix(y * k2 ^ z * k0) * k2
70+
} else {
71+
k2
72+
}
73+
}
74+
75+
// This probably works well for 16-byte strings as well, but it may be overkill
76+
// in that case.
77+
private final def hashLen17to32(len: Int)(implicit data: Array[Byte]): Long = {
78+
val mul: Long = k2 + len * 2
79+
val a: Long = fetch64(0) * k1
80+
val b: Long = fetch64(8)
81+
val c: Long = fetch64(len - 8) * mul
82+
val d: Long = fetch64(len - 16) * k2
83+
hashLen16(rotate(a + b, 43) + rotate(c, 30) + d, a + rotate(b + k2, 18) + c, mul)
84+
}
85+
86+
/** Return an 8-byte hash for 33 to 64 bytes. */
87+
private final def hashLen33to64(len: Int)(implicit data: Array[Byte]): Long = {
88+
val mul: Long = k2 + len * 2
89+
val a = fetch64(0) * k2
90+
val b = fetch64(8)
91+
val c = fetch64(len - 24)
92+
val d = fetch64(len - 32)
93+
val e = fetch64(16) * k2
94+
val f = fetch64(24) * 9
95+
val g = fetch64(len - 8)
96+
val h = fetch64(len - 16) * mul
97+
val u = rotate(a + g, 43) + (rotate(b, 30) + c) * 9
98+
val v = ((a + g) ^ d) + f + 1
99+
val w = bswap64((u + v) * mul) + h
100+
val x = rotate(e + f, 42) + c
101+
val y = (bswap64((v + w) * mul) + g) * mul
102+
val z = e + f + c
103+
val a2 = bswap64((x + z) * mul + y) + b
104+
shiftMix((z + a2) * mul + d + h) * mul + x
105+
}
106+
107+
private final def hashLen16(hi: Long, lo: Long): Long = {
108+
val kMul = 0x9ddfea08eb382d69L
109+
var a = (lo ^ hi) * kMul
110+
a ^= (a >> 47)
111+
var b = (hi ^ a) * kMul
112+
b ^= (b >> 47)
113+
b * kMul
114+
}
115+
116+
private final def hashLen16(u: Long, v: Long, mul: Long): Long = {
117+
// Murmur-inspired hashing.
118+
var a = (u ^ v) * mul
119+
a ^= (a >> 47)
120+
a = (v ^ a) * mul
121+
a ^= (a >> 47)
122+
a * mul
123+
}
124+
125+
/** Return a 16-byte hash for 48 bytes. Quick and dirty.
126+
* Callers do best to use "random-looking" values for a and b.
127+
*/
128+
private final def weakHashLen32WithSeeds(w: Long, x: Long, y: Long, z: Long, a0: Long, b0: Long): (Long, Long) = {
129+
var a = a0
130+
var b = b0
131+
a += w
132+
b = rotate(b + a + z, 21)
133+
val c: Long = a
134+
a += x
135+
a += y
136+
b += rotate(a, 44)
137+
(a + z, b + c)
138+
}
139+
140+
/** Return a 16-byte hash for s[0] ... s[31], a, and b. Quick and dirty. */
141+
private final def weakHashLen32WithSeeds(s: Int, a: Long, b: Long)(implicit data: Array[Byte]): (Long, Long) =
142+
weakHashLen32WithSeeds(fetch64(s), fetch64(s + 8), fetch64(s + 16), fetch64(s + 24), a, b)
143+
144+
private final def fetch64(idx: Int)(implicit data: Array[Byte]): Long = {
145+
var x: Long = data(idx)
146+
x = data(idx + 1) | (x << 8)
147+
x = data(idx + 2) | (x << 8)
148+
x = data(idx + 3) | (x << 8)
149+
x = data(idx + 4) | (x << 8)
150+
x = data(idx + 5) | (x << 8)
151+
x = data(idx + 6) | (x << 8)
152+
data(idx + 7) | (x << 8)
153+
}
154+
155+
private final def fetch32(idx: Int)(implicit data: Array[Byte]): Long = {
156+
var x: Int = data(idx)
157+
x = data(idx + 1) | (x << 8)
158+
x = data(idx + 2) | (x << 8)
159+
data(idx + 3) | (x << 8)
160+
}
161+
162+
private final def bswap64(x: Long): Long = {
163+
((x & 0xff00000000000000L) >> 56) |
164+
((x & 0x00ff000000000000L) >> 40) |
165+
((x & 0x0000ff0000000000L) >> 24) |
166+
((x & 0x000000ff00000000L) >> 8) |
167+
((x & 0x00000000ff000000L) << 8) |
168+
((x & 0x0000000000ff0000L) << 24) |
169+
((x & 0x000000000000ff00L) << 40) |
170+
((x & 0x00000000000000ffL) << 56)
171+
}
172+
173+
// Bitwise right rotate. Normally this will compile to a single
174+
// instruction, especially if the shift is a manifest constant.
175+
private final def rotate(v: Long, shift: Int): Long = {
176+
// Avoid shifting by 64: doing so yields an undefined result.
177+
if (shift == 0) v else (v >> shift) | (v << (64 - shift))
178+
}
179+
180+
private final def shiftMix(v: Long): Long = v ^ (v >> 47)
181+
182+
}
183+
184+
object CityHash extends CityHash {
185+
def bytesHash(data: Array[Byte]): Long = cityHash64(data)
186+
}

0 commit comments

Comments
 (0)