Cover a dev build and a map that leaves its let
Two gaps nothing in the suite reached. A dev build, because the hash and equality pair emitted for a struct key is a function nobody wrote, and the only other inhabitant of the lifted list — a handler-bind clause — carries a parent this one cannot: the pair is shared by every function that maps that key type, so it has no single parent. A dev build puts every body behind an indirection cell and is the build that would notice. It does not; maps.flan answers the same nineteen ways at --dev as it does at -O2 and -O0. And a map crossing a function boundary in both directions. Everything else in the file lives and dies inside one let, so nothing would have noticed if the 48-byte header travelled wrongly by value while every runtime operation takes its address. Returning one and passing one are both moves, which is the rule a Vec already follows — verified against a Vec rather than assumed, since a refusal that fired for the wrong reason would look the same. has-key? is flagged in BUILT.md as what it is: an addition, not something spec-memory.md names.
This commit is contained in:
parent
b2059520ab
commit
ea24461107
7
BUILT.md
7
BUILT.md
@ -1719,9 +1719,14 @@ calls per field, which has no channel to hand on.
|
||||
| `(map-new)` `(map-new K V)` `(map-new a)` `(map-new K V a)` | a new map; the pair may be omitted where the context says |
|
||||
| `(put m k v)` | upsert, `Unit` |
|
||||
| `(get m k)` | `(Option V)` — absence is `None` |
|
||||
| `(has-key? m k)` | `bool`, copying no value |
|
||||
| `(has-key? m k)` | `bool`, copying no value — **an addition; the spec does not name it** |
|
||||
| `(len m)` `(reserve m n)` `(clone m)` `(clone m a)` `(free m)` | extended, not duplicated |
|
||||
|
||||
`has-key?` is **not in `spec-memory.md`** and is an addition, flagged because everything else here is the spec's.
|
||||
`(get m k)` answers the same question, but through an `Option` the caller then has to match, and the common use is a
|
||||
condition. It copies no value, which is also why it is not just `get` with the result thrown away. The `?` suffix
|
||||
follows `can-free?`.
|
||||
|
||||
`len`, `reserve`, `clone` and `free` were **extended rather than given map-shaped names of their own**, which is what
|
||||
`at` and `len` already did for `Vec`: one question, one word. `reserve`'s `n` is entries, not slots — the runtime sizes
|
||||
the block so `n` still sits under the load factor, which is the only reading of "room for n" that does not reallocate
|
||||
|
||||
@ -8,6 +8,25 @@
|
||||
(defstruct Named [tag string n i32])
|
||||
(defenum Suit [hearts 0 spades 1 clubs 2])
|
||||
|
||||
;;;; (8) A map crosses a function boundary in both directions. Returning one
|
||||
;;;; and passing one are both *moves* — the same rule a Vec follows, so the
|
||||
;;;; binding is dead afterwards — and the 48-byte header travels by value while
|
||||
;;;; every runtime operation takes its address. Nothing else in this file
|
||||
;;;; leaves a single let, so nothing else would notice if it did not.
|
||||
(defstruct Cell2 [x i32 y i32])
|
||||
|
||||
(defn make-grid [] (Map Cell2 i32)
|
||||
(let [m (map-new Cell2 i32)]
|
||||
(put m (Cell2 {.x 1 .y 2}) 12)
|
||||
(put m (Cell2 {.x 3 .y 4}) 34)
|
||||
m))
|
||||
|
||||
;; Takes the map, which is a move: this owns it now, and frees it.
|
||||
(defn consume-grid [m (Map Cell2 i32)] i32
|
||||
(let [n (len m)]
|
||||
(free m)
|
||||
n))
|
||||
|
||||
(defn main [] i32
|
||||
;; (1) An integer key past several grows. The map starts at 8 slots, so 2000
|
||||
;; entries is eight reallocations, and every one of them rehashes against a
|
||||
@ -103,4 +122,8 @@
|
||||
(print (len t)) (println "") ; 500
|
||||
(match (get t 499) (Some v) (do (print v) (println "")) None (println "?")))) ; 998
|
||||
(free-all ar))
|
||||
|
||||
(let [g (make-grid)]
|
||||
(print (len g)) (println "") ; 2
|
||||
(print (consume-grid g)) (println "")) ; 2
|
||||
0)
|
||||
|
||||
@ -1567,10 +1567,16 @@ ERR@7 unexpected token: not the kind the caller was reading
|
||||
2000 entries is eight grows and then every one of them read back. *)
|
||||
let maps_out =
|
||||
"2000\n0\n1600\n709\ntrue\nfalse\n3\n20\nfalse\n2\n3\nfalse\n\
|
||||
100\n999\n2\n1\n3\n500\n998\n"
|
||||
100\n999\n2\n1\n3\n500\n998\n2\n2\n"
|
||||
in
|
||||
outputs "maps" "programs/maps.flan" maps_out;
|
||||
outputs ~opt:"-O0" "maps, -O0" "programs/maps.flan" maps_out;
|
||||
(* A dev build, because the hash and equality pair emitted for a struct key
|
||||
is a function nobody wrote and the only other inhabitant of that list —
|
||||
a lifted handler clause — carries a parent this one cannot: the pair is
|
||||
shared by every function that maps that key type. A dev build puts every
|
||||
body behind an indirection cell, so it is the build that would notice. *)
|
||||
outputs ~dev:true "maps, dev" "programs/maps.flan" maps_out;
|
||||
|
||||
(* The allocation-failure rule is one rule over every allocating operation,
|
||||
so it has to hold for map-new, put, reserve and clone as it does for the
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user