diff --git a/JoltConstraints/Constraints/InstructionLookupRa.lean b/JoltConstraints/Constraints/InstructionLookupRa.lean new file mode 100644 index 0000000..0996581 --- /dev/null +++ b/JoltConstraints/Constraints/InstructionLookupRa.lean @@ -0,0 +1,29 @@ +import Mathlib.Algebra.BigOperators.Group.Finset.Basic +import Mathlib.Algebra.Field.Defs +import JoltConstraints.witness +import JoltConstraints.lookup_table +import JoltConstraints.honest_witness + +set_option autoImplicit false + +namespace JoltConstraints + +open scoped BigOperators + +/-- The `chunk`th virtual chunk of a 128-bit lookup address, most significant first. -/ +def instructionLookupChunk (params : WitnessParams) (address : Fin (2 ^ 128)) + (chunk : Fin params.virtualInstructionChunks) : Fin (2 ^ params.virtualChunkBits) := + ⟨(address.val / + 2 ^ ((params.virtualInstructionChunks - 1 - chunk.val) * params.virtualChunkBits)) % + 2 ^ params.virtualChunkBits, + Nat.mod_lt _ (pow_pos (by decide : 0 < (2 : Nat)) _)⟩ + +/-- `LookupRa(x,t)` in `constraints.md`: multiply the virtual instruction-address +entries selected by the chunks of `x`. -/ +noncomputable def instructionLookupRa {F : Type} [Field F] {params : WitnessParams} + (witness : WitnessType F params) (address : Fin (2 ^ 128)) + (t : Fin params.traceLength) : F := + ∏ chunk : Fin params.virtualInstructionChunks, + witness.InstructionRa chunk (instructionLookupChunk params address chunk) t + +end JoltConstraints diff --git a/JoltConstraints/Constraints/InstructionReadSelection.lean b/JoltConstraints/Constraints/InstructionReadSelection.lean index a67b071..ae24c11 100644 --- a/JoltConstraints/Constraints/InstructionReadSelection.lean +++ b/JoltConstraints/Constraints/InstructionReadSelection.lean @@ -1,4 +1,4 @@ -import JoltConstraints.Constraints.LookupOutputEqInstructionReadRaf +import JoltConstraints.Constraints.InstructionLookupRa set_option autoImplicit false diff --git a/JoltConstraints/Constraints/LookupEntryProofHelpers.lean b/JoltConstraints/Constraints/LookupEntryProofHelpers.lean new file mode 100644 index 0000000..73720d6 --- /dev/null +++ b/JoltConstraints/Constraints/LookupEntryProofHelpers.lean @@ -0,0 +1,1050 @@ +import JoltConstraints.Constraints.LookupOperandData +import JoltConstraints.Constraints.LookupWriteProofHelpers +import JoltConstraints.Constraints.LookupPextProofHelpers + +/-! +# Per-table obligations for constraint (39) + +Constraint (39) says that on an execution row the lookup output equals the +selected table entry at the honest lookup address. After the address sum and the +table sum are collapsed (see `LookupOutputEqInstructionReadRaf.lean`), what is +left is one obligation per `LookupTableKind`: + + lookupTableEntry k (rowLookupAddress row) = rowLookupOutput row + +whenever the row's instruction uses table `k`. This file states that obligation +as `LookupEntryCorrect` and proves it table by table as +`lookupEntryCorrect_`. `lookupEntryCorrect_of_lookupTable` dispatches on +the table. +-/ + +set_option autoImplicit false + +namespace JoltConstraints + +open HonestWitness +open Sail PreSail LeanRV64D.Functions + +/-- The expanded instruction executed by a trace row. -/ +abbrev rowInstruction {program : JoltProgram} (row : JoltTraceRow program) : JoltISA.Instr := + (getElem program.expandedBytecode row.rowIndex.val row.rowIndex.isLt).expandedInstruction + +/-- The honest 128-bit lookup index of an execution row. -/ +noncomputable def rowLookupIndex {program : JoltProgram} (row : JoltTraceRow program) : + BitVec 128 := + let bc := getElem program.expandedBytecode row.rowIndex.val row.rowIndex.isLt + instructionLookupIndex bc.expandedInstruction bc.address row.preState row.postState + +/-- The honest lookup index as a table address. -/ +noncomputable def rowLookupAddress {program : JoltProgram} (row : JoltTraceRow program) : + Fin (2 ^ 128) := + (rowLookupIndex row).toFin + +/-- The per-table obligation of constraint (39): table `table` at the honest +address is the honest lookup output. -/ +def LookupEntryCorrect (F : Type) [Field F] {program : JoltProgram} + (table : LookupTableKind) (row : JoltTraceRow program) : Prop := + lookupTableEntry (F := F) table (rowLookupAddress row) = ((rowLookupOutput row).toNat : F) + +/-! ## Uninterleaving -/ + +theorem uninterleave_fst_getLsbD (v : BitVec 128) (i : Nat) (hi : i < 64) : + (uninterleave v.toFin).1.getLsbD i = v.getLsbD (2 * i + 1) := by + interval_cases i <;> (simp only [uninterleave, BitVec.ofFin_toFin]; bv_decide) + +theorem uninterleave_snd_getLsbD (v : BitVec 128) (i : Nat) (hi : i < 64) : + (uninterleave v.toFin).2.getLsbD i = v.getLsbD (2 * i) := by + interval_cases i <;> (simp only [uninterleave, BitVec.ofFin_toFin]; bv_decide) + +theorem uninterleave_interleave (l r : BitVec 64) : + uninterleave (interleaveLookupOperands l r).toFin = (l, r) := by + ext i hi + · rw [← BitVec.getLsbD_eq_getElem, ← BitVec.getLsbD_eq_getElem, + uninterleave_fst_getLsbD _ i hi, BitVec.getLsbD, + interleaveLookupOperands_testBit l r (by omega)] + have h1 : (2 * i + 1) % 2 = 1 := by omega + have h2 : (2 * i + 1) / 2 = i := by omega + simp only [h1, h2, ↓reduceIte] + · rw [← BitVec.getLsbD_eq_getElem, ← BitVec.getLsbD_eq_getElem, + uninterleave_snd_getLsbD _ i hi, BitVec.getLsbD, + interleaveLookupOperands_testBit l r (by omega)] + have h1 : 2 * i % 2 = 0 := by omega + have h2 : 2 * i / 2 = i := by omega + simp only [h1, h2, Nat.zero_ne_one, ↓reduceIte] + +/-! ## Wide arithmetic as bit-vector arithmetic -/ + +theorem ofNat128_addWide (x y : BitVec 64) : + BitVec.ofNat 128 (JoltISA.addWide x y) = x.setWidth 128 + y.setWidth 128 := by + apply BitVec.eq_of_toNat_eq + have hx := x.isLt; have hy := y.isLt + simp only [JoltISA.addWide, BitVec.toNat_ofNat, BitVec.toNat_add, BitVec.toNat_setWidth] + omega + +theorem ofNat64_addWide (x y : BitVec 64) : + BitVec.ofNat 64 (JoltISA.addWide x y) = x + y := by + apply BitVec.eq_of_toNat_eq + simp only [JoltISA.addWide, BitVec.toNat_ofNat, BitVec.toNat_add] + +theorem ofNat128_mulWide (x y : BitVec 64) : + BitVec.ofNat 128 (JoltISA.mulWide x y) = x.setWidth 128 * y.setWidth 128 := by + apply BitVec.eq_of_toNat_eq + have hx := x.isLt; have hy := y.isLt + have hxy : x.toNat * y.toNat < 2 ^ 128 := by + calc x.toNat * y.toNat < 2 ^ 64 * 2 ^ 64 := Nat.mul_lt_mul'' hx hy + _ = 2 ^ 128 := by norm_num + simp only [JoltISA.mulWide, BitVec.toNat_ofNat, BitVec.toNat_mul, BitVec.toNat_setWidth] + rw [Nat.mod_eq_of_lt (a := x.toNat) (by omega), Nat.mod_eq_of_lt (a := y.toNat) (by omega), + Nat.mod_eq_of_lt hxy] + +/-! ## Table entries at honest addresses -/ + +section entries +variable {F : Type} [Field F] + +theorem rangeCheck_entry (v : BitVec 128) : + rangeCheckTableEntry (F := F) v.toFin = ((v.setWidth 64).toNat : F) := by + unfold rangeCheckTableEntry + have h : (v &&& ((1#128 <<< 64) - 1)).setWidth 64 = v.setWidth 64 := by bv_decide + simp only [BitVec.ofFin_toFin, h] + +theorem interleave_testBit_odd (l r : BitVec 64) (i : Nat) (hi : i < 64) : + (interleaveLookupOperands l r).toNat.testBit (2 * i + 1) = l.getLsbD i := by + rw [interleaveLookupOperands_testBit l r (by omega)] + have h1 : (2 * i + 1) % 2 = 1 := by omega + have h2 : (2 * i + 1) / 2 = i := by omega + simp only [h1, h2, ↓reduceIte] + +theorem interleave_testBit_even (l r : BitVec 64) (i : Nat) (hi : i < 64) : + (interleaveLookupOperands l r).toNat.testBit (2 * i) = r.getLsbD i := by + rw [interleaveLookupOperands_testBit l r (by omega)] + have h1 : 2 * i % 2 = 0 := by omega + have h2 : 2 * i / 2 = i := by omega + simp only [h1, h2, Nat.zero_ne_one, ↓reduceIte] + +theorem and_entry (l r : BitVec 64) : + andTableEntry (F := F) (interleaveLookupOperands l r).toFin = ((l &&& r).toNat : F) := by + rw [bitVec_toNat_cast_eq_sum] + unfold andTableEntry + refine Finset.sum_congr rfl fun i _ => ?_ + simp only [BitVec.val_toFin, interleave_testBit_odd l r i i.isLt, + interleave_testBit_even l r i i.isLt, BitVec.getLsbD_and] + +theorem or_entry (l r : BitVec 64) : + orTableEntry (F := F) (interleaveLookupOperands l r).toFin = ((l ||| r).toNat : F) := by + rw [bitVec_toNat_cast_eq_sum] + unfold orTableEntry + refine Finset.sum_congr rfl fun i _ => ?_ + simp only [BitVec.val_toFin, interleave_testBit_odd l r i i.isLt, + interleave_testBit_even l r i i.isLt, BitVec.getLsbD_or] + +theorem xor_entry (l r : BitVec 64) : + xorTableEntry (F := F) (interleaveLookupOperands l r).toFin = ((l ^^^ r).toNat : F) := by + rw [bitVec_toNat_cast_eq_sum] + unfold xorTableEntry + refine Finset.sum_congr rfl fun i _ => ?_ + simp only [BitVec.val_toFin, interleave_testBit_odd l r i i.isLt, + interleave_testBit_even l r i i.isLt, BitVec.getLsbD_xor, bne] + +theorem andn_entry (l r : BitVec 64) : + andnTableEntry (F := F) (interleaveLookupOperands l r).toFin = ((l &&& ~~~r).toNat : F) := by + simp only [andnTableEntry, uninterleave_interleave] + +theorem equal_entry (l r : BitVec 64) : + equalTableEntry (F := F) (interleaveLookupOperands l r).toFin = if l = r then 1 else 0 := by + simp only [equalTableEntry, uninterleave_interleave] + +theorem notEqual_entry (l r : BitVec 64) : + notEqualTableEntry (F := F) (interleaveLookupOperands l r).toFin = if l ≠ r then 1 else 0 := by + simp only [notEqualTableEntry, uninterleave_interleave] + +theorem unsignedLessThan_entry (l r : BitVec 64) : + unsignedLessThanTableEntry (F := F) (interleaveLookupOperands l r).toFin = + if l < r then 1 else 0 := by + simp only [unsignedLessThanTableEntry, uninterleave_interleave] + +theorem unsignedLessThanEqual_entry (l r : BitVec 64) : + unsignedLessThanEqualTableEntry (F := F) (interleaveLookupOperands l r).toFin = + if l ≤ r then 1 else 0 := by + simp only [unsignedLessThanEqualTableEntry, uninterleave_interleave] + +theorem unsignedGreaterThanEqual_entry (l r : BitVec 64) : + unsignedGreaterThanEqualTableEntry (F := F) (interleaveLookupOperands l r).toFin = + if l ≥ r then 1 else 0 := by + simp only [unsignedGreaterThanEqualTableEntry, uninterleave_interleave] + +theorem signedLessThan_entry (l r : BitVec 64) : + signedLessThanTableEntry (F := F) (interleaveLookupOperands l r).toFin = + if l.toInt < r.toInt then 1 else 0 := by + simp only [signedLessThanTableEntry, uninterleave_interleave] + +theorem signedGreaterThanEqual_entry (l r : BitVec 64) : + signedGreaterThanEqualTableEntry (F := F) (interleaveLookupOperands l r).toFin = + if l.toInt ≥ r.toInt then 1 else 0 := by + simp only [signedGreaterThanEqualTableEntry, uninterleave_interleave] + +theorem validDiv0_entry (l r : BitVec 64) : + validDiv0TableEntry (F := F) (interleaveLookupOperands l r).toFin = + if l = 0 ∧ r ≠ -1 then 0 else 1 := by + have hmax : ((1#128 <<< 64) - 1).setWidth 64 = (-1 : BitVec 64) := by decide + simp only [validDiv0TableEntry, uninterleave_interleave, hmax] + split_ifs <;> simp_all + +theorem validUnsignedRemainder_entry (l r : BitVec 64) : + validUnsignedRemainderTableEntry (F := F) (interleaveLookupOperands l r).toFin = + if r = 0 ∨ l < r then 1 else 0 := by + simp only [validUnsignedRemainderTableEntry, uninterleave_interleave] + +theorem signMask_entry (l r : BitVec 64) : + signMaskTableEntry (F := F) (interleaveLookupOperands l r).toFin = + ((if l.msb then (-1 : BitVec 64) else 0).toNat : F) := by + unfold signMaskTableEntry + have hbit : (interleaveLookupOperands l r &&& 1#128 <<< 127 ≠ 0) = (l.msb = true) := by + have h127 := interleave_testBit_odd l r 63 (by omega) + have hiff : ∀ v : BitVec 128, (v &&& 1#128 <<< 127 ≠ 0) ↔ v.getLsbD 127 = true := by + intro v; bv_decide + rw [hiff, BitVec.getLsbD, h127, BitVec.msb_eq_getLsbD_last] + have hones : ((1#128 <<< 64) - 1).setWidth 64 = (-1 : BitVec 64) := by decide + simp only [BitVec.ofFin_toFin, hbit, hones] + split <;> simp + +theorem cast_ite_one_zero (c : Prop) [Decidable c] : + (((if c then (1 : BitVec 64) else 0).toNat : F)) = if c then 1 else 0 := by + split <;> simp + +end entries + +theorem jolt_slt_value_eq (x y : BitVec 64) : + jolt_slt_value x y = if x.toInt < y.toInt then 1 else 0 := by + unfold jolt_slt_value zopz0zI_s bool_to_bit bool_bit_forwards zero_extend Sail.BitVec.zeroExtend + by_cases hc : x.toInt < y.toInt <;> simp [hc] + +theorem toNatInt_lt (x y : BitVec 64) : (BitVec.toNatInt x < BitVec.toNatInt y) ↔ x < y := by + simp [BitVec.toNatInt, BitVec.lt_def] + +theorem toNatInt_ge (x y : BitVec 64) : (BitVec.toNatInt x ≥ BitVec.toNatInt y) ↔ x ≥ y := by + simp [BitVec.toNatInt, BitVec.le_def] + +theorem jolt_sltu_value_eq (x y : BitVec 64) : + jolt_sltu_value x y = if x < y then 1 else 0 := by + unfold jolt_sltu_value zopz0zI_u bool_to_bit bool_bit_forwards zero_extend Sail.BitVec.zeroExtend + by_cases hc : x < y <;> simp [hc, toNatInt_lt] + + +theorem row_executes_exists {program : JoltProgram} (row : JoltTraceRow program) : + ∃ advice : (rowInstruction row).RuntimeAdvice, + JoltISA.execInstr ((rowInstruction row).withRuntimeAdvice advice) row.preState = + .ok (.Retire_Success ()) row.postState := + ⟨row.runtimeAdvice, row.executes⟩ + +theorem upper_zero_of_lt (n : Nat) (h : n < 2 ^ 64) : BitVec.ofNat 128 n >>> 64 = 0 := by + apply BitVec.eq_of_toNat_eq + simp only [BitVec.toNat_ushiftRight, BitVec.toNat_ofNat, Nat.shiftRight_eq_div_pow] + rw [Nat.mod_eq_of_lt (by omega), Nat.div_eq_of_lt h] + rfl + +/-! ## Per-table obligations -/ + +section tables +variable {F : Type} [Field F] {program : JoltProgram} + +set_option hygiene false in +/-- Split on the row instruction, keep the instructions that use the table, and +unfold the index, output and table definitions. -/ +macro "lookup_cases" : tactic => `(tactic| ( + unfold LookupEntryCorrect rowLookupAddress rowLookupIndex + cases hi : rowInstruction row <;> + simp only [hi, JoltMetadata.lookupTable, reduceCtorEq, Option.some.injEq] at h <;> + simp only [lookupTableEntry, rowLookupOutput, instructionLookupIndex, hi])) + +theorem lookupEntryCorrect_RangeCheck (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .RangeCheck) : + LookupEntryCorrect F .RangeCheck row := by + lookup_cases + all_goals simp only [rangeCheck_entry] + all_goals simp [ BitVec.setWidth_ofNat_of_le, BitVec.setWidth_setWidth_of_le, + jolt_virtual_muli_value] + +theorem lookupEntryCorrect_And (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .And) : + LookupEntryCorrect F .And row := by + lookup_cases + all_goals simp only [and_entry, Riscv.andi] + +theorem lookupEntryCorrect_Or (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .Or) : + LookupEntryCorrect F .Or row := by + lookup_cases + all_goals simp only [or_entry, Riscv.ori] + +theorem lookupEntryCorrect_Xor (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .Xor) : + LookupEntryCorrect F .Xor row := by + lookup_cases + all_goals simp only [xor_entry, jolt_xor_value] + +theorem lookupEntryCorrect_Andn (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .Andn) : + LookupEntryCorrect F .Andn row := by + lookup_cases + all_goals simp only [andn_entry, jolt_andn_value] + +theorem lookupEntryCorrect_SignedLessThan (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .SignedLessThan) : + LookupEntryCorrect F .SignedLessThan row := by + lookup_cases + all_goals simp only [signedLessThan_entry, jolt_slt_value_eq, cast_ite_one_zero, + JoltISA.branchDecisionPure, zopz0zI_s, decide_eq_true_eq] + +theorem lookupEntryCorrect_UnsignedLessThan (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .UnsignedLessThan) : + LookupEntryCorrect F .UnsignedLessThan row := by + lookup_cases + all_goals simp only [unsignedLessThan_entry, jolt_sltu_value_eq, cast_ite_one_zero, + JoltISA.branchDecisionPure, zopz0zI_u, decide_eq_true_eq, toNatInt_lt] + +theorem lookupEntryCorrect_SignedGreaterThanEqual (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .SignedGreaterThanEqual) : + LookupEntryCorrect F .SignedGreaterThanEqual row := by + lookup_cases + all_goals simp only [signedGreaterThanEqual_entry, cast_ite_one_zero, + JoltISA.branchDecisionPure, zopz0zKzJ_s, decide_eq_true_eq] + +theorem lookupEntryCorrect_UnsignedGreaterThanEqual (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .UnsignedGreaterThanEqual) : + LookupEntryCorrect F .UnsignedGreaterThanEqual row := by + lookup_cases + all_goals simp only [unsignedGreaterThanEqual_entry, cast_ite_one_zero, + JoltISA.branchDecisionPure, zopz0zKzJ_u, decide_eq_true_eq, toNatInt_ge] + +theorem lookupEntryCorrect_Equal (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .Equal) : + LookupEntryCorrect F .Equal row := by + lookup_cases + all_goals simp only [equal_entry, cast_ite_one_zero, JoltISA.branchDecisionPure, + beq_iff_eq, jolt_assert_eq] + +theorem lookupEntryCorrect_NotEqual (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .NotEqual) : + LookupEntryCorrect F .NotEqual row := by + lookup_cases + all_goals simp only [notEqual_entry, cast_ite_one_zero, JoltISA.branchDecisionPure, + bne_iff_ne, ne_eq] + +end tables + +section entries2 +variable {F : Type} [Field F] + +theorem signExtendWord_entry (v : BitVec 128) : + signExtendWordTableEntry (F := F) v.toFin = + (((v.setWidth 32).signExtend 64).toNat : F) := by + unfold signExtendWordTableEntry + simp only [BitVec.ofFin_toFin] + split + · rename_i hs + congr 2 + bv_decide + · rename_i hs + congr 2 + bv_decide + +theorem upperWord_entry (n : Nat) (hn : n < 2 ^ 128) : + upperWordTableEntry (F := F) (BitVec.ofNat 128 n).toFin = + ((BitVec.ofNat 64 (n / 2 ^ 64)).toNat : F) := by + unfold upperWordTableEntry + simp only [BitVec.ofFin_toFin] + congr 2 + apply BitVec.eq_of_toNat_eq + simp only [BitVec.toNat_setWidth, BitVec.toNat_ushiftRight, BitVec.toNat_ofNat, + Nat.shiftRight_eq_div_pow, Nat.mod_eq_of_lt hn] + +theorem lowerHalfWord_entry (v : BitVec 128) : + lowerHalfWordTableEntry (F := F) v.toFin = (((v.setWidth 32).setWidth 64).toNat : F) := by + unfold lowerHalfWordTableEntry + simp only [BitVec.ofFin_toFin] + congr 2 + bv_decide + +theorem rangeCheckAligned_entry (v : BitVec 128) : + rangeCheckAlignedTableEntry (F := F) v.toFin = + ((v.setWidth 64 &&& ~~~1#64).toNat : F) := by + unfold rangeCheckAlignedTableEntry + have h : (v &&& ((1#128 <<< 64) - 1)).setWidth 64 = v.setWidth 64 := by bv_decide + simp only [BitVec.ofFin_toFin, h] + +theorem alignAddr_entry (v : BitVec 128) : + alignAddrTableEntry (F := F) v.toFin = + ((v.setWidth 64 &&& ~~~7#64).toNat : F) := by + unfold alignAddrTableEntry + have h : (v &&& ((1#128 <<< 64) - 1)).setWidth 64 = v.setWidth 64 := by bv_decide + simp only [BitVec.ofFin_toFin, h] + +theorem umod_toNat (v : BitVec 128) (m : Nat) (hm : m < 2 ^ 128) : + (v % BitVec.ofNat 128 m).toNat = v.toNat % m := by + simp only [BitVec.toNat_umod, BitVec.toNat_ofNat, Nat.mod_eq_of_lt hm] + +theorem one_shiftLeft_eq (k : Nat) : 1#64 <<< k = BitVec.ofNat 64 (2 ^ k) := by + apply BitVec.eq_of_toNat_eq + simp only [BitVec.toNat_shiftLeft, BitVec.toNat_ofNat, Nat.shiftLeft_eq] + norm_num + +theorem pow2_entry (v : BitVec 128) : + pow2TableEntry (F := F) v.toFin = ((BitVec.ofNat 64 (2 ^ (v.toNat % 64))).toNat : F) := by + unfold pow2TableEntry + simp only [BitVec.ofFin_toFin, one_shiftLeft_eq] + rw [show (64 : BitVec 128) = BitVec.ofNat 128 64 from rfl, umod_toNat _ _ (by norm_num)] + +theorem pow2W_entry (v : BitVec 128) : + pow2WTableEntry (F := F) v.toFin = ((BitVec.ofNat 64 (2 ^ (v.toNat % 32))).toNat : F) := by + unfold pow2WTableEntry + simp only [BitVec.ofFin_toFin, one_shiftLeft_eq] + rw [show (32 : BitVec 128) = BitVec.ofNat 128 32 from rfl, umod_toNat _ _ (by norm_num)] + +theorem shiftRightBitmask_value (s : Nat) (hs : s < 64) : + ((1#128 <<< (64 - s)) - 1).setWidth 64 <<< s = + BitVec.ofNat 64 (((1 <<< (64 - s)) - 1) <<< s) := by + interval_cases s <;> decide + +theorem shiftRightBitmask_entry (v : BitVec 128) : + shiftRightBitmaskTableEntry (F := F) v.toFin = + ((BitVec.ofNat 64 (((1 <<< (64 - v.toNat % 64)) - 1) <<< (v.toNat % 64))).toNat : F) := by + unfold shiftRightBitmaskTableEntry + simp only [BitVec.ofFin_toFin] + rw [show (64 : BitVec 128) = BitVec.ofNat 128 64 from rfl, umod_toNat _ _ (by norm_num), + shiftRightBitmask_value _ (Nat.mod_lt _ (by norm_num))] + +theorem shiftRightBitmaskW_value (s : Nat) (hs : s < 32) : + ((1#128 <<< 32) - (1#128 <<< s)).setWidth 64 = BitVec.ofNat 64 (2 ^ 32 - 2 ^ s) := by + interval_cases s <;> decide + +theorem shiftRightBitmaskW_entry (v : BitVec 128) : + shiftRightBitmaskWTableEntry (F := F) v.toFin = + ((BitVec.ofNat 64 (2 ^ 32 - 2 ^ (v.toNat % 32))).toNat : F) := by + unfold shiftRightBitmaskWTableEntry + simp only [BitVec.ofFin_toFin] + rw [show (32 : BitVec 128) = BitVec.ofNat 128 32 from rfl, umod_toNat _ _ (by norm_num), + shiftRightBitmaskW_value _ (Nat.mod_lt _ (by norm_num))] + +theorem negateIf_entry (l r : BitVec 64) : + virtualNegateIfTableEntry (F := F) (interleaveLookupOperands l r).toFin = + ((if l.msb then -r else r).toNat : F) := by + have hmask : ((1#128 <<< 64) - 1).setWidth 64 = (-1 : BitVec 64) := by decide + have h1 : r &&& -1 = r := by bv_decide + have h2 : -r &&& -1 = -r := by bv_decide + simp only [virtualNegateIfTableEntry, uninterleave_interleave, hmask, h1, h2] + have hsign : (l &&& 1#64 <<< 63 = 0) ↔ l.msb = false := by bv_decide + by_cases hm : l.msb + · have : ¬ (l &&& 1#64 <<< 63 = 0) := by rw [hsign, hm]; simp + simp only [this, hm, ↓reduceIte] + · have : l &&& 1#64 <<< 63 = 0 := by rw [hsign]; simpa using hm + simp only [this, hm, ↓reduceIte, Bool.false_eq_true] + +theorem shiftDataB_entry (l r : BitVec 64) : + shiftDataBTableEntry (F := F) (interleaveLookupOperands l r).toFin = + ((jolt_virtual_shift_data_b_value l r).toNat : F) := by + have hm : ((1#128 <<< 8) - 1).setWidth 64 = (0xFF : BitVec 64) := by decide + simp only [shiftDataBTableEntry, uninterleave_interleave, hm, jolt_virtual_shift_data_b_value] + +theorem shiftDataH_entry (l r : BitVec 64) : + shiftDataHTableEntry (F := F) (interleaveLookupOperands l r).toFin = + ((jolt_virtual_shift_data_h_value l r).toNat : F) := by + have hm : ((1#128 <<< 16) - 1).setWidth 64 = (0xFFFF : BitVec 64) := by decide + simp only [shiftDataHTableEntry, uninterleave_interleave, hm, jolt_virtual_shift_data_h_value] + +theorem shiftDataW_entry (l r : BitVec 64) : + shiftDataWTableEntry (F := F) (interleaveLookupOperands l r).toFin = + ((jolt_virtual_shift_data_w_value l r).toNat : F) := by + have hm : ((1#128 <<< 32) - 1).setWidth 64 = (0xFFFF_FFFF : BitVec 64) := by decide + simp only [shiftDataWTableEntry, uninterleave_interleave, hm, jolt_virtual_shift_data_w_value] + +theorem shiftLeft_ofNat_eq (c k : Nat) : + BitVec.ofNat 64 c <<< k = BitVec.ofNat 64 (c <<< k) := by + apply BitVec.eq_of_toNat_eq + simp only [BitVec.toNat_shiftLeft, BitVec.toNat_ofNat, Nat.shiftLeft_eq] + rw [Nat.mod_mul_mod] + +theorem toNat_eq_of_setWidth (a : BitVec 128) (b : BitVec 64) (h : a = b.setWidth 128) : + a.toNat = b.toNat := by + rw [h, BitVec.toNat_setWidth, Nat.mod_eq_of_lt (by have := b.isLt; omega)] + +theorem ofNat64_eq_setWidth (n : Nat) : + BitVec.ofNat 64 n = (BitVec.ofNat 128 n).setWidth 64 := by + rw [BitVec.setWidth_ofNat_of_le (by norm_num)] + +theorem windowMaskB_entry (n : Nat) : + windowMaskBTableEntry (F := F) (BitVec.ofNat 128 n).toFin = + ((BitVec.ofNat 64 (0xFF <<< (8 * (BitVec.ofNat 64 n &&& 7).toNat))).toNat : F) := by + have hm : ((1#128 <<< 8) - 1).setWidth 64 = BitVec.ofNat 64 0xFF := by decide + have ho : (BitVec.ofNat 128 n &&& 7).toNat = (BitVec.ofNat 64 n &&& 7).toNat := + toNat_eq_of_setWidth _ _ (by rw [ofNat64_eq_setWidth]; bv_decide) + simp only [windowMaskBTableEntry, BitVec.ofFin_toFin, hm, shiftLeft_ofNat_eq, ho] + +theorem windowMaskH_entry (n : Nat) : + windowMaskHTableEntry (F := F) (BitVec.ofNat 128 n).toFin = + ((BitVec.ofNat 64 (0xFFFF <<< (8 * (BitVec.ofNat 64 n &&& 6).toNat))).toNat : F) := by + have hm : ((1#128 <<< 16) - 1).setWidth 64 = BitVec.ofNat 64 0xFFFF := by decide + have ho : (BitVec.ofNat 128 n &&& 6).toNat = (BitVec.ofNat 64 n &&& 6).toNat := + toNat_eq_of_setWidth _ _ (by rw [ofNat64_eq_setWidth]; bv_decide) + simp only [windowMaskHTableEntry, BitVec.ofFin_toFin, hm, shiftLeft_ofNat_eq, ho] + +theorem windowMaskW_entry (n : Nat) : + windowMaskWTableEntry (F := F) (BitVec.ofNat 128 n).toFin = + ((BitVec.ofNat 64 (0xFFFF_FFFF <<< (32 * ((BitVec.ofNat 64 n >>> 2) &&& 1).toNat))).toNat : F) := by + have hm : ((1#128 <<< 32) - 1).setWidth 64 = BitVec.ofNat 64 0xFFFF_FFFF := by decide + have ho : ((BitVec.ofNat 128 n >>> 2) &&& 1).toNat = ((BitVec.ofNat 64 n >>> 2) &&& 1).toNat := + toNat_eq_of_setWidth _ _ (by rw [ofNat64_eq_setWidth]; bv_decide) + simp only [windowMaskWTableEntry, BitVec.ofFin_toFin, hm, shiftLeft_ofNat_eq, ho] + +theorem rotater_eq {n : Nat} (z : BitVec n) (s : Nat) (hs : s ≤ n) : + rotater z s = (z >>> s) ||| (z <<< (n - s)) := by + unfold rotater + have : (Sail.BitVec.length z -i (s : Int)) = ((n - s : Nat) : Int) := by + simp only [Sail.BitVec.length]; omega + rw [this] + rfl + +theorem rotatel_one (z : BitVec 64) : rotatel z 1 = (z <<< 1) ||| (z >>> 63) := rfl + +theorem xorRotL1_getLsbD (x y : BitVec 64) (i : Nat) (hi : i < 64) : + (x ^^^ ((y <<< 1) ||| (y >>> 63))).getLsbD i = (x.getLsbD i != y.getLsbD ((i + 63) % 64)) := by + interval_cases i <;> bv_decide + +theorem xorRotL1_entry (l r : BitVec 64) : + virtualXorRotL1TableEntry (F := F) (interleaveLookupOperands l r).toFin = + ((jolt_virtual_xorrotl1_value l r).toNat : F) := by + rw [jolt_virtual_xorrotl1_value, rotatel_one, bitVec_toNat_cast_eq_sum] + unfold virtualXorRotL1TableEntry + refine Finset.sum_congr rfl fun i _ => ?_ + simp only [BitVec.val_toFin, interleave_testBit_odd l r i i.isLt, + interleave_testBit_even l r _ (Nat.mod_lt _ (by omega)), xorRotL1_getLsbD l r i i.isLt] + +theorem xorRot_entry (r : Nat) (hr : r = 32 ∨ r = 24 ∨ r = 16 ∨ r = 63) (l y : BitVec 64) : + virtualXorRotTableEntry (F := F) r (interleaveLookupOperands l y).toFin = + ((jolt_virtual_xorrot_value r l y).toNat : F) := by + have hr64 : r < 64 := by omega + simp only [virtualXorRotTableEntry, uninterleave_interleave, jolt_virtual_xorrot_value, + rotater_eq _ r hr64.le, Nat.mod_eq_of_lt hr64] + congr 2 + rcases hr with rfl | rfl | rfl | rfl <;> bv_decide + +theorem xorRotW_entry (r : Nat) + (hr : r = 16 ∨ r = 12 ∨ r = 8 ∨ r = 7 ∨ r = 22 ∨ r = 19 ∨ r = 6) (l y : BitVec 64) : + virtualXorRotWTableEntry (F := F) r (interleaveLookupOperands l y).toFin = + ((jolt_virtual_xorrotw_value r l y).toNat : F) := by + have hr32 : r < 32 := by omega + simp only [virtualXorRotWTableEntry, uninterleave_interleave, jolt_virtual_xorrotw_value, + rotater_eq _ r hr32.le, Nat.mod_eq_of_lt hr32, zero_extend, Sail.BitVec.zeroExtend, + Sail.BitVec.extractLsb] + congr 2 + rcases hr with rfl | rfl | rfl | rfl | rfl | rfl | rfl <;> bv_decide + +theorem halfword_aligned (n : Nat) (h : BitVec.ofNat 64 n &&& 1 = 0) : + BitVec.ofNat 128 n % 2 = 0 := by + rw [ofNat64_eq_setWidth] at h + generalize BitVec.ofNat 128 n = v at h ⊢ + bv_decide + +theorem word_aligned (n : Nat) (h : BitVec.ofNat 64 n &&& 3 = 0) : + BitVec.ofNat 128 n % 4 = 0 := by + rw [ofNat64_eq_setWidth] at h + generalize BitVec.ofNat 128 n = v at h ⊢ + bv_decide + +end entries2 + +theorem mulWide_lt (x y : BitVec 64) : JoltISA.mulWide x y < 2 ^ 128 := by + have hx := x.isLt; have hy := y.isLt + calc x.toNat * y.toNat < 2 ^ 64 * 2 ^ 64 := Nat.mul_lt_mul'' hx hy + _ = 2 ^ 128 := by norm_num + +section tables2 +variable {F : Type} [Field F] {program : JoltProgram} + +theorem lookupEntryCorrect_SignExtendWord (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .SignExtendWord) : + LookupEntryCorrect F .SignExtendWord row := by + lookup_cases + all_goals simp only [signExtendWord_entry] + all_goals simp only [jolt_addiw_value64, jolt_addw_value, + jolt_subw_value, jolt_mulw_value, jolt_virtual_muliw_value, jolt_virtual_sign_extend_word_value, + BitVec.setWidth_ofNat_of_le (by norm_num : 32 ≤ 128), BitVec.setWidth_ofNat_of_le (by norm_num : 32 ≤ 64), + BitVec.setWidth_setWidth_of_le _ (by norm_num : 32 ≤ 128)] + +theorem lookupEntryCorrect_UpperWord (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .UpperWord) : + LookupEntryCorrect F .UpperWord row := by + lookup_cases + rw [upperWord_entry _ (mulWide_lt _ _)] + rfl + +theorem lookupEntryCorrect_LowerHalfWord (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .LowerHalfWord) : + LookupEntryCorrect F .LowerHalfWord row := by + lookup_cases + simp only [lowerHalfWord_entry, jolt_virtual_zero_extend_word_value, zero_extend, + Sail.BitVec.zeroExtend, Sail.BitVec.extractLsb] + congr 2 + +theorem lookupEntryCorrect_RangeCheckAligned (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .RangeCheckAligned) : + LookupEntryCorrect F .RangeCheckAligned row := by + lookup_cases + simp only [rangeCheckAligned_entry, jolt_jalr_target64, BitVec.setWidth_ofNat_of_le (by norm_num : 64 ≤ 128), + Sail.BitVec.update, Sail.BitVec.updateSubrange'] + congr 2 + bv_decide + +theorem lookupEntryCorrect_AlignAddr (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .AlignAddr) : + LookupEntryCorrect F .AlignAddr row := by + lookup_cases + simp only [alignAddr_entry, jolt_virtual_align_addr_value64, + BitVec.setWidth_ofNat_of_le (by norm_num : 64 ≤ 128)] + rfl + +theorem lookupEntryCorrect_SignMask (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .SignMask) : + LookupEntryCorrect F .SignMask row := by + lookup_cases + simp only [signMask_entry, jolt_movsign_value] + +theorem lookupEntryCorrect_VirtualNegateIf (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .VirtualNegateIf) : + LookupEntryCorrect F .VirtualNegateIf row := by + lookup_cases + simp only [negateIf_entry, jolt_virtual_negate_if_value] + +theorem lookupEntryCorrect_Pow2 (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .Pow2) : + LookupEntryCorrect F .Pow2 row := by + lookup_cases + all_goals simp only [pow2_entry, jolt_virtual_pow2_value, jolt_virtual_pow2i_value] + all_goals congr 4 + all_goals simp [BitVec.toNat_setWidth, Nat.mod_mod_of_dvd] + +theorem lookupEntryCorrect_Pow2W (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .Pow2W) : + LookupEntryCorrect F .Pow2W row := by + lookup_cases + all_goals simp only [pow2W_entry, jolt_virtual_pow2w_value, jolt_virtual_pow2iw_value] + all_goals congr 4 + all_goals simp [BitVec.toNat_setWidth, Nat.mod_mod_of_dvd] + +theorem lookupEntryCorrect_ShiftRightBitmask (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .ShiftRightBitmask) : + LookupEntryCorrect F .ShiftRightBitmask row := by + lookup_cases + all_goals simp only [shiftRightBitmask_entry, jolt_virtual_shift_right_bitmask_value, + jolt_virtual_shift_right_bitmaski_value] + all_goals congr 4 <;> simp [BitVec.toNat_setWidth, Nat.mod_mod_of_dvd] + +theorem lookupEntryCorrect_ShiftRightBitmaskW (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .ShiftRightBitmaskW) : + LookupEntryCorrect F .ShiftRightBitmaskW row := by + lookup_cases + simp only [shiftRightBitmaskW_entry, jolt_virtual_shift_right_bitmaskw_value] + congr 5 + +theorem lookupEntryCorrect_ShiftDataB (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .ShiftDataB) : + LookupEntryCorrect F .ShiftDataB row := by + lookup_cases + simp only [shiftDataB_entry] + +theorem lookupEntryCorrect_ShiftDataH (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .ShiftDataH) : + LookupEntryCorrect F .ShiftDataH row := by + lookup_cases + simp only [shiftDataH_entry] + +theorem lookupEntryCorrect_ShiftDataW (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .ShiftDataW) : + LookupEntryCorrect F .ShiftDataW row := by + lookup_cases + simp only [shiftDataW_entry] + +theorem lookupEntryCorrect_WindowMaskB (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .WindowMaskB) : + LookupEntryCorrect F .WindowMaskB row := by + lookup_cases + simp only [windowMaskB_entry, jolt_virtual_window_mask_b_value64] + +theorem lookupEntryCorrect_WindowMaskH (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .WindowMaskH) : + LookupEntryCorrect F .WindowMaskH row := by + lookup_cases + simp only [windowMaskH_entry, jolt_virtual_window_mask_h_value64] + +theorem lookupEntryCorrect_WindowMaskW (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .WindowMaskW) : + LookupEntryCorrect F .WindowMaskW row := by + lookup_cases + simp only [windowMaskW_entry, jolt_virtual_window_mask_w_value64] + +theorem lookupEntryCorrect_VirtualXORROTL1 (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .VirtualXORROTL1) : + LookupEntryCorrect F .VirtualXORROTL1 row := by + lookup_cases + simp only [xorRotL1_entry] + +theorem lookupEntryCorrect_VirtualXORROT32 (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .VirtualXORROT32) : + LookupEntryCorrect F .VirtualXORROT32 row := by + lookup_cases + exact xorRot_entry 32 (by decide) _ _ + +theorem lookupEntryCorrect_VirtualXORROT24 (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .VirtualXORROT24) : + LookupEntryCorrect F .VirtualXORROT24 row := by + lookup_cases + exact xorRot_entry 24 (by decide) _ _ + +theorem lookupEntryCorrect_VirtualXORROT16 (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .VirtualXORROT16) : + LookupEntryCorrect F .VirtualXORROT16 row := by + lookup_cases + exact xorRot_entry 16 (by decide) _ _ + +theorem lookupEntryCorrect_VirtualXORROT63 (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .VirtualXORROT63) : + LookupEntryCorrect F .VirtualXORROT63 row := by + lookup_cases + exact xorRot_entry 63 (by decide) _ _ + +theorem lookupEntryCorrect_VirtualXORROTW16 (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .VirtualXORROTW16) : + LookupEntryCorrect F .VirtualXORROTW16 row := by + lookup_cases + exact xorRotW_entry 16 (by decide) _ _ + +theorem lookupEntryCorrect_VirtualXORROTW12 (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .VirtualXORROTW12) : + LookupEntryCorrect F .VirtualXORROTW12 row := by + lookup_cases + exact xorRotW_entry 12 (by decide) _ _ + +theorem lookupEntryCorrect_VirtualXORROTW8 (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .VirtualXORROTW8) : + LookupEntryCorrect F .VirtualXORROTW8 row := by + lookup_cases + exact xorRotW_entry 8 (by decide) _ _ + +theorem lookupEntryCorrect_VirtualXORROTW7 (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .VirtualXORROTW7) : + LookupEntryCorrect F .VirtualXORROTW7 row := by + lookup_cases + exact xorRotW_entry 7 (by decide) _ _ + +theorem lookupEntryCorrect_VirtualXORROTW22 (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .VirtualXORROTW22) : + LookupEntryCorrect F .VirtualXORROTW22 row := by + lookup_cases + exact xorRotW_entry 22 (by decide) _ _ + +theorem lookupEntryCorrect_VirtualXORROTW19 (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .VirtualXORROTW19) : + LookupEntryCorrect F .VirtualXORROTW19 row := by + lookup_cases + exact xorRotW_entry 19 (by decide) _ _ + +theorem lookupEntryCorrect_VirtualXORROTW6 (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .VirtualXORROTW6) : + LookupEntryCorrect F .VirtualXORROTW6 row := by + lookup_cases + exact xorRotW_entry 6 (by decide) _ _ + +theorem rev8_32 (x : BitVec 32) : rev8 x = swapBytes32 x := by + unfold rev8 + simp only [Id.run] + change IntRange.forIn' _ _ _ >>= _ = _ + unfold IntRange.forIn' + simp only [Sail.BitVec.length, BitVec.length, Nat.cast_ofNat] + iterate 5 + rw [IntRange.forIn'.loop] + simp (config := { decide := true }) only [Membership.mem, dite_true, dite_false, pure_bind, + bind_pure, Int.reduceAdd, Int.reduceSub, Int.reduceToNat] + change BitVec.updateSubrange _ _ _ _ = _ + simp only [BitVec.updateSubrange, Sail.BitVec.updateSubrange', Sail.BitVec.extractLsb, + zeros, swapBytes32, Nat.reduceSub, Nat.reduceAdd] + bv_decide + +theorem rev8w_eq (v : BitVec 64) : + rev8w v = swapBytes32 (v.extractLsb 63 32) +++ swapBytes32 (v.extractLsb 31 0) := by + simp only [rev8w, swapBytes32] + bv_decide + +theorem lookupEntryCorrect_VirtualRev8W (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .VirtualRev8W) : + LookupEntryCorrect F .VirtualRev8W row := by + lookup_cases + simp only [virtualRev8WTableEntry, BitVec.ofFin_toFin, jolt_virtual_rev8w_value, rev8_32, + Sail.BitVec.extractLsb, BitVec.setWidth_setWidth_of_le _ (by norm_num : 64 ≤ 128)] + rw [BitVec.setWidth_eq, rev8w_eq] + + +-- FIXME: the six shift/rotate lemmas below are false while +-- `JoltProgram.expandedBytecode` is an arbitrary array: a `VirtualSRLI` row with +-- mask immediate `2` (or a `VirtualSRL` row whose mask register holds `2`) is +-- accepted, and its entry differs from its output. Rust's expander only emits +-- right-shift bitmasks here. Once `JoltProgram` records that construction, +-- prove these for bitmask-shaped masks. +/-- FALSE for arbitrary programs; left as `sorry`. +The table reads the right operand as a right-shift bitmask (ones from bit +`s` upward) and is correct only for such masks, while the honest output of +`VirtualSRL`/`VirtualSRLI` shifts by `ctz` of whatever mask the row carries. +`JoltProgram.expandedBytecode` is an arbitrary array (see the FIXME on +`JoltProgram.rowValid`), so nothing constrains the mask shape. +Counterexample: source `4`, mask `2`: entry `0`, output `4 >>> ctz 2 = 2`. +See `JoltConstraints/Tests/LookupShiftMask.lean`. -/ +theorem lookupEntryCorrect_VirtualSRL (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .VirtualSRL) : + LookupEntryCorrect F .VirtualSRL row := by + sorry + +/-- FALSE for arbitrary programs; left as `sorry`. +The table reads the right operand as a right-shift bitmask (ones from bit +`s` upward) and is correct only for such masks, while the honest output of +`VirtualSRA`/`VirtualSRAI` shifts by `ctz` of whatever mask the row carries. +`JoltProgram.expandedBytecode` is an arbitrary array (see the FIXME on +`JoltProgram.rowValid`), so nothing constrains the mask shape. +Counterexample: source `4`, mask `2`: entry `0`, output `4.sshiftRight 1 = 2`. +See `JoltConstraints/Tests/LookupShiftMask.lean`. -/ +theorem lookupEntryCorrect_VirtualSRA (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .VirtualSRA) : + LookupEntryCorrect F .VirtualSRA row := by + sorry + +/-- FALSE for arbitrary programs; left as `sorry`. +The table reads the right operand as a right-shift bitmask (ones from bit +`s` upward) and is correct only for such masks, while the honest output of +`VirtualSRLW`/`VirtualSRLIW` shifts by `ctz` of whatever mask the row carries. +`JoltProgram.expandedBytecode` is an arbitrary array (see the FIXME on +`JoltProgram.rowValid`), so nothing constrains the mask shape. +Counterexample: source `4`, mask `2`: entry `0`, output `2`. Also mask `0` for `VirtualSRLW`: entry `0`, output the sign-extended low word. +See `JoltConstraints/Tests/LookupShiftMask.lean`. -/ +theorem lookupEntryCorrect_VirtualSRLW (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .VirtualSRLW) : + LookupEntryCorrect F .VirtualSRLW row := by + sorry + +/-- FALSE for arbitrary programs; left as `sorry`. +The table reads the right operand as a right-shift bitmask (ones from bit +`s` upward) and is correct only for such masks, while the honest output of +`VirtualSRAW`/`VirtualSRAIW` shifts by `ctz` of whatever mask the row carries. +`JoltProgram.expandedBytecode` is an arbitrary array (see the FIXME on +`JoltProgram.rowValid`), so nothing constrains the mask shape. +Counterexample: source `4`, mask `2`: entry `0`, output `2`. +See `JoltConstraints/Tests/LookupShiftMask.lean`. -/ +theorem lookupEntryCorrect_VirtualSRAW (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .VirtualSRAW) : + LookupEntryCorrect F .VirtualSRAW row := by + sorry + +/-- FALSE for arbitrary programs; left as `sorry`. +The table reads the right operand as a right-shift bitmask (ones from bit +`s` upward) and is correct only for such masks, while the honest output of +`VirtualROTRI` shifts by `ctz` of whatever mask the row carries. +`JoltProgram.expandedBytecode` is an arbitrary array (see the FIXME on +`JoltProgram.rowValid`), so nothing constrains the mask shape. +Counterexample: source `4`, mask `2`: entry `4`, output `rotater 4 1 = 2`. +See `JoltConstraints/Tests/LookupShiftMask.lean`. -/ +theorem lookupEntryCorrect_VirtualROTR (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .VirtualROTR) : + LookupEntryCorrect F .VirtualROTR row := by + sorry + +/-- FALSE for arbitrary programs; left as `sorry`. +The table reads the right operand as a right-shift bitmask (ones from bit +`s` upward) and is correct only for such masks, while the honest output of +`VirtualROTRIW` shifts by `ctz` of whatever mask the row carries. +`JoltProgram.expandedBytecode` is an arbitrary array (see the FIXME on +`JoltProgram.rowValid`), so nothing constrains the mask shape. +Counterexample: source `4`, mask `2`: entry `4`, output `2`. +See `JoltConstraints/Tests/LookupShiftMask.lean`. -/ +theorem lookupEntryCorrect_VirtualROTRW (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .VirtualROTRW) : + LookupEntryCorrect F .VirtualROTRW row := by + sorry + +theorem lookupEntryCorrect_Pext (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .Pext) : + LookupEntryCorrect F .Pext row := by + lookup_cases + all_goals simp only [pextTableEntry, uninterleave_interleave, pext_eq_jolt_virtual_pext_value] + +theorem lookupEntryCorrect_PextSigned (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .PextSigned) : + LookupEntryCorrect F .PextSigned row := by + lookup_cases + all_goals simp only [pextSignedTableEntry, uninterleave_interleave, + pextSigned_eq_jolt_virtual_pext_signed_value] + +theorem lookupEntryCorrect_UnsignedLessThanEqual (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .UnsignedLessThanEqual) : + LookupEntryCorrect F .UnsignedLessThanEqual row := by + have hexec := row_executes_exists row + lookup_cases + rw [hi] at hexec + obtain ⟨_, hexec⟩ := hexec + simp only [JoltISA.Instr.withRuntimeAdvice, JoltISA.execInstr] at hexec + have h1 := lookup_read_bind _ _ _ _ _ hexec + have h2 := lookup_read_bind _ _ _ _ _ h1 + split_ifs at h2 with hc + · rw [unsignedLessThanEqual_entry, if_pos (BitVec.le_def.mpr hc)] + simp + · exact absurd h2 (by simp [throw, throwThe, MonadExceptOf.throw, EStateM.throw]) + +theorem lookupEntryCorrect_HalfwordAlignment (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .HalfwordAlignment) : + LookupEntryCorrect F .HalfwordAlignment row := by + have hexec := row_executes_exists row + lookup_cases + rw [hi] at hexec + obtain ⟨_, hexec⟩ := hexec + simp only [JoltISA.Instr.withRuntimeAdvice, JoltISA.execInstr] at hexec + have h1 := lookup_read_bind _ _ _ _ _ hexec + split_ifs at h1 with hc + · simp only [halfwordAlignmentTableEntry, BitVec.ofFin_toFin] + rw [if_pos (halfword_aligned _ hc)] + simp + · exact absurd h1 (by simp [pure, EStateM.pure]) + +theorem lookupEntryCorrect_WordAlignment (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .WordAlignment) : + LookupEntryCorrect F .WordAlignment row := by + have hexec := row_executes_exists row + lookup_cases + rw [hi] at hexec + obtain ⟨_, hexec⟩ := hexec + simp only [JoltISA.Instr.withRuntimeAdvice, JoltISA.execInstr] at hexec + have h1 := lookup_read_bind _ _ _ _ _ hexec + split_ifs at h1 with hc + · simp only [wordAlignmentTableEntry, BitVec.ofFin_toFin] + rw [if_pos (word_aligned _ hc)] + simp + · exact absurd h1 (by simp [pure, EStateM.pure]) + +theorem lookupEntryCorrect_MulUNoOverflow (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .MulUNoOverflow) : + LookupEntryCorrect F .MulUNoOverflow row := by + have hexec := row_executes_exists row + lookup_cases + rw [hi] at hexec + obtain ⟨_, hexec⟩ := hexec + simp only [JoltISA.Instr.withRuntimeAdvice, JoltISA.execInstr] at hexec + have h1 := lookup_read_bind _ _ _ _ _ hexec + have h2 := lookup_read_bind _ _ _ _ _ h1 + split_ifs at h2 with hc + · simp only [mulUNoOverflowTableEntry, BitVec.ofFin_toFin] + rw [if_pos (upper_zero_of_lt _ hc)] + simp + · exact absurd h2 (by simp [throw, throwThe, MonadExceptOf.throw, EStateM.throw]) + +theorem lookupEntryCorrect_ValidDiv0 (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .ValidDiv0) : + LookupEntryCorrect F .ValidDiv0 row := by + have hexec := row_executes_exists row + lookup_cases + rw [hi] at hexec + obtain ⟨_, hexec⟩ := hexec + simp only [JoltISA.Instr.withRuntimeAdvice, JoltISA.execInstr] at hexec + have h1 := lookup_read_bind _ _ _ _ _ hexec + have h2 := lookup_read_bind _ _ _ _ _ h1 + split_ifs at h2 with hc + · exact absurd h2 (by simp [throw, throwThe, MonadExceptOf.throw, EStateM.throw]) + · rw [validDiv0_entry, if_neg (by simpa using hc)] + simp + +theorem lookupEntryCorrect_ValidUnsignedRemainder (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = some .ValidUnsignedRemainder) : + LookupEntryCorrect F .ValidUnsignedRemainder row := by + have hexec := row_executes_exists row + lookup_cases + rw [hi] at hexec + obtain ⟨_, hexec⟩ := hexec + simp only [JoltISA.Instr.withRuntimeAdvice, JoltISA.execInstr] at hexec + have h1 := lookup_read_bind _ _ _ _ _ hexec + have h2 := lookup_read_bind _ _ _ _ _ h1 + split_ifs at h2 with hc + · rw [validUnsignedRemainder_entry, if_pos (by simpa [BitVec.lt_def] using hc)] + simp + · exact absurd h2 (by simp [throw, throwThe, MonadExceptOf.throw, EStateM.throw]) + +/-- Rows whose instruction has no lookup table (`FENCE`, `LD`, `SD`, +`VirtualHostIO`) have lookup output zero. -/ +theorem rowLookupOutput_eq_zero_of_lookupTable_none (row : JoltTraceRow program) + (h : JoltMetadata.lookupTable (rowInstruction row) = none) : + rowLookupOutput row = 0 := by + unfold rowLookupOutput + cases hi : rowInstruction row <;> + simp only [hi, JoltMetadata.lookupTable, reduceCtorEq] at h <;> + simp only [hi] + +/-- Dispatcher: the honest entry of the row's lookup table equals the honest +lookup output. Depends on every `lookupEntryCorrect_*` lemma, including the +ones left as `sorry` (`VirtualSRL`, `VirtualSRA`, `VirtualSRLW`, `VirtualSRAW`, +`VirtualROTR`, `VirtualROTRW`). -/ +theorem lookupEntryCorrect_of_lookupTable (row : JoltTraceRow program) (k : LookupTableKind) + (h : JoltMetadata.lookupTable (rowInstruction row) = some k) : + LookupEntryCorrect F k row := + match k with + | .RangeCheck => lookupEntryCorrect_RangeCheck row h + | .RangeCheckAligned => lookupEntryCorrect_RangeCheckAligned row h + | .And => lookupEntryCorrect_And row h + | .Andn => lookupEntryCorrect_Andn row h + | .Or => lookupEntryCorrect_Or row h + | .Xor => lookupEntryCorrect_Xor row h + | .Equal => lookupEntryCorrect_Equal row h + | .SignedGreaterThanEqual => lookupEntryCorrect_SignedGreaterThanEqual row h + | .UnsignedGreaterThanEqual => lookupEntryCorrect_UnsignedGreaterThanEqual row h + | .NotEqual => lookupEntryCorrect_NotEqual row h + | .SignedLessThan => lookupEntryCorrect_SignedLessThan row h + | .UnsignedLessThan => lookupEntryCorrect_UnsignedLessThan row h + | .SignMask => lookupEntryCorrect_SignMask row h + | .UpperWord => lookupEntryCorrect_UpperWord row h + | .UnsignedLessThanEqual => lookupEntryCorrect_UnsignedLessThanEqual row h + | .ValidUnsignedRemainder => lookupEntryCorrect_ValidUnsignedRemainder row h + | .ValidDiv0 => lookupEntryCorrect_ValidDiv0 row h + | .HalfwordAlignment => lookupEntryCorrect_HalfwordAlignment row h + | .WordAlignment => lookupEntryCorrect_WordAlignment row h + | .LowerHalfWord => lookupEntryCorrect_LowerHalfWord row h + | .SignExtendWord => lookupEntryCorrect_SignExtendWord row h + | .Pow2 => lookupEntryCorrect_Pow2 row h + | .Pow2W => lookupEntryCorrect_Pow2W row h + | .ShiftRightBitmask => lookupEntryCorrect_ShiftRightBitmask row h + | .VirtualRev8W => lookupEntryCorrect_VirtualRev8W row h + | .VirtualSRL => lookupEntryCorrect_VirtualSRL row h + | .VirtualSRA => lookupEntryCorrect_VirtualSRA row h + | .VirtualROTR => lookupEntryCorrect_VirtualROTR row h + | .VirtualROTRW => lookupEntryCorrect_VirtualROTRW row h + | .VirtualNegateIf => lookupEntryCorrect_VirtualNegateIf row h + | .MulUNoOverflow => lookupEntryCorrect_MulUNoOverflow row h + | .VirtualXORROT32 => lookupEntryCorrect_VirtualXORROT32 row h + | .VirtualXORROT24 => lookupEntryCorrect_VirtualXORROT24 row h + | .VirtualXORROT16 => lookupEntryCorrect_VirtualXORROT16 row h + | .VirtualXORROT63 => lookupEntryCorrect_VirtualXORROT63 row h + | .VirtualXORROTW16 => lookupEntryCorrect_VirtualXORROTW16 row h + | .VirtualXORROTW12 => lookupEntryCorrect_VirtualXORROTW12 row h + | .VirtualXORROTW8 => lookupEntryCorrect_VirtualXORROTW8 row h + | .VirtualXORROTW7 => lookupEntryCorrect_VirtualXORROTW7 row h + | .WindowMaskW => lookupEntryCorrect_WindowMaskW row h + | .PextSigned => lookupEntryCorrect_PextSigned row h + | .VirtualXORROTW22 => lookupEntryCorrect_VirtualXORROTW22 row h + | .VirtualXORROTW19 => lookupEntryCorrect_VirtualXORROTW19 row h + | .VirtualXORROTW6 => lookupEntryCorrect_VirtualXORROTW6 row h + | .ShiftRightBitmaskW => lookupEntryCorrect_ShiftRightBitmaskW row h + | .VirtualSRLW => lookupEntryCorrect_VirtualSRLW row h + | .VirtualSRAW => lookupEntryCorrect_VirtualSRAW row h + | .Pext => lookupEntryCorrect_Pext row h + | .WindowMaskB => lookupEntryCorrect_WindowMaskB row h + | .WindowMaskH => lookupEntryCorrect_WindowMaskH row h + | .AlignAddr => lookupEntryCorrect_AlignAddr row h + | .ShiftDataB => lookupEntryCorrect_ShiftDataB row h + | .ShiftDataH => lookupEntryCorrect_ShiftDataH row h + | .ShiftDataW => lookupEntryCorrect_ShiftDataW row h + | .VirtualXORROTL1 => lookupEntryCorrect_VirtualXORROTL1 row h + +end tables2 + +end JoltConstraints diff --git a/JoltConstraints/Constraints/LookupOperandData.lean b/JoltConstraints/Constraints/LookupOperandData.lean index b9e7f42..7f000a8 100644 --- a/JoltConstraints/Constraints/LookupOperandData.lean +++ b/JoltConstraints/Constraints/LookupOperandData.lean @@ -1,4 +1,4 @@ -import JoltConstraints.Constraints.LookupOutputEqInstructionReadRaf +import JoltConstraints.Constraints.InstructionLookupRa set_option autoImplicit false diff --git a/JoltConstraints/Constraints/LookupOutputEqInstructionReadRaf.lean b/JoltConstraints/Constraints/LookupOutputEqInstructionReadRaf.lean index 6c57fff..3fa7cb7 100644 --- a/JoltConstraints/Constraints/LookupOutputEqInstructionReadRaf.lean +++ b/JoltConstraints/Constraints/LookupOutputEqInstructionReadRaf.lean @@ -1,8 +1,6 @@ -import Mathlib.Algebra.BigOperators.Group.Finset.Basic -import Mathlib.Algebra.Field.Defs -import JoltConstraints.witness -import JoltConstraints.lookup_table -import JoltConstraints.honest_witness +import JoltConstraints.Constraints.InstructionLookupRa +import JoltConstraints.Constraints.InstructionReadSelection +import JoltConstraints.Constraints.LookupEntryProofHelpers set_option autoImplicit false @@ -10,22 +8,6 @@ namespace JoltConstraints open scoped BigOperators -/-- The `chunk`th virtual chunk of a 128-bit lookup address, most significant first. -/ -def instructionLookupChunk (params : WitnessParams) (address : Fin (2 ^ 128)) - (chunk : Fin params.virtualInstructionChunks) : Fin (2 ^ params.virtualChunkBits) := - ⟨(address.val / - 2 ^ ((params.virtualInstructionChunks - 1 - chunk.val) * params.virtualChunkBits)) % - 2 ^ params.virtualChunkBits, - Nat.mod_lt _ (pow_pos (by decide : 0 < (2 : Nat)) _)⟩ - -/-- `LookupRa(x,t)` in `constraints.md`: multiply the virtual instruction-address -entries selected by the chunks of `x`. -/ -noncomputable def instructionLookupRa {F : Type} [Field F] {params : WitnessParams} - (witness : WitnessType F params) (address : Fin (2 ^ 128)) - (t : Fin params.traceLength) : F := - ∏ chunk : Fin params.virtualInstructionChunks, - witness.InstructionRa chunk (instructionLookupChunk params address chunk) t - /- For every padded trace index t ∈ T: @@ -47,8 +29,9 @@ def lookupOutputEqInstructionReadRaf {F : Type} [Field F] {params : WitnessParam ∑ address : Fin (2 ^ 128), instructionLookupRa witness address t * ∑ table : LookupTableKind, witness.LookupTableFlag table t * lookupTableEntry table address -/-- Completeness of the lookup-output constraint. The execution-row case is -pending the correspondence of the fixed table definitions with ISA outputs. -/ +/-- Completeness of the lookup-output constraint. The execution-row case +reduces to `lookupEntryCorrect_of_lookupTable` (one lemma per lookup table in +`LookupEntryProofHelpers.lean`); some of those remain `sorry`, see there. -/ theorem honestWitness_lookupOutputEqInstructionReadRaf {F : Type} [Field F] (params : WitnessParams) {program : JoltProgram} (trace : JoltTrace program) @@ -59,11 +42,35 @@ theorem honestWitness_lookupOutputEqInstructionReadRaf (JoltProgram.honestWitness (F := F) params trace ramFits traceFits bytecodeDomain) := by intro t by_cases inBounds : t.val < trace.rows.size - · -- FIXME (translation): all lookup-table entries are now defined, and - -- honest address selection is proved in `InstructionReadSelection.lean`. - -- Relate each selected entry to `HonestWitness.rowLookupOutput` before - -- closing this target. - sorry + · -- Execution row: collapse the address sum to the honest index, the table + -- sum to the flagged table, and apply the per-table entry lemmas. + set row := getElem trace.rows t.val inBounds with hrow + rw [instructionRead_honest params trace ramFits traceFits bytecodeDomain + (fun a => ∑ table : LookupTableKind, + (JoltProgram.honestWitness (F := F) params trace ramFits traceFits bytecodeDomain).LookupTableFlag + table t * lookupTableEntry table a) t] + have hidx : (⟨(HonestWitness.lookupIndex trace t.val).toNat, + (HonestWitness.lookupIndex trace t.val).isLt⟩ : Fin (2 ^ 128)) = rowLookupAddress row := by + simp only [HonestWitness.lookupIndex, dif_pos inBounds, rowLookupAddress, rowLookupIndex, hrow] + rfl + rw [hidx] + have hflag : ∀ table : LookupTableKind, + (JoltProgram.honestWitness (F := F) params trace ramFits traceFits bytecodeDomain).LookupTableFlag + table t = if JoltMetadata.lookupTable (rowInstruction row) = some table then 1 else 0 := by + intro table + simp only [JoltProgram.honestWitness, HonestWitness.LookupTableFlag, dif_pos inBounds, + JoltMetadata.lookupTableFlag, beq_iff_eq, rowInstruction, hrow] + have hout : (JoltProgram.honestWitness (F := F) params trace ramFits traceFits bytecodeDomain).LookupOutput t + = ((HonestWitness.rowLookupOutput row).toNat : F) := by + simp only [JoltProgram.honestWitness, HonestWitness.LookupOutput, dif_pos inBounds] + rfl + simp only [hflag, hout, ite_mul, one_mul, zero_mul] + cases hk : JoltMetadata.lookupTable (rowInstruction row) with + | none => + simp [rowLookupOutput_eq_zero_of_lookupTable_none row hk] + | some k => + simp only [Option.some.injEq, Finset.sum_ite_eq, Finset.mem_univ, if_true] + exact (lookupEntryCorrect_of_lookupTable row k hk).symm · -- Padding has zero output and every table flag is zero. simp [JoltProgram.honestWitness, HonestWitness.LookupOutput, HonestWitness.LookupTableFlag, inBounds] diff --git a/JoltConstraints/Constraints/LookupPextProofHelpers.lean b/JoltConstraints/Constraints/LookupPextProofHelpers.lean new file mode 100644 index 0000000..8f9b414 --- /dev/null +++ b/JoltConstraints/Constraints/LookupPextProofHelpers.lean @@ -0,0 +1,460 @@ +import Mathlib.Tactic.Ring +import JoltConstraints.lookup_table +import JoltBytecode.JoltISA.Values + +/-! +# The PEXT tables against the honest `VirtualPext` values + +`pext_eq_jolt_virtual_pext_value` shows that the table's `pext` (a contiguous +fast path plus a 64-pass clear-lowest-set-bit loop) equals +`jolt_virtual_pext_value`, the fuel recursion `jolt_pext_nat 64` used by the +honest witness. `pextSigned_eq_jolt_virtual_pext_signed_value` adds the sign +extension: `windowSignBit` reads the source bit at the mask's highest set bit, +which `jolt_pext_nat_testBit_top` shows is bit `popcount - 1` of the extracted +value, and adding `2^64 - 2^popcount` to a value below `2^popcount` is the same +as or-ing in the high ones. + +The loop is handled by rewriting its `forIn` as a `List.foldl` of `pextStep` +and carrying `PextInv`: after `n` passes the extracted prefix `out` and the +remaining mask `bits` satisfy +`jolt_pext_nat 64 x y = out + 2^k * jolt_pext_nat 64 x bits`, and the low `n` +bits of `bits` are clear, so `bits = 0` after 64 passes. +-/ + +set_option autoImplicit false + +namespace JoltConstraints + +/-- Peel the lowest set bit `p` of the mask `2^p * (2q+1)`. -/ +theorem jolt_pext_nat_peel (p : Nat) : ∀ (fuel x q : Nat), p < fuel → + jolt_pext_nat fuel x (2 ^ p * (2 * q + 1)) = + x / 2 ^ p % 2 + 2 * jolt_pext_nat fuel x (2 ^ (p + 1) * q) := by + induction p with + | zero => + intro fuel x q hp + obtain ⟨f, rfl⟩ : ∃ f, fuel = f + 1 := ⟨fuel - 1, by omega⟩ + rw [jolt_pext_nat, jolt_pext_nat] + simp only [zero_add, pow_zero, pow_one, one_mul, Nat.div_one] + rw [if_pos (by omega), if_neg (by omega)] + rw [(show (2 * q + 1) / 2 = q by omega), (show 2 * q / 2 = q by omega)] + | succ p ih => + intro fuel x q hp + obtain ⟨f, rfl⟩ : ∃ f, fuel = f + 1 := ⟨fuel - 1, by omega⟩ + rw [jolt_pext_nat, jolt_pext_nat] + have e1 : 2 ^ (p + 1) * (2 * q + 1) = 2 * (2 ^ p * (2 * q + 1)) := by ring + have e2 : 2 ^ (p + 1 + 1) * q = 2 * (2 ^ (p + 1) * q) := by ring + rw [e1, e2, if_neg (by omega), if_neg (by omega), + Nat.mul_div_cancel_left _ (by decide), Nat.mul_div_cancel_left _ (by decide), + ih f (x / 2) q (by omega), Nat.div_div_eq_div_mul, pow_succ, mul_comm (2 ^ p) 2] + +theorem jolt_pext_nat_zero (fuel x : Nat) : jolt_pext_nat fuel x 0 = 0 := by + induction fuel generalizing x with + | zero => rfl + | succ fuel ih => rw [jolt_pext_nat, if_neg (by decide), Nat.zero_div, ih] + +/-- A low-ones mask (`n &&& (n+1) = 0`) extracts by masking. -/ +theorem jolt_pext_nat_of_low_ones : ∀ (fuel x n : Nat), n < 2 ^ fuel → n &&& (n + 1) = 0 → + jolt_pext_nat fuel x n = x &&& n := by + intro fuel + induction fuel with + | zero => intro x n hn _; simp at hn; subst hn; simp [jolt_pext_nat] + | succ f ih => + intro x n hn hlow + rw [jolt_pext_nat] + split_ifs with hodd + · have hm : (n / 2) &&& (n / 2 + 1) = 0 := by + have := congrArg (· / 2) hlow + simp only [Nat.and_div_two, Nat.zero_div] at this + rwa [show (n + 1) / 2 = n / 2 + 1 by omega] at this + rw [ih (x / 2) (n / 2) (by rw [pow_succ] at hn; omega) hm] + apply Nat.eq_of_testBit_eq + intro i + cases i with + | zero => simp [Nat.testBit_zero, hodd] + | succ i => + have hd : (x % 2 + 2 * (x / 2 &&& n / 2)) / 2 = x / 2 &&& n / 2 := by omega + simp only [Nat.testBit_succ, hd, Nat.and_div_two] + · have hn0 : n = 0 := by + by_contra hne + have h2 : (n &&& (n + 1)) % 2 = 0 := by simp [hlow] + have hb : n &&& (n + 1) = n := by + apply Nat.eq_of_testBit_eq; intro i + cases i with + | zero => simp [Nat.testBit_zero]; omega + | succ i => + have hd : (n + 1) / 2 = n / 2 := by omega + simp only [Nat.testBit_succ, Nat.and_div_two, hd, Nat.and_self] + omega + subst hn0 + simp [jolt_pext_nat_zero] + + +/-- One pass of the table's general `pext` loop on the state (bits, k, out). -/ +private def pextStep (x : BitVec 64) (r : MProd (BitVec 64) (MProd Nat (BitVec 64))) : + MProd (BitVec 64) (MProd Nat (BitVec 64)) := + if r.fst = 0 then r + else ⟨r.fst &&& r.fst - 1, r.snd.fst + 1, + r.snd.snd ||| ((x >>> r.fst.ctz.toNat) &&& 1) <<< r.snd.fst⟩ + +private theorem forIn_eq_foldl_of {S : Type} (g : S → S) (f : Nat → S → Id (ForInStep S)) + (hf : ∀ a r, f a r = pure (.yield (g r))) (l : List Nat) (s : S) : + forIn l s f = pure (l.foldl (fun r _ => g r) s) := by + have : f = fun _ r => pure (.yield (g r)) := funext fun a => funext fun r => hf a r + subst this + simp + +private theorem pext_general (x y : BitVec 64) (hy : y ≠ 0) + (hc : ¬ (y >>> y.ctz.toNat) &&& ((y >>> y.ctz.toNat) + 1) = 0) : + pext x y = ((List.range 64).foldl (fun r _ => pextStep x r) ⟨y, 0, 0⟩).snd.snd := by + unfold pext + simp only [Id.run, hy, if_false, hc, pure_bind] + rw [forIn_eq_foldl_of (pextStep x)] + · simp only [pure_bind] + generalize List.foldl _ _ _ = r + rfl + · rintro _ ⟨b, k, o⟩ + by_cases h0 : b = 0 + · subst h0; simp [pextStep] + · simp only [pextStep]; rw [if_neg h0, if_pos h0] + +private theorem two_pow_mul_odd_and_pred (p q : Nat) : + (2 ^ p * (2 * q + 1)) &&& (2 ^ p * (2 * q + 1) - 1) = 2 ^ (p + 1) * q := by + have hp : 0 < 2 ^ p := Nat.two_pow_pos p + have ha : 2 ^ p * (2 * q + 1) = 2 ^ (p + 1) * q + 2 ^ p := by rw [pow_succ]; ring + have hb : 2 ^ p * (2 * q + 1) - 1 = 2 ^ (p + 1) * q + (2 ^ p - 1) := by rw [ha]; omega + have h1 : 2 ^ p < 2 ^ (p + 1) := Nat.pow_lt_pow_right (by decide) (by omega) + rw [hb, ha] + apply Nat.eq_of_testBit_eq + intro j + rw [Nat.testBit_and, Nat.testBit_two_pow_mul_add _ h1, Nat.testBit_two_pow_mul_add _ (by omega), + Nat.testBit_two_pow_mul] + by_cases hj : j < p + 1 + · simp only [hj, if_true, Nat.testBit_two_pow, Nat.testBit_two_pow_sub_one] + have h3 : ¬ (p + 1 ≤ j) := by omega + by_cases hjp : p = j + · subst hjp; simp + · simp [hjp, ge_iff_le, h3] + · simp only [hj, if_false, Bool.and_self] + simp; omega + +private theorem eq_two_pow_mul_odd (m p : Nat) (hlow : m % 2 ^ p = 0) (hbit : m.testBit p = true) : + m = 2 ^ p * (2 * (m / 2 ^ p / 2) + 1) := by + have h1 : (m / 2 ^ p) % 2 = 1 := by + have := Nat.testBit_div_two_pow (n := p) m 0 + rw [zero_add, hbit, Nat.testBit_zero] at this + simpa using this + have h2 : 2 * (m / 2 ^ p / 2) + 1 = m / 2 ^ p := by omega + rw [h2, Nat.mul_div_cancel' (Nat.dvd_of_mod_eq_zero hlow)] + + +/-- Loop invariant after `n` passes from `⟨y, 0, 0⟩`. -/ +private def PextInv (x y : BitVec 64) (n : Nat) + (r : MProd (BitVec 64) (MProd Nat (BitVec 64))) : Prop := + jolt_pext_nat 64 x.toNat y.toNat = + r.snd.snd.toNat + 2 ^ r.snd.fst * jolt_pext_nat 64 x.toNat r.fst.toNat ∧ + r.snd.snd.toNat < 2 ^ r.snd.fst ∧ r.snd.fst ≤ n ∧ 2 ^ n ∣ r.fst.toNat + +private theorem toNat_sub_one_of_ne_zero {b : BitVec 64} (hb : b ≠ 0) : + (b - 1).toNat = b.toNat - 1 := by + have : b.toNat ≠ 0 := fun h => hb (BitVec.eq_of_toNat_eq (by simpa using h)) + rw [BitVec.toNat_sub]; simp; omega + +private theorem pextInv_step (x y : BitVec 64) (n : Nat) + (r : MProd (BitVec 64) (MProd Nat (BitVec 64))) + (h : PextInv x y n r) : PextInv x y (n + 1) (pextStep x r) := by + obtain ⟨b, k, o⟩ := r + obtain ⟨hJ, ho, hk, hd⟩ := h + simp only at hJ ho hk hd + by_cases hb : b = 0 + · subst hb + simp only [pextStep, if_true] + exact ⟨hJ, ho, show k ≤ n + 1 by omega, by simp⟩ + · simp only [pextStep, if_neg hb] + generalize hp : b.ctz.toNat = p + have hlow : b.toNat % 2 ^ p = 0 := by + apply Nat.eq_of_testBit_eq; intro i + rw [Nat.testBit_mod_two_pow, Nat.zero_testBit] + by_cases hi : i < p + · rw [BitVec.testBit_toNat, BitVec.getLsbD_false_of_lt_ctz (by omega)]; simp + · simp [hi] + have hbit : b.toNat.testBit p = true := by + rw [BitVec.testBit_toNat, ← hp]; exact BitVec.getLsbD_true_ctz_of_ne_zero hb + have hp64 : p < 64 := by + by_contra hc + have := Nat.testBit_lt_two_pow + (lt_of_lt_of_le b.isLt (Nat.pow_le_pow_right (by decide) (by omega : 64 ≤ p))) + rw [hbit] at this; exact absurd this (by decide) + have hnp : n ≤ p := by + by_contra hc + obtain ⟨c, hc'⟩ := hd + have : b.toNat.testBit p = false := by + rw [hc', Nat.testBit_two_pow_mul]; simp; omega + rw [hbit] at this; exact absurd this (by decide) + obtain ⟨q, hq⟩ : ∃ q, b.toNat = 2 ^ p * (2 * q + 1) := ⟨_, eq_two_pow_mul_odd _ _ hlow hbit⟩ + have hbits : (b &&& (b - 1)).toNat = 2 ^ (p + 1) * q := by + rw [BitVec.toNat_and, toNat_sub_one_of_ne_zero hb, hq, two_pow_mul_odd_and_pred] + have hc : ((x >>> p) &&& 1).toNat = x.toNat / 2 ^ p % 2 := by + simp [BitVec.toNat_and, BitVec.toNat_ushiftRight, Nat.shiftRight_eq_div_pow] + have hc1 : x.toNat / 2 ^ p % 2 ≤ 1 := by omega + have hk64 : 2 ^ k ≤ 2 ^ 63 := Nat.pow_le_pow_right (by decide) (by omega) + have hsh : (((x >>> p) &&& 1) <<< k).toNat = 2 ^ k * (x.toNat / 2 ^ p % 2) := by + rw [BitVec.toNat_shiftLeft, hc, Nat.shiftLeft_eq, Nat.mul_comm] + apply Nat.mod_eq_of_lt + have := Nat.mul_le_mul hk64 hc1 + omega + have hor : (o ||| ((x >>> p) &&& 1) <<< k).toNat = o.toNat + 2 ^ k * (x.toNat / 2 ^ p % 2) := by + rw [BitVec.toNat_or, hsh, Nat.or_comm, ← Nat.two_pow_add_eq_or_of_lt ho]; ring + refine ⟨?_, ?_, by simp only; omega, ?_⟩ + · simp only [hbits, hor] + rw [hJ, hq, jolt_pext_nat_peel p 64 _ q hp64, pow_succ]; ring + · simp only [hor] + have := Nat.mul_le_mul_left (2 ^ k) hc1 + rw [pow_succ]; omega + · simp only [hbits] + exact Dvd.dvd.mul_right (Nat.pow_dvd_pow 2 (by omega)) q + +private theorem pextInv_foldl (x y : BitVec 64) : + ∀ (l : List Nat) (n : Nat) (r : MProd (BitVec 64) (MProd Nat (BitVec 64))), + PextInv x y n r → PextInv x y (n + l.length) (l.foldl (fun r _ => pextStep x r) r) + | [], n, r, h => by simpa using h + | _ :: l, n, r, h => by + simp only [List.foldl_cons, List.length_cons] + have := pextInv_foldl x y l (n + 1) _ (pextInv_step x y n r h) + rwa [show n + 1 + l.length = n + (l.length + 1) by omega] at this + +private theorem pext_eq_of_general (x y : BitVec 64) (hy : y ≠ 0) + (hc : ¬ (y >>> y.ctz.toNat) &&& ((y >>> y.ctz.toNat) + 1) = 0) : + pext x y = jolt_virtual_pext_value x y := by + rw [pext_general x y hy hc] + have h := pextInv_foldl x y (List.range 64) 0 ⟨y, 0, 0⟩ ⟨by simp, by simp, le_refl _, by simp⟩ + generalize (List.range 64).foldl _ _ = r at h ⊢ + obtain ⟨b, k, o⟩ := r + obtain ⟨hJ, -, -, hd⟩ := h + simp only [List.length_range, zero_add] at hd hJ + have hb0 : b.toNat = 0 := Nat.eq_zero_of_dvd_of_lt hd b.isLt + rw [hb0, jolt_pext_nat_zero, mul_zero, add_zero] at hJ + apply BitVec.eq_of_toNat_eq + simp only [jolt_virtual_pext_value, BitVec.toNat_ofNat, hJ] + exact (Nat.mod_eq_of_lt o.isLt).symm + + +theorem jolt_pext_nat_two_pow_mul (f X : Nat) : ∀ (t m : Nat), + jolt_pext_nat (f + t) X (2 ^ t * m) = jolt_pext_nat f (X / 2 ^ t) m := by + intro t + induction t generalizing X with + | zero => intro m; simp + | succ t ih => + intro m + rw [show f + (t + 1) = (f + t) + 1 by omega, jolt_pext_nat, + show 2 ^ (t + 1) * m = 2 * (2 ^ t * m) by rw [pow_succ]; ring, + if_neg (by omega), Nat.mul_div_cancel_left _ (by decide), ih, + Nat.div_div_eq_div_mul, pow_succ, mul_comm (2 ^ t) 2] + +private theorem toNat_mod_two_pow_ctz (b : BitVec 64) : b.toNat % 2 ^ b.ctz.toNat = 0 := by + apply Nat.eq_of_testBit_eq; intro i + rw [Nat.testBit_mod_two_pow, Nat.zero_testBit] + by_cases hi : i < b.ctz.toNat + · rw [BitVec.testBit_toNat, BitVec.getLsbD_false_of_lt_ctz hi]; simp + · simp [hi] + +private theorem ctz_toNat_lt (b : BitVec 64) (hb : b ≠ 0) : b.ctz.toNat < 64 := by + by_contra hc + have := Nat.testBit_lt_two_pow + (lt_of_lt_of_le b.isLt (Nat.pow_le_pow_right (by decide) (by omega : 64 ≤ b.ctz.toNat))) + rw [BitVec.testBit_toNat, BitVec.getLsbD_true_ctz_of_ne_zero hb] at this + exact absurd this (by decide) + +private theorem pext_eq_of_contiguous (x y : BitVec 64) (hy : y ≠ 0) + (hc : (y >>> y.ctz.toNat) &&& ((y >>> y.ctz.toNat) + 1) = 0) : + pext x y = jolt_virtual_pext_value x y := by + have hpx : pext x y = (x >>> y.ctz.toNat) &&& (y >>> y.ctz.toNat) := by + unfold pext + simp only [Id.run, hy, if_false, hc, if_true, pure_bind] + rfl + rw [hpx] + generalize ht : y.ctz.toNat = t at hc + have ht64 : t < 64 := ht ▸ ctz_toNat_lt y hy + have hlow : y.toNat % 2 ^ t = 0 := ht ▸ toNat_mod_two_pow_ctz y + have hy' : y.toNat = 2 ^ t * (y.toNat / 2 ^ t) := + (Nat.mul_div_cancel' (Nat.dvd_of_mod_eq_zero hlow)).symm + have hN : y.toNat / 2 ^ t < 2 ^ (64 - t) := by + rw [Nat.div_lt_iff_lt_mul (Nat.two_pow_pos t), ← pow_add, show 64 - t + t = 64 by omega] + exact y.isLt + have hNlow : (y.toNat / 2 ^ t) &&& (y.toNat / 2 ^ t + 1) = 0 := by + have h := congrArg BitVec.toNat hc + simp only [BitVec.toNat_and, BitVec.toNat_add, BitVec.toNat_ushiftRight, + Nat.shiftRight_eq_div_pow] at h + rw [show (1 : BitVec 64).toNat = 1 from rfl, show (0 : BitVec 64).toNat = 0 from rfl] at h + by_cases hw : y.toNat / 2 ^ t + 1 < 2 ^ 64 + · rwa [Nat.mod_eq_of_lt hw] at h + · have he : y.toNat / 2 ^ t = 2 ^ 64 - 1 := by + have : y.toNat / 2 ^ t ≤ y.toNat := Nat.div_le_self _ _ + have := y.isLt; omega + rw [he, show 2 ^ 64 - 1 + 1 = 2 ^ 64 by rfl] + apply Nat.eq_of_testBit_eq; intro i + simp only [Nat.testBit_and, Nat.testBit_two_pow_sub_one, Nat.testBit_two_pow, + Nat.zero_testBit] + by_cases hi : i < 64 <;> simp [hi]; omega + apply BitVec.eq_of_toNat_eq + simp only [BitVec.toNat_and, BitVec.toNat_ushiftRight, Nat.shiftRight_eq_div_pow, + jolt_virtual_pext_value, BitVec.toNat_ofNat] + have hs : jolt_pext_nat 64 x.toNat (2 ^ t * (y.toNat / 2 ^ t)) = + jolt_pext_nat (64 - t) (x.toNat / 2 ^ t) (y.toNat / 2 ^ t) := by + have := jolt_pext_nat_two_pow_mul (64 - t) x.toNat t (y.toNat / 2 ^ t) + rwa [show 64 - t + t = 64 by omega] at this + rw [hy', hs, Nat.mul_div_cancel_left _ (Nat.two_pow_pos t), + jolt_pext_nat_of_low_ones _ _ _ hN hNlow] + have : y.toNat / 2 ^ t < 2 ^ 64 := lt_of_le_of_lt (Nat.div_le_self _ _) y.isLt + exact (Nat.mod_eq_of_lt (lt_of_le_of_lt Nat.and_le_right this)).symm + +/-- The PEXT table's `pext` is the honest `VirtualPext` value. -/ +theorem pext_eq_jolt_virtual_pext_value (x y : BitVec 64) : + pext x y = jolt_virtual_pext_value x y := by + by_cases hy : y = 0 + · subst hy + unfold pext + simp [Id.run, jolt_virtual_pext_value, jolt_pext_nat_zero] + rfl + by_cases hc : (y >>> y.ctz.toNat) &&& ((y >>> y.ctz.toNat) + 1) = 0 + · exact pext_eq_of_contiguous x y hy hc + · exact pext_eq_of_general x y hy hc + + +/-! ## PextSigned -/ + +theorem jolt_popcount_nat_eq_sum : ∀ (f m : Nat), + jolt_popcount_nat f m = ∑ i ∈ Finset.range f, (m.testBit i).toNat := by + intro f + induction f with + | zero => intro m; rfl + | succ f ih => + intro m + rw [jolt_popcount_nat, ih, Finset.sum_range_succ'] + simp only [Nat.testBit_succ, Nat.testBit_zero] + rw [add_comm]; congr 1 + by_cases h : m % 2 = 1 <;> simp [h]; omega + +private theorem cpopNatRec_eq_sum (b : BitVec 64) : ∀ n : Nat, + b.cpopNatRec n 0 = ∑ i ∈ Finset.range n, (b.getLsbD i).toNat := by + intro n + induction n with + | zero => rfl + | succ n ih => rw [BitVec.cpopNatRec_succ, BitVec.cpopNatRec_eq, ih, Finset.sum_range_succ]; simp + +private theorem cpop_toNat_eq (y : BitVec 64) : y.cpop.toNat = jolt_popcount_nat 64 y.toNat := by + rw [BitVec.toNat_cpop, cpopNatRec_eq_sum, jolt_popcount_nat_eq_sum] + rfl + +theorem jolt_popcount_nat_le : ∀ (f m : Nat), jolt_popcount_nat f m ≤ f := by + intro f; induction f with + | zero => intro m; rfl + | succ f ih => intro m; rw [jolt_popcount_nat]; have := ih (m / 2); omega + +theorem jolt_popcount_nat_zero : ∀ (f : Nat), jolt_popcount_nat f 0 = 0 := by + intro f; induction f with + | zero => rfl + | succ f ih => rw [jolt_popcount_nat, Nat.zero_div, ih] + +theorem jolt_popcount_nat_pos : ∀ (f m : Nat), m ≠ 0 → m < 2 ^ f → 0 < jolt_popcount_nat f m := by + intro f; induction f with + | zero => intro m hm hlt; simp at hlt; omega + | succ f ih => + intro m hm hlt + rw [jolt_popcount_nat] + by_cases h : m % 2 = 1 + · omega + · have := ih (m / 2) (by omega) (by rw [pow_succ] at hlt; omega); omega + +theorem jolt_pext_nat_lt : ∀ (f X m : Nat), jolt_pext_nat f X m < 2 ^ jolt_popcount_nat f m := by + intro f; induction f with + | zero => intro X m; simp [jolt_pext_nat, jolt_popcount_nat] + | succ f ih => + intro X m + have := ih (X / 2) (m / 2) + rw [jolt_pext_nat, jolt_popcount_nat] + split_ifs with h + · rw [h, Nat.add_comm 1, pow_succ]; omega + · rw [show m % 2 = 0 by omega, zero_add]; exact this + +theorem jolt_pext_nat_testBit_top : ∀ (f X m : Nat), m ≠ 0 → m < 2 ^ f → + (jolt_pext_nat f X m).testBit (jolt_popcount_nat f m - 1) = X.testBit m.log2 := by + intro f; induction f with + | zero => intro X m hm hlt; simp at hlt; omega + | succ f ih => + intro X m hm hlt + have hlt' : m / 2 < 2 ^ f := by rw [pow_succ] at hlt; omega + rw [jolt_pext_nat, jolt_popcount_nat] + split_ifs with h + · by_cases h0 : m / 2 = 0 + · have hm1 : m = 1 := by omega + subst hm1 + simp only [h0, jolt_pext_nat_zero, jolt_popcount_nat_zero, (Nat.log2_eq_iff (by decide)).mpr + (by decide : 2 ^ 0 ≤ 1 ∧ 1 < 2 ^ (0 + 1))] + simp [Nat.testBit_zero] + · have hpos := jolt_popcount_nat_pos f (m / 2) h0 hlt' + have hlog : m.log2 = (m / 2).log2 + 1 := by + rw [Nat.log2_def, if_pos (by omega)] + rw [h, hlog, show 1 + jolt_popcount_nat f (m / 2) - 1 = + (jolt_popcount_nat f (m / 2) - 1) + 1 by omega, Nat.testBit_succ, Nat.testBit_succ, + show (X % 2 + 2 * jolt_pext_nat f (X / 2) (m / 2)) / 2 = jolt_pext_nat f (X / 2) (m / 2) + by omega] + exact ih _ _ h0 hlt' + · have h0 : m / 2 ≠ 0 := by omega + have hlog : m.log2 = (m / 2).log2 + 1 := by + rw [Nat.log2_def, if_pos (by omega)] + rw [show m % 2 = 0 by omega, zero_add, hlog, Nat.testBit_succ] + exact ih _ _ h0 hlt' + +private theorem shift_and_one_toNat (x : BitVec 64) (p : Nat) : + ((x >>> p) &&& 1).toNat = x.toNat / 2 ^ p % 2 := by + simp [BitVec.toNat_and, BitVec.toNat_ushiftRight, Nat.shiftRight_eq_div_pow] + +/-- The PEXT_SIGNED table's `pextSigned` is the honest `VirtualPextSigned` value. -/ +theorem pextSigned_eq_jolt_virtual_pext_signed_value (x y : BitVec 64) : + pextSigned x y = jolt_virtual_pext_signed_value x y := by + unfold pextSigned jolt_virtual_pext_signed_value + rw [cpop_toNat_eq, pext_eq_jolt_virtual_pext_value] + generalize hP : jolt_popcount_nat 64 y.toNat = P + by_cases hP0 : P = 0 + · simp [hP0] + simp only [hP0, if_false] + have hy : y ≠ 0 := by + rintro rfl + rw [show (0 : BitVec 64).toNat = 0 from rfl, jolt_popcount_nat_zero] at hP; exact hP0 hP.symm + have hY : y.toNat ≠ 0 := fun h => hy (BitVec.eq_of_toNat_eq (by simpa using h)) + have hP64 : P ≤ 64 := hP ▸ jolt_popcount_nat_le 64 y.toNat + have hJ : jolt_pext_nat 64 x.toNat y.toNat < 2 ^ P := hP ▸ jolt_pext_nat_lt 64 x.toNat y.toNat + have hPle : 2 ^ P ≤ 2 ^ 64 := Nat.pow_le_pow_right (by decide) hP64 + have hE : (jolt_virtual_pext_value x y).toNat = jolt_pext_nat 64 x.toNat y.toNat := by + simp only [jolt_virtual_pext_value, BitVec.toNat_ofNat]; exact Nat.mod_eq_of_lt (by omega) + have htop : (jolt_virtual_pext_value x y).getLsbD (P - 1) = x.toNat.testBit y.toNat.log2 := by + rw [← BitVec.testBit_toNat, hE, ← hP]; exact jolt_pext_nat_testBit_top 64 _ _ hY y.isLt + have hsign : (windowSignBit x y = 1) ↔ x.toNat.testBit y.toNat.log2 = true := by + unfold windowSignBit + rw [if_neg hy, ← BitVec.toNat_inj, shift_and_one_toNat, Nat.testBit_eq_decide_div_mod_eq] + simp + rw [htop] + by_cases hb : x.toNat.testBit y.toNat.log2 = true + · rw [if_pos (hsign.mpr hb), if_pos hb] + apply BitVec.eq_of_toNat_eq + have hsub : (((1#128 <<< 64) - (1#128 <<< P)).setWidth 64).toNat = 2 ^ 64 - 2 ^ P := by + rw [BitVec.toNat_setWidth, BitVec.toNat_sub, BitVec.toNat_shiftLeft, BitVec.toNat_shiftLeft, + show (1#128).toNat = 1 from rfl, Nat.shiftLeft_eq, Nat.shiftLeft_eq, Nat.one_mul, + Nat.one_mul] + have hP2 : 2 ≤ 2 ^ P := by + calc 2 = 2 ^ 1 := rfl + _ ≤ 2 ^ P := Nat.pow_le_pow_right (by decide) (by omega) + generalize 2 ^ P = Q at * + omega + have hlow : (BitVec.ofNat 64 (2 ^ P - 1)).toNat = 2 ^ P - 1 := by + rw [BitVec.toNat_ofNat]; exact Nat.mod_eq_of_lt (by omega) + have hsplit : 2 ^ 64 - 2 ^ P = 2 ^ P * (2 ^ (64 - P) - 1) := by + rw [Nat.mul_sub_one, ← pow_add, show P + (64 - P) = 64 by omega] + rw [BitVec.toNat_add, BitVec.toNat_or, BitVec.toNat_not, hlow, hE, hsub, + show 2 ^ 64 - 1 - (2 ^ P - 1) = 2 ^ 64 - 2 ^ P by omega, hsplit, Nat.or_comm, + ← Nat.two_pow_add_eq_or_of_lt hJ, ← hsplit, Nat.mod_eq_of_lt (by omega)] + omega + · have hns : ¬ windowSignBit x y = 1 := fun h => hb (hsign.mp h) + rw [if_neg hns, if_neg hb]; simp + +end JoltConstraints diff --git a/JoltConstraints/Tests/LookupShiftMask.lean b/JoltConstraints/Tests/LookupShiftMask.lean new file mode 100644 index 0000000..7960077 --- /dev/null +++ b/JoltConstraints/Tests/LookupShiftMask.lean @@ -0,0 +1,99 @@ +import JoltConstraints.Constraints.LookupEntryProofHelpers + +set_option autoImplicit false + +/-! +Counterexamples and checks for the per-table lemmas in +`JoltConstraints/Constraints/LookupEntryProofHelpers.lean` left as `sorry`. +Not part of the default build; check with `lake env lean JoltConstraints/Tests/LookupShiftMask.lean`. + +Shift/rotate tables (`VirtualSRL`, `VirtualSRA`, `VirtualSRLW`, `VirtualSRAW`, +`VirtualROTR`, `VirtualROTRW`) read the right operand as a right-shift bitmask, +while the honest outputs shift by `ctz` of whatever mask the row carries. +Source `4`, mask `2`: index `interleave 4 2 = 36`; entries 0/0/0/0/4/4 versus +output `2` for all six. `VirtualSRLW` with mask `0` (register form): index 34, +entry 0, output `5`. +-/ + +open JoltConstraints + +#eval (HonestWitness.interleaveLookupOperands 4 2).toNat +#eval (HonestWitness.interleaveLookupOperands 5 0).toNat +#eval (jolt_virtual_srl_value 4 2, jolt_virtual_srli_value 4 2, jolt_virtual_sra_value 4 2, + jolt_virtual_srlw_value 4 2, jolt_virtual_sraw_value 4 2, jolt_virtual_rotri_value 4 2, + jolt_virtual_rotriw_value 4 2, jolt_virtual_srlw_value 5 0, jolt_virtual_srliw_value 5 0) + +set_option maxRecDepth 8192 in +example : (lookupTableEntry .VirtualSRL ⟨36, by decide⟩ : ℚ) = 0 := by decide +kernel +set_option maxRecDepth 8192 in +example : (lookupTableEntry .VirtualSRA ⟨36, by decide⟩ : ℚ) = 0 := by decide +kernel +set_option maxRecDepth 8192 in +example : (lookupTableEntry .VirtualSRLW ⟨36, by decide⟩ : ℚ) = 0 := by decide +kernel +set_option maxRecDepth 8192 in +example : (lookupTableEntry .VirtualSRAW ⟨36, by decide⟩ : ℚ) = 0 := by decide +kernel +set_option maxRecDepth 8192 in +example : (lookupTableEntry .VirtualROTR ⟨36, by decide⟩ : ℚ) = 4 := by decide +kernel +set_option maxRecDepth 8192 in +example : (lookupTableEntry .VirtualROTRW ⟨36, by decide⟩ : ℚ) = 4 := by decide +kernel +set_option maxRecDepth 8192 in +example : (lookupTableEntry .VirtualSRLW ⟨34, by decide⟩ : ℚ) = 0 := by decide +kernel + +/-! Row-level refutation of `lookupEntryCorrect_VirtualSRL`: a one-row program +`VirtualSRLI v32, v33, 2` (mask immediate `2`, not a right-shift bitmask) run +from any state with `v33 = 4`. `OperandsRepresentable` only asks `2 < 2^64`. -/ +namespace SRLICounterexample + +def instruction : JoltISA.Instr := .VirtualSRLI (.vreg 32) (.vreg 33) 2 + +def bytecodeRow : JoltProgramRow := + { inputInstruction := instruction + registerOperandsCanonical := by decide + isBytecodeTemplate := True.intro + operandsRepresentable := by + simp [finalProgramRowInstruction, instruction, JoltISA.Instr.OperandsRepresentable] + address := 0x80000000 + virtualSequenceRemaining := some 0 + isFirstInSequence := true + isCompressed := false } + +def program (state : SailJoltState) : JoltProgram := + { expandedBytecode := #[bytecodeRow] + initialState := state } + +noncomputable def visit (state : SailJoltState) : JoltTraceRow (program state) := + { rowIndex := ⟨0, by simp [program]⟩ + runtimeAdvice := () + preState := state + postState := { state with + vregs := fun r => if r = 32 then (jolt_virtual_srli_value (state.vregs 33) 2) else state.vregs r } + executes := rfl + storeMemoryPresent := True.intro } + +example (state : SailJoltState) : + JoltMetadata.lookupTable (rowInstruction (visit state)) = some .VirtualSRL := rfl + +example (state : SailJoltState) (h : state.vregs 33 = 4) : + HonestWitness.rowLookupOutput (visit state) = 2 := by + change jolt_virtual_srli_value (state.vregs 33) 2 = 2 + rw [h]; decide +kernel + +example (state : SailJoltState) (h : state.vregs 33 = 4) : + rowLookupAddress (visit state) = ⟨36, by decide⟩ := by + change (HonestWitness.interleaveLookupOperands (state.vregs 33) (BitVec.ofNat 64 2)).toFin = _ + rw [h]; decide +kernel + +set_option maxRecDepth 8192 in +theorem not_lookupEntryCorrect (state : SailJoltState) (h : state.vregs 33 = 4) : + ¬ LookupEntryCorrect ℚ .VirtualSRL (visit state) := by + unfold LookupEntryCorrect + have ha : rowLookupAddress (visit state) = ⟨36, by decide⟩ := by + change (HonestWitness.interleaveLookupOperands (state.vregs 33) (BitVec.ofNat 64 2)).toFin = _ + rw [h]; decide +kernel + have ho : HonestWitness.rowLookupOutput (visit state) = 2 := by + change jolt_virtual_srli_value (state.vregs 33) 2 = 2 + rw [h]; decide +kernel + have he : (lookupTableEntry .VirtualSRL ⟨36, by decide⟩ : ℚ) = 0 := by decide +kernel + rw [ha, ho, he] + simp + +end SRLICounterexample diff --git a/JoltConstraints/model_review.md b/JoltConstraints/model_review.md index f536fb2..b4aaebb 100644 --- a/JoltConstraints/model_review.md +++ b/JoltConstraints/model_review.md @@ -218,7 +218,7 @@ are **deferred work or documented modeling choices**, not established proofs. | [program.lean](program.lean), two `FIXME`s | `rowValid` (including writable jump destinations, canonical x0 writes, and jumps at the end of expansions) and `noEarlyNextPCChange` use `sorry` and are false for arbitrary arrays. Connect rows to Rust source expansion and native rewrites, then prove the claims for those outputs. | | [program.lean](program.lean), `WARNING` | Rust currently permits source-PC wraparound; `SequenceLayout.addressAdvanceNoWrap` is a temporary assumption pending the fix tracked by [Jolt issue #1949](https://github.com/a16z/jolt/issues/1949). | | [trace.lean](trace.lean), `FIXME` | Derive `hostIOPreservesPC` from live `execInstr` using the new Sail byte-read frame; it is still an explicit trace-row obligation. | -| [LookupOutputEqInstructionReadRaf.lean](Constraints/LookupOutputEqInstructionReadRaf.lean), `FIXME` | Connect each now-defined fixed table entry to `HonestWitness.rowLookupOutput` for execution rows. | +| [LookupEntryProofHelpers.lean](Constraints/LookupEntryProofHelpers.lean), `FIXME` | Constraint (39) is proved modulo six per-table `sorry`s. `lookupEntryCorrect_{VirtualSRL,VirtualSRA,VirtualSRLW,VirtualSRAW,VirtualROTR,VirtualROTRW}` are false for arbitrary bytecode (non-bitmask mask operand; see `JoltConstraints/Tests/LookupShiftMask.lean`) and need the expander's bitmask invariant. | | [RamValEqInitialPlusPrefixRamInc.lean](Constraints/RamValEqInitialPlusPrefixRamInc.lean), `FIXME` | Relate captured RAM reads to initialized memory and prefix RAM history. | | [RamValFinalEqInitialPlusRamInc.lean](Constraints/RamValFinalEqInitialPlusRamInc.lean), `FIXME` | Establish Rust memory-history and terminal-state correspondence. | | [RamOutputEqPublicIo.lean](Constraints/RamOutputEqPublicIo.lean), `FIXME` | Relate Rust's final public output to the Lean witness. |