Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ Thumbs.db
/dist/
/out/

# Idris2 build artifacts
**/build/
*.ttc
*.ttm

# Dependencies
/node_modules/
/vendor/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ module Anvomidaviser.ABI.Layout
import Anvomidaviser.ABI.Types
import Data.Vect
import Data.So
import Data.Nat
import Decidable.Equality

%default total

Expand All @@ -27,7 +29,7 @@ paddingFor : (offset : Nat) -> (alignment : Nat) -> Nat
paddingFor offset alignment =
if offset `mod` alignment == 0
then 0
else alignment - (offset `mod` alignment)
else minus alignment (offset `mod` alignment)

||| Proof that alignment divides aligned size
public export
Expand All @@ -40,11 +42,25 @@ alignUp : (size : Nat) -> (alignment : Nat) -> Nat
alignUp size alignment =
size + paddingFor size alignment

||| Proof that alignUp produces aligned result
||| Sound decision procedure: does n divide m?
||| For n = S k, compute the candidate quotient q = m `div` (S k) and check
||| that q * (S k) recovers m exactly. The equality proof is real (decEq),
||| never assumed.
public export
alignUpCorrect : (size : Nat) -> (align : Nat) -> (align > 0) -> Divides align (alignUp size align)
alignUpCorrect size align prf =
DivideBy ((size + paddingFor size align) `div` align) Refl
decDivides : (n : Nat) -> (m : Nat) -> Maybe (Divides n m)
decDivides Z m = Nothing
decDivides (S k) m =
let q = m `div` (S k) in
case decEq m (q * (S k)) of
Yes prf => Just (DivideBy q prf)
No _ => Nothing

||| Decide whether alignUp produced an aligned result. Returning a universal
||| proof would be unsound (paddingFor uses runtime division, which does not
||| reduce), so we hand back a genuine, checked witness via decDivides.
public export
alignUpDivides : (size : Nat) -> (align : Nat) -> Maybe (Divides align (alignUp size align))
alignUpDivides size align = decDivides align (alignUp size align)

--------------------------------------------------------------------------------
-- Struct Field Layout
Expand Down Expand Up @@ -76,7 +92,7 @@ record StructLayout where

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

||| Proof that field offsets are correctly aligned
public export
data FieldsAligned : Vect n Field -> Type where
data FieldsAligned : Vect k Field -> Type where
NoFields : FieldsAligned []
ConsField :
(f : Field) ->
(rest : Vect n Field) ->
(rest : Vect k Field) ->
Divides f.alignment f.offset ->
FieldsAligned rest ->
FieldsAligned (f :: rest)

||| Decide whether every field in a vector is aligned (offset divisible by
||| alignment), building a real FieldsAligned witness if so.
public export
decFieldsAligned : (fields : Vect k Field) -> Maybe (FieldsAligned fields)
decFieldsAligned [] = Just NoFields
decFieldsAligned (f :: fs) =
case decDivides f.alignment f.offset of
Nothing => Nothing
Just dv =>
case decFieldsAligned fs of
Nothing => Nothing
Just rest => Just (ConsField f fs dv rest)

||| Verify a struct layout is valid
public export
verifyLayout : (fields : Vect n Field) -> (align : Nat) -> Either String StructLayout
verifyLayout : (fields : Vect k Field) -> (align : Nat) -> Either String StructLayout
verifyLayout fields align =
let size = calcStructSize fields align
in case decSo (size >= sum (map (\f => f.size) fields)) of
Yes prf => Right (MkStructLayout fields size align)
No _ => Left "Invalid struct size"
in case choose (size >= sum (map (\f => f.size) fields)) of
Right _ => Left "Invalid struct size"
Left szPrf =>
case decDivides align size of
Nothing => Left "Total size is not a multiple of the alignment"
Just dv =>
Right (MkStructLayout fields size align {sizeCorrect = szPrf} {aligned = dv})

