diff --git a/compiler/test/stdlib/hash.test.gr b/compiler/test/stdlib/hash.test.gr
index b8e4d5698..a6a581b09 100644
--- a/compiler/test/stdlib/hash.test.gr
+++ b/compiler/test/stdlib/hash.test.gr
@@ -48,17 +48,18 @@ let numbers = [
808,
909,
]
-assert uniq(List.map(n => Hash.hash(n), numbers))
-
-assert Hash.hash(42) == Hash.hash(42)
-assert Hash.hash(0) == Hash.hash(0)
-assert Hash.hash(100l) == Hash.hash(100l)
-assert Hash.hash(100ul) == Hash.hash(100ul)
-assert Hash.hash(100.0f) == Hash.hash(100.0f)
-assert Hash.hash(100.0d) == Hash.hash(100.0d)
-assert Hash.hash(100L) == Hash.hash(100L)
-assert Hash.hash(100uL) == Hash.hash(100uL)
-assert Hash.hash(2/3) == Hash.hash(2/3)
+let globalInstance = Hash.make()
+assert uniq(List.map(n => Hash.hash(globalInstance, n), numbers))
+
+assert Hash.hash(globalInstance, 42) == Hash.hash(globalInstance, 42)
+assert Hash.hash(globalInstance, 0) == Hash.hash(globalInstance, 0)
+assert Hash.hash(globalInstance, 100l) == Hash.hash(globalInstance, 100l)
+assert Hash.hash(globalInstance, 100ul) == Hash.hash(globalInstance, 100ul)
+assert Hash.hash(globalInstance, 100.0f) == Hash.hash(globalInstance, 100.0f)
+assert Hash.hash(globalInstance, 100.0d) == Hash.hash(globalInstance, 100.0d)
+assert Hash.hash(globalInstance, 100L) == Hash.hash(globalInstance, 100L)
+assert Hash.hash(globalInstance, 100uL) == Hash.hash(globalInstance, 100uL)
+assert Hash.hash(globalInstance, 2/3) == Hash.hash(globalInstance, 2/3)
let strings = [
"",
@@ -95,17 +96,20 @@ let strings = [
"$",
"%",
]
-assert uniq(List.map(n => Hash.hash(n), strings))
+assert uniq(List.map(n => Hash.hash(globalInstance, n), strings))
-assert Hash.hash("") == Hash.hash("")
-assert Hash.hash("grain > ore > wool > lumber > brick") ==
- Hash.hash("grain > ore > wool > lumber > brick")
+assert Hash.hash(globalInstance, "") == Hash.hash(globalInstance, "")
+assert Hash.hash(globalInstance, "grain > ore > wool > lumber > brick") ==
+ Hash.hash(globalInstance, "grain > ore > wool > lumber > brick")
let chars = String.explode("!@#$%^&*()1234567890-qwertyuiop🌾💯🔥😈😤💪🏼")
let charList = Array.toList(chars)
-assert uniq(List.map(Hash.hash, charList))
-Array.forEach(c => assert Hash.hash(c) == Hash.hash(c), chars)
+assert uniq(List.map(e => Hash.hash(globalInstance, e), charList))
+Array.forEach(
+ c => assert Hash.hash(globalInstance, c) == Hash.hash(globalInstance, c),
+ chars
+)
enum rec Variants {
A,
@@ -128,11 +132,13 @@ let variants = [
E("dab"),
E("bad"),
]
-assert uniq(List.map(n => Hash.hash(n), variants))
+assert uniq(List.map(n => Hash.hash(globalInstance, n), variants))
-assert Hash.hash(A) == Hash.hash(A)
-assert Hash.hash(D(1, [A, B])) == Hash.hash(D(1, [A, B]))
-assert Hash.hash(E("wasm")) == Hash.hash(E("wasm"))
+assert Hash.hash(globalInstance, A) == Hash.hash(globalInstance, A)
+assert Hash.hash(globalInstance, D(1, [A, B])) ==
+ Hash.hash(globalInstance, D(1, [A, B]))
+assert Hash.hash(globalInstance, E("wasm")) ==
+ Hash.hash(globalInstance, E("wasm"))
let tuples = [
(1, A, ""),
@@ -142,10 +148,12 @@ let tuples = [
(12, B, "gr"),
(12, E("wasm"), "gr"),
]
-assert uniq(List.map(n => Hash.hash(n), tuples))
+assert uniq(List.map(n => Hash.hash(globalInstance, n), tuples))
-assert Hash.hash((12, E("wasm"), "gr")) == Hash.hash((12, E("wasm"), "gr"))
-assert Hash.hash((0, A, "")) == Hash.hash((0, A, ""))
+assert Hash.hash(globalInstance, (12, E("wasm"), "gr")) ==
+ Hash.hash(globalInstance, (12, E("wasm"), "gr"))
+assert Hash.hash(globalInstance, (0, A, "")) ==
+ Hash.hash(globalInstance, (0, A, ""))
record Rec {
num: Number,
@@ -161,12 +169,24 @@ let recs = [
{ num: 12, var: B, str: "gr" },
{ num: 12, var: E("wasm"), str: "gr" },
]
-assert uniq(List.map(n => Hash.hash(n), recs))
-
-assert Hash.hash({ num: 12, var: E("wasm"), str: "gr" }) ==
- Hash.hash({ num: 12, var: E("wasm"), str: "gr" })
-assert Hash.hash({ num: 0, var: A, str: "" }) ==
- Hash.hash({ num: 0, var: A, str: "" })
-
-assert Hash.hash(Bytes.fromString("foo")) == Hash.hash(Bytes.fromString("foo"))
-assert Hash.hash(Bytes.fromString("foo")) != Hash.hash(Bytes.fromString("bar"))
+assert uniq(List.map(n => Hash.hash(globalInstance, n), recs))
+
+assert Hash.hash(globalInstance, { num: 12, var: E("wasm"), str: "gr" }) ==
+ Hash.hash(globalInstance, { num: 12, var: E("wasm"), str: "gr" })
+assert Hash.hash(globalInstance, { num: 0, var: A, str: "" }) ==
+ Hash.hash(globalInstance, { num: 0, var: A, str: "" })
+
+assert Hash.hash(globalInstance, Bytes.fromString("foo")) ==
+ Hash.hash(globalInstance, Bytes.fromString("foo"))
+assert Hash.hash(globalInstance, Bytes.fromString("foo")) !=
+ Hash.hash(globalInstance, Bytes.fromString("bar"))
+
+// hashInstance tests
+let globalInstance1 = Hash.make()
+let globalInstance2 = Hash.make() // uses a global seed generated at runtime, Hash.make is the same
+assert Hash.hash(globalInstance1, 42) == Hash.hash(globalInstance1, 42)
+let seededInstance1 = Hash.makeSeeded(1)
+assert Hash.hash(seededInstance1, 32) == Hash.hash(seededInstance1, 32)
+assert Hash.hash(seededInstance1, 32) != Hash.hash(seededInstance1, 31)
+let seededInstance2 = Hash.makeSeeded(2)
+assert Hash.hash(seededInstance1, 30) != Hash.hash(seededInstance2, 30)
diff --git a/stdlib/hash.gr b/stdlib/hash.gr
index 0ff4e8edd..463765f61 100644
--- a/stdlib/hash.gr
+++ b/stdlib/hash.gr
@@ -3,8 +3,12 @@
*
* @example from "hash" include Hash
*
- * @example Hash.hash(1)
- * @example Hash.hash("Hello World")
+ * @example
+ * let hashInstance = Hash.make()
+ * assert Hash.hash(hashInstance, "Hello World") == Hash.hash(hashInstance, "Hello World")
+ * @example
+ * let hashInstance = Hash.makeSeeded(10)
+ * assert Hash.hash(hashInstance, "Hello World") == Hash.hash(hashInstance, "Hello World")
*
* @since v0.1.0
*/
@@ -34,7 +38,7 @@ from "runtime/unsafe/wasmi64" include WasmI64
from "runtime/unsafe/tags" include Tags
from "runtime/dataStructures" include DataStructures
-use DataStructures.{ tagSimpleNumber }
+use DataStructures.{ tagSimpleNumber, untagSimpleNumber }
from "runtime/numbers" include Numbers
use Numbers.{ coerceNumberToWasmI32 }
from "runtime/bigint" include Bigint as BI
@@ -42,18 +46,6 @@ from "runtime/bigint" include Bigint as BI
from "wasi/random" include Random
from "result" include Result
-@unsafe
-let mut seed = 0n
-
-@unsafe
-let initalize = () => {
- // Delay initialization to the first call to `hash` to prevent WASI calls
- // during startup
- let random = Random.random()
- seed = coerceNumberToWasmI32(Result.unwrap(random))
- seed
-}
-
@unsafe
let _MAX_HASH_DEPTH = 31n
@@ -71,55 +63,53 @@ let m = 5n
let n = 0xe6546b64n
@unsafe
-let mut h = seed
-
-@unsafe
-let hash32 = k => {
+let hash32 = (h, k) => {
let mut k = k * c1
k = WasmI32.rotl(k, r1)
k *= c2
- h = h ^ k
- h = WasmI32.rotl(h, r2)
- h = h * m + n
+ let h = h ^ k
+ let h = WasmI32.rotl(h, r2)
+ h * m + n
}
@unsafe
-let hash64 = k => {
+let hash64 = (h, k) => {
use WasmI64.{ (>>>) }
// convenience function for hashing 64-bit values
- hash32(WasmI32.wrapI64(k))
- hash32(WasmI32.wrapI64(k >>> 32N))
+ let h = hash32(h, WasmI32.wrapI64(k))
+ let h = hash32(h, WasmI32.wrapI64(k >>> 32N))
+ h
}
@unsafe
-let hashRemaining = r => {
+let hashRemaining = (h, r) => {
// Note: wasm is little-endian so no swap is necessary
let mut r = r * c1
r = WasmI32.rotl(r, r1)
r *= c2
- h = h ^ r
+ h ^ r
}
@unsafe
-let finalize = len => {
- h = h ^ len
+let finalize = (h, len) => {
+ let h = h ^ len
- h = h ^ h >>> 16n
- h *= 0x85ebca6bn
- h = h ^ h >>> 13n
- h *= 0xc2b2ae35n
- h = h ^ h >>> 16n
+ let h = h ^ h >>> 16n
+ let h = h * 0x85ebca6bn
+ let h = h ^ h >>> 13n
+ let h = h * 0xc2b2ae35n
+ h ^ h >>> 16n
}
@unsafe
-let rec hashOne = (val, depth) => {
+let rec hashOne = (h, val, depth) => {
if (depth > _MAX_HASH_DEPTH) {
- void
+ h
} else if ((val & Tags._GRAIN_NUMBER_TAG_MASK) != 0n) {
- hash32(val)
+ hash32(h, val)
} else if (
(val & Tags._GRAIN_GENERIC_TAG_MASK) ==
Tags._GRAIN_GENERIC_HEAP_TAG_TYPE
@@ -130,141 +120,205 @@ let rec hashOne = (val, depth) => {
let length = WasmI32.load(heapPtr, 4n)
let extra = length % 4n
let l = length - extra
+ let mut h = h
for (let mut i = 0n; i < l; i += 4n) {
- hash32(WasmI32.load(heapPtr + i, 8n))
+ h = hash32(h, WasmI32.load(heapPtr + i, 8n))
}
let mut rem = 0n
for (let mut i = 0n; i < extra; i += 1n) {
rem = rem << 8n
rem = rem | WasmI32.load8U(heapPtr + l + i, 8n)
}
- if (rem != 0n) hashRemaining(rem)
- finalize(length)
+ if (rem != 0n) h = hashRemaining(h, rem)
+ finalize(h, length)
},
t when t == Tags._GRAIN_ADT_HEAP_TAG => {
// moduleId
- hash32(WasmI32.load(heapPtr, 4n))
+ let h = hash32(h, WasmI32.load(heapPtr, 4n))
// typeId
- hash32(WasmI32.load(heapPtr, 8n))
+ let h = hash32(h, WasmI32.load(heapPtr, 8n))
// variantId
- hash32(WasmI32.load(heapPtr, 12n))
+ let h = hash32(h, WasmI32.load(heapPtr, 12n))
let arity = WasmI32.load(heapPtr, 16n)
let a = arity * 4n
+ let mut h = h
for (let mut i = 0n; i < a; i += 4n) {
- hashOne(WasmI32.load(heapPtr + i, 20n), depth + 1n)
+ h = hashOne(h, WasmI32.load(heapPtr + i, 20n), depth + 1n)
}
- finalize(arity)
+ finalize(h, arity)
},
t when t == Tags._GRAIN_RECORD_HEAP_TAG => {
// moduleId
- hash32(WasmI32.load(heapPtr, 4n))
+ let h = hash32(h, WasmI32.load(heapPtr, 4n))
// typeId
- hash32(WasmI32.load(heapPtr, 8n))
+ let h = hash32(h, WasmI32.load(heapPtr, 8n))
let arity = WasmI32.load(heapPtr, 12n)
let a = arity * 4n
+ let mut h = h
for (let mut i = 0n; i < a; i += 4n) {
- hashOne(WasmI32.load(heapPtr + i, 16n), depth + 1n)
+ h = hashOne(h, WasmI32.load(heapPtr + i, 16n), depth + 1n)
}
- finalize(arity)
+ finalize(h, arity)
},
t when t == Tags._GRAIN_ARRAY_HEAP_TAG => {
let arity = WasmI32.load(heapPtr, 4n)
let a = arity * 4n
+ let mut h = h
for (let mut i = 0n; i < a; i += 4n) {
- hashOne(WasmI32.load(heapPtr + i, 8n), depth + 1n)
+ h = hashOne(h, WasmI32.load(heapPtr + i, 8n), depth + 1n)
}
- finalize(arity)
+ finalize(h, arity)
},
t when t == Tags._GRAIN_TUPLE_HEAP_TAG => {
let tupleLength = WasmI32.load(heapPtr, 4n)
let l = tupleLength * 4n
+ let mut h = h
for (let mut i = 0n; i < l; i += 4n) {
- hashOne(WasmI32.load(heapPtr + i, 8n), depth + 1n)
+ h = hashOne(h, WasmI32.load(heapPtr + i, 8n), depth + 1n)
}
- finalize(tupleLength)
+ finalize(h, tupleLength)
},
t when t == Tags._GRAIN_LAMBDA_HEAP_TAG => {
- hash32(heapPtr)
+ hash32(h, heapPtr)
},
t when t == Tags._GRAIN_BOXED_NUM_HEAP_TAG => {
let tag = WasmI32.load(heapPtr, 4n)
match (tag) {
t when t == Tags._GRAIN_INT64_BOXED_NUM_TAG => {
- hash32(WasmI32.load(heapPtr, 8n))
- hash32(WasmI32.load(heapPtr, 12n))
+ let h = hash32(h, WasmI32.load(heapPtr, 8n))
+ hash32(h, WasmI32.load(heapPtr, 12n))
},
t when t == Tags._GRAIN_BIGINT_BOXED_NUM_TAG => {
// TODO(#1187): should include fixint size once implemented
let size = BI.getSize(heapPtr)
- hash32(size)
- hash32(BI.getFlags(heapPtr))
+ let h = hash32(h, size)
+ let mut h = hash32(h, BI.getFlags(heapPtr))
for (let mut i = 0n; i < size; i += 1n) {
- hash64(BI.getLimb(heapPtr, i))
+ h = hash64(h, BI.getLimb(heapPtr, i))
}
+ h
},
t when t == Tags._GRAIN_FLOAT64_BOXED_NUM_TAG => {
- hash32(WasmI32.load(heapPtr, 8n))
- hash32(WasmI32.load(heapPtr, 12n))
+ let h = hash32(h, WasmI32.load(heapPtr, 8n))
+ let h = hash32(h, WasmI32.load(heapPtr, 12n))
+ h
},
t when t == Tags._GRAIN_RATIONAL_BOXED_NUM_TAG => {
- hashOne(WasmI32.load(heapPtr, 8n), depth + 1n)
- hashOne(WasmI32.load(heapPtr, 12n), depth + 1n)
+ let h = hashOne(h, WasmI32.load(heapPtr, 8n), depth + 1n)
+ let h = hashOne(h, WasmI32.load(heapPtr, 12n), depth + 1n)
+ h
},
_ => {
- hash32(heapPtr)
+ hash32(h, heapPtr)
},
}
},
t when t == Tags._GRAIN_INT32_HEAP_TAG ||
t == Tags._GRAIN_FLOAT32_HEAP_TAG ||
t == Tags._GRAIN_UINT32_HEAP_TAG => {
- hash32(WasmI32.load(heapPtr, 4n))
+ hash32(h, WasmI32.load(heapPtr, 4n))
},
t when t == Tags._GRAIN_UINT64_HEAP_TAG => {
- hash32(WasmI32.load(heapPtr, 8n))
- hash32(WasmI32.load(heapPtr, 12n))
+ let h = hash32(h, WasmI32.load(heapPtr, 8n))
+ let h = hash32(h, WasmI32.load(heapPtr, 12n))
+ h
},
_ => {
- hash32(heapPtr)
+ hash32(h, heapPtr)
},
}
} else {
// Handle non-heap values: booleans, chars, void, etc.
- hash32(val)
+ hash32(h, val)
+ }
+}
+
+/**
+ * Represents a particular hashing instance.
+ *
+ * @since v0.7.0
+ */
+abstract type HashInstance = Number
+
+let mut seed: HashInstance = 0
+@unsafe
+let getSeed = () => {
+ if (WasmI32.eqz(untagSimpleNumber(seed))) {
+ // Delay initialization to the first call to `hash` to prevent WASI calls
+ // during startup
+ let random = Random.random()
+ seed = Result.unwrap(random)
}
+ seed: HashInstance
}
/**
- * A generic hash function that produces an integer from any value. If `a == b` then `Hash.hash(a) == Hash.hash(b)`.
+ * Produces a generic hash instance using a random seed value.
+ *
+ * @returns A hashing instance that can be consumed during hashing
+ *
+ * @throws Failure(String): If WASI random_get fails
+ *
+ * @example
+ * let hashInstance = Hash.make()
+ * assert Hash.hash(hashInstance," Hello World") == Hash.hash(hashInstance, "Hello World)
*
+ * @since v0.7.0
+ */
+provide let make = () => {
+ let seed = getSeed()
+ seed
+}
+
+/**
+ * Produces a hashInstance using the given seed.
+ *
+ * @param seed: The seed to use while hashing
+ * @returns A hashing instance that can be consumed during hashing
+ *
+ * @example
+ * let hashInstance = Hash.makeSeeded(1)
+ * assert Hash.hash(hashInstance," Hello World") == Hash.hash(hashInstance, "Hello World)
+ * @example
+ * let hashInstance1 = Hash.makeSeeded(1)
+ * let hashInstance2 = Hash.makeSeeded(2)
+ * assert Hash.hash(hashInstance1," Hello World") != Hash.hash(hashInstance2, "Hello World)
+ *
+ * @since v0.7.0
+ */
+@unsafe
+provide let makeSeeded = (seed: Number) => {
+ return seed: HashInstance
+}
+
+/**
+ * A generic hash function that produces an integer from any value given a hashing instance. If `a == b` then `Hash.hash(h, a) == Hash.hash(h, b)`.
+ *
+ * @param hashInstance: The hashing instance to use as a seed
* @param anything: The value to hash
+ *
* @returns A hash for the given value
- *
- * @throws Failure(String): If WASI random_get fails
*
- * @example assert Hash.hash(1) == Hash.hash(1)
- * @example assert Hash.hash("Hello World") == Hash.hash("Hello World")
+ * @example
+ * let hashInstance = Hash.makeSeeded(1)
+ * assert Hash.hash(hashInstance," Hello World") == Hash.hash(hashInstance, "Hello World)
*
* @since v0.1.0
+ * @history v0.7.0: Added `hashInstance` parameter instead of using a global seed
*/
@unsafe
-provide let hash = anything => {
- h = if (WasmI32.eqz(seed)) {
- initalize()
- } else {
- seed
- }
-
- hashOne(WasmI32.fromGrain(anything), 0n)
+provide let hash = (hashInstance: HashInstance, anything) => {
+ let h = coerceNumberToWasmI32(hashInstance)
+ let h = hashOne(h, WasmI32.fromGrain(anything), 0n)
ignore(anything)
- finalize(0n)
+ let h = finalize(h, 0n)
// Tag the number on the way out.
// Since Grain has proper modulus, negative numbers are okay.
diff --git a/stdlib/hash.md b/stdlib/hash.md
index aeab5ca40..fb41b0a82 100644
--- a/stdlib/hash.md
+++ b/stdlib/hash.md
@@ -14,55 +14,143 @@ from "hash" include Hash
```
```grain
-Hash.hash(1)
+let hashInstance = Hash.make()
+assert Hash.hash(hashInstance, "Hello World") == Hash.hash(hashInstance, "Hello World")
```
```grain
-Hash.hash("Hello World")
+let hashInstance = Hash.makeSeeded(10)
+assert Hash.hash(hashInstance, "Hello World") == Hash.hash(hashInstance, "Hello World")
```
+## Types
+
+Type declarations included in the Hash module.
+
+### Hash.**HashInstance**
+
+
+Added in next
+No other changes yet.
+
+
+```grain
+type HashInstance
+```
+
+Represents a particular hashing instance.
+
## Values
Functions and constants included in the Hash module.
-### Hash.**hash**
+### Hash.**make**
-Added in 0.1.0
+Added in next
No other changes yet.
```grain
-hash : (anything: a) => Number
+make : () => HashInstance
+```
+
+Produces a generic hash instance using a random seed value.
+
+Returns:
+
+|type|description|
+|----|-----------|
+|`HashInstance`|A hashing instance that can be consumed during hashing|
+
+Throws:
+
+`Failure(String)`
+
+* If WASI random_get fails
+
+Examples:
+
+```grain
+let hashInstance = Hash.make()
+assert Hash.hash(hashInstance," Hello World") == Hash.hash(hashInstance, "Hello World)
```
-A generic hash function that produces an integer from any value. If `a == b` then `Hash.hash(a) == Hash.hash(b)`.
+### Hash.**makeSeeded**
+
+
+Added in next
+No other changes yet.
+
+
+```grain
+makeSeeded : (seed: Number) => HashInstance
+```
+
+Produces a hashInstance using the given seed.
Parameters:
|param|type|description|
|-----|----|-----------|
-|`anything`|`a`|The value to hash|
+|`seed`|`Number`|The seed to use while hashing|
Returns:
|type|description|
|----|-----------|
-|`Number`|A hash for the given value|
+|`HashInstance`|A hashing instance that can be consumed during hashing|
-Throws:
+Examples:
-`Failure(String)`
+```grain
+let hashInstance = Hash.makeSeeded(1)
+assert Hash.hash(hashInstance," Hello World") == Hash.hash(hashInstance, "Hello World)
+```
-* If WASI random_get fails
+```grain
+let hashInstance1 = Hash.makeSeeded(1)
+let hashInstance2 = Hash.makeSeeded(2)
+assert Hash.hash(hashInstance1," Hello World") != Hash.hash(hashInstance2, "Hello World)
+```
-Examples:
+### Hash.**hash**
+
+
+Added in 0.1.0
+
+
+
version
changes
+
+
+
next
Added `hashInstance` parameter instead of using a global seed
+
+
+
```grain
-assert Hash.hash(1) == Hash.hash(1)
+hash : (hashInstance: HashInstance, anything: a) => Number
```
+A generic hash function that produces an integer from any value given a hashing instance. If `a == b` then `Hash.hash(h, a) == Hash.hash(h, b)`.
+
+Parameters:
+
+|param|type|description|
+|-----|----|-----------|
+|`hashInstance`|`HashInstance`|The hashing instance to use as a seed|
+|`anything`|`a`|The value to hash|
+
+Returns:
+
+|type|description|
+|----|-----------|
+|`Number`|A hash for the given value|
+
+Examples:
+
```grain
-assert Hash.hash("Hello World") == Hash.hash("Hello World")
+let hashInstance = Hash.makeSeeded(1)
+assert Hash.hash(hashInstance," Hello World") == Hash.hash(hashInstance, "Hello World)
```
diff --git a/stdlib/map.gr b/stdlib/map.gr
index 4ec222da9..cf771ff99 100644
--- a/stdlib/map.gr
+++ b/stdlib/map.gr
@@ -14,7 +14,6 @@ from "option" include Option
from "runtime/dataStructures" include DataStructures
use DataStructures.{ allocateArray, untagSimpleNumber }
from "hash" include Hash
-use Hash.{ hash }
from "runtime/unsafe/memory" include Memory
from "runtime/unsafe/wasmi32" include WasmI32
@@ -27,6 +26,7 @@ record rec Bucket {
abstract record Map {
mut size: Number,
+ hashInstance: Hash.HashInstance,
mut buckets: Array