Skip to content

Commit e76b2fb

Browse files
hyperpolymathJonathan D.A. Jewellclaude
authored
ABI: make Idris2 proofs genuinely compile + add machine-checked theorems (#37)
## Idris2 ABI proofs — genuinely compile + machine-checked theorems Part of the family-wide ABI-proofs review. The hand-written ABI did **not** typecheck under Idris2 0.7.0; this makes it real and verified, following the merged **iseriser** reference. **Systemic fixes:** `decEq _ _ = No absurd` → explicit off-diagonal cases; `{auto 0 nonNull}` smart constructors → `choose`-based; `paddingFor` `-` → `minus`; real `decDivides`/`decFieldsAligned` replacing `?fieldsAlignedProof`; honest `offsetInBounds`; `thisPlatform` de-`%runElab`'d; buildable nested layout + ipkg; new `Proofs.idr` with real theorems. **Verified:** `idris2 --build` clean (0/0); adversarial re-verify found no `believe_me`/`postulate`/holes. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- _Generated by [Claude Code](https://claude.ai/code/session_01DF9CcCuL4YJoqs26eHsYiA)_ --------- Co-authored-by: Jonathan D.A. Jewell <paraordinate@yahoo.co.uk> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 32d9596 commit e76b2fb

6 files changed

Lines changed: 212 additions & 81 deletions

File tree

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ Thumbs.db
1818
/dist/
1919
/out/
2020

21+
# Idris2 build artifacts
22+
**/build/
23+
*.ttc
24+
*.ttm
25+
2126
# Dependencies
2227
/node_modules/
2328
/vendor/
File renamed without changes.

src/interface/abi/Layout.idr renamed to src/interface/abi/Anvomidaviser/ABI/Layout.idr

Lines changed: 87 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ module Anvomidaviser.ABI.Layout
1414
import Anvomidaviser.ABI.Types
1515
import Data.Vect
1616
import Data.So
17+
import Data.Nat
18+
import Decidable.Equality
1719

1820
%default total
1921

@@ -27,7 +29,7 @@ paddingFor : (offset : Nat) -> (alignment : Nat) -> Nat
2729
paddingFor offset alignment =
2830
if offset `mod` alignment == 0
2931
then 0
30-
else alignment - (offset `mod` alignment)
32+
else minus alignment (offset `mod` alignment)
3133

3234
||| Proof that alignment divides aligned size
3335
public export
@@ -40,11 +42,25 @@ alignUp : (size : Nat) -> (alignment : Nat) -> Nat
4042
alignUp size alignment =
4143
size + paddingFor size alignment
4244

43-
||| Proof that alignUp produces aligned result
45+
||| Sound decision procedure: does n divide m?
46+
||| For n = S k, compute the candidate quotient q = m `div` (S k) and check
47+
||| that q * (S k) recovers m exactly. The equality proof is real (decEq),
48+
||| never assumed.
4449
public export
45-
alignUpCorrect : (size : Nat) -> (align : Nat) -> (align > 0) -> Divides align (alignUp size align)
46-
alignUpCorrect size align prf =
47-
DivideBy ((size + paddingFor size align) `div` align) Refl
50+
decDivides : (n : Nat) -> (m : Nat) -> Maybe (Divides n m)
51+
decDivides Z m = Nothing
52+
decDivides (S k) m =
53+
let q = m `div` (S k) in
54+
case decEq m (q * (S k)) of
55+
Yes prf => Just (DivideBy q prf)
56+
No _ => Nothing
57+
58+
||| Decide whether alignUp produced an aligned result. Returning a universal
59+
||| proof would be unsound (paddingFor uses runtime division, which does not
60+
||| reduce), so we hand back a genuine, checked witness via decDivides.
61+
public export
62+
alignUpDivides : (size : Nat) -> (align : Nat) -> Maybe (Divides align (alignUp size align))
63+
alignUpDivides size align = decDivides align (alignUp size align)
4864

4965
--------------------------------------------------------------------------------
5066
-- Struct Field Layout
@@ -76,7 +92,7 @@ record StructLayout where
7692

7793
||| Calculate total struct size with padding
7894
public export
79-
calcStructSize : Vect n Field -> Nat -> Nat
95+
calcStructSize : Vect k Field -> Nat -> Nat
8096
calcStructSize [] align = 0
8197
calcStructSize (f :: fs) align =
8298
let lastOffset = foldl (\acc, field => nextFieldOffset field) f.offset fs
@@ -85,23 +101,40 @@ calcStructSize (f :: fs) align =
85101

86102
||| Proof that field offsets are correctly aligned
87103
public export
88-
data FieldsAligned : Vect n Field -> Type where
104+
data FieldsAligned : Vect k Field -> Type where
89105
NoFields : FieldsAligned []
90106
ConsField :
91107
(f : Field) ->
92-
(rest : Vect n Field) ->
108+
(rest : Vect k Field) ->
93109
Divides f.alignment f.offset ->
94110
FieldsAligned rest ->
95111
FieldsAligned (f :: rest)
96112

113+
||| Decide whether every field in a vector is aligned (offset divisible by
114+
||| alignment), building a real FieldsAligned witness if so.
115+
public export
116+
decFieldsAligned : (fields : Vect k Field) -> Maybe (FieldsAligned fields)
117+
decFieldsAligned [] = Just NoFields
118+
decFieldsAligned (f :: fs) =
119+
case decDivides f.alignment f.offset of
120+
Nothing => Nothing
121+
Just dv =>
122+
case decFieldsAligned fs of
123+
Nothing => Nothing
124+
Just rest => Just (ConsField f fs dv rest)
125+
97126
||| Verify a struct layout is valid
98127
public export
99-
verifyLayout : (fields : Vect n Field) -> (align : Nat) -> Either String StructLayout
128+
verifyLayout : (fields : Vect k Field) -> (align : Nat) -> Either String StructLayout
100129
verifyLayout fields align =
101130
let size = calcStructSize fields align
102-
in case decSo (size >= sum (map (\f => f.size) fields)) of
103-
Yes prf => Right (MkStructLayout fields size align)
104-
No _ => Left "Invalid struct size"
131+
in case choose (size >= sum (map (\f => f.size) fields)) of
132+
Right _ => Left "Invalid struct size"
133+
Left szPrf =>
134+
case decDivides align size of
135+
Nothing => Left "Total size is not a multiple of the alignment"
136+
Just dv =>
137+
Right (MkStructLayout fields size align {sizeCorrect = szPrf} {aligned = dv})
105138

106139
--------------------------------------------------------------------------------
107140
-- Platform-Specific Layouts
@@ -136,7 +169,9 @@ data CABICompliant : StructLayout -> Type where
136169
public export
137170
checkCABI : (layout : StructLayout) -> Either String (CABICompliant layout)
138171
checkCABI layout =
139-
Right (CABIOk layout ?fieldsAlignedProof)
172+
case decFieldsAligned layout.fields of
173+
Just prf => Right (CABIOk layout prf)
174+
Nothing => Left "Struct fields are not C-ABI aligned"
140175

141176
--------------------------------------------------------------------------------
142177
-- ISU Element Layouts
@@ -160,11 +195,23 @@ technicalElementLayout =
160195
]
161196
16 -- Total size: 16 bytes
162197
4 -- Alignment: 4 bytes
198+
{sizeCorrect = Oh}
199+
{aligned = DivideBy 4 Refl} -- 16 = 4 * 4
163200

