Lindenii Project Forge
Login

hare-ds

Data structures for Hare
Commit info
ID
1f9282ff0fe14e50b26d5945f39f8f2c51b43f04
Author
Runxi Yu <me@runxiyu.org>
Author date
Sun, 21 Sep 2025 19:37:02 +0800
Committer
Runxi Yu <me@runxiyu.org>
Committer date
Sun, 21 Sep 2025 19:37:02 +0800
Actions
Maps should own their keys
// SPDX-License-Identifier: MPL-2.0

// Frees resources associated with a [[map]].
export fn finish(m: *map) void = {
	node_finish(m.root);
	free(m);
};

fn node_finish(n: *node) void = {
	if (!n.leaf) {
		for (let i = 0z; i < len(n.children); i += 1) {
			node_finish(n.children[i]);
		};
		free(n.children);
	};
	for (let i = 0z; i < len(n.keys); i += 1) {
		free(n.keys[i]);
	};
	free(n.keys);
	free(n.vals);
	free(n);
};
// SPDX-License-Identifier: MPL-2.0

use bytes;
use sort;

fn keycmp(a: []u8, b: []u8) int = {
	let n = if (len(a) < len(b)) len(a) else len(b);
	for (let i = 0z; i < n; i += 1) {
		if (a[i] < b[i]) return -1;
		if (a[i] > b[i]) return 1;
	};
	if (len(a) < len(b)) return -1;
	if (len(a) > len(b)) return 1;
	return 0;
};

fn cmp_u8slice(a: const *opaque, b: const *opaque) int = {
	let sa = *(a: *[]u8);
	let sb = *(b: *[]u8);
	return keycmp(sa, sb);
};

fn node_new(t: size, leaf: bool) (*node | nomem) = {
	let capk = 2 * t - 1;
	let capc = if (leaf) 0z else 2z * t;

	let empty_keys: [][]u8 = [];
	let keys = alloc(empty_keys, capk)?;

	let empty_vals: []*opaque = [];
	let vals = alloc(empty_vals, capk)?;

	let children: []*node = if (leaf) {
		yield [];
	} else {
		let empty_children: []*node = [];
		yield alloc(empty_children, capc)?;
	};

	let nd = alloc(node {
		leaf = leaf,
		keys = keys,
		vals = vals,
		children = children,
	})?;
	return nd;
};

fn split_child(m: *map, x: *node, i: size) (void | nomem) = {
	const t = m.t;
	let y = x.children[i];
	let z = node_new(t, y.leaf)?;

	let medk = y.keys[t - 1];
	let medv = y.vals[t - 1];

	append(z.keys, y.keys[t..]...)?;
	append(z.vals, y.vals[t..]...)?;
	if (!y.leaf) {
		append(z.children, y.children[t..]...)?;
	};

	y.keys = y.keys[..t - 1];
	y.vals = y.vals[..t - 1];
	if (!y.leaf) {
		y.children = y.children[..t];
	};

	insert(x.keys[i], medk)?;
	insert(x.vals[i], medv)?;
	insert(x.children[i + 1], z)?;
};

fn dup_u8(src: []u8) ([]u8 | nomem) = {
	return match (alloc(src, len(src))) {
	case let b: []u8 => yield b;
	case nomem => return nomem;
	};
};

fn insert_nonfull(m: *map, x: *node, key: []u8, val: *opaque) (void | nomem) = {
	let i = sort::lbisect((x.keys: []const opaque), size([]u8),
		(&key: const *opaque), &cmp_u8slice);

	if (i < len(x.keys) && bytes::equal(x.keys[i], key)) {
		x.vals[i] = val;
		return;
	};

	if (x.leaf) {
		insert(x.keys[i], key)?;
		let kcopy = match (dup_u8(key)) {
		case let b: []u8 => yield b;
		case nomem => return nomem;
		};
		insert(x.keys[i], kcopy)?;
		insert(x.vals[i], val)?;
		return;
	};

	if (len(x.children[i].keys) == 2 * m.t - 1) {
		split_child(m, x, i)?;
		let cmp = cmp_u8slice((&key: const *opaque),
			(&x.keys[i]: const *opaque));
		if (cmp == 0) {
			x.vals[i] = val;
			return;
		};
		if (cmp > 0) {
			i += 1;
		};
	};
	insert_nonfull(m, x.children[i], key, val)?;
};

