I decided to try using SPARK after I had already started the design of the ACO library and implemented one encryption algorithm (Blowfish). Once I decided to use SPARK for the core algorithms, I knew that I needed to refactor my existing code to make it conform to SPARK rules. I wasn't sure how that was going to work out so I felt the refactoring had to be done right away to ensure that it was even going to be possible.
I just completed the refactoring yesterday. I had to rearrange a number of packages, as expected, but overall it went fine and the resulting code is quite reasonable. The main tricky aspect was related to the intrinsic shift procedures. GNAT provides Shift_Left and Shift_Right procedures as "intrinsics" in package Interfaces. This means that they are compiled directly into single machine instructions without procedure call overhead. However,
I didn't want to use the types defined in package Interfaces because I wanted to use representation clauses to define the properties of my basic types precisely (specifically, the size). Such clauses must appear in the same package as where the type is defined, and I don't have the option of editing the library package Interfaces.
However, my benchmark experiments with Blowfish showed that it was very important to performance to use the intrinsic procedures. Correctness may have first priority in ACO, but performance is important too. I didn't feel like I could worry about this matter later. Fortunately GNAT allows me to specify procedures of my own as intrinsic provided the procedures have the proper names and operate on types with appropriate properties. This allowed me to define Shift_Left and Shift_Right procedures for my type ACO.Octet while retaining the speed of the intrinsic operations.
The complication that arose was that I also wanted to do the same for the types ACO.Double_Octet, ACO.Quadruple_Octet, and ACO.Octuple_Octet. To make all the necessary shifts intrinsic I had to use particular procedure names. However, SPARK does not allow overloading of procedure names so this created a conflict between SPARK rules and my desire for an efficient implementation. To work around this, I created separate packages for the operations on each type. This caused something of a proliferation of packages, but it worked out to be a reasonable approach.
I also factored out the core algorithms from the object oriented interfaces so that the core algorithms could be made SPARKable while still retaining the convenient interfaces for users who want them. This went smoothly. There is now slightly more overhead involved when using the object oriented interfaces than there used to be because of the extra layer of procedure calling involved. However, benchmark tests on Blowfish show only a slight slowdown due to this effect. The beauty of this approach is that while the main algorithms are (will be) SPARKable, the high level interfaces made available by the library can continue to use full Ada 2005.
So far I have only applied flow annotations to the packages that I intend to be SPARKable. This helps increase confidence in those packages somewhat, but I believe the real gain in reliability will come when I start using the proof annotations. I intend to put that off for a while, however, until the library has been fleshed out a bit more. Yet even the flow annotations are useful. They forced me to properly check error status on all called procedures; something I hadn't been doing consistently before.
I feel that now the basic structure of ACO is complete and what remains is mostly a matter of building on that structure with actual algorithm implementations. I will probably continue by implementing SHA1 next.