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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ Thumbs.db
/target/
/_build/
/build/
**/build/
*.ttc
*.ttm
/dist/
/out/

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ module Betlangiser.ABI.Foreign

import Betlangiser.ABI.Types
import Betlangiser.ABI.Layout
import Data.So

%default total

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ module Betlangiser.ABI.Layout
import Betlangiser.ABI.Types
import Data.Vect
import Data.So
import Data.Nat
import Decidable.Equality

%default total

Expand All @@ -29,24 +31,39 @@ 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
data Divides : Nat -> Nat -> Type where
DivideBy : (k : Nat) -> {n : Nat} -> {m : Nat} -> (m = k * n) -> Divides n m

||| Sound decision procedure for divisibility.
||| For n = S k, the quotient q = div m (S k) is tested by checking
||| m = q * (S k) via decidable equality on Nat. Division does not reduce
||| during typechecking, so we obtain the witness by an explicit equality test.
public export
decDivides : (n : Nat) -> (m : Nat) -> Maybe (Divides n m)
decDivides Z _ = Nothing
decDivides (S k) m =
let q = div m (S k) in
case decEq m (q * (S k)) of
Yes prf => Just (DivideBy q prf)
No _ => Nothing

||| Round up to next alignment boundary
public export
alignUp : (size : Nat) -> (alignment : Nat) -> Nat
alignUp size alignment =
size + paddingFor size alignment

||| Proof that alignUp produces aligned result
||| Decide whether alignUp produced an aligned result.
||| Soundly returns a divisibility witness when the rounded-up size is
||| genuinely a multiple of the alignment (it always is for align > 0, but
||| we obtain the witness via the decision procedure rather than asserting it).
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
alignUpCorrect : (size : Nat) -> (align : Nat) -> Maybe (Divides align (alignUp size align))
alignUpCorrect size align = decDivides align (alignUp size align)

--------------------------------------------------------------------------------
-- Struct Field Layout
Expand All @@ -70,7 +87,8 @@ nextFieldOffset f = alignUp (f.offset + f.size) f.alignment
public export
record StructLayout where
constructor MkStructLayout
fields : Vect n Field
{0 fieldCount : Nat}
fields : Vect fieldCount Field
totalSize : Nat
alignment : Nat
{auto 0 sizeCorrect : So (totalSize >= sum (map (\f => f.size) fields))}
Expand Down Expand Up @@ -102,7 +120,10 @@ verifyLayout : (fields : Vect n Field) -> (align : Nat) -> Either String StructL
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)
Yes prf =>
case decDivides align size of
Just dvd => Right (MkStructLayout fields size align {sizeCorrect = prf} {aligned = dvd})
Nothing => Left "Total size not aligned"
No _ => Left "Invalid struct size"

--------------------------------------------------------------------------------
Expand Down Expand Up @@ -135,6 +156,8 @@ distributionLayout =
]
40 -- Total size: 40 bytes
8 -- Alignment: 8 bytes
{sizeCorrect = Oh}
{aligned = DivideBy 5 Refl}

--------------------------------------------------------------------------------
-- Sample Buffer Layout
Expand Down Expand Up @@ -166,6 +189,8 @@ sampleBufferLayout =
]
56 -- Total size: 56 bytes
8 -- Alignment: 8 bytes
{sizeCorrect = Oh}
{aligned = DivideBy 7 Refl}

--------------------------------------------------------------------------------
-- Confidence Interval Layout
Expand All @@ -189,6 +214,8 @@ confidenceIntervalLayout =
]
24 -- Total size: 24 bytes
8 -- Alignment: 8 bytes
{sizeCorrect = Oh}
{aligned = DivideBy 3 Refl}

--------------------------------------------------------------------------------
-- Ternary Bool Array Layout
Expand All @@ -211,6 +238,8 @@ ternaryArrayLayout =
]
16 -- Total size: 16 bytes
8 -- Alignment: 8 bytes
{sizeCorrect = Oh}
{aligned = DivideBy 2 Refl}

--------------------------------------------------------------------------------
-- Platform-Specific Layouts
Expand Down Expand Up @@ -241,26 +270,65 @@ data CABICompliant : StructLayout -> Type where
FieldsAligned layout.fields ->
CABICompliant layout

