From dd1d17df54c37be5e16ab7afadff60a4db697a80 Mon Sep 17 00:00:00 2001 From: Michael McInerney Date: Wed, 10 Jan 2024 11:15:36 +1030 Subject: [PATCH] lib: add Heap_List.thy, for reasoning about linked lists on heaps From the rt branch Signed-off-by: Michael McInerney --- lib/Heap_List.thy | 348 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 348 insertions(+) create mode 100644 lib/Heap_List.thy diff --git a/lib/Heap_List.thy b/lib/Heap_List.thy new file mode 100644 index 000000000..3b64851ea --- /dev/null +++ b/lib/Heap_List.thy @@ -0,0 +1,348 @@ +(* + * Copyright 2020, Data61, CSIRO (ABN 41 687 119 230) + * + * SPDX-License-Identifier: BSD-2-Clause + *) + +(* Singly-linked lists on heaps or projections from heaps, as predicate and as recursive function. + Loosely after ~~/src/HOL/Hoare/Pointer_Examples.thy *) + +theory Heap_List +imports Main "HOL-Library.Prefix_Order" +begin + +(* Given a heap projection that returns the next-pointer for an object at address x, + given a start pointer x, and an end pointer y, determine if the given list + is the path of addresses visited when following the next-pointers from x to y *) +primrec heap_path :: "('a \ 'a) \ 'a option \ 'a list \ 'a option \ bool" where + "heap_path hp x [] y = (x = y)" +| "heap_path hp x (a#as) y = (x = Some a \ heap_path hp (hp a) as y)" + +(* When a path ends in None, it is a singly-linked list *) +abbreviation heap_ls :: "('a \ 'a) \ 'a option \ 'a list \ bool" where + "heap_ls hp x xs \ heap_path hp x xs None" + +(* Walk a linked list of next pointers, recording which it visited. + Terminates artificially at loops, and otherwise because the address domain is finite *) +function heap_walk :: "('a::finite \ 'a) \ 'a option \ 'a list \ 'a list" where + "heap_walk hp None xs = xs" +| "heap_walk hp (Some x) xs = (if x \ set xs then xs else heap_walk hp (hp x) (xs@[x]))" + by pat_completeness auto + +lemma card_set_UNIV: + fixes xs :: "'a::finite list" + assumes "x \ set xs" + shows "card (set xs) < card(UNIV::'a set)" +proof - + have "finite (UNIV::'a set)" by simp + moreover + from assms have "set xs \ UNIV" by blast + ultimately + show ?thesis by (rule psubset_card_mono) +qed + +termination heap_walk + by (relation "measure (\(_, _, xs). card(UNIV :: 'a set) - card (set xs))"; + simp add: card_set_UNIV diff_less_mono2) + +lemma heap_path_append[simp]: + "heap_path hp start (xs @ ys) end = (\x. heap_path hp start xs x \ heap_path hp x ys end)" + by (induct xs arbitrary: start; simp) + +lemma heap_path_None[simp]: + "heap_path hp None xs end = (xs = [] \ end = None)" + by (cases xs, auto) + +lemma heap_ls_unique: + "\ heap_ls hp x xs; heap_ls hp x ys \ \ xs = ys" + by (induct xs arbitrary: ys x; simp) (case_tac ys; clarsimp) + +lemma heap_ls_hd_not_in_tl: + "heap_ls hp (hp x) xs \ x \ set xs" +proof + assume "x \ set xs" + then obtain ys zs where xs: "xs = ys @ x # zs" by (auto simp: in_set_conv_decomp) + moreover assume "heap_ls hp (hp x) xs" + moreover from this xs have "heap_ls hp (hp x) zs" by clarsimp + ultimately show False by (fastforce dest: heap_ls_unique) +qed + +lemma heap_ls_distinct: + "heap_ls hp x xs \ distinct xs" + by (induct xs arbitrary: x; clarsimp simp: heap_ls_hd_not_in_tl) + +lemma heap_ls_is_walk': + "\ heap_ls hp x xs; set xs \ set ys = {} \ \ heap_walk hp x ys = ys @ xs" + by (frule heap_ls_distinct) (induct xs arbitrary: x ys; clarsimp) + +lemma heap_ls_is_walk: + "heap_ls hp x xs \ heap_walk hp x [] = xs" + using heap_ls_is_walk' by fastforce + +lemma heap_path_end_unique: + "heap_path hp x xs y \ heap_path hp x xs y' \ y = y'" + by (induct xs arbitrary: x; clarsimp) + +lemma heap_path_head: + "heap_path hp x xs y \ xs \ [] \ x = Some (hd xs)" + by (induct xs arbitrary: x; clarsimp) + +lemma heap_path_non_nil_lookup_next: + "heap_path hp x (xs@z#ys) y \ hp z = (case ys of [] \ y | _ \ Some (hd ys))" + by (cases ys; fastforce) + +lemma heap_path_prefix: + "heap_path hp st ls ed \ \xs\ls. heap_path hp st xs (if xs = [] then st else hp (last xs))" + apply clarsimp + apply (erule Prefix_Order.prefixE) + by (metis append_butlast_last_id heap_path_append heap_path_non_nil_lookup_next list.case(1)) + +lemma heap_path_butlast: + "heap_path hp st ls ed \ ls \ [] \ heap_path hp st (butlast ls) (Some (last ls))" + by (induct ls rule: rev_induct; simp) + +lemma in_list_decompose_takeWhile: + "x \ set xs \ + xs = (takeWhile ((\) x) xs) @ x # (drop (length (takeWhile ((\) x) xs) + 1) xs)" + by (induct xs arbitrary: x; clarsimp) + +lemma takeWhile_neq_hd_eq_Nil[simp]: + "takeWhile ((\) (hd xs)) xs = Nil" + by (metis (full_types) hd_Cons_tl takeWhile.simps(1) takeWhile.simps(2)) + +lemma heap_not_in_dom[simp]: + "ptr \ dom hp \ hp(ptr := None) = hp" + by (auto simp: dom_def) + +lemma heap_path_takeWhile_lookup_next: + "\ heap_path hp st rs ed; r \ set rs \ + \ heap_path hp st (takeWhile ((\) r) rs) (Some r)" + apply (drule heap_path_prefix) + apply (subgoal_tac "takeWhile ((\) r) rs @ [r] \ rs", fastforce) + by (fastforce dest!: in_list_decompose_takeWhile intro: Prefix_Order.prefixI) + +lemma heap_path_heap_upd_not_in: + "\heap_path hp st rs ed; r \ set rs\ \ heap_path (hp(r:= x)) st rs ed" + by (induct rs arbitrary: st; clarsimp) + +lemma heap_walk_lb: + "heap_walk hp x xs \ xs" + apply (induct xs rule: heap_walk.induct; clarsimp) + by (metis Prefix_Order.prefixE Prefix_Order.prefixI append_assoc) + +lemma heal_walk_Some_nonempty': + "heap_walk hp (Some x) [] > []" + by (fastforce intro: heap_walk_lb less_le_trans[where y="[x]"]) + +lemma heal_walk_Some_nonempty: + "heap_walk hp (Some x) [] \ []" + by (metis less_list_def heal_walk_Some_nonempty') + +lemma heap_walk_Nil_None: + "heap_walk hp st [] = [] \ st = None" + by (case_tac st; simp only: heal_walk_Some_nonempty) + +lemma heap_ls_last_None: + "heap_ls hp st xs \ xs \ [] \ hp (last xs) = None" + by (induct xs rule: rev_induct; clarsimp) + +(* sym_heap *) + +definition sym_heap where + "sym_heap hp hp' \ \p p'. hp p = Some p' \ hp' p' = Some p" + +lemma sym_heapD1: + "sym_heap hp hp' \ hp p = Some p' \ hp' p' = Some p" + by (clarsimp simp: sym_heap_def) + +lemma sym_heapD2: + "sym_heap hp hp' \ hp' p' = Some p \ hp p = Some p'" + by (clarsimp simp: sym_heap_def) + +lemma sym_heap_symmetric: + "sym_heap hp hp' \ sym_heap hp' hp" + unfolding sym_heap_def by blast + +lemma sym_heap_None: + "\sym_heap hp hp'; hp p = None\ \ \p'. hp' p' \ Some p" unfolding sym_heap_def by force + +lemma sym_heap_path_reverse: + "sym_heap hp hp' \ + heap_path hp (Some p) (p#ps) (Some p') + \ heap_path hp' (Some p') (p'#(rev ps)) (Some p)" + unfolding sym_heap_def by (induct ps arbitrary: p p' rule: rev_induct; force) + +lemma sym_heap_ls_rev_Cons: + "\sym_heap hp hp'; heap_ls hp (Some p) (p#ps)\ + \ heap_path hp' (Some (last (p#ps))) (rev ps) (Some p)" + supply rev.simps[simp del] + apply (induct ps arbitrary: p rule: rev_induct; simp add: rev.simps) + by (auto dest!: sym_heap_path_reverse[THEN iffD1]) + +lemma sym_heap_ls_rev: + "\sym_heap hp hp'; heap_ls hp (Some p) ps\ + \ heap_path hp' (Some (last ps)) (butlast (rev ps)) (Some p) + \ hp (last ps) = None" + apply (induct ps arbitrary: p rule: rev_induct, simp) + apply (frule heap_path_head; clarsimp) + by (auto dest!: sym_heap_path_reverse[THEN iffD1]) + +(* more on heap_path : next/prev in path *) + +lemma heap_path_extend: + "heap_path hp st (ls @ [p]) (hp p) \ heap_path hp st ls (Some p)" + by (induct ls rule: rev_induct; simp) + +lemma heap_path_prefix_heap_ls: + "\heap_ls hp st xs; heap_path hp st ys ed\ \ ys \ xs" + apply (induct xs arbitrary: ys st, simp) + apply (case_tac ys; clarsimp) + done + +lemma distinct_decompose2: + "\distinct xs; xs = ys @ x # y # zs\ + \ x \ y \ x \ set ys \ y \ set ys \ x \ set zs \ y \ set zs" + by (simp add: in_set_conv_decomp) + +lemma heap_path_distinct_next_cases: (* the other direction needs sym_heap *) + "\heap_path hp st xs ed; distinct xs; p \ set xs; hp p = Some np\ + \ ed = Some p \ ed = Some np \ np \ set xs" + apply (cases ed; simp) + apply (frule in_list_decompose_takeWhile) + apply (subgoal_tac "heap_ls hp st (takeWhile ((\) p) xs @ p # drop (length (takeWhile ((\) p) xs) + 1) xs)") + apply (drule heap_path_non_nil_lookup_next) + apply (case_tac "drop (length (takeWhile ((\) p) xs) + 1) xs"; simp) + apply (metis in_set_dropD list.set_intros(1)) + apply simp + apply (frule in_list_decompose_takeWhile) + apply (subgoal_tac "heap_path hp st (takeWhile ((\) p) xs @ p # drop (length (takeWhile ((\) p) xs) + 1) xs) ed") + apply (frule heap_path_non_nil_lookup_next) + apply (case_tac "drop (length (takeWhile ((\) p) xs) + 1) xs", simp) + apply (simp split: if_split_asm) + apply (drule (1) distinct_decompose2) + apply clarsimp + by (metis in_set_dropD list.set_intros(1)) simp + +lemma heap_ls_next_in_list: + "\heap_ls hp st xs; p \ set xs; hp p = Some np\ + \ np \ set xs" + apply (subgoal_tac "distinct xs") + by (fastforce dest!: heap_path_distinct_next_cases) (erule heap_ls_distinct) + +lemma heap_path_distinct_sym_prev_cases: + "\heap_path hp st xs ed; distinct xs; np \ set xs; hp p = Some np; sym_heap hp hp'\ + \ st = Some np \ p \ set xs" + apply (cases st; simp) + apply (rename_tac stp) + apply (case_tac "stp = np"; simp) + apply (cases xs; simp del: heap_path.simps) + apply (frule heap_path_head, simp) + apply (cases ed, clarsimp) + apply (frule sym_heap_ls_rev_Cons, fastforce) + apply (drule heap_path_distinct_next_cases[where hp=hp']; simp add: sym_heap_def) + apply simp + apply (simp del: heap_path.simps) + apply (frule (1) sym_heap_path_reverse[where hp'=hp', THEN iffD1]) + apply simp + apply (frule heap_path_distinct_next_cases[where hp=hp']; simp add: sym_heap_def) + apply fastforce + done + +lemma heap_ls_prev_cases: + "\heap_ls hp st xs; np \ set xs; hp p = Some np; sym_heap hp hp'\ + \ st = Some np \ p \ set xs" + apply (subgoal_tac "distinct xs") + by (fastforce dest!: heap_path_distinct_sym_prev_cases) (erule heap_ls_distinct) + +lemma heap_ls_prev_not_in: + "\heap_ls hp st xs; np \ set xs; hp p = Some np\ + \ p \ set xs" + by (meson heap_ls_next_in_list) + +lemma heap_path_distinct_prev_not_in: + "\heap_path hp st xs ed; distinct xs; np \ set xs; hp p = Some np; ed \ Some np; ed \ Some p\ + \ p \ set xs" + using heap_path_distinct_next_cases + by fastforce + +lemma heap_path_distinct_next_not_in: + "\heap_path hp st xs ed; distinct xs; p \ set xs; hp p = Some np; + sym_heap hp hp'; st \ Some np\ + \ np \ set xs" + by (fastforce dest!: heap_path_distinct_sym_prev_cases[simplified]) + +lemma heap_ls_next_not_in: + "\heap_ls hp st xs; p \ set xs; hp p = Some np; sym_heap hp hp'; st \ Some np\ + \ np \ set xs" + by (fastforce dest!: heap_ls_prev_cases[simplified]) + +(* more on heap_path *) + +(* moved from ListLibLemmas *) +lemma distinct_inj_middle: "distinct list \ list = (xa @ x # xb) \ list = (ya @ x # yb) \ xa = ya \ xb = yb" + apply (induct list arbitrary: xa ya) + apply simp + apply clarsimp + apply (case_tac "xa") + apply simp + apply (case_tac "ya") + apply simp + apply clarsimp + apply clarsimp + apply (case_tac "ya") + apply (simp (no_asm_simp)) + apply simp + apply clarsimp + done + +lemma heap_ls_next_takeWhile_append: + "\heap_ls hp st xs; p \ set xs; hp p = Some np\ + \ takeWhile ((\) np) xs = (takeWhile ((\) p) xs) @ [p]" + apply (frule heap_ls_distinct) + apply (frule in_list_decompose_takeWhile) + apply (subgoal_tac "heap_ls hp st (takeWhile ((\) p) xs @ p # drop (length (takeWhile ((\) p) xs) + 1) xs)") + prefer 2 apply simp + apply (drule heap_path_non_nil_lookup_next) + apply (case_tac "drop (length (takeWhile ((\) p) xs) + 1) xs"; simp) + apply (subgoal_tac "np \ set xs") + prefer 2 apply (erule (2) heap_ls_next_in_list) + apply (frule in_list_decompose_takeWhile[where x=np]) + apply (drule (1) distinct_inj_middle[where x=np and xa="takeWhile ((\) np) xs" and ya="takeWhile ((\) p) xs @ [p]"]) + apply simp+ + done + +(* RT FIXME: Move *) +lemma takeWhile_neq_notin_same: + "x \ set xs \ takeWhile ((\) x) xs = xs" + using takeWhile_eq_all_conv by blast + +lemma heap_path_extend_takeWhile: + "\heap_ls hp st xs; heap_path hp st (takeWhile ((\) p) xs) (Some p); hp p = Some np\ + \ heap_path hp st (takeWhile ((\) np) xs) (Some np)" + apply (case_tac "p \ set xs") + apply (subst heap_ls_next_takeWhile_append[where p=p and np=np and hp=hp]; simp) + apply (drule takeWhile_neq_notin_same, simp) + apply (drule (1) heap_path_end_unique, simp) + done + +lemma heap_ls_next_takeWhile_append_sym: + "\heap_ls hp st xs; np \ set xs; st \ Some np; hp p = Some np; sym_heap hp hp'\ + \takeWhile ((\) np) xs = (takeWhile ((\) p) xs) @ [p]" + apply (frule (3) heap_ls_prev_cases, simp) + apply (fastforce elim!: heap_ls_next_takeWhile_append) + done + +lemma heap_path_curtail_takeWhile: + "\heap_ls hp st xs; heap_path hp st (takeWhile ((\) np) xs) (Some np); + st \ Some np; hp p = Some np; sym_heap hp hp'\ + \ heap_path hp st (takeWhile ((\) p) xs) (Some p)" + apply (case_tac "np \ set xs") + apply (drule (4) heap_ls_next_takeWhile_append_sym) + apply simp + apply (drule takeWhile_neq_notin_same, simp) + apply (drule (1) heap_path_end_unique, simp) + done + +(* more on heap_path : end *) + +end \ No newline at end of file