-----------------------------------------------------------------------------
-- |
-- Module      :  Data.SBV.Examples.CodeGeneration.Fibonacci
-- Copyright   :  (c) Lee Pike, Levent Erkok
-- License     :  BSD3
-- Maintainer  :  erkokl@gmail.com
-- Stability   :  experimental
--
-- 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.
-----------------------------------------------------------------------------

module Data.SBV.Examples.CodeGeneration.Fibonacci where

import Data.SBV

-----------------------------------------------------------------------------
-- * A naive implementation
-----------------------------------------------------------------------------

-- | This is a naive implementation of fibonacci, and will work fine (albeit slow)
-- for concrete inputs:
--
-- >>> map fib0 [0..6]
-- [0 :: SWord64,1 :: SWord64,1 :: SWord64,2 :: SWord64,3 :: SWord64,5 :: SWord64,8 :: SWord64]
--
-- 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.)
fib0 :: SWord64 -> SWord64
fib0 n = ite (n .== 0 ||| n .== 1)
             n
             (fib0 (n-1) + fib0 (n-2))

-----------------------------------------------------------------------------
-- * Using a recursion depth, and accumulating parameters
-----------------------------------------------------------------------------

{- $genLookup
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.
-}

-- | The recursion-depth limited version of fibonacci. Limiting the maximum number to be 20, we can say:
--
-- >>> map (fib1 20) [0..6]
-- [0 :: SWord64,1 :: SWord64,1 :: SWord64,2 :: SWord64,3 :: SWord64,5 :: SWord64,8 :: SWord64]
--
-- 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.
--
-- A note on modular arithmetic: The 64-bit word we use to represent the values will of course
-- eventually overflow, beware! Fibonacci is a fast growing function..
fib1 :: SWord64 -> SWord64 -> SWord64
fib1 top n = fib' 0 1 0
  where fib' :: SWord64 -> SWord64 -> SWord64 -> SWord64
        fib' prev' prev m = ite (m .== top ||| m .== n)          -- did we reach recursion depth, or the index we're looking for
                                prev'                            -- stop and return the result
                                (fib' prev (prev' + prev) (m+1)) -- otherwise recurse

-- | 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:
--
-- > SWord64 fib1(const SWord64 x)
-- > {
-- >   const SWord64 s0 = x;
-- >   const SBool   s2 = s0 == 0x0000000000000000ULL;
-- >   const SBool   s4 = s0 == 0x0000000000000001ULL;
-- >   const SBool   s6 = s0 == 0x0000000000000002ULL;
-- >   const SBool   s8 = s0 == 0x0000000000000003ULL;
-- >   const SBool   s10 = s0 == 0x0000000000000004ULL;
-- >   const SBool   s12 = s0 == 0x0000000000000005ULL;
-- >   const SBool   s14 = s0 == 0x0000000000000006ULL;
-- >   const SBool   s17 = s0 == 0x0000000000000007ULL;
-- >   const SBool   s19 = s0 == 0x0000000000000008ULL;
-- >   const SBool   s22 = s0 == 0x0000000000000009ULL;
-- >   const SWord64 s25 = s22 ? 0x0000000000000022ULL : 0x0000000000000037ULL;
-- >   const SWord64 s26 = s19 ? 0x0000000000000015ULL : s25;
-- >   const SWord64 s27 = s17 ? 0x000000000000000dULL : s26;
-- >   const SWord64 s28 = s14 ? 0x0000000000000008ULL : s27;
-- >   const SWord64 s29 = s12 ? 0x0000000000000005ULL : s28;
-- >   const SWord64 s30 = s10 ? 0x0000000000000003ULL : s29;
-- >   const SWord64 s31 = s8 ? 0x0000000000000002ULL : s30;
-- >   const SWord64 s32 = s6 ? 0x0000000000000001ULL : s31;
-- >   const SWord64 s33 = s4 ? 0x0000000000000001ULL : s32;
-- >   const SWord64 s34 = s2 ? 0x0000000000000000ULL : s33;
-- >   
-- >   return s34;
-- > }
genFib1 :: SWord64 -> IO ()
genFib1 top = compileToC Nothing "fib1" "" $ do
        x <- cgInput "x"
        cgReturn $ fib1 top x