fn merge_children(m: *map, x: *node, i: size) void = {
	let left = x.children[i];
	let right = x.children[i + 1];

	insert(left.keys[len(left.keys)], x.keys[i])!;
	insert(left.vals[len(left.vals)], x.vals[i])!;

	append(left.keys, right.keys...)!;
	append(left.vals, right.vals...)!;
	if (!left.leaf) {
		append(left.children, right.children...)!;
	};

	delete(x.keys[i]);
	delete(x.vals[i]);
	delete(x.children[i + 1]);
};

fn ensure_child_has_space(m: *map, x: *node, i: size) void = {
	const t = m.t;
	let c = x.children[i];

	if (len(c.keys) >= t) return;

	if (i > 0 && len(x.children[i - 1].keys) >= t) {
		let ls = x.children[i - 1];

		insert(c.keys[0], x.keys[i - 1])!;
		insert(c.vals[0], x.vals[i - 1])!;

		if (!c.leaf) {
			let moved = ls.children[len(ls.children) - 1];
			insert(c.children[0], moved)!;
			delete(ls.children[len(ls.children) - 1]);
		};

		x.keys[i - 1] = ls.keys[len(ls.keys) - 1];
		x.vals[i - 1] = ls.vals[len(ls.vals) - 1];
		delete(ls.keys[len(ls.keys) - 1]);
		delete(ls.vals[len(ls.vals) - 1]);
		return;
	};

	if (i + 1 < len(x.children) && len(x.children[i + 1].keys) >= t) {
		let rs = x.children[i + 1];

		insert(c.keys[len(c.keys)], x.keys[i])!;
		insert(c.vals[len(c.vals)], x.vals[i])!;

		if (!c.leaf) {
			let moved = rs.children[0];
			insert(c.children[len(c.children)], moved)!;
			delete(rs.children[0]);
		};

		x.keys[i] = rs.keys[0];
		x.vals[i] = rs.vals[0];
		delete(rs.keys[0]);
		delete(rs.vals[0]);
		return;
	};

	if (i + 1 < len(x.children)) {
		merge_children(m, x, i);
	} else {
		merge_children(m, x, i - 1);
	};
};

fn pop_max(m: *map, x: *node) ([]u8, *opaque) = {
	let cur = x;
	for (!cur.leaf) {
		let last_before = len(cur.children) - 1;
		ensure_child_has_space(m, cur, last_before);
		let last = len(cur.children) - 1;
		cur = cur.children[last];
	};
	let k = cur.keys[len(cur.keys) - 1];
	let v = cur.vals[len(cur.vals) - 1];
	delete(cur.keys[len(cur.keys) - 1]);
	delete(cur.vals[len(cur.vals) - 1]);
	return (k, v);
};

fn pop_min(m: *map, x: *node) ([]u8, *opaque) = {
	let cur = x;
	for (!cur.leaf) {
		ensure_child_has_space(m, cur, 0);
		cur = cur.children[0];
	};
	let k = cur.keys[0];
	let v = cur.vals[0];
	delete(cur.keys[0]);
	delete(cur.vals[0]);
	return (k, v);
};

