diff --git a/fec/src/check.c b/fec/src/check.c index 357b955..77bf9b7 100644 --- a/fec/src/check.c +++ b/fec/src/check.c @@ -445,7 +445,15 @@ FeType *method_type(FeCheck *c, FeNode *node, FeType *owner) (strcmp(node->text,"&")==0 || strcmp(node->text,"&mut")==0) && node->a && node->a->text && strcmp(node->a->text,"Self")==0) return fe_type_ref(&c->types,owner,strcmp(node->text,"&mut")==0); - return node_type(c,node); + /* The rest of a method's signature is written in the unit that declared + the type, so a name in it means what that unit means by it and not what + the caller happens to mean. */ + { + int back=enter_declaring_unit(c,owner ? owner->unit : 0); + FeType *t=node_type(c,node); + if (back>=0) enter_unit(c,(unsigned)back); + return t; + } } diff --git a/fec/std/arena.fe b/fec/std/arena.fe new file mode 100644 index 0000000..8fa5117 --- /dev/null +++ b/fec/std/arena.fe @@ -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; + } +} diff --git a/fec/tests/exec/arenat.fe b/fec/tests/exec/arenat.fe new file mode 100644 index 0000000..41469bd --- /dev/null +++ b/fec/tests/exec/arenat.fe @@ -0,0 +1,81 @@ +// EXIT:0 +// OUTPUT:alloc 10 20 30 len 3 +// OUTPUT:freed len 2 stale -1 live 30 +// OUTPUT:reused index 1 old -1 new 99 +// OUTPUT:swap 99 10 +// OUTPUT:set 77 take 77 after 5 +// OUTPUT:reset len 0 before -1 +// OUTPUT:other -1 +// OUTPUT:full yes +// OUTPUT:balanced +unit arenat; + +import std.io; +import std.sys; +import std.arena; + +// SPEC R11: the arena owns the values and a handle is a number. The point of +// the number carrying a generation is that using it after the slot was given +// back answers `null` rather than whatever moved in afterwards. + +fn run() -> !void { + var a: arena.Arena(i32) = try arena.Arena(i32).with_capacity(1, 3); + let x: arena.Handle(i32) = try a.alloc(10); + let y: arena.Handle(i32) = try a.alloc(20); + let z: arena.Handle(i32) = try a.alloc(30); + @print("alloc {} {} {} len {}\n", a.get(x) orelse -1, a.get(y) orelse -1, + a.get(z) orelse -1, a.len()); + + // Give one back. Its handle stops meaning anything; the others do not. + let gone: bool = a.free(y); + @print("freed len {} stale {} live {}\n", a.len(), a.get(y) orelse -1, + a.get(z) orelse -1); + + // The slot comes back on the free list, and the old handle still does not + // match the new occupant. + let again: arena.Handle(i32) = try a.alloc(99); + @print("reused index {} old {} new {}\n", again.index, + a.get(y) orelse -1, a.get(again) orelse -1); + + let ok: bool = a.swap(x, again); + @print("swap {} {}\n", a.get(x) orelse -1, a.get(again) orelse -1); + + let wrote: bool = a.set(x, 77); + let took: i32 = a.take(x, 5) orelse -1; + @print("set {} take {} after {}\n", 77, took, a.get(x) orelse -1); + + // Reset moves the epoch, so every handle made before it is stale without + // a single slot being touched. + a.reset(); + @print("reset len {} before {}\n", a.len(), a.get(x) orelse -1); + + // A handle from one arena means nothing to another. + var b: arena.Arena(i32) = try arena.Arena(i32).with_capacity(2, 2); + let h: arena.Handle(i32) = try b.alloc(41); + @print("other {}\n", a.get(h) orelse -1); + + // Running out of room is an error, not a trap. + var full: bool = false; + fill(&mut b) catch |e| { full = true; }; + @print("full {}\n", yesno(full)); + return; +} + +/// Two more into an arena that has room for one. +fn fill(b: &mut arena.Arena(i32)) -> !void { + let p: arena.Handle(i32) = try b.alloc(1); + let q: arena.Handle(i32) = try b.alloc(2); + return; +} + +fn yesno(b: bool) -> []u8 { + if b { return "yes"; } + return "no"; +} + +fn main() -> i32 { + run() catch |e| { @print("failed\n"); return 1; }; + if sys.allocs() == sys.frees() { @print("balanced\n"); } + else { @print("leaked\n"); } + return 0; +}