{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE Strict #-}

-- | High-level API for invoking the Futhark compiler.
module Futhark.Compiler
  ( runPipelineOnProgram,
    runCompilerOnProgram,
    dumpError,
    handleWarnings,
    module Futhark.Compiler.Program,
    module Futhark.Compiler.Config,
    readProgram,
    readProgramOrDie,
    readUntypedProgram,
    readUntypedProgramOrDie,
  )
where

import Control.Monad
import Control.Monad.Except
import Data.Bifunctor (first)
import qualified Data.Text.IO as T
import qualified Futhark.Analysis.Alias as Alias
import Futhark.Compiler.Config
import Futhark.Compiler.Program
import Futhark.IR
import qualified Futhark.IR.SOACS as I
import Futhark.Internalise
import Futhark.MonadFreshNames
import Futhark.Pipeline
import qualified Futhark.TypeCheck as I
import Futhark.Util.Log
import Futhark.Util.Pretty (ppr, prettyText)
import qualified Language.Futhark as E
import Language.Futhark.Semantic (includeToString)
import Language.Futhark.Warnings
import System.Exit (ExitCode (..), exitWith)
import System.IO

-- | Print a compiler error to stdout.  The 'FutharkConfig' controls
-- to which degree auxiliary information (e.g. the failing program) is
-- also printed.
dumpError :: FutharkConfig -> CompilerError -> IO ()
dumpError :: FutharkConfig -> CompilerError -> IO ()
dumpError FutharkConfig
config CompilerError
err =
  case CompilerError
err of
    ExternalError Doc
s -> do
      Handle -> Text -> IO ()
T.hPutStrLn Handle
stderr (Text -> IO ()) -> Text -> IO ()
forall a b. (a -> b) -> a -> b
$ Doc -> Text
forall a. Pretty a => a -> Text
prettyText Doc
s
      Handle -> Text -> IO ()
T.hPutStrLn Handle
stderr Text
""
      Handle -> Text -> IO ()
T.hPutStrLn Handle
stderr Text
"If you find this error message confusing, uninformative, or wrong, please open an issue at\nhttps://github.com/diku-dk/futhark/issues."
    InternalError Text
s Text
info ErrorClass
CompilerBug -> do
      Handle -> Text -> IO ()
T.hPutStrLn Handle
stderr Text
"Internal compiler error."
      Handle -> Text -> IO ()
T.hPutStrLn Handle
stderr Text
"Please report this at https://github.com/diku-dk/futhark/issues."
      Text -> Text -> IO ()
report Text
s Text
info
    InternalError Text
s Text
info ErrorClass
CompilerLimitation -> do
      Handle -> Text -> IO ()
T.hPutStrLn Handle
stderr Text
"Known compiler limitation encountered.  Sorry."
      Handle -> Text -> IO ()
T.hPutStrLn Handle
stderr Text
"Revise your program or try a different Futhark compiler."
      Text -> Text -> IO ()
report Text
s Text
info
  where
    report :: Text -> Text -> IO ()
report Text
s Text
info = do
      Handle -> Text -> IO ()
T.hPutStrLn Handle
stderr Text
s
      Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when ((Verbosity, Maybe [Char]) -> Verbosity
forall a b. (a, b) -> a
fst (FutharkConfig -> (Verbosity, Maybe [Char])
futharkVerbose FutharkConfig
config) Verbosity -> Verbosity -> Bool
forall a. Ord a => a -> a -> Bool
> Verbosity
NotVerbose) (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$
        (Text -> IO ())
-> ([Char] -> Text -> IO ()) -> Maybe [Char] -> Text -> IO ()
forall b a. b -> (a -> b) -> Maybe a -> b
maybe
          (Handle -> Text -> IO ()
T.hPutStr Handle
stderr)
          [Char] -> Text -> IO ()
T.writeFile
          ((Verbosity, Maybe [Char]) -> Maybe [Char]
forall a b. (a, b) -> b
snd (FutharkConfig -> (Verbosity, Maybe [Char])
futharkVerbose FutharkConfig
config))
          (Text -> IO ()) -> Text -> IO ()
forall a b. (a -> b) -> a -> b
$ Text
info Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"\n"

-- | Read a program from the given 'FilePath', run the given
-- 'Pipeline', and finish up with the given 'Action'.
runCompilerOnProgram ::
  FutharkConfig ->
  Pipeline I.SOACS rep ->
  Action rep ->
  FilePath ->
  IO ()
runCompilerOnProgram :: forall rep.
FutharkConfig
-> Pipeline SOACS rep -> Action rep -> [Char] -> IO ()
runCompilerOnProgram FutharkConfig
config Pipeline SOACS rep
pipeline Action rep
action [Char]
file = do
  Either CompilerError ()
res <- FutharkM () -> Verbosity -> IO (Either CompilerError ())
forall a. FutharkM a -> Verbosity -> IO (Either CompilerError a)
runFutharkM FutharkM ()
compile (Verbosity -> IO (Either CompilerError ()))
-> Verbosity -> IO (Either CompilerError ())
forall a b. (a -> b) -> a -> b
$ (Verbosity, Maybe [Char]) -> Verbosity
forall a b. (a, b) -> a
fst ((Verbosity, Maybe [Char]) -> Verbosity)
-> (Verbosity, Maybe [Char]) -> Verbosity
forall a b. (a -> b) -> a -> b
$ FutharkConfig -> (Verbosity, Maybe [Char])
futharkVerbose FutharkConfig
config
  case Either CompilerError ()
res of
    Left CompilerError
err -> IO () -> IO ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ do
      FutharkConfig -> CompilerError -> IO ()
dumpError FutharkConfig
config CompilerError
err
      ExitCode -> IO ()
forall a. ExitCode -> IO a
exitWith (ExitCode -> IO ()) -> ExitCode -> IO ()
forall a b. (a -> b) -> a -> b
$ Int -> ExitCode
ExitFailure Int
2
    Right () ->
      () -> IO ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    compile :: FutharkM ()
compile = do
      Prog rep
prog <- FutharkConfig
-> Pipeline SOACS rep -> [Char] -> FutharkM (Prog rep)
forall torep.
FutharkConfig
-> Pipeline SOACS torep -> [Char] -> FutharkM (Prog torep)
runPipelineOnProgram FutharkConfig
config Pipeline SOACS rep
pipeline [Char]
file
      Bool -> FutharkM () -> FutharkM ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when ((Verbosity -> Verbosity -> Bool
forall a. Ord a => a -> a -> Bool
> Verbosity
NotVerbose) (Verbosity -> Bool)
-> ((Verbosity, Maybe [Char]) -> Verbosity)
-> (Verbosity, Maybe [Char])
-> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Verbosity, Maybe [Char]) -> Verbosity
forall a b. (a, b) -> a
fst ((Verbosity, Maybe [Char]) -> Bool)
-> (Verbosity, Maybe [Char]) -> Bool
forall a b. (a -> b) -> a -> b
$ FutharkConfig -> (Verbosity, Maybe [Char])
futharkVerbose FutharkConfig
config) (FutharkM () -> FutharkM ()) -> FutharkM () -> FutharkM ()
forall a b. (a -> b) -> a -> b
$
        [Char] -> FutharkM ()
forall (m :: * -> *) a. (MonadLogger m, ToLog a) => a -> m ()
logMsg ([Char] -> FutharkM ()) -> [Char] -> FutharkM ()
forall a b. (a -> b) -> a -> b
$ [Char]
"Running action " [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ Action rep -> [Char]
forall rep. Action rep -> [Char]
actionName Action rep
action
      Action rep -> Prog rep -> FutharkM ()
forall rep. Action rep -> Prog rep -> FutharkM ()
actionProcedure Action rep
action Prog rep
prog
      Bool -> FutharkM () -> FutharkM ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when ((Verbosity -> Verbosity -> Bool
forall a. Ord a => a -> a -> Bool
> Verbosity
NotVerbose) (Verbosity -> Bool)
-> ((Verbosity, Maybe [Char]) -> Verbosity)
-> (Verbosity, Maybe [Char])
-> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Verbosity, Maybe [Char]) -> Verbosity
forall a b. (a, b) -> a
fst ((Verbosity, Maybe [Char]) -> Bool)
-> (Verbosity, Maybe [Char]) -> Bool
forall a b. (a -> b) -> a -> b
$ FutharkConfig -> (Verbosity, Maybe [Char])
futharkVerbose FutharkConfig
config) (FutharkM () -> FutharkM ()) -> FutharkM () -> FutharkM ()
forall a b. (a -> b) -> a -> b
$
        [Char] -> FutharkM ()
forall (m :: * -> *) a. (MonadLogger m, ToLog a) => a -> m ()
logMsg ([Char]
"Done." :: String)

-- | Read a program from the given 'FilePath', run the given
-- 'Pipeline', and return it.
runPipelineOnProgram ::
  FutharkConfig ->
  Pipeline I.SOACS torep ->
  FilePath ->
  FutharkM (Prog torep)
runPipelineOnProgram :: forall torep.
FutharkConfig
-> Pipeline SOACS torep -> [Char] -> FutharkM (Prog torep)
runPipelineOnProgram FutharkConfig
config Pipeline SOACS torep
pipeline [Char]
file = do
  Bool -> FutharkM () -> FutharkM ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (PipelineConfig -> Bool
pipelineVerbose PipelineConfig
pipeline_config) (FutharkM () -> FutharkM ()) -> FutharkM () -> FutharkM ()
forall a b. (a -> b) -> a -> b
$
    [Char] -> FutharkM ()
forall (m :: * -> *) a. (MonadLogger m, ToLog a) => a -> m ()
logMsg ([Char]
"Reading and type-checking source program" :: String)
  (Imports
prog_imports, VNameSource
namesrc) <-
    FutharkConfig
-> FutharkM (Warnings, (Imports, VNameSource))
-> FutharkM (Imports, VNameSource)
forall a. FutharkConfig -> FutharkM (Warnings, a) -> FutharkM a
handleWarnings FutharkConfig
config (FutharkM (Warnings, (Imports, VNameSource))
 -> FutharkM (Imports, VNameSource))
-> FutharkM (Warnings, (Imports, VNameSource))
-> FutharkM (Imports, VNameSource)
forall a b. (a -> b) -> a -> b
$
      (\(Warnings
a, Imports
b, VNameSource
c) -> (Warnings
a, (Imports
b, VNameSource
c)))
        ((Warnings, Imports, VNameSource)
 -> (Warnings, (Imports, VNameSource)))
-> FutharkM (Warnings, Imports, VNameSource)
-> FutharkM (Warnings, (Imports, VNameSource))
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [Name] -> [Char] -> FutharkM (Warnings, Imports, VNameSource)
forall (m :: * -> *).
(MonadError CompilerError m, MonadIO m) =>
[Name] -> [Char] -> m (Warnings, Imports, VNameSource)
readProgram (FutharkConfig -> [Name]
futharkEntryPoints FutharkConfig
config) [Char]
file

  VNameSource -> FutharkM ()
forall (m :: * -> *). MonadFreshNames m => VNameSource -> m ()
putNameSource VNameSource
namesrc
  Prog SOACS
int_prog <- FutharkConfig -> Imports -> FutharkM (Prog SOACS)
forall (m :: * -> *).
(MonadFreshNames m, MonadLogger m) =>
FutharkConfig -> Imports -> m (Prog SOACS)
internaliseProg FutharkConfig
config Imports
prog_imports
  Bool -> FutharkM () -> FutharkM ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (PipelineConfig -> Bool
pipelineVerbose PipelineConfig
pipeline_config) (FutharkM () -> FutharkM ()) -> FutharkM () -> FutharkM ()
forall a b. (a -> b) -> a -> b
$
    [Char] -> FutharkM ()
forall (m :: * -> *) a. (MonadLogger m, ToLog a) => a -> m ()
logMsg ([Char]
"Type-checking internalised program" :: String)
  Prog SOACS -> FutharkM ()
typeCheckInternalProgram Prog SOACS
int_prog
  Pipeline SOACS torep
-> PipelineConfig -> Prog SOACS -> FutharkM (Prog torep)
forall fromrep torep.
Pipeline fromrep torep
-> PipelineConfig -> Prog fromrep -> FutharkM (Prog torep)
runPipeline Pipeline SOACS torep
pipeline PipelineConfig
pipeline_config Prog SOACS
int_prog
  where
    pipeline_config :: PipelineConfig
pipeline_config =
      PipelineConfig :: Bool -> Bool -> PipelineConfig
PipelineConfig
        { pipelineVerbose :: Bool
pipelineVerbose = (Verbosity, Maybe [Char]) -> Verbosity
forall a b. (a, b) -> a
fst (FutharkConfig -> (Verbosity, Maybe [Char])
futharkVerbose FutharkConfig
config) Verbosity -> Verbosity -> Bool
forall a. Ord a => a -> a -> Bool
> Verbosity
NotVerbose,
          pipelineValidate :: Bool
pipelineValidate = FutharkConfig -> Bool
futharkTypeCheck FutharkConfig
config
        }

typeCheckInternalProgram :: I.Prog I.SOACS -> FutharkM ()
typeCheckInternalProgram :: Prog SOACS -> FutharkM ()
typeCheckInternalProgram Prog SOACS
prog =
  case Prog (Aliases SOACS) -> Either (TypeError SOACS) ()
forall rep.
Checkable rep =>
Prog (Aliases rep) -> Either (TypeError rep) ()
I.checkProg Prog (Aliases SOACS)
prog' of
    Left TypeError SOACS
err -> [Char] -> Doc -> FutharkM ()
forall (m :: * -> *) a.
MonadError CompilerError m =>
[Char] -> Doc -> m a
internalErrorS ([Char]
"After internalisation:\n" [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ TypeError SOACS -> [Char]
forall a. Show a => a -> [Char]
show TypeError SOACS
err) (Prog (Aliases SOACS) -> Doc
forall a. Pretty a => a -> Doc
ppr Prog (Aliases SOACS)
prog')
    Right () -> () -> FutharkM ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    prog' :: Prog (Aliases SOACS)
prog' = Prog SOACS -> Prog (Aliases SOACS)
forall rep.
(ASTRep rep, CanBeAliased (Op rep)) =>
Prog rep -> Prog (Aliases rep)
Alias.aliasAnalysis Prog SOACS
prog

-- | Read and type-check a Futhark program, including all imports.
readProgram ::
  (MonadError CompilerError m, MonadIO m) =>
  [I.Name] ->
  FilePath ->
  m (Warnings, Imports, VNameSource)
readProgram :: forall (m :: * -> *).
(MonadError CompilerError m, MonadIO m) =>
[Name] -> [Char] -> m (Warnings, Imports, VNameSource)
readProgram [Name]
extra_eps = [Name] -> [[Char]] -> m (Warnings, Imports, VNameSource)
forall (m :: * -> *).
(MonadError CompilerError m, MonadIO m) =>
[Name] -> [[Char]] -> m (Warnings, Imports, VNameSource)
readLibrary [Name]
extra_eps ([[Char]] -> m (Warnings, Imports, VNameSource))
-> ([Char] -> [[Char]])
-> [Char]
-> m (Warnings, Imports, VNameSource)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Char] -> [[Char]]
forall (f :: * -> *) a. Applicative f => a -> f a
pure

-- | Read and parse (but do not type-check) a Futhark program,
-- including all imports.
readUntypedProgram ::
  (MonadError CompilerError m, MonadIO m) =>
  FilePath ->
  m [(String, E.UncheckedProg)]
readUntypedProgram :: forall (m :: * -> *).
(MonadError CompilerError m, MonadIO m) =>
[Char] -> m [([Char], UncheckedProg)]
readUntypedProgram =
  ([(ImportName, UncheckedProg)] -> [([Char], UncheckedProg)])
-> m [(ImportName, UncheckedProg)] -> m [([Char], UncheckedProg)]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (((ImportName, UncheckedProg) -> ([Char], UncheckedProg))
-> [(ImportName, UncheckedProg)] -> [([Char], UncheckedProg)]
forall a b. (a -> b) -> [a] -> [b]
map ((ImportName -> [Char])
-> (ImportName, UncheckedProg) -> ([Char], UncheckedProg)
forall (p :: * -> * -> *) a b c.
Bifunctor p =>
(a -> b) -> p a c -> p b c
first ImportName -> [Char]
includeToString)) (m [(ImportName, UncheckedProg)] -> m [([Char], UncheckedProg)])
-> ([Char] -> m [(ImportName, UncheckedProg)])
-> [Char]
-> m [([Char], UncheckedProg)]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [[Char]] -> m [(ImportName, UncheckedProg)]
forall (m :: * -> *).
(MonadIO m, MonadError CompilerError m) =>
[[Char]] -> m [(ImportName, UncheckedProg)]
readUntypedLibrary ([[Char]] -> m [(ImportName, UncheckedProg)])
-> ([Char] -> [[Char]])
-> [Char]
-> m [(ImportName, UncheckedProg)]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Char] -> [[Char]]
forall (f :: * -> *) a. Applicative f => a -> f a
pure

orDie :: MonadIO m => FutharkM a -> m a
orDie :: forall (m :: * -> *) a. MonadIO m => FutharkM a -> m a
orDie FutharkM a
m = IO a -> m a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO a -> m a) -> IO a -> m a
forall a b. (a -> b) -> a -> b
$ do
  Either CompilerError a
