module LLVM.Util.Optimize(optimizeModule) where

import LLVM.Core.Util (Module, withModule, getObjList)

import qualified LLVM.FFI.Transforms.PassManagerBuilder as PMB
import qualified LLVM.FFI.Core as FFI
import LLVM.FFI.Transforms.Scalar (addVerifierPass)

import Control.Exception (bracket, bracket_)


{- |
Result tells whether the module was modified by any of the passes.

It is very important that you set target triple and target data layout
before optimizing.
Otherwise the optimizer will make wrong assumptions
and e.g. corrupt your record offsets.
See e.g. example/Array for how this can be achieved.

In the future I might enforce via types
that you set target parameters before optimization.
-}
optimizeModule :: Int -> Module -> IO Bool
optimizeModule optLevel mdl =
    withModule mdl $ \ m ->
    {-
    Core.Util.createPassManager would provide a finalizer for us,
    but I think it is better here to immediately dispose the manager
    when we need it no longer.
    -}
    bracket FFI.createPassManager FFI.disposePassManager $ \mpasses ->
    bracket (FFI.createFunctionPassManagerForModule m)
            FFI.disposePassManager $ \fpasses -> do
        addVerifierPass mpasses

        bracket PMB.create PMB.dispose $ \passBuilder -> do
            PMB.setOptLevel passBuilder $ fromIntegral optLevel
            {-
            PMB.setSizeLevel passBuilder 1
            PMB.setDisableUnitAtATime passBuilder false
            PMB.setDisableUnrollLoops passBuilder (optLevel <= 1)
            PMB.setDisableSimplifyLibCalls passBuilder false
            PMB.setHaveExceptions passBuilder true
            PMB.setInliningPass passBuilder true
            -}

            PMB.populateFunctionPassManager passBuilder fpasses
            PMB.populateModulePassManager passBuilder mpasses

        fchanged <-
            bracket_
                (FFI.initializeFunctionPassManager fpasses)
                (FFI.finalizeFunctionPassManager fpasses)
                (mapM (FFI.runFunctionPassManager fpasses) =<<
                 getObjList withModule
                    FFI.getFirstFunction FFI.getNextFunction mdl)
        mchanged <- FFI.runPassManager mpasses m
        return $ any FFI.deconsBool $ mchanged : fchanged

{-
ToDo:
Function that adds passes according to a list of opt-options.
This would simplify to get consistent behaviour between opt and optimizeModule.

-adce                      addAggressiveDCEPass
-deadargelim               addDeadArgEliminationPass
-deadtypeelim              addDeadTypeEliminationPass
-dse                       addDeadStoreEliminationPass
-functionattrs             addFunctionAttrsPass
-globalopt                 addGlobalOptimizerPass
-indvars                   addIndVarSimplifyPass
-instcombine               addInstructionCombiningPass
-ipsccp                    addIPSCCPPass
-jump-threading            addJumpThreadingPass
-licm                      addLICMPass
-loop-deletion             addLoopDeletionPass
-loop-rotate               addLoopRotatePass
-memcpyopt                 addMemCpyOptPass
-prune-eh                  addPruneEHPass
-reassociate               addReassociatePass
-scalarrepl                addScalarReplAggregatesPass
-sccp                      addSCCPPass
-simplifycfg               addCFGSimplificationPass
-simplify-libcalls         addSimplifyLibCallsPass
-strip-dead-prototypes     addStripDeadPrototypesPass
-tailcallelim              addTailCallEliminationPass
-verify                    addVerifierPass
-}