GOAL P3-1/3-2: 핸들로 닿는 아레나
R11 이 데이터가 데이터를 가리킬 때 내놓는 답이다. 아레나가 값을 소유하고 핸들은 숫자라서 참조가 아니고, R4 가 반대할 것이 없다. mem.Arena 옆에 두되 다른 자료구조다 -- 저쪽은 크기가 제각각인 것을 오프셋으로 나눠주고, 이쪽은 슬롯마다 고정된 T 를 담고 슬롯을 되받는다. 핸들은 믿는 것이 아니라 검사받는다. 슬롯의 세대, 아레나의 epoch, 어느 아레나에서 왔는지를 8바이트에 담아서, 놓아준 뒤에 쓰거나 reset 뒤에 쓰거나 다른 아레나에 물어보면 옆값이 아니라 null 이 나온다. 여덟 바이트는 빌드마다 같다 -- --no-checks 는 비교를 건너뛸 뿐 배치를 바꾸지 않는다. 세대가 다 닳은 슬롯은 재사용 목록에 넣지 않고 버린다. 한 바퀴 돌면 옛 핸들이 다시 유효해지는데, 그것이 세대가 막으려던 바로 그 일이다. get 은 사본을 돌려준다. 대여가 문장을 넘기지 않는 이유가 그것이고, 그래서 한 슬롯을 읽으면서 다른 슬롯에 쓸 수 있다. get_mut 은 두지 않았다 -- 아레나 하나를 통째로 잠그는 참조를 오래 들고 있게 하는 API 다. 길에서 고친 것: 메서드 호출이 시그니처를 호출자 유닛에서 풀고 있었다. 그래서 Arena(T) 안의 Handle(T) 를 부르는 쪽 유닛에서 찾다가 실패했다. 필드 타입에 쓰던 enter_declaring_unit 을 method_type 에도 물렸다. 241/241, 36/36.
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
unit std.arena;
|
||||
|
||||
import std.mem;
|
||||
|
||||
// An arena of `T` reached by handle, and the handle that reaches it.
|
||||
//
|
||||
// This is the shape SPEC R11 asks for when data points at data: the arena owns
|
||||
// the values and a handle is a number, so nothing here is a reference and R4
|
||||
// has nothing to object to. `mem.Arena` next door is a different structure --
|
||||
// a block of bytes handed out by offset, for data whose size varies. This one
|
||||
// holds a fixed `T` per slot and can take slots back.
|
||||
//
|
||||
// A handle is checked, not trusted. It carries the generation of the slot it
|
||||
// was made from, the epoch of the arena, and which arena it came from, so
|
||||
// using it after the slot was freed, after the arena was reset, or against
|
||||
// some other arena all answer `null` rather than a neighbouring value.
|
||||
//
|
||||
// `T` is expected to be Copy -- integers, handles, small records. `get`
|
||||
// answers with a copy, which is the whole reason a borrow never outlives the
|
||||
// statement it was taken in. For a `T` that owns something, `take` moves it
|
||||
// out and leaves a replacement (SPEC 5 R7).
|
||||
|
||||
/// No slot. Every real index is smaller.
|
||||
pub const NONE: u32 = 4294967295;
|
||||
|
||||
/// Eight bytes, and the same eight in every build: `--no-checks` skips the
|
||||
/// comparison, never the layout. A handle that changed shape with a flag
|
||||
/// would not survive being written down.
|
||||
pub struct Handle(T) {
|
||||
pub index: u32,
|
||||
/// `arena 8 | epoch 8 | generation 16`
|
||||
pub tag: u32,
|
||||
|
||||
pub fn none() -> Self { return Self{ index: NONE, tag: 0 }; }
|
||||
|
||||
pub fn is_none(self: &Self) -> bool { return self.index == NONE; }
|
||||
|
||||
pub fn same(self: &Self, other: Handle(T)) -> bool {
|
||||
return self.index == other.index and self.tag == other.tag;
|
||||
}
|
||||
}
|
||||
|
||||
struct Slot(T) {
|
||||
value: T,
|
||||
/// Bumped every time the slot is freed, so old handles stop matching.
|
||||
gen: u32,
|
||||
live: bool,
|
||||
/// The next slot on the free list, or `NONE`.
|
||||
next: u32,
|
||||
}
|
||||
|
||||
pub struct Arena(T) {
|
||||
slots: ^[]mut Slot(T),
|
||||
/// How many slots have ever been handed out; slots past this are untouched.
|
||||
high: usize,
|
||||
/// How many are live right now.
|
||||
count: usize,
|
||||
free: u32,
|
||||
id: u32,
|
||||
epoch: u32,
|
||||
|
||||
/// `id` tells one arena from another in a handle. A program with a handful
|
||||
/// of arenas numbers them itself; eight bits is more than that needs.
|
||||
pub fn with_capacity(id: u32, n: usize) -> !Self {
|
||||
let room: ^[]mut Slot(T) = try mem.alloc_slice(Slot(T), n);
|
||||
return Self{ slots: room, high: 0, count: 0, free: NONE,
|
||||
id: id % 256, epoch: 0 };
|
||||
}
|
||||
|
||||
pub fn len(self: &Self) -> usize { return self.count; }
|
||||
|
||||
pub fn room(self: &Self) -> usize { return self.slots.^.n; }
|
||||
|
||||
fn tag_of(self: &Self, gen: u32) -> u32 {
|
||||
return (self.id * 16777216) + (self.epoch * 65536) + gen;
|
||||
}
|
||||
|
||||
/// Is this handle still talking about a live slot in this arena?
|
||||
pub fn valid(self: &Self, h: Handle(T)) -> bool {
|
||||
if h.index == NONE { return false; }
|
||||
if (h.index as usize) >= self.high { return false; }
|
||||
if not self.slots.^[h.index as usize].live { return false; }
|
||||
return self.slots.^[h.index as usize].gen == h.tag;
|
||||
}
|
||||
|
||||
pub fn alloc(self: &mut Self, v: T) -> !Handle(T) {
|
||||
var at: usize = 0;
|
||||
if self.free != NONE {
|
||||
at = self.free as usize;
|
||||
self.free = self.slots.^[at].next;
|
||||
} else {
|
||||
if self.high == self.slots.^.n { return error.OutOfMemory; }
|
||||
at = self.high;
|
||||
self.high = self.high + 1;
|
||||
self.slots.^[at].gen = self.tag_of(0);
|
||||
}
|
||||
self.slots.^[at].value = v;
|
||||
self.slots.^[at].live = true;
|
||||
self.slots.^[at].next = NONE;
|
||||
self.count = self.count + 1;
|
||||
return Handle(T){ index: at as u32, tag: self.slots.^[at].gen };
|
||||
}
|
||||
|
||||
/// A copy of what the slot holds, or nothing when the handle is stale.
|
||||
/// The borrow of the arena ends with this statement, which is what lets a
|
||||
/// caller read one slot while writing another.
|
||||
pub fn get(self: &Self, h: Handle(T)) -> ?T {
|
||||
if not self.valid(h) { return null; }
|
||||
return self.slots.^[h.index as usize].value;
|
||||
}
|
||||
|
||||
/// Overwrite in place. Says whether the handle was good.
|
||||
pub fn set(self: &mut Self, h: Handle(T), v: T) -> bool {
|
||||
if not self.valid(h) { return false; }
|
||||
self.slots.^[h.index as usize].value = v;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Move the value out and leave `replacement` behind (SPEC 5 R7). This is
|
||||
/// how a `T` that owns something leaves the arena.
|
||||
pub fn take(self: &mut Self, h: Handle(T), replacement: T) -> ?T {
|
||||
if not self.valid(h) { return null; }
|
||||
return mem.replace(&mut self.slots.^[h.index as usize].value,
|
||||
replacement);
|
||||
}
|
||||
|
||||
/// Exchange what two slots hold. Both handles have to be good.
|
||||
pub fn swap(self: &mut Self, a: Handle(T), b: Handle(T)) -> bool {
|
||||
if not self.valid(a) { return false; }
|
||||
if not self.valid(b) { return false; }
|
||||
let ai: usize = a.index as usize;
|
||||
let bi: usize = b.index as usize;
|
||||
if ai == bi { return true; }
|
||||
let first: T = self.slots.^[ai].value;
|
||||
let second: T = mem.replace(&mut self.slots.^[bi].value, first);
|
||||
self.slots.^[ai].value = second;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Give the slot back. Every handle to it stops matching. A slot whose
|
||||
/// generation has run out is retired rather than reused -- wrapping around
|
||||
/// would make an old handle valid again, which is the one thing the
|
||||
/// generation is there to prevent.
|
||||
pub fn free(self: &mut Self, h: Handle(T)) -> bool {
|
||||
if not self.valid(h) { return false; }
|
||||
let at: usize = h.index as usize;
|
||||
self.slots.^[at].live = false;
|
||||
self.count = self.count - 1;
|
||||
let gen: u32 = self.slots.^[at].gen % 65536;
|
||||
if gen == 65535 { return true; }
|
||||
self.slots.^[at].gen = self.slots.^[at].gen + 1;
|
||||
self.slots.^[at].next = self.free;
|
||||
self.free = h.index;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Forget everything at once. The epoch moves, so every handle made before
|
||||
/// now is stale without having to touch a single slot.
|
||||
pub fn reset(self: &mut Self) -> void {
|
||||
self.epoch = (self.epoch + 1) % 256;
|
||||
self.high = 0;
|
||||
self.count = 0;
|
||||
self.free = NONE;
|
||||
return;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user