res <- FutharkM a -> Verbosity -> IO (Either CompilerError a)
forall a. FutharkM a -> Verbosity -> IO (Either CompilerError a)
runFutharkM FutharkM a
m Verbosity
NotVerbose
  case Either CompilerError a
res of
    Left CompilerError
err -> do
      FutharkConfig -> CompilerError -> IO ()
dumpError FutharkConfig
newFutharkConfig CompilerError
err
      ExitCode -> IO a
forall a. ExitCode -> IO a
exitWith (ExitCode -> IO a) -> ExitCode -> IO a
forall a b. (a -> b) -> a -> b
$ Int -> ExitCode
ExitFailure Int
2
    Right a
res' -> a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return a
res'

-- | Not verbose, and terminates process on error.
readProgramOrDie :: MonadIO m => FilePath -> m (Warnings, Imports, VNameSource)
readProgramOrDie :: forall (m :: * -> *).
MonadIO m =>
[Char] -> m (Warnings, Imports, VNameSource)
readProgramOrDie [Char]
file = FutharkM (Warnings, Imports, VNameSource)
-> m (Warnings, Imports, VNameSource)
forall (m :: * -> *) a. MonadIO m => FutharkM a -> m a
orDie (FutharkM (Warnings, Imports, VNameSource)
 -> m (Warnings, Imports, VNameSource))