fn delete_rec(m: *map, x: *node, key: []u8) (*opaque | void) = {
	let i = sort::lbisect((x.keys: []const opaque), size([]u8),
		(&key: const *opaque), &cmp_u8slice);

	if (i < len(x.keys) && bytes::equal(x.keys[i], key)) {
		if (x.leaf) {
			let ret = x.vals[i];
			free(x.keys[i]);
			delete(x.keys[i]);
			delete(x.vals[i]);
			return ret;
		};

		const t = m.t;
		let y = x.children[i];
		let z = x.children[i + 1];

		if (len(y.keys) >= t) {
			let (pk, pv) = pop_max(m, y);
			let ret = x.vals[i];
			let oldk = x.keys[i];
			x.keys[i] = pk;
			x.vals[i] = pv;
			free(oldk);
			return ret;
		} else if (len(z.keys) >= t) {
			let (sk, sv) = pop_min(m, z);
			let ret = x.vals[i];
			let oldk = x.keys[i];
			x.keys[i] = sk;
			x.vals[i] = sv;
			free(oldk);
			return ret;
		} else {
			merge_children(m, x, i);
			return delete_rec(m, y, key);
		};
	};

	if (x.leaf) {
		return;
	};

	ensure_child_has_space(m, x, i);
	if (i >= len(x.children)) {
		i = len(x.children) - 1;
	};
	return delete_rec(m, x.children[i], key);
};
// Deletes an item from a [[map]]. Returns the removed value or void.
export fn del(m: *map, key: []u8) (*opaque | void) = {
	let z = find_node(m, key);
	match (z) {
	case null => return;
	case let nodez: *node =>
		let ret = nodez.val;

		let y = nodez;
		let y_orig = y.color;

		let x: nullable *node = null;
		let p_for_fix: nullable *node = null;

		if (nodez.left == null) {
			x = nodez.right;
			p_for_fix = nodez.parent;
			transplant(m, nodez, nodez.right);
		} else if (nodez.right == null) {
			x = nodez.left;
			p_for_fix = nodez.parent;
			transplant(m, nodez, nodez.left);
		} else {
			let r = match (nodez.right) {
			case let rr: *node => yield rr;
			case null => abort("rb invariant violated: del: right is null");
			};
			let s = subtree_min(r);
			y = s;
			let yor = y.color;
			y_orig = yor;

			x = y.right;
			if (y.parent == (nodez: nullable *node)) {
				p_for_fix = y;
				set_parent(x, y);
			} else {
				p_for_fix = y.parent;
				transplant(m, y, y.right);
				y.right = nodez.right;
				set_parent(y.right, y);
			};

			transplant(m, nodez, y);
			y.left = nodez.left;
			set_parent(y.left, y);
			y.color = nodez.color;
		};

		free(nodez.key);
		free(nodez);

		if (y_orig == color::BLACK) {
			delete_fixup(m, x, p_for_fix);
		};

		return ret;
	};
};
fn free_subtree(n: nullable *node) void = {
	match (n) {
	case null => return;
	case let p: *node =>
		free_subtree(p.left);
		free_subtree(p.right);
		free(p.key);
		free(p);
	};
};

// Frees resources associated with a [[map]].
export fn finish(m: *map) void = {
	free_subtree(m.root);
	free(m);
};
// SPDX-License-Identifier: MPL-2.0

use bytes;

fn dup_u8(src: []u8) ([]u8 | nomem) = {
	return match (alloc(src, len(src))) {
	case let b: []u8 => yield b;
	case nomem => return nomem;
	};
};

export fn set(m: *map, key: []u8, value: *opaque) (void | nomem) = {
	match (find_node(m, key)) {
	case let ex: *node =>
		ex.val = value;
		return;
	case null => void;
	};

	let kcopy = match (dup_u8(key)) {
	case let b: []u8 => yield b;
	case nomem => return nomem;
	};

	let z = alloc(node {
		color = color::RED,
		key = key,
		key = kcopy,
		val = value,
		left = null,
		right = null,
		parent = null,
	})?;

	let y: nullable *node = null;
	let x = m.root;

	for (true) {
		match (x) {
		case null => break;
		case let xn: *node =>
			y = xn;
			if (keycmp(z.key, xn.key) < 0) {
				x = xn.left;
			} else {
				x = xn.right;
			};
		};
	};

	z.parent = y;
	match (y) {
	case null =>
		m.root = z;
	case let yn: *node =>
		if (keycmp(z.key, yn.key) < 0) {
			yn.left = z;
		} else {
			yn.right = z;
		};
	};

	insert_fixup(m, z);
};
// SPDX-License-Identifier: MPL-2.0

use bytes;

