|
| 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 | +||| Flagship semantic proof for Futharkiser (Idris2 ABI Layer 2). |
| 5 | +||| |
| 6 | +||| Futharkiser compiles annotated array operations to GPU kernels via Futhark. |
| 7 | +||| The single most important optimisation that justifies that pipeline is |
| 8 | +||| MAP FUSION: rewriting `map f (map g xs)` into `map (f . g) xs`. Futhark's |
| 9 | +||| whole performance story rests on fusing chained SOACs so that intermediate |
| 10 | +||| arrays are never materialised on the device. If fusion is not semantically |
| 11 | +||| sound, the generated GPU kernel computes the WRONG answer. |
| 12 | +||| |
| 13 | +||| This module gives a faithful, length-indexed model of `map` over a Futhark |
| 14 | +||| array (a `Vect n a`) and proves, as a real propositional equality, that: |
| 15 | +||| |
| 16 | +||| (1) map preserves length : mapV f xs : Vect n b (by construction) |
| 17 | +||| (2) FUSION LAW : mapV f (mapV g xs) = mapV (f . g) xs |
| 18 | +||| (3) IDENTITY LAW (functor identity) : mapV id xs = xs |
| 19 | +||| |
| 20 | +||| (2) is the headline. Both are proved by structural induction with no |
| 21 | +||| escape hatches (no believe_me / postulate / assert). A certifier maps a |
| 22 | +||| concrete fused/unfused pair to a ProofStatus-like verdict, and there is a |
| 23 | +||| positive control (a witnessed instance of the law) plus a negative control |
| 24 | +||| (a machine-checked refutation of a DELIBERATELY wrong fusion). |
| 25 | + |
| 26 | +module Futharkiser.ABI.Semantics |
| 27 | + |
| 28 | +import Data.Vect |
| 29 | +import Decidable.Equality |
| 30 | + |
| 31 | +%default total |
| 32 | + |
| 33 | +-------------------------------------------------------------------------------- |
| 34 | +-- Faithful model of the Futhark `map` SOAC over a length-indexed array. |
| 35 | +-------------------------------------------------------------------------------- |
| 36 | + |
| 37 | +||| `mapV` is our model of the Futhark `map` second-order array combinator. |
| 38 | +||| The output Vect has exactly the same length `n` as the input: the type |
| 39 | +||| itself is the proof that `map` preserves array length / shape, which is the |
| 40 | +||| rank-and-length invariant the GPU codegen relies on. |
| 41 | +public export |
| 42 | +mapV : (a -> b) -> Vect n a -> Vect n b |
| 43 | +mapV f [] = [] |
| 44 | +mapV f (x :: xs) = f x :: mapV f xs |
| 45 | + |
| 46 | +-------------------------------------------------------------------------------- |
| 47 | +-- Length preservation (made explicit as a value-level fact). |
| 48 | +-------------------------------------------------------------------------------- |
| 49 | + |
| 50 | +||| `mapV` preserves length. This is true by construction (the result type is |
| 51 | +||| `Vect n b` for input `Vect n a`), and we record it as an explicit equality |
| 52 | +||| on the runtime `length`. |
| 53 | +public export |
| 54 | +mapPreservesLength : (f : a -> b) -> (xs : Vect n a) -> |
| 55 | + length (mapV f xs) = length xs |
| 56 | +mapPreservesLength f [] = Refl |
| 57 | +mapPreservesLength f (x :: xs) = cong S (mapPreservesLength f xs) |
| 58 | + |
| 59 | +-------------------------------------------------------------------------------- |
| 60 | +-- THE HEADLINE: the map-fusion law (sound fusion of chained SOACs). |
| 61 | +-------------------------------------------------------------------------------- |
| 62 | + |
| 63 | +||| Map fusion law: applying `map g` then `map f` is exactly the same array as |
| 64 | +||| applying `map (f . g)` once. This is what licenses Futhark's kernel-fusion |
| 65 | +||| optimisation — eliminating the intermediate device array is observationally |
| 66 | +||| equivalent to the unfused pipeline, element for element. |
| 67 | +||| |
| 68 | +||| Proved by structural induction on the input vector; the cons case rewrites |
| 69 | +||| under the inductive hypothesis. No axioms. |
| 70 | +public export |
| 71 | +mapFusion : (f : b -> c) -> (g : a -> b) -> (xs : Vect n a) -> |
| 72 | + mapV f (mapV g xs) = mapV (\x => f (g x)) xs |
| 73 | +mapFusion f g [] = Refl |
| 74 | +mapFusion f g (x :: xs) = cong (f (g x) ::) (mapFusion f g xs) |
| 75 | + |
| 76 | +||| Functor identity law: fusing with the identity function is a no-op. Together |
| 77 | +||| with `mapFusion` this makes `mapV` a lawful functor, so any chain of fused |
| 78 | +||| maps in the generated kernel is equal to the unfused source pipeline. |
| 79 | +public export |
| 80 | +mapIdentity : (xs : Vect n a) -> mapV (\x => x) xs = xs |
| 81 | +mapIdentity [] = Refl |
| 82 | +mapIdentity (x :: xs) = cong (x ::) (mapIdentity xs) |
| 83 | + |
| 84 | +-------------------------------------------------------------------------------- |
| 85 | +-- Fusion verdict + soundness certifier. |
| 86 | +-------------------------------------------------------------------------------- |
| 87 | + |
| 88 | +||| Verdict of checking a proposed fusion rewrite. |
| 89 | +public export |
| 90 | +data FusionVerdict = FusionSound | FusionUnsound |
| 91 | + |
| 92 | +public export |
| 93 | +DecEq FusionVerdict where |
| 94 | + decEq FusionSound FusionSound = Yes Refl |
| 95 | + decEq FusionUnsound FusionUnsound = Yes Refl |
| 96 | + decEq FusionSound FusionUnsound = No (\case Refl impossible) |
| 97 | + decEq FusionUnsound FusionSound = No (\case Refl impossible) |
| 98 | + |
| 99 | +||| Certify that fusing `map f . map g` into `map (f . g)` over a CONCRETE |
| 100 | +||| witness vector produces the identical array. Because the fusion law holds |
| 101 | +||| for ALL vectors, the certifier can always return `FusionSound` and discharge |
| 102 | +||| its own soundness obligation with `mapFusion`. |
| 103 | +public export |
| 104 | +certifyFusion : (f : b -> c) -> (g : a -> b) -> (xs : Vect n a) -> FusionVerdict |
| 105 | +certifyFusion f g xs = FusionSound |
| 106 | + |
| 107 | +||| Soundness of the certifier: whenever it reports `FusionSound`, the fused and |
| 108 | +||| unfused pipelines really are the same array. |
| 109 | +public export |
| 110 | +certifyFusionSound : (f : b -> c) -> (g : a -> b) -> (xs : Vect n a) -> |
| 111 | + certifyFusion f g xs = FusionSound -> |
| 112 | + mapV f (mapV g xs) = mapV (\x => f (g x)) xs |
| 113 | +certifyFusionSound f g xs _ = mapFusion f g xs |
| 114 | + |
| 115 | +-------------------------------------------------------------------------------- |
| 116 | +-- POSITIVE CONTROL: an inhabited witness of the fusion law on real data. |
| 117 | +-------------------------------------------------------------------------------- |
| 118 | + |
| 119 | +||| Concrete input array. |
| 120 | +public export |
| 121 | +sampleArray : Vect 3 Nat |
| 122 | +sampleArray = [10, 20, 30] |
| 123 | + |
| 124 | +||| `g = (+1)`, `f = (*2)`. The fused and unfused pipelines must agree. |
| 125 | +||| This is a fully evaluated, machine-checked instance of the headline law: |
| 126 | +||| map (*2) (map (+1) [10,20,30]) = map (\x => (x+1)*2) [10,20,30] = [22,42,62] |
| 127 | +public export |
| 128 | +fusionWitness : mapV (\x => x * 2) (mapV (\x => x + 1) Semantics.sampleArray) |
| 129 | + = mapV (\x => (x + 1) * 2) Semantics.sampleArray |
| 130 | +fusionWitness = Refl |
| 131 | + |
| 132 | +||| The same instance routed through the general theorem (not just `Refl`), |
| 133 | +||| showing the proof term is the genuine inductive one. |
| 134 | +public export |
| 135 | +fusionWitnessViaTheorem : mapV (\x => x * 2) (mapV (\x => x + 1) Semantics.sampleArray) |
| 136 | + = mapV (\x => (x + 1) * 2) Semantics.sampleArray |
| 137 | +fusionWitnessViaTheorem = mapFusion (\x => x * 2) (\x => x + 1) Semantics.sampleArray |
| 138 | + |
| 139 | +-------------------------------------------------------------------------------- |
| 140 | +-- NEGATIVE CONTROL: a DELIBERATELY wrong fusion is refuted (non-vacuity). |
| 141 | +-------------------------------------------------------------------------------- |
| 142 | + |
| 143 | +||| A bogus "fusion" that drops the inner map's effect: claiming |
| 144 | +||| map (*2) (map (+1) xs) = map (*2) xs |
| 145 | +||| This is FALSE for our sample data, and we prove the inequality. |
| 146 | +||| If this `Not (...)` were inhabitable, the fusion law would be vacuous. |
| 147 | +public export |
| 148 | +wrongFusionRefuted : |
| 149 | + Not (mapV (\x => x * 2) (mapV (\x => x + 1) Semantics.sampleArray) |
| 150 | + = mapV (\x => x * 2) Semantics.sampleArray) |
| 151 | +wrongFusionRefuted Refl impossible |
0 commit comments