rocq/theories/Maps.v
Finite maps keyed by names, the model's counterpart of library(assoc), which ir.pl and cfg.pl use for every table indexed by a block label or a temporary.
The association lists of Term.v (lookup, put) take time linear in their length, and a function of a thousand blocks and twenty thousand temporaries asks them millions of questions. A name map hashes each name to an integer and keeps, under each hash, an association list of the names that share it. A lookup compares names with name_eqb within its bucket, so two names with one hash stay apart: the hash decides only speed, never an answer.
The buckets are kept in the stdlib's AVL tree keyed by integers, whose extracted lookups compare integers. A tree keyed by positives (FMapPositive) walks its key bit by bit, and each bit is a division of a zarith integer once extracted: the validator was thirty times slower with it.
The stdlib's AVL trees keyed by integers, which hold the buckets.
Hashing
The bound of every hash: hashes are the integers below 2^20.
One step of a hash: the hash h so far times 31, plus the next number x, modulo hash_bound.
The hash of the string s, its character codes folded into the hash h by hash_step.
A hash of the ground term t, from its form (each of the four starts from its own seed) and its parts.
The key of the bucket a name n sits in: its term_hash.
Name maps
A name map from names to values of A: under each hash, the association list of the names with that hash and their values.
The map with no names, empty_assoc/1.
As get_assoc(Key, Assoc, V): the value of the name n in the map m, found in n's bucket by lookup, which compares names, not hashes.
As put_assoc(Key, Assoc0, V, Assoc): the map m with the name n now mapped to v.
As del_assoc(Key, Assoc0, V, Assoc): the map m in which the name n no longer has a value.
Whether the name n has a value in the map m.
As list_to_assoc(Pairs, Assoc): the map of the pairs ps, for pairs whose keys differ (here a later pair would replace an earlier one; list_to_assoc/2 throws).
What a lookup answers
The lemmas below say what each operation does to a lookup, by name and not by hash: a map answers as the association list of its puts would.
The stdlib's facts about ZMap's lookups, for the proofs below.
Explore proof 4 tactics
Source script
Looking up the name k after put n v answers v if k is n, and otherwise what the association list ps answered before.
Explore proof 10 tactics
Initial goal
intros A k n v ps
simpl
destruct name_eqbk n eqn: E ; reflexivity |
induction ps as | k′ v′ ps IH ; simpl ; reflexivity |
destruct name_eqbn k′ eqn: E′ ; simpl
2 open–apply term_eqb_eq in E′
2 opensubst k′
2 openrewrite E
2 openexact IH
–destruct name_eqbk k′ ; reflexivity | exact IH
No remaining goals.
Qed
✓ Proof checked and closed by Rocq.
The empty map has no value for any name k.
Explore proof 4 tactics
After nm_add n v, the map answers v for the name n and what it answered before for every other name k: a shared hash never shows.
Explore proof 15 tactics
Initial goal
intros A k n v m
unfold nm_find , nm_add
destruct Z.eq_decname_keyn name_keyk as E | E
2 openfocused goal
–rewrite ZF.add_eq_o by exact E
2 opendeferred goal
rewrite lookup_put_name
2 opendeferred goal
destruct name_eqbk n eqn: Ek ; reflexivity |
2 opendeferred goal
rewrite <- E
2 opendeferred goal
destruct ZMap.findname_keyn m ; reflexivity
deferred goal
–rewrite ZF.add_neq_o by exact E
destruct name_eqbk n eqn: Ek ; | reflexivity
exfalso
apply E
symmetry
apply name_key_eqb
exact Ek
No remaining goals.
Qed
✓ Proof checked and closed by Rocq.
Source script
After nm_remove n, the map has no value for the name n and its old value for every other name k.
Explore proof 34 tactics
Initial goal
intros A k n m
unfold nm_find , nm_remove
destruct ZMap.findname_keyn m as bucket | eqn: Eb
2 open–destruct Z.eq_decname_keyn name_keyk as E | E
3 open+rewrite ZF.add_eq_o by exact E
3 openrewrite <- E , Eb
3 opendestruct name_eqbk n eqn: Ek
4 openfocused goal
∗apply term_eqb_eq in Ek
4 opendeferred goal
subst k
4 opendeferred goal
clear Eb E
4 opendeferred goal
induction bucket as | k′ v′ b IH ; simpl ; reflexivity |
4 opendeferred goal
destruct name_eqbn k′ eqn: E′ ; simpl ; exact IH |
4 opendeferred goal
rewrite E′
4 opendeferred goal
exact IH
3 opendeferred goal
∗clear Eb E
3 openinduction bucket as | k′ v′ b IH ; simpl ; reflexivity |
3 opendestruct name_eqbn k′ eqn: E′ ; simpl
4 openfocused goal
--apply term_eqb_eq in E′
4 opendeferred goal
subst k′
4 opendeferred goal
rewrite Ek
4 opendeferred goal
exact IH
3 opendeferred goal
--destruct name_eqbk k′ ; reflexivity | exact IH
2 open+rewrite ZF.add_neq_o by exact E
2 opendestruct name_eqbk n eqn: Ek ; | reflexivity
2 openexfalso
2 openapply E
2 opensymmetry
2 openapply name_key_eqb
2 openexact Ek
–destruct name_eqbk n eqn: Ek ; | reflexivity
apply term_eqb_eq in Ek
subst k
rewrite Eb
reflexivity
No remaining goals.
Qed
✓ Proof checked and closed by Rocq.
Source script
A name k has a value after adding the name n exactly when k is n or it had one already.
Explore proof 4 tactics
Initial goal
intros A k n v m
unfold nm_mem
rewrite nm_find_add
destruct name_eqbk n ; reflexivity
No remaining goals.
Qed
✓ Proof checked and closed by Rocq.
Source script
A lookup of k in two association lists joined, ps then qs, answers from ps if k is there, and otherwise from qs.
Explore proof 3 tactics
Initial goal
intros A k ps qs
induction ps as | k′ v′ ps IH ; simpl ; reflexivity |
destruct name_eqbk k′ ; reflexivity | exact IH
No remaining goals.
Qed
✓ Proof checked and closed by Rocq.
Source script
Adding the pairs ps to a map m one by one, as nm_of_list does from the empty map, a later pair replaces an earlier one: a lookup of k answers as the reversed list does, and falls back to m.
Explore proof 6 tactics
Initial goal
intros A k ps
induction ps as | k′ v′ ps IH ; intros m ; simpl ; reflexivity |
rewrite IH , lookup_app , nm_find_add
simpl
destruct lookupk revps ; reflexivity |
destruct name_eqbk k′ ; reflexivity
No remaining goals.
Qed
✓ Proof checked and closed by Rocq.