-
Notifications
You must be signed in to change notification settings - Fork 8
/
Setup.hs
312 lines (287 loc) · 14.4 KB
/
Setup.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
{-# LANGUAGE CPP #-}
{-# OPTIONS_GHC -Wall #-}
module Main where
import Control.Arrow (first)
import Control.Applicative (pure, (<$>), (<*>))
import Control.Monad (when, unless)
import Prelude
import Data.Char (isSpace)
import Data.List (stripPrefix)
import Data.Maybe
#if MIN_VERSION_Cabal(2,0,0)
import qualified Data.Map as Map
#endif
import Distribution.ModuleName (ModuleName, fromString)
#if MIN_VERSION_Cabal(2,0,0)
import Distribution.PackageDescription (BuildInfo(..), Library(..), PackageDescription(..), defaultLibName)
import Distribution.Types.LocalBuildInfo (LocalBuildInfo(..))
#else
import Distribution.PackageDescription (BuildInfo(..), Library(..), PackageDescription(..))
#endif
import Distribution.Simple (UserHooks(..), simpleUserHooks, defaultMainWithHooksArgs, CompilerFlavor(..), buildCompilerFlavor)
import Distribution.Simple.BuildPaths
import Distribution.Simple.Program.Builtin (ghcProgram)
import Distribution.Simple.Program.Types (programFindLocation, ProgramSearchPathEntry(ProgramSearchPathDefault))
import Distribution.System (Arch(..), buildArch)
import Distribution.Verbosity (silent)
import System.Environment (getArgs, getEnvironment)
import System.Exit (ExitCode(..))
import System.FilePath ((</>), replaceExtension)
import System.IO (hPutStrLn, stderr)
import System.IO.Temp (withSystemTempDirectory)
import System.Process (readProcessWithExitCode)
import System.Cpuid.Basic (supportsSSE2, supportsAVX2, supportsAVX512f)
import Generator (genCode, PatsMode(..))
data SSESupport = SSESupport {
supportSSE2 :: Bool -- ^ Support for 128-bit vectors exists
,supportAVX2 :: Bool -- ^ Support for 256-bit vectors exists
,supportAVX512f :: Bool -- ^ Support for 512-bit vectors exists
} deriving Show
-- | Determine the processor and OS support for SSE2, AVX2 and AVX512f.
checkSSESupport :: Bool -> IO SSESupport
checkSSESupport chatty = case buildArch of
I386 -> SSESupport <$>
supportsSSE2 <*>
supportsAVX2 <*>
supportsAVX512f
X86_64 -> SSESupport <$>
supportsSSE2 <*>
supportsAVX2 <*>
supportsAVX512f
_ -> do
when chatty $ hPutStrLn stderr "WARNING: Unsupported architecture, defaulting to pure Haskell implementation"
pure $ SSESupport False False False
-- | Check if LLVM works by compiling a simple hello-world program.
checkLLVMSupport :: Bool -> IO Bool
checkLLVMSupport chatty = case buildCompilerFlavor of
GHC -> do
mLoc <- fmap fst <$> programFindLocation ghcProgram silent [ProgramSearchPathDefault]
case mLoc of
Nothing -> do
when chatty $ hPutStrLn stderr "Could not determine GHC location, disabled usage of LLVM"
pure False
Just loc -> withSystemTempDirectory "llvm-test" $ \ tmpDir -> do
let hsFile = tmpDir </> "LLVMTest.hs"
exeFile = tmpDir </> replaceExtension "LLVMTest" exeExtension
writeFile hsFile "main = putStrLn \"Hello, World\""
(exitCode, stdoutS, stderrS) <- readProcessWithExitCode loc ["-O", hsFile, "-fllvm", "-o", exeFile] ""
case exitCode of
ExitSuccess -> do
exitVals@(exitCode', stdoutS', stderrS') <- readProcessWithExitCode exeFile [] ""
if exitVals == (ExitSuccess, "Hello, World\n", "")
then pure True
else do
when chatty $ do
hPutStrLn stderr $ "WARNING: Code compiled with LLVM did not return expected output, the result was " ++ show exitCode'
hPutStrLn stderr $ "=============================\nSTDOUT:\n" ++ stdoutS'
hPutStrLn stderr $ "=============================\nSTDERR:\n" ++ stderrS'
hPutStrLn stderr $ "=============================\nDisabled LLVM code generation"
pure False
_ -> do
when chatty $ do
hPutStrLn stderr $ "WARNING: Failed to compile code with LLVM, the result was " ++ show exitCode
hPutStrLn stderr $ "=============================\nSTDOUT:\n" ++ stdoutS
hPutStrLn stderr $ "=============================\nSTDERR:\n" ++ stderrS
hPutStrLn stderr $ "=============================\nDisabled LLVM code generation"
pure False
_ -> do
when chatty $ hPutStrLn stderr "Usage of LLVM is currently only supported for GHC"
pure False
-- | Example code for our use of pattern synonyms. We use it to make sure we can
-- use them (we can't on GHC 8.0.1).
patSynTestCode :: Bool -> String
patSynTestCode patSigs = unlines
["{-# LANGUAGE PatternSynonyms #-}"
,"{-# LANGUAGE ViewPatterns #-}"
,"{-# LANGUAGE TypeFamilies #-}"
,"module PatSynTest where"
,""
,"data X a = X a a"
,""
,"class Vector v where"
," type ElemType v"
," type ElemTuple v"
," packVector :: ElemTuple v -> v"
," unpackVector :: v -> ElemTuple v"
,""
,"instance Vector (X a) where"
," type ElemType (X a) = a"
," type ElemTuple (X a) = (a, a)"
," packVector (a, b) = X a b"
," unpackVector (X a b) = (a, b)"
,""
,if patSigs then "pattern Vec2 :: (Vector v, ElemTuple v ~ (a, b)) => a -> b -> v" else ""
,"pattern Vec2 x1 x2 <- (unpackVector -> (x1, x2)) where"
," Vec2 x1 x2 = packVector (x1, x2)"
]
-- | Check if we can compile pattern synonyms. Our detection scheme is not really
-- advanced,
getPatSynSupport :: Bool -> IO PatsMode
getPatSynSupport chatty = case buildCompilerFlavor of
GHC -> do
mLoc <- fmap fst <$> programFindLocation ghcProgram silent [ProgramSearchPathDefault]
case mLoc of
Nothing -> pure NoPats
Just loc -> withSystemTempDirectory "patsyn-test" $ \ tmpDir -> do
let hsFile = tmpDir </> "PatSyns.hs"
exeFile = tmpDir </> replaceExtension "PatSyns" objExtension
writeFile hsFile (patSynTestCode True)
(exitCode, stdoutS, stderrS) <- readProcessWithExitCode loc ["-O", hsFile, "-o", exeFile, "-c"] ""
case exitCode of
ExitSuccess -> pure Pats
_ -> do
-- maybe we can get by without pattern signatures...
writeFile hsFile (patSynTestCode False)
(exitCode', _, _) <- readProcessWithExitCode loc ["-O", hsFile, "-o", exeFile, "-c"] ""
case exitCode' of
ExitSuccess -> pure NoPatSigs
_ -> do
when chatty $ do
hPutStrLn stderr $ "WARNING: Failed to compile code with Pattern Synonyms, the result was " ++ show exitCode
hPutStrLn stderr $ "=============================\nSTDOUT:\n" ++ stdoutS
hPutStrLn stderr $ "=============================\nSTDERR:\n" ++ stderrS
hPutStrLn stderr $ "=============================\nDisabled pattern synonym code generation"
pure NoPats
_ -> do
when chatty $ hPutStrLn stderr "WARNING: Unsupported compiler, compilation may fail..."
pure NoPats
-- | Generate sources in for the given vector width in the given directory.
-- Also takes care of figuring out the pattern synonym support.
genSrc :: Int -> FilePath -> IO ()
genSrc n autogenDir = do
usePatSyns <- getPatSynSupport True
when (usePatSyns == NoPats) $
hPutStrLn stderr $ "WARNING: The compiler does not seem to support pattern synonyms "
++ "(GHC 8.0.1 does not correctly and crashes!), the synonyms Vec<2,4,8,16,32,64> will "
++ "be missing. If you encounter undefined references of that name, you need to use a "
++ "compiler supporting pattern synonyms."
genCode (autogenDir </> "Data/Primitive/SIMD") usePatSyns n
-- | As 'genSrc', but takes a flag instead of the vector size in bytes.
genSrcForFlag :: Flag -> FilePath -> IO ()
genSrcForFlag NoVec = genSrc 0
genSrcForFlag Vec128 = genSrc (128 `quot` 8)
genSrcForFlag Vec256 = genSrc (256 `quot` 8)
genSrcForFlag Vec512 = genSrc (512 `quot` 8)
data Flag = NoVec | Vec128 | Vec256 | Vec512
deriving (Enum, Bounded, Eq)
showFlag :: Flag -> String
showFlag NoVec = "no-vec"
showFlag Vec128 = "vec128"
showFlag Vec256 = "vec256"
showFlag Vec512 = "vec512"
parseFlags :: [String] -> [(Flag, Bool)]
parseFlags = mapMaybe parseFlag
parseFlag :: String -> Maybe (Flag, Bool)
parseFlag s = do
s' <- stripPrefix "--flags=" s
case s' of
'-' : flagS -> do
flag <- translateFlag flagS
pure (flag, False)
_ -> do
flag <- translateFlag s'
pure (flag, True)
translateFlag :: String -> Maybe Flag
translateFlag s = case [f | f <- [minBound .. maxBound], showFlag f == s] of
[x] -> Just x
_ -> Nothing
setFlag :: Flag -> [String] -> [String]
setFlag flag cArgs = filter (isNothing . parseFlag) cArgs
++ ["--flag=" ++ ['-' | f /= flag] ++ showFlag f | f <- [minBound .. maxBound]]
resolveFlags :: Bool -> [String] -> IO ([String], Flag)
resolveFlags chatty cArgs = do
-- to configure this package, you can also set an environment variable
-- so you do not need to alter the command line options
-- this way you can easily force 128-bit SIMD usage even if your computer
-- supports 256 or 512-bit SIMD instructions, e.g. when building a package
-- or executable for distribution.
envOverride <- lookup "PRIMITIVE_SIMD_FLAG" <$> getEnvironment
case envOverride of
Just vecFlag
| Just flag <- translateFlag vecFlag
-> pure (setFlag flag cArgs, flag)
| otherwise
-> fail $ "Invalid vector flag: " ++ show vecFlag
Nothing -> do
sse <- checkSSESupport chatty
llvm <- checkLLVMSupport chatty
let flags = parseFlags cArgs
if any snd flags
then do
let require flag b = do
unless (b || not chatty) $
hPutStrLn stderr $ "Configured with setting "
++ showFlag flag
++ ", but could not determine LLVM/processor support. "
++ "We will try building, but this may fail."
pure flag
flag <- case filter snd flags of
[] -> fail "impossible..."
[(flag, _)] -> case flag of
NoVec -> pure flag
Vec128 -> require flag $ llvm && supportSSE2 sse
Vec256 -> require flag $ llvm && supportSSE2 sse && supportAVX2 sse
Vec512 -> require flag $ llvm && supportSSE2 sse && supportAVX2 sse && supportAVX512f sse
xs -> fail $ "More than one flag set! " ++ show (map (first showFlag) xs)
pure (cArgs, flag)
else do
let flag = case (llvm, supportAVX512f sse, supportAVX2 sse, supportSSE2 sse) of
(True, True, True, True) -> Vec512
(True, False, True, True) -> Vec256
(True, False, False, True) -> Vec128
_ -> NoVec
pure (setFlag flag cArgs, flag)
hooks :: UserHooks
hooks = simpleUserHooks {
confHook = \ (pkgDesc, hookBuildInfo) confFlags -> do
-- first get the local build information, so we can figure out the directory
-- we have to place our sources in
localBuildInfo <- confHook simpleUserHooks (pkgDesc, hookBuildInfo) confFlags
-- then run 'resolveFlags' a second time, but this time without printing anything
-- one could split it into two parts, but then we would have to duplicate a lot of logic
strArgs <- getArgs
(_, flag) <- resolveFlags False strArgs
-- generate sources
#if MIN_VERSION_Cabal(2,0,0)
let componentLocalBuildInfo = case fromMaybe [] $ Map.lookup defaultLibName $ componentNameMap localBuildInfo of
[] -> error "Can't find library component build info"
(x:_) -> x
genSrcForFlag flag $ autogenComponentModulesDir localBuildInfo componentLocalBuildInfo
#else
genSrcForFlag flag $ autogenModulesDir localBuildInfo
#endif
pure localBuildInfo
,sDistHook = \ pkgDesc mLocBuildInfo uHooks sDistFlags -> do
-- we have to filter our the auto generated modules to avoid cabal complaining
-- about not finding them
let parseXAutogenModules :: String -> [ModuleName]
parseXAutogenModules = map (fromString . filter (not. isSpace)) . lines
filterModules :: BuildInfo -> [ModuleName] -> [ModuleName]
filterModules bi = case maybe [] parseXAutogenModules $ lookup "x-autogen-modules" $ customFieldsBI bi of
autogens -> filter (`notElem` autogens)
fixBuildInfoAutogens :: BuildInfo -> BuildInfo
fixBuildInfoAutogens bi = bi { otherModules = filterModules bi (otherModules bi) }
fixLibraryAutogens :: Library -> Library
fixLibraryAutogens lib = lib {
exposedModules = filterModules (libBuildInfo lib) (exposedModules lib)
#if MIN_VERSION_Cabal(2,0,0)
,signatures = filterModules (libBuildInfo lib) (signatures lib)
#else
,requiredSignatures = filterModules (libBuildInfo lib) (requiredSignatures lib)
,exposedSignatures = filterModules (libBuildInfo lib) (exposedSignatures lib)
#endif
,libBuildInfo = fixBuildInfoAutogens (libBuildInfo lib)
}
pkgDesc' :: PackageDescription
pkgDesc' = pkgDesc { library = fixLibraryAutogens <$> library pkgDesc }
sDistHook simpleUserHooks pkgDesc' mLocBuildInfo uHooks sDistFlags
}
main :: IO ()
main = do
args <- getArgs
args' <- case args of
("configure":cArgs) -> do
(newArgs, _) <- resolveFlags True cArgs
pure $ "configure" : newArgs
_ -> pure args
defaultMainWithHooksArgs hooks args'