164201
||| Proof that TechnicalElement layout is valid
165202
export
166-
technicalElementLayoutValid : CABICompliant technicalElementLayout
167-
technicalElementLayoutValid = CABIOk technicalElementLayout ?techElemFieldsAligned
203+
technicalElementLayoutValid : CABICompliant Layout.technicalElementLayout
204+
technicalElementLayoutValid =
205+
CABIOk Layout.technicalElementLayout
206+
(ConsField _ _ (DivideBy 0 Refl)
207+
(ConsField _ _ (DivideBy 1 Refl)
208+
(ConsField _ _ (DivideBy 2 Refl)
209+
(ConsField _ _ (DivideBy 3 Refl)
210+
(ConsField _ _ (DivideBy 1 Refl)
211+
(ConsField _ _ (DivideBy 8 Refl)
212+
(ConsField _ _ (DivideBy 9 Refl)
213+
(ConsField _ _ (DivideBy 3 Refl)
214+
NoFields))))))))
168215

169216
||| Layout for ProgramScore struct in the C ABI
170217
||| Fields: total_base (u32), total_goe (i32), deductions (u32),
@@ -188,11 +235,26 @@ programScoreLayout =
188235
]
189236
28 -- Total size: 28 bytes
190237
4 -- Alignment: 4 bytes
238+
{sizeCorrect = Oh}
239+
{aligned = DivideBy 7 Refl} -- 28 = 7 * 4
191240

