Lindenii Project Forge
Prepand hashmap_ to fnv and siphash
fnv: Fowler-Noll-Vo hashmap
hashmap_fnv: Fowler-Noll-Vo hashmap
This module provides a simple implementation of a hashmap using the Fowler-Noll-Vo (FNV) hashing algorithm. FNV is not collision-resistant, so it should only be used for trusted keys (i.e., not user input that could be deliberately chosen to cause collisions).
// SPDX-License-Identifier: MPL-2.0 // SPDX-FileCopyrightText: 2024 Drew DeVault <drew@ddevault.org> // SPDX-FileCopyrightText: 2025 Runxi Yu <me@runxiyu.org> use ds::map; // Deletes an item from a [[map]]. export fn del(m: *map, key: []u8) (*opaque | void) = { return map::del(m.inner, key); };
// SPDX-License-Identifier: MPL-2.0 // SPDX-FileCopyrightText: 2024 Drew DeVault <drew@ddevault.org> // SPDX-FileCopyrightText: 2025 Runxi Yu <me@runxiyu.org> use ds::map; // Frees resources associated with a [[map]]. export fn finish(m: *map) void = { map::finish(m.inner); free(m); };
// SPDX-License-Identifier: MPL-2.0 // SPDX-FileCopyrightText: 2024 Drew DeVault <drew@ddevault.org> // SPDX-FileCopyrightText: 2025 Runxi Yu <me@runxiyu.org> use ds::map; // Gets an item from a [[map]] by key, returning void if not found. export fn get(m: *map, key: []u8) (*opaque | void) = { return map::get(m.inner, key); };
use hash; use hash::fnv; fn hash64(_params: nullable *opaque, key: []u8) size = { let h = fnv::fnv64a(); hash::write(&h, key); return fnv::sum64(&h): size; };
// SPDX-License-Identifier: MPL-2.0 // SPDX-FileCopyrightText: 2024 Drew DeVault <drew@ddevault.org> // SPDX-FileCopyrightText: 2025 Runxi Yu <me@runxiyu.org> use ds::map; // A simple hash map from byte strings to opaque pointers, using FNV for // hashing and fallback maps for collision resolution. // // You are advised to create these with [[new]]. export type map = struct { vt: map::map, inner: *map::map, }; const _vt: map::vtable = map::vtable { getter = &vt_get, setter = &vt_set, deleter = &vt_del, finisher = &vt_finish, }; fn vt_get(m: *map::map, key: []u8) (*opaque | void) = get(m: *map, key); fn vt_set(m: *map::map, key: []u8, v: *opaque) (void | nomem) = set(m: *map, key, v); fn vt_del(m: *map::map, key: []u8) (*opaque | void) = del(m: *map, key); fn vt_finish(m: *map::map) void = finish(m: *map);
// SPDX-License-Identifier: MPL-2.0 // SPDX-FileCopyrightText: 2024 Drew DeVault <drew@ddevault.org> // SPDX-FileCopyrightText: 2025 Runxi Yu <me@runxiyu.org> use errors; use ds::map; use ds::map::hashmap; // Creates a new [[map]] with the given number of buckets. // make_fallback is a function that creates per-bucket fallback maps. export fn new( make_fallback: *fn() (*map::map | nomem), n: size, ) (*map | errors::invalid | nomem) = { let inner = match (hashmap::new(make_fallback, n, &hash64, null)) { case let hm: *hashmap::map => yield (hm: *map::map); case errors::invalid => return errors::invalid; case nomem => return nomem; }; let m = match (alloc(map { vt = &_vt, inner = inner, })) { case let p: *map => yield p; case nomem => map::finish(inner); return nomem; }; return m; };
// SPDX-License-Identifier: MPL-2.0 // SPDX-FileCopyrightText: 2024 Drew DeVault <drew@ddevault.org> // SPDX-FileCopyrightText: 2025 Runxi Yu <me@runxiyu.org> use ds::map; // Sets an item in a [[map]], replacing any existing item with the same key. export fn set(m: *map, key: []u8, value: *opaque) (void | nomem) = { return map::set(m.inner, key, value); };
use crypto::random; use ds::map; use ds::map::slice_basic; use ds::map::slice_sorted; use ds::map::btree; use ds::map::rbtree; use ds::map::swiss_siphash; use errors; fn mk_slice_basic() (*map::map | nomem) = { match (slice_basic::new()) { case let p: *slice_basic::map => return (p: *map::map); case nomem => return nomem; }; }; @test fn test() void = { const buckets: [_]size = [128z, 256z]; const makers: [_]*fn() (*map::map | nomem) = [&mk_slice_basic]; for (let bi = 0z; bi < len(buckets); bi += 1) { for (let mi = 0z; mi < len(makers); mi += 1) { let m: *map = match (new(makers[mi], buckets[bi])) { case let p: *map => yield p; case errors::invalid => abort("fnv: invalid"); case nomem => abort("fnv: nomem"); }; defer finish(m); map::stress_test(m, 20000); }; }; };
siphash: SipHash hashmap
hashmap_siphash: SipHash hashmap
This module provides a simple implementation of a hashmap using the SipHash hashing algorithm for collision-resistant mapping. It is designed for situations where keys may be untrusted.
// SPDX-License-Identifier: MPL-2.0 // SPDX-FileCopyrightText: 2024 Drew DeVault <drew@ddevault.org> // SPDX-FileCopyrightText: 2025 Runxi Yu <me@runxiyu.org> use ds::map; // Deletes an item from a [[map]]. export fn del(m: *map, key: []u8) (*opaque | void) = { return map::del(m.inner, key); };
// SPDX-License-Identifier: MPL-2.0 // SPDX-FileCopyrightText: 2024 Drew DeVault <drew@ddevault.org> // SPDX-FileCopyrightText: 2025 Runxi Yu <me@runxiyu.org> use ds::map; // Frees resources associated with a [[map]]. export fn finish(m: *map) void = { map::finish(m.inner); free(m.key); free(m); };
// SPDX-License-Identifier: MPL-2.0 // SPDX-FileCopyrightText: 2024 Drew DeVault <drew@ddevault.org> // SPDX-FileCopyrightText: 2025 Runxi Yu <me@runxiyu.org> use ds::map; // Gets an item from a [[map]] by key, returning void if not found. export fn get(m: *map, key: []u8) (*opaque | void) = { return map::get(m.inner, key); };
// SPDX-License-Identifier: MPL-2.0 // SPDX-FileCopyrightText: 2024 Drew DeVault <drew@ddevault.org> // SPDX-FileCopyrightText: 2025 Runxi Yu <me@runxiyu.org> use hash; use hash::siphash; fn hash64(params: nullable *opaque, key: []u8) size = { let keyptr = match (params) { case null => abort("ds::map::siphash: missing key"); case let p: *opaque => yield (p: *[16]u8); }; let h = siphash::siphash(2, 4, keyptr); defer hash::close(&h); hash::write(&h, key); return siphash::sum(&h): size; };
// SPDX-License-Identifier: MPL-2.0 // SPDX-FileCopyrightText: 2024 Drew DeVault <drew@ddevault.org> // SPDX-FileCopyrightText: 2025 Runxi Yu <me@runxiyu.org> use ds::map; // A simple hash map from byte strings to opaque pointers, using SipHash for // hashing and fallback maps for collision resolution. // // You are advised to create these with [[new]]. export type map = struct { vt: map::map, inner: *map::map, key: *[16]u8, }; const _vt: map::vtable = map::vtable { getter = &vt_get, setter = &vt_set, deleter = &vt_del, finisher = &vt_finish, }; fn vt_get(m: *map::map, key: []u8) (*opaque | void) = get(m: *map, key); fn vt_set(m: *map::map, key: []u8, v: *opaque) (void | nomem) = set(m: *map, key, v); fn vt_del(m: *map::map, key: []u8) (*opaque | void) = del(m: *map, key); fn vt_finish(m: *map::map) void = finish(m: *map);
// SPDX-License-Identifier: MPL-2.0 // SPDX-FileCopyrightText: 2024 Drew DeVault <drew@ddevault.org> // SPDX-FileCopyrightText: 2025 Runxi Yu <me@runxiyu.org> use errors; use ds::map; use ds::map::hashmap; // Creates a new [[map]] with a function that creates fallback maps, the number // of buckets, and the SipHash key. export fn new( make_fallback: *fn() (*map::map | nomem), n: size, siphash_key: [16]u8, ) (*map | errors::invalid | nomem) = { let keybox = match (alloc(siphash_key)) { case let kp: *[16]u8 => yield kp; case nomem => return nomem; }; let inner = match (hashmap::new( make_fallback, n, &hash64, (keybox: *opaque), )) { case let hm: *hashmap::map => yield (hm: *map::map); case errors::invalid => free(keybox); return errors::invalid; case nomem => free(keybox); return nomem; }; let m = match (alloc(map { vt = &_vt, inner = inner, key = keybox, })) { case let p: *map => yield p; case nomem => map::finish(inner); free(keybox); return nomem; }; return m; };
// SPDX-License-Identifier: MPL-2.0 // SPDX-FileCopyrightText: 2024 Drew DeVault <drew@ddevault.org> // SPDX-FileCopyrightText: 2025 Runxi Yu <me@runxiyu.org> use ds::map; // Sets an item in a [[map]], replacing any existing item with the same key. export fn set(m: *map, key: []u8, value: *opaque) (void | nomem) = { return map::set(m.inner, key, value); };
use crypto::random; use ds::map; use ds::map::slice_basic; use ds::map::slice_sorted; use ds::map::btree; use ds::map::rbtree; use ds::map::swiss_siphash; use errors; fn mk_slice_basic() (*map::map | nomem) = { match (slice_basic::new()) { case let p: *slice_basic::map => return (p: *map::map); case nomem => return nomem; }; }; @test fn test() void = { const buckets: [_]size = [128z, 256z]; const makers: [_]*fn() (*map::map | nomem) = [&mk_slice_basic]; let key1: [16]u8 = [0...]; let key2: [16]u8 = [0...]; random::buffer(&key1); random::buffer(&key2); const keys: [2]*[16]u8 = [&key1, &key2]; for (let bi = 0z; bi < len(buckets); bi += 1) { for (let ki = 0z; ki < len(keys); ki += 1) { for (let mi = 0z; mi < len(makers); mi += 1) { let m: *map = match (new(makers[mi], buckets[bi], *keys[ki])) { case let p: *map => yield p; case errors::invalid => abort("siphash: invalid"); case nomem => abort("siphash: nomem"); }; defer finish(m); map::stress_test(m, 20000); }; }; }; };