-> FutharkM (Warnings, Imports, VNameSource)
-> m (Warnings, Imports, VNameSource)
forall a b. (a -> b) -> a -> b
$ [Name] -> [Char] -> FutharkM (Warnings, Imports, VNameSource)
forall (m :: * -> *).
(MonadError CompilerError m, MonadIO m) =>
[Name] -> [Char] -> m (Warnings, Imports, VNameSource)
readProgram [Name]
forall a. Monoid a => a
mempty [Char]
file

-- | Not verbose, and terminates process on error.
readUntypedProgramOrDie :: MonadIO m => FilePath -> m [(String, E.UncheckedProg)]
readUntypedProgramOrDie :: forall (m :: * -> *).
MonadIO m =>
[Char] -> m [([Char], UncheckedProg)]
readUntypedProgramOrDie [Char]
file = FutharkM [([Char], UncheckedProg)] -> m [([Char], UncheckedProg)]
forall (m :: * -> *) a. MonadIO m => FutharkM a -> m a
orDie (FutharkM [([Char], UncheckedProg)] -> m [([Char], UncheckedProg)])
-> FutharkM [([Char], UncheckedProg)]
-> m [([Char], UncheckedProg)]
forall a b. (a -> b) -> a -> b
$ [Char] -> FutharkM [([Char], UncheckedProg)]
forall (m :: * -> *).
(MonadError CompilerError m, MonadIO m) =>
[Char] -> m [([Char], UncheckedProg)]
readUntypedProgram [Char]
file

