Files
doslang-mirror/fec/std/map.fe
T
coolguy f7e667652e 대여는 변수가 아니라 place 단위다
p.a 와 p.b 는 서로 다른 자리인데 한쪽을 대여하면 다른 쪽까지 잠겼다. 메서드가
하는 일의 대부분이 한 필드에 쓰면서 다른 필드를 읽는 것이라, std.map 의 keep 은
그것 때문에 함수 둘로 갈라져 있었고 파서도 같은 자리에서 걸렸다.

FeOwnState 가 필드별 칸을 넷 갖는다. 값으로 복사되는 구조체라 흐름 병합과
스냅샷은 손댈 것이 없었다. 전체를 대여하면 모든 필드와 충돌하고, 필드를
대여하면 전체 및 같은 필드와 충돌한다. 칸이 모자라면 전체 대여로 되돌아가
더 많이 보고할 뿐 놓치지 않는다.

읽기는 식별자에서 일어나는데 그 자리에서는 자기가 무엇의 밑동인지 알 수 없다.
그래서 투영이 내려가는 길에 어느 필드인지 적어두고 식별자가 그것을 집는다.
인덱스는 갈라지지 않는다 -- xs[i] 의 i 는 상수가 아닐 수 있고, 필드 이름은
상수다.

길에서 나온 것: mem.replace 가 목적지 대여를 가져가고 돌려주지 않았다. 일반
호출 인자는 문장 끝에 돌려주는데 intrinsic 경로에만 그것이 없었다. 전에는
그 자리가 어차피 거부돼서 드러나지 않았다.

  var p = Pair{ a: 1, b: 2 };
  let r = &mut p.a;
  p.b = 3;      // ok -- 전에는 에러
  p.a = 3;      // 에러
  take(p);      // 에러

SPEC §5 R6 을 고쳤고, 옛 규칙을 그대로 적어둔 문단과 예제를 갈아치웠다.
own/badrfld 는 이제 허용되는 코드였으므로 같은 필드를 건드리도록 다시 겨눴고
okrfld·badrall·badrsame·exec/fieldbrw 를 더했다.

228/228, 32/32.
2026-08-17 15:29:12 +09:00

162 lines
5.3 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; }
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;
}