{-# LANGUAGE TypeOperators #-}
module Main (main) where

import Convert

import LLVM.ExecutionEngine
          (runEngineAccessWithModule, generateFunction, getExecutionFunction)
import LLVM.Util.Optimize (optimizeModule, )
import LLVM.Util.Loop (forLoop, )
import LLVM.Core

import qualified Type.Data.Num.Decimal.Number as Dec
import Type.Data.Num.Decimal.Literal (D16, )

import Control.Monad.IO.Class (liftIO, )
import Control.Monad (liftM2, when, )
import Data.Word (Word32, )

-- Type of vector elements.
type T = Float

-- Number of vector elements.
type N = D16

retAccName, fName :: String
retAccName = "retacc"
fName = "vectest"

cgvec :: CodeGenModule (Function (T -> IO T))
cgvec = do
    -- A global variable that vectest messes with.
    acc <- createNamedGlobal False ExternalLinkage "acc" (constOf (0 :: T))

    -- Return the global variable.
    retAcc <- createNamedFunction ExternalLinkage retAccName $ do
        vacc <- load acc
        ret vacc
    let _ = retAcc :: Function (IO T)  -- Force the type of retAcc.

    -- A function that tests vector operations.
    f <- createNamedFunction ExternalLinkage fName $ \ x -> do

        let v = value (zero :: ConstValue (Vector N T))
            n = Dec.integralFromSingleton (Dec.singleton :: Dec.Singleton N) :: Word32

        -- Fill the vector with x, x+1, x+2, ...
        (_, v1) <- forLoop (valueOf 0) (valueOf n) (x, v) $ \ i (x1, v1) -> do
            x1' <- add x1 (valueOf (1::T))
            v1' <- insertelement v1 x1 i
            return (x1', v1')

        -- Elementwise cubing of the vector.
        vcb <- mul v1 =<< mul v1 v1

        -- Sum the elements of the vector.
        s <- forLoop (valueOf 0) (valueOf n) (valueOf 0) $ \ i s -> do
            y <- extractelement vcb i
            add s (y :: Value T)

        -- Update the global variable.
        vacc <- load acc
        vacc' <- add vacc s
        store vacc' acc

        ret (s :: Value T)

    when False $ liftIO $ dumpValue f
    return f

createFuncModule :: IO (Module, Function (T -> IO T))
createFuncModule = do
    m <- newModule
    iovec <- defineModule m $ setTarget hostTriple >> cgvec
    return (m, iovec)

main :: IO ()
main = do
    -- Initialize jitter
    initializeNativeTarget

    -- First run standard code.
    do  (m, iovec) <- createFuncModule
        fvec <- runEngineAccessWithModule m $ getExecutionFunction convert iovec
        fvec 10 >>= print

    do  (m, iovec) <- createFuncModule
        vec <- runEngineAccessWithModule m $ generateFunction iovec
        vec 10 >>= print

    -- And then optimize and run.
    do  m <- fmap fst createFuncModule
        _ <- optimizeModule 1 m

        funcs <- getModuleValues m
        print $ map fst funcs

        let iovec' :: Function (T -> IO T)
            Just iovec' = castModuleValue =<< lookup fName funcs
            ioretacc' :: Function (IO T)
            Just ioretacc' = castModuleValue =<< lookup retAccName funcs

        (vec', retacc') <-
            runEngineAccessWithModule m $
            liftM2 (,) (generateFunction iovec') (generateFunction ioretacc')

        when False $ dumpValue iovec'

        vec' 10 >>= print
        vec' 0 >>= print
        retacc' >>= print
