diff --git a/fec/src/checkexp.c b/fec/src/checkexp.c index a8a9af0..a1b3bb5 100644 --- a/fec/src/checkexp.c +++ b/fec/src/checkexp.c @@ -142,9 +142,20 @@ int lvalue_writable(FeCheckerState *s, FeNode *n) t=n->a ? n->a->sem_type : 0; if (t && t->kind==FE_TYPE_REF && n->b && n->b->text && strcmp(n->b->text,"^")==0) return t->ref_mut; + /* Through an owner, what may be written is decided by what is owned, + not by whether the binding may be pointed somewhere else. `let p: + ^[]mut T` fixes p and leaves what it owns writable. */ + if (t && t->kind==FE_TYPE_OWNED && n->b && n->b->text && + strcmp(n->b->text,"^")==0) + return !t->elem || t->elem->kind!=FE_TYPE_SLICE || t->elem->ref_mut; + return lvalue_writable(s,n->a); + } + if (n->kind == FE_N_INDEX) { + /* An index into a slice asks the slice, not the binding. */ + t=n->a ? n->a->sem_type : 0; + if (t && t->kind==FE_TYPE_SLICE) return t->ref_mut; return lvalue_writable(s,n->a); } - if (n->kind == FE_N_INDEX) return lvalue_writable(s,n->a); return 0; } @@ -440,10 +451,16 @@ FeType *check_expr_core(FeCheckerState *s, FeNode *n) if (strcmp(n->a->b->text,"alloc_slice")==0) { FeNode *count=arg ? arg->next : 0; FeType *item; - if(!arg || arg->kind!=FE_N_IDENT || !count || count->next) - err(c,n->loc,"mem.alloc_slice requires a type and length"); - item=arg && arg->kind==FE_N_IDENT ? - fe_type_intern(&c->types,arg->text) : unknown(c); + { + /* The element type may be an instance -- `Slot(V)` -- and + not just a name. */ + int named=0; + item=arg ? type_from_expr(s,arg,&named) : unknown(c); + if(!arg || !named || !count || count->next) { + err(c,n->loc,"mem.alloc_slice requires a type and length"); + item=unknown(c); + } + } b=count ? check_expr(s,count) : unknown(c); if(known(b) && !fe_type_is_integer(b)) err(c,count->loc,"slice length must be an integer"); @@ -578,9 +595,23 @@ FeType *check_expr_core(FeCheckerState *s, FeNode *n) while(param && arg) { a=check_expr(s,arg); b=method_type(c,param->a,et); - if(!compatible(b,a,arg) && a->kind!=FE_TYPE_UNKNOWN) + /* A method argument gets the same call-only weakening a + free function's does: an exclusive view may be handed + over as a shared one for the length of the call, and an + exclusive borrow is lent rather than given. */ + if(!compatible(b,a,arg) && + !(b && a && b->kind==FE_TYPE_SLICE && + a->kind==FE_TYPE_SLICE && !b->ref_mut && a->ref_mut && + fe_type_equal(b->elem,a->elem)) && + !(b && a && b->kind==FE_TYPE_REF && a->kind==FE_TYPE_REF && + !b->ref_mut && a->ref_mut && + fe_type_equal(b->elem,a->elem)) && + a->kind!=FE_TYPE_UNKNOWN) err(c,arg->loc,"method argument type mismatch"); - mark_moved(s,arg,a); + if(!call_reborrows(b,a) && + !(b && a && b->kind==FE_TYPE_SLICE && + a->kind==FE_TYPE_SLICE && !b->ref_mut && a->ref_mut)) + mark_moved(s,arg,a); param=param->next; arg=arg->next; } diff --git a/fec/src/checkgen.c b/fec/src/checkgen.c index 45c1a31..619213f 100644 --- a/fec/src/checkgen.c +++ b/fec/src/checkgen.c @@ -226,9 +226,13 @@ FeType *build_struct_instance(FeCheck *c, FeUnit *home, FeNode *decl, for (f=decl->children;f;f=f->next) if (f->kind==FE_N_FIELD) ++fields; t->field_count=fields; if (fields) { + const char *save_unit=c->types.unit_name; t->fields=(FeFieldType *)fe_arena_alloc(&c->arena, fields*sizeof(FeFieldType)); if (!t->fields) { t->field_count=0; return t; } + /* Field types are written in the unit that declared the struct, not in + whichever unit asked for this instance. */ + c->types.unit_name=home->name; push_instance_bindings(c,&save,t); bind_self(c,t); i=0; @@ -240,6 +244,7 @@ FeType *build_struct_instance(FeCheck *c, FeUnit *home, FeNode *decl, ++i; } pop_bindings(c,&save); + c->types.unit_name=save_unit; } fe_type_layout_all(&c->types); /* A type that says how to let go of itself needs that method to exist for diff --git a/fec/std/map.fe b/fec/std/map.fe index 5e238c6..3427f19 100644 --- a/fec/std/map.fe +++ b/fec/std/map.fe @@ -1,4 +1,176 @@ -unit map; -pub struct Map(K, V) { +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. +// +// 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; + } + + /// Make sure `need` bytes fit, moving to a bigger buffer if they do not. + /// + /// Kept apart from `keep` because the borrow that swaps the buffer in is + /// a borrow of the whole of `self` -- borrowing is tracked at the root -- + /// and it has to be over before any field is read again. + fn ensure_room(self: &mut Self, need: usize) -> !void { + let have: usize = self.bytes.^.n; + if need <= have { return; } + var room: usize = have; + while room < need { room = room * 2; } + let bigger: ^[]mut u8 = try mem.alloc_slice(u8, room); + var k: usize = 0; + let filled: usize = self.used_bytes; + while k < filled { + bigger.^[k] = self.bytes.^[k]; + k = k + 1; + } + let old: ^[]mut u8 = mem.replace(&mut self.bytes, bigger); + mem.destroy(old); + return; + } + + /// Copy `key` into the byte buffer and point the slot at it. + fn keep(self: &mut Self, key: []u8, slot: usize) -> !void { + let at: usize = self.used_bytes; + let need: usize = at + key.n; + try self.ensure_room(need); + 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; + } + + pub fn drop(self: &mut Self) -> void { + mem.destroy(self.slots); + mem.destroy(self.bytes); + } +} + +/// 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; } diff --git a/fec/tests/exec/maps.fe b/fec/tests/exec/maps.fe new file mode 100644 index 0000000..c499f61 --- /dev/null +++ b/fec/tests/exec/maps.fe @@ -0,0 +1,48 @@ +// EXIT:0 +// OUTPUT:count 5 +// OUTPUT:fn 2 let 3 missing 0 +// OUTPUT:grown 64 +// OUTPUT:after 40 +// OUTPUT:balanced +unit maps; +import std.io; +import std.map; +import std.fmt; +import std.sys; + +fn run() -> !void { + var seen: map.Map(i32) = try map.Map(i32).with_capacity(4); + try seen.put("unit", 1); + try seen.put("fn", 2); + try seen.put("let", 3); + try seen.put("struct", 4); + try seen.put("return", 5); + @print("count {}\n", seen.count_of()); + @print("fn {} let {} missing {}\n", seen.get("fn", 0), seen.get("let", 0), + seen.get("nope", 0)); + // Enough keys to make it grow more than once. + var buf: [8]u8 = undefined; + var i: usize = 0; + while i < 40 { + let n: usize = fmt.fmt_i32(buf[..], i as i32); + try seen.put(buf[0..n], (i as i32) + 100); + i = i + 1; + } + @print("grown {}\n", seen.room()); + var found: usize = 0; + i = 0; + while i < 40 { + let n: usize = fmt.fmt_i32(buf[..], i as i32); + if seen.get(buf[0..n], 0) == (i as i32) + 100 { found = found + 1; } + i = i + 1; + } + @print("after {}\n", found); + return; +} + +fn main() -> i32 { + run() catch |e| { @print("failed\n"); return 1; }; + if sys.allocs() != sys.frees() { @print("leaked\n"); return 2; } + @print("balanced\n"); + return 0; +}