-----------------------------------------------------------------------------
-- * Generating a look-up table
-----------------------------------------------------------------------------

{- $genLookup
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.
-}

-- | Compute the fibonacci numbers statically at /code-generation/ time and
-- put them in a table, accessed by the 'select' call. 
fib2 :: SWord64 -> SWord64 -> SWord64
fib2 top = select table 0
  where table = map (fib1 top) [0 .. top]

-- | 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@.)
--
-- > SWord64 fibLookup(const SWord64 x)
-- > {
-- >   const SWord64 s0 = x;
-- >   static const SWord64 table0[] = {
-- >       0x0000000000000000ULL, 0x0000000000000001ULL,
-- >       0x0000000000000001ULL, 0x0000000000000002ULL,
-- >       0x0000000000000003ULL, 0x0000000000000005ULL,
-- >       0x0000000000000008ULL, 0x000000000000000dULL,
-- >       0x0000000000000015ULL, 0x0000000000000022ULL,
-- >       0x0000000000000037ULL, 0x0000000000000059ULL,
-- >       0x0000000000000090ULL, 0x00000000000000e9ULL,
-- >       0x0000000000000179ULL, 0x0000000000000262ULL,
-- >       0x00000000000003dbULL, 0x000000000000063dULL,
-- >       0x0000000000000a18ULL, 0x0000000000001055ULL,
-- >       0x0000000000001a6dULL, 0x0000000000002ac2ULL,
-- >       0x000000000000452fULL, 0x0000000000006ff1ULL,
-- >       0x000000000000b520ULL, 0x0000000000012511ULL,
-- >       0x000000000001da31ULL, 0x000000000002ff42ULL,
-- >       0x000000000004d973ULL, 0x000000000007d8b5ULL,
-- >       0x00000000000cb228ULL, 0x0000000000148addULL,
-- >       0x0000000000213d05ULL, 0x000000000035c7e2ULL,
-- >       0x00000000005704e7ULL, 0x00000000008cccc9ULL,
-- >       0x0000000000e3d1b0ULL, 0x0000000001709e79ULL,
-- >       0x0000000002547029ULL, 0x0000000003c50ea2ULL,
-- >       0x0000000006197ecbULL, 0x0000000009de8d6dULL,
-- >       0x000000000ff80c38ULL, 0x0000000019d699a5ULL,
-- >       0x0000000029cea5ddULL, 0x0000000043a53f82ULL,
-- >       0x000000006d73e55fULL, 0x00000000b11924e1ULL,
-- >       0x000000011e8d0a40ULL, 0x00000001cfa62f21ULL,
-- >       0x00000002ee333961ULL, 0x00000004bdd96882ULL,
-- >       0x00000007ac0ca1e3ULL, 0x0000000c69e60a65ULL,
-- >       0x0000001415f2ac48ULL, 0x000000207fd8b6adULL,
-- >       0x0000003495cb62f5ULL, 0x0000005515a419a2ULL,
-- >       0x00000089ab6f7c97ULL, 0x000000dec1139639ULL,
-- >       0x000001686c8312d0ULL, 0x000002472d96a909ULL,
-- >       0x000003af9a19bbd9ULL, 0x000005f6c7b064e2ULL, 0x000009a661ca20bbULL
-- >   };
-- >   const SWord64 s65 = s0 >= 65 ? 0x0000000000000000ULL : table0[s0];
-- >   
-- >   return s65;
-- > }
genFib2 :: SWord64 -> IO ()
genFib2 top = compileToC Nothing "fibLookup" "" $ do
        cgPerformRTCs True       -- protect against potential overflow, our table is not big enough
        x <- cgInput "x"
        cgReturn $ fib2 top x
