sbv-0.9.18: Symbolic Bit Vectors: Prove bit-precise program properties using SMT solvers.





Computing Fibonacci numbers and generating C code. Inspired by Lee Pike's original implementation, modified for inclusion in the package. It illustrates symbolic termination issues one can have when working with recursive algorithms and how to deal with such, eventually generating good C code.


A naive implementation

fib0 :: SWord32 -> SWord32Source

This is a naive implementation of fibonacci, and will work fine (albeit slow) for concrete inputs:

>>> map fib0 [0..6]
[0 :: SWord32,1 :: SWord32,1 :: SWord32,2 :: SWord32,3 :: SWord32,5 :: SWord32,8 :: SWord32]

However, it is not suitable for doing proofs or generating code, as it is not symbolically terminating when it is called with a symbolic value n. When we recursively call fib0 on n-1 (or n-2), the test against 0 will always explore both branches since the result will be symbolic, hence will not terminate. (An integrated theorem prover can establish termination after a certain number of unrollings, but this would be quite expensive to implement, and would be impractical.)

Using a recursion depth, and accumulating parameters

One way to deal with symbolic termination is to limit the number of recursive calls. In this version, we impose a limit on the index to the function, working correctly upto that limit. If we use a compile-time constant, then SBV's code generator can produce code as the unrolling will eventually stop.

fib1 :: SWord32 -> SWord32 -> SWord32Source

The recursion-depth limited version of fibonacci. Limiting the maximum number to be 20, we can say:

>>> map (fib1 20) [0..6]
[0 :: SWord32,1 :: SWord32,1 :: SWord32,2 :: SWord32,3 :: SWord32,5 :: SWord32,8 :: SWord32]

The function will work correctly, so long as the index we query is at most top, and otherwise will return the value at top. Note that we also use accumulating parameters here for efficiency, although this is orthogonal to the termination concern.

genFib1 :: SWord32 -> IO ()Source

We can generate code for fib1 using the genFib1 action. Note that the generated code will grow larger as we pick larger values of top, but only linearly, thanks to the accumulating parameter trick used by fib1. The following is an excerpt from the code generated for the call genFib1 10, where the code will work correctly for indexes up to 10:

 SWord32 fib1(const SWord32 x)
   const SWord32 s0 = x;
   const SBool   s2 = s0 == 0x00000000UL;
   const SBool   s4 = s0 == 0x00000001UL;
   const SBool   s6 = s0 == 0x00000002UL;
   const SBool   s8 = s0 == 0x00000003UL;
   const SBool   s10 = s0 == 0x00000004UL;
   const SBool   s12 = s0 == 0x00000005UL;
   const SBool   s14 = s0 == 0x00000006UL;
   const SBool   s17 = s0 == 0x00000007UL;
   const SBool   s19 = s0 == 0x00000008UL;
   const SBool   s22 = s0 == 0x00000009UL;
   const SWord32 s25 = s22 ? 0x00000022UL : 0x00000037UL;
   const SWord32 s26 = s19 ? 0x00000015UL : s25;
   const SWord32 s27 = s17 ? 0x0000000dUL : s26;
   const SWord32 s28 = s14 ? 0x00000008UL : s27;
   const SWord32 s29 = s12 ? 0x00000005UL : s28;
   const SWord32 s30 = s10 ? 0x00000003UL : s29;
   const SWord32 s31 = s8 ? 0x00000002UL : s30;
   const SWord32 s32 = s6 ? 0x00000001UL : s31;
   const SWord32 s33 = s4 ? 0x00000001UL : s32;
   const SWord32 s34 = s2 ? 0x00000000UL : s33;
   return s34;

Generating a look-up table

While fib1 generates good C code, we can do much better by taking advantage of the inherent partial-evaluation capabilities of SBV to generate a look-up table, as follows.

fib2 :: SWord32 -> SWord32 -> SWord32Source

Compute the fibonacci numbers statically at code-generation time and put them in a table, accessed by the select call.

genFib2 :: SWord32 -> IO ()Source

Once we have fib2, we can generate the C code straightforwardly. Below is an excerpt from the code that SBV generates for the call genFib2 64. Note that this code is a constant-time look-up table implementation of fibonacci, with no run-time overhead. The index can be made arbitrarily large, naturally. (Note that this function returns 0 if the index is larger than 64, as specified by the call to select with default 0.)

 SWord32 fibLookup(const SWord32 x)
   const SWord32 s0 = x;
   static const SWord32 table0[] = {
       0x00000000UL, 0x00000001UL, 0x00000001UL, 0x00000002UL,
       0x00000003UL, 0x00000005UL, 0x00000008UL, 0x0000000dUL,
       0x00000015UL, 0x00000022UL, 0x00000037UL, 0x00000059UL,
       0x00000090UL, 0x000000e9UL, 0x00000179UL, 0x00000262UL,
       0x000003dbUL, 0x0000063dUL, 0x00000a18UL, 0x00001055UL,
       0x00001a6dUL, 0x00002ac2UL, 0x0000452fUL, 0x00006ff1UL,
       0x0000b520UL, 0x00012511UL, 0x0001da31UL, 0x0002ff42UL,
       0x0004d973UL, 0x0007d8b5UL, 0x000cb228UL, 0x00148addUL,
       0x00213d05UL, 0x0035c7e2UL, 0x005704e7UL, 0x008cccc9UL,
       0x00e3d1b0UL, 0x01709e79UL, 0x02547029UL, 0x03c50ea2UL,
       0x06197ecbUL, 0x09de8d6dUL, 0x0ff80c38UL, 0x19d699a5UL,
       0x29cea5ddUL, 0x43a53f82UL, 0x6d73e55fUL, 0xb11924e1UL,
       0x1e8d0a40UL, 0xcfa62f21UL, 0xee333961UL, 0xbdd96882UL,
       0xac0ca1e3UL, 0x69e60a65UL, 0x15f2ac48UL, 0x7fd8b6adUL,
       0x95cb62f5UL, 0x15a419a2UL, 0xab6f7c97UL, 0xc1139639UL,
       0x6c8312d0UL, 0x2d96a909UL, 0x9a19bbd9UL, 0xc7b064e2UL,
   const SWord32 s65 = s0 >= 65 ? 0x00000000UL : table0[s0];
   return s65;