||| Check if layout follows C ABI
||| Sound decision procedure: build a FieldsAligned witness for a Vect of
||| fields by checking, for each field, that its alignment divides its offset.
||| Returns Nothing if any field is misaligned.
public export
decFieldsAligned : (fields : Vect n Field) -> Maybe (FieldsAligned fields)
decFieldsAligned [] = Just NoFields
decFieldsAligned (f :: fs) =
case decDivides f.alignment f.offset of
Just dvd =>
case decFieldsAligned fs of
Just rest => Just (ConsField f fs dvd rest)
Nothing => Nothing
Nothing => Nothing

||| Check if layout follows C ABI by deciding field alignment soundly.
public export
checkCABI : (layout : StructLayout) -> Either String (CABICompliant layout)
checkCABI layout =
Right (CABIOk layout ?fieldsAlignedProof)

||| Proof that distribution layout is valid
case decFieldsAligned layout.fields of
Just prf => Right (CABIOk layout prf)
Nothing => Left "Struct fields are not correctly aligned"

||| Proof that distribution layout is valid.
||| Each field offset is a literal multiple of its alignment, so the
||| divisibility witnesses are built directly (multiplication reduces during
||| typechecking; division does not).
export
distributionLayoutValid : CABICompliant distributionLayout
distributionLayoutValid = CABIOk distributionLayout ?distributionFieldsAligned
distributionLayoutValid : CABICompliant Layout.distributionLayout
distributionLayoutValid =
CABIOk distributionLayout
(ConsField _ _ (DivideBy 0 Refl)
(ConsField _ _ (DivideBy 1 Refl)
(ConsField _ _ (DivideBy 1 Refl)
(ConsField _ _ (DivideBy 2 Refl)
(ConsField _ _ (DivideBy 3 Refl)
(ConsField _ _ (DivideBy 8 Refl)
(ConsField _ _ (DivideBy 9 Refl) NoFields)))))))

||| Proof that sample buffer layout is valid
export
sampleBufferLayoutValid : CABICompliant sampleBufferLayout
sampleBufferLayoutValid = CABIOk sampleBufferLayout ?sampleBufferFieldsAligned
sampleBufferLayoutValid : CABICompliant Layout.sampleBufferLayout
sampleBufferLayoutValid =
CABIOk sampleBufferLayout
(ConsField _ _ (DivideBy 0 Refl)
(ConsField _ _ (DivideBy 1 Refl)
(ConsField _ _ (DivideBy 2 Refl)
(ConsField _ _ (DivideBy 3 Refl)
(ConsField _ _ (DivideBy 4 Refl)
(ConsField _ _ (DivideBy 5 Refl)
(ConsField _ _ (DivideBy 6 Refl) NoFields)))))))

||| Proof that confidence interval layout is valid
export
confidenceIntervalLayoutValid : CABICompliant confidenceIntervalLayout
confidenceIntervalLayoutValid = CABIOk confidenceIntervalLayout ?confidenceIntervalFieldsAligned
confidenceIntervalLayoutValid : CABICompliant Layout.confidenceIntervalLayout
confidenceIntervalLayoutValid =
CABIOk confidenceIntervalLayout
(ConsField _ _ (DivideBy 0 Refl)
(ConsField _ _ (DivideBy 1 Refl)
(ConsField _ _ (DivideBy 2 Refl) NoFields)))

--------------------------------------------------------------------------------
-- Offset Calculation
Expand All @@ -274,7 +342,13 @@ 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.
||| This is not universally true for arbitrary fields, so it is decided at
||| runtime via `choose` and only yields the witness when it actually holds.
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
110 changes: 110 additions & 0 deletions src/interface/abi/Betlangiser/ABI/Proofs.idr
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
-- 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 Betlangiser
|||
||| This module collects genuine, machine-checked theorems about the
||| betlangiser ABI: that each concrete C-struct layout is C-ABI compliant
||| (every field offset is a multiple of its alignment), and that the
||| result-code encoding pins the success code to zero.
|||
||| Every proof below reduces by computation alone — no holes, no postulates,
||| no `believe_me`. The divisibility witnesses are built DIRECTLY (each field
||| offset is a literal `k * alignment`, and multiplication reduces during
||| typechecking) rather than via the runtime decision procedure.