192241
||| Proof that ProgramScore layout is valid
193242
export
194-
programScoreLayoutValid : CABICompliant programScoreLayout
195-
programScoreLayoutValid = CABIOk programScoreLayout ?progScoreFieldsAligned
243+
programScoreLayoutValid : CABICompliant Layout.programScoreLayout
244+
programScoreLayoutValid =
245+
CABIOk Layout.programScoreLayout
246+
(ConsField _ _ (DivideBy 0 Refl)
247+
(ConsField _ _ (DivideBy 1 Refl)
248+
(ConsField _ _ (DivideBy 2 Refl)
249+
(ConsField _ _ (DivideBy 12 Refl)
250+
(ConsField _ _ (DivideBy 13 Refl)
251+
(ConsField _ _ (DivideBy 14 Refl)
252+
(ConsField _ _ (DivideBy 15 Refl)
253+
(ConsField _ _ (DivideBy 16 Refl)
254+
(ConsField _ _ (DivideBy 17 Refl)
255+
(ConsField _ _ (DivideBy 5 Refl)
256+
(ConsField _ _ (DivideBy 6 Refl)
257+
NoFields)))))))))))
196258

197259
--------------------------------------------------------------------------------
198260
-- Offset Calculation
@@ -206,7 +268,12 @@ fieldOffset layout name =
206268
Just idx => Just (finToNat idx ** index idx layout.fields)
207269
Nothing => Nothing
208270