// Deletes an item from a [[map]]. Returns the removed value or void.
export fn del(m: *map, key: []u8) (*opaque | void) = {
	for (let i = 0z; i < len(m.items); i += 1) {
		if (bytes::equal(m.items[i].0, key)) {
			let v = m.items[i].1;
			free(m.items[i].0);
			delete(m.items[i]);
			return v;
		};
	};
};
// SPDX-License-Identifier: MPL-2.0

// Frees resources associated with a [[map]].
export fn finish(m: *map) void = {
	for (let i = 0z; i < len(m.items); i += 1) {
		free(m.items[i].0);
	};
	free(m.items);
	free(m);
};
// SPDX-License-Identifier: MPL-2.0

use bytes;

fn dup_u8(src: []u8) ([]u8 | nomem) = {
	return match (alloc(src, len(src))) {
	case let b: []u8 => yield b;
	case nomem => return nomem;
	};
};

// 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) = {
	for (let i = 0z; i < len(m.items); i += 1) {
		if (bytes::equal(m.items[i].0, key)) {
			m.items[i].1 = value;
			return;
		};
	};
	append(m.items, (key, value))?;
	let kb = match (dup_u8(key)) {
	case let b: []u8 => yield b;
	case nomem => return nomem;
	};
	append(m.items, (kb, value))?;
};
use sort;

// Deletes an item from a [[map]]. Returns the removed value or void.
export fn del(m: *map, key: []u8) (*opaque | void) = {
	let dummy = 0;
	let probe = (key, (&dummy: *opaque));
	match (sort::search((m.items: []const opaque),
		size(([]u8, *opaque)),
		(&probe: const *opaque), &cmp_kv)) {
	case let idx: size =>
		let v = m.items[idx].1;
		free(m.items[idx].0);
		delete(m.items[idx]);
		return v;
	case void =>
		return;
	};
};
// SPDX-License-Identifier: MPL-2.0

// Frees resources associated with a [[map]].
export fn finish(m: *map) void = {
	for (let i = 0z; i < len(m.items); i += 1) {
		free(m.items[i].0);
	};
	free(m.items);
	free(m);
};
use sort;

fn dup_u8(src: []u8) ([]u8 | nomem) = {
	return match (alloc(src, len(src))) {
	case let b: []u8 => yield b;
	case nomem => return nomem;
	};
};

// 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) = {
	let dummy = 0;
	let probe = (key, (&dummy: *opaque));

	match (sort::search((m.items: []const opaque),
		size(([]u8, *opaque)),
		(&probe: const *opaque), &cmp_kv)) {
	case let idx: size =>
		m.items[idx].1 = value;
	case void =>
		let ins = sort::lbisect((m.items: []const opaque),
			size(([]u8, *opaque)),
			(&probe: const *opaque), &cmp_kv);
		insert(m.items[ins], (key, value))?;
		let kb = match (dup_u8(key)) {
		case let b: []u8 => yield b;
		case nomem => return nomem;
		};
		insert(m.items[ins], (kb, value))?;
	};
};
// SPDX-License-Identifier: Apache-2.0 AND MPL-2.0
// SPDX-FileCopyrightText: 2024 The Cockroach Authors
// SPDX-FileCopyrightText: 2025 Runxi Yu

use bytes;

// Deletes an item from a [[map]]. Returns the removed value or void.
export fn del(m: *map, key: []u8) (*opaque | void) = {
	if (len(m.groups) == 0) return;
	let hv = m.hash64(m.hash_params, key): u64;
	let t = h2(hv);
	let mask = m.group_mask;
	let off: size = (h1(hv): size) & mask;
	let idx: size = 0;

	for (true) {
		let g = &m.groups[off];
		for (let i = 0z; i < GROUP_SIZE; i += 1) {
			let c = g.ctrl[i];
			if (is_full_ctrl(c) && c == t) {
				if (bytes::equal(g.keys[i], key)) {
					let v = g.vals[i];
					g.ctrl[i] = CTRL_DELETED;
					free(g.keys[i]);
					g.keys[i] = [];
					g.vals[i] = null;
					m.used -= 1;
					m.tombs += 1;
					// elide the tombstones if exceed 1/3 of the capacity
					if (m.tombs * 3 >= capacity_slots(m)) {
						rehash_in_place(m);
					};
					match (v) {
					case null =>
						abort("map: null internal state escaped");
					case let p: *opaque =>
						return p;
					};
				};
			} else if (c == CTRL_EMPTY) {
				return;
			};
		};
		let next = probe_next(off, idx, mask);
		off = next.0;
		idx = next.1;
	};
};
// SPDX-License-Identifier: Apache-2.0 AND MPL-2.0
// SPDX-FileCopyrightText: 2024 The Cockroach Authors
// SPDX-FileCopyrightText: 2025 Runxi Yu