--------------------------------------------------------------------------------
-- Platform-Specific Layouts
Expand Down Expand Up @@ -136,7 +169,9 @@ data CABICompliant : StructLayout -> Type where
public export
checkCABI : (layout : StructLayout) -> Either String (CABICompliant layout)
checkCABI layout =
Right (CABIOk layout ?fieldsAlignedProof)
case decFieldsAligned layout.fields of
Just prf => Right (CABIOk layout prf)
Nothing => Left "Struct fields are not C-ABI aligned"

--------------------------------------------------------------------------------
-- ISU Element Layouts
Expand All @@ -160,11 +195,23 @@ technicalElementLayout =
]
16 -- Total size: 16 bytes
4 -- Alignment: 4 bytes
{sizeCorrect = Oh}
{aligned = DivideBy 4 Refl} -- 16 = 4 * 4

||| Proof that TechnicalElement layout is valid
export
technicalElementLayoutValid : CABICompliant technicalElementLayout
technicalElementLayoutValid = CABIOk technicalElementLayout ?techElemFieldsAligned
technicalElementLayoutValid : CABICompliant Layout.technicalElementLayout
technicalElementLayoutValid =
CABIOk Layout.technicalElementLayout
(ConsField _ _ (DivideBy 0 Refl)
(ConsField _ _ (DivideBy 1 Refl)
(ConsField _ _ (DivideBy 2 Refl)
(ConsField _ _ (DivideBy 3 Refl)
(ConsField _ _ (DivideBy 1 Refl)
(ConsField _ _ (DivideBy 8 Refl)
(ConsField _ _ (DivideBy 9 Refl)
(ConsField _ _ (DivideBy 3 Refl)
NoFields))))))))

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

||| Proof that ProgramScore layout is valid
export
programScoreLayoutValid : CABICompliant programScoreLayout
programScoreLayoutValid = CABIOk programScoreLayout ?progScoreFieldsAligned
programScoreLayoutValid : CABICompliant Layout.programScoreLayout
programScoreLayoutValid =
CABIOk Layout.programScoreLayout
(ConsField _ _ (DivideBy 0 Refl)
(ConsField _ _ (DivideBy 1 Refl)
(ConsField _ _ (DivideBy 2 Refl)
(ConsField _ _ (DivideBy 12 Refl)
(ConsField _ _ (DivideBy 13 Refl)
(ConsField _ _ (DivideBy 14 Refl)
(ConsField _ _ (DivideBy 15 Refl)
(ConsField _ _ (DivideBy 16 Refl)
(ConsField _ _ (DivideBy 17 Refl)
(ConsField _ _ (DivideBy 5 Refl)
(ConsField _ _ (DivideBy 6 Refl)
NoFields)))))))))))

--------------------------------------------------------------------------------
-- Offset Calculation
Expand All @@ -206,7 +268,12 @@ fieldOffset layout name =
Just idx => Just (finToNat idx ** index idx layout.fields)
Nothing => Nothing

||| Proof that field offset is within struct bounds
||| Decide whether a field lies within the struct bounds.
||| Universally claiming the bound would be unsound (it is false for
||| arbitrary fields), so we return a decision via choose.
public export
offsetInBounds : (layout : StructLayout) -> (f : Field) -> So (f.offset + f.size <= layout.totalSize)
offsetInBounds layout f = ?offsetInBoundsProof
offsetInBounds : (layout : StructLayout) -> (f : Field) -> Maybe (So (f.offset + f.size <= layout.totalSize))
offsetInBounds layout f =
case choose (f.offset + f.size <= layout.totalSize) of
Left ok => Just ok
Right _ => Nothing
77 changes: 77 additions & 0 deletions src/interface/abi/Anvomidaviser/ABI/Proofs.idr
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
-- SPDX-License-Identifier: MPL-2.0
-- Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
--
||| Machine-checked ABI theorems for Anvomidaviser
|||
||| This module collects genuine, compiler-verified proofs about the
||| concrete C-ABI struct layouts and the result-code encoding used at the
||| FFI boundary. Every witness here is built directly so that it reduces
||| during typechecking — no holes, no postulates, no believe_me.