module Betlangiser.ABI.Proofs

import Betlangiser.ABI.Types
import Betlangiser.ABI.Layout
import Data.Vect

%default total

--------------------------------------------------------------------------------
-- Layout Compliance Theorems
--------------------------------------------------------------------------------

||| The Distribution C-struct layout is C-ABI compliant: every field offset
||| is an exact multiple of that field's alignment.
||| Offsets/alignments: tag 0/4, _pad0 4/4, param1 8/8, param2 16/8,
||| custom_ptr 24/8, custom_len 32/4, _pad1 36/4.
export
distributionCompliant : CABICompliant Layout.distributionLayout
distributionCompliant =
CABIOk Layout.distributionLayout
(ConsField _ _ (DivideBy 0 Refl)
(ConsField _ _ (DivideBy 1 Refl)
(ConsField _ _ (DivideBy 1 Refl)
(ConsField _ _ (DivideBy 2 Refl)
(ConsField _ _ (DivideBy 3 Refl)
(ConsField _ _ (DivideBy 8 Refl)
(ConsField _ _ (DivideBy 9 Refl) NoFields)))))))

||| The sample-buffer C-struct layout is C-ABI compliant.
||| All seven fields are 8 bytes at offsets 0,8,16,24,32,40,48 with align 8.
export
sampleBufferCompliant : CABICompliant Layout.sampleBufferLayout
sampleBufferCompliant =
CABIOk Layout.sampleBufferLayout
(ConsField _ _ (DivideBy 0 Refl)
(ConsField _ _ (DivideBy 1 Refl)
(ConsField _ _ (DivideBy 2 Refl)
(ConsField _ _ (DivideBy 3 Refl)
(ConsField _ _ (DivideBy 4 Refl)
(ConsField _ _ (DivideBy 5 Refl)
(ConsField _ _ (DivideBy 6 Refl) NoFields)))))))

||| The confidence-interval C-struct layout is C-ABI compliant.
||| Three 8-byte doubles at offsets 0,8,16 with align 8.
export
confidenceIntervalCompliant : CABICompliant Layout.confidenceIntervalLayout
confidenceIntervalCompliant =
CABIOk Layout.confidenceIntervalLayout
(ConsField _ _ (DivideBy 0 Refl)
(ConsField _ _ (DivideBy 1 Refl)
(ConsField _ _ (DivideBy 2 Refl) NoFields)))

||| The ternary-array C-struct layout is C-ABI compliant.
||| Two 8-byte fields at offsets 0,8 with align 8.
export
ternaryArrayCompliant : CABICompliant Layout.ternaryArrayLayout
ternaryArrayCompliant =
CABIOk Layout.ternaryArrayLayout
(ConsField _ _ (DivideBy 0 Refl)
(ConsField _ _ (DivideBy 1 Refl) NoFields))

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

||| The success result code encodes to the integer 0, as required by the
||| C-ABI convention (zero means success).
export
okIsZero : resultToInt Ok = 0
okIsZero = Refl

||| The result-code encoding is injective on the two codes that matter most
||| at the boundary: success (0) is distinct from the generic error (1).
||| Pinned here so a future re-ordering of the `Result` enum cannot silently
||| collide success with an error.
export
okNotError : Not (resultToInt Ok = resultToInt Error)
okNotError = \case Refl impossible

--------------------------------------------------------------------------------
-- Ternary Logic Theorems
--------------------------------------------------------------------------------

||| Kleene NOT is self-inverse on the indeterminate value: re-exported as a
||| concrete, fully-applied theorem (no universally-quantified variable) so it
||| witnesses a closed fact about the ABI's Unknown encoding.
export
notUnknownIsUnknown : ternaryNot TUnknown = TUnknown
notUnknownIsUnknown = Refl

||| Kleene AND is annihilated by False regardless of the other operand being
||| the indeterminate value — the strong-falsity law at the ABI boundary.
export
falseAndUnknownIsFalse : ternaryAnd TFalse TUnknown = TFalse
falseAndUnknownIsFalse = Refl
Loading
Loading