// Frees resources associated with a [[map]].
export fn finish(m: *map) void = {
	if (len(m.groups) != 0) {
		for (let gi = 0z; gi <= m.group_mask; gi += 1) {
			let g = &m.groups[gi];
			for (let si = 0z; si < GROUP_SIZE; si += 1) {
				let c = g.ctrl[si];
				if (!is_full_ctrl(c)) continue;
				free(g.keys[si]);
			};
		};
		free(m.groups);
	};
	free(m);
};
// SPDX-License-Identifier: Apache-2.0 AND MPL-2.0
// SPDX-FileCopyrightText: 2024 The Cockroach Authors
// SPDX-FileCopyrightText: 2025 Runxi Yu

use ds::map;
use bytes;

fn dup_u8(src: []u8) ([]u8 | nomem) = {
	return match (alloc(src, len(src))) {
	case let b: []u8 => yield b;
	case nomem => return nomem;
	};
};

// 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) = {
	let need_insert = true;

	if (len(m.groups) != 0) {
		let hv0 = m.hash64(m.hash_params, key);
		let t0 = h2(hv0: u64);
		let mask0 = m.group_mask;
		let off0: size = (h1(hv0: u64): size) & mask0;
		let idx0: size = 0;

		need_insert = false;
		for (true) {
			let g = &m.groups[off0];

			for (let i = 0z; i < GROUP_SIZE; i += 1) {
				let c = g.ctrl[i];
				if (is_full_ctrl(c) && c == t0) {
					if (bytes::equal(g.keys[i], key)) {
						g.vals[i] = value;
						return;
					};
				} else if (c == CTRL_EMPTY) {
					need_insert = true;
					break;
				};
			};

			if (need_insert) {
				break;
			};

			let next = probe_next(off0, idx0, mask0);
			off0 = next.0;
			idx0 = next.1;
		};
	} else {
		need_insert = true;
	};

	if (!need_insert) {
		return;
	};

	match (ensure_capacity_for_insert(m)) {
	case void => yield;
	case nomem => return nomem;
	};

	let hv = m.hash64(m.hash_params, key): u64;
	let t = h2(hv);
	let mask = m.group_mask;
	let off: size = (h1(hv): size) & mask;
	let idx: size = 0;

	for (true) {
		let g = &m.groups[off];
		let first_dead: (size | void) = void;

		for (let i = 0z; i < GROUP_SIZE; i += 1) {
			let c = g.ctrl[i];
			if (is_full_ctrl(c)) {
				if (c == t && bytes::equal(g.keys[i], key)) {
					g.vals[i] = value;
					return;
				};
				continue;
			} else if (c == CTRL_DELETED) {
				if (first_dead is void) first_dead = i;
			} else {
				let slot = match (first_dead) {
				case void => yield i;
				case let di: size => yield di;
				};
				g.keys[slot] = key;
				let kb = match (dup_u8(key)) {
				case let b: []u8 => yield b;
				case nomem => return nomem;
				};
				g.keys[slot] = kb;
				g.vals[slot] = value;
				g.ctrl[slot] = t;
				m.used += 1;
				if (slot != i) {
					m.tombs -= 1;
				};
				return;
			};
		};

		let next = probe_next(off, idx, mask);
		off = next.0;
		idx = next.1;
	};
};