module Anvomidaviser.ABI.Proofs

import Anvomidaviser.ABI.Types
import Anvomidaviser.ABI.Layout
import Data.Vect

%default total

--------------------------------------------------------------------------------
-- C-ABI Compliance of Concrete Layouts
--------------------------------------------------------------------------------

||| The TechnicalElement struct layout is C-ABI compliant: every field offset
||| is an exact multiple of that field's alignment. Each DivideBy witness is
||| built directly (field.offset = k * field.alignment), since multiplication
||| reduces during typechecking while division does not.
export
technicalElementCompliant : CABICompliant Layout.technicalElementLayout
technicalElementCompliant =
CABIOk Layout.technicalElementLayout
(ConsField _ _ (DivideBy 0 Refl) -- element_type: offset 0 = 0 * 1
(ConsField _ _ (DivideBy 1 Refl) -- subtype: offset 1 = 1 * 1
(ConsField _ _ (DivideBy 2 Refl) -- rotation: offset 2 = 2 * 1
(ConsField _ _ (DivideBy 3 Refl) -- level: offset 3 = 3 * 1
(ConsField _ _ (DivideBy 1 Refl) -- base_value: offset 4 = 1 * 4
(ConsField _ _ (DivideBy 8 Refl) -- goe: offset 8 = 8 * 1
(ConsField _ _ (DivideBy 9 Refl) -- padding: offset 9 = 9 * 1
(ConsField _ _ (DivideBy 3 Refl) -- goe_value: offset 12 = 3 * 4
NoFields))))))))

||| The ProgramScore struct layout is C-ABI compliant.
export
programScoreCompliant : CABICompliant Layout.programScoreLayout
programScoreCompliant =
CABIOk Layout.programScoreLayout
(ConsField _ _ (DivideBy 0 Refl) -- total_base: offset 0 = 0 * 4
(ConsField _ _ (DivideBy 1 Refl) -- total_goe: offset 4 = 1 * 4
(ConsField _ _ (DivideBy 2 Refl) -- deductions: offset 8 = 2 * 4
(ConsField _ _ (DivideBy 12 Refl) -- pcs_skating_skills: offset 12 = 12 * 1
(ConsField _ _ (DivideBy 13 Refl) -- pcs_transitions: offset 13 = 13 * 1
(ConsField _ _ (DivideBy 14 Refl) -- pcs_performance: offset 14 = 14 * 1
(ConsField _ _ (DivideBy 15 Refl) -- pcs_composition: offset 15 = 15 * 1
(ConsField _ _ (DivideBy 16 Refl) -- pcs_interpretation: offset 16 = 16 * 1
(ConsField _ _ (DivideBy 17 Refl) -- padding: offset 17 = 17 * 1
(ConsField _ _ (DivideBy 5 Refl) -- component_factor: offset 20 = 5 * 4
(ConsField _ _ (DivideBy 6 Refl) -- total_segment: offset 24 = 6 * 4
NoFields)))))))))))

--------------------------------------------------------------------------------
-- Result-Code Encoding
--------------------------------------------------------------------------------

||| The success result encodes to the C integer 0.
export
okIsZero : resultToInt Ok = 0
okIsZero = Refl

||| The result-code encoding is injective at the two boundary cases we rely on
||| at the FFI layer: Ok and Error encode to distinct integers.
export
okNotError : Not (resultToInt Ok = resultToInt Error)
okNotError = \case Refl impossible

||| RuleViolation pins to 5, the highest defined error code.
export
ruleViolationIsFive : resultToInt RuleViolation = 5
ruleViolationIsFive = Refl
Loading
Loading