ast.Node 가 이름이 무엇으로 해석됐는지 들고, Map 이 저장소를 유지한 채 키만 잊는다. 스코프가 끝날 때 표를 다음 스코프에 넘기는 것이 리졸버가 원하는 모양이다 -- 함수마다가 아니라 중첩 단계마다 표 하나. bind 는 Name 노드의 남는 a 필드를 재활용할 수도 있었지만 명시적인 쪽을 골랐다. 노드가 32 에서 36 바이트가 되는 값으로 그 자리가 무엇인지 이름이 말한다. clear 는 아무도 부르지 않는 채로 들어와 있었다. maps.fe 가 이제 부른다: 키가 사라지고, 방은 64 로 남고, 그 위에 다시 채워도 버퍼를 새로 잡지 않는다. cleared 0 room 64 gone 0 / refilled 3 / balanced GOAL.md 를 더했다. 외부 감사와, 그 항목들을 실제로 빌드해서 확인한 결과를 합친 P0~P4 다. 228/228, 32/32.
176 lines
5.8 KiB
Plaintext
176 lines
5.8 KiB
Plaintext
unit std.map;
|
|
|
|
// A table from a run of bytes to a value.
|
|
//
|
|
// A compiler looks names up constantly and a linear scan over a list is the
|
|
// wrong shape for that. Keys are copied into one buffer the map owns and each
|
|
// slot records where in it the key sits -- the arena-and-handle shape R11 asks
|
|
// for, which also means letting go of the map is two frees and not one per
|
|
// entry. Both buffers are owned, so R1 does the letting go and no `drop` is
|
|
// written here.
|
|
//
|
|
// Open addressing with linear probing. The table is a power of two so the
|
|
// index is a mask rather than a division, and it grows at three quarters full
|
|
// because probing gets long well before the table gets full.
|
|
|
|
pub struct Slot(V) {
|
|
at: usize,
|
|
len: usize,
|
|
used: bool,
|
|
value: V,
|
|
}
|
|
|
|
pub struct Map(V) {
|
|
slots: ^[]mut Slot(V),
|
|
bytes: ^[]mut u8,
|
|
used_bytes: usize,
|
|
count: usize,
|
|
|
|
pub fn with_capacity(n: usize) -> !Self {
|
|
var room: usize = 8;
|
|
while room < n * 2 { room = room * 2; }
|
|
let table: ^[]mut Slot(V) = try mem.alloc_slice(Slot(V), room);
|
|
let text: ^[]mut u8 = try mem.alloc_slice(u8, 64);
|
|
var i: usize = 0;
|
|
while i < room {
|
|
table.^[i].used = false;
|
|
i = i + 1;
|
|
}
|
|
return Self{ slots: table, bytes: text, used_bytes: 0, count: 0 };
|
|
}
|
|
|
|
pub fn count_of(self: &Self) -> usize { return self.count; }
|
|
|
|
/// Forget every key but keep the storage. A scope that ends can hand its
|
|
/// table to the next one without going back to the allocator, which is
|
|
/// what a resolver wants: one table per nesting level, not per function.
|
|
pub fn clear(self: &mut Self) -> void {
|
|
var i: usize = 0;
|
|
while i < self.slots.^.n {
|
|
self.slots.^[i].used = false;
|
|
i = i + 1;
|
|
}
|
|
self.used_bytes = 0;
|
|
self.count = 0;
|
|
return;
|
|
}
|
|
|
|
pub fn room(self: &Self) -> usize { return self.slots.^.n; }
|
|
|
|
/// Where `key` sits in the table: the slot holding it, or the first free
|
|
/// slot it could go in. Probing stops at a free slot, which is why a slot
|
|
/// is never cleared -- only ever filled.
|
|
fn find(self: &Self, key: []u8) -> usize {
|
|
let mask: usize = self.slots.^.n - 1;
|
|
var at: usize = hash(key) & mask;
|
|
while true {
|
|
if not self.slots.^[at].used { return at; }
|
|
if self.same(at, key) { return at; }
|
|
at = (at + 1) & mask;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
fn same(self: &Self, slot: usize, key: []u8) -> bool {
|
|
if self.slots.^[slot].len != key.n { return false; }
|
|
let from: usize = self.slots.^[slot].at;
|
|
var i: usize = 0;
|
|
while i < key.n {
|
|
if self.bytes.^[from + i] != key[i] { return false; }
|
|
i = i + 1;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
pub fn has(self: &Self, key: []u8) -> bool {
|
|
return self.slots.^[self.find(key)].used;
|
|
}
|
|
|
|
pub fn get(self: &Self, key: []u8, missing: V) -> V {
|
|
let at: usize = self.find(key);
|
|
if self.slots.^[at].used { return self.slots.^[at].value; }
|
|
return missing;
|
|
}
|
|
|
|
pub fn put(self: &mut Self, key: []u8, value: V) -> !void {
|
|
if self.count * 4 >= self.slots.^.n * 3 { try self.regrow(); }
|
|
let at: usize = self.find(key);
|
|
if self.slots.^[at].used {
|
|
self.slots.^[at].value = value;
|
|
return;
|
|
}
|
|
try self.keep(key, at);
|
|
self.slots.^[at].value = value;
|
|
self.slots.^[at].used = true;
|
|
self.count = self.count + 1;
|
|
return;
|
|
}
|
|
|
|
/// Copy `key` into the byte buffer and point the slot at it, moving to a
|
|
/// bigger buffer first if it does not fit.
|
|
fn keep(self: &mut Self, key: []u8, slot: usize) -> !void {
|
|
let at: usize = self.used_bytes;
|
|
if at + key.n > self.bytes.^.n {
|
|
var room: usize = self.bytes.^.n;
|
|
while room < at + key.n { room = room * 2; }
|
|
let bigger: ^[]mut u8 = try mem.alloc_slice(u8, room);
|
|
var k: usize = 0;
|
|
while k < at {
|
|
bigger.^[k] = self.bytes.^[k];
|
|
k = k + 1;
|
|
}
|
|
let old: ^[]mut u8 = mem.replace(&mut self.bytes, bigger);
|
|
mem.destroy(old);
|
|
}
|
|
var i: usize = 0;
|
|
while i < key.n {
|
|
self.bytes.^[at + i] = key[i];
|
|
i = i + 1;
|
|
}
|
|
self.slots.^[slot].at = at;
|
|
self.slots.^[slot].len = key.n;
|
|
self.used_bytes = at + key.n;
|
|
return;
|
|
}
|
|
|
|
/// Twice the slots, everything placed again. The keys do not move: they
|
|
/// live in the byte buffer and the slots only point at them.
|
|
fn regrow(self: &mut Self) -> !void {
|
|
let bigger: ^[]mut Slot(V) = try mem.alloc_slice(Slot(V),
|
|
self.slots.^.n * 2);
|
|
let mask: usize = bigger.^.n - 1;
|
|
var i: usize = 0;
|
|
while i < bigger.^.n {
|
|
bigger.^[i].used = false;
|
|
i = i + 1;
|
|
}
|
|
i = 0;
|
|
while i < self.slots.^.n {
|
|
if self.slots.^[i].used {
|
|
let from: usize = self.slots.^[i].at;
|
|
let len: usize = self.slots.^[i].len;
|
|
var at: usize = hash(self.bytes.^[from..from + len]) & mask;
|
|
while bigger.^[at].used { at = (at + 1) & mask; }
|
|
bigger.^[at] = self.slots.^[i];
|
|
}
|
|
i = i + 1;
|
|
}
|
|
let old: ^[]mut Slot(V) = mem.replace(&mut self.slots, bigger);
|
|
mem.destroy(old);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/// FNV-1a. Small, fast, and good enough for identifiers; nothing here has to
|
|
/// resist an adversary choosing the keys.
|
|
pub fn hash(key: []u8) -> usize {
|
|
var h: u32 = 2166136261;
|
|
var i: usize = 0;
|
|
while i < key.n {
|
|
h = h ^ (key[i] as u32);
|
|
h = h * 16777619;
|
|
i = i + 1;
|
|
}
|
|
return h as usize;
|
|
}
|