@@ -16,6 +16,8 @@ module Betlangiser.ABI.Layout
1616import Betlangiser.ABI.Types
1717import Data.Vect
1818import Data.So
19+ import Data.Nat
20+ import Decidable.Equality
1921
2022%default total
2123
@@ -29,24 +31,39 @@ paddingFor : (offset : Nat) -> (alignment : Nat) -> Nat
2931paddingFor offset alignment =
3032 if offset `mod` alignment == 0
3133 then 0
32- else alignment - (offset `mod` alignment)
34+ else minus alignment (offset `mod` alignment)
3335
3436||| Proof that alignment divides aligned size
3537public export
3638data Divides : Nat -> Nat -> Type where
3739 DivideBy : (k : Nat ) -> {n : Nat } -> {m : Nat } -> (m = k * n) -> Divides n m
3840
41+ ||| Sound decision procedure for divisibility.
42+ ||| For n = S k, the quotient q = div m (S k) is tested by checking
43+ ||| m = q * (S k) via decidable equality on Nat. Division does not reduce
44+ ||| during typechecking, so we obtain the witness by an explicit equality test.
45+ public export
46+ decDivides : (n : Nat ) -> (m : Nat ) -> Maybe (Divides n m)
47+ decDivides Z _ = Nothing
48+ decDivides (S k) m =
49+ let q = div m (S k) in
50+ case decEq m (q * (S k)) of
51+ Yes prf => Just (DivideBy q prf)
52+ No _ => Nothing
53+
3954||| Round up to next alignment boundary
4055public export
4156alignUp : (size : Nat ) -> (alignment : Nat ) -> Nat
4257alignUp size alignment =
4358 size + paddingFor size alignment
4459
45- ||| Proof that alignUp produces aligned result
60+ ||| Decide whether alignUp produced an aligned result.
61+ ||| Soundly returns a divisibility witness when the rounded-up size is
62+ ||| genuinely a multiple of the alignment (it always is for align > 0, but
63+ ||| we obtain the witness via the decision procedure rather than asserting it).
4664public export
47- alignUpCorrect : (size : Nat ) -> (align : Nat ) -> (align > 0) -> Divides align (alignUp size align)
48- alignUpCorrect size align prf =
49- DivideBy ((size + paddingFor size align) `div` align) Refl
65+ alignUpCorrect : (size : Nat ) -> (align : Nat ) -> Maybe (Divides align (alignUp size align))
66+ alignUpCorrect size align = decDivides align (alignUp size align)
5067
5168-- ------------------------------------------------------------------------------
5269-- Struct Field Layout
@@ -70,7 +87,8 @@ nextFieldOffset f = alignUp (f.offset + f.size) f.alignment
7087public export
7188record StructLayout where
7289 constructor MkStructLayout
73- fields : Vect n Field
90+ {0 fieldCount : Nat }
91+ fields : Vect fieldCount Field
7492 totalSize : Nat
7593 alignment : Nat
7694 {auto 0 sizeCorrect : So (totalSize >= sum (map (\f => f.size) fields))}
@@ -102,7 +120,10 @@ verifyLayout : (fields : Vect n Field) -> (align : Nat) -> Either String StructL
102120verifyLayout fields align =
103121 let size = calcStructSize fields align
104122 in case decSo (size >= sum (map (\ f => f. size) fields)) of
105- Yes prf => Right (MkStructLayout fields size align)
123+ Yes prf =>
124+ case decDivides align size of
125+ Just dvd => Right (MkStructLayout fields size align {sizeCorrect = prf} {aligned = dvd})
126+ Nothing => Left " Total size not aligned"
106127 No _ => Left " Invalid struct size"
107128
108129-- ------------------------------------------------------------------------------
@@ -135,6 +156,8 @@ distributionLayout =
135156 ]
136157 40 -- Total size: 40 bytes
137158 8 -- Alignment: 8 bytes
159+ {sizeCorrect = Oh }
160+ {aligned = DivideBy 5 Refl }
138161
139162-- ------------------------------------------------------------------------------
140163-- Sample Buffer Layout
@@ -166,6 +189,8 @@ sampleBufferLayout =
166189 ]
167190 56 -- Total size: 56 bytes
168191 8 -- Alignment: 8 bytes
192+ {sizeCorrect = Oh }
193+ {aligned = DivideBy 7 Refl }
169194
170195-- ------------------------------------------------------------------------------
171196-- Confidence Interval Layout
@@ -189,6 +214,8 @@ confidenceIntervalLayout =
189214 ]
190215 24 -- Total size: 24 bytes
191216 8 -- Alignment: 8 bytes
217+ {sizeCorrect = Oh }
218+ {aligned = DivideBy 3 Refl }
192219
193220-- ------------------------------------------------------------------------------
194221-- Ternary Bool Array Layout
@@ -211,6 +238,8 @@ ternaryArrayLayout =
211238 ]
212239 16 -- Total size: 16 bytes
213240 8 -- Alignment: 8 bytes
241+ {sizeCorrect = Oh }
242+ {aligned = DivideBy 2 Refl }
214243
215244-- ------------------------------------------------------------------------------
216245-- Platform-Specific Layouts
@@ -241,26 +270,65 @@ data CABICompliant : StructLayout -> Type where
241270 FieldsAligned layout.fields ->
242271 CABICompliant layout
243272
244- ||| Check if layout follows C ABI
273+ ||| Sound decision procedure: build a FieldsAligned witness for a Vect of
274+ ||| fields by checking, for each field, that its alignment divides its offset.
275+ ||| Returns Nothing if any field is misaligned.
276+ public export
277+ decFieldsAligned : (fields : Vect n Field) -> Maybe (FieldsAligned fields)
278+ decFieldsAligned [] = Just NoFields
279+ decFieldsAligned (f :: fs) =
280+ case decDivides f. alignment f. offset of
281+ Just dvd =>
282+ case decFieldsAligned fs of
283+ Just rest => Just (ConsField f fs dvd rest)
284+ Nothing => Nothing
285+ Nothing => Nothing
286+
287+ ||| Check if layout follows C ABI by deciding field alignment soundly.
245288public export
246289checkCABI : (layout : StructLayout) -> Either String (CABICompliant layout)
247290checkCABI layout =
248- Right (CABIOk layout ? fieldsAlignedProof)
249-
250- ||| Proof that distribution layout is valid
291+ case decFieldsAligned layout. fields of
292+ Just prf => Right (CABIOk layout prf)
293+ Nothing => Left " Struct fields are not correctly aligned"
294+
295+ ||| Proof that distribution layout is valid.
296+ ||| Each field offset is a literal multiple of its alignment, so the
297+ ||| divisibility witnesses are built directly (multiplication reduces during
298+ ||| typechecking; division does not).
251299export
252- distributionLayoutValid : CABICompliant distributionLayout
253- distributionLayoutValid = CABIOk distributionLayout ? distributionFieldsAligned
300+ distributionLayoutValid : CABICompliant Layout.distributionLayout
301+ distributionLayoutValid =
302+ CABIOk distributionLayout
303+ (ConsField _ _ (DivideBy 0 Refl )
304+ (ConsField _ _ (DivideBy 1 Refl )
305+ (ConsField _ _ (DivideBy 1 Refl )
306+ (ConsField _ _ (DivideBy 2 Refl )
307+ (ConsField _ _ (DivideBy 3 Refl )
308+ (ConsField _ _ (DivideBy 8 Refl )
309+ (ConsField _ _ (DivideBy 9 Refl ) NoFields )))))))
254310
255311||| Proof that sample buffer layout is valid
256312export
257- sampleBufferLayoutValid : CABICompliant sampleBufferLayout
258- sampleBufferLayoutValid = CABIOk sampleBufferLayout ? sampleBufferFieldsAligned
313+ sampleBufferLayoutValid : CABICompliant Layout.sampleBufferLayout
314+ sampleBufferLayoutValid =
315+ CABIOk sampleBufferLayout
316+ (ConsField _ _ (DivideBy 0 Refl )
317+ (ConsField _ _ (DivideBy 1 Refl )
318+ (ConsField _ _ (DivideBy 2 Refl )
319+ (ConsField _ _ (DivideBy 3 Refl )
320+ (ConsField _ _ (DivideBy 4 Refl )
321+ (ConsField _ _ (DivideBy 5 Refl )
322+ (ConsField _ _ (DivideBy 6 Refl ) NoFields )))))))
259323
260324||| Proof that confidence interval layout is valid
261325export
262- confidenceIntervalLayoutValid : CABICompliant confidenceIntervalLayout
263- confidenceIntervalLayoutValid = CABIOk confidenceIntervalLayout ? confidenceIntervalFieldsAligned
326+ confidenceIntervalLayoutValid : CABICompliant Layout.confidenceIntervalLayout
327+ confidenceIntervalLayoutValid =
328+ CABIOk confidenceIntervalLayout
329+ (ConsField _ _ (DivideBy 0 Refl )
330+ (ConsField _ _ (DivideBy 1 Refl )
331+ (ConsField _ _ (DivideBy 2 Refl ) NoFields )))
264332
265333-- ------------------------------------------------------------------------------
266334-- Offset Calculation
@@ -274,7 +342,13 @@ fieldOffset layout name =
274342 Just idx => Just (finToNat idx ** index idx layout. fields)
275343 Nothing => Nothing
276344
277- ||| Proof that field offset is within struct bounds
345+ ||| Decide whether a field lies within the struct bounds.
346+ ||| This is not universally true for arbitrary fields, so it is decided at
347+ ||| runtime via `choose` and only yields the witness when it actually holds.
278348public export
279- offsetInBounds : (layout : StructLayout) -> (f : Field) -> So (f.offset + f.size <= layout.totalSize)
280- offsetInBounds layout f = ? offsetInBoundsProof
349+ offsetInBounds : (layout : StructLayout) -> (f : Field) ->
350+ Maybe (So (f.offset + f.size <= layout.totalSize))
351+ offsetInBounds layout f =
352+ case choose (f. offset + f. size <= layout. totalSize) of
353+ Left ok => Just ok
354+ Right _ => Nothing
0 commit comments