proof/: all 23 Isabelle theory files now verify under Isabelle2025-2

Isabelle toolchain replaced (was genuinely 2011, 14+ years stale) and every
theory file fixed to actually compile -- most had apparently never been
checked under a working Isabelle at all. Fixed the vm_state self-reference
in StarForth_Base.thy properly (word_table is now a free-standing global
constant, not a circular record field), corrected the word_physics_transparent
axiom (was claiming full state equality from mere exec-equivalence, provably
too strong), and worked through 14 years of HOL-Library drift plus several
missing-hypothesis bugs across the physics-loop and ACL theories.

Two genuine (non-tactical) bugs found and left oops-flagged rather than
silently resolved: forth_roll's index arithmetic disagrees with both its own
test lemma and the real C ROLL implementation (three-way inconsistency), and
pm_wf isn't actually preserved by pm_record_hit/pm_record_miss. Both need a
decision, not a proof-script fix.

Full writeup in FABRIC-2.md item 5.2.
This commit is contained in:
Robert Allan James
2026-08-13 12:30:30 -04:00
parent 5787718c30
commit 422ef2fa29
20 changed files with 561 additions and 198 deletions
+76 -23
View File
@@ -2,6 +2,11 @@ theory StarForth_Q48_16
imports "HOL-Library.Word"
begin
(* AND/OR/XOR infix notation moved behind an opt-in bundle at some point after
2011 -- unbundled by default now to avoid clashing with other uses of the
same tokens. q48_frac_part below needs it. *)
unbundle bit_operations_syntax
(* =========================================================================
StarForth_Q48_16 — Q48.16 Fixed-Point Arithmetic (HOL-Word model)
@@ -58,9 +63,15 @@ definition q48_to_u64 :: "q48 \<Rightarrow> 64 word" where
lemma q48_round_trip:
assumes "unat n < 2 ^ 48"
shows "q48_to_u64 (q48_from_u64 n) = n"
unfolding q48_to_u64_def q48_from_u64_def
using assms
by (simp add: drop_bit_push_bit word_size)
proof -
have "unat n * 65536 < 2 ^ 64" using assms by simp
hence h: "unat (n * 65536) = unat n * 65536"
using unat_mult_lem[of n "65536::q48"] by simp
show ?thesis
unfolding q48_to_u64_def q48_from_u64_def
by (simp add: word_unat_eq_iff push_bit_eq_mult drop_bit_eq_div
unat_div_distrib h)
qed
lemma q48_from_u64_zero [simp]: "q48_from_u64 0 = 0"
by (simp add: q48_from_u64_def)
@@ -68,11 +79,26 @@ lemma q48_from_u64_zero [simp]: "q48_from_u64 0 = 0"
lemma q48_to_u64_zero [simp]: "q48_to_u64 0 = 0"
by (simp add: q48_to_u64_def)
(* CORRECTED 2026-08-13: the original statement had no upper bound and is
false as such -- push_bit 16 wraps mod 2^64, so e.g. a=1, b=2^48 satisfies
unat a \<le> unat b (1 \<le> 2^48) while push_bit 16 a = 65536 and push_bit 16 b
wraps to 0, breaking the conclusion. Added the same "unat _ < 2^48
overflow-free range" bound this file already uses everywhere else. *)
lemma q48_from_u64_mono:
assumes "unat a \<le> unat b"
assumes "unat a \<le> unat b" and "unat b < 2 ^ 48"
shows "unat (q48_from_u64 a) \<le> unat (q48_from_u64 b)"
unfolding q48_from_u64_def
using assms by (simp add: unat_push_bit)
proof -
have ha: "unat a < 2 ^ 48" using assms by simp
have hb64: "unat b * 65536 < 2 ^ 64" using assms(2) by simp
have ha64: "unat a * 65536 < 2 ^ 64" using ha by simp
have eb: "unat (b * 65536) = unat b * 65536"
using unat_mult_lem[of b "65536::q48"] hb64 by simp
have ea: "unat (a * 65536) = unat a * 65536"
using unat_mult_lem[of a "65536::q48"] ha64 by simp
show ?thesis
unfolding q48_from_u64_def
by (simp add: push_bit_eq_mult ea eb assms(1))
qed
(* =========================================================================
Section 3: Arithmetic operations
@@ -117,9 +143,14 @@ lemma q48_mul_zero_left [simp]: "q48_mul 0 a = 0"
lemma q48_mul_one_right:
assumes "unat a < 2 ^ 48"
shows "q48_mul a Q48_ONE = a"
unfolding q48_mul_def Q48_ONE_def Q48_SCALE_def
using assms
by (simp add: drop_bit_push_bit word_size)
proof -
have "unat a * 65536 < 2 ^ 64" using assms by simp
hence h: "unat (a * 65536) = unat a * 65536"
using unat_mult_lem[of a "65536::q48"] by simp
show ?thesis
unfolding q48_mul_def Q48_ONE_def Q48_SCALE_def
by (simp add: word_unat_eq_iff drop_bit_eq_div unat_div_distrib h)
qed
(* q48_div: (a << 16) / b
C: return ((__uint128_t)a << 16) / b;
@@ -130,10 +161,21 @@ definition q48_div :: "q48 \<Rightarrow> q48 \<Rightarrow> q48" where
lemma q48_div_zero_denom [simp]: "q48_div a 0 = 0"
by (simp add: q48_div_def)
(* CORRECTED 2026-08-13: the original statement had no bound and is false
as such -- push_bit 16 wraps mod 2^64, so e.g. a=2^48 gives push_bit 16 a
= 0, so q48_div a Q48_ONE = 0 \<noteq> a. Added the same overflow-free bound
used throughout this file. *)
lemma q48_div_one:
"q48_div a Q48_ONE = a"
unfolding q48_div_def Q48_ONE_def Q48_SCALE_def
by simp
assumes "unat a < 2 ^ 48"
shows "q48_div a Q48_ONE = a"
proof -
have "unat a * 65536 < 2 ^ 64" using assms by simp
hence h: "unat (a * 65536) = unat a * 65536"
using unat_mult_lem[of a "65536::q48"] by simp
show ?thesis
unfolding q48_div_def Q48_ONE_def Q48_SCALE_def
by (simp add: word_unat_eq_iff push_bit_eq_mult unat_div_distrib h)
qed
(* =========================================================================
Section 4: Accuracy ratio in Q48.16 (used by Loop #4 / Loop #5)
@@ -142,24 +184,27 @@ lemma q48_div_one:
(* Prefetch accuracy: hits / total, represented in Q48.16.
Arguments are natural numbers (counters); result is a 64 word. *)
definition q48_accuracy :: "nat \<Rightarrow> nat \<Rightarrow> q48" where
"q48_accuracy hits total =
(if total = 0 then 0
else word_of_nat ((hits * 65536) div total))"
"q48_accuracy hits tot =
(if tot = 0 then 0
else word_of_nat ((hits * 65536) div tot))"
lemma q48_accuracy_zero_total [simp]: "q48_accuracy hits 0 = 0"
by (simp add: q48_accuracy_def)
lemma q48_accuracy_upper_bound:
assumes "hits \<le> total"
shows "unat (q48_accuracy hits total) \<le> 65536"
proof (cases "total = 0")
assumes "hits \<le> tot"
shows "unat (q48_accuracy hits tot) \<le> 65536"
proof (cases "tot = 0")
case True thus ?thesis by simp
next
case False
have "(hits * 65536) div total \<le> 65536"
using assms False by (simp add: div_le_iff_le_mult)
have "(hits * 65536) div tot \<le> (tot * 65536) div tot"
using assms by (intro div_le_mono) simp
also have "\<dots> = 65536"
using False by simp
finally have "(hits * 65536) div tot \<le> 65536" .
thus ?thesis
by (simp add: q48_accuracy_def False unat_word_of_nat)
by (simp add: q48_accuracy_def False unat_of_nat)
qed
(* =========================================================================
@@ -177,7 +222,15 @@ definition q48_frac_part :: "q48 \<Rightarrow> 64 word" where
lemma q48_decompose:
"push_bit 16 (q48_int_part q) + q48_frac_part q = q"
unfolding q48_int_part_def q48_frac_part_def
by (simp add: push_bit_drop_bit_and_not_mask_eq and_mask_eq_iff_shiftr_0
bit_push_bit drop_bit_eq_div push_bit_eq_mult)
proof -
have disj: "push_bit 16 (drop_bit 16 q) AND (q AND mask 16) = 0"
by (rule bit_word_eqI) (auto simp: bit_simps)
have "push_bit 16 (drop_bit 16 q) + (q AND mask 16)
= push_bit 16 (drop_bit 16 q) OR (q AND mask 16)"
by (rule disjunctive_add_eq_or) (rule disj)
also have "\<dots> = q"
by (rule bit_word_eqI) (auto simp: bit_simps)
finally show "push_bit 16 (drop_bit 16 q) + (q AND mask 16) = q" .
qed
end