-- | Run an operation that produces warnings, and handle them
-- appropriately, yielding the non-warning return value.  "Proper
-- handling" means e.g. to print them to the screen, as directed by
-- the compiler configuration.
handleWarnings :: FutharkConfig -> FutharkM (Warnings, a) -> FutharkM a
handleWarnings :: forall a. FutharkConfig -> FutharkM (Warnings, a) -> FutharkM a
handleWarnings FutharkConfig
config FutharkM (Warnings, a)
m = do
  (Warnings
ws, a
a) <- FutharkM (Warnings, a)
m

  Bool -> FutharkM () -> FutharkM ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (FutharkConfig -> Bool
futharkWarn FutharkConfig
config Bool -> Bool -> Bool
&& Warnings -> Bool
anyWarnings Warnings
ws) (FutharkM () -> FutharkM ()) -> FutharkM () -> FutharkM ()
forall a b. (a -> b) -> a -> b
$ do
    IO () -> FutharkM ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> FutharkM ()) -> IO () -> FutharkM ()
forall a b. (a -> b) -> a -> b
$ Handle -> [Char] -> IO ()
hPutStrLn Handle
stderr ([Char] -> IO ()) -> [Char] -> IO ()
forall a b. (a -> b) -> a -> b
$ Warnings -> [Char]
forall a. Pretty a => a -> [Char]
pretty Warnings
ws
    Bool -> FutharkM () -> FutharkM ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (FutharkConfig -> Bool
futharkWerror FutharkConfig
config) (FutharkM () -> FutharkM ()) -> FutharkM () -> FutharkM ()
forall a b. (a -> b) -> a -> b
$
      [Char] -> FutharkM ()
forall (m :: * -> *) a. MonadError CompilerError m => [Char] -> m a
externalErrorS [Char]
"Treating above warnings as errors due to --Werror."

  a -> FutharkM a
forall (m :: * -> *) a. Monad m => a -> m a
return a
a