futhark 0.7.3 → 0.7.4
raw patch · 7 files changed
+327/−75 lines, 7 filesdep +aesondep −jsondep ~base
Dependencies added: aeson
Dependencies removed: json
Dependency ranges changed: base
Files
- futhark.cabal +27/−26
- rts/futhark-doc/style.css +226/−0
- src/Futhark/CodeGen/ImpGen/Kernels/ToOpenCL.hs +3/−3
- src/Futhark/CodeGen/OpenCL/Kernels.hs +47/−25
- src/Futhark/Internalise/Defunctionalise.hs +7/−4
- src/Language/Futhark/Parser/Parser.y +4/−4
- src/futhark-bench.hs +13/−13
futhark.cabal view
@@ -2,10 +2,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: 3b4f60b44c2ac9e9de368d6116299bafb8b535f9a6ae7a26613bca65b22ad1d9+-- hash: 27326a3d0b5daec736d8b0de8fda5938beaf2ee6e75215a5cfb6a29b1008afb0 name: futhark-version: 0.7.3+version: 0.7.4 synopsis: An optimising compiler for a functional, array-oriented language. description: See the website at https://futhark-lang.org category: Language@@ -36,6 +36,7 @@ rts/csharp/panic.cs rts/csharp/reader.cs rts/csharp/scalar.cs+ rts/futhark-doc/style.css rts/python/__init__.py rts/python/memory.py rts/python/opencl.py@@ -312,7 +313,8 @@ Paths_futhark ghc-options: -Wall -Wcompat -Wredundant-constraints -Wincomplete-record-updates -Wmissing-export-lists -threaded -rtsopts "-with-rtsopts=-N -qg" build-depends:- ansi-terminal >=0.6.3.1+ aeson+ , ansi-terminal >=0.6.3.1 , array >=0.4 , base >=4 && <5 , bifunctors >=5.4.2@@ -333,7 +335,6 @@ , http-client >=0.5.7.0 , http-client-tls >=0.3.5.1 , http-conduit >=2.2.4- , json , language-c-quote >=0.12 , mainland-pretty >=0.6.1 , markdown >=0.1.16@@ -370,7 +371,8 @@ Paths_futhark ghc-options: -Wall -Wcompat -Wredundant-constraints -Wincomplete-record-updates -Wmissing-export-lists -threaded -rtsopts "-with-rtsopts=-N -qg" build-depends:- ansi-terminal >=0.6.3.1+ aeson+ , ansi-terminal >=0.6.3.1 , array >=0.4 , base >=4 && <5 , bifunctors >=5.4.2@@ -391,7 +393,6 @@ , http-client >=0.5.7.0 , http-client-tls >=0.3.5.1 , http-conduit >=2.2.4- , json , language-c-quote >=0.12 , mainland-pretty >=0.6.1 , markdown >=0.1.16@@ -428,7 +429,8 @@ Paths_futhark ghc-options: -Wall -Wcompat -Wredundant-constraints -Wincomplete-record-updates -Wmissing-export-lists -threaded -rtsopts "-with-rtsopts=-N -qg" build-depends:- ansi-terminal >=0.6.3.1+ aeson+ , ansi-terminal >=0.6.3.1 , array >=0.4 , base >=4 && <5 , bifunctors >=5.4.2@@ -449,7 +451,6 @@ , http-client >=0.5.7.0 , http-client-tls >=0.3.5.1 , http-conduit >=2.2.4- , json , language-c-quote >=0.12 , mainland-pretty >=0.6.1 , markdown >=0.1.16@@ -486,7 +487,8 @@ Paths_futhark ghc-options: -Wall -Wcompat -Wredundant-constraints -Wincomplete-record-updates -Wmissing-export-lists -threaded -rtsopts "-with-rtsopts=-N -qg" build-depends:- ansi-terminal >=0.6.3.1+ aeson+ , ansi-terminal >=0.6.3.1 , array >=0.4 , base >=4 && <5 , bifunctors >=5.4.2@@ -507,7 +509,6 @@ , http-client >=0.5.7.0 , http-client-tls >=0.3.5.1 , http-conduit >=2.2.4- , json , language-c-quote >=0.12 , mainland-pretty >=0.6.1 , markdown >=0.1.16@@ -544,7 +545,8 @@ Paths_futhark ghc-options: -Wall -Wcompat -Wredundant-constraints -Wincomplete-record-updates -Wmissing-export-lists -threaded -rtsopts "-with-rtsopts=-N -qg" build-depends:- ansi-terminal >=0.6.3.1+ aeson+ , ansi-terminal >=0.6.3.1 , array >=0.4 , base >=4 && <5 , bifunctors >=5.4.2@@ -565,7 +567,6 @@ , http-client >=0.5.7.0 , http-client-tls >=0.3.5.1 , http-conduit >=2.2.4- , json , language-c-quote >=0.12 , mainland-pretty >=0.6.1 , markdown >=0.1.16@@ -602,7 +603,8 @@ Paths_futhark ghc-options: -Wall -Wcompat -Wredundant-constraints -Wincomplete-record-updates -Wmissing-export-lists -threaded -rtsopts "-with-rtsopts=-N -qg" build-depends:- ansi-terminal >=0.6.3.1+ aeson+ , ansi-terminal >=0.6.3.1 , array >=0.4 , base >=4 && <5 , bifunctors >=5.4.2@@ -623,7 +625,6 @@ , http-client >=0.5.7.0 , http-client-tls >=0.3.5.1 , http-conduit >=2.2.4- , json , language-c-quote >=0.12 , mainland-pretty >=0.6.1 , markdown >=0.1.16@@ -660,7 +661,8 @@ Paths_futhark ghc-options: -Wall -Wcompat -Wredundant-constraints -Wincomplete-record-updates -Wmissing-export-lists -threaded -rtsopts "-with-rtsopts=-N -qg" build-depends:- ansi-terminal >=0.6.3.1+ aeson+ , ansi-terminal >=0.6.3.1 , array >=0.4 , base >=4 && <5 , bifunctors >=5.4.2@@ -681,7 +683,6 @@ , http-client >=0.5.7.0 , http-client-tls >=0.3.5.1 , http-conduit >=2.2.4- , json , language-c-quote >=0.12 , mainland-pretty >=0.6.1 , markdown >=0.1.16@@ -718,7 +719,8 @@ Paths_futhark ghc-options: -Wall -Wcompat -Wredundant-constraints -Wincomplete-record-updates -Wmissing-export-lists -threaded -rtsopts "-with-rtsopts=-N -qg" build-depends:- ansi-terminal >=0.6.3.1+ aeson+ , ansi-terminal >=0.6.3.1 , array >=0.4 , base >=4 && <5 , bifunctors >=5.4.2@@ -739,7 +741,6 @@ , http-client >=0.5.7.0 , http-client-tls >=0.3.5.1 , http-conduit >=2.2.4- , json , language-c-quote >=0.12 , mainland-pretty >=0.6.1 , markdown >=0.1.16@@ -776,7 +777,8 @@ Paths_futhark ghc-options: -Wall -Wcompat -Wredundant-constraints -Wincomplete-record-updates -Wmissing-export-lists -threaded -rtsopts "-with-rtsopts=-N -qg" build-depends:- ansi-terminal >=0.6.3.1+ aeson+ , ansi-terminal >=0.6.3.1 , array >=0.4 , base >=4 && <5 , bifunctors >=5.4.2@@ -797,7 +799,6 @@ , http-client >=0.5.7.0 , http-client-tls >=0.3.5.1 , http-conduit >=2.2.4- , json , language-c-quote >=0.12 , mainland-pretty >=0.6.1 , markdown >=0.1.16@@ -834,7 +835,8 @@ Paths_futhark ghc-options: -Wall -Wcompat -Wredundant-constraints -Wincomplete-record-updates -Wmissing-export-lists -threaded -rtsopts "-with-rtsopts=-N -qg" build-depends:- ansi-terminal >=0.6.3.1+ aeson+ , ansi-terminal >=0.6.3.1 , array >=0.4 , base >=4 && <5 , bifunctors >=5.4.2@@ -855,7 +857,6 @@ , http-client >=0.5.7.0 , http-client-tls >=0.3.5.1 , http-conduit >=2.2.4- , json , language-c-quote >=0.12 , mainland-pretty >=0.6.1 , markdown >=0.1.16@@ -892,7 +893,8 @@ Paths_futhark ghc-options: -Wall -Wcompat -Wredundant-constraints -Wincomplete-record-updates -Wmissing-export-lists -threaded -rtsopts "-with-rtsopts=-N -qg" build-depends:- ansi-terminal >=0.6.3.1+ aeson+ , ansi-terminal >=0.6.3.1 , array >=0.4 , base >=4 && <5 , bifunctors >=5.4.2@@ -913,7 +915,6 @@ , http-client >=0.5.7.0 , http-client-tls >=0.3.5.1 , http-conduit >=2.2.4- , json , language-c-quote >=0.12 , mainland-pretty >=0.6.1 , markdown >=0.1.16@@ -950,7 +951,8 @@ Paths_futhark ghc-options: -Wall -Wcompat -Wredundant-constraints -Wincomplete-record-updates -Wmissing-export-lists -threaded -rtsopts "-with-rtsopts=-N -qg" build-depends:- ansi-terminal >=0.6.3.1+ aeson+ , ansi-terminal >=0.6.3.1 , array >=0.4 , base >=4 && <5 , bifunctors >=5.4.2@@ -971,7 +973,6 @@ , http-client >=0.5.7.0 , http-client-tls >=0.3.5.1 , http-conduit >=2.2.4- , json , language-c-quote >=0.12 , mainland-pretty >=0.6.1 , markdown >=0.1.16
+ rts/futhark-doc/style.css view
@@ -0,0 +1,226 @@+body {+ background-color: #fff9e5;+ font-family: sans-serif;+ padding: 0px;+ margin: 0px;+ margin-top: 0em;+ margin-left: 0em;+ margin-right: 0em;+ overflow-y: scroll;+ line-height: 1.4;+}++h1 {+ margin-top: 0;+ display: inline;+}++h2 {+ margin-bottom: 1ex;+ font-size: 2em;+ color: #5f021f;+}++h3 {+ font-size: 1em;+ color: #5f021f;+}++p {+ max-width: 65em;+}++td {+ vertical-align: top;+}++#header {+ color: #fff9e5;+ background: #5f0220;+ padding-left: 1em;+}++#navigation {+ float: right;+ vertical-align: centre;+}++#navigation ol {+ display: block;+}++#navigation li {+ display: inline;+ list-style-type: none;+ border-left: 1px solid #fff9e5;+ padding-left: 1em;+ padding-right: 1em;+}++#navigation a, #navigation a:visited {+ color: #fff9e5;+ text-decoration: none;+}++#content {+ display: flex;+}++main {+ padding-left: 1em;+ padding-right: 1em;+ width: 100%;+}++#abstract {+ margin-bottom: 2ex;+}++#footer {+ color: #fff9e5;+ background: #5f0220;+ padding-left: 1em;+ padding-right: 1em;+}++#footer a, #footer a:visited {+ color: #fff9e5;+}++@media (max-width:950px) {+ #filenav {+ display: none;+ }+}++#filenav {+ color: #fff9e5;+ background: #5f0220;+ padding-left: 1em;+ padding-right: 1em;+}++#filenav a:hover {+ background: #fff9e5;+ display: block;+ color: #5f0220;+}++#filenav a, #footer a:visited {+ color: #fff9e5;+ text-decoration: none;+}++#filenav > ul {+ list-style: none;+ padding: 0;+ font-family: monospace;+ font-weight: bold;+}++.file_desc {+ /* Hack to avoid breaking a file description across multiple columns+ on the contents page. */+ display: table;+ width: 100%;+}++@media (min-width:950px) {+ .file_list {+ columns: 3;+ }++ .file_list > dd {+ break-before: avoid;+ }+}++.keyword {+ font-weight: bold;+}++.specs {+ margin-left: 2ex;+ border-collapse: collapse;+}++.specs tr:hover td {+ background: #ccc;+}++.spec_eql {+ text-align: right;+}++.decl_description {+ margin-bottom: 2ex;+}++.decl_name {+ font-weight: bold;+}++.desc_header {+ background: #ffcc7a;+ font-family: monospace;+ border-top: 1px solid #5f021f;+}++.desc_doc {+ margin-left: 2ex;+}++.synopsis_link {+ font-weight: bold;+ text-decoration: none;+ font-size: 1.3em;+ width: 2ex;+ display: inline-block;+}++.self_link {+ text-decoration: none;+}++#module {+ font-family: monospace;+ white-space: pre;+ display: block;+ unicode-bidi: embed;+}++#doc_index thead {+ font-weight: bold;+}++#doc_index td {+ padding-right: 1em;+}++.doc_index_name {+ font-family: monospace;+}++.doc_index_namespace {+ font-style: italic;+}++.doc_index_initial {+ font-size: 2em;+ font-weight: bold;+}++.doc_index_initial a, .doc_index_initial a:visited {+ color: #5f0220;+}++#doc_index_list li {+ display: inline;+ list-style-type: none;+ padding-right: 1em;+}++#doc_index_list a, #doc_index_list a:visited {+ color: #5f0220;+ text-decoration: none;+}
src/Futhark/CodeGen/ImpGen/Kernels/ToOpenCL.hs view
@@ -580,10 +580,10 @@ transposeKernelAndGroupSize :: ImpOpenCL.Exp -> ImpOpenCL.Exp -> ImpOpenCL.Exp -> ([ImpOpenCL.Exp], [ImpOpenCL.Exp]) transposeKernelAndGroupSize num_arrays x_elems y_elems =- ([x_elems `roundUpTo` transposeBlockDim ,- y_elems `roundUpTo` transposeBlockDim,+ ([(x_elems `roundUpTo` transposeBlockDim) *+ (y_elems `roundUpTo` transposeBlockDim) * num_arrays],- [transposeBlockDim, transposeBlockDim, 1])+ [transposeBlockDim * transposeBlockDim]) roundUpTo :: ImpOpenCL.Exp -> ImpOpenCL.Exp -> ImpOpenCL.Exp roundUpTo x y = x + ((y - (x `impRem` y)) `impRem` y)
src/Futhark/CodeGen/OpenCL/Kernels.hs view
@@ -89,37 +89,44 @@ case transpose_type of TransposeNormal -> bigKernel []- [C.cexp|get_global_id(0)|]- [C.cexp|get_global_id(1)|]- [C.cexp|get_group_id(1) * FUT_BLOCK_DIM + get_local_id(0)|]- [C.cexp|get_group_id(0) * FUT_BLOCK_DIM + get_local_id(1)|]+ [C.cexp|global_id_x|]+ [C.cexp|global_id_y|]+ [C.cexp|group_id_y * FUT_BLOCK_DIM + local_id_x|]+ [C.cexp|group_id_x * FUT_BLOCK_DIM + local_id_y|]+ (toNumGroups [C.cexp|width|])+ (toNumGroups [C.cexp|height|]) TransposeLowWidth -> bigKernel [C.cparams|uint muly|]- [C.cexp|get_group_id(0) * FUT_BLOCK_DIM + (get_local_id(0) / muly)|]- [C.cexp|get_group_id(1) * FUT_BLOCK_DIM * muly- + get_local_id(1)- + (get_local_id(0) % muly) * FUT_BLOCK_DIM+ [C.cexp|group_id_x * FUT_BLOCK_DIM + (local_id_x / muly)|]+ [C.cexp|group_id_y * FUT_BLOCK_DIM * muly+ + local_id_y+ + (local_id_x % muly) * FUT_BLOCK_DIM |]- [C.cexp|get_group_id(1) * FUT_BLOCK_DIM * muly- + get_local_id(0)- + (get_local_id(1) % muly) * FUT_BLOCK_DIM|]- [C.cexp|get_group_id(0) * FUT_BLOCK_DIM + (get_local_id(1) / muly)|]+ [C.cexp|group_id_y * FUT_BLOCK_DIM * muly+ + local_id_x+ + (local_id_y % muly) * FUT_BLOCK_DIM|]+ [C.cexp|group_id_x * FUT_BLOCK_DIM + (local_id_y / muly)|]+ (toNumGroups [C.cexp|width|])+ (toNumGroups [C.cexp|(height + muly - 1) / muly|]) TransposeLowHeight -> bigKernel [C.cparams|uint mulx|]- [C.cexp|get_group_id(0) * FUT_BLOCK_DIM * mulx- + get_local_id(0)- + (get_local_id(1) % mulx) * FUT_BLOCK_DIM+ [C.cexp|group_id_x * FUT_BLOCK_DIM * mulx+ + local_id_x+ + (local_id_y % mulx) * FUT_BLOCK_DIM |]- [C.cexp|get_group_id(1) * FUT_BLOCK_DIM + (get_local_id(1) / mulx)|]- [C.cexp|get_group_id(1) * FUT_BLOCK_DIM + (get_local_id(0) / mulx)|]- [C.cexp|get_group_id(0) * FUT_BLOCK_DIM * mulx- + get_local_id(1)- + (get_local_id(0) % mulx) * FUT_BLOCK_DIM+ [C.cexp|group_id_y * FUT_BLOCK_DIM + (local_id_y / mulx)|]+ [C.cexp|group_id_y * FUT_BLOCK_DIM + (local_id_x / mulx)|]+ [C.cexp|group_id_x * FUT_BLOCK_DIM * mulx+ + local_id_y+ + (local_id_x % mulx) * FUT_BLOCK_DIM |]+ (toNumGroups [C.cexp|(width + mulx - 1) / mulx|])+ (toNumGroups [C.cexp|height|]) TransposeSmall -> smallKernel where- bigKernel extraparams x_in_index y_in_index x_out_index y_out_index =+ toNumGroups e = [C.cexp|($exp:e + FUT_BLOCK_DIM - 1) / FUT_BLOCK_DIM|]+ bigKernel extraparams x_in_index y_in_index x_out_index y_out_index ngrpx ngrpy = [C.cfun| // This kernel is optimized to ensure all global reads and writes are coalesced, // and to avoid bank conflicts in shared memory. The shared memory array is sized@@ -129,6 +136,21 @@ // Note that input_size/output_size may not equal width*height if we are dealing with // a truncated array - this happens sometimes for coalescing optimisations. __kernel void $id:kernel_name($params:params) {+ uint num_groups_y = $exp:ngrpy;+ uint num_groups_x = $exp:ngrpx;+ uint num_groups_z = ((uint)get_num_groups(0)) / (num_groups_y * num_groups_x);++ uint local_id_y = ((uint)get_local_id(0)) / FUT_BLOCK_DIM;+ uint local_id_x = ((uint)get_local_id(0)) % FUT_BLOCK_DIM;+ uint group_id_z = ((uint)get_group_id(0)) / (num_groups_y * num_groups_x);+ uint group_id_yx = ((uint)get_group_id(0)) % (num_groups_y * num_groups_x);+ uint group_id_y = group_id_yx / num_groups_x;+ uint group_id_x = group_id_yx % num_groups_x;++ uint global_id_z = group_id_z;+ uint global_id_y = group_id_y * FUT_BLOCK_DIM + local_id_y;+ uint global_id_x = group_id_x * FUT_BLOCK_DIM + local_id_x;+ uint x_index; uint y_index; uint our_array_offset;@@ -138,7 +160,7 @@ idata += idata_offset/sizeof($ty:elem_type); // Adjust the input and output arrays for the third dimension.- our_array_offset = get_global_id(2) * width * height;+ our_array_offset = global_id_z * width * height; odata += our_array_offset; idata += our_array_offset; @@ -150,7 +172,7 @@ if(x_index < width && y_index < height && index_in < input_size) {- block[get_local_id(1)*(FUT_BLOCK_DIM+1)+get_local_id(0)] = idata[index_in];+ block[local_id_y*(FUT_BLOCK_DIM+1)+local_id_x] = idata[index_in]; } barrier(CLK_LOCAL_MEM_FENCE);@@ -163,7 +185,7 @@ if(x_index < height && y_index < width && index_out < output_size) {- odata[index_out] = block[get_local_id(0)*(FUT_BLOCK_DIM+1)+get_local_id(1)];+ odata[index_out] = block[local_id_x*(FUT_BLOCK_DIM+1)+local_id_y]; } }|] where params = [C.cparams|__global $ty:elem_type *odata,@@ -205,4 +227,4 @@ if (get_global_id(0) < input_size) { odata[index_out] = idata[index_in]; }- }|]+}|]
src/Futhark/Internalise/Defunctionalise.hs view
@@ -451,9 +451,12 @@ return ([], e, sv) defuncLet _ [] body (Info rettype) = do (body', sv) <- defuncExp body- case sv of- Dynamic _ -> return ([], body', Dynamic $ fromStruct $ removeShapeAnnotations rettype)- _ -> return ([], body', sv)+ return ([], body', imposeType sv rettype )+ where imposeType Dynamic{} t =+ Dynamic $ fromStruct $ removeShapeAnnotations t+ imposeType (RecordSV fs1) (Record fs2) =+ RecordSV $ M.toList $ M.intersectionWith imposeType (M.fromList fs1) fs2+ imposeType sv _ = sv -- | Defunctionalize an application expression at a given depth of application. -- Calls to dynamic (first-order) functions are preserved at much as possible,@@ -476,7 +479,7 @@ -- Inline certain trivial lifted functions immediately. This is -- purely an optimisation to avoid having the rest of the- -- compiler spend a lot if time processing them (they will end+ -- compiler spend a lot of time processing them (they will end -- up being inlined later anyway). We also try to simplify away -- some let-bindings, to make the generated code look slightly -- more comprehensible.
src/Language/Futhark/Parser/Parser.y view
@@ -277,10 +277,10 @@ : val id TypeParams ':' TypeExpDecl { let L loc (ID name) = $2 in ValSpec name $3 $5 Nothing (srcspan $1 $>) }- | val BindingBinOp ':' TypeExpDecl- { ValSpec $2 [] $4 Nothing (srcspan $1 $>) }- | val BindingUnOp ':' TypeExpDecl- { ValSpec $2 [] $4 Nothing (srcspan $1 $>) }+ | val BindingBinOp TypeParams ':' TypeExpDecl+ { ValSpec $2 $3 $5 Nothing (srcspan $1 $>) }+ | val BindingUnOp TypeParams ':' TypeExpDecl+ { ValSpec $2 $3 $5 Nothing (srcspan $1 $>) } | TypeAbbr { TypeAbbrSpec $1 } | type id TypeParams
src/futhark-bench.hs view
@@ -14,6 +14,7 @@ import Data.Maybe import Data.Semigroup ((<>)) import Data.List+import qualified Data.Map as M import qualified Data.Text as T import qualified Data.Text.IO as T import qualified Data.Text.Encoding as T@@ -25,7 +26,7 @@ import System.Timeout import System.Process.ByteString (readProcessWithExitCode) import System.Exit-import qualified Text.JSON as JSON+import qualified Data.Aeson as JSON import Text.Printf import Text.Regex.TDFA @@ -56,24 +57,23 @@ data DataResult = DataResult String (Either T.Text ([RunResult], T.Text)) data BenchResult = BenchResult FilePath [DataResult] -resultsToJSON :: [BenchResult] -> JSON.JSValue-resultsToJSON = JSON.JSObject . JSON.toJSObject . map benchResultToJSObject+resultsToJSON :: [BenchResult] -> JSON.Value+resultsToJSON = JSON.toJSON . M.fromList . map benchResultToJSObject where benchResultToJSObject :: BenchResult- -> (String, JSON.JSValue)+ -> (String, JSON.Value) benchResultToJSObject (BenchResult prog rs) =- (prog, JSON.JSObject $ JSON.toJSObject- [("datasets", JSON.JSObject $ JSON.toJSObject $- map dataResultToJSObject rs)])+ (prog, JSON.toJSON $ M.fromList+ [("datasets" :: String, M.fromList $ map dataResultToJSObject rs)]) dataResultToJSObject :: DataResult- -> (String, JSON.JSValue)+ -> (String, JSON.Value) dataResultToJSObject (DataResult desc (Left err)) =- (desc, JSON.showJSON err)+ (desc, JSON.toJSON $ show err) dataResultToJSObject (DataResult desc (Right (runtimes, progerr))) =- (desc, JSON.JSObject $ JSON.toJSObject- [("runtimes", JSON.showJSON $ map runMicroseconds runtimes),- ("stderr", JSON.showJSON progerr)])+ (desc, JSON.toJSON $ M.fromList+ [("runtimes" :: String, JSON.toJSON $ map runMicroseconds runtimes),+ ("stderr", JSON.toJSON $ show progerr)]) fork :: (a -> IO b) -> a -> IO (MVar b) fork f x = do cell <- newEmptyMVar@@ -107,7 +107,7 @@ results <- concat <$> mapM (runBenchmark opts) compiled_benchmarks case optJSON opts of Nothing -> return ()- Just file -> writeFile file $ JSON.encode $ resultsToJSON results+ Just file -> LBS.writeFile file $ JSON.encode $ resultsToJSON results when (anyFailed results) exitFailure where ignored f = any (`match` f) $ optIgnoreFiles opts