proof/: migrate cell from int to 64-bit signed word, full suite verifies
cell_t is a 64-bit signed C long; the formal model previously used unbounded HOL int, hiding wraparound and signed/unsigned distinctions entirely. Switches cell to "64 word" throughout and fixes every proof site that assumed int semantics: - StarForth_Base.thy: cell_safe/cell_abs/cell_sdiv/cell_smod plus the sint-bridging lemmas used across the suite - StarForth_Loop1_Heat.thy, StarForth_Loop3_Decay.thy: heat tracking converted to signed word comparisons (<s/\<le>s) - StarForth_Stack_Words.thy: PICK/ROLL against real C ground truth - StarForth_Arithmetic_Words.thy: ABS/MIN/MAX/div/mod rebuilt on signed word semantics (cell_sdiv/cell_smod match C99 truncating division; 2/ uses signed_drop_bit to match "n >> 1"); documents a genuine ABS(INT64_MIN) wraparound hazard mirroring the real C behavior - StarForth_Memory_Words.thy: @/!/C@/C! address checks converted to the signed order All 23 theory files verify with zero errors, including StarForth_Concurrent and StarForth_Correctness. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
9b4bbc9de6
commit
fe6e705867
+140
-7
@@ -1,7 +1,12 @@
|
||||
theory StarForth_Base
|
||||
imports Main
|
||||
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. Needed here since cell is now a
|
||||
word type and several word derivations use it (see Section 1 below). *)
|
||||
unbundle bit_operations_syntax
|
||||
|
||||
(* =========================================================================
|
||||
SPECIFICATION AUTHORITY NOTICE
|
||||
─────────────────────────────────────────────────────────────────────────
|
||||
@@ -29,18 +34,33 @@ begin
|
||||
|
||||
(* ○ CODE-MUST-MATCH: cell_t in include/vm.h is `typedef signed long cell_t`.
|
||||
On x86-64 Linux, signed long = 64-bit signed integer.
|
||||
We model it as HOL int (arbitrary precision).
|
||||
All word proofs hold in C provided no intermediate value overflows the
|
||||
64-bit signed range. This is not a hidden assumption — it is the stated
|
||||
correctness domain for FORTH programs that avoid overflow UB. *)
|
||||
type_synonym cell = int
|
||||
|
||||
CORRECTED 2026-08-13: was modeled as HOL int (arbitrary precision,
|
||||
never wraps). Changed to a genuine 64-bit word, matching how
|
||||
StarForth_Q48_16.thy already models q48. The old int model made every
|
||||
wraparound-dependent C behavior inexpressible (confirmed while scoping
|
||||
StarForth_Double_Words.thy: D+/D-/DNEGATE explicitly do carry/borrow
|
||||
arithmetic and bitwise-complement wraparound that only make sense
|
||||
against a fixed-width type). cell_t is C's *signed* long, so value
|
||||
comparisons on cell throughout this proof suite use the Word library's
|
||||
SIGNED order (<s, \<le>s, sint) rather than the default unsigned (<, \<le>),
|
||||
which does not match FORTH's signed-integer semantics (e.g. -1 must
|
||||
compare less than 0, not greater, as it would under unsigned order).
|
||||
Structural comparisons (list lengths, nat indices) are unaffected and
|
||||
stay plain nat as before -- only actual cell VALUES need <s/\<le>s. *)
|
||||
type_synonym cell = "64 word"
|
||||
|
||||
(* ○ CODE-MUST-MATCH: include/vm.h defines:
|
||||
#define FORTH_TRUE ((cell_t)-1)
|
||||
#define FORTH_FALSE ((cell_t) 0)
|
||||
Any C logical word that produces a boolean result MUST use these macros,
|
||||
not raw 1/0 or any other encoding. Verified in:
|
||||
src/word_source/logical_words.c — ALL comparison and test words *)
|
||||
src/word_source/logical_words.c — ALL comparison and test words
|
||||
|
||||
-1 as a cell (64 word) numeral is already the correct two's-complement
|
||||
all-ones bit pattern (0xFFFFFFFFFFFFFFFF) via Word's standard numeral
|
||||
semantics -- identical to C's (cell_t)-1 bit-for-bit, no change needed
|
||||
here despite the type change above. *)
|
||||
definition forth_true :: cell where "forth_true = -1"
|
||||
definition forth_false :: cell where "forth_false = 0"
|
||||
|
||||
@@ -54,6 +74,119 @@ lemma to_forth_bool_False [simp]: "to_forth_bool False = 0"
|
||||
lemma to_forth_bool_eq: "to_forth_bool b = (if b then -1 else 0)"
|
||||
by (cases b; simp)
|
||||
|
||||
(* =========================================================================
|
||||
Reusable safe-arithmetic infrastructure for cell (64 word) values,
|
||||
added during the cell-as-word migration 2026-08-13.
|
||||
|
||||
Every numeric constant this proof suite actually uses (HEAT_MAX =
|
||||
10^6, ACL_MAX_TTL = 65535, DECAY_SLOPE_MAX in the low millions, etc.)
|
||||
is many orders of magnitude below the 64-bit signed range
|
||||
[-2^63, 2^63-1]. cell_safe below names a single generous safe zone
|
||||
(|c| < 2^32, still 2^31 times larger than any constant actually in
|
||||
use) so every call site needs only "cell_safe c" (or the two bare
|
||||
inequalities it unfolds to) instead of re-deriving a
|
||||
signed_take_bit/sint no-overflow argument from scratch every time a
|
||||
proof adds or subtracts 1 from a cell value near a bound. Without
|
||||
this, +1/-1 near a bound is NOT provably safe in general -- a bare
|
||||
64-bit word wraps -- so this is a genuine hypothesis every such lemma
|
||||
needs, not a formality. *)
|
||||
|
||||
definition cell_safe :: "cell \<Rightarrow> bool" where
|
||||
"cell_safe c \<longleftrightarrow> -4294967296 \<le>s c \<and> c \<le>s 4294967296" \<comment> \<open>|c| < 2^32\<close>
|
||||
|
||||
lemma cell_safe_sint_bounds:
|
||||
assumes "cell_safe c"
|
||||
shows "-4294967296 \<le> sint c" "sint c \<le> 4294967296"
|
||||
using assms by (simp_all add: cell_safe_def word_sle_eq)
|
||||
|
||||
(* The one lemma every "+1 near a bound" argument actually needs: adding
|
||||
or subtracting a small delta to a cell_safe value produces exactly
|
||||
the expected sint, no wraparound -- because the safe zone leaves a
|
||||
2^31-times margin against the true 2^63 boundary. *)
|
||||
lemma cell_safe_add_sint:
|
||||
assumes "cell_safe c"
|
||||
assumes "\<bar>delta\<bar> \<le> 4294967296"
|
||||
shows "sint (c + word_of_int delta) = sint c + delta"
|
||||
proof -
|
||||
have b: "-4294967296 \<le> sint c" "sint c \<le> 4294967296"
|
||||
using assms(1) by (simp_all add: cell_safe_sint_bounds)
|
||||
have step1: "sint (c + word_of_int delta)
|
||||
= signed_take_bit 63 (sint c + sint (word_of_int delta :: cell))"
|
||||
by (simp add: sint_word_add)
|
||||
have step2: "sint (word_of_int delta :: cell) = delta"
|
||||
using assms(2)
|
||||
by (simp add: sint_sbintrunc' signed_take_bit_int_eq_self)
|
||||
have step3: "signed_take_bit 63 (sint c + delta) = sint c + delta"
|
||||
by (rule signed_take_bit_int_eq_self) (use b assms(2) in auto)
|
||||
from step1 step2 step3 show ?thesis by simp
|
||||
qed
|
||||
|
||||
lemma cell_safe_1 [simp]: "cell_safe 1"
|
||||
by (simp add: cell_safe_def word_sle_eq)
|
||||
lemma cell_safe_0 [simp]: "cell_safe 0"
|
||||
by (simp add: cell_safe_def word_sle_eq)
|
||||
|
||||
(* Subtraction variant, for the common "de_heat e - amount" shape (amount
|
||||
is itself a cell parameter, not a fixed literal, so cell_safe_add_sint
|
||||
with a literal delta doesn't directly apply). *)
|
||||
lemma cell_safe_sub_sint:
|
||||
assumes "cell_safe c"
|
||||
assumes "cell_safe amount"
|
||||
shows "sint (c - amount) = sint c - sint amount"
|
||||
proof -
|
||||
have bc: "-4294967296 \<le> sint c" "sint c \<le> 4294967296"
|
||||
using assms(1) by (simp_all add: cell_safe_sint_bounds)
|
||||
have ba: "-4294967296 \<le> sint amount" "sint amount \<le> 4294967296"
|
||||
using assms(2) by (simp_all add: cell_safe_sint_bounds)
|
||||
have "sint (c - amount) = signed_take_bit 63 (sint c - sint amount)"
|
||||
by (simp add: sint_word_diff)
|
||||
also have "\<dots> = sint c - sint amount"
|
||||
by (rule signed_take_bit_int_eq_self) (use bc ba in auto)
|
||||
finally show ?thesis .
|
||||
qed
|
||||
|
||||
(* Every "nat n" conversion the old int-based PICK/ROLL/etc. definitions
|
||||
did becomes "unat n" under the word migration -- but unat reinterprets
|
||||
the raw bit pattern as unsigned, which only agrees with the natural
|
||||
"nat of the signed value" reading when n is non-negative in the SIGNED
|
||||
sense (0 \<le>s n). Every call site that uses this already checks n <s 0
|
||||
first and only reaches the nat-conversion in the non-negative case, so
|
||||
this lemma is exactly the bridge those proofs need, proved once here
|
||||
rather than re-derived at every site. *)
|
||||
lemma unat_eq_nat_sint_of_nonneg:
|
||||
assumes "0 \<le>s (n :: cell)"
|
||||
shows "unat n = nat (sint n)"
|
||||
proof -
|
||||
have lo: "0 \<le> sint n" using assms by (simp add: word_sle_eq)
|
||||
have hi63: "sint n < 2 ^ 63" using sint_lt[of n] by simp
|
||||
have hi: "sint n < 2 ^ (64::nat)" using hi63 by simp
|
||||
have "uint n = take_bit (64::nat) (sint n)" by (simp add: uint_sint)
|
||||
also have "\<dots> = sint n"
|
||||
by (rule take_bit_int_eq_self) (use lo hi in auto)
|
||||
finally show ?thesis by (simp add: unat_eq_nat_uint)
|
||||
qed
|
||||
|
||||
(* Signed absolute value on cell. word's inherited "abs" (if it resolved at
|
||||
all) would be unsigned-magnitude, which is wrong for cell_t's signed
|
||||
semantics -- must build this explicitly from the signed order <s.
|
||||
NOTE: cell_abs min_word = min_word (two's-complement negation of
|
||||
INT64_MIN wraps back to itself, matching C's ABS(LONG_MIN) hazard) --
|
||||
this is a genuine, documented exclusion, not an oversight. *)
|
||||
definition cell_abs :: "cell \<Rightarrow> cell" where
|
||||
"cell_abs n = (if n <s 0 then -n else n)"
|
||||
|
||||
(* Signed truncating division/remainder, matching C99's "/" and "%" on
|
||||
signed cell_t: quotient truncates toward zero, remainder takes the sign
|
||||
of the dividend. word's inherited div/mod are unsigned-magnitude and
|
||||
would silently give the wrong answer for negative operands -- e.g.
|
||||
(-7) div 2 under unsigned word semantics treats the bit pattern of -7 as
|
||||
a huge positive number, nothing like C's -7 / 2 = -3. *)
|
||||
definition cell_sdiv :: "cell \<Rightarrow> cell \<Rightarrow> cell" where
|
||||
"cell_sdiv a b = word_of_int (sgn (sint a) * sgn (sint b) * (\<bar>sint a\<bar> div \<bar>sint b\<bar>))"
|
||||
|
||||
definition cell_smod :: "cell \<Rightarrow> cell \<Rightarrow> cell" where
|
||||
"cell_smod a b = word_of_int (sgn (sint a) * (\<bar>sint a\<bar> mod \<bar>sint b\<bar>))"
|
||||
|
||||
(* =========================================================================
|
||||
Section 2: Stack type and capacity constants
|
||||
======================================================================== *)
|
||||
|
||||
Reference in New Issue
Block a user