stage2 更多的工具库 stage1 修复问题

This commit is contained in:
zzy committed 2026-09-18 10:10:53 +08:00
1 parent 16a1f030d4
commit b3e38ba7e4
28 files changed
+2274 -523

No files matched your search

+124 -59
View File
@@ -1,4 +1,5 @@
// Expect retcode = 0
// 开放寻址 + tombstone 哈希表。键和值都按字节复制到表自己的存储。
// 键类型由 hash_fn / eq_fn 解释:回调收到指向键字节的指针(如 *usize / *ByteView)。
// 普通赋值只复制句柄(与 Vec 一样),所有权转移必须使用 take。
@@ -7,11 +8,29 @@ type .{ .Error, .Status, .make } = @import("error.spl");
type .{ .Vec } = @import("vec.spl");
type .{ .ByteView } = @import("byte_view.spl");
type .{ .hash_usize, .eq_usize, .hash_view, .eq_view } = @import("hash.spl");
type .{ .checked_add, .checked_mul, .grow_capacity } = @import("memory.spl");
type .{ .copy } = @import("../sys/mem.spl");
// 槽位状态:0=EMPTY,1=OCCUPIED,2=DELETED(墓碑,仅在扩容重哈希时清除)。
// 槽位状态:Empty/Occupied/Deleted(墓碑,仅在扩容重哈希时清除)。
// 负载因子 70/128。
type SlotState = enum {
Empty,
Occupied,
Deleted,
}
fn state_code(state: SlotState): u8 {
var s: SlotState = state;
ret @enum2int(s) as u8;
}
// 查找/删除结果:显式区分命中与缺失,替代 bool 哨兵。
type LookupResult = enum {
Found,
NotFound,
}
type Map = @self;
keys: Vec,
@@ -64,12 +83,22 @@ fn slot_count(self: Map): usize {
ret self.states.len;
}
fn slot_state(self: Map, index: usize): u8 {
ret self.states.data[index];
fn slot_state(self: Map, index: usize): SlotState {
var code = self.states.data[index];
var occupied: SlotState = .Occupied;
var deleted_state: SlotState = .Deleted;
if code == state_code(occupied) { ret .Occupied; }
if code == state_code(deleted_state) { ret .Deleted; }
ret .Empty;
}
fn slot_occupied(state: u8): bool {
ret state == 1;
fn slot_occupied(state: SlotState): bool {
match state {
.Occupied => { ret true; },
.Empty => { ret false; },
.Deleted => { ret false; },
}
ret false;
}
fn key_at(self: *Map, index: usize): *u8 {
@@ -85,8 +114,11 @@ fn find_slot(self: *Map, key: *u8): usize {
if self.states.cap == 0 { ret ~(0 as usize); }
var mask = self.states.cap - 1;
var i = self.hash_fn(key) & mask;
while self.states.data[i] != 0 {
if self.states.data[i] == 1 && self.eq_fn(self.keys.data + i * self.key_size, key) {
var empty: SlotState = .Empty;
var occupied: SlotState = .Occupied;
while self.states.data[i] != state_code(empty) {
if self.states.data[i] == state_code(occupied) &&
self.eq_fn(self.keys.data + i * self.key_size, key) {
ret i;
}
i = (i + 1) & mask;
@@ -98,12 +130,14 @@ fn find_slot(self: *Map, key: *u8): usize {
fn place(self: *Map, key: *u8, val: *u8) {
var mask = self.states.cap - 1;
var i = self.hash_fn(key) & mask;
while self.states.data[i] != 0 {
var empty: SlotState = .Empty;
var occupied: SlotState = .Occupied;
while self.states.data[i] != state_code(empty) {
i = (i + 1) & mask;
}
copy((self.keys.data + i * self.key_size) as *any, key as *any, self.key_size);
copy((self.vals.data + i * self.val_size) as *any, val as *any, self.val_size);
self.states.data[i] = 1;
self.states.data[i] = state_code(occupied);
self.size = self.size + 1;
}
@@ -112,15 +146,7 @@ fn reserve(self: *Map, need: usize): Status {
if !valid(self.*) { ret .Err(make(.InvalidArgument)); }
if need <= self.states.cap { ret .Ok; }
var new_cap = self.states.cap;
if new_cap < 8 { new_cap = 8; }
while new_cap < need {
if new_cap > (~(0 as usize)) / 2 {
new_cap = need;
break;
}
new_cap = new_cap * 2;
}
var new_cap = grow_capacity(self.states.cap, 8, need);
var fresh = self.*;
fresh.keys = Vec.blank(self.key_size, self.keys.elem_align);
@@ -145,14 +171,16 @@ fn reserve(self: *Map, need: usize): Status {
fresh.states.len = new_cap;
var i: usize = 0;
var empty: SlotState = .Empty;
var occupied: SlotState = .Occupied;
while i < new_cap {
fresh.states.data[i] = 0;
fresh.states.data[i] = state_code(empty);
i = i + 1;
}
i = 0;
while i < self.states.len {
if self.states.data[i] == 1 {
if self.states.data[i] == state_code(occupied) {
place(&fresh, self.keys.data + i * self.key_size,
self.vals.data + i * self.val_size);
}
@@ -181,42 +209,64 @@ fn put(self: *Map, key: *u8, val: *u8): Status {
ret .Ok;
}
if self.states.cap == 0 || (self.size + 1) * 128 / self.states.cap >= 70 {
var need = self.states.cap * 2;
if self.states.cap == 0 { need = 8; }
match reserve(self, need) {
if self.states.cap == 0 {
match reserve(self, 8) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
} else {
var next_size: usize = 0;
match checked_add(self.size, 1) {
.Ok(value) => { next_size = value; },
.Err(error) => { ret .Err(error); },
}
var scaled: usize = 0;
match checked_mul(next_size, 128) {
.Ok(value) => { scaled = value; },
.Err(error) => { ret .Err(error); },
}
if scaled / self.states.cap >= 70 {
var need: usize = 0;
match checked_mul(self.states.cap, 2) {
.Ok(value) => { need = value; },
.Err(error) => { ret .Err(error); },
}
match reserve(self, need) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
}
}
place(self, key, val);
ret .Ok;
}
// 找到键则把值复制到 out 并返回 true;否则 out 不变并返回 false。
fn get(self: *Map, key: *u8, out: *u8): bool {
if !valid(self.*) || key == null || out == null { ret false; }
// 找到键则把值复制到 out 并返回 Found;否则 out 不变并返回 NotFound。
fn get(self: *Map, key: *u8, out: *u8): LookupResult {
if !valid(self.*) || key == null || out == null { ret .NotFound; }
var i = find_slot(self, key);
if i == ~(0 as usize) { ret false; }
if i == ~(0 as usize) { ret .NotFound; }
copy(out as *any, (self.vals.data + i * self.val_size) as *any, self.val_size);
ret true;
ret .Found;
}
fn del(self: *Map, key: *u8): bool {
if !valid(self.*) || key == null { ret false; }
fn del(self: *Map, key: *u8): LookupResult {
if !valid(self.*) || key == null { ret .NotFound; }
var i = find_slot(self, key);
if i == ~(0 as usize) { ret false; }
self.states.data[i] = 2;
if i == ~(0 as usize) { ret .NotFound; }
var deleted_state: SlotState = .Deleted;
self.states.data[i] = state_code(deleted_state);
self.size = self.size - 1;
ret true;
ret .Found;
}
// 清空内容但保留容量。墓碑与负载计数一并复位。
fn clear(self: *Map) {
var i: usize = 0;
var empty: SlotState = .Empty;
while i < self.states.len {
self.states.data[i] = 0;
self.states.data[i] = state_code(empty);
i = i + 1;
}
self.size = 0;
@@ -269,6 +319,22 @@ fn count_occupied(m: *Map): usize {
ret n;
}
fn found(m: *Map, key: *u8, out: *u8): bool {
match get(m, key, out) {
.Found => { ret true; },
.NotFound => { ret false; },
}
ret false;
}
fn deleted(m: *Map, key: *u8): bool {
match del(m, key) {
.Found => { ret true; },
.NotFound => { ret false; },
}
ret false;
}
fn make_int_map(): Map {
ret new(@sizeof(usize), @alignof(usize), @sizeof(i32), @alignof(i32), hash_usize, eq_usize);
}
@@ -292,24 +358,26 @@ fn test_put_get_update() {
@assert(size(m) == 1 && valid(m));
out = -1;
@assert(get(&m, &key as *u8, &out as *u8) && out == 70, "insert then get");
@assert(found(&m, &key as *u8, &out as *u8) && out == 70, "insert then get");
value = 71;
expect_ok(put(&m, &key as *u8, &value as *u8));
@assert(size(m) == 1, "update does not grow size");
out = -1;
@assert(get(&m, &key as *u8, &out as *u8) && out == 71, "update overwrites");
@assert(found(&m, &key as *u8, &out as *u8) && out == 71, "update overwrites");
key = 8;
out = 123;
@assert(!get(&m, &key as *u8, &out as *u8) && out == 123, "missing key preserves out");
@assert(!found(&m, &key as *u8, &out as *u8) && out == 123, "missing key preserves out");
// drop 后还有断言,故保持手动释放,不改为 defer。
drop(&m);
@assert(size(m) == 0 && capacity(m) == 0, "drop resets map");
}
fn test_growth_and_delete() {
var m = make_int_map();
defer { drop(&m); }
var i: i32 = 0;
while i < 1000 {
var key: usize = i as usize;
@@ -324,7 +392,7 @@ fn test_growth_and_delete() {
while i < 1000 {
var key: usize = i as usize;
var out: i32 = 0;
@assert(get(&m, &key as *u8, &out as *u8), "get after growth");
@assert(found(&m, &key as *u8, &out as *u8), "get after growth");
@assert(out == i * 3 - 7, "value after growth");
i = i + 1;
}
@@ -332,7 +400,7 @@ fn test_growth_and_delete() {
i = 0;
while i < 500 {
var key: usize = i as usize;
@assert(del(&m, &key as *u8));
@assert(deleted(&m, &key as *u8));
i = i + 1;
}
@assert(size(m) == 500 && count_occupied(&m) == 500, "deleted slots not occupied");
@@ -341,7 +409,7 @@ fn test_growth_and_delete() {
while i < 500 {
var key: usize = i as usize;
var out: i32 = 0;
@assert(!get(&m, &key as *u8, &out as *u8), "deleted key missing");
@assert(!found(&m, &key as *u8, &out as *u8), "deleted key missing");
i = i + 1;
}
@@ -351,41 +419,40 @@ fn test_growth_and_delete() {
expect_ok(put(&m, &key as *u8, &value as *u8));
@assert(size(m) == 501);
var out: i32 = 0;
@assert(get(&m, &key as *u8, &out as *u8) && out == 99, "reuse tombstone");
@assert(found(&m, &key as *u8, &out as *u8) && out == 99, "reuse tombstone");
@assert(del(&m, &key as *u8), "delete key");
@assert(deleted(&m, &key as *u8), "delete key");
@assert(size(m) == 500);
@assert(!get(&m, &key as *u8, &out as *u8));
@assert(!found(&m, &key as *u8, &out as *u8));
clear(&m);
@assert(size(m) == 0 && count_occupied(&m) == 0, "clear empties");
drop(&m);
}
fn test_take_and_self_put() {
var m = make_int_map();
defer { drop(&m); }
var key: usize = 5;
var value: i32 = 55;
expect_ok(put(&m, &key as *u8, &value as *u8));
var moved = take(&m);
defer { drop(&moved); }
@assert(size(m) == 0 && capacity(m) == 0, "take leaves source empty");
var out: i32 = 0;
@assert(get(&moved, &key as *u8, &out as *u8) && out == 55, "moved keeps content");
@assert(found(&moved, &key as *u8, &out as *u8) && out == 55, "moved keeps content");
// 值指针可以指向表内已有的值(按字节移动,允许重叠)。
var alias: i32 = 0;
@assert(get(&moved, &key as *u8, &alias as *u8));
@assert(found(&moved, &key as *u8, &alias as *u8));
expect_ok(put(&moved, &key as *u8, &alias as *u8));
@assert(get(&moved, &key as *u8, &out as *u8) && out == 55, "self value put");
drop(&moved);
@assert(found(&moved, &key as *u8, &out as *u8) && out == 55, "self value put");
}
fn test_byte_view_keys() {
var m = new(@sizeof(ByteView), @alignof(ByteView), @sizeof(i32), @alignof(i32),
hash_view, eq_view);
defer { drop(&m); }
var alpha = ByteView.from_slice("alpha");
var beta = ByteView.from_slice("beta");
@@ -400,21 +467,19 @@ fn test_byte_view_keys() {
@assert(size(m) == 3);
var out: i32 = 0;
@assert(get(&m, &alpha as *u8, &out as *u8) && out == 1, "alpha");
@assert(get(&m, &beta as *u8, &out as *u8) && out == 2, "beta");
@assert(get(&m, &gamma as *u8, &out as *u8) && out == 3, "gamma");
@assert(found(&m, &alpha as *u8, &out as *u8) && out == 1, "alpha");
@assert(found(&m, &beta as *u8, &out as *u8) && out == 2, "beta");
@assert(found(&m, &gamma as *u8, &out as *u8) && out == 3, "gamma");
// 内容相同的不同 ByteView 视为同一个键。
var alpha2 = ByteView.from_slice("alpha");
out = 0;
@assert(get(&m, &alpha2 as *u8, &out as *u8) && out == 1, "equal content key");
@assert(found(&m, &alpha2 as *u8, &out as *u8) && out == 1, "equal content key");
@assert(size(m) == 3, "equal content does not insert");
@assert(del(&m, &beta as *u8), "delete beta");
@assert(deleted(&m, &beta as *u8), "delete beta");
@assert(size(m) == 2);
@assert(!get(&m, &beta as *u8, &out as *u8));
drop(&m);
@assert(!found(&m, &beta as *u8, &out as *u8));
}
fn main() {