209-
||| Proof that field offset is within struct bounds
271+
||| Decide whether a field lies within the struct bounds.
272+
||| Universally claiming the bound would be unsound (it is false for
273+
||| arbitrary fields), so we return a decision via choose.
210274
public export
211-
offsetInBounds : (layout : StructLayout) -> (f : Field) -> So (f.offset + f.size <= layout.totalSize)
212-
offsetInBounds layout f = ?offsetInBoundsProof
275+
offsetInBounds : (layout : StructLayout) -> (f : Field) -> Maybe (So (f.offset + f.size <= layout.totalSize))
276+
offsetInBounds layout f =
277+
case choose (f.offset + f.size <= layout.totalSize) of
278+
Left ok => Just ok
279+
Right _ => Nothing
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
-- SPDX-License-Identifier: MPL-2.0
2+
-- Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
3+
--
4+
||| Machine-checked ABI theorems for Anvomidaviser
5+
|||
6+
||| This module collects genuine, compiler-verified proofs about the
7+
||| concrete C-ABI struct layouts and the result-code encoding used at the
8+
||| FFI boundary. Every witness here is built directly so that it reduces
9+
||| during typechecking — no holes, no postulates, no believe_me.
10+
11+
module Anvomidaviser.ABI.Proofs
12+
13+
import Anvomidaviser.ABI.Types
14+
import Anvomidaviser.ABI.Layout
15+
import Data.Vect
16+
17+
%default total
18+
19+
--------------------------------------------------------------------------------
20+
-- C-ABI Compliance of Concrete Layouts
21+
--------------------------------------------------------------------------------
22+
23+
||| The TechnicalElement struct layout is C-ABI compliant: every field offset
24+
||| is an exact multiple of that field's alignment. Each DivideBy witness is
25+
||| built directly (field.offset = k * field.alignment), since multiplication
26+
||| reduces during typechecking while division does not.
27+
export
28+
technicalElementCompliant : CABICompliant Layout.technicalElementLayout
29+
technicalElementCompliant =
30+
CABIOk Layout.technicalElementLayout
31+
(ConsField _ _ (DivideBy 0 Refl) -- element_type: offset 0 = 0 * 1
32+
(ConsField _ _ (DivideBy 1 Refl) -- subtype: offset 1 = 1 * 1
33+
(ConsField _ _ (DivideBy 2 Refl) -- rotation: offset 2 = 2 * 1
34+
(ConsField _ _ (DivideBy 3 Refl) -- level: offset 3 = 3 * 1
35+
(ConsField _ _ (DivideBy 1 Refl) -- base_value: offset 4 = 1 * 4
36+
(ConsField _ _ (DivideBy 8 Refl) -- goe: offset 8 = 8 * 1
37+
(ConsField _ _ (DivideBy 9 Refl) -- padding: offset 9 = 9 * 1
38+
(ConsField _ _ (DivideBy 3 Refl) -- goe_value: offset 12 = 3 * 4
39+
NoFields))))))))
40+
41+
||| The ProgramScore struct layout is C-ABI compliant.
42+
export
43+
programScoreCompliant : CABICompliant Layout.programScoreLayout
44+
programScoreCompliant =
45+
CABIOk Layout.programScoreLayout
46+
(ConsField _ _ (DivideBy 0 Refl) -- total_base: offset 0 = 0 * 4
47+
(ConsField _ _ (DivideBy 1 Refl) -- total_goe: offset 4 = 1 * 4
48+
(ConsField _ _ (DivideBy 2 Refl) -- deductions: offset 8 = 2 * 4
49+
(ConsField _ _ (DivideBy 12 Refl) -- pcs_skating_skills: offset 12 = 12 * 1
50+
(ConsField _ _ (DivideBy 13 Refl) -- pcs_transitions: offset 13 = 13 * 1
51+
(ConsField _ _ (DivideBy 14 Refl) -- pcs_performance: offset 14 = 14 * 1
52+
(ConsField _ _ (DivideBy 15 Refl) -- pcs_composition: offset 15 = 15 * 1
53+
(ConsField _ _ (DivideBy 16 Refl) -- pcs_interpretation: offset 16 = 16 * 1
54+
(ConsField _ _ (DivideBy 17 Refl) -- padding: offset 17 = 17 * 1
55+
(ConsField _ _ (DivideBy 5 Refl) -- component_factor: offset 20 = 5 * 4
56+
(ConsField _ _ (DivideBy 6 Refl) -- total_segment: offset 24 = 6 * 4
57+
NoFields)))))))))))
58+
59+
--------------------------------------------------------------------------------
60+
-- Result-Code Encoding
61+
--------------------------------------------------------------------------------
62+
63+
||| The success result encodes to the C integer 0.
64+
export
65+
okIsZero : resultToInt Ok = 0
66+
okIsZero = Refl
67+
68+
||| The result-code encoding is injective at the two boundary cases we rely on
69+
||| at the FFI layer: Ok and Error encode to distinct integers.
70+
export
71+
okNotError : Not (resultToInt Ok = resultToInt Error)
72+
okNotError = \case Refl impossible
73+
74+
||| RuleViolation pins to 5, the highest defined error code.
75+
export
76+
ruleViolationIsFive : resultToInt RuleViolation = 5
77+
ruleViolationIsFive = Refl

0 commit comments

Comments
 (0)