accelerate-llvm-ptx 1.1.0.1 → 1.4.0.0
raw patch · 91 files changed
Files
- CHANGELOG.md +85/−9
- Data/Array/Accelerate/LLVM/PTX.hs +0/−458
- Data/Array/Accelerate/LLVM/PTX/Analysis/Device.hs +0/−50
- Data/Array/Accelerate/LLVM/PTX/Analysis/Launch.hs +0/−75
- Data/Array/Accelerate/LLVM/PTX/Array/Data.hs +0/−213
- Data/Array/Accelerate/LLVM/PTX/Array/Prim.hs +0/−508
- Data/Array/Accelerate/LLVM/PTX/Array/Remote.hs +0/−163
- Data/Array/Accelerate/LLVM/PTX/Array/Table.hs +0/−60
- Data/Array/Accelerate/LLVM/PTX/CodeGen.hs +0/−47
- Data/Array/Accelerate/LLVM/PTX/CodeGen/Base.hs +0/−408
- Data/Array/Accelerate/LLVM/PTX/CodeGen/Fold.hs +0/−628
- Data/Array/Accelerate/LLVM/PTX/CodeGen/FoldSeg.hs +0/−464
- Data/Array/Accelerate/LLVM/PTX/CodeGen/Generate.hs +0/−60
- Data/Array/Accelerate/LLVM/PTX/CodeGen/Intrinsic.hs +0/−359
- Data/Array/Accelerate/LLVM/PTX/CodeGen/Loop.hs +0/−47
- Data/Array/Accelerate/LLVM/PTX/CodeGen/Map.hs +0/−60
- Data/Array/Accelerate/LLVM/PTX/CodeGen/Permute.hs +0/−366
- Data/Array/Accelerate/LLVM/PTX/CodeGen/Queue.hs +0/−118
- Data/Array/Accelerate/LLVM/PTX/CodeGen/Scan.hs +0/−1335
- Data/Array/Accelerate/LLVM/PTX/Compile.hs +0/−322
- Data/Array/Accelerate/LLVM/PTX/Compile/Cache.hs +0/−40
- Data/Array/Accelerate/LLVM/PTX/Compile/Libdevice.hs +0/−177
- Data/Array/Accelerate/LLVM/PTX/Compile/Libdevice/Load.hs +0/−143
- Data/Array/Accelerate/LLVM/PTX/Compile/Libdevice/TH.hs +0/−188
- Data/Array/Accelerate/LLVM/PTX/Context.hs +0/−147
- Data/Array/Accelerate/LLVM/PTX/Debug.hs +0/−101
- Data/Array/Accelerate/LLVM/PTX/Embed.hs +0/−86
- Data/Array/Accelerate/LLVM/PTX/Execute.hs +0/−595
- Data/Array/Accelerate/LLVM/PTX/Execute/Async.hs +0/−63
- Data/Array/Accelerate/LLVM/PTX/Execute/Environment.hs +0/−22
- Data/Array/Accelerate/LLVM/PTX/Execute/Event.hs +0/−162
- Data/Array/Accelerate/LLVM/PTX/Execute/Event.hs-boot +0/−26
- Data/Array/Accelerate/LLVM/PTX/Execute/Marshal.hs +0/−147
- Data/Array/Accelerate/LLVM/PTX/Execute/Stream.hs +0/−181
- Data/Array/Accelerate/LLVM/PTX/Execute/Stream.hs-boot +0/−32
- Data/Array/Accelerate/LLVM/PTX/Execute/Stream/Reservoir.hs +0/−102
- Data/Array/Accelerate/LLVM/PTX/Foreign.hs +0/−81
- Data/Array/Accelerate/LLVM/PTX/Link.hs +0/−153
- Data/Array/Accelerate/LLVM/PTX/Link/Cache.hs +0/−22
- Data/Array/Accelerate/LLVM/PTX/Link/Object.hs +0/−41
- Data/Array/Accelerate/LLVM/PTX/State.hs +0/−111
- Data/Array/Accelerate/LLVM/PTX/Target.hs +0/−182
- LICENSE +1/−1
- README.md +117/−141
- Setup.hs +0/−2
- accelerate-llvm-ptx.cabal +75/−115
- src/Data/Array/Accelerate/LLVM/PTX.hs +561/−0
- src/Data/Array/Accelerate/LLVM/PTX/Analysis/Device.hs +79/−0
- src/Data/Array/Accelerate/LLVM/PTX/Analysis/Launch.hs +74/−0
- src/Data/Array/Accelerate/LLVM/PTX/Array/Data.hs +205/−0
- src/Data/Array/Accelerate/LLVM/PTX/Array/Prim.hs +391/−0
- src/Data/Array/Accelerate/LLVM/PTX/Array/Remote.hs +167/−0
- src/Data/Array/Accelerate/LLVM/PTX/Array/Table.hs +58/−0
- src/Data/Array/Accelerate/LLVM/PTX/CodeGen.hs +45/−0
- src/Data/Array/Accelerate/LLVM/PTX/CodeGen/Base.hs +825/−0
- src/Data/Array/Accelerate/LLVM/PTX/CodeGen/Fold.hs +616/−0
- src/Data/Array/Accelerate/LLVM/PTX/CodeGen/FoldSeg.hs +476/−0
- src/Data/Array/Accelerate/LLVM/PTX/CodeGen/Generate.hs +62/−0
- src/Data/Array/Accelerate/LLVM/PTX/CodeGen/Intrinsic.hs +360/−0
- src/Data/Array/Accelerate/LLVM/PTX/CodeGen/Loop.hs +48/−0
- src/Data/Array/Accelerate/LLVM/PTX/CodeGen/Map.hs +63/−0
- src/Data/Array/Accelerate/LLVM/PTX/CodeGen/Permute.hs +439/−0
- src/Data/Array/Accelerate/LLVM/PTX/CodeGen/Scan.hs +1300/−0
- src/Data/Array/Accelerate/LLVM/PTX/CodeGen/Stencil.hs +190/−0
- src/Data/Array/Accelerate/LLVM/PTX/CodeGen/Transform.hs +67/−0
- src/Data/Array/Accelerate/LLVM/PTX/Compile.hs +283/−0
- src/Data/Array/Accelerate/LLVM/PTX/Compile/Cache.hs +42/−0
- src/Data/Array/Accelerate/LLVM/PTX/Compile/Libdevice/Load.hs +60/−0
- src/Data/Array/Accelerate/LLVM/PTX/Context.hs +269/−0
- src/Data/Array/Accelerate/LLVM/PTX/Debug.hs +113/−0
- src/Data/Array/Accelerate/LLVM/PTX/Embed.hs +83/−0
- src/Data/Array/Accelerate/LLVM/PTX/Execute.hs +882/−0
- src/Data/Array/Accelerate/LLVM/PTX/Execute/Async.hs +197/−0
- src/Data/Array/Accelerate/LLVM/PTX/Execute/Environment.hs +22/−0
- src/Data/Array/Accelerate/LLVM/PTX/Execute/Event.hs +161/−0
- src/Data/Array/Accelerate/LLVM/PTX/Execute/Event.hs-boot +26/−0
- src/Data/Array/Accelerate/LLVM/PTX/Execute/Marshal.hs +108/−0
- src/Data/Array/Accelerate/LLVM/PTX/Execute/Stream.hs +174/−0
- src/Data/Array/Accelerate/LLVM/PTX/Execute/Stream.hs-boot +32/−0
- src/Data/Array/Accelerate/LLVM/PTX/Execute/Stream/Reservoir.hs +98/−0
- src/Data/Array/Accelerate/LLVM/PTX/Foreign.hs +88/−0
- src/Data/Array/Accelerate/LLVM/PTX/Link.hs +144/−0
- src/Data/Array/Accelerate/LLVM/PTX/Link/Cache.hs +22/−0
- src/Data/Array/Accelerate/LLVM/PTX/Link/Object.hs +46/−0
- src/Data/Array/Accelerate/LLVM/PTX/Pool.hs +193/−0
- src/Data/Array/Accelerate/LLVM/PTX/State.hs +176/−0
- src/Data/Array/Accelerate/LLVM/PTX/Target.hs +83/−0
- src/GHC/Heap/NormalForm.hs +93/−0
- src/System/Process/Extra.hs +48/−0
- test/nofib/Data/Array/Accelerate/LLVM/PTX/NoFib/RunQ.hs +31/−0
- test/nofib/Main.hs +19/−0
CHANGELOG.md view
@@ -7,37 +7,113 @@ Policy (PVP)](https://pvp.haskell.org) +## [1.4.0.0] - ?+### Changed+ * Support for LLVM-16 to 22.+ * Use shuffle instructions for faster warp-level folds and scans.+ * Support new compute capabilities.++### Fixed+ * Device memory was deallocated too early or overwritten [accelerate-llvm#111]+ * CUDA Context double free [accelerate-llvm#113]+ * Execution of stencil boundaries were not properly synchronised+ * Scans sometimes gave incorrect results+ * Various other small correctness issues++### Contributors++Special thanks to those who contributed patches as part of this release:++ * Trevor L. McDonell (@tmcdonell)+ * Tom Smeding (@tomsmeding)+ * David van Balen (@dpvanbalen)+ * Ivo Gabe de Wolff (@ivogabe)+ * Michael Swam (@michael-swan)+ * Travis Whitaker (@TravisWhitaker)+ * Noah Williams (@noahmartinwilliams)+ * Robbert van der Helm (@robbert-vdh)+ * Jann Müller (@j-mueller)++## [1.3.0.0] - 2018-08-27+### Changed+ * Code generation improvements for stencil operations++### Fixed+ * Segmented folds crash or give inconsistent results ([accelerate#423])+ * Synchronisation problems on SM7+ [#436] ++### Contributors++Special thanks to those who contributed patches as part of this release:++ * Trevor L. McDonell (@tmcdonell)+ * Josh Meredith (@JoshMeredith)+ * Ivo Gabe de Wolff (@ivogabe)+ * Lars van den Haak (@sakehl)+ * Joshua Meredith (@JoshMeredith)+++## [1.2.0.0] - 2018-04-03+### Changed+ * `run` variants which do not take an explicit execution context now execute on+ the first available device in an exclusive fashion. Multi-GPU systems can+ specify the default set of GPUs to use with environment variable+ `ACCELERATE_LLVM_PTX_DEVICES` as a list of device ordinals.++### Added+ * support for half-precision floats+ * support for struct-of-array-of-struct representations+ * support 64-bit atomic-add instruction in forward permutations ([#363])+ * support for LLVM-6.0+ * support for GHC-8.4++### Contributors++Special thanks to those who contributed patches as part of this release:++ * Trevor L. McDonell (@tmcdonell)+ * Moritz Kiefer (@cocreature)++ ## [1.1.0.1] - 2018-01-08 ### Fixed- * add support for building with CUDA-9.x+ * add support for building with CUDA-9.x + ## [1.1.0.0] - 2017-09-21 ### Added- * support for GHC-8.2- * caching of compilation results ([accelerate-llvm#17])- * support for ahead-of-time compilation (`runQ` and `runQAsync`)+ * support for GHC-8.2+ * caching of compilation results ([accelerate-llvm#17])+ * support for ahead-of-time compilation (`runQ` and `runQAsync`) ### Changed- * generalise `run1*` to polyvariadic `runN*`+ * generalise `run1*` to polyvariadic `runN*` ### Fixed- * Fixed synchronisation bug in multidimensional reduction- + * Fixed synchronisation bug in multidimensional reduction + ## [1.0.0.1] - 2017-05-25 ### Fixed- * [#386] (partial fix)+ * device kernel image is invalid ([#386]) + ## [1.0.0.0] - 2017-03-31 * initial release +[1.3.0.0]: https://github.com/AccelerateHS/accelerate-llvm/compare/1.2.0.0...v1.3.0.0+[1.2.0.0]: https://github.com/AccelerateHS/accelerate-llvm/compare/1.1.0.1-ptx...1.2.0.0 [1.1.0.1]: https://github.com/AccelerateHS/accelerate-llvm/compare/1.1.0.0...1.1.0.1-ptx [1.1.0.0]: https://github.com/AccelerateHS/accelerate-llvm/compare/1.0.0.0...1.1.0.0 [1.0.0.1]: https://github.com/AccelerateHS/accelerate-llvm/compare/1.0.0.0...1.0.0.1 [1.0.0.0]: https://github.com/AccelerateHS/accelerate-llvm/compare/be7f91295f77434b2103c70aa1cabb6a4f2b09a8...1.0.0.0 [#386]: https://github.com/AccelerateHS/accelerate/issues/386+[#363]: https://github.com/AccelerateHS/accelerate/issues/363 +[#436]: https://github.com/AccelerateHS/accelerate/issues/436 [accelerate-llvm#17]: https://github.com/AccelerateHS/accelerate-llvm/issues/17-+[accelerate#423]: https://github.com/AccelerateHS/accelerate/issues/423+[accelerate-llvm#111]: https://github.com/AccelerateHS/accelerate-llvm/pull/111+[accelerate-llvm#113]: https://github.com/AccelerateHS/accelerate-llvm/pull/113
− Data/Array/Accelerate/LLVM/PTX.hs
@@ -1,458 +0,0 @@-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE CPP #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE TypeSynonymInstances #-}--- |--- Module : Data.Array.Accelerate.LLVM.PTX--- Copyright : [2014..2017] Trevor L. McDonell--- [2014..2014] Vinod Grover (NVIDIA Corporation)--- License : BSD3------ Maintainer : Trevor L. McDonell <tmcdonell@cse.unsw.edu.au>--- Stability : experimental--- Portability : non-portable (GHC extensions)------ This module implements a backend for the /Accelerate/ language targeting--- NVPTX for execution on NVIDIA GPUs. Expressions are on-line translated into--- LLVM code, which is just-in-time executed in parallel on the GPU.-----module Data.Array.Accelerate.LLVM.PTX (-- Acc, Arrays,-- -- * Synchronous execution- run, runWith,- run1, run1With,- runN, runNWith,- stream, streamWith,-- -- * Asynchronous execution- Async,- wait, poll, cancel,-- runAsync, runAsyncWith,- run1Async, run1AsyncWith,- runNAsync, runNAsyncWith,-- -- * Ahead-of-time compilation- runQ, runQWith,- runQAsync, runQAsyncWith,-- -- * Execution targets- PTX, createTargetForDevice, createTargetFromContext,-- -- * Controlling host-side allocation- registerPinnedAllocator, registerPinnedAllocatorWith,--) where---- accelerate-import Data.Array.Accelerate.AST ( PreOpenAfun(..) )-import Data.Array.Accelerate.Array.Sugar ( Arrays )-import Data.Array.Accelerate.Async-import Data.Array.Accelerate.Debug as Debug-import Data.Array.Accelerate.Error-import Data.Array.Accelerate.Smart ( Acc )-import Data.Array.Accelerate.Trafo--import Data.Array.Accelerate.LLVM.Execute.Async ( AsyncR(..) )-import Data.Array.Accelerate.LLVM.Execute.Environment ( AvalR(..) )-import Data.Array.Accelerate.LLVM.PTX.Compile-import Data.Array.Accelerate.LLVM.PTX.Embed ( embedOpenAcc )-import Data.Array.Accelerate.LLVM.PTX.Execute-import Data.Array.Accelerate.LLVM.PTX.Execute.Environment ( Aval )-import Data.Array.Accelerate.LLVM.PTX.Link-import Data.Array.Accelerate.LLVM.PTX.State-import Data.Array.Accelerate.LLVM.PTX.Target-import Data.Array.Accelerate.LLVM.State-import qualified Data.Array.Accelerate.LLVM.PTX.Array.Data as AD-import qualified Data.Array.Accelerate.LLVM.PTX.Context as CT-import qualified Data.Array.Accelerate.LLVM.PTX.Execute.Async as E--import Foreign.CUDA.Driver as CUDA ( CUDAException, mallocHostForeignPtr )---- standard library-import Data.Typeable-import Control.Exception-import Control.Monad.Trans-import System.IO.Unsafe-import Text.Printf-import qualified Language.Haskell.TH as TH-import qualified Language.Haskell.TH.Syntax as TH----- Accelerate: LLVM backend for NVIDIA GPUs--- -------------------------------------------- | Compile and run a complete embedded array program.------ The result is copied back to the host only once the arrays are demanded (or--- the result is forced to normal form). For results consisting of multiple--- components (a tuple of arrays or array of tuples) this applies per primitive--- array. Evaluating the result of 'run' to WHNF will initiate the computation,--- but does not copy the results back from the device.------ /NOTE:/ it is recommended to use 'runN' or 'runQ' whenever possible.----run :: Arrays a => Acc a -> a-run = runWith defaultTarget---- | As 'run', but execute using the specified target rather than using the--- default, automatically selected device.------ Contexts passed to this function may all target to the same device, or to--- separate devices of differing compute capabilities.----runWith :: Arrays a => PTX -> Acc a -> a-runWith target a- = unsafePerformIO- $ wait =<< runAsyncWith target a----- | As 'run', but run the computation asynchronously and return immediately--- without waiting for the result. The status of the computation can be queried--- using 'wait', 'poll', and 'cancel'.------ Note that a CUDA context can be active on only one host thread at a time. If--- you want to execute multiple computations in parallel, on the same or--- different devices, use 'runAsyncWith'.----runAsync :: Arrays a => Acc a -> IO (Async a)-runAsync = runAsyncWith defaultTarget---- | As 'runWith', but execute asynchronously. Be sure not to destroy the context,--- or attempt to attach it to a different host thread, before all outstanding--- operations have completed.----runAsyncWith :: Arrays a => PTX -> Acc a -> IO (Async a)-runAsyncWith target a = asyncBound execute- where- !acc = convertAccWith config a- execute = do- dumpGraph acc- evalPTX target $ do- acc `seq` dumpSimplStats- build <- phase "compile" (compileAcc acc)- exec <- phase "link" (linkAcc build)- res <- phase "execute" (executeAcc exec >>= AD.copyToHostLazy)- return res----- | This is 'runN', specialised to an array program of one argument.----run1 :: (Arrays a, Arrays b) => (Acc a -> Acc b) -> a -> b-run1 = run1With defaultTarget---- | As 'run1', but execute using the specified target rather than using the--- default, automatically selected device.----run1With :: (Arrays a, Arrays b) => PTX -> (Acc a -> Acc b) -> a -> b-run1With = runNWith----- | Prepare and execute an embedded array program.------ This function can be used to improve performance in cases where the array--- program is constant between invocations, because it enables us to bypass--- front-end conversion stages and move directly to the execution phase. If you--- have a computation applied repeatedly to different input data, use this,--- specifying any changing aspects of the computation via the input parameters.--- If the function is only evaluated once, this is equivalent to 'run'.------ In order to use 'runN' you must express your Accelerate program as a function--- of array terms:------ > f :: (Arrays a, Arrays b, ... Arrays c) => Acc a -> Acc b -> ... -> Acc c------ This function then returns the compiled version of 'f':------ > runN f :: (Arrays a, Arrays b, ... Arrays c) => a -> b -> ... -> c------ At an example, rather than:------ > step :: Acc (Vector a) -> Acc (Vector b)--- > step = ...--- >--- > simulate :: Vector a -> Vector b--- > simulate xs = run $ step (use xs)------ Instead write:------ > simulate = runN step------ You can use the debugging options to check whether this is working--- successfully. For example, running with the @-ddump-phases@ flag should show--- that the compilation steps only happen once, not on the second and subsequent--- invocations of 'simulate'. Note that this typically relies on GHC knowing--- that it can lift out the function returned by 'runN' and reuse it.------ As with 'run', the resulting array(s) are only copied back to the host once--- they are actually demanded (forced to normal form). Thus, splitting a program--- into multiple 'runN' steps does not imply transferring intermediate--- computations back and forth between host and device. However note that--- Accelerate is not able to optimise (fuse) across separate 'runN' invocations.------ See the programs in the 'accelerate-examples' package for examples.------ See also 'runQ', which compiles the Accelerate program at _Haskell_ compile--- time, thus eliminating the runtime overhead altogether.----runN :: Afunction f => f -> AfunctionR f-runN = runNWith defaultTarget---- | As 'runN', but execute using the specified target device.----runNWith :: Afunction f => PTX -> f -> AfunctionR f-runNWith target f = exec- where- !acc = convertAfunWith config f- !afun = unsafePerformIO $ do- dumpGraph acc- evalPTX target $ do- build <- phase "compile" (compileAfun acc) >>= dumpStats- link <- phase "link" (linkAfun build)- return link- !exec = go afun (return Aempty)-- go :: ExecOpenAfun PTX aenv t -> LLVM PTX (Aval aenv) -> t- go (Alam l) k = \arrs ->- let k' = do aenv <- k- AsyncR _ a <- E.async (AD.useRemoteAsync arrs)- return (aenv `Apush` a)- in go l k'- go (Abody b) k = unsafePerformIO . phase "execute" . evalPTX target $ do- aenv <- k- r <- E.async (executeOpenAcc b aenv)- AD.copyToHostLazy =<< E.get r----- | As 'run1', but the computation is executed asynchronously.----run1Async :: (Arrays a, Arrays b) => (Acc a -> Acc b) -> a -> IO (Async b)-run1Async = run1AsyncWith defaultTarget---- | As 'run1With', but execute asynchronously.----run1AsyncWith :: (Arrays a, Arrays b) => PTX -> (Acc a -> Acc b) -> a -> IO (Async b)-run1AsyncWith = runNAsyncWith----- | As 'runN', but execute asynchronously.----runNAsync :: (Afunction f, RunAsync r, AfunctionR f ~ RunAsyncR r) => f -> r-runNAsync = runNAsyncWith defaultTarget---- | As 'runNWith', but execute asynchronously.----runNAsyncWith :: (Afunction f, RunAsync r, AfunctionR f ~ RunAsyncR r) => PTX -> f -> r-runNAsyncWith target f = runAsync' target afun (return Aempty)- where- !acc = convertAfunWith config f- !afun = unsafePerformIO $ do- dumpGraph acc- evalPTX target $ do- build <- phase "compile" (compileAfun acc) >>= dumpStats- exec <- phase "link" (linkAfun build)- return exec--class RunAsync f where- type RunAsyncR f- runAsync' :: PTX -> ExecOpenAfun PTX aenv (RunAsyncR f) -> LLVM PTX (Aval aenv) -> f--instance RunAsync b => RunAsync (a -> b) where- type RunAsyncR (a -> b) = a -> RunAsyncR b- runAsync' _ Abody{} _ _ = error "runAsync: function oversaturated"- runAsync' target (Alam l) k arrs =- let k' = do aenv <- k- AsyncR _ a <- E.async (AD.useRemoteAsync arrs)- return (aenv `Apush` a)- in runAsync' target l k'--instance RunAsync (IO (Async b)) where- type RunAsyncR (IO (Async b)) = b- runAsync' _ Alam{} _ = error "runAsync: function not fully applied"- runAsync' target (Abody b) k = asyncBound . phase "execute" . evalPTX target $ do- aenv <- k- r <- E.async (executeOpenAcc b aenv)- AD.copyToHostLazy =<< E.get r----- | Stream a lazily read list of input arrays through the given program,--- collecting results as we go.----stream :: (Arrays a, Arrays b) => (Acc a -> Acc b) -> [a] -> [b]-stream = streamWith defaultTarget---- | As 'stream', but execute using the specified target.----streamWith :: (Arrays a, Arrays b) => PTX -> (Acc a -> Acc b) -> [a] -> [b]-streamWith target f arrs = map go arrs- where- !go = run1With target f----- | Ahead-of-time compilation for an embedded array program.------ This function will generate, compile, and link into the final executable,--- code to execute the given Accelerate computation /at Haskell compile time/.--- This eliminates any runtime overhead associated with the other @run*@--- operations. The generated code will be compiled for the current (default) GPU--- architecture.------ Since the Accelerate program will be generated at Haskell compile time,--- construction of the Accelerate program, in particular via meta-programming,--- will be limited to operations available to that phase. Also note that any--- arrays which are embedded into the program via 'Data.Array.Accelerate.use'--- will be stored as part of the final executable.------ Usage of this function in your program is similar to that of 'runN'. First,--- express your Accelerate program as a function of array terms:------ > f :: (Arrays a, Arrays b, ... Arrays c) => Acc a -> Acc b -> ... -> Acc c------ This function then returns a compiled version of @f@ as a Template Haskell--- splice, to be added into your program at Haskell compile time:------ > {-# LANGUAGE TemplateHaskell #-}--- >--- > f' :: a -> b -> ... -> c--- > f' = $( runQ f )------ Note that at the splice point the usage of @f@ must monomorphic; i.e. the--- types @a@, @b@ and @c@ must be at some known concrete type.------ See the <https://github.com/tmcdonell/lulesh-accelerate lulesh-accelerate>--- project for an example.------ [/Note:/]------ Due to <https://ghc.haskell.org/trac/ghc/ticket/13587 GHC#13587>, this--- currently must be as an /untyped/ splice.------ The correct type of this function is similar to that of 'runN':------ > runQ :: Afunction f => f -> Q (TExp (AfunctionR f))------ @since 1.1.0.0----runQ :: Afunction f => f -> TH.ExpQ-runQ = runQ' [| unsafePerformIO |] [| defaultTarget |]---- | Ahead-of-time analogue of 'runNWith'. See 'runQ' for more information.------ /NOTE:/ The supplied (at runtime) target must be compatible with the--- architecture that this function was compiled for (the 'defaultTarget' of the--- compiling machine). Running on a device with the same compute capability is--- best, but this should also be forward compatible to newer architectures.------ The correct type of this function is:------ > runQWith :: Afunction f => f -> Q (TExp (PTX -> AfunctionR f))------ @since 1.1.0.0----runQWith :: Afunction f => f -> TH.ExpQ-runQWith f = do- target <- TH.newName "target"- TH.lamE [TH.varP target] (runQ' [| unsafePerformIO |] (TH.varE target) f)----- | Ahead-of-time analogue of 'runNAsync'. See 'runQ' for more information.------ The correct type of this function is:------ > runQAsync :: (Afunction f, RunAsync r, AfunctionR f ~ RunAsyncR r) => f -> Q (TExp r)------ @since 1.1.0.0----runQAsync :: Afunction f => f -> TH.ExpQ-runQAsync = runQ' [| async |] [| defaultTarget |]---- | Ahead-of-time analogue of 'runNAsyncWith'. See 'runQWith' for more information.------ The correct type of this function is:------ > runQAsyncWith :: (Afunction f, RunAsync r, AfunctionR f ~ RunAsyncR r) => f -> Q (TExp (PTX -> r))------ @since 1.1.0.0----runQAsyncWith :: Afunction f => f -> TH.ExpQ-runQAsyncWith f = do- target <- TH.newName "target"- TH.lamE [TH.varP target] (runQ' [| async |] (TH.varE target) f)---runQ' :: Afunction f => TH.ExpQ -> TH.ExpQ -> f -> TH.ExpQ-runQ' using target f = do- afun <- let acc = convertAfunWith config f- in TH.runIO $ do- dumpGraph acc- evalPTX defaultTarget $- phase "compile" (compileAfun acc) >>= dumpStats- let- go :: Typeable aenv => CompiledOpenAfun PTX aenv t -> [TH.PatQ] -> [TH.ExpQ] -> [TH.StmtQ] -> TH.ExpQ- go (Alam lam) xs as stmts = do- x <- TH.newName "x" -- lambda bound variable- a <- TH.newName "a" -- local array name- s <- TH.bindS (TH.conP 'AsyncR [TH.wildP, TH.varP a]) [| E.async (AD.useRemoteAsync $(TH.varE x)) |]- go lam (TH.varP x : xs) (TH.varE a : as) (return s : stmts)-- go (Abody body) xs as stmts =- let aenv = foldr (\a gamma -> [| $gamma `Apush` $a |] ) [| Aempty |] as- eval = TH.noBindS [| AD.copyToHostLazy =<< E.get =<< E.async (executeOpenAcc $(TH.unTypeQ (embedOpenAcc defaultTarget body)) $aenv) |]- in- TH.lamE (reverse xs) [| $using . phase "execute" . evalPTX $target $- $(TH.doE (reverse (eval : stmts))) |]- --- go afun [] [] []----- How the Accelerate program should be evaluated.------ TODO: make sharing/fusion runtime configurable via debug flags or otherwise.----config :: Phase-config = phases- { convertOffsetOfSegment = True- }----- Controlling host-side allocation--- ------------------------------------ | Configure the default execution target to allocate all future host-side--- arrays using (CUDA) pinned memory. Any newly allocated arrays will be--- page-locked and directly accessible from the device, enabling high-speed--- (asynchronous) DMA.------ Note that since the amount of available pageable memory will be reduced,--- overall system performance can suffer.----registerPinnedAllocator :: IO ()-registerPinnedAllocator = registerPinnedAllocatorWith defaultTarget----- | As with 'registerPinnedAllocator', but configure the given execution--- context.----registerPinnedAllocatorWith :: PTX -> IO ()-registerPinnedAllocatorWith target =- AD.registerForeignPtrAllocator $ \bytes ->- CT.withContext (ptxContext target) (CUDA.mallocHostForeignPtr [] bytes)- `catch`- \e -> $internalError "registerPinnedAlocator" (show (e :: CUDAException))----- Debugging--- =========--dumpStats :: MonadIO m => a -> m a-dumpStats x = dumpSimplStats >> return x--phase :: MonadIO m => String -> m a -> m a-phase n go = timed dump_phases (\wall cpu -> printf "phase %s: %s" n (elapsed wall cpu)) go-
− Data/Array/Accelerate/LLVM/PTX/Analysis/Device.hs
@@ -1,50 +0,0 @@--- |--- Module : Data.Array.Accelerate.LLVM.PTX.Analysis.Device--- Copyright : [2008..2017] Manuel M T Chakravarty, Gabriele Keller--- [2009..2017] Trevor L. McDonell--- License : BSD3------ Maintainer : Trevor L. McDonell <tmcdonell@cse.unsw.edu.au>--- Stability : experimental--- Portability : non-portable (GHC extensions)-----module Data.Array.Accelerate.LLVM.PTX.Analysis.Device- where--import Data.Ord-import Data.List-import Data.Function-import Foreign.CUDA.Driver.Device-import Foreign.CUDA.Analysis.Device-import qualified Foreign.CUDA.Driver as CUDA----- Select the best of the available CUDA capable devices. This prefers devices--- with higher compute capability, followed by maximum throughput. This does not--- take into account any other factors, such as whether the device is currently--- in use by another process.------ Ignore the possibility of emulation-mode devices, as this has been deprecated--- as of CUDA v3.0 (compute-capability == 9999.9999)----selectBestDevice :: IO (Device, DeviceProperties)-selectBestDevice = do- dev <- mapM CUDA.device . enumFromTo 0 . subtract 1 =<< CUDA.count- prop <- mapM CUDA.props dev- return . minimumBy (flip cmp `on` snd) $ zip dev prop- where- compute = computeCapability- flops d = multiProcessorCount d * coresPerMultiProcessor d * clockRate d- cmp x y- | compute x == compute y = comparing flops x y- | otherwise = comparing compute x y----- Number of CUDA cores per streaming multiprocessor for a given architecture--- revision. This is the number of SIMD arithmetic units per multiprocessor,--- executing in lockstep in half-warp groupings (16 ALUs).----coresPerMultiProcessor :: DeviceProperties -> Int-coresPerMultiProcessor = coresPerMP . deviceResources-
− Data/Array/Accelerate/LLVM/PTX/Analysis/Launch.hs
@@ -1,75 +0,0 @@-{-# LANGUAGE QuasiQuotes #-}-{-# LANGUAGE TemplateHaskell #-}--- |--- Module : Data.Array.Accelerate.LLVM.PTX.Analysis.Launch--- Copyright : [2008..2017] Manuel M T Chakravarty, Gabriele Keller--- [2009..2017] Trevor L. McDonell--- License : BSD3------ Maintainer : Trevor L. McDonell <tmcdonell@cse.unsw.edu.au>--- Stability : experimental--- Portability : non-portable (GHC extensions)-----module Data.Array.Accelerate.LLVM.PTX.Analysis.Launch (-- DeviceProperties, Occupancy, LaunchConfig,- simpleLaunchConfig, launchConfig,- multipleOf, multipleOfQ,--) where--import Foreign.CUDA.Analysis as CUDA-import Language.Haskell.TH----- | Given information about the resource usage of the compiled kernel,--- determine the optimum launch parameters.----type LaunchConfig- = Int -- maximum #threads per block- -> Int -- #registers per thread- -> Int -- #bytes of static shared memory- -> ( Occupancy- , Int -- thread block size- , Int -> Int -- grid size required to process the given input size- , Int -- #bytes dynamic shared memory- , Q (TExp (Int -> Int))- )---- | Analytics for a simple kernel which requires no additional shared memory or--- have other constraints on launch configuration. The smallest thread block--- size, in increments of a single warp, with the highest occupancy is used.----simpleLaunchConfig :: DeviceProperties -> LaunchConfig-simpleLaunchConfig dev = launchConfig dev (decWarp dev) (const 0) multipleOf multipleOfQ----- | Determine the optimal kernel launch configuration for a kernel.----launchConfig- :: DeviceProperties -- ^ Device architecture to optimise for- -> [Int] -- ^ Thread block sizes to consider- -> (Int -> Int) -- ^ Shared memory (#bytes) as a function of thread block size- -> (Int -> Int -> Int) -- ^ Determine grid size for input size 'n' (first arg) over thread blocks of size 'm' (second arg)- -> Q (TExp (Int -> Int -> Int))- -> LaunchConfig-launchConfig dev candidates dynamic_smem grid_size grid_sizeQ maxThreads registers static_smem =- let- (cta, occ) = optimalBlockSizeOf dev (filter (<= maxThreads) candidates) (const registers) smem- maxGrid = multiProcessorCount dev * activeThreadBlocks occ- grid n = maxGrid `min` grid_size n cta- smem n = static_smem + dynamic_smem n- gridQ = [|| \n -> (maxGrid::Int) `min` $$grid_sizeQ (n::Int) (cta::Int) ||]- in- ( occ, cta, grid, dynamic_smem cta, gridQ )----- | The next highest multiple of 'y' from 'x'.----multipleOf :: Int -> Int -> Int-multipleOf x y = ((x + y - 1) `quot` y)--multipleOfQ :: Q (TExp (Int -> Int -> Int))-multipleOfQ = [|| multipleOf ||]-
− Data/Array/Accelerate/LLVM/PTX/Array/Data.hs
@@ -1,213 +0,0 @@-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE RecordWildCards #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}--- |--- Module : Data.Array.Accelerate.LLVM.PTX.Array.Data--- Copyright : [2014..2017] Trevor L. McDonell--- [2014..2014] Vinod Grover (NVIDIA Corporation)--- License : BSD3------ Maintainer : Trevor L. McDonell <tmcdonell@cse.unsw.edu.au>--- Stability : experimental--- Portability : non-portable (GHC extensions)-----module Data.Array.Accelerate.LLVM.PTX.Array.Data (-- module Data.Array.Accelerate.LLVM.Array.Data,- module Data.Array.Accelerate.LLVM.PTX.Array.Data,--) where---- accelerate-import Data.Array.Accelerate.Array.Sugar-import Data.Array.Accelerate.Array.Unique ( UniqueArray(..) )-import Data.Array.Accelerate.Lifetime ( Lifetime(..) )-import qualified Data.Array.Accelerate.Array.Representation as R--import Data.Array.Accelerate.LLVM.Array.Data-import Data.Array.Accelerate.LLVM.State--import Data.Array.Accelerate.LLVM.PTX.State-import Data.Array.Accelerate.LLVM.PTX.Target-import Data.Array.Accelerate.LLVM.PTX.Execute.Async-import qualified Data.Array.Accelerate.LLVM.PTX.Array.Prim as Prim---- standard library-import Control.Applicative-import Control.Monad.State ( liftIO, gets )-import Data.Typeable-import Foreign.Ptr-import Foreign.Storable-import System.IO.Unsafe-import Prelude----- Instance of remote array memory management for the PTX target----instance Remote PTX where-- {-# INLINEABLE allocateRemote #-}- allocateRemote !sh = do- arr <- liftIO $ allocateArray sh- runArray arr (\ad -> Prim.mallocArray (size sh) ad >> return ad)-- {-# INLINEABLE useRemoteR #-}- useRemoteR !n !mst !ad = do- case mst of- Nothing -> Prim.useArray n ad- Just st -> Prim.useArrayAsync st n ad-- {-# INLINEABLE copyToRemoteR #-}- copyToRemoteR !from !to !mst !ad = do- case mst of- Nothing -> Prim.pokeArrayR from to ad- Just st -> Prim.pokeArrayAsyncR st from to ad-- {-# INLINEABLE copyToHostR #-}- copyToHostR !from !to !mst !ad = do- case mst of- Nothing -> Prim.peekArrayR from to ad- Just st -> Prim.peekArrayAsyncR st from to ad-- {-# INLINEABLE copyToPeerR #-}- copyToPeerR !from !to !dst !mst !ad = do- case mst of- Nothing -> Prim.copyArrayPeerR (ptxContext dst) (ptxMemoryTable dst) from to ad- Just st -> Prim.copyArrayPeerAsyncR (ptxContext dst) (ptxMemoryTable dst) st from to ad-- {-# INLINEABLE indexRemote #-}- indexRemote arr i =- runIndexArray Prim.indexArray arr i----- | Copy an array from the remote device to the host. Although the Accelerate--- program is hyper-strict and will evaluate the computation as soon as any part--- of it is demanded, the individual array payloads are copied back to the host--- _only_ as they are demanded by the Haskell program. This has several--- consequences:------ 1. If the device has multiple memcpy engines, only one will be used. The--- transfers are however associated with a non-default stream.------ 2. Using 'seq' to force an Array to head-normal form will initiate the--- computation, but not transfer the results back to the host. Requesting--- an array element or using 'deepseq' to force to normal form is required--- to actually transfer the data.----copyToHostLazy- :: Arrays arrs- => arrs- -> LLVM PTX arrs-copyToHostLazy arrs = do- ptx <- gets llvmTarget- liftIO $ runArrays arrs $ \(Array sh adata) ->- let- n :: Int- n = R.size sh-- peekR :: (ArrayElt e, ArrayPtrs e ~ Ptr a, Storable a, Typeable a, Typeable e)- => ArrayData e- -> UniqueArray a- -> IO (UniqueArray a)- peekR ad (UniqueArray uid (Lifetime ref weak fp)) = do- fp' <- unsafeInterleaveIO $- evalPTX ptx $ do- s <- fork- copyToHostR 0 n (Just s) ad- e <- checkpoint s- block e- join s- return fp- return $ UniqueArray uid (Lifetime ref weak fp')-- runR :: ArrayEltR e -> ArrayData e -> IO (ArrayData e)- runR ArrayEltRunit AD_Unit = return AD_Unit- runR (ArrayEltRpair aeR2 aeR1) (AD_Pair ad2 ad1) = AD_Pair <$> runR aeR2 ad2 <*> runR aeR1 ad1- runR ArrayEltRint ad@(AD_Int ua) = AD_Int <$> peekR ad ua- runR ArrayEltRint8 ad@(AD_Int8 ua) = AD_Int8 <$> peekR ad ua- runR ArrayEltRint16 ad@(AD_Int16 ua) = AD_Int16 <$> peekR ad ua- runR ArrayEltRint32 ad@(AD_Int32 ua) = AD_Int32 <$> peekR ad ua- runR ArrayEltRint64 ad@(AD_Int64 ua) = AD_Int64 <$> peekR ad ua- runR ArrayEltRword ad@(AD_Word ua) = AD_Word <$> peekR ad ua- runR ArrayEltRword8 ad@(AD_Word8 ua) = AD_Word8 <$> peekR ad ua- runR ArrayEltRword16 ad@(AD_Word16 ua) = AD_Word16 <$> peekR ad ua- runR ArrayEltRword32 ad@(AD_Word32 ua) = AD_Word32 <$> peekR ad ua- runR ArrayEltRword64 ad@(AD_Word64 ua) = AD_Word64 <$> peekR ad ua- runR ArrayEltRcshort ad@(AD_CShort ua) = AD_CShort <$> peekR ad ua- runR ArrayEltRcushort ad@(AD_CUShort ua) = AD_CUShort <$> peekR ad ua- runR ArrayEltRcint ad@(AD_CInt ua) = AD_CInt <$> peekR ad ua- runR ArrayEltRcuint ad@(AD_CUInt ua) = AD_CUInt <$> peekR ad ua- runR ArrayEltRclong ad@(AD_CLong ua) = AD_CLong <$> peekR ad ua- runR ArrayEltRculong ad@(AD_CULong ua) = AD_CULong <$> peekR ad ua- runR ArrayEltRcllong ad@(AD_CLLong ua) = AD_CLLong <$> peekR ad ua- runR ArrayEltRcullong ad@(AD_CULLong ua) = AD_CULLong <$> peekR ad ua- runR ArrayEltRfloat ad@(AD_Float ua) = AD_Float <$> peekR ad ua- runR ArrayEltRdouble ad@(AD_Double ua) = AD_Double <$> peekR ad ua- runR ArrayEltRcfloat ad@(AD_CFloat ua) = AD_CFloat <$> peekR ad ua- runR ArrayEltRcdouble ad@(AD_CDouble ua) = AD_CDouble <$> peekR ad ua- runR ArrayEltRbool ad@(AD_Bool ua) = AD_Bool <$> peekR ad ua- runR ArrayEltRchar ad@(AD_Char ua) = AD_Char <$> peekR ad ua- runR ArrayEltRcchar ad@(AD_CChar ua) = AD_CChar <$> peekR ad ua- runR ArrayEltRcschar ad@(AD_CSChar ua) = AD_CSChar <$> peekR ad ua- runR ArrayEltRcuchar ad@(AD_CUChar ua) = AD_CUChar <$> peekR ad ua- in- Array sh <$> runR arrayElt adata----- | Clone an array into a newly allocated array on the device.----cloneArrayAsync- :: (Shape sh, Elt e)- => Stream- -> Array sh e- -> LLVM PTX (Array sh e)-cloneArrayAsync stream arr@(Array _ src) = do- out@(Array _ dst) <- allocateRemote sh- copyR arrayElt src dst- return out- where- sh = shape arr- n = size sh-- copyR :: ArrayEltR e -> ArrayData e -> ArrayData e -> LLVM PTX ()- copyR ArrayEltRunit _ _ = return ()- copyR (ArrayEltRpair aeR1 aeR2) ad1 ad2 = copyR aeR1 (fstArrayData ad1) (fstArrayData ad2) >>- copyR aeR2 (sndArrayData ad1) (sndArrayData ad2)- --- copyR ArrayEltRint ad1 ad2 = copyPrim ad1 ad2- copyR ArrayEltRint8 ad1 ad2 = copyPrim ad1 ad2- copyR ArrayEltRint16 ad1 ad2 = copyPrim ad1 ad2- copyR ArrayEltRint32 ad1 ad2 = copyPrim ad1 ad2- copyR ArrayEltRint64 ad1 ad2 = copyPrim ad1 ad2- copyR ArrayEltRword ad1 ad2 = copyPrim ad1 ad2- copyR ArrayEltRword8 ad1 ad2 = copyPrim ad1 ad2- copyR ArrayEltRword16 ad1 ad2 = copyPrim ad1 ad2- copyR ArrayEltRword32 ad1 ad2 = copyPrim ad1 ad2- copyR ArrayEltRword64 ad1 ad2 = copyPrim ad1 ad2- copyR ArrayEltRfloat ad1 ad2 = copyPrim ad1 ad2- copyR ArrayEltRdouble ad1 ad2 = copyPrim ad1 ad2- copyR ArrayEltRbool ad1 ad2 = copyPrim ad1 ad2- copyR ArrayEltRchar ad1 ad2 = copyPrim ad1 ad2- copyR ArrayEltRcshort ad1 ad2 = copyPrim ad1 ad2- copyR ArrayEltRcushort ad1 ad2 = copyPrim ad1 ad2- copyR ArrayEltRcint ad1 ad2 = copyPrim ad1 ad2- copyR ArrayEltRcuint ad1 ad2 = copyPrim ad1 ad2- copyR ArrayEltRclong ad1 ad2 = copyPrim ad1 ad2- copyR ArrayEltRculong ad1 ad2 = copyPrim ad1 ad2- copyR ArrayEltRcllong ad1 ad2 = copyPrim ad1 ad2- copyR ArrayEltRcullong ad1 ad2 = copyPrim ad1 ad2- copyR ArrayEltRcfloat ad1 ad2 = copyPrim ad1 ad2- copyR ArrayEltRcdouble ad1 ad2 = copyPrim ad1 ad2- copyR ArrayEltRcchar ad1 ad2 = copyPrim ad1 ad2- copyR ArrayEltRcschar ad1 ad2 = copyPrim ad1 ad2- copyR ArrayEltRcuchar ad1 ad2 = copyPrim ad1 ad2-- copyPrim- :: (ArrayElt e, ArrayPtrs e ~ Ptr a, Typeable e, Storable a, Typeable a)- => ArrayData e- -> ArrayData e- -> LLVM PTX ()- copyPrim a1 a2 = Prim.copyArrayAsync stream n a1 a2-
− Data/Array/Accelerate/LLVM/PTX/Array/Prim.hs
@@ -1,508 +0,0 @@-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE CPP #-}-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE TypeOperators #-}--- |--- Module : Data.Array.Accelerate.LLVM.PTX.Array.Prim--- Copyright : [2014..2017] Trevor L. McDonell--- [2014..2014] Vinod Grover (NVIDIA Corporation)--- License : BSD3------ Maintainer : Trevor L. McDonell <tmcdonell@cse.unsw.edu.au>--- Stability : experimental--- Portability : non-portable (GHC extensions)-----module Data.Array.Accelerate.LLVM.PTX.Array.Prim (-- mallocArray,- memsetArray, memsetArrayAsync,- useArray, useArrayAsync,- indexArray,- peekArray, peekArrayR, peekArrayAsync, peekArrayAsyncR,- pokeArray, pokeArrayR, pokeArrayAsync, pokeArrayAsyncR,- copyArray, copyArrayR, copyArrayAsync, copyArrayAsyncR,- copyArrayPeer, copyArrayPeerR, copyArrayPeerAsync, copyArrayPeerAsyncR,- withDevicePtr,--) where---- accelerate-import Data.Array.Accelerate.Array.Data-import Data.Array.Accelerate.Error-import Data.Array.Accelerate.Lifetime-import Data.Array.Accelerate.Type--import Data.Array.Accelerate.LLVM.State--import Data.Array.Accelerate.LLVM.PTX.Context-import Data.Array.Accelerate.LLVM.PTX.Target-import Data.Array.Accelerate.LLVM.PTX.Execute.Event-import Data.Array.Accelerate.LLVM.PTX.Execute.Stream-import Data.Array.Accelerate.LLVM.PTX.Array.Table-import Data.Array.Accelerate.LLVM.PTX.Array.Remote as Remote-import qualified Data.Array.Accelerate.LLVM.PTX.Debug as Debug---- CUDA-import qualified Foreign.CUDA.Driver as CUDA-import qualified Foreign.CUDA.Driver.Stream as CUDA---- standard library-import Control.Exception-import Control.Monad-import Control.Monad.State-import Data.Typeable-import Foreign.Ptr-import Foreign.Storable-import GHC.TypeLits-import Text.Printf-import Prelude hiding ( lookup )----- | Allocate a device-side array associated with the given host array. If the--- allocation fails due to a memory error, we attempt some last-ditch memory--- cleanup before trying again.----{-# INLINEABLE mallocArray #-}-mallocArray- :: forall e a. (ArrayElt e, ArrayPtrs e ~ Ptr a, Storable a, Typeable a, Typeable e)- => Int- -> ArrayData e- -> LLVM PTX ()-mallocArray !n !ad = do- message ("mallocArray: " ++ showBytes (n * sizeOf (undefined::a)))- void $ malloc ad n False----- | A combination of 'mallocArray' and 'pokeArray', that allocates remotes--- memory and uploads an existing array. This is specialised because we tell the--- allocator that the host-side array is frozen, and thus it is safe to evict--- the remote memory and re-upload the data at any time.----{-# INLINEABLE useArray #-}-useArray- :: forall e a. (ArrayElt e, ArrayPtrs e ~ Ptr a, Storable a, Typeable a, Typeable e)- => Int- -> ArrayData e- -> LLVM PTX ()-useArray !n !ad =- blocking $ \st -> useArrayAsync st n ad--{-# INLINEABLE useArrayAsync #-}-useArrayAsync- :: forall e a. (ArrayElt e, ArrayPtrs e ~ Ptr a, Storable a, Typeable a, Typeable e)- => Stream- -> Int- -> ArrayData e- -> LLVM PTX ()-useArrayAsync !st !n !ad = do- alloc <- malloc ad n True- when alloc $ pokeArrayAsync st n ad----- | Copy data from the host to an existing array on the device----{-# INLINEABLE pokeArray #-}-pokeArray- :: forall e a. (ArrayElt e, ArrayPtrs e ~ Ptr a, Typeable e, Storable a, Typeable a)- => Int- -> ArrayData e- -> LLVM PTX ()-pokeArray !n !ad =- blocking $ \st -> pokeArrayAsync st n ad--{-# INLINEABLE pokeArrayAsync #-}-pokeArrayAsync- :: forall e a. (ArrayElt e, ArrayPtrs e ~ Ptr a, Typeable e, Storable a, Typeable a)- => Stream- -> Int- -> ArrayData e- -> LLVM PTX ()-pokeArrayAsync !stream !n !ad = do- let !src = CUDA.HostPtr (ptrsOfArrayData ad)- !bytes = n * sizeOf (undefined :: a)- !st = unsafeGetValue stream- --- withDevicePtr ad $ \dst ->- nonblocking stream $- transfer "pokeArray" bytes (Just st) $ CUDA.pokeArrayAsync n src dst (Just st)- liftIO (touchLifetime stream)- liftIO (Debug.didCopyBytesToRemote (fromIntegral bytes))---{-# INLINEABLE pokeArrayR #-}-pokeArrayR- :: forall e a. (ArrayElt e, ArrayPtrs e ~ Ptr a, Typeable e, Typeable a, Storable a)- => Int- -> Int- -> ArrayData e- -> LLVM PTX ()-pokeArrayR !from !to !ad =- blocking $ \st -> pokeArrayAsyncR st from to ad--{-# INLINEABLE pokeArrayAsyncR #-}-pokeArrayAsyncR- :: forall e a. (ArrayElt e, ArrayPtrs e ~ Ptr a, Typeable e, Typeable a, Storable a)- => Stream- -> Int- -> Int- -> ArrayData e- -> LLVM PTX ()-pokeArrayAsyncR !stream !from !to !ad = do- let !n = to - from- !bytes = n * sizeOf (undefined :: a)- !offset = from * sizeOf (undefined :: a)- !src = CUDA.HostPtr (ptrsOfArrayData ad)- !st = unsafeGetValue stream- --- withDevicePtr ad $ \dst ->- nonblocking stream $- transfer "pokeArray" bytes (Just st) $- CUDA.pokeArrayAsync n (src `CUDA.plusHostPtr` offset) (dst `CUDA.plusDevPtr` offset) (Just st)- liftIO (touchLifetime stream)- liftIO (Debug.didCopyBytesToRemote (fromIntegral bytes))----- | Read a single element from an array at a given row-major index----{-# INLINEABLE indexArray #-}-indexArray- :: forall e a. (ArrayElt e, ArrayPtrs e ~ Ptr a, Typeable e, Typeable a, Storable a)- => ArrayData e- -> Int- -> LLVM PTX a-indexArray !ad !i =- blocking $ \stream ->- withDevicePtr ad $ \src -> liftIO $- bracket (CUDA.mallocHostArray [] 1) CUDA.freeHost $ \dst -> do- let !st = unsafeGetValue stream- message $ "indexArray: " ++ showBytes (sizeOf (undefined::a))- Debug.didCopyBytesFromRemote (fromIntegral (sizeOf (undefined::a)))- CUDA.peekArrayAsync 1 (src `CUDA.advanceDevPtr` i) dst (Just st)- CUDA.block st- touchLifetime stream- r <- peek (CUDA.useHostPtr dst)- return (Nothing, r)----- | Copy data from the device into the associated host-side Accelerate array----{-# INLINEABLE peekArray #-}-peekArray- :: forall e a. (ArrayElt e, ArrayPtrs e ~ Ptr a, Typeable e, Typeable a, Storable a)- => Int- -> ArrayData e- -> LLVM PTX ()-peekArray !n !ad =- blocking $ \st -> peekArrayAsync st n ad--{-# INLINEABLE peekArrayAsync #-}-peekArrayAsync- :: forall e a. (ArrayElt e, ArrayPtrs e ~ Ptr a, Typeable e, Typeable a, Storable a)- => Stream- -> Int- -> ArrayData e- -> LLVM PTX ()-peekArrayAsync !stream !n !ad = do- let !bytes = n * sizeOf (undefined :: a)- !dst = CUDA.HostPtr (ptrsOfArrayData ad)- !st = unsafeGetValue stream- --- withDevicePtr ad $ \src ->- nonblocking stream $- transfer "peekArray" bytes (Just st) $ CUDA.peekArrayAsync n src dst (Just st)- liftIO (touchLifetime stream)- liftIO (Debug.didCopyBytesFromRemote (fromIntegral bytes))--{-# INLINEABLE peekArrayR #-}-peekArrayR- :: forall e a. (ArrayElt e, ArrayPtrs e ~ Ptr a, Typeable a, Typeable e, Storable a)- => Int- -> Int- -> ArrayData e- -> LLVM PTX ()-peekArrayR !from !to !ad =- blocking $ \st -> peekArrayAsyncR st from to ad--{-# INLINEABLE peekArrayAsyncR #-}-peekArrayAsyncR- :: forall e a. (ArrayElt e, ArrayPtrs e ~ Ptr a, Typeable e, Typeable a, Storable a)- => Stream- -> Int- -> Int- -> ArrayData e- -> LLVM PTX ()-peekArrayAsyncR !stream !from !to !ad = do- let !n = to - from- !bytes = n * sizeOf (undefined :: a)- !offset = from * sizeOf (undefined :: a)- !dst = CUDA.HostPtr (ptrsOfArrayData ad)- !st = unsafeGetValue stream- --- withDevicePtr ad $ \src ->- nonblocking stream $- transfer "peekArray" bytes (Just st) $- CUDA.peekArrayAsync n (src `CUDA.plusDevPtr` offset) (dst `CUDA.plusHostPtr` offset) (Just st)- liftIO (touchLifetime stream)- liftIO (Debug.didCopyBytesFromRemote (fromIntegral bytes))----- | Copy data between arrays in the same context----{-# INLINEABLE copyArray #-}-copyArray- :: forall e a. (ArrayElt e, ArrayPtrs e ~ Ptr a, Typeable e, Storable a, Typeable a)- => Int- -> ArrayData e- -> ArrayData e- -> LLVM PTX ()-copyArray !n !src !dst =- blocking $ \st -> copyArrayAsync st n src dst--{-# INLINEABLE copyArrayAsync #-}-copyArrayAsync- :: forall e a. (ArrayElt e, ArrayPtrs e ~ Ptr a, Typeable e, Storable a, Typeable a)- => Stream- -> Int- -> ArrayData e- -> ArrayData e- -> LLVM PTX ()-copyArrayAsync !stream !n !ad_src !ad_dst = do- let !bytes = n * sizeOf (undefined :: a)- !st = unsafeGetValue stream- --- withDevicePtr ad_src $ \src -> do- e <- withDevicePtr ad_dst $ \dst -> do- (e,()) <- nonblocking stream- $ transfer "copyArray" bytes (Just st) $ CUDA.copyArrayAsync n src dst (Just st)- return (e,e)- return (e,())- liftIO (touchLifetime stream)--{-# INLINEABLE copyArrayR #-}-copyArrayR- :: forall e a. (ArrayElt e, ArrayPtrs e ~ Ptr a, Typeable e, Storable a, Typeable a)- => Int- -> Int- -> ArrayData e- -> ArrayData e- -> LLVM PTX ()-copyArrayR !from !to !src !dst =- blocking $ \st -> copyArrayAsyncR st from to src dst--{-# INLINEABLE copyArrayAsyncR #-}-copyArrayAsyncR- :: forall e a. (ArrayElt e, ArrayPtrs e ~ Ptr a, Typeable e, Storable a, Typeable a)- => Stream- -> Int- -> Int- -> ArrayData e- -> ArrayData e- -> LLVM PTX ()-copyArrayAsyncR !stream !from !to !ad_src !ad_dst = do- let !n = to - from- !bytes = n * sizeOf (undefined :: a)- !offset = from * sizeOf (undefined :: a)- !st = unsafeGetValue stream- --- withDevicePtr ad_src $ \src -> do- e <- withDevicePtr ad_dst $ \dst -> do- (e,()) <- nonblocking stream- $ transfer "copyArray" bytes (Just st)- $ CUDA.copyArrayAsync n (src `CUDA.plusDevPtr` offset) (dst `CUDA.plusDevPtr` offset) (Just st)- return (e,e)- return (e,())- liftIO (touchLifetime stream)----- | Copy data from one device context into a _new_ array on the second context.--- It is an error if the destination array already exists.----{-# INLINEABLE copyArrayPeer #-}-copyArrayPeer- :: forall e a. (ArrayElt e, ArrayPtrs e ~ Ptr a, Storable a, Typeable a)- => Context -- destination context- -> MemoryTable -- destination memory table- -> Int- -> ArrayData e- -> LLVM PTX ()-copyArrayPeer !ctx2 !mt2 !n !ad =- blocking $ \st -> copyArrayPeerAsync ctx2 mt2 st n ad--{-# INLINEABLE copyArrayPeerAsync #-}-copyArrayPeerAsync- :: forall e a. (ArrayElt e, ArrayPtrs e ~ Ptr a, Storable a, Typeable a)- => Context -- destination context- -> MemoryTable -- destination memory table- -> Stream- -> Int- -> ArrayData e- -> LLVM PTX ()-copyArrayPeerAsync = error "copyArrayPeerAsync"-{---copyArrayPeerAsync !ctx2 !mt2 !st !n !ad = do- let !bytes = n * sizeOf (undefined :: a)- src <- devicePtr mt1 ad- dst <- mallocArray ctx2 mt2 n ad- transfer "copyArrayPeer" bytes (Just st) $- CUDA.copyArrayPeerAsync n src (deviceContext ctx1) dst (deviceContext ctx2) (Just st)---}---- | Copy part of an array from one device context to another. Both source and--- destination arrays must exist.----{-# INLINEABLE copyArrayPeerR #-}-copyArrayPeerR- :: forall e a. (ArrayElt e, ArrayPtrs e ~ Ptr a, Storable a, Typeable a)- => Context -- destination context- -> MemoryTable -- destination memory table- -> Int- -> Int- -> ArrayData e- -> LLVM PTX ()-copyArrayPeerR !ctx2 !mt2 !from !to !ad =- blocking $ \st -> copyArrayPeerAsyncR ctx2 mt2 st from to ad--{-# INLINEABLE copyArrayPeerAsyncR #-}-copyArrayPeerAsyncR- :: forall e a. (ArrayElt e, ArrayPtrs e ~ Ptr a, Storable a, Typeable a)- => Context -- destination context- -> MemoryTable -- destination memory table- -> Stream- -> Int- -> Int- -> ArrayData e- -> LLVM PTX ()-copyArrayPeerAsyncR = error "copyArrayPeerAsyncR"-{---copyArrayPeerAsyncR !ctx2 !mt2 !st !from !to !ad = do- let !n = to - from- !bytes = n * sizeOf (undefined :: a)- !offset = from * sizeOf (undefined :: a)- src <- devicePtr mt1 ad :: IO (CUDA.DevicePtr a)- dst <- devicePtr mt2 ad :: IO (CUDA.DevicePtr a)- transfer "copyArrayPeer" bytes (Just st) $- CUDA.copyArrayPeerAsync n (src `CUDA.plusDevPtr` offset) (deviceContext ctx1)- (dst `CUDA.plusDevPtr` offset) (deviceContext ctx2) (Just st)---}----- | Set elements of the array to the specified value. Only 8-, 16-, and 32-bit--- values are supported.----{-# INLINEABLE memsetArray #-}-memsetArray- :: forall e a. (ArrayElt e, ArrayPtrs e ~ Ptr a, Typeable e, Typeable a, Storable a, BitSize a <= 32)- => Int- -> a- -> ArrayData e- -> LLVM PTX ()-memsetArray !n !v !ad =- blocking $ \st -> memsetArrayAsync st n v ad--{-# INLINEABLE memsetArrayAsync #-}-memsetArrayAsync- :: forall e a. (ArrayElt e, ArrayPtrs e ~ Ptr a, Typeable e, Typeable a, Storable a, BitSize a <= 32)- => Stream- -> Int- -> a- -> ArrayData e- -> LLVM PTX ()-memsetArrayAsync !stream !n !v !ad = do- let !bytes = n * sizeOf (undefined :: a)- !st = unsafeGetValue stream- --- withDevicePtr ad $ \ptr ->- nonblocking stream $- transfer "memset" bytes (Just st) $ CUDA.memsetAsync ptr n v (Just st)- liftIO (touchLifetime stream)---{----- | Lookup the device memory associated with a given host array----{-# INLINEABLE devicePtr #-}-devicePtr- :: (ArrayElt e, ArrayPtrs e ~ Ptr a, Typeable a, Typeable b)- => ArrayData e- -> LLVM PTX (CUDA.DevicePtr b)-devicePtr !ad = do- undefined- {--- mv <- Table.lookup mt ad- case mv of- Just v -> return v- Nothing -> $internalError "devicePtr" "lost device memory"- --}---}---- Auxiliary--- ------------- | Lookup the device memory associated with a given host array and do--- something with it.----{-# INLINEABLE withDevicePtr #-}-withDevicePtr- :: (ArrayElt e, ArrayPtrs e ~ Ptr a, Typeable e, Typeable a, Storable a)- => ArrayData e- -> (CUDA.DevicePtr a -> LLVM PTX (Maybe Event, r))- -> LLVM PTX r-withDevicePtr !ad !f = do- mr <- withRemote ad f- case mr of- Nothing -> $internalError "withDevicePtr" "array does not exist on the device"- Just r -> return r---- | Execute the given operation in a new stream, and wait for the operation to--- complete before returning.----{-# INLINE blocking #-}-blocking :: (Stream -> LLVM PTX a) -> LLVM PTX a-blocking !f =- streaming f $ \e r -> do- liftIO $ block e- return r---- | Execute a (presumable asynchronous) operation and return the result--- together with an event recorded immediately afterwards in the given stream.----{-# INLINE nonblocking #-}-nonblocking :: Stream -> LLVM PTX a -> LLVM PTX (Maybe Event, a)-nonblocking !stream !f = do- r <- f- e <- waypoint stream- return (Just e, r)----- Debug--- -------{-# INLINE showBytes #-}-showBytes :: Int -> String-showBytes x = Debug.showFFloatSIBase (Just 0) 1024 (fromIntegral x :: Double) "B"--{-# INLINE trace #-}-trace :: MonadIO m => String -> m a -> m a-trace msg next = liftIO (Debug.traceIO Debug.dump_gc ("gc: " ++ msg)) >> next--{-# INLINE message #-}-message :: MonadIO m => String -> m ()-message s = s `trace` return ()--{-# INLINE transfer #-}-transfer :: MonadIO m => String -> Int -> Maybe CUDA.Stream -> IO () -> m ()-transfer name bytes stream action- = let showRate x t = Debug.showFFloatSIBase (Just 3) 1024 (fromIntegral x / t) "B/s"- msg wall cpu gpu = printf "gc: %s: %s bytes @ %s, %s"- name- (showBytes bytes)- (showRate bytes wall)- (Debug.elapsed wall cpu gpu)- in- liftIO (Debug.timed Debug.dump_gc msg stream action)-
− Data/Array/Accelerate/LLVM/PTX/Array/Remote.hs
@@ -1,163 +0,0 @@-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeFamilies #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}--- |--- Module : Data.Array.Accelerate.LLVM.PTX.Array.Remote--- Copyright : [2014..2017] Trevor L. McDonell--- License : BSD3------ Maintainer : Trevor L. McDonell <tmcdonell@cse.unsw.edu.au>--- Stability : experimental--- Portability : non-portable (GHC extensions)-----module Data.Array.Accelerate.LLVM.PTX.Array.Remote (-- withRemote, malloc,--) where--import Data.Array.Accelerate.LLVM.State-import Data.Array.Accelerate.LLVM.PTX.Target-import {-# SOURCE #-} Data.Array.Accelerate.LLVM.PTX.Execute.Event-import {-# SOURCE #-} Data.Array.Accelerate.LLVM.PTX.Execute.Stream--import Data.Array.Accelerate.Lifetime-import Data.Array.Accelerate.Array.Data-import qualified Data.Array.Accelerate.Array.Remote as Remote-import qualified Data.Array.Accelerate.LLVM.PTX.Debug as Debug--import Foreign.CUDA.Driver.Error-import qualified Foreign.CUDA.Ptr as CUDA-import qualified Foreign.CUDA.Driver as CUDA-import qualified Foreign.CUDA.Driver.Stream as CUDA--import Control.Exception-import Control.Monad.State-import Data.Typeable-import Foreign.Ptr-import Foreign.Storable-import Text.Printf----- Events signal once a computation has completed----instance Remote.Task (Maybe Event) where- completed Nothing = return True- completed (Just e) = query e--instance Remote.RemoteMemory (LLVM PTX) where- type RemotePtr (LLVM PTX) = CUDA.DevicePtr- --- mallocRemote n- | n <= 0 = return (Just CUDA.nullDevPtr)- | otherwise = liftIO $ do- ep <- try (CUDA.mallocArray n)- case ep of- Right p -> do liftIO (Debug.didAllocateBytesRemote (fromIntegral n))- return (Just p)- Left (ExitCode OutOfMemory) -> do return Nothing- Left e -> do message ("malloc failed with error: " ++ show e)- throwIO e-- peekRemote n src ad =- let bytes = n * sizeOfPtr src- dst = CUDA.HostPtr (ptrsOfArrayData ad)- in- blocking $ \stream ->- withLifetime stream $ \st -> do- Debug.didCopyBytesFromRemote (fromIntegral bytes)- transfer "peekRemote" bytes (Just st) $ CUDA.peekArrayAsync n src dst (Just st)-- pokeRemote n dst ad =- let bytes = n * sizeOfPtr dst- src = CUDA.HostPtr (ptrsOfArrayData ad)- in- blocking $ \stream ->- withLifetime stream $ \st -> do- Debug.didCopyBytesToRemote (fromIntegral bytes)- transfer "pokeRemote" bytes (Just st) $ CUDA.pokeArrayAsync n src dst (Just st)-- castRemotePtr _ = CUDA.castDevPtr- availableRemoteMem = liftIO $ fst `fmap` CUDA.getMemInfo- totalRemoteMem = liftIO $ snd `fmap` CUDA.getMemInfo- remoteAllocationSize = return 4096------ | Allocate an array in the remote memory space sufficient to hold the given--- number of elements, and associated with the given host side array. Space will--- be freed from the remote device if necessary.----{-# INLINEABLE malloc #-}-malloc- :: (ArrayElt e, ArrayPtrs e ~ Ptr a, Typeable e, Typeable a, Storable a)- => ArrayData e- -> Int- -> Bool- -> LLVM PTX Bool-malloc !ad !n !frozen = do- PTX{..} <- gets llvmTarget- Remote.malloc ptxMemoryTable ad frozen n----- | Lookup up the remote array pointer for the given host-side array----{-# INLINEABLE withRemote #-}-withRemote- :: (ArrayElt e, ArrayPtrs e ~ Ptr a, Typeable e, Typeable a, Storable a)- => ArrayData e- -> (CUDA.DevicePtr a -> LLVM PTX (Maybe Event, r))- -> LLVM PTX (Maybe r)-withRemote !ad !f = do- PTX{..} <- gets llvmTarget- Remote.withRemote ptxMemoryTable ad f----- Auxiliary--- ------------- | Execute the given operation in a new stream, and wait for the operation to--- complete before returning.----{-# INLINE blocking #-}-blocking :: (Stream -> IO a) -> LLVM PTX a-blocking !fun =- streaming (liftIO . fun) $ \e r -> do- liftIO $ block e- return r--{-# INLINE sizeOfPtr #-}-sizeOfPtr :: forall a. Storable a => CUDA.DevicePtr a -> Int-sizeOfPtr _ = sizeOf (undefined :: a)---- Debugging--- -----------{-# INLINE showBytes #-}-showBytes :: Int -> String-showBytes x = Debug.showFFloatSIBase (Just 0) 1024 (fromIntegral x :: Double) "B"--{-# INLINE trace #-}-trace :: String -> IO a -> IO a-trace msg next = Debug.traceIO Debug.dump_gc ("gc: " ++ msg) >> next--{-# INLINE message #-}-message :: String -> IO ()-message s = s `trace` return ()--{-# INLINE transfer #-}-transfer :: String -> Int -> Maybe CUDA.Stream -> IO () -> IO ()-transfer name bytes stream action- = let showRate x t = Debug.showFFloatSIBase (Just 3) 1024 (fromIntegral x / t) "B/s"- msg wall cpu gpu = printf "gc: %s: %s bytes @ %s, %s"- name- (showBytes bytes)- (showRate bytes wall)- (Debug.elapsed wall cpu gpu)- in- Debug.timed Debug.dump_gc msg stream action-
− Data/Array/Accelerate/LLVM/PTX/Array/Table.hs
@@ -1,60 +0,0 @@-{-# LANGUAGE BangPatterns #-}--- |--- Module : Data.Array.Accelerate.LLVM.PTX.Array.Table--- Copyright : [2014..2017] Trevor L. McDonell--- License : BSD3------ Maintainer : Trevor L. McDonell <tmcdonell@cse.unsw.edu.au>--- Stability : experimental--- Portability : non-portable (GHC extensions)-----module Data.Array.Accelerate.LLVM.PTX.Array.Table (-- MemoryTable,- new,--) where--import Data.Array.Accelerate.LLVM.PTX.Context ( Context, withContext )-import qualified Data.Array.Accelerate.Array.Remote as Remote-import qualified Data.Array.Accelerate.LLVM.PTX.Debug as Debug-import {-# SOURCE #-} Data.Array.Accelerate.LLVM.PTX.Execute.Event--import qualified Foreign.CUDA.Ptr as CUDA-import qualified Foreign.CUDA.Driver as CUDA--import Text.Printf----- Remote memory tables. This builds upon the LRU-cached memory tables provided--- by the base Accelerate package.----type MemoryTable = Remote.MemoryTable CUDA.DevicePtr (Maybe Event)----- | Create a new PTX memory table. This is specific to a given PTX target, as--- devices arrays are unique to a CUDA context.----{-# INLINEABLE new #-}-new :: Context -> IO MemoryTable-new !ctx = Remote.new freeRemote- where- freeRemote :: CUDA.DevicePtr a -> IO ()- freeRemote !ptr = do- message (printf "freeRemote %s" (show ptr))- withContext ctx (CUDA.free ptr)----- Debugging--- -----------{-# INLINE trace #-}-trace :: String -> IO a -> IO a-trace msg next = Debug.traceIO Debug.dump_gc ("gc: " ++ msg) >> next--{-# INLINE message #-}-message :: String -> IO ()-message s = s `trace` return ()--
− Data/Array/Accelerate/LLVM/PTX/CodeGen.hs
@@ -1,47 +0,0 @@-{-# OPTIONS_GHC -fno-warn-orphans #-}--- |--- Module : Data.Array.Accelerate.LLVM.PTX.CodeGen--- Copyright : [2014..2017] Trevor L. McDonell--- [2014..2014] Vinod Grover (NVIDIA Corporation)--- License : BSD3------ Maintainer : Trevor L. McDonell <tmcdonell@cse.unsw.edu.au>--- Stability : experimental--- Portability : non-portable (GHC extensions)-----module Data.Array.Accelerate.LLVM.PTX.CodeGen (-- KernelMetadata(..),--) where---- accelerate-import Data.Array.Accelerate.LLVM.CodeGen--import Data.Array.Accelerate.LLVM.PTX.CodeGen.Base-import Data.Array.Accelerate.LLVM.PTX.CodeGen.Fold-import Data.Array.Accelerate.LLVM.PTX.CodeGen.FoldSeg-import Data.Array.Accelerate.LLVM.PTX.CodeGen.Generate-import Data.Array.Accelerate.LLVM.PTX.CodeGen.Intrinsic ()-import Data.Array.Accelerate.LLVM.PTX.CodeGen.Map-import Data.Array.Accelerate.LLVM.PTX.CodeGen.Permute-import Data.Array.Accelerate.LLVM.PTX.CodeGen.Scan-import Data.Array.Accelerate.LLVM.PTX.Target---instance Skeleton PTX where- map ptx _ = mkMap ptx- generate ptx _ = mkGenerate ptx- fold ptx _ = mkFold ptx- fold1 ptx _ = mkFold1 ptx- foldSeg ptx _ = mkFoldSeg ptx- fold1Seg ptx _ = mkFold1Seg ptx- scanl ptx _ = mkScanl ptx- scanl1 ptx _ = mkScanl1 ptx- scanl' ptx _ = mkScanl' ptx- scanr ptx _ = mkScanr ptx- scanr1 ptx _ = mkScanr1 ptx- scanr' ptx _ = mkScanr' ptx- permute ptx _ = mkPermute ptx-
− Data/Array/Accelerate/LLVM/PTX/CodeGen/Base.hs
@@ -1,408 +0,0 @@-{-# LANGUAGE GADTs #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE ViewPatterns #-}--- |--- Module : Data.Array.Accelerate.LLVM.PTX.CodeGen.Base--- Copyright : [2014..2017] Trevor L. McDonell--- License : BSD3------ Maintainer : Trevor L. McDonell <tmcdonell@cse.unsw.edu.au>--- Stability : experimental--- Portability : non-portable (GHC extensions)-----module Data.Array.Accelerate.LLVM.PTX.CodeGen.Base (-- -- Types- DeviceProperties, KernelMetadata(..),-- -- Thread identifiers- blockDim, gridDim, threadIdx, blockIdx, warpSize,- gridSize, globalThreadIdx,- gangParam,-- -- Other intrinsics- laneId, warpId,- laneMask_eq, laneMask_lt, laneMask_le, laneMask_gt, laneMask_ge,- atomicAdd_f,-- -- Barriers and synchronisation- __syncthreads,- __threadfence_block, __threadfence_grid,-- -- Shared memory- staticSharedMem,- dynamicSharedMem,- sharedMemAddrSpace,-- -- Kernel definitions- (+++),- makeOpenAcc, makeOpenAccWith,--) where---- llvm-import LLVM.AST.Type.AddrSpace-import LLVM.AST.Type.Constant-import LLVM.AST.Type.Global-import LLVM.AST.Type.Instruction-import LLVM.AST.Type.Instruction.Volatile-import LLVM.AST.Type.Metadata-import LLVM.AST.Type.Name-import LLVM.AST.Type.Operand-import LLVM.AST.Type.Representation-import qualified LLVM.AST.Global as LLVM-import qualified LLVM.AST.Constant as LLVM hiding ( type' )-import qualified LLVM.AST.Linkage as LLVM-import qualified LLVM.AST.Name as LLVM-import qualified LLVM.AST.Type as LLVM---- accelerate-import Data.Array.Accelerate.Analysis.Type-import Data.Array.Accelerate.Array.Sugar ( Elt, Vector, eltType )-import Data.Array.Accelerate.Error--import Data.Array.Accelerate.LLVM.CodeGen.Arithmetic as A-import Data.Array.Accelerate.LLVM.CodeGen.Base-import Data.Array.Accelerate.LLVM.CodeGen.Constant-import Data.Array.Accelerate.LLVM.CodeGen.Downcast-import Data.Array.Accelerate.LLVM.CodeGen.IR-import Data.Array.Accelerate.LLVM.CodeGen.Module-import Data.Array.Accelerate.LLVM.CodeGen.Monad-import Data.Array.Accelerate.LLVM.CodeGen.Ptr-import Data.Array.Accelerate.LLVM.CodeGen.Sugar-import Data.Array.Accelerate.LLVM.CodeGen.Type--import Data.Array.Accelerate.LLVM.PTX.Analysis.Launch-import Data.Array.Accelerate.LLVM.PTX.Context-import Data.Array.Accelerate.LLVM.PTX.Target---- standard library-import Control.Applicative-import Control.Monad ( void )-import Data.String-import Text.Printf-import Prelude as P----- Thread identifiers--- ---------------------- | Read the builtin registers that store CUDA thread and grid identifiers------ <https://github.com/llvm-mirror/llvm/blob/master/include/llvm/IR/IntrinsicsNVVM.td>----specialPTXReg :: Label -> CodeGen (IR Int32)-specialPTXReg f =- call (Body type' f) [NoUnwind, ReadNone]--blockDim, gridDim, threadIdx, blockIdx, warpSize :: CodeGen (IR Int32)-blockDim = specialPTXReg "llvm.nvvm.read.ptx.sreg.ntid.x"-gridDim = specialPTXReg "llvm.nvvm.read.ptx.sreg.nctaid.x"-threadIdx = specialPTXReg "llvm.nvvm.read.ptx.sreg.tid.x"-blockIdx = specialPTXReg "llvm.nvvm.read.ptx.sreg.ctaid.x"-warpSize = specialPTXReg "llvm.nvvm.read.ptx.sreg.warpsize"--laneId :: CodeGen (IR Int32)-laneId = specialPTXReg "llvm.nvvm.read.ptx.sreg.laneid"--laneMask_eq, laneMask_lt, laneMask_le, laneMask_gt, laneMask_ge :: CodeGen (IR Int32)-laneMask_eq = specialPTXReg "llvm.nvvm.read.ptx.sreg.lanemask.eq"-laneMask_lt = specialPTXReg "llvm.nvvm.read.ptx.sreg.lanemask.lt"-laneMask_le = specialPTXReg "llvm.nvvm.read.ptx.sreg.lanemask.le"-laneMask_gt = specialPTXReg "llvm.nvvm.read.ptx.sreg.lanemask.gt"-laneMask_ge = specialPTXReg "llvm.nvvm.read.ptx.sreg.lanemask.ge"----- | NOTE: The special register %warpid as volatile value and is not guaranteed--- to be constant over the lifetime of a thread or thread block.------ http://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#sm-id-and-warp-id------ http://docs.nvidia.com/cuda/parallel-thread-execution/index.html#special-registers-warpid------ We might consider passing in the (constant) warp size from device properties,--- so that the division can be optimised to a shift.----warpId :: CodeGen (IR Int32)-warpId = do- tid <- threadIdx- ws <- warpSize- A.quot integralType tid ws--_warpId :: CodeGen (IR Int32)-_warpId = specialPTXReg "llvm.ptx.read.warpid"----- | The size of the thread grid------ > gridDim.x * blockDim.x----gridSize :: CodeGen (IR Int32)-gridSize = do- ncta <- gridDim- nt <- blockDim- mul numType ncta nt----- | The global thread index------ > blockDim.x * blockIdx.x + threadIdx.x----globalThreadIdx :: CodeGen (IR Int32)-globalThreadIdx = do- ntid <- blockDim- ctaid <- blockIdx- tid <- threadIdx- --- u <- mul numType ntid ctaid- v <- add numType tid u- return v----- | Generate function parameters that will specify the first and last (linear)--- index of the array this kernel should evaluate.----gangParam :: (IR Int32, IR Int32, [LLVM.Parameter])-gangParam =- let t = scalarType- start = "ix.start"- end = "ix.end"- in- (local t start, local t end, [ scalarParameter t start, scalarParameter t end ] )----- Barriers and synchronisation--- -------------------------------- | Call a builtin CUDA synchronisation intrinsic----barrier :: Label -> CodeGen ()-barrier f = void $ call (Body VoidType f) [NoUnwind, NoDuplicate, Convergent]----- | Wait until all threads in the thread block have reached this point and all--- global and shared memory accesses made by these threads prior to--- __syncthreads() are visible to all threads in the block.------ <http://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#synchronization-functions>----__syncthreads :: CodeGen ()-__syncthreads = barrier "llvm.nvvm.barrier0"----- | Ensure that all writes to shared and global memory before the call to--- __threadfence_block() are observed by all threads in the *block* of the--- calling thread as occurring before all writes to shared and global memory--- made by the calling thread after the call.------ <http://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#memory-fence-functions>----__threadfence_block :: CodeGen ()-__threadfence_block = barrier "llvm.nvvm.membar.cta"----- | As __threadfence_block(), but the synchronisation is for *all* thread blocks.--- In CUDA this is known simply as __threadfence().----__threadfence_grid :: CodeGen ()-__threadfence_grid = barrier "llvm.nvvm.membar.gl"----- Atomic functions--- -------------------- LLVM provides atomic instructions for integer arguments only. CUDA provides--- additional support for atomic add on floating point types, which can be--- accessed through the following intrinsics.------ Double precision is only supported on Compute 6.0 devices and later. LLVM-4.0--- currently lacks support for this intrinsic, however it may be possible to use--- inline assembly.------ <https://github.com/AccelerateHS/accelerate/issues/363>----atomicAdd_f :: FloatingType a -> Operand (Ptr a) -> Operand a -> CodeGen ()-atomicAdd_f t addr val =- let- width :: Int- width =- case t of- TypeFloat{} -> 32- TypeDouble{} -> 64- TypeCFloat{} -> 32- TypeCDouble{} -> 64-- addrspace :: Word32- (t_addr, t_val, addrspace) =- case typeOf addr of- PrimType ta@(PtrPrimType (ScalarPrimType tv) (AddrSpace as))- -> (ta, tv, as)- _ -> $internalError "atomicAdd" "unexpected operand type"-- t_ret = PrimType (ScalarPrimType t_val)- fun = fromString $ printf "llvm.nvvm.atomic.load.add.f%d.p%df%d" width addrspace width- in- void $ call (Lam t_addr addr (Lam (ScalarPrimType t_val) val (Body t_ret fun))) [NoUnwind]----- Shared memory--- ---------------sharedMemAddrSpace :: AddrSpace-sharedMemAddrSpace = AddrSpace 3--sharedMemVolatility :: Volatility-sharedMemVolatility = Volatile----- Declare a new statically allocated array in the __shared__ memory address--- space, with enough storage to contain the given number of elements.----staticSharedMem- :: forall e. Elt e- => Word64- -> CodeGen (IRArray (Vector e))-staticSharedMem n = do- ad <- go (eltType (undefined::e))- return $ IRArray { irArrayShape = IR (OP_Pair OP_Unit (OP_Int (integral integralType (P.fromIntegral n))))- , irArrayData = IR ad- , irArrayAddrSpace = sharedMemAddrSpace- , irArrayVolatility = sharedMemVolatility- }- where- go :: TupleType s -> CodeGen (Operands s)- go UnitTuple = return OP_Unit- go (PairTuple t1 t2) = OP_Pair <$> go t1 <*> go t2- go tt@(SingleTuple t) = do- -- Declare a new global reference for the statically allocated array- -- located in the __shared__ memory space.- nm <- freshName- sm <- return $ ConstantOperand $ GlobalReference (PrimType (PtrPrimType (ArrayType n t) sharedMemAddrSpace)) nm- declare $ LLVM.globalVariableDefaults- { LLVM.addrSpace = sharedMemAddrSpace- , LLVM.type' = LLVM.ArrayType n (downcast t)- , LLVM.linkage = LLVM.External- , LLVM.name = downcast nm- , LLVM.alignment = 4 `P.max` P.fromIntegral (sizeOf tt)- }-- -- Return a pointer to the first element of the __shared__ memory array.- -- We do this rather than just returning the global reference directly due- -- to how __shared__ memory needs to be indexed with the GEP instruction.- p <- instr' $ GetElementPtr sm [num numType 0, num numType 0 :: Operand Int32]- q <- instr' $ PtrCast (PtrPrimType (ScalarPrimType t) sharedMemAddrSpace) p-- return $ ir' t (unPtr q)----- External declaration in shared memory address space. This must be declared in--- order to access memory allocated dynamically by the CUDA driver. This results--- in the following global declaration:------ > @__shared__ = external addrspace(3) global [0 x i8]----initialiseDynamicSharedMemory :: CodeGen (Operand (Ptr Word8))-initialiseDynamicSharedMemory = do- declare $ LLVM.globalVariableDefaults- { LLVM.addrSpace = sharedMemAddrSpace- , LLVM.type' = LLVM.ArrayType 0 (LLVM.IntegerType 8)- , LLVM.linkage = LLVM.External- , LLVM.name = LLVM.Name "__shared__"- , LLVM.alignment = 4- }- return $ ConstantOperand $ GlobalReference (PrimType (PtrPrimType (ArrayType 0 scalarType) sharedMemAddrSpace)) "__shared__"----- Declared a new dynamically allocated array in the __shared__ memory space--- with enough space to contain the given number of elements.----dynamicSharedMem- :: forall e int. (Elt e, IsIntegral int)- => IR int -- number of array elements- -> IR int -- #bytes of shared memory the have already been allocated- -> CodeGen (IRArray (Vector e))-dynamicSharedMem n@(op integralType -> m) (op integralType -> offset) = do- smem <- initialiseDynamicSharedMemory- let- go :: TupleType s -> Operand int -> CodeGen (Operand int, Operands s)- go UnitTuple i = return (i, OP_Unit)- go (PairTuple t2 t1) i0 = do- (i1, p1) <- go t1 i0- (i2, p2) <- go t2 i1- return $ (i2, OP_Pair p2 p1)- go (SingleTuple t) i = do- p <- instr' $ GetElementPtr smem [num numType 0, i] -- TLM: note initial zero index!!- q <- instr' $ PtrCast (PtrPrimType (ScalarPrimType t) sharedMemAddrSpace) p- a <- instr' $ Mul numType m (integral integralType (P.fromIntegral (sizeOf (SingleTuple t))))- b <- instr' $ Add numType i a- return (b, ir' t (unPtr q))- --- (_, ad) <- go (eltType (undefined::e)) offset- IR sz <- A.fromIntegral integralType (numType :: NumType Int) n- return $ IRArray { irArrayShape = IR $ OP_Pair OP_Unit sz- , irArrayData = IR ad- , irArrayAddrSpace = sharedMemAddrSpace- , irArrayVolatility = sharedMemVolatility- }----- Global kernel definitions--- ---------------------------data instance KernelMetadata PTX = KM_PTX LaunchConfig---- | Combine kernels into a single program----(+++) :: IROpenAcc PTX aenv a -> IROpenAcc PTX aenv a -> IROpenAcc PTX aenv a-IROpenAcc k1 +++ IROpenAcc k2 = IROpenAcc (k1 ++ k2)----- | Create a single kernel program with the default launch configuration.----makeOpenAcc- :: PTX- -> Label- -> [LLVM.Parameter]- -> CodeGen ()- -> CodeGen (IROpenAcc PTX aenv a)-makeOpenAcc (deviceProperties . ptxContext -> dev) =- makeOpenAccWith (simpleLaunchConfig dev)---- | Create a single kernel program with the given launch analysis information.----makeOpenAccWith- :: LaunchConfig- -> Label- -> [LLVM.Parameter]- -> CodeGen ()- -> CodeGen (IROpenAcc PTX aenv a)-makeOpenAccWith config name param kernel = do- body <- makeKernel config name param kernel- return $ IROpenAcc [body]---- | Create a complete kernel function by running the code generation process--- specified in the final parameter.----makeKernel :: LaunchConfig -> Label -> [LLVM.Parameter] -> CodeGen () -> CodeGen (Kernel PTX aenv a)-makeKernel config name@(Label l) param kernel = do- _ <- kernel- code <- createBlocks- addMetadata "nvvm.annotations"- [ Just . MetadataConstantOperand $ LLVM.GlobalReference (LLVM.PointerType (LLVM.FunctionType LLVM.VoidType [ t | LLVM.Parameter t _ _ <- param ] False) (AddrSpace 0)) (LLVM.Name l)- , Just . MetadataStringOperand $ "kernel"- , Just . MetadataConstantOperand $ LLVM.Int 32 1- ]- return $ Kernel- { kernelMetadata = KM_PTX config- , unKernel = LLVM.functionDefaults- { LLVM.returnType = LLVM.VoidType- , LLVM.name = downcast name- , LLVM.parameters = (param, False)- , LLVM.basicBlocks = code- }- }-
− Data/Array/Accelerate/LLVM/PTX/CodeGen/Fold.hs
@@ -1,628 +0,0 @@-{-# LANGUAGE GADTs #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RebindableSyntax #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE TypeOperators #-}-{-# LANGUAGE ViewPatterns #-}--- |--- Module : Data.Array.Accelerate.LLVM.PTX.CodeGen.Fold--- Copyright : [2016..2017] Trevor L. McDonell--- License : BSD3------ Maintainer : Trevor L. McDonell <tmcdonell@cse.unsw.edu.au>--- Stability : experimental--- Portability : non-portable (GHC extensions)-----module Data.Array.Accelerate.LLVM.PTX.CodeGen.Fold- where---- accelerate-import Data.Array.Accelerate.Analysis.Match-import Data.Array.Accelerate.Analysis.Type-import Data.Array.Accelerate.Array.Sugar ( Array, Scalar, Vector, Shape, Z, (:.), Elt(..) )---- accelerate-llvm-*-import Data.Array.Accelerate.LLVM.Analysis.Match-import Data.Array.Accelerate.LLVM.CodeGen.Arithmetic as A-import Data.Array.Accelerate.LLVM.CodeGen.Array-import Data.Array.Accelerate.LLVM.CodeGen.Base-import Data.Array.Accelerate.LLVM.CodeGen.Environment-import Data.Array.Accelerate.LLVM.CodeGen.Exp-import Data.Array.Accelerate.LLVM.CodeGen.IR-import Data.Array.Accelerate.LLVM.CodeGen.Loop as Loop-import Data.Array.Accelerate.LLVM.CodeGen.Monad-import Data.Array.Accelerate.LLVM.CodeGen.Sugar--import Data.Array.Accelerate.LLVM.PTX.Analysis.Launch-import Data.Array.Accelerate.LLVM.PTX.CodeGen.Base-import Data.Array.Accelerate.LLVM.PTX.CodeGen.Generate-import Data.Array.Accelerate.LLVM.PTX.Context-import Data.Array.Accelerate.LLVM.PTX.Target--import LLVM.AST.Type.Representation---- cuda-import qualified Foreign.CUDA.Analysis as CUDA--import Control.Applicative ( (<$>), (<*>) )-import Control.Monad ( (>=>), (<=<) )-import Data.String ( fromString )-import Data.Bits as P-import Prelude as P----- Reduce an array along the innermost dimension. The reduction function must be--- associative to allow for an efficient parallel implementation, but the--- initial element does /not/ need to be a neutral element of operator.------ TODO: Specialise for commutative operations (such as (+)) and those with--- a neutral element {(+), 0}----mkFold- :: forall aenv sh e. (Shape sh, Elt e)- => PTX- -> Gamma aenv- -> IRFun2 PTX aenv (e -> e -> e)- -> IRExp PTX aenv e- -> IRDelayed PTX aenv (Array (sh :. Int) e)- -> CodeGen (IROpenAcc PTX aenv (Array sh e))-mkFold ptx@(deviceProperties . ptxContext -> dev) aenv f z acc- | Just Refl <- matchShapeType (undefined::sh) (undefined::Z)- = (+++) <$> mkFoldAll dev aenv f (Just z) acc- <*> mkFoldFill ptx aenv z-- | otherwise- = (+++) <$> mkFoldDim dev aenv f (Just z) acc- <*> mkFoldFill ptx aenv z----- Reduce a non-empty array along the innermost dimension. The reduction--- function must be associative to allow for an efficient parallel--- implementation.------ TODO: Specialise for commutative operations (such as (+)) and those with--- a neutral element {(+), 0}----mkFold1- :: forall aenv sh e. (Shape sh, Elt e)- => PTX- -> Gamma aenv- -> IRFun2 PTX aenv (e -> e -> e)- -> IRDelayed PTX aenv (Array (sh :. Int) e)- -> CodeGen (IROpenAcc PTX aenv (Array sh e))-mkFold1 (deviceProperties . ptxContext -> dev) aenv f acc- | Just Refl <- matchShapeType (undefined::sh) (undefined::Z)- = mkFoldAll dev aenv f Nothing acc-- | otherwise- = mkFoldDim dev aenv f Nothing acc----- Reduce an array to a single element.------ Since reductions consume arrays that have been fused into them, parallel--- reduction requires two separate kernels. At an example, take vector dot--- product:------ > dotp xs ys = fold (+) 0 (zipWith (*) xs ys)------ 1. The first pass reads in the fused array data, in this case corresponding--- to the function (\i -> (xs!i) * (ys!i)).------ 2. The second pass reads in the manifest array data from the first step and--- directly reduces the array. This can be done recursively in-place until--- only a single element remains.------ In both phases, thread blocks cooperatively reduce a stripe of the input (one--- element per thread) to a single element, which is stored to the output array.----mkFoldAll- :: forall aenv e. Elt e- => DeviceProperties -- ^ properties of the target GPU- -> Gamma aenv -- ^ array environment- -> IRFun2 PTX aenv (e -> e -> e) -- ^ combination function- -> Maybe (IRExp PTX aenv e) -- ^ seed element, if this is an exclusive reduction- -> IRDelayed PTX aenv (Vector e) -- ^ input data- -> CodeGen (IROpenAcc PTX aenv (Scalar e))-mkFoldAll dev aenv combine mseed acc =- foldr1 (+++) <$> sequence [ mkFoldAllS dev aenv combine mseed acc- , mkFoldAllM1 dev aenv combine acc- , mkFoldAllM2 dev aenv combine mseed- ]----- Reduction to an array to a single element, for small arrays which can be--- processed by a single thread block.----mkFoldAllS- :: forall aenv e. Elt e- => DeviceProperties -- ^ properties of the target GPU- -> Gamma aenv -- ^ array environment- -> IRFun2 PTX aenv (e -> e -> e) -- ^ combination function- -> Maybe (IRExp PTX aenv e)- -> IRDelayed PTX aenv (Vector e) -- ^ input data- -> CodeGen (IROpenAcc PTX aenv (Scalar e))-mkFoldAllS dev aenv combine mseed IRDelayed{..} =- let- (start, end, paramGang) = gangParam- (arrOut, paramOut) = mutableArray ("out" :: Name (Scalar e))- paramEnv = envParam aenv- --- config = launchConfig dev (CUDA.incWarp dev) smem multipleOf multipleOfQ- smem n = warps * (1 + per_warp) * bytes- where- ws = CUDA.warpSize dev- warps = n `P.quot` ws- per_warp = ws + ws `P.quot` 2- bytes = sizeOf (eltType (undefined :: e))- in- makeOpenAccWith config "foldAllS" (paramGang ++ paramOut ++ paramEnv) $ do-- tid <- threadIdx- bd <- blockDim-- -- We can assume that there is only a single thread block- i0 <- A.add numType start tid- sz <- A.sub numType end start- when (A.lt scalarType i0 sz) $ do-- -- Thread reads initial element and then participates in block-wide- -- reduction.- x0 <- app1 delayedLinearIndex =<< A.fromIntegral integralType numType i0- r0 <- if A.eq scalarType sz bd- then reduceBlockSMem dev combine Nothing x0- else reduceBlockSMem dev combine (Just sz) x0-- when (A.eq scalarType tid (lift 0)) $- writeArray arrOut tid =<<- case mseed of- Nothing -> return r0- Just z -> flip (app2 combine) r0 =<< z -- Note: initial element on the left-- return_----- Reduction of an entire array to a single element. This kernel implements step--- one for reducing large arrays which must be processed by multiple thread--- blocks.----mkFoldAllM1- :: forall aenv e. Elt e- => DeviceProperties -- ^ properties of the target GPU- -> Gamma aenv -- ^ array environment- -> IRFun2 PTX aenv (e -> e -> e) -- ^ combination function- -> IRDelayed PTX aenv (Vector e) -- ^ input data- -> CodeGen (IROpenAcc PTX aenv (Scalar e))-mkFoldAllM1 dev aenv combine IRDelayed{..} =- let- (start, end, paramGang) = gangParam- (arrTmp, paramTmp) = mutableArray ("tmp" :: Name (Vector e))- paramEnv = envParam aenv- --- config = launchConfig dev (CUDA.incWarp dev) smem const [|| const ||]- smem n = warps * (1 + per_warp) * bytes- where- ws = CUDA.warpSize dev- warps = n `P.quot` ws- per_warp = ws + ws `P.quot` 2- bytes = sizeOf (eltType (undefined :: e))- in- makeOpenAccWith config "foldAllM1" (paramGang ++ paramTmp ++ paramEnv) $ do-- -- Each thread block cooperatively reduces a stripe of the input and stores- -- that value into a temporary array at a corresponding index. Since the- -- order of operations remains fixed, this method supports non-commutative- -- reductions.- --- tid <- threadIdx- bd <- blockDim- sz <- i32 . indexHead =<< delayedExtent-- imapFromTo start end $ \seg -> do-- -- Wait for all threads to catch up before beginning the stripe- __syncthreads-- -- Bounds of the input array we will reduce between- from <- A.mul numType seg bd- step <- A.add numType from bd- to <- A.min scalarType sz step-- -- Threads cooperatively reduce this stripe- reduceFromTo dev from to combine- (app1 delayedLinearIndex <=< A.fromIntegral integralType numType)- (when (A.eq scalarType tid (lift 0)) . writeArray arrTmp seg)-- return_----- Reduction of an array to a single element, (recursive) step 2 of multi-block--- reduction algorithm.----mkFoldAllM2- :: forall aenv e. Elt e- => DeviceProperties- -> Gamma aenv- -> IRFun2 PTX aenv (e -> e -> e)- -> Maybe (IRExp PTX aenv e)- -> CodeGen (IROpenAcc PTX aenv (Scalar e))-mkFoldAllM2 dev aenv combine mseed =- let- (start, end, paramGang) = gangParam- (arrTmp, paramTmp) = mutableArray ("tmp" :: Name (Vector e))- (arrOut, paramOut) = mutableArray ("out" :: Name (Vector e))- paramEnv = envParam aenv- --- config = launchConfig dev (CUDA.incWarp dev) smem const [|| const ||]- smem n = warps * (1 + per_warp) * bytes- where- ws = CUDA.warpSize dev- warps = n `P.quot` ws- per_warp = ws + ws `P.quot` 2- bytes = sizeOf (eltType (undefined :: e))- in- makeOpenAccWith config "foldAllM2" (paramGang ++ paramTmp ++ paramOut ++ paramEnv) $ do-- -- Threads cooperatively reduce a stripe of the input (temporary) array- -- output from the first phase, storing the results into another temporary.- -- When only a single thread block remains, we have reached the final- -- reduction step and add the initial element (for exclusive reductions).- --- tid <- threadIdx- bd <- blockDim- gd <- gridDim- sz <- i32 . indexHead $ irArrayShape arrTmp-- imapFromTo start end $ \seg -> do-- -- Wait for all threads to catch up before beginning the stripe- __syncthreads-- -- Bounds of the input we will reduce between- from <- A.mul numType seg bd- step <- A.add numType from bd- to <- A.min scalarType sz step-- -- Threads cooperatively reduce this stripe- reduceFromTo dev from to combine (readArray arrTmp) $ \r ->- when (A.eq scalarType tid (lift 0)) $- writeArray arrOut seg =<<- case mseed of- Nothing -> return r- Just z -> if A.eq scalarType gd (lift 1)- then flip (app2 combine) r =<< z -- Note: initial element on the left- else return r-- return_----- Reduce an array of arbitrary rank along the innermost dimension only.------ For simplicity, each element of the output (reduction along an--- innermost-dimension index) is computed by a single thread block, meaning we--- don't have to worry about inter-block synchronisation. A more balanced method--- would be a segmented reduction (specialised, since the length of each segment--- is known a priori).----mkFoldDim- :: forall aenv sh e. (Shape sh, Elt e)- => DeviceProperties -- ^ properties of the target GPU- -> Gamma aenv -- ^ array environment- -> IRFun2 PTX aenv (e -> e -> e) -- ^ combination function- -> Maybe (IRExp PTX aenv e) -- ^ seed element, if this is an exclusive reduction- -> IRDelayed PTX aenv (Array (sh :. Int) e) -- ^ input data- -> CodeGen (IROpenAcc PTX aenv (Array sh e))-mkFoldDim dev aenv combine mseed IRDelayed{..} =- let- (start, end, paramGang) = gangParam- (arrOut, paramOut) = mutableArray ("out" :: Name (Array sh e))- paramEnv = envParam aenv- --- config = launchConfig dev (CUDA.incWarp dev) smem const [|| const ||]- smem n = warps * (1 + per_warp) * bytes- where- ws = CUDA.warpSize dev- warps = n `P.quot` ws- per_warp = ws + ws `P.quot` 2- bytes = sizeOf (eltType (undefined :: e))- in- makeOpenAccWith config "fold" (paramGang ++ paramOut ++ paramEnv) $ do-- -- If the innermost dimension is smaller than the number of threads in the- -- block, those threads will never contribute to the output.- tid <- threadIdx- sz <- i32 . indexHead =<< delayedExtent- when (A.lt scalarType tid sz) $ do-- -- Thread blocks iterate over the outer dimensions, each thread block- -- cooperatively reducing along each outermost index to a single value.- --- imapFromTo start end $ \seg -> do-- -- Wait for threads to catch up before starting this segment. We could- -- also place this at the bottom of the loop, but here allows threads to- -- exit quickly on the last iteration.- __syncthreads-- -- Step 1: initialise local sums- from <- A.mul numType seg sz -- first linear index this block will reduce- to <- A.add numType from sz -- last linear index this block will reduce (exclusive)-- i0 <- A.add numType from tid- x0 <- app1 delayedLinearIndex =<< A.fromIntegral integralType numType i0- bd <- blockDim- r0 <- if A.gte scalarType sz bd- then reduceBlockSMem dev combine Nothing x0- else reduceBlockSMem dev combine (Just sz) x0-- -- Step 2: keep walking over the input- next <- A.add numType from bd- r <- iterFromStepTo next bd to r0 $ \offset r -> do-- -- Wait for all threads to catch up before starting the next stripe- __syncthreads-- -- Threads cooperatively reduce this stripe of the input- i <- A.add numType offset tid- v' <- A.sub numType to offset- r' <- if A.gte scalarType v' bd- -- All threads of the block are valid, so we can avoid- -- bounds checks.- then do- x <- app1 delayedLinearIndex =<< A.fromIntegral integralType numType i- y <- reduceBlockSMem dev combine Nothing x- return y-- -- Otherwise, we require bounds checks when reading the input- -- and during the reduction. Note that even though only the- -- valid threads will contribute useful work in the- -- reduction, we must still have all threads enter the- -- reduction procedure to avoid synchronisation divergence.- else do- x <- if A.lt scalarType i to- then app1 delayedLinearIndex =<< A.fromIntegral integralType numType i- else return r- y <- reduceBlockSMem dev combine (Just v') x- return y-- if A.eq scalarType tid (lift 0)- then app2 combine r r'- else return r'-- -- Step 3: Thread 0 writes the aggregate reduction of this dimension to- -- memory. If this is an exclusive fold, combine with the initial element.- --- when (A.eq scalarType tid (lift 0)) $- writeArray arrOut seg =<<- case mseed of- Nothing -> return r- Just z -> flip (app2 combine) r =<< z -- Note: initial element on the left-- return_----- Exclusive reductions over empty arrays (of any dimension) fill the lower--- dimensions with the initial element.----mkFoldFill- :: (Shape sh, Elt e)- => PTX- -> Gamma aenv- -> IRExp PTX aenv e- -> CodeGen (IROpenAcc PTX aenv (Array sh e))-mkFoldFill ptx aenv seed =- mkGenerate ptx aenv (IRFun1 (const seed))----- Efficient threadblock-wide reduction using the specified operator. The--- aggregate reduction value is stored in thread zero. Supports non-commutative--- operators.------ Requires dynamically allocated memory: (#warps * (1 + 1.5 * warp size)).------ Example: https://github.com/NVlabs/cub/blob/1.5.2/cub/block/specializations/block_reduce_warp_reductions.cuh----reduceBlockSMem- :: forall aenv e. Elt e- => DeviceProperties -- ^ properties of the target device- -> IRFun2 PTX aenv (e -> e -> e) -- ^ combination function- -> Maybe (IR Int32) -- ^ number of valid elements (may be less than block size)- -> IR e -- ^ calling thread's input element- -> CodeGen (IR e) -- ^ thread-block-wide reduction using the specified operator (lane 0 only)-reduceBlockSMem dev combine size = warpReduce >=> warpAggregate- where- int32 :: Integral a => a -> IR Int32- int32 = lift . P.fromIntegral-- -- Temporary storage required for each warp- bytes = sizeOf (eltType (undefined::e))- warp_smem_elems = CUDA.warpSize dev + (CUDA.warpSize dev `P.quot` 2)-- -- Step 1: Reduction in every warp- --- warpReduce :: IR e -> CodeGen (IR e)- warpReduce input = do- -- Allocate (1.5 * warpSize) elements of shared memory for each warp- wid <- warpId- skip <- A.mul numType wid (int32 (warp_smem_elems * bytes))- smem <- dynamicSharedMem (int32 warp_smem_elems) skip-- -- Are we doing bounds checking for this warp?- --- case size of- -- The entire thread block is valid, so skip bounds checks.- Nothing ->- reduceWarpSMem dev combine smem Nothing input-- -- Otherwise check how many elements are valid for this warp. If it is- -- full then we can still skip bounds checks for it.- Just n -> do- offset <- A.mul numType wid (int32 (CUDA.warpSize dev))- valid <- A.sub numType n offset- if A.gte scalarType valid (int32 (CUDA.warpSize dev))- then reduceWarpSMem dev combine smem Nothing input- else reduceWarpSMem dev combine smem (Just valid) input-- -- Step 2: Aggregate per-warp reductions- --- warpAggregate :: IR e -> CodeGen (IR e)- warpAggregate input = do- -- Allocate #warps elements of shared memory- bd <- blockDim- warps <- A.quot integralType bd (int32 (CUDA.warpSize dev))- skip <- A.mul numType warps (int32 (warp_smem_elems * bytes))- smem <- dynamicSharedMem warps skip-- -- Share the per-lane aggregates- wid <- warpId- lane <- laneId- when (A.eq scalarType lane (lift 0)) $ do- writeArray smem wid input-- -- Wait for each warp to finish its local reduction- __syncthreads-- -- Update the total aggregate. Thread 0 just does this sequentially (as is- -- done in CUB), but we could also do this cooperatively (better for- -- larger thread blocks?)- tid <- threadIdx- if A.eq scalarType tid (lift 0)- then do- steps <- case size of- Nothing -> return warps- Just n -> do- a <- A.add numType n (int32 (CUDA.warpSize dev - 1))- b <- A.quot integralType a (int32 (CUDA.warpSize dev))- return b- iterFromStepTo (lift 1) (lift 1) steps input $ \step x ->- app2 combine x =<< readArray smem step- else- return input----- Efficient warp-wide reduction using shared memory. The aggregate reduction--- value for the warp is stored in thread lane zero.------ Each warp requires 48 (1.5 x warp size) elements of shared memory. The--- routine assumes that is is allocated individually per-warp (i.e. can be--- indexed in the range [0,warp size)).------ Example: https://github.com/NVlabs/cub/blob/1.5.2/cub/warp/specializations/warp_reduce_smem.cuh#L128----reduceWarpSMem- :: forall aenv e. Elt e- => DeviceProperties -- ^ properties of the target device- -> IRFun2 PTX aenv (e -> e -> e) -- ^ combination function- -> IRArray (Vector e) -- ^ temporary storage array in shared memory (1.5 warp size elements)- -> Maybe (IR Int32) -- ^ number of items that will be reduced by this warp, otherwise all lanes are valid- -> IR e -- ^ calling thread's input element- -> CodeGen (IR e) -- ^ warp-wide reduction using the specified operator (lane 0 only)-reduceWarpSMem dev combine smem size = reduce 0- where- log2 :: Double -> Double- log2 = P.logBase 2-- -- Number steps required to reduce warp- steps = P.floor . log2 . P.fromIntegral . CUDA.warpSize $ dev-- -- Return whether the index is valid. Assume that constant branches are- -- optimised away.- valid i =- case size of- Nothing -> return (lift True)- Just n -> A.lt scalarType i n-- -- Unfold the reduction as a recursive code generation function.- reduce :: Int -> IR e -> CodeGen (IR e)- reduce step x- | step >= steps = return x- | offset <- 1 `P.shiftL` step = do- -- share input through buffer- lane <- laneId- writeArray smem lane x-- -- update input if in range- i <- A.add numType lane (lift offset)- x' <- if valid i- then app2 combine x =<< readArray smem i- else return x-- reduce (step+1) x'----- Efficient warp reduction using __shfl_up instruction (compute >= 3.0)------ Example: https://github.com/NVlabs/cub/blob/1.5.2/cub/warp/specializations/warp_reduce_shfl.cuh#L310------ reduceWarpShfl--- :: IRFun2 PTX aenv (e -> e -> e) -- ^ combination function--- -> IR e -- ^ this thread's input value--- -> CodeGen (IR e) -- ^ final result--- reduceWarpShfl combine input =--- error "TODO: PTX.reduceWarpShfl"----- Reduction loops--- -----------------reduceFromTo- :: Elt a- => DeviceProperties- -> IR Int32 -- ^ starting index- -> IR Int32 -- ^ final index (exclusive)- -> (IRFun2 PTX aenv (a -> a -> a)) -- ^ combination function- -> (IR Int32 -> CodeGen (IR a)) -- ^ function to retrieve element at index- -> (IR a -> CodeGen ()) -- ^ what to do with the value- -> CodeGen ()-reduceFromTo dev from to combine get set = do-- tid <- threadIdx- bd <- blockDim-- valid <- A.sub numType to from- i <- A.add numType from tid-- _ <- if A.gte scalarType valid bd- then do- -- All threads in the block will participate in the reduction, so- -- we can avoid bounds checks- x <- get i- r <- reduceBlockSMem dev combine Nothing x- set r-- return (IR OP_Unit :: IR ()) -- unsightly, but free- else do- -- Only in-bounds threads can read their input and participate in- -- the reduction- when (A.lt scalarType i to) $ do- x <- get i- r <- reduceBlockSMem dev combine (Just valid) x- set r-- return (IR OP_Unit :: IR ())-- return ()------ Utilities--- -----------i32 :: IR Int -> CodeGen (IR Int32)-i32 = A.fromIntegral integralType numType---imapFromTo- :: IR Int32- -> IR Int32- -> (IR Int32 -> CodeGen ())- -> CodeGen ()-imapFromTo start end body = do- bid <- blockIdx- gd <- gridDim- i0 <- A.add numType start bid- imapFromStepTo i0 gd end body-
− Data/Array/Accelerate/LLVM/PTX/CodeGen/FoldSeg.hs
@@ -1,464 +0,0 @@-{-# LANGUAGE GADTs #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RebindableSyntax #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE TypeOperators #-}-{-# LANGUAGE ViewPatterns #-}--- |--- Module : Data.Array.Accelerate.LLVM.PTX.CodeGen.FoldSeg--- Copyright : [2016..2017] Trevor L. McDonell--- License : BSD3------ Maintainer : Trevor L. McDonell <tmcdonell@cse.unsw.edu.au>--- Stability : experimental--- Portability : non-portable (GHC extensions)-----module Data.Array.Accelerate.LLVM.PTX.CodeGen.FoldSeg- where---- accelerate-import Data.Array.Accelerate.Analysis.Type-import Data.Array.Accelerate.Array.Sugar ( Array, Segments, Shape(rank), (:.), Elt(..) )---- accelerate-llvm-*-import LLVM.AST.Type.Representation--import Data.Array.Accelerate.LLVM.CodeGen.Arithmetic as A-import Data.Array.Accelerate.LLVM.CodeGen.Array-import Data.Array.Accelerate.LLVM.CodeGen.Base-import Data.Array.Accelerate.LLVM.CodeGen.Constant-import Data.Array.Accelerate.LLVM.CodeGen.Environment-import Data.Array.Accelerate.LLVM.CodeGen.Exp-import Data.Array.Accelerate.LLVM.CodeGen.IR-import Data.Array.Accelerate.LLVM.CodeGen.Loop as Loop-import Data.Array.Accelerate.LLVM.CodeGen.Monad-import Data.Array.Accelerate.LLVM.CodeGen.Sugar--import Data.Array.Accelerate.LLVM.PTX.Analysis.Launch-import Data.Array.Accelerate.LLVM.PTX.CodeGen.Base-import Data.Array.Accelerate.LLVM.PTX.CodeGen.Fold ( reduceBlockSMem, reduceWarpSMem, imapFromTo )--- import Data.Array.Accelerate.LLVM.PTX.CodeGen.Queue-import Data.Array.Accelerate.LLVM.PTX.Context-import Data.Array.Accelerate.LLVM.PTX.Target---- cuda-import qualified Foreign.CUDA.Analysis as CUDA--import Control.Applicative ( (<$>), (<*>) )-import Control.Monad ( void )-import Data.String ( fromString )-import Prelude as P----- Segmented reduction along the innermost dimension of an array. Performs one--- reduction per segment of the source array.----mkFoldSeg- :: forall aenv sh i e. (Shape sh, IsIntegral i, Elt i, Elt e)- => PTX- -> Gamma aenv- -> IRFun2 PTX aenv (e -> e -> e)- -> IRExp PTX aenv e- -> IRDelayed PTX aenv (Array (sh :. Int) e)- -> IRDelayed PTX aenv (Segments i)- -> CodeGen (IROpenAcc PTX aenv (Array (sh :. Int) e))-mkFoldSeg (deviceProperties . ptxContext -> dev) aenv combine seed arr seg =- (+++) <$> mkFoldSegP_block dev aenv combine (Just seed) arr seg- <*> mkFoldSegP_warp dev aenv combine (Just seed) arr seg----- Segmented reduction along the innermost dimension of an array, where /all/--- segments are non-empty.----mkFold1Seg- :: forall aenv sh i e. (Shape sh, IsIntegral i, Elt i, Elt e)- => PTX- -> Gamma aenv- -> IRFun2 PTX aenv (e -> e -> e)- -> IRDelayed PTX aenv (Array (sh :. Int) e)- -> IRDelayed PTX aenv (Segments i)- -> CodeGen (IROpenAcc PTX aenv (Array (sh :. Int) e))-mkFold1Seg (deviceProperties . ptxContext -> dev) aenv combine arr seg =- (+++) <$> mkFoldSegP_block dev aenv combine Nothing arr seg- <*> mkFoldSegP_warp dev aenv combine Nothing arr seg----- This implementation assumes that the segments array represents the offset--- indices to the source array, rather than the lengths of each segment. The--- segment-offset approach is required for parallel implementations.------ Each segment is computed by a single thread block, meaning we don't have to--- worry about inter-block synchronisation.----mkFoldSegP_block- :: forall aenv sh i e. (Shape sh, IsIntegral i, Elt i, Elt e)- => DeviceProperties- -> Gamma aenv- -> IRFun2 PTX aenv (e -> e -> e)- -> Maybe (IRExp PTX aenv e)- -> IRDelayed PTX aenv (Array (sh :. Int) e)- -> IRDelayed PTX aenv (Segments i)- -> CodeGen (IROpenAcc PTX aenv (Array (sh :. Int) e))-mkFoldSegP_block dev aenv combine mseed arr seg =- let- (start, end, paramGang) = gangParam- (arrOut, paramOut) = mutableArray ("out" :: Name (Array (sh :. Int) e))- paramEnv = envParam aenv- --- config = launchConfig dev (CUDA.decWarp dev) dsmem const [|| const ||]- dsmem n = warps * (1 + per_warp) * bytes- where- ws = CUDA.warpSize dev- warps = n `P.quot` ws- per_warp = ws + ws `P.quot` 2- bytes = sizeOf (eltType (undefined :: e))- in- makeOpenAccWith config "foldSeg_block" (paramGang ++ paramOut ++ paramEnv) $ do-- -- We use a dynamically scheduled work queue in order to evenly distribute- -- the uneven workload, due to the variable length of each segment, over the- -- available thread blocks.- -- queue <- globalWorkQueue-- -- All threads in the block need to know what the start and end indices of- -- this segment are in order to participate in the reduction. We use- -- variables in __shared__ memory to communicate these values between- -- threads in the block. Furthermore, by using a 2-element array, we can- -- have the first two threads of the block read the start and end indices as- -- a single coalesced read, since they will be sequential in the- -- segment-offset array.- --- smem <- staticSharedMem 2-- -- Compute the number of segments and size of the innermost dimension. These- -- are required if we are reducing a rank-2 or higher array, to properly- -- compute the start and end indices of the portion of the array this thread- -- block reduces. Note that this is a segment-offset array computed by- -- 'scanl (+) 0' of the segment length array, so its size has increased by- -- one.- --- sz <- i32 . indexHead =<< delayedExtent arr- ss <- do n <- i32 . indexHead =<< delayedExtent seg- A.sub numType n (lift 1)-- -- Each thread block cooperatively reduces a segment.- -- s0 <- dequeue queue (lift 1)- -- for s0 (\s -> A.lt scalarType s end) (\_ -> dequeue queue (lift 1)) $ \s -> do- imapFromTo start end $ \s -> do-- -- The first two threads of the block determine the indices of the- -- segments array that we will reduce between and distribute those values- -- to the other threads in the block.- tid <- threadIdx- when (A.lt scalarType tid (lift 2)) $ do- i <- case rank (undefined::sh) of- 0 -> return s- _ -> A.rem integralType s ss- j <- A.add numType i tid- v <- app1 (delayedLinearIndex seg) =<< A.fromIntegral integralType numType j- writeArray smem tid =<< i32 v-- -- Once all threads have caught up, begin work on the new segment.- __syncthreads-- u <- readArray smem (lift 0 :: IR Int32)- v <- readArray smem (lift 1 :: IR Int32)-- -- Determine the index range of the input array we will reduce over.- -- Necessary for multidimensional segmented reduction.- (inf,sup) <- A.unpair <$> case rank (undefined::sh) of- 0 -> return (A.pair u v)- _ -> do q <- A.quot integralType s ss- a <- A.mul numType q sz- A.pair <$> A.add numType u a- <*> A.add numType v a-- void $- if A.eq scalarType inf sup- -- This segment is empty. If this is an exclusive reduction the- -- first thread writes out the initial element for this segment.- then do- case mseed of- Nothing -> return (IR OP_Unit :: IR ())- Just z -> do- when (A.eq scalarType tid (lift 0)) $ writeArray arrOut s =<< z- return (IR OP_Unit)-- -- This is a non-empty segment.- else do- -- Step 1: initialise local sums- --- -- NOTE: We require all threads to enter this branch and execute the- -- first step, even if they do not have a valid element and must- -- return 'undef'. If we attempt to skip this entire section for- -- non-participating threads (i.e. 'when (i0 < sup)'), it seems that- -- those threads die and will not participate in the computation of- -- _any_ further segment. I'm not sure if this is a CUDA oddity- -- (e.g. we must have all threads convergent on __syncthreads) or- -- a bug in NVPTX / ptxas.- --- bd <- blockDim- i0 <- A.add numType inf tid- x0 <- if A.lt scalarType i0 sup- then app1 (delayedLinearIndex arr) =<< A.fromIntegral integralType numType i0- else let- go :: TupleType a -> Operands a- go UnitTuple = OP_Unit- go (PairTuple a b) = OP_Pair (go a) (go b)- go (SingleTuple t) = ir' t (undef t)- in- return . IR $ go (eltType (undefined::e))-- v0 <- A.sub numType sup inf- r0 <- if A.gte scalarType v0 bd- then reduceBlockSMem dev combine Nothing x0- else reduceBlockSMem dev combine (Just v0) x0-- -- Step 2: keep walking over the input- nxt <- A.add numType inf bd- r <- iterFromStepTo nxt bd sup r0 $ \offset r -> do-- -- Wait for threads to catch up before starting the next stripe- __syncthreads-- i' <- A.add numType offset tid- v' <- A.sub numType sup offset- r' <- if A.gte scalarType v' bd- -- All threads in the block are in bounds, so we- -- can avoid bounds checks.- then do- x <- app1 (delayedLinearIndex arr) =<< A.fromIntegral integralType numType i'- y <- reduceBlockSMem dev combine Nothing x- return y-- -- Not all threads are valid. Note that we still- -- have all threads enter the reduction procedure- -- to avoid thread divergence on synchronisation- -- points, similar to the above NOTE.- else do- x <- if A.lt scalarType i' sup- then app1 (delayedLinearIndex arr) =<< A.fromIntegral integralType numType i'- else return r- y <- reduceBlockSMem dev combine (Just v') x- return y-- -- first thread incorporates the result from the previous- -- iteration- if A.eq scalarType tid (lift 0)- then app2 combine r r'- else return r'-- -- Step 3: Thread zero writes the aggregate reduction for this- -- segment to memory. If this is an exclusive fold combine with the- -- initial element as well.- when (A.eq scalarType tid (lift 0)) $- writeArray arrOut s =<<- case mseed of- Nothing -> return r- Just z -> flip (app2 combine) r =<< z -- Note: initial element on the left-- return (IR OP_Unit)-- return_----- This implementation assumes that the segments array represents the offset--- indices to the source array, rather than the lengths of each segment. The--- segment-offset approach is required for parallel implementations.------ Each segment is computed by a single warp, meaning we don't have to worry--- about inter- or intra-block synchronisation.----mkFoldSegP_warp- :: forall aenv sh i e. (Shape sh, IsIntegral i, Elt i, Elt e)- => DeviceProperties- -> Gamma aenv- -> IRFun2 PTX aenv (e -> e -> e)- -> Maybe (IRExp PTX aenv e)- -> IRDelayed PTX aenv (Array (sh :. Int) e)- -> IRDelayed PTX aenv (Segments i)- -> CodeGen (IROpenAcc PTX aenv (Array (sh :. Int) e))-mkFoldSegP_warp dev aenv combine mseed arr seg =- let- (start, end, paramGang) = gangParam- (arrOut, paramOut) = mutableArray ("out" :: Name (Array (sh :. Int) e))- paramEnv = envParam aenv- --- config = launchConfig dev (CUDA.decWarp dev) dsmem grid gridQ- dsmem n = warps * (2 + per_warp_elems) * bytes- where- warps = n `P.quot` ws- --- grid n m = multipleOf n (m `P.quot` ws)- gridQ = [|| \n m -> $$multipleOfQ n (m `P.quot` ws) ||]- --- per_warp_bytes = per_warp_elems * bytes- per_warp_elems = ws + (ws `P.quot` 2)- ws = CUDA.warpSize dev- bytes = sizeOf (eltType (undefined :: e))-- int32 :: Integral a => a -> IR Int32- int32 = lift . P.fromIntegral- in- makeOpenAccWith config "foldSeg_warp" (paramGang ++ paramOut ++ paramEnv) $ do-- -- Each warp works independently.- -- Determine the ID of this warp within the thread block.- tid <- threadIdx- wid <- A.quot integralType tid (int32 ws)-- -- Number of warps per thread block- bd <- blockDim- wpb <- A.quot integralType bd (int32 ws)-- -- ID of this warp within the grid- bid <- blockIdx- gwid <- do a <- A.mul numType bid wpb- b <- A.add numType wid a- return b-- -- All threads in the warp need to know what the start and end indices of- -- this segment are in order to participate in the reduction. We use- -- variables in __shared__ memory to communicate these values between- -- threads. Furthermore, by using a 2-element array, we can have the first- -- two threads of the warp read the start and end indices as a single- -- coalesced read, as these elements will be adjacent in the segment-offset- -- array.- --- lim <- do- a <- A.mul numType wid (int32 (2 * bytes))- b <- dynamicSharedMem (lift 2) a- return b-- -- Allocate (1.5 * warpSize) elements of share memory for each warp- smem <- do- a <- A.mul numType wpb (int32 (2 * bytes))- b <- A.mul numType wid (int32 per_warp_bytes)- c <- A.add numType a b- d <- dynamicSharedMem (int32 per_warp_elems) c- return d-- -- Compute the number of segments and size of the innermost dimension. These- -- are required if we are reducing a rank-2 or higher array, to properly- -- compute the start and end indices of the portion of the array this warp- -- reduces. Note that this is a segment-offset array computed by 'scanl (+) 0'- -- of the segment length array, so its size has increased by one.- --- sz <- i32 . indexHead =<< delayedExtent arr- ss <- do a <- i32 . indexHead =<< delayedExtent seg- b <- A.sub numType a (lift 1)- return b-- -- Each thread reduces a segment independently- s0 <- A.add numType start gwid- gd <- gridDim- step <- A.mul numType wpb gd- imapFromStepTo s0 step end $ \s -> do-- -- The first two threads of the warp determine the indices of the segments- -- array that we will reduce between and distribute those values to the- -- other threads in the warp- lane <- laneId- when (A.lt scalarType lane (lift 2)) $ do- a <- case rank (undefined::sh) of- 0 -> return s- _ -> A.rem integralType s ss- b <- A.add numType a lane- c <- app1 (delayedLinearIndex seg) =<< A.fromIntegral integralType numType b- writeArray lim lane =<< i32 c-- -- Determine the index range of the input array we will reduce over.- -- Necessary for multidimensional segmented reduction.- (inf,sup) <- do- u <- readArray lim (lift 0 :: IR Int32)- v <- readArray lim (lift 1 :: IR Int32)- A.unpair <$> case rank (undefined::sh) of- 0 -> return (A.pair u v)- _ -> do q <- A.quot integralType s ss- a <- A.mul numType q sz- A.pair <$> A.add numType u a <*> A.add numType v a-- -- TLM: I don't think this should be necessary...- __syncthreads-- void $- if A.eq scalarType inf sup- -- This segment is empty. If this is an exclusive reduction the first- -- lane writes out the initial element for this segment.- then do- case mseed of- Nothing -> return (IR OP_Unit :: IR ())- Just z -> do- when (A.eq scalarType lane (lift 0)) $ writeArray arrOut s =<< z- return (IR OP_Unit)-- -- This is a non-empty segment.- else do- -- Step 1: initialise local sums- --- -- See comment above why we initialise the loop in this way- --- i0 <- A.add numType inf lane- x0 <- if A.lt scalarType i0 sup- then app1 (delayedLinearIndex arr) =<< A.fromIntegral integralType numType i0- else let- go :: TupleType a -> Operands a- go UnitTuple = OP_Unit- go (PairTuple a b) = OP_Pair (go a) (go b)- go (SingleTuple t) = ir' t (undef t)- in- return . IR $ go (eltType (undefined::e))-- v0 <- A.sub numType sup inf- r0 <- if A.gte scalarType v0 (int32 ws)- then reduceWarpSMem dev combine smem Nothing x0- else reduceWarpSMem dev combine smem (Just v0) x0-- -- Step 2: Keep walking over the rest of the segment- nx <- A.add numType inf (int32 ws)- r <- iterFromStepTo nx (int32 ws) sup r0 $ \offset r -> do-- -- TLM: Similarly, I think this is unnecessary...- __syncthreads-- i' <- A.add numType offset lane- v' <- A.sub numType sup offset- r' <- if A.gte scalarType v' (int32 ws)- then do- -- All lanes are in bounds, so avoid bounds checks- x <- app1 (delayedLinearIndex arr) =<< A.fromIntegral integralType numType i'- y <- reduceWarpSMem dev combine smem Nothing x- return y-- else do- x <- if A.lt scalarType i' sup- then app1 (delayedLinearIndex arr) =<< A.fromIntegral integralType numType i'- else return r- y <- reduceWarpSMem dev combine smem (Just v') x- return y-- -- The first lane incorporates the result from the previous- -- iteration- if A.eq scalarType lane (lift 0)- then app2 combine r r'- else return r'-- -- Step 3: Lane zero writes the aggregate reduction for this- -- segment to memory. If this is an exclusive reduction, also- -- combine with the initial element- when (A.eq scalarType lane (lift 0)) $- writeArray arrOut s =<<- case mseed of- Nothing -> return r- Just z -> flip (app2 combine) r =<< z -- Note: initial element on the left-- return (IR OP_Unit)-- return_---i32 :: IsIntegral a => IR a -> CodeGen (IR Int32)-i32 = A.fromIntegral integralType numType-
− Data/Array/Accelerate/LLVM/PTX/CodeGen/Generate.hs
@@ -1,60 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ScopedTypeVariables #-}--- |--- Module : Data.Array.Accelerate.LLVM.PTX.CodeGen.Generate--- Copyright : [2014..2017] Trevor L. McDonell--- License : BSD3------ Maintainer : Trevor L. McDonell <tmcdonell@cse.unsw.edu.au>--- Stability : experimental--- Portability : non-portable (GHC extensions)-----module Data.Array.Accelerate.LLVM.PTX.CodeGen.Generate- where--import Prelude hiding ( fromIntegral )---- accelerate-import Data.Array.Accelerate.Array.Sugar ( Array, Shape, Elt )-import Data.Array.Accelerate.Type--import Data.Array.Accelerate.LLVM.CodeGen.Arithmetic-import Data.Array.Accelerate.LLVM.CodeGen.Array-import Data.Array.Accelerate.LLVM.CodeGen.Base-import Data.Array.Accelerate.LLVM.CodeGen.Environment-import Data.Array.Accelerate.LLVM.CodeGen.Exp-import Data.Array.Accelerate.LLVM.CodeGen.Monad-import Data.Array.Accelerate.LLVM.CodeGen.Sugar--import Data.Array.Accelerate.LLVM.PTX.CodeGen.Base-import Data.Array.Accelerate.LLVM.PTX.CodeGen.Loop-import Data.Array.Accelerate.LLVM.PTX.Target ( PTX )----- Construct a new array by applying a function to each index. Each thread--- processes multiple adjacent elements.----mkGenerate- :: forall aenv sh e. (Shape sh, Elt e)- => PTX- -> Gamma aenv- -> IRFun1 PTX aenv (sh -> e)- -> CodeGen (IROpenAcc PTX aenv (Array sh e))-mkGenerate ptx aenv apply =- let- (start, end, paramGang) = gangParam- (arrOut, paramOut) = mutableArray ("out" :: Name (Array sh e))- paramEnv = envParam aenv- in- makeOpenAcc ptx "generate" (paramGang ++ paramOut ++ paramEnv) $ do-- imapFromTo start end $ \i -> do- i' <- fromIntegral integralType numType i -- loop counter is Int32- ix <- indexOfInt (irArrayShape arrOut) i' -- convert to multidimensional index- r <- app1 apply ix -- apply generator function- writeArray arrOut i' r -- store result-- return_--
− Data/Array/Accelerate/LLVM/PTX/CodeGen/Intrinsic.hs
@@ -1,359 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}--- |--- Module : Data.Array.Accelerate.LLVM.PTX.CodeGen.Intrinsic--- Copyright : [2017] Trevor L. McDonell--- License : BSD3------ Maintainer : Trevor L. McDonell <tmcdonell@cse.unsw.edu.au>--- Stability : experimental--- Portability : non-portable (GHC extensions)-----module Data.Array.Accelerate.LLVM.PTX.CodeGen.Intrinsic ( )- where--import LLVM.AST.Type.Name ( Label(..) )--import Data.Array.Accelerate.LLVM.CodeGen.Intrinsic-import Data.Array.Accelerate.LLVM.PTX.Target--import Data.ByteString.Short ( ShortByteString )-import Data.HashMap.Strict ( HashMap )-import Data.Monoid-import qualified Data.HashMap.Strict as HashMap---instance Intrinsic PTX where- intrinsicForTarget _ = libdeviceIndex---- The list of functions implemented by libdevice. These are all more-or-less--- named consistently based on the standard mathematical functions they--- implement, with the "__nv_" prefix stripped.----libdeviceIndex :: HashMap ShortByteString Label-libdeviceIndex =- let nv base = (base, Label $ "__nv_" <> base)- in- HashMap.fromList $ map nv- [ "abs"- , "acos"- , "acosf"- , "acosh"- , "acoshf"- , "asin"- , "asinf"- , "asinh"- , "asinhf"- , "atan"- , "atan2"- , "atan2f"- , "atanf"- , "atanh"- , "atanhf"- , "brev"- , "brevll"- , "byte_perm"- , "cbrt"- , "cbrtf"- , "ceil"- , "ceilf"- , "clz"- , "clzll"- , "copysign"- , "copysignf"- , "cos"- , "cosf"- , "cosh"- , "coshf"- , "cospi"- , "cospif"- , "dadd_rd"- , "dadd_rn"- , "dadd_ru"- , "dadd_rz"- , "ddiv_rd"- , "ddiv_rn"- , "ddiv_ru"- , "ddiv_rz"- , "dmul_rd"- , "dmul_rn"- , "dmul_ru"- , "dmul_rz"- , "double2float_rd"- , "double2float_rn"- , "double2float_ru"- , "double2float_rz"- , "double2hiint"- , "double2int_rd"- , "double2int_rn"- , "double2int_ru"- , "double2int_rz"- , "double2ll_rd"- , "double2ll_rn"- , "double2ll_ru"- , "double2ll_rz"- , "double2loint"- , "double2uint_rd"- , "double2uint_rn"- , "double2uint_ru"- , "double2uint_rz"- , "double2ull_rd"- , "double2ull_rn"- , "double2ull_ru"- , "double2ull_rz"- , "double_as_longlong"- , "drcp_rd"- , "drcp_rn"- , "drcp_ru"- , "drcp_rz"- , "dsqrt_rd"- , "dsqrt_rn"- , "dsqrt_ru"- , "dsqrt_rz"- , "erf"- , "erfc"- , "erfcf"- , "erfcinv"- , "erfcinvf"- , "erfcx"- , "erfcxf"- , "erff"- , "erfinv"- , "erfinvf"- , "exp"- , "exp10"- , "exp10f"- , "exp2"- , "exp2f"- , "expf"- , "expm1"- , "expm1f"- , "fabs"- , "fabsf"- , "fadd_rd"- , "fadd_rn"- , "fadd_ru"- , "fadd_rz"- , "fast_cosf"- , "fast_exp10f"- , "fast_expf"- , "fast_fdividef"- , "fast_log10f"- , "fast_log2f"- , "fast_logf"- , "fast_powf"- , "fast_sincosf"- , "fast_sinf"- , "fast_tanf"- , "fdim"- , "fdimf"- , "fdiv_rd"- , "fdiv_rn"- , "fdiv_ru"- , "fdiv_rz"- , "ffs"- , "ffsll"- , "finitef"- , "float2half_rn"- , "float2int_rd"- , "float2int_rn"- , "float2int_ru"- , "float2int_rz"- , "float2ll_rd"- , "float2ll_rn"- , "float2ll_ru"- , "float2ll_rz"- , "float2uint_rd"- , "float2uint_rn"- , "float2uint_ru"- , "float2uint_rz"- , "float2ull_rd"- , "float2ull_rn"- , "float2ull_ru"- , "float2ull_rz"- , "float_as_int"- , "floor"- , "floorf"- , "fma"- , "fma_rd"- , "fma_rn"- , "fma_ru"- , "fma_rz"- , "fmaf"- , "fmaf_rd"- , "fmaf_rn"- , "fmaf_ru"- , "fmaf_rz"- , "fmax"- , "fmaxf"- , "fmin"- , "fminf"- , "fmod"- , "fmodf"- , "fmul_rd"- , "fmul_rn"- , "fmul_ru"- , "fmul_rz"- , "frcp_rd"- , "frcp_rn"- , "frcp_ru"- , "frcp_rz"- , "frexp"- , "frexpf"- , "frsqrt_rn"- , "fsqrt_rd"- , "fsqrt_rn"- , "fsqrt_ru"- , "fsqrt_rz"- , "fsub_rd"- , "fsub_rn"- , "fsub_ru"- , "fsub_rz"- , "hadd"- , "half2float"- , "hiloint2double"- , "hypot"- , "hypotf"- , "ilogb"- , "ilogbf"- , "int2double_rn"- , "int2float_rd"- , "int2float_rn"- , "int2float_ru"- , "int2float_rz"- , "int_as_float"- , "isfinited"- , "isinfd"- , "isinff"- , "isnand"- , "isnanf"- , "j0"- , "j0f"- , "j1"- , "j1f"- , "jn"- , "jnf"- , "ldexp"- , "ldexpf"- , "lgamma"- , "lgammaf"- , "ll2double_rd"- , "ll2double_rn"- , "ll2double_ru"- , "ll2double_rz"- , "ll2float_rd"- , "ll2float_rn"- , "ll2float_ru"- , "ll2float_rz"- , "llabs"- , "llmax"- , "llmin"- , "llrint"- , "llrintf"- , "llround"- , "llroundf"- , "log"- , "log10"- , "log10f"- , "log1p"- , "log1pf"- , "log2"- , "log2f"- , "logb"- , "logbf"- , "logf"- , "longlong_as_double"- , "max"- , "min"- , "modf"- , "modff"- , "mul24"- , "mul64hi"- , "mulhi"- , "nan"- , "nanf"- , "nearbyint"- , "nearbyintf"- , "nextafter"- , "nextafterf"- , "normcdf"- , "normcdff"- , "normcdfinv"- , "normcdfinvf"- , "popc"- , "popcll"- , "pow"- , "powf"- , "powi"- , "powif"- , "rcbrt"- , "rcbrtf"- , "remainder"- , "remainderf"- , "remquo"- , "remquof"- , "rhadd"- , "rint"- , "rintf"- , "round"- , "roundf"- , "rsqrt"- , "rsqrtf"- , "sad"- , "saturatef"- , "scalbn"- , "scalbnf"- , "signbitd"- , "signbitf"- , "sin"- , "sincos"- , "sincosf"- , "sincospi"- , "sincospif"- , "sinf"- , "sinh"- , "sinhf"- , "sinpi"- , "sinpif"- , "sqrt"- , "sqrtf"- , "tan"- , "tanf"- , "tanh"- , "tanhf"- , "tgamma"- , "tgammaf"- , "trunc"- , "truncf"- , "uhadd"- , "uint2double_rn"- , "uint2float_rd"- , "uint2float_rn"- , "uint2float_ru"- , "uint2float_rz"- , "ull2double_rd"- , "ull2double_rn"- , "ull2double_ru"- , "ull2double_rz"- , "ull2float_rd"- , "ull2float_rn"- , "ull2float_ru"- , "ull2float_rz"- , "ullmax"- , "ullmin"- , "umax"- , "umin"- , "umul24"- , "umul64hi"- , "umulhi"- , "urhadd"- , "usad"- , "y0"- , "y0f"- , "y1"- , "y1f"- , "yn"- , "ynf"- ]-
− Data/Array/Accelerate/LLVM/PTX/CodeGen/Loop.hs
@@ -1,47 +0,0 @@--- |--- Module : Data.Array.Accelerate.LLVM.PTX.CodeGen.Loop--- Copyright : [2015..2017] Trevor L. McDonell--- License : BSD3------ Maintainer : Trevor L. McDonell <tmcdonell@cse.unsw.edu.au>--- Stability : experimental--- Portability : non-portable (GHC extensions)-----module Data.Array.Accelerate.LLVM.PTX.CodeGen.Loop- where---- accelerate-import Data.Array.Accelerate.Type--import Data.Array.Accelerate.LLVM.CodeGen.Arithmetic-import Data.Array.Accelerate.LLVM.CodeGen.IR-import Data.Array.Accelerate.LLVM.CodeGen.Monad-import qualified Data.Array.Accelerate.LLVM.CodeGen.Loop as Loop--import Data.Array.Accelerate.LLVM.PTX.CodeGen.Base----- | A standard loop where the CUDA threads cooperatively step over an index--- space from the start to end indices. The threads stride the array in a way--- that maintains memory coalescing.------ The start and end array indices are given as natural array indexes, and the--- thread specific indices are calculated by the loop.------ > for ( int32 i = blockDim.x * blockIdx.x + threadIdx.x + start--- > ; i < end--- > ; i += blockDim.x * gridDim.x )------ TODO: This assumes that the starting offset retains alignment to the warp--- boundary. This might not always be the case, so provide a version that--- explicitly aligns reads to the warp boundary.----imapFromTo :: IR Int32 -> IR Int32 -> (IR Int32 -> CodeGen ()) -> CodeGen ()-imapFromTo start end body = do- step <- gridSize- tid <- globalThreadIdx- i0 <- add numType tid start- --- Loop.imapFromStepTo i0 step end body-
− Data/Array/Accelerate/LLVM/PTX/CodeGen/Map.hs
@@ -1,60 +0,0 @@-{-# LANGUAGE GADTs #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE ScopedTypeVariables #-}--- |--- Module : Data.Array.Accelerate.LLVM.PTX.CodeGen.Map--- Copyright : [2014..2017] Trevor L. McDonell--- License : BSD3------ Maintainer : Trevor L. McDonell <tmcdonell@cse.unsw.edu.au>--- Stability : experimental--- Portability : non-portable (GHC extensions)-----module Data.Array.Accelerate.LLVM.PTX.CodeGen.Map- where--import Prelude hiding ( fromIntegral )---- accelerate-import Data.Array.Accelerate.Array.Sugar ( Array, Elt )-import Data.Array.Accelerate.Type--import Data.Array.Accelerate.LLVM.CodeGen.Arithmetic-import Data.Array.Accelerate.LLVM.CodeGen.Array-import Data.Array.Accelerate.LLVM.CodeGen.Base-import Data.Array.Accelerate.LLVM.CodeGen.Environment-import Data.Array.Accelerate.LLVM.CodeGen.Monad-import Data.Array.Accelerate.LLVM.CodeGen.Sugar--import Data.Array.Accelerate.LLVM.PTX.CodeGen.Base-import Data.Array.Accelerate.LLVM.PTX.CodeGen.Loop-import Data.Array.Accelerate.LLVM.PTX.Target ( PTX )----- Apply a unary function to each element of an array. Each thread processes--- multiple elements, striding the array by the grid size.----mkMap :: forall aenv sh a b. Elt b- => PTX- -> Gamma aenv- -> IRFun1 PTX aenv (a -> b)- -> IRDelayed PTX aenv (Array sh a)- -> CodeGen (IROpenAcc PTX aenv (Array sh b))-mkMap ptx aenv apply IRDelayed{..} =- let- (start, end, paramGang) = gangParam- (arrOut, paramOut) = mutableArray ("out" :: Name (Array sh b))- paramEnv = envParam aenv- in- makeOpenAcc ptx "map" (paramGang ++ paramOut ++ paramEnv) $ do-- imapFromTo start end $ \i -> do- i' <- fromIntegral integralType numType i- xs <- app1 delayedLinearIndex i'- ys <- app1 apply xs- writeArray arrOut i' ys-- return_-
− Data/Array/Accelerate/LLVM/PTX/CodeGen/Permute.hs
@@ -1,366 +0,0 @@-{-# LANGUAGE GADTs #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE ViewPatterns #-}--- |--- Module : Data.Array.Accelerate.LLVM.PTX.CodeGen.Permute--- Copyright : [2016..2017] Trevor L. McDonell--- License : BSD3------ Maintainer : Trevor L. McDonell <tmcdonell@cse.unsw.edu.au>--- Stability : experimental--- Portability : non-portable (GHC extensions)-----module Data.Array.Accelerate.LLVM.PTX.CodeGen.Permute (-- mkPermute,--) where---- accelerate-import Data.Array.Accelerate.Analysis.Type-import Data.Array.Accelerate.Array.Sugar ( Array, Vector, Shape, Elt, eltType )-import Data.Array.Accelerate.Error-import qualified Data.Array.Accelerate.Array.Sugar as S--import Data.Array.Accelerate.LLVM.CodeGen.Arithmetic as A-import Data.Array.Accelerate.LLVM.CodeGen.Array-import Data.Array.Accelerate.LLVM.CodeGen.Base-import Data.Array.Accelerate.LLVM.CodeGen.Constant-import Data.Array.Accelerate.LLVM.CodeGen.Environment-import Data.Array.Accelerate.LLVM.CodeGen.Exp-import Data.Array.Accelerate.LLVM.CodeGen.IR-import Data.Array.Accelerate.LLVM.CodeGen.Monad-import Data.Array.Accelerate.LLVM.CodeGen.Permute-import Data.Array.Accelerate.LLVM.CodeGen.Ptr-import Data.Array.Accelerate.LLVM.CodeGen.Sugar--import Data.Array.Accelerate.LLVM.PTX.CodeGen.Base-import Data.Array.Accelerate.LLVM.PTX.CodeGen.Loop-import Data.Array.Accelerate.LLVM.PTX.Context-import Data.Array.Accelerate.LLVM.PTX.Target--import LLVM.AST.Type.AddrSpace-import LLVM.AST.Type.Instruction-import LLVM.AST.Type.Instruction.Atomic-import LLVM.AST.Type.Instruction.RMW as RMW-import LLVM.AST.Type.Instruction.Volatile-import LLVM.AST.Type.Operand-import LLVM.AST.Type.Representation--import Foreign.CUDA.Analysis--import Data.Typeable-import Control.Monad ( void )-import Prelude----- Forward permutation specified by an indexing mapping. The resulting array is--- initialised with the given defaults, and any further values that are permuted--- into the result array are added to the current value using the combination--- function.------ The combination function must be /associative/ and /commutative/. Elements--- that are mapped to the magic index 'ignore' are dropped.------ Parallel forward permutation has to take special care because different--- threads could concurrently try to update the same memory location. Where--- available we make use of special atomic instructions and other optimisations,--- but in the general case each element of the output array has a lock which--- must be obtained by the thread before it can update that memory location.------ TODO: After too many failures to acquire the lock on an element, the thread--- should back off and try a different element, adding this failed element to--- a queue or some such.----mkPermute- :: forall aenv sh sh' e. (Shape sh, Shape sh', Elt e)- => PTX- -> Gamma aenv- -> IRPermuteFun PTX aenv (e -> e -> e)- -> IRFun1 PTX aenv (sh -> sh')- -> IRDelayed PTX aenv (Array sh e)- -> CodeGen (IROpenAcc PTX aenv (Array sh' e))-mkPermute ptx aenv IRPermuteFun{..} project arr =- let- bytes = sizeOf (eltType (undefined :: e))- sizeOk = bytes == 4 || bytes == 8- in- case atomicRMW of- Just (rmw, f) | sizeOk -> mkPermute_rmw ptx aenv rmw f project arr- _ -> mkPermute_mutex ptx aenv combine project arr----- Parallel forward permutation function which uses atomic instructions to--- implement lock-free array updates.------ Atomic instruction support on CUDA devices is a bit patchy, so depending on--- the element type and compute capability of the target hardware we may need to--- emulate the operation using atomic compare-and-swap.------ Int32 Int64 Float32 Float64--- +------------------------------------------- (+) | 2.0 2.0 2.0 6.0--- (-) | 2.0 2.0 x x--- (.&.) | 2.0 3.2--- (.|.) | 2.0 3.2--- xor | 2.0 3.2--- min | 2.0 3.2 x x--- max | 2.0 3.2 x x--- CAS | 2.0 2.0------ Note that NVPTX requires at least compute 2.0, so we can always implement the--- lockfree update operations in terms of compare-and-swap.----mkPermute_rmw- :: forall aenv sh sh' e. (Shape sh, Shape sh', Elt e)- => PTX- -> Gamma aenv- -> RMWOperation- -> IRFun1 PTX aenv (e -> e)- -> IRFun1 PTX aenv (sh -> sh')- -> IRDelayed PTX aenv (Array sh e)- -> CodeGen (IROpenAcc PTX aenv (Array sh' e))-mkPermute_rmw ptx@(deviceProperties . ptxContext -> dev) aenv rmw update project IRDelayed{..} =- let- (start, end, paramGang) = gangParam- (arrOut, paramOut) = mutableArray ("out" :: Name (Array sh' e))- paramEnv = envParam aenv- --- bytes = sizeOf (eltType (undefined :: e))- compute = computeCapability dev- compute32 = Compute 3 2- -- compute60 = Compute 6 0- in- makeOpenAcc ptx "permute_rmw" (paramGang ++ paramOut ++ paramEnv) $ do-- sh <- delayedExtent-- imapFromTo start end $ \i -> do-- i' <- A.fromIntegral integralType numType i- ix <- indexOfInt sh i'- ix' <- app1 project ix-- unless (ignore ix') $ do- j <- intOfIndex (irArrayShape arrOut) ix'- x <- app1 delayedLinearIndex i'- r <- app1 update x-- case rmw of- Exchange- -> writeArray arrOut j r- --- _ | SingleTuple s <- eltType (undefined::e)- , Just adata <- gcast (irArrayData arrOut)- , Just r' <- gcast r- -> do- addr <- instr' $ GetElementPtr (asPtr defaultAddrSpace (op s adata)) [op integralType j]- --- let- rmw_integral :: IntegralType t -> Operand (Ptr t) -> Operand t -> CodeGen ()- rmw_integral t ptr val- | primOk = void . instr' $ AtomicRMW t NonVolatile rmw ptr val (CrossThread, AcquireRelease)- | otherwise =- case rmw of- RMW.And -> atomicCAS_rmw s' (A.band t (ir t val)) ptr- RMW.Or -> atomicCAS_rmw s' (A.bor t (ir t val)) ptr- RMW.Xor -> atomicCAS_rmw s' (A.xor t (ir t val)) ptr- RMW.Min -> atomicCAS_cmp s' A.lt ptr val- RMW.Max -> atomicCAS_cmp s' A.gt ptr val- _ -> $internalError "mkPermute_rmw.integral" "unexpected transition"- where- s' = NumScalarType (IntegralNumType t)- primOk = compute >= compute32- || bytes == 4- || case rmw of- RMW.Add -> True- RMW.Sub -> True- _ -> False-- rmw_floating :: FloatingType t -> Operand (Ptr t) -> Operand t -> CodeGen ()- rmw_floating t ptr val =- case rmw of- RMW.Min -> atomicCAS_cmp s' A.lt ptr val- RMW.Max -> atomicCAS_cmp s' A.gt ptr val- RMW.Sub -> atomicCAS_rmw s' (A.sub n (ir t val)) ptr- RMW.Add- | primAdd -> atomicAdd_f t ptr val- | otherwise -> atomicCAS_rmw s' (A.add n (ir t val)) ptr- _ -> $internalError "mkPermute_rmw.floating" "unexpected transition"- where- n = FloatingNumType t- s' = NumScalarType n- primAdd = bytes == 4- -- Disabling due to missing support from llvm-4.0.- -- <https://github.com/AccelerateHS/accelerate/issues/363>- -- compute >= compute60-- rmw_nonnum :: NonNumType t -> Operand (Ptr t) -> Operand t -> CodeGen ()- rmw_nonnum TypeChar{} ptr val = do- ptr32 <- instr' $ PtrCast (primType :: PrimType (Ptr Word32)) ptr- val32 <- instr' $ BitCast (scalarType :: ScalarType Word32) val- void $ instr' $ AtomicRMW (integralType :: IntegralType Word32) NonVolatile rmw ptr32 val32 (CrossThread, AcquireRelease)- rmw_nonnum _ _ _ = -- C character types are 8-bit, and thus not supported- $internalError "mkPermute_rmw.nonnum" "unexpected transition"- case s of- NumScalarType (IntegralNumType t) -> rmw_integral t addr (op t r')- NumScalarType (FloatingNumType t) -> rmw_floating t addr (op t r')- NonNumScalarType t -> rmw_nonnum t addr (op t r')- --- _ -> $internalError "mkPermute_rmw" "unexpected transition"-- return_----- Parallel forward permutation function which uses a spinlock to acquire--- a mutex before updating the value at that location.----mkPermute_mutex- :: forall aenv sh sh' e. (Shape sh, Shape sh', Elt e)- => PTX- -> Gamma aenv- -> IRFun2 PTX aenv (e -> e -> e)- -> IRFun1 PTX aenv (sh -> sh')- -> IRDelayed PTX aenv (Array sh e)- -> CodeGen (IROpenAcc PTX aenv (Array sh' e))-mkPermute_mutex ptx aenv combine project IRDelayed{..} =- let- (start, end, paramGang) = gangParam- (arrOut, paramOut) = mutableArray ("out" :: Name (Array sh' e))- (arrLock, paramLock) = mutableArray ("lock" :: Name (Vector Word32))- paramEnv = envParam aenv- in- makeOpenAcc ptx "permute_mutex" (paramGang ++ paramOut ++ paramLock ++ paramEnv) $ do-- sh <- delayedExtent-- imapFromTo start end $ \i -> do-- i' <- A.fromIntegral integralType numType i- ix <- indexOfInt sh i'- ix' <- app1 project ix-- -- project element onto the destination array and (atomically) update- unless (ignore ix') $ do- j <- intOfIndex (irArrayShape arrOut) ix'- x <- app1 delayedLinearIndex i'-- atomically arrLock j $ do- y <- readArray arrOut j- r <- app2 combine x y- writeArray arrOut j r-- return_----- Atomically execute the critical section only when the lock at the given array--- index is obtained. The thread spins waiting for the lock to be released and--- there is no backoff strategy in case the lock is contended.------ The canonical implementation of a spin-lock looks like this:------ > do {--- > old = atomic_exchange(&lock[i], 1);--- > } while (old == 1);--- >--- > /* critical section */--- >--- > atomic_exchange(&lock[i], 0);------ The initial loop repeatedly attempts to take the lock by writing a 1 (locked)--- into the lock slot. Once the 'old' state of the lock returns 0 (unlocked),--- then we just acquired the lock and the atomic section can be computed.--- Finally, the lock is released by writing 0 back to the lock slot.------ However, there is a complication with CUDA devices because all threads in--- a warp must execute in lockstep (with predicated execution). In the above--- setup, once a thread acquires a lock, then it will be disabled and stop--- participating in the loop, waiting for all other threads (to acquire their--- locks) before continuing program execution. If two threads in the same warp--- attempt to acquire the same lock, then once the lock is acquired by one--- thread then it will sit idle waiting while the second thread spins attempting--- to grab a lock that will never be released because the first thread (which--- holds the lock) can not make progress. DEADLOCK.------ To prevent this situation we must invert the algorithm so that threads can--- always make progress, until each warp in the thread has committed their--- result.------ > done = 0;--- > do {--- > if ( atomic_exchange(&lock[i], 1) == 0 ) {--- >--- > /* critical section */--- >--- > done = 1;--- > atomic_exchange(&lock[i], 0);--- > }--- > } while ( done == 0 );----atomically- :: IRArray (Vector Word32)- -> IR Int- -> CodeGen a- -> CodeGen a-atomically barriers i action = do- let- lock = integral integralType 1- unlock = integral integralType 0- unlock' = lift 0- --- spin <- newBlock "spinlock.entry"- crit <- newBlock "spinlock.critical-start"- skip <- newBlock "spinlock.critical-end"- exit <- newBlock "spinlock.exit"-- addr <- instr' $ GetElementPtr (asPtr defaultAddrSpace (op integralType (irArrayData barriers))) [op integralType i]- _ <- br spin-- -- Loop until this thread has completed its critical section. If the slot was- -- unlocked then we just acquired the lock and the thread can perform the- -- critical section, otherwise skip to the bottom of the critical section.- setBlock spin- old <- instr $ AtomicRMW integralType NonVolatile Exchange addr lock (CrossThread, Acquire)- ok <- A.eq scalarType old unlock'- no <- cbr ok crit skip-- -- If we just acquired the lock, execute the critical section- setBlock crit- r <- action- _ <- instr $ AtomicRMW integralType NonVolatile Exchange addr unlock (CrossThread, Release)- yes <- br skip-- -- At the base of the critical section, threads participate in a memory fence- -- to ensure the lock state is committed to memory. Depending on which- -- incoming edge the thread arrived at this block from determines whether they- -- have completed their critical section.- setBlock skip- done <- phi [(lift True, yes), (lift False, no)]-- __syncthreads- _ <- cbr done exit spin-- setBlock exit- return r----- Helper functions--- -------------------- Test whether the given index is the magic value 'ignore'. This operates--- strictly rather than performing short-circuit (&&).----ignore :: forall ix. Shape ix => IR ix -> CodeGen (IR Bool)-ignore (IR ix) = go (S.eltType (undefined::ix)) (S.fromElt (S.ignore::ix)) ix- where- go :: TupleType t -> t -> Operands t -> CodeGen (IR Bool)- go UnitTuple () OP_Unit = return (lift True)- go (PairTuple tsh tsz) (ish, isz) (OP_Pair sh sz) = do x <- go tsh ish sh- y <- go tsz isz sz- land' x y- go (SingleTuple t) ig sz = A.eq t (ir t (scalar t ig)) (ir t (op' t sz))-
− Data/Array/Accelerate/LLVM/PTX/CodeGen/Queue.hs
@@ -1,118 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TemplateHaskell #-}--- |--- Module : Data.Array.Accelerate.LLVM.PTX.CodeGen.Queue--- Copyright : [2014..2017] Trevor L. McDonell--- License : BSD3------ Maintainer : Trevor L. McDonell <tmcdonell@cse.unsw.edu.au>--- Stability : experimental--- Portability : non-portable (GHC extensions)------ Abstractions for creating simply dynamically scheduled work queues. This--- works by atomically incrementing a global counter (in global memory) and--- distributing this result to each thread in the block (via shared memory).--- Thus there is an additional ~1000 cycle overhead for a thread block to--- determine their next work item. This also implies all thread blocks are--- contending for the same global counter.------ In practice this extra overhead is not always worth paying. We use it for--- segmented reductions, because the length of each segment is unknown apriori--- and the entire thread block participates in the reduction of a segment. On--- the other hand, the arithmetically unbalanced mandelbrot fractal program was--- (generally) slower with this addition, so for now at least keep (morally)--- balanced operations (map, generate) with a static schedule. (Admittidely this--- test was on my very old 650M, so newer/more powerful GPUs with faster atomic--- instructions or more inflight thread blocks could benefit more.)-----module Data.Array.Accelerate.LLVM.PTX.CodeGen.Queue- where--import Data.Array.Accelerate.LLVM.CodeGen.Arithmetic as A-import Data.Array.Accelerate.LLVM.CodeGen.Downcast-import Data.Array.Accelerate.LLVM.CodeGen.IR-import Data.Array.Accelerate.LLVM.CodeGen.Monad-import Data.Array.Accelerate.LLVM.CodeGen.Sugar--import Data.Array.Accelerate.LLVM.PTX.Analysis.Launch-import Data.Array.Accelerate.LLVM.PTX.CodeGen.Base-import Data.Array.Accelerate.LLVM.PTX.Target--import LLVM.AST.Type.Constant-import LLVM.AST.Type.Instruction-import LLVM.AST.Type.Instruction.Atomic-import LLVM.AST.Type.Instruction.Volatile-import LLVM.AST.Type.Operand-import LLVM.AST.Type.Representation-import qualified LLVM.AST.Global as LLVM-import qualified LLVM.AST.Linkage as LLVM-import qualified LLVM.AST.Name as LLVM-import qualified LLVM.AST.Type as LLVM-import qualified LLVM.AST.Type.Instruction.RMW as RMW----- Interface--- -----------type WorkQueue = (Operand (Ptr Int32), Operand (Ptr Int32))---- Declare a new dynamically scheduled global work queue. Don't forget to--- initialise the queue with the kernel generated by 'mkQueueInit'.----globalWorkQueue :: CodeGen WorkQueue-globalWorkQueue = do- sn <- freshName- declare $ LLVM.globalVariableDefaults- { LLVM.name = LLVM.Name "__queue__"- , LLVM.type' = LLVM.IntegerType 32- , LLVM.alignment = 4- }- declare $ LLVM.globalVariableDefaults- { LLVM.name = downcast sn- , LLVM.addrSpace = sharedMemAddrSpace- , LLVM.type' = LLVM.IntegerType 32- , LLVM.linkage = LLVM.Internal- , LLVM.alignment = 4- }- return ( ConstantOperand (GlobalReference type' "__queue__")- , ConstantOperand (GlobalReference type' sn) )----- Dequeue the next 'n' items from the work queue for evaluation by the calling--- thread block. Each thread in the thread block receives the index of the start--- of the newly acquired range.----dequeue :: WorkQueue -> IR Int32 -> CodeGen (IR Int32)-dequeue (queue, smem) n = do- tid <- threadIdx- when (A.eq scalarType tid (lift 0)) $ do- v <- instr' $ AtomicRMW integralType NonVolatile RMW.Add queue (op integralType n) (CrossThread, AcquireRelease)- _ <- instr' $ Store Volatile smem v- return ()- --- __syncthreads- v <- instr' $ Load scalarType Volatile smem- return (ir integralType v)----- Initialisation kernel--- ------------------------- This kernel is used to initialise the dynamically scheduled work queue. It--- must be called before the main kernel, which uses the work queue, is invoked.----mkQueueInit- :: DeviceProperties- -> CodeGen (IROpenAcc PTX aenv a)-mkQueueInit dev =- let- (start, _end, paramGang) = gangParam- config = launchConfig dev [1] (\_ -> 0) (\_ _ -> 1) [|| \_ _ -> 1 ||]- in- makeOpenAccWith config "qinit" paramGang $ do- (queue,_) <- globalWorkQueue- _ <- instr' $ Store Volatile queue (op integralType start)- return_-
− Data/Array/Accelerate/LLVM/PTX/CodeGen/Scan.hs
@@ -1,1335 +0,0 @@-{-# LANGUAGE GADTs #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE PatternGuards #-}-{-# LANGUAGE RebindableSyntax #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE TypeOperators #-}-{-# LANGUAGE ViewPatterns #-}--- |--- Module : Data.Array.Accelerate.LLVM.PTX.CodeGen.Scan--- Copyright : [2016..2017] Trevor L. McDonell--- License : BSD3------ Maintainer : Trevor L. McDonell <tmcdonell@cse.unsw.edu.au>--- Stability : experimental--- Portability : non-portable (GHC extensions)-----module Data.Array.Accelerate.LLVM.PTX.CodeGen.Scan (-- mkScanl, mkScanl1, mkScanl',- mkScanr, mkScanr1, mkScanr',--) where---- accelerate-import Data.Array.Accelerate.Analysis.Type-import Data.Array.Accelerate.Array.Sugar--import Data.Array.Accelerate.LLVM.Analysis.Match-import Data.Array.Accelerate.LLVM.CodeGen.Arithmetic as A-import Data.Array.Accelerate.LLVM.CodeGen.Array-import Data.Array.Accelerate.LLVM.CodeGen.Base-import Data.Array.Accelerate.LLVM.CodeGen.Constant-import Data.Array.Accelerate.LLVM.CodeGen.Environment-import Data.Array.Accelerate.LLVM.CodeGen.Exp-import Data.Array.Accelerate.LLVM.CodeGen.IR-import Data.Array.Accelerate.LLVM.CodeGen.Loop-import Data.Array.Accelerate.LLVM.CodeGen.Monad-import Data.Array.Accelerate.LLVM.CodeGen.Sugar-import Data.Array.Accelerate.LLVM.PTX.Analysis.Launch-import Data.Array.Accelerate.LLVM.PTX.CodeGen.Base-import Data.Array.Accelerate.LLVM.PTX.CodeGen.Generate-import Data.Array.Accelerate.LLVM.PTX.Context-import Data.Array.Accelerate.LLVM.PTX.Target--import LLVM.AST.Type.Representation--import qualified Foreign.CUDA.Analysis as CUDA--import Control.Applicative-import Control.Monad ( (>=>), void )-import Data.String ( fromString )-import Data.Coerce as Safe-import Data.Bits as P-import Prelude as P hiding ( last )---data Direction = L | R---- 'Data.List.scanl' style left-to-right exclusive scan, but with the--- restriction that the combination function must be associative to enable--- efficient parallel implementation.------ > scanl (+) 10 (use $ fromList (Z :. 10) [0..])--- >--- > ==> Array (Z :. 11) [10,10,11,13,16,20,25,31,38,46,55]----mkScanl- :: forall aenv sh e. (Shape sh, Elt e)- => PTX- -> Gamma aenv- -> IRFun2 PTX aenv (e -> e -> e)- -> IRExp PTX aenv e- -> IRDelayed PTX aenv (Array (sh:.Int) e)- -> CodeGen (IROpenAcc PTX aenv (Array (sh:.Int) e))-mkScanl ptx@(deviceProperties . ptxContext -> dev) aenv combine seed arr- | Just Refl <- matchShapeType (undefined::sh) (undefined::Z)- = foldr1 (+++) <$> sequence [ mkScanAllP1 L dev aenv combine (Just seed) arr- , mkScanAllP2 L dev aenv combine- , mkScanAllP3 L dev aenv combine (Just seed)- , mkScanFill ptx aenv seed- ]- --- | otherwise- = (+++) <$> mkScanDim L dev aenv combine (Just seed) arr- <*> mkScanFill ptx aenv seed----- 'Data.List.scanl1' style left-to-right inclusive scan, but with the--- restriction that the combination function must be associative to enable--- efficient parallel implementation. The array must not be empty.------ > scanl1 (+) (use $ fromList (Z :. 10) [0..])--- >--- > ==> Array (Z :. 10) [0,1,3,6,10,15,21,28,36,45]----mkScanl1- :: forall aenv sh e. (Shape sh, Elt e)- => PTX- -> Gamma aenv- -> IRFun2 PTX aenv (e -> e -> e)- -> IRDelayed PTX aenv (Array (sh:.Int) e)- -> CodeGen (IROpenAcc PTX aenv (Array (sh:.Int) e))-mkScanl1 (deviceProperties . ptxContext -> dev) aenv combine arr- | Just Refl <- matchShapeType (undefined::sh) (undefined::Z)- = foldr1 (+++) <$> sequence [ mkScanAllP1 L dev aenv combine Nothing arr- , mkScanAllP2 L dev aenv combine- , mkScanAllP3 L dev aenv combine Nothing- ]- --- | otherwise- = mkScanDim L dev aenv combine Nothing arr----- Variant of 'scanl' where the final result is returned in a separate array.------ > scanr' (+) 10 (use $ fromList (Z :. 10) [0..])--- >--- > ==> ( Array (Z :. 10) [10,10,11,13,16,20,25,31,38,46]--- , Array Z [55]--- )----mkScanl'- :: forall aenv sh e. (Shape sh, Elt e)- => PTX- -> Gamma aenv- -> IRFun2 PTX aenv (e -> e -> e)- -> IRExp PTX aenv e- -> IRDelayed PTX aenv (Array (sh:.Int) e)- -> CodeGen (IROpenAcc PTX aenv (Array (sh:.Int) e, Array sh e))-mkScanl' ptx@(deviceProperties . ptxContext -> dev) aenv combine seed arr- | Just Refl <- matchShapeType (undefined::sh) (undefined::Z)- = foldr1 (+++) <$> sequence [ mkScan'AllP1 L dev aenv combine seed arr- , mkScan'AllP2 L dev aenv combine- , mkScan'AllP3 L dev aenv combine- , mkScan'Fill ptx aenv seed- ]- --- | otherwise- = (+++) <$> mkScan'Dim L dev aenv combine seed arr- <*> mkScan'Fill ptx aenv seed----- 'Data.List.scanr' style right-to-left exclusive scan, but with the--- restriction that the combination function must be associative to enable--- efficient parallel implementation.------ > scanr (+) 10 (use $ fromList (Z :. 10) [0..])--- >--- > ==> Array (Z :. 11) [55,55,54,52,49,45,40,34,27,19,10]----mkScanr- :: forall aenv sh e. (Shape sh, Elt e)- => PTX- -> Gamma aenv- -> IRFun2 PTX aenv (e -> e -> e)- -> IRExp PTX aenv e- -> IRDelayed PTX aenv (Array (sh:.Int) e)- -> CodeGen (IROpenAcc PTX aenv (Array (sh:.Int) e))-mkScanr ptx@(deviceProperties . ptxContext -> dev) aenv combine seed arr- | Just Refl <- matchShapeType (undefined::sh) (undefined::Z)- = foldr1 (+++) <$> sequence [ mkScanAllP1 R dev aenv combine (Just seed) arr- , mkScanAllP2 R dev aenv combine- , mkScanAllP3 R dev aenv combine (Just seed)- , mkScanFill ptx aenv seed- ]- --- | otherwise- = (+++) <$> mkScanDim R dev aenv combine (Just seed) arr- <*> mkScanFill ptx aenv seed----- 'Data.List.scanr1' style right-to-left inclusive scan, but with the--- restriction that the combination function must be associative to enable--- efficient parallel implementation. The array must not be empty.------ > scanr (+) 10 (use $ fromList (Z :. 10) [0..])--- >--- > ==> Array (Z :. 10) [45,45,44,42,39,35,30,24,17,9]----mkScanr1- :: forall aenv sh e. (Shape sh, Elt e)- => PTX- -> Gamma aenv- -> IRFun2 PTX aenv (e -> e -> e)- -> IRDelayed PTX aenv (Array (sh:.Int) e)- -> CodeGen (IROpenAcc PTX aenv (Array (sh:.Int) e))-mkScanr1 (deviceProperties . ptxContext -> dev) aenv combine arr- | Just Refl <- matchShapeType (undefined::sh) (undefined::Z)- = foldr1 (+++) <$> sequence [ mkScanAllP1 R dev aenv combine Nothing arr- , mkScanAllP2 R dev aenv combine- , mkScanAllP3 R dev aenv combine Nothing- ]- --- | otherwise- = mkScanDim R dev aenv combine Nothing arr----- Variant of 'scanr' where the final result is returned in a separate array.------ > scanr' (+) 10 (use $ fromList (Z :. 10) [0..])--- >--- > ==> ( Array (Z :. 10) [55,54,52,49,45,40,34,27,19,10]--- , Array Z [55]--- )----mkScanr'- :: forall aenv sh e. (Shape sh, Elt e)- => PTX- -> Gamma aenv- -> IRFun2 PTX aenv (e -> e -> e)- -> IRExp PTX aenv e- -> IRDelayed PTX aenv (Array (sh:.Int) e)- -> CodeGen (IROpenAcc PTX aenv (Array (sh:.Int) e, Array sh e))-mkScanr' ptx@(deviceProperties . ptxContext -> dev) aenv combine seed arr- | Just Refl <- matchShapeType (undefined::sh) (undefined::Z)- = foldr1 (+++) <$> sequence [ mkScan'AllP1 R dev aenv combine seed arr- , mkScan'AllP2 R dev aenv combine- , mkScan'AllP3 R dev aenv combine- , mkScan'Fill ptx aenv seed- ]- --- | otherwise- = (+++) <$> mkScan'Dim R dev aenv combine seed arr- <*> mkScan'Fill ptx aenv seed----- Device wide scans--- ----------------------- This is a classic two-pass algorithm which proceeds in two phases and--- requires ~4n data movement to global memory. In future we would like to--- replace this with a single pass algorithm.------- Parallel scan, step 1.------ Threads scan a stripe of the input into a temporary array, incorporating the--- initial element and any fused functions on the way. The final reduction--- result of this chunk is written to a separate array.----mkScanAllP1- :: forall aenv e. Elt e- => Direction- -> DeviceProperties -- ^ properties of the target GPU- -> Gamma aenv -- ^ array environment- -> IRFun2 PTX aenv (e -> e -> e) -- ^ combination function- -> Maybe (IRExp PTX aenv e) -- ^ seed element, if this is an exclusive scan- -> IRDelayed PTX aenv (Vector e) -- ^ input data- -> CodeGen (IROpenAcc PTX aenv (Vector e))-mkScanAllP1 dir dev aenv combine mseed IRDelayed{..} =- let- (start, end, paramGang) = gangParam- (arrOut, paramOut) = mutableArray ("out" :: Name (Vector e))- (arrTmp, paramTmp) = mutableArray ("tmp" :: Name (Vector e))- paramEnv = envParam aenv- --- config = launchConfig dev (CUDA.incWarp dev) smem const [|| const ||]- smem n = warps * (1 + per_warp) * bytes- where- ws = CUDA.warpSize dev- warps = n `P.quot` ws- per_warp = ws + ws `P.quot` 2- bytes = sizeOf (eltType (undefined :: e))- in- makeOpenAccWith config "scanP1" (paramGang ++ paramTmp ++ paramOut ++ paramEnv) $ do-- -- Size of the input array- sz <- A.fromIntegral integralType numType . indexHead =<< delayedExtent-- -- A thread block scans a non-empty stripe of the input, storing the final- -- block-wide aggregate into a separate array- --- -- For exclusive scans, thread 0 of segment 0 must incorporate the initial- -- element into the input and output. Threads shuffle their indices- -- appropriately.- --- bid <- blockIdx- gd <- gridDim- s0 <- A.add numType start bid-- -- iterating over thread-block-wide segments- imapFromStepTo s0 gd end $ \chunk -> do-- bd <- blockDim- inf <- A.mul numType chunk bd-- -- index i* is the index that this thread will read data from. Recall that- -- the supremum index is exclusive- tid <- threadIdx- i0 <- case dir of- L -> A.add numType inf tid- R -> do x <- A.sub numType sz inf- y <- A.sub numType x tid- z <- A.sub numType y (lift 1)- return z-- -- index j* is the index that we write to. Recall that for exclusive scans- -- the output array is one larger than the input; the initial element will- -- be written into this spot by thread 0 of the first thread block.- j0 <- case mseed of- Nothing -> return i0- Just _ -> case dir of- L -> A.add numType i0 (lift 1)- R -> return i0-- -- If this thread has input, read data and participate in thread-block scan- let valid i = case dir of- L -> A.lt scalarType i sz- R -> A.gte scalarType i (lift 0)-- when (valid i0) $ do- x0 <- app1 delayedLinearIndex =<< A.fromIntegral integralType numType i0- x1 <- case mseed of- Nothing -> return x0- Just seed ->- if A.eq scalarType tid (lift 0) `A.land` A.eq scalarType chunk (lift 0)- then do- z <- seed- case dir of- L -> writeArray arrOut (lift 0 :: IR Int32) z >> app2 combine z x0- R -> writeArray arrOut sz z >> app2 combine x0 z- else- return x0-- n <- A.sub numType sz inf- x2 <- if A.gte scalarType n bd- then scanBlockSMem dir dev combine Nothing x1- else scanBlockSMem dir dev combine (Just n) x1-- -- Write this thread's scan result to memory- writeArray arrOut j0 x2-- -- The last thread also writes its result---the aggregate for this- -- thread block---to the temporary partial sums array. This is only- -- necessary for full blocks in a multi-block scan; the final- -- partially-full tile does not have a successor block.- last <- A.sub numType bd (lift 1)- when (A.gt scalarType gd (lift 1) `land` A.eq scalarType tid last) $- case dir of- L -> writeArray arrTmp chunk x2- R -> do u <- A.sub numType end chunk- v <- A.sub numType u (lift 1)- writeArray arrTmp v x2-- return_----- Parallel scan, step 2------ A single thread block performs a scan of the per-block aggregates computed in--- step 1. This gives the per-block prefix which must be added to each element--- in step 3.----mkScanAllP2- :: forall aenv e. Elt e- => Direction- -> DeviceProperties -- ^ properties of the target GPU- -> Gamma aenv -- ^ array environment- -> IRFun2 PTX aenv (e -> e -> e) -- ^ combination function- -> CodeGen (IROpenAcc PTX aenv (Vector e))-mkScanAllP2 dir dev aenv combine =- let- (start, end, paramGang) = gangParam- (arrTmp, paramTmp) = mutableArray ("tmp" :: Name (Vector e))- paramEnv = envParam aenv- --- config = launchConfig dev (CUDA.incWarp dev) smem grid gridQ- grid _ _ = 1- gridQ = [|| \_ _ -> 1 ||]- smem n = warps * (1 + per_warp) * bytes- where- ws = CUDA.warpSize dev- warps = n `P.quot` ws- per_warp = ws + ws `P.quot` 2- bytes = sizeOf (eltType (undefined :: e))- in- makeOpenAccWith config "scanP2" (paramGang ++ paramTmp ++ paramEnv) $ do-- -- The first and last threads of the block need to communicate the- -- block-wide aggregate as a carry-in value across iterations.- --- -- TODO: We could optimise this a bit if we can get access to the shared- -- memory area used by 'scanBlockSMem', and from there directly read the- -- value computed by the last thread.- carry <- staticSharedMem 1-- bd <- blockDim- imapFromStepTo start bd end $ \offset -> do-- -- Index of the partial sums array that this thread will process.- tid <- threadIdx- i0 <- case dir of- L -> A.add numType offset tid- R -> do x <- A.sub numType end offset- y <- A.sub numType x tid- z <- A.sub numType y (lift 1)- return z-- let valid i = case dir of- L -> A.lt scalarType i end- R -> A.gte scalarType i start-- when (valid i0) $ do-- __syncthreads-- x0 <- readArray arrTmp i0- x1 <- if A.gt scalarType offset (lift 0) `land` A.eq scalarType tid (lift 0)- then do- c <- readArray carry (lift 0 :: IR Int32)- case dir of- L -> app2 combine c x0- R -> app2 combine x0 c- else do- return x0-- n <- A.sub numType end offset- x2 <- if A.gte scalarType n bd- then scanBlockSMem dir dev combine Nothing x1- else scanBlockSMem dir dev combine (Just n) x1-- -- Update the temporary array with this thread's result- writeArray arrTmp i0 x2-- -- The last thread writes the carry-out value. If the last thread is not- -- active, then this must be the last stripe anyway.- last <- A.sub numType bd (lift 1)- when (A.eq scalarType tid last) $- writeArray carry (lift 0 :: IR Int32) x2-- return_----- Parallel scan, step 3.------ Threads combine every element of the partial block results with the carry-in--- value computed in step 2.----mkScanAllP3- :: forall aenv e. Elt e- => Direction- -> DeviceProperties -- ^ properties of the target GPU- -> Gamma aenv -- ^ array environment- -> IRFun2 PTX aenv (e -> e -> e) -- ^ combination function- -> Maybe (IRExp PTX aenv e) -- ^ seed element, if this is an exclusive scan- -> CodeGen (IROpenAcc PTX aenv (Vector e))-mkScanAllP3 dir dev aenv combine mseed =- let- (start, end, paramGang) = gangParam- (arrOut, paramOut) = mutableArray ("out" :: Name (Vector e))- (arrTmp, paramTmp) = mutableArray ("tmp" :: Name (Vector e))- paramEnv = envParam aenv- --- stride = local scalarType ("ix.stride" :: Name Int32)- paramStride = scalarParameter scalarType ("ix.stride" :: Name Int32)- --- config = launchConfig dev (CUDA.incWarp dev) (const 0) const [|| const ||]- in- makeOpenAccWith config "scanP3" (paramGang ++ paramTmp ++ paramOut ++ paramStride : paramEnv) $ do-- sz <- A.fromIntegral integralType numType (indexHead (irArrayShape arrOut))- tid <- threadIdx-- -- Threads that will never contribute can just exit immediately. The size of- -- each chunk is set by the block dimension of the step 1 kernel, which may- -- be different from the block size of this kernel.- when (A.lt scalarType tid stride) $ do-- -- Iterate over the segments computed in phase 1. Note that we have one- -- fewer chunk to process because the first has no carry-in.- bid <- blockIdx- gd <- gridDim- c0 <- A.add numType start bid- imapFromStepTo c0 gd end $ \chunk -> do-- -- Determine the start and end indicies of this chunk to which we will- -- carry-in the value. Returned for left-to-right traversal.- (inf,sup) <- case dir of- L -> do- a <- A.add numType chunk (lift 1)- b <- A.mul numType stride a- case mseed of- Just{} -> do- c <- A.add numType b (lift 1)- d <- A.add numType c stride- e <- A.min scalarType d sz- return (c,e)- Nothing -> do- c <- A.add numType b stride- d <- A.min scalarType c sz- return (b,d)- R -> do- a <- A.sub numType end chunk- b <- A.mul numType stride a- c <- A.sub numType sz b- case mseed of- Just{} -> do- d <- A.sub numType c (lift 1)- e <- A.sub numType d stride- f <- A.max scalarType e (lift 0)- return (f,d)- Nothing -> do- d <- A.sub numType c stride- e <- A.max scalarType d (lift 0)- return (e,c)-- -- Read the carry-in value- carry <- case dir of- L -> readArray arrTmp chunk- R -> do- a <- A.add numType chunk (lift 1)- b <- readArray arrTmp a- return b-- -- Apply the carry-in value to each element in the chunk- bd <- blockDim- i0 <- A.add numType inf tid- imapFromStepTo i0 bd sup $ \i -> do- v <- readArray arrOut i- u <- case dir of- L -> app2 combine carry v- R -> app2 combine v carry- writeArray arrOut i u-- return_----- Parallel scan', step 1.------ Similar to mkScanAllP1. Threads scan a stripe of the input into a temporary--- array, incorporating the initial element and any fused functions on the way.--- The final reduction result of this chunk is written to a separate array.----mkScan'AllP1- :: forall aenv e. Elt e- => Direction- -> DeviceProperties- -> Gamma aenv- -> IRFun2 PTX aenv (e -> e -> e)- -> IRExp PTX aenv e- -> IRDelayed PTX aenv (Vector e)- -> CodeGen (IROpenAcc PTX aenv (Vector e, Scalar e))-mkScan'AllP1 dir dev aenv combine seed IRDelayed{..} =- let- (start, end, paramGang) = gangParam- (arrOut, paramOut) = mutableArray ("out" :: Name (Vector e))- (arrTmp, paramTmp) = mutableArray ("tmp" :: Name (Vector e))- paramEnv = envParam aenv- --- config = launchConfig dev (CUDA.incWarp dev) smem const [|| const ||]- smem n = warps * (1 + per_warp) * bytes- where- ws = CUDA.warpSize dev- warps = n `P.quot` ws- per_warp = ws + ws `P.quot` 2- bytes = sizeOf (eltType (undefined :: e))- in- makeOpenAccWith config "scanP1" (paramGang ++ paramTmp ++ paramOut ++ paramEnv) $ do-- -- Size of the input array- sz <- A.fromIntegral integralType numType . indexHead =<< delayedExtent-- -- A thread block scans a non-empty stripe of the input, storing the partial- -- result and the final block-wide aggregate- bid <- blockIdx- gd <- gridDim- s0 <- A.add numType start bid-- -- iterate over thread-block wide segments- imapFromStepTo s0 gd end $ \seg -> do-- bd <- blockDim- inf <- A.mul numType seg bd-- -- i* is the index that this thread will read data from- tid <- threadIdx- i0 <- case dir of- L -> A.add numType inf tid- R -> do x <- A.sub numType sz inf- y <- A.sub numType x tid- z <- A.sub numType y (lift 1)- return z-- -- j* is the index this thread will write to. This is just shifted by one- -- to make room for the initial element- j0 <- case dir of- L -> A.add numType i0 (lift 1)- R -> A.sub numType i0 (lift 1)-- -- If this thread has input it participates in the scan- let valid i = case dir of- L -> A.lt scalarType i sz- R -> A.gte scalarType i (lift 0)-- when (valid i0) $ do- x0 <- app1 delayedLinearIndex =<< A.fromIntegral integralType numType i0-- -- Thread 0 of the first segment must also evaluate and store the- -- initial element- x1 <- if A.eq scalarType tid (lift 0) `A.land` A.eq scalarType seg (lift 0)- then do- z <- seed- writeArray arrOut i0 z- case dir of- L -> app2 combine z x0- R -> app2 combine x0 z- else- return x0-- -- Block-wide scan- n <- A.sub numType sz inf- x2 <- if A.gte scalarType n bd- then scanBlockSMem dir dev combine Nothing x1- else scanBlockSMem dir dev combine (Just n) x1-- -- Write this thread's scan result to memory. Recall that we had to make- -- space for the initial element, so the very last thread does not store- -- its result here.- case dir of- L -> when (A.lt scalarType j0 sz) $ writeArray arrOut j0 x2- R -> when (A.gte scalarType j0 (lift 0)) $ writeArray arrOut j0 x2-- -- Last active thread writes its result to the partial sums array. These- -- will be used to compute the carry-in value in step 2.- m <- do x <- A.min scalarType n bd- y <- A.sub numType x (lift 1)- return y- when (A.eq scalarType tid m) $- case dir of- L -> writeArray arrTmp seg x2- R -> do x <- A.sub numType end seg- y <- A.sub numType x (lift 1)- writeArray arrTmp y x2-- return_----- Parallel scan', step 2------ A single thread block performs an inclusive scan of the partial sums array to--- compute the per-block carry-in values, as well as the final reduction result.----mkScan'AllP2- :: forall aenv e. Elt e- => Direction- -> DeviceProperties- -> Gamma aenv- -> IRFun2 PTX aenv (e -> e -> e)- -> CodeGen (IROpenAcc PTX aenv (Vector e, Scalar e))-mkScan'AllP2 dir dev aenv combine =- let- (start, end, paramGang) = gangParam- (arrTmp, paramTmp) = mutableArray ("tmp" :: Name (Vector e))- (arrSum, paramSum) = mutableArray ("sum" :: Name (Scalar e))- paramEnv = envParam aenv- --- config = launchConfig dev (CUDA.incWarp dev) smem grid gridQ- grid _ _ = 1- gridQ = [|| \_ _ -> 1 ||]- smem n = warps * (1 + per_warp) * bytes- where- ws = CUDA.warpSize dev- warps = n `P.quot` ws- per_warp = ws + ws `P.quot` 2- bytes = sizeOf (eltType (undefined :: e))- in- makeOpenAccWith config "scanP2" (paramGang ++ paramTmp ++ paramSum ++ paramEnv) $ do-- -- The first and last threads of the block need to communicate the- -- block-wide aggregate as a carry-in value across iterations.- carry <- staticSharedMem 1-- -- A single thread block iterates over the per-block partial results from- -- step 1- tid <- threadIdx- bd <- blockDim- imapFromStepTo start bd end $ \offset -> do-- i0 <- case dir of- L -> A.add numType offset tid- R -> do x <- A.sub numType end offset- y <- A.sub numType x tid- z <- A.sub numType y (lift 1)- return z-- let valid i = case dir of- L -> A.lt scalarType i end- R -> A.gte scalarType i start-- when (valid i0) $ do-- -- wait for the carry-in value to be updated- __syncthreads-- x0 <- readArray arrTmp i0- x1 <- if A.gt scalarType offset (lift 0) `A.land` A.eq scalarType tid (lift 0)- then do- c <- readArray carry (lift 0 :: IR Int32)- case dir of- L -> app2 combine c x0- R -> app2 combine x0 c- else- return x0-- n <- A.sub numType end offset- x2 <- if A.gte scalarType n bd- then scanBlockSMem dir dev combine Nothing x1- else scanBlockSMem dir dev combine (Just n) x1-- -- Update the partial results array- writeArray arrTmp i0 x2-- -- The last active thread saves its result as the carry-out value.- m <- do x <- A.min scalarType bd n- y <- A.sub numType x (lift 1)- return y- when (A.eq scalarType tid m) $- writeArray carry (lift 0 :: IR Int32) x2-- -- First thread stores the final carry-out values at the final reduction- -- result for the entire array- __syncthreads-- when (A.eq scalarType tid (lift 0)) $- writeArray arrSum (lift 0 :: IR Int32) =<< readArray carry (lift 0 :: IR Int32)-- return_----- Parallel scan', step 3.------ Threads combine every element of the partial block results with the carry-in--- value computed in step 2.----mkScan'AllP3- :: forall aenv e. Elt e- => Direction- -> DeviceProperties -- ^ properties of the target GPU- -> Gamma aenv -- ^ array environment- -> IRFun2 PTX aenv (e -> e -> e) -- ^ combination function- -> CodeGen (IROpenAcc PTX aenv (Vector e, Scalar e))-mkScan'AllP3 dir dev aenv combine =- let- (start, end, paramGang) = gangParam- (arrOut, paramOut) = mutableArray ("out" :: Name (Vector e))- (arrTmp, paramTmp) = mutableArray ("tmp" :: Name (Vector e))- paramEnv = envParam aenv- --- stride = local scalarType ("ix.stride" :: Name Int32)- paramStride = scalarParameter scalarType ("ix.stride" :: Name Int32)- --- config = launchConfig dev (CUDA.incWarp dev) (const 0) const [|| const ||]- in- makeOpenAccWith config "scanP3" (paramGang ++ paramTmp ++ paramOut ++ paramStride : paramEnv) $ do-- sz <- A.fromIntegral integralType numType (indexHead (irArrayShape arrOut))- tid <- threadIdx-- when (A.lt scalarType tid stride) $ do-- bid <- blockIdx- gd <- gridDim- c0 <- A.add numType start bid- imapFromStepTo c0 gd end $ \chunk -> do-- (inf,sup) <- case dir of- L -> do- a <- A.add numType chunk (lift 1)- b <- A.mul numType stride a- c <- A.add numType b (lift 1)- d <- A.add numType c stride- e <- A.min scalarType d sz- return (c,e)- R -> do- a <- A.sub numType end chunk- b <- A.mul numType stride a- c <- A.sub numType sz b- d <- A.sub numType c (lift 1)- e <- A.sub numType d stride- f <- A.max scalarType e (lift 0)- return (f,d)-- carry <- case dir of- L -> readArray arrTmp chunk- R -> do- a <- A.add numType chunk (lift 1)- b <- readArray arrTmp a- return b-- -- Apply the carry-in value to each element in the chunk- bd <- blockDim- i0 <- A.add numType inf tid- imapFromStepTo i0 bd sup $ \i -> do- v <- readArray arrOut i- u <- case dir of- L -> app2 combine carry v- R -> app2 combine v carry- writeArray arrOut i u-- return_----- Multidimensional scans--- -------------------------- Multidimensional scan along the innermost dimension------ A thread block individually computes along each innermost dimension. This is--- a single-pass operation.------ * We can assume that the array is non-empty; exclusive scans with empty--- innermost dimension will be instead filled with the seed element via--- 'mkScanFill'.------ * Small but non-empty innermost dimension arrays (size << thread--- block size) will have many threads which do no work.----mkScanDim- :: forall aenv sh e. (Shape sh, Elt e)- => Direction- -> DeviceProperties -- ^ properties of the target GPU- -> Gamma aenv -- ^ array environment- -> IRFun2 PTX aenv (e -> e -> e) -- ^ combination function- -> Maybe (IRExp PTX aenv e) -- ^ seed element, if this is an exclusive scan- -> IRDelayed PTX aenv (Array (sh:.Int) e) -- ^ input data- -> CodeGen (IROpenAcc PTX aenv (Array (sh:.Int) e))-mkScanDim dir dev aenv combine mseed IRDelayed{..} =- let- (start, end, paramGang) = gangParam- (arrOut, paramOut) = mutableArray ("out" :: Name (Array (sh:.Int) e))- paramEnv = envParam aenv- --- config = launchConfig dev (CUDA.incWarp dev) smem const [|| const ||]- smem n = warps * (1 + per_warp) * bytes- where- ws = CUDA.warpSize dev- warps = n `P.quot` ws- per_warp = ws + ws `P.quot` 2- bytes = sizeOf (eltType (undefined :: e))- in- makeOpenAccWith config "scan" (paramGang ++ paramOut ++ paramEnv) $ do-- -- The first and last threads of the block need to communicate the- -- block-wide aggregate as a carry-in value across iterations.- --- -- TODO: we could optimise this a bit if we can get access to the shared- -- memory area used by 'scanBlockSMem', and from there directly read the- -- value computed by the last thread.- carry <- staticSharedMem 1-- -- Size of the input array- sz <- A.fromIntegral integralType numType . indexHead =<< delayedExtent-- -- Thread blocks iterate over the outer dimensions. Threads in a block- -- cooperatively scan along one dimension, but thread blocks do not- -- communicate with each other.- --- bid <- blockIdx- gd <- gridDim- s0 <- A.add numType start bid- imapFromStepTo s0 gd end $ \seg -> do-- -- Index this thread reads from- tid <- threadIdx- i0 <- case dir of- L -> do x <- A.mul numType seg sz- y <- A.add numType x tid- return y-- R -> do x <- A.add numType seg (lift 1)- y <- A.mul numType x sz- z <- A.sub numType y tid- w <- A.sub numType z (lift 1)- return w-- -- Index this thread writes to- j0 <- case mseed of- Nothing -> return i0- Just{} -> do szp1 <- A.fromIntegral integralType numType (indexHead (irArrayShape arrOut))- case dir of- L -> do x <- A.mul numType seg szp1- y <- A.add numType x tid- return y-- R -> do x <- A.add numType seg (lift 1)- y <- A.mul numType x szp1- z <- A.sub numType y tid- w <- A.sub numType z (lift 1)- return w-- -- Stride indices by block dimension- bd <- blockDim- let next ix = case dir of- L -> A.add numType ix bd- R -> A.sub numType ix bd-- -- Initialise this scan segment- --- -- If this is an exclusive scan then the first thread just evaluates the- -- seed element and stores this value into the carry-in slot. All threads- -- shift their write-to index (j) by one, to make space for this element.- --- -- If this is an inclusive scan then do a block-wide scan. The last thread- -- in the block writes the carry-in value.- --- r <-- case mseed of- Just seed -> do- when (A.eq scalarType tid (lift 0)) $ do- z <- seed- writeArray arrOut j0 z- writeArray carry (lift 0 :: IR Int32) z- j1 <- case dir of- L -> A.add numType j0 (lift 1)- R -> A.sub numType j0 (lift 1)- return $ A.trip sz i0 j1-- Nothing -> do- when (A.lt scalarType tid sz) $ do- x0 <- app1 delayedLinearIndex =<< A.fromIntegral integralType numType i0- r0 <- if A.gte scalarType sz bd- then scanBlockSMem dir dev combine Nothing x0- else scanBlockSMem dir dev combine (Just sz) x0- writeArray arrOut j0 r0-- ll <- A.sub numType bd (lift 1)- when (A.eq scalarType tid ll) $- writeArray carry (lift 0 :: IR Int32) r0-- n1 <- A.sub numType sz bd- i1 <- next i0- j1 <- next j0- return $ A.trip n1 i1 j1-- -- Iterate over the remaining elements in this segment- void $ while- (\(A.fst3 -> n) -> A.gt scalarType n (lift 0))- (\(A.untrip -> (n,i,j)) -> do-- -- Wait for the carry-in value from the previous iteration to be updated- __syncthreads-- -- Compute and store the next element of the scan- --- -- NOTE: As with 'foldSeg' we require all threads to participate in- -- every iteration of the loop otherwise they will die prematurely.- -- Out-of-bounds threads return 'undef' at this point, which is really- -- unfortunate ):- --- x <- if A.lt scalarType tid n- then app1 delayedLinearIndex =<< A.fromIntegral integralType numType i- else let- go :: TupleType a -> Operands a- go UnitTuple = OP_Unit- go (PairTuple a b) = OP_Pair (go a) (go b)- go (SingleTuple t) = ir' t (undef t)- in- return . IR $ go (eltType (undefined::e))-- -- Thread zero incorporates the carry-in element- y <- if A.eq scalarType tid (lift 0)- then do- c <- readArray carry (lift 0 :: IR Int32)- case dir of- L -> app2 combine c x- R -> app2 combine x c- else- return x-- -- Perform the scan and write the result to memory- z <- if A.gte scalarType n bd- then scanBlockSMem dir dev combine Nothing y- else scanBlockSMem dir dev combine (Just n) y-- when (A.lt scalarType tid n) $ do- writeArray arrOut j z-- -- The last thread of the block writes its result as the carry-out- -- value. If this thread is not active then we are on the last- -- iteration of the loop and it will not be needed.- w <- A.sub numType bd (lift 1)- when (A.eq scalarType tid w) $- writeArray carry (lift 0 :: IR Int32) z-- -- Update indices for the next iteration- n' <- A.sub numType n bd- i' <- next i- j' <- next j- return $ A.trip n' i' j')- r-- return_----- Multidimensional scan' along the innermost dimension------ A thread block individually computes along each innermost dimension. This is--- a single-pass operation.------ * We can assume that the array is non-empty; exclusive scans with empty--- innermost dimension will be instead filled with the seed element via--- 'mkScan'Fill'.------ * Small but non-empty innermost dimension arrays (size << thread--- block size) will have many threads which do no work.----mkScan'Dim- :: forall aenv sh e. (Shape sh, Elt e)- => Direction- -> DeviceProperties -- ^ properties of the target GPU- -> Gamma aenv -- ^ array environment- -> IRFun2 PTX aenv (e -> e -> e) -- ^ combination function- -> IRExp PTX aenv e -- ^ seed element- -> IRDelayed PTX aenv (Array (sh:.Int) e) -- ^ input data- -> CodeGen (IROpenAcc PTX aenv (Array (sh:.Int) e, Array sh e))-mkScan'Dim dir dev aenv combine seed IRDelayed{..} =- let- (start, end, paramGang) = gangParam- (arrOut, paramOut) = mutableArray ("out" :: Name (Array (sh:.Int) e))- (arrSum, paramSum) = mutableArray ("sum" :: Name (Array sh e))- paramEnv = envParam aenv- --- config = launchConfig dev (CUDA.incWarp dev) smem const [|| const ||]- smem n = warps * (1 + per_warp) * bytes- where- ws = CUDA.warpSize dev- warps = n `P.quot` ws- per_warp = ws + ws `P.quot` 2- bytes = sizeOf (eltType (undefined :: e))- in- makeOpenAccWith config "scan" (paramGang ++ paramOut ++ paramSum ++ paramEnv) $ do-- -- The first and last threads of the block need to communicate the- -- block-wide aggregate as a carry-in value across iterations.- --- -- TODO: we could optimise this a bit if we can get access to the shared- -- memory area used by 'scanBlockSMem', and from there directly read the- -- value computed by the last thread.- carry <- staticSharedMem 1-- -- Size of the input array- sz <- A.fromIntegral integralType numType . indexHead =<< delayedExtent-- -- If the innermost dimension is smaller than the number of threads in the- -- block, those threads will never contribute to the output.- tid <- threadIdx- when (A.lte scalarType tid sz) $ do-- -- Thread blocks iterate over the outer dimensions, each thread block- -- cooperatively scanning along each outermost index.- bid <- blockIdx- gd <- gridDim- s0 <- A.add numType start bid- imapFromStepTo s0 gd end $ \seg -> do-- -- Not necessary to wait for threads to catch up before starting this segment- -- __syncthreads-- -- Linear index bounds for this segment- inf <- A.mul numType seg sz- sup <- A.add numType inf sz-- -- Index that this thread will read from. Recall that the supremum index- -- is exclusive.- i0 <- case dir of- L -> A.add numType inf tid- R -> do x <- A.sub numType sup tid- y <- A.sub numType x (lift 1)- return y-- -- The index that this thread will write to. This is just shifted along- -- by one to make room for the initial element.- j0 <- case dir of- L -> A.add numType i0 (lift 1)- R -> A.sub numType i0 (lift 1)-- -- Evaluate the initial element. Store it into the carry-in slot as well- -- as to the array as the first element. This is always valid because if- -- the input array is empty then we will be evaluating via mkScan'Fill.- when (A.eq scalarType tid (lift 0)) $ do- z <- seed- writeArray arrOut i0 z- writeArray carry (lift 0 :: IR Int32) z-- bd <- blockDim- let next ix = case dir of- L -> A.add numType ix bd- R -> A.sub numType ix bd-- -- Now, threads iterate over the elements along the innermost dimension.- -- At each iteration the first thread incorporates the carry-in value- -- from the previous step.- --- -- The index tracks how many elements remain for the thread block, since- -- indices i* and j* are local to each thread- n0 <- A.sub numType sup inf- void $ while- (\(A.fst3 -> n) -> A.gt scalarType n (lift 0))- (\(A.untrip -> (n,i,j)) -> do-- -- Wait for threads to catch up to ensure the carry-in value from- -- the last iteration has been updated- __syncthreads-- -- If all threads in the block will participate this round we can- -- avoid (almost) all bounds checks.- _ <- if A.gte scalarType n bd- -- All threads participate. No bounds checks required but- -- the last thread needs to update the carry-in value.- then do- x <- app1 delayedLinearIndex =<< A.fromIntegral integralType numType i- y <- if A.eq scalarType tid (lift 0)- then do- c <- readArray carry (lift 0 :: IR Int32)- case dir of- L -> app2 combine c x- R -> app2 combine x c- else- return x- z <- scanBlockSMem dir dev combine Nothing y-- -- Write results to the output array. Note that if we- -- align directly on the boundary of the array this is not- -- valid for the last thread.- case dir of- L -> when (A.lt scalarType j sup) $ writeArray arrOut j z- R -> when (A.gte scalarType j inf) $ writeArray arrOut j z-- -- Last thread of the block also saves its result as the- -- carry-in value- bd1 <- A.sub numType bd (lift 1)- when (A.eq scalarType tid bd1) $- writeArray carry (lift 0 :: IR Int32) z-- return (IR OP_Unit :: IR ())-- -- Only threads that are in bounds can participate. This is- -- the last iteration of the loop. The last active thread- -- still needs to store its value into the carry-in slot.- else do- when (A.lt scalarType tid n) $ do- x <- app1 delayedLinearIndex =<< A.fromIntegral integralType numType i- y <- if A.eq scalarType tid (lift 0)- then do- c <- readArray carry (lift 0 :: IR Int32)- case dir of- L -> app2 combine c x- R -> app2 combine x c- else- return x- z <- scanBlockSMem dir dev combine (Just n) y-- m <- A.sub numType n (lift 1)- _ <- if A.lt scalarType tid m- then writeArray arrOut j z >> return (IR OP_Unit :: IR ())- else writeArray carry (lift 0 :: IR Int32) z >> return (IR OP_Unit :: IR ())-- return ()- return (IR OP_Unit :: IR ())-- A.trip <$> A.sub numType n bd <*> next i <*> next j)- (A.trip n0 i0 j0)-- -- Wait for the carry-in value to be updated- __syncthreads-- -- Store the carry-in value to the separate final results array- when (A.eq scalarType tid (lift 0)) $- writeArray arrSum seg =<< readArray carry (lift 0 :: IR Int32)-- return_------ Parallel scan, auxiliary------ If this is an exclusive scan of an empty array, we just fill the result with--- the seed element.----mkScanFill- :: (Shape sh, Elt e)- => PTX- -> Gamma aenv- -> IRExp PTX aenv e- -> CodeGen (IROpenAcc PTX aenv (Array sh e))-mkScanFill ptx aenv seed =- mkGenerate ptx aenv (IRFun1 (const seed))--mkScan'Fill- :: forall aenv sh e. (Shape sh, Elt e)- => PTX- -> Gamma aenv- -> IRExp PTX aenv e- -> CodeGen (IROpenAcc PTX aenv (Array (sh:.Int) e, Array sh e))-mkScan'Fill ptx aenv seed =- Safe.coerce <$> (mkGenerate ptx aenv (IRFun1 (const seed)) :: CodeGen (IROpenAcc PTX aenv (Array sh e)))----- Block wide scan--- ------------------- Efficient block-wide (inclusive) scan using the specified operator.------ Each block requires (#warps * (1 + 1.5*warp size)) elements of dynamically--- allocated shared memory.------ Example: https://github.com/NVlabs/cub/blob/1.5.4/cub/block/specializations/block_scan_warp_scans.cuh----scanBlockSMem- :: forall aenv e. Elt e- => Direction- -> DeviceProperties -- ^ properties of the target device- -> IRFun2 PTX aenv (e -> e -> e) -- ^ combination function- -> Maybe (IR Int32) -- ^ number of valid elements (may be less than block size)- -> IR e -- ^ calling thread's input element- -> CodeGen (IR e)-scanBlockSMem dir dev combine nelem = warpScan >=> warpPrefix- where- int32 :: Integral a => a -> IR Int32- int32 = lift . P.fromIntegral-- -- Temporary storage required for each warp- warp_smem_elems = CUDA.warpSize dev + (CUDA.warpSize dev `P.quot` 2)- warp_smem_bytes = warp_smem_elems * sizeOf (eltType (undefined::e))-- -- Step 1: Scan in every warp- warpScan :: IR e -> CodeGen (IR e)- warpScan input = do- -- Allocate (1.5 * warpSize) elements of shared memory for each warp- -- (individually addressable by each warp)- wid <- warpId- skip <- A.mul numType wid (int32 warp_smem_bytes)- smem <- dynamicSharedMem (int32 warp_smem_elems) skip- scanWarpSMem dir dev combine smem input-- -- Step 2: Collect the aggregate results of each warp to compute the prefix- -- values for each warp and combine with the partial result to compute each- -- thread's final value.- warpPrefix :: IR e -> CodeGen (IR e)- warpPrefix input = do- -- Allocate #warps elements of shared memory- bd <- blockDim- warps <- A.quot integralType bd (int32 (CUDA.warpSize dev))- skip <- A.mul numType warps (int32 warp_smem_bytes)- smem <- dynamicSharedMem warps skip-- -- Share warp aggregates- wid <- warpId- lane <- laneId- when (A.eq scalarType lane (int32 (CUDA.warpSize dev - 1))) $ do- writeArray smem wid input-- -- Wait for each warp to finish its local scan and share the aggregate- __syncthreads-- -- Compute the prefix value for this warp and add to the partial result.- -- This step is not required for the first warp, which has no carry-in.- if A.eq scalarType wid (lift 0)- then return input- else do- -- Every thread sequentially scans the warp aggregates to compute- -- their prefix value. We do this sequentially, but could also have- -- warp 0 do it cooperatively if we limit thread block sizes to- -- (warp size ^ 2).- steps <- case nelem of- Nothing -> return wid- Just n -> A.min scalarType wid =<< A.quot integralType n (int32 (CUDA.warpSize dev))-- p0 <- readArray smem (lift 0 :: IR Int32)- prefix <- iterFromStepTo (lift 1) (lift 1) steps p0 $ \step x -> do- y <- readArray smem step- case dir of- L -> app2 combine x y- R -> app2 combine y x-- case dir of- L -> app2 combine prefix input- R -> app2 combine input prefix----- Warp-wide scan--- ------------------ Efficient warp-wide (inclusive) scan using the specified operator.------ Each warp requires 48 (1.5 x warp size) elements of shared memory. The--- routine assumes that it is allocated individually per-warp (i.e. can be--- indexed in the range [0, warp size)).------ Example: https://github.com/NVlabs/cub/blob/1.5.4/cub/warp/specializations/warp_scan_smem.cuh----scanWarpSMem- :: forall aenv e. Elt e- => Direction- -> DeviceProperties -- ^ properties of the target device- -> IRFun2 PTX aenv (e -> e -> e) -- ^ combination function- -> IRArray (Vector e) -- ^ temporary storage array in shared memory (1.5 x warp size elements)- -> IR e -- ^ calling thread's input element- -> CodeGen (IR e)-scanWarpSMem dir dev combine smem = scan 0- where- log2 :: Double -> Double- log2 = P.logBase 2-- -- Number of steps required to scan warp- steps = P.floor (log2 (P.fromIntegral (CUDA.warpSize dev)))- halfWarp = P.fromIntegral (CUDA.warpSize dev `P.quot` 2)-- -- Unfold the scan as a recursive code generation function- scan :: Int -> IR e -> CodeGen (IR e)- scan step x- | step >= steps = return x- | offset <- 1 `P.shiftL` step = do- -- share partial result through shared memory buffer- lane <- laneId- i <- A.add numType lane (lift halfWarp)- writeArray smem i x-- -- update partial result if in range- x' <- if A.gte scalarType lane (lift offset)- then do- i' <- A.sub numType i (lift offset) -- lane + HALF_WARP - offset- x' <- readArray smem i'- case dir of- L -> app2 combine x' x- R -> app2 combine x x'-- else- return x-- scan (step+1) x'-
− Data/Array/Accelerate/LLVM/PTX/Compile.hs
@@ -1,322 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE TypeFamilies #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}--- |--- Module : Data.Array.Accelerate.LLVM.PTX.Compile--- Copyright : [2014..2017] Trevor L. McDonell--- [2014..2014] Vinod Grover (NVIDIA Corporation)--- License : BSD3------ Maintainer : Trevor L. McDonell <tmcdonell@cse.unsw.edu.au>--- Stability : experimental--- Portability : non-portable (GHC extensions)-----module Data.Array.Accelerate.LLVM.PTX.Compile (-- module Data.Array.Accelerate.LLVM.Compile,- ObjectR(..),--) where---- llvm-hs-import qualified LLVM.AST as AST-import qualified LLVM.AST.Name as LLVM-import qualified LLVM.Context as LLVM-import qualified LLVM.Module as LLVM-import qualified LLVM.PassManager as LLVM-import qualified LLVM.Target as LLVM-import qualified LLVM.Internal.Module as LLVM.Internal-import qualified LLVM.Internal.FFI.LLVMCTypes as LLVM.Internal.FFI-#ifdef ACCELERATE_INTERNAL_CHECKS-import qualified LLVM.Analysis as LLVM-#endif---- accelerate-import Data.Array.Accelerate.Error ( internalError )-import Data.Array.Accelerate.Trafo ( DelayedOpenAcc )--import Data.Array.Accelerate.LLVM.CodeGen-import Data.Array.Accelerate.LLVM.CodeGen.Environment ( Gamma )-import Data.Array.Accelerate.LLVM.CodeGen.Module ( Module(..) )-import Data.Array.Accelerate.LLVM.Compile-import Data.Array.Accelerate.LLVM.State-import Data.Array.Accelerate.LLVM.Util--import Data.Array.Accelerate.LLVM.PTX.Analysis.Launch-import Data.Array.Accelerate.LLVM.PTX.CodeGen-import Data.Array.Accelerate.LLVM.PTX.Compile.Cache-import Data.Array.Accelerate.LLVM.PTX.Compile.Libdevice-import Data.Array.Accelerate.LLVM.PTX.Foreign ( )-import Data.Array.Accelerate.LLVM.PTX.Target--import qualified Data.Array.Accelerate.LLVM.PTX.Debug as Debug---- cuda-import Foreign.CUDA.Path-import qualified Foreign.CUDA.Analysis as CUDA-import qualified Foreign.NVVM as NVVM---- standard library-import Control.Concurrent-import Control.DeepSeq-import Control.Exception-import Control.Monad.Except-import Control.Monad.State-import Data.ByteString ( ByteString )-import Data.ByteString.Short ( ShortByteString )-import Data.Maybe-import Data.Word-import Foreign.C-import Foreign.ForeignPtr-import Foreign.Ptr-import Foreign.Storable-import GHC.IO.Exception ( IOErrorType(..), IOException(..) )-import System.Directory-import System.Exit-import System.FilePath-import System.IO-import System.IO.Unsafe-import System.Process-import Text.Printf ( printf )-import qualified Data.ByteString as B-import qualified Data.ByteString.Char8 as B8-import qualified Data.ByteString.Internal as B-import qualified Data.Map as Map-import Prelude as P---instance Compile PTX where- data ObjectR PTX = ObjectR { objId :: {-# UNPACK #-} !UID- , ptxConfig :: ![(ShortByteString, LaunchConfig)]- , objData :: {- LAZY -} ByteString- }- compileForTarget = compile----- | Compile an Accelerate expression to object code.------ This generates the target code together with a list of each kernel function--- defined in the module paired with its occupancy information.----compile :: DelayedOpenAcc aenv a -> Gamma aenv -> LLVM PTX (ObjectR PTX)-compile acc aenv = do- target <- gets llvmTarget- (uid, cacheFile) <- cacheOfOpenAcc acc-- -- Generate code for this Acc operation- --- let Module ast md = llvmOfOpenAcc target uid acc aenv- dev = ptxDeviceProperties target- config = [ (f,x) | (LLVM.Name f, KM_PTX x) <- Map.toList md ]-- -- Lower the generated LLVM into a CUBIN object code.- --- -- The 'objData' field is lazily evaluated since the object code might have- -- already been loaded into the current context from a different function, in- -- which case it will be found by the linker cache.- --- cubin <- liftIO . unsafeInterleaveIO $ do- exists <- doesFileExist cacheFile- recomp <- Debug.queryFlag Debug.force_recomp- if exists && not (fromMaybe False recomp)- then do- Debug.traceIO Debug.dump_cc (printf "cc: found cached object code %016x" uid)- B.readFile cacheFile-- else- LLVM.withContext $ \ctx -> do- ptx <- compilePTX dev ctx ast- cubin <- compileCUBIN dev cacheFile ptx- return cubin-- return $! ObjectR uid config cubin----- | Compile the LLVM module to PTX assembly. This is done either by the--- closed-source libNVVM library, or via the standard NVPTX backend (which is--- the default).----compilePTX :: CUDA.DeviceProperties -> LLVM.Context -> AST.Module -> IO ByteString-compilePTX dev ctx ast = do-#ifdef ACCELERATE_USE_NVVM- ptx <- withLibdeviceNVVM dev ctx ast (compileModuleNVVM dev (AST.moduleName ast))-#else- ptx <- withLibdeviceNVPTX dev ctx ast (compileModuleNVPTX dev)-#endif- Debug.when Debug.dump_asm $ Debug.traceIO Debug.verbose (B8.unpack ptx)- return ptx----- | Compile the given PTX assembly to a CUBIN file (SASS object code). The--- compiled code will be stored at the given FilePath.----compileCUBIN :: CUDA.DeviceProperties -> FilePath -> ByteString -> IO ByteString-compileCUBIN dev sass ptx = do- _verbose <- Debug.queryFlag Debug.verbose- _debug <- Debug.queryFlag Debug.debug_cc- --- let verboseFlag = if _verbose then [ "-v" ] else []- debugFlag = if _debug then [ "-g", "-lineinfo" ] else []- arch = printf "-arch=sm_%d%d" m n- CUDA.Compute m n = CUDA.computeCapability dev- flags = "-" : "-o" : sass : arch : verboseFlag ++ debugFlag- --- cp = (proc (cudaBinPath </> "ptxas") flags)- { std_in = CreatePipe- , std_out = NoStream- , std_err = CreatePipe- }-- -- Invoke the 'ptxas' executable (which must be on the PATH) to compile the- -- PTX into SASS. The output is written directly to the final cache location.- --- withCreateProcess cp $ \(Just inh) Nothing (Just errh) ph -> do-- -- fork off a thread to start consuming stderr- info <- hGetContents errh- withForkWait (evaluate (rnf info)) $ \waitErr -> do-- -- write the PTX to the input handle- -- closing the handle performs an implicit flush, thus may trigger SIGPIPE- ignoreSIGPIPE $ B.hPut inh ptx- ignoreSIGPIPE $ hClose inh-- -- wait on the output- waitErr- hClose errh-- -- wait on the process- ex <- waitForProcess ph- case ex of- ExitFailure r -> $internalError "compile" (printf "ptxas %s (exit %d)\n%s" (unwords flags) r info)- ExitSuccess -> return ()-- when _verbose $- unless (null info) $- Debug.traceIO Debug.dump_cc (printf "ptx: compiled entry function(s)\n%s" info)-- -- Read back the results- B.readFile sass----- | Fork a thread while doing something else, but kill it if there's an--- exception.------ This is important because we want to kill the thread that is holding the--- Handle lock, because when we clean up the process we try to close that--- handle, which could otherwise deadlock.------ Stolen from the 'process' package.----withForkWait :: IO () -> (IO () -> IO a) -> IO a-withForkWait async body = do- waitVar <- newEmptyMVar :: IO (MVar (Either SomeException ()))- mask $ \restore -> do- tid <- forkIO $ try (restore async) >>= putMVar waitVar- let wait = takeMVar waitVar >>= either throwIO return- restore (body wait) `onException` killThread tid--ignoreSIGPIPE :: IO () -> IO ()-ignoreSIGPIPE =- handle $ \e ->- case e of- IOError{..} | ResourceVanished <- ioe_type- , Just ioe <- ioe_errno- , Errno ioe == ePIPE- -> return ()- _ -> throwIO e----- Compile and optimise the module to PTX using the (closed source) NVVM--- library. This _may_ produce faster object code than the LLVM NVPTX compiler.----compileModuleNVVM :: CUDA.DeviceProperties -> String -> [(String, ByteString)] -> LLVM.Module -> IO ByteString-compileModuleNVVM dev name libdevice mdl = do- _debug <- Debug.queryFlag Debug.debug_cc- --- let arch = CUDA.computeCapability dev- verbose = if _debug then [ NVVM.GenerateDebugInfo ] else []- flags = NVVM.Target arch : verbose-- -- Note: [NVVM and target datalayout]- --- -- The NVVM library does not correctly parse the target datalayout field,- -- instead doing a (very dodgy) string compare against exactly two- -- expected values. This means that it is sensitive to, e.g. the ordering- -- of the fields, and changes to the representation in each LLVM release.- --- -- We get around this by only specifying the data layout in a separate- -- (otherwise empty) module that we additionally link against.- --- header = case bitSize (undefined::Int) of- 32 -> "target triple = \"nvptx-nvidia-cuda\"\ntarget datalayout = \"e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v16:16:16-v32:32:32-v64:64:64-v128:128:128-n16:32:64\""- 64 -> "target triple = \"nvptx64-nvidia-cuda\"\ntarget datalayout = \"e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v16:16:16-v32:32:32-v64:64:64-v128:128:128-n16:32:64\""- _ -> $internalError "compileModuleNVVM" "I don't know what architecture I am"-- Debug.when Debug.dump_cc $ do- Debug.when Debug.verbose $ do- ll <- LLVM.moduleLLVMAssembly mdl -- TLM: unfortunate to do the lowering twice in debug mode- Debug.traceIO Debug.verbose (B8.unpack ll)-- -- Lower the generated module to bitcode, then compile and link together with- -- the shim header and libdevice library (if necessary)- bc <- LLVM.moduleBitcode mdl- ptx <- NVVM.compileModules (("",header) : (name,bc) : libdevice) flags-- unless (B.null (NVVM.compileLog ptx)) $ do- Debug.traceIO Debug.dump_cc $ "llvm: " ++ B8.unpack (NVVM.compileLog ptx)-- -- Return the generated binary code- return (NVVM.compileResult ptx)----- Compiling with the NVPTX backend uses LLVM-3.3 and above----compileModuleNVPTX :: CUDA.DeviceProperties -> LLVM.Module -> IO ByteString-compileModuleNVPTX dev mdl =- withPTXTargetMachine dev $ \nvptx -> do-- -- Run the standard optimisation pass- --- let pss = LLVM.defaultCuratedPassSetSpec { LLVM.optLevel = Just 3 }- LLVM.withPassManager pss $ \pm -> do-#ifdef ACCELERATE_INTERNAL_CHECKS- LLVM.verify mdl-#endif- b1 <- LLVM.runPassManager pm mdl-- -- debug printout- Debug.when Debug.dump_cc $ do- Debug.traceIO Debug.dump_cc $ printf "llvm: optimisation did work? %s" (show b1)- Debug.traceIO Debug.verbose . B8.unpack =<< LLVM.moduleLLVMAssembly mdl-- -- Lower the LLVM module into target assembly (PTX)- moduleTargetAssembly nvptx mdl----- | Produce target specific assembly as a 'ByteString'.----moduleTargetAssembly :: LLVM.TargetMachine -> LLVM.Module -> IO ByteString-moduleTargetAssembly tm m = unsafe0 =<< LLVM.Internal.emitToByteString LLVM.Internal.FFI.codeGenFileTypeAssembly tm m- where- -- Ensure that the ByteString is NULL-terminated, so that it can be passed- -- directly to C. This will unsafely mutate the underlying ForeignPtr if the- -- string is not NULL-terminated but the last character is a whitespace- -- character (there are usually a few blank lines at the end).- --- unsafe0 :: ByteString -> IO ByteString- unsafe0 bs@(B.PS fp s l) =- liftIO . withForeignPtr fp $ \p -> do- let p' :: Ptr Word8- p' = p `plusPtr` (s+l-1)- --- x <- peek p'- case x of- 0 -> return bs- _ | B.isSpaceWord8 x -> poke p' 0 >> return bs- _ -> return (B.snoc bs 0)-
− Data/Array/Accelerate/LLVM/PTX/Compile/Cache.hs
@@ -1,40 +0,0 @@-{-# OPTIONS_GHC -fno-warn-orphans #-}--- |--- Module : Data.Array.Accelerate.LLVM.PTX.Compile.Cache--- Copyright : [2017] Trevor L. McDonell--- License : BSD3------ Maintainer : Trevor L. McDonell <tmcdonell@cse.unsw.edu.au>--- Stability : experimental--- Portability : non-portable (GHC extensions)-----module Data.Array.Accelerate.LLVM.PTX.Compile.Cache (-- module Data.Array.Accelerate.LLVM.Compile.Cache--) where--import Data.Array.Accelerate.LLVM.Compile.Cache-import Data.Array.Accelerate.LLVM.PTX.Target--import Control.Monad.State-import Data.Version-import Foreign.CUDA.Analysis-import System.FilePath-import qualified Data.ByteString.Char8 as B8-import qualified Data.ByteString.Short.Char8 as S8--import Paths_accelerate_llvm_ptx---instance Persistent PTX where- targetCacheTemplate = do- dev <- gets ptxDeviceProperties- let Compute m n = computeCapability dev- --- return $ "accelerate-llvm-ptx-" ++ showVersion version- </> S8.unpack ptxTargetTriple- </> B8.unpack (ptxISAVersion m n)- </> "morp.sass"-
− Data/Array/Accelerate/LLVM/PTX/Compile/Libdevice.hs
@@ -1,177 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE ViewPatterns #-}--- |--- Module : Data.Array.Accelerate.LLVM.PTX.Compile.Libdevice--- Copyright : [2014..2017] Trevor L. McDonell--- [2014..2014] Vinod Grover (NVIDIA Corporation)--- License : BSD3------ Maintainer : Trevor L. McDonell <tmcdonell@cse.unsw.edu.au>--- Stability : experimental--- Portability : non-portable (GHC extensions)-----module Data.Array.Accelerate.LLVM.PTX.Compile.Libdevice (-- withLibdeviceNVVM,- withLibdeviceNVPTX,--) where---- llvm-hs-import LLVM.Context-import qualified LLVM.Module as LLVM--import LLVM.AST as AST-import LLVM.AST.Global as G-import LLVM.AST.Linkage---- accelerate-import Data.Array.Accelerate.LLVM.PTX.Compile.Libdevice.Load-import qualified Data.Array.Accelerate.LLVM.PTX.Debug as Debug---- cuda-import Foreign.CUDA.Analysis---- standard library-import Control.Monad-import Data.ByteString ( ByteString )-import Data.ByteString.Short.Char8 ( ShortByteString )-import Data.HashSet ( HashSet )-import Data.List-import Data.Maybe-import Text.Printf-import qualified Data.ByteString.Short.Char8 as S8-import qualified Data.ByteString.Short.Extra as BS-import qualified Data.HashSet as Set----- | Lower an LLVM AST to C++ objects and link it against the libdevice module,--- iff any libdevice functions are referenced from the base module.------ Note: [Linking with libdevice]------ The CUDA toolkit comes with an LLVM bitcode library called 'libdevice' that--- implements many common mathematical functions. The library can be used as a--- high performance math library for targets of the LLVM NVPTX backend, such as--- this one. To link a module 'foo' with libdevice, the following compilation--- pipeline is recommended:------ 1. Save all external functions in module 'foo'------ 2. Link module 'foo' with the appropriate 'libdevice_compute_XX.YY.bc'------ 3. Internalise all functions not in the list from (1)------ 4. Eliminate all unused internal functions------ 5. Run the NVVMReflect pass (see note: [NVVM Reflect Pass])------ 6. Run the standard optimisation pipeline----withLibdeviceNVPTX- :: DeviceProperties- -> Context- -> Module- -> (LLVM.Module -> IO a)- -> IO a-withLibdeviceNVPTX dev ctx ast next =- case Set.null externs of- True -> LLVM.withModuleFromAST ctx ast next- False ->- LLVM.withModuleFromAST ctx ast $ \mdl ->- LLVM.withModuleFromAST ctx nvvmReflect $ \refl ->- LLVM.withModuleFromAST ctx (internalise externs libdev) $ \libd -> do- LLVM.linkModules mdl refl- LLVM.linkModules mdl libd- Debug.traceIO Debug.dump_cc msg- next mdl- where- -- Replace the target triple and datalayout from the libdevice.bc module- -- with those of the generated code. This avoids warnings such as "linking- -- two modules of different target triples..."- libdev = (libdevice arch) { moduleTargetTriple = moduleTargetTriple ast- , moduleDataLayout = moduleDataLayout ast- }- externs = analyse ast- arch = computeCapability dev-- msg = printf "cc: linking with libdevice: %s"- $ intercalate ", "- $ map S8.unpack- $ Set.toList externs----- | Lower an LLVM AST to C++ objects and prepare it for linking against--- libdevice using the nvvm bindings, iff any libdevice functions are referenced--- from the base module.------ Rather than internalise and strip any unused functions ourselves, allow the--- nvvm library to do so when linking the two modules together.------ TLM: This really should work with the above method, however for some reason--- we get a "CUDA Exception: function named symbol not found" error, even though--- the function is clearly visible in the generated code. hmm...----withLibdeviceNVVM- :: DeviceProperties- -> Context- -> Module- -> ([(String, ByteString)] -> LLVM.Module -> IO a)- -> IO a-withLibdeviceNVVM dev ctx ast next =- LLVM.withModuleFromAST ctx ast $ \mdl -> do- when withlib $ Debug.traceIO Debug.dump_cc msg- next lib mdl- where- externs = analyse ast- withlib = not (Set.null externs)- lib | withlib = [ nvvmReflect, libdevice arch ]- | otherwise = []-- arch = computeCapability dev-- msg = printf "cc: linking with libdevice: %s"- $ intercalate ", "- $ map S8.unpack- $ Set.toList externs----- | Analyse the LLVM AST module and determine if any of the external--- declarations are intrinsics implemented by libdevice. The set of such--- functions is returned, and will be used when determining which functions from--- libdevice to internalise.----analyse :: Module -> HashSet ShortByteString-analyse Module{..} =- let intrinsic (GlobalDefinition Function{..})- | null basicBlocks- , Name n <- name- , "__nv_" <- BS.take 5 n- = Just n-- intrinsic _- = Nothing- in- Set.fromList (mapMaybe intrinsic moduleDefinitions)----- | Mark all definitions in the module as internal linkage. This means that--- unused definitions can be removed as dead code. Be careful to leave any--- declarations as external.----internalise :: HashSet ShortByteString -> Module -> Module-internalise externals Module{..} =- let internal (GlobalDefinition Function{..})- | Name n <- name- , not (Set.member n externals) -- we don't call this function directly; and- , not (null basicBlocks) -- it is not an external declaration- = GlobalDefinition Function { linkage=Internal, .. }-- internal x- = x- in- Module { moduleDefinitions = map internal moduleDefinitions, .. }-
− Data/Array/Accelerate/LLVM/PTX/Compile/Libdevice/Load.hs
@@ -1,143 +0,0 @@-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE TupleSections #-}--- |--- Module : Data.Array.Accelerate.LLVM.PTX.Compile.Libdevice.Load--- Copyright : [2014..2017] Trevor L. McDonell--- [2014..2014] Vinod Grover (NVIDIA Corporation)--- License : BSD3------ Maintainer : Trevor L. McDonell <tmcdonell@cse.unsw.edu.au>--- Stability : experimental--- Portability : non-portable (GHC extensions)-----module Data.Array.Accelerate.LLVM.PTX.Compile.Libdevice.Load (-- nvvmReflect, libdevice,--) where---- llvm-hs-import LLVM.Context-import LLVM.Module as LLVM-import LLVM.AST as AST ( Module(..) )---- accelerate-import Data.Array.Accelerate.Error-import Data.Array.Accelerate.LLVM.PTX.Compile.Libdevice.TH-import Data.Array.Accelerate.LLVM.PTX.Execute.Event ( ) -- GHC#1012-import Data.Array.Accelerate.LLVM.PTX.Execute.Stream ( ) -- GHC#1012---- cuda-import Foreign.CUDA.Analysis-import qualified Foreign.CUDA.Driver as CUDA---- standard library-import Data.ByteString ( ByteString )-import System.IO.Unsafe----- NVVM Reflect--- --------------class NVVMReflect a where- nvvmReflect :: a--instance NVVMReflect AST.Module where- nvvmReflect = nvvmReflectModule--instance NVVMReflect (String, ByteString) where- nvvmReflect = $$( nvvmReflectBitcode nvvmReflectModule )----- libdevice--- ------------- Compatible version of libdevice for a given compute capability should be--- listed here:------ https://github.com/llvm-mirror/llvm/blob/master/lib/Target/NVPTX/NVPTX.td#L72----class Libdevice a where- libdevice :: Compute -> a--instance Libdevice AST.Module where- libdevice _- | CUDA.libraryVersion >= 9000- = libdevice_50_mdl- --- libdevice (Compute n m) =- case (n,m) of- (2,_) -> libdevice_20_mdl -- 2.0, 2.1- (3,x) | x < 5 -> libdevice_30_mdl -- 3.0, 3.2- | otherwise -> libdevice_35_mdl -- 3.5, 3.7- (5,_) -> libdevice_50_mdl -- 5.x- (6,_) -> libdevice_50_mdl -- 6.x- _ -> $internalError "libdevice" "no binary for this architecture"--instance Libdevice (String, ByteString) where- libdevice _- | CUDA.libraryVersion >= 9000- = libdevice_50_bc- --- libdevice (Compute n m) =- case (n,m) of- (2,_) -> libdevice_20_bc -- 2.0, 2.1- (3,x) | x < 5 -> libdevice_30_bc -- 3.0, 3.2- | otherwise -> libdevice_35_bc -- 3.5, 3.7- (5,_) -> libdevice_50_bc -- 5.x- (6,_) -> libdevice_50_bc -- 6.x- _ -> $internalError "libdevice" "no binary for this architecture"----- Load the libdevice bitcode files as an LLVM AST module. The top-level--- unsafePerformIO ensures that the data is only read from disk once per program--- execution.------ TLM: As of CUDA-9.0, libdevice is no longer split into multiple files--- depending on the target compute architecture. The function 'libdeviceBitcode'--- knows this and ignores the architecture parameter, and in the above instances--- we only refer to the 5.0 module below. Although the TH splices will be run--- 4 times (and read in the same file 4 times) hopefully GHC is smart enough to--- remove the unused bindings as dead code...----{-# NOINLINE libdevice_20_mdl #-}-{-# NOINLINE libdevice_30_mdl #-}-{-# NOINLINE libdevice_35_mdl #-}-{-# NOINLINE libdevice_50_mdl #-}-libdevice_20_mdl, libdevice_30_mdl, libdevice_35_mdl, libdevice_50_mdl :: AST.Module-libdevice_20_mdl = unsafePerformIO $ libdeviceModule (Compute 2 0)-libdevice_30_mdl = unsafePerformIO $ libdeviceModule (Compute 3 0)-libdevice_35_mdl = unsafePerformIO $ libdeviceModule (Compute 3 5)-libdevice_50_mdl = unsafePerformIO $ libdeviceModule (Compute 5 0)---- Load the libdevice bitcode files as raw binary data.----libdevice_20_bc, libdevice_30_bc, libdevice_35_bc, libdevice_50_bc :: (String,ByteString)-libdevice_20_bc = $$( libdeviceBitcode (Compute 2 0) )-libdevice_30_bc = $$( libdeviceBitcode (Compute 3 0) )-libdevice_35_bc = $$( libdeviceBitcode (Compute 3 5) )-libdevice_50_bc = $$( libdeviceBitcode (Compute 5 0) )----- Load the libdevice bitcode file for the given compute architecture, and raise--- it to a Haskell AST that can be kept for future use. The name of the bitcode--- files follows:------ libdevice.compute_XX.YY.bc------ Where XX represents the compute capability, and YY represents a version(?) We--- search the libdevice PATH for all files of the appropriate compute capability--- and load the most recent.----libdeviceModule :: Compute -> IO AST.Module-libdeviceModule arch = do- let bc :: (String, ByteString)- bc = libdevice arch- --- withContext $ \ctx ->- withModuleFromBitcode ctx bc moduleAST-
− Data/Array/Accelerate/LLVM/PTX/Compile/Libdevice/TH.hs
@@ -1,188 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE TemplateHaskell #-}--- |--- Module : Data.Array.Accelerate.LLVM.PTX.Compile.Libdevice.TH--- Copyright : [2017] Trevor L. McDonell--- License : BSD3------ Maintainer : Trevor L. McDonell <tmcdonell@cse.unsw.edu.au>--- Stability : experimental--- Portability : non-portable (GHC extensions)-----module Data.Array.Accelerate.LLVM.PTX.Compile.Libdevice.TH (-- nvvmReflectModule, nvvmReflectBitcode,- libdeviceBitcode,--) where--import qualified LLVM.AST as AST-import qualified LLVM.AST.Attribute as AST-import qualified LLVM.AST.Global as AST.G-import qualified LLVM.Context as LLVM-import qualified LLVM.Module as LLVM--import LLVM.AST.Type.Representation--import Data.Array.Accelerate.Error-import Data.Array.Accelerate.LLVM.CodeGen.Base-import Data.Array.Accelerate.LLVM.CodeGen.Downcast-import Data.Array.Accelerate.LLVM.PTX.Target--import Foreign.CUDA.Path-import Foreign.CUDA.Analysis-import qualified Foreign.CUDA.Driver as CUDA--import Data.ByteString ( ByteString )-import Data.FileEmbed-import Data.List-import Data.Maybe-import Language.Haskell.TH.Syntax hiding ( Name )-import System.Directory-import System.FilePath-import Text.Printf-import qualified Data.ByteString.Short as BS----- This is a hacky module that can be linked against in order to provide the--- same functionality as running the NVVMReflect pass.------ Note: [NVVM Reflect Pass]------ To accommodate various math-related compiler flags that can affect code--- generation of libdevice code, the library code depends on a special LLVM IR--- pass (NVVMReflect) to handle conditional compilation within LLVM IR. This--- pass looks for calls to the @__nvvm_reflect function and replaces them with--- constants based on the defined reflection parameters.------ libdevice currently uses the following reflection parameters to control code--- generation:------ * __CUDA_FTZ={0,1} fast math that flushes denormals to zero------ Since this is currently the only reflection parameter supported, and that we--- prefer correct results over pure speed, we do not flush denormals to zero. If--- the list of supported parameters ever changes, we may need to re-evaluate--- this implementation.----nvvmReflectModule :: AST.Module-nvvmReflectModule =- AST.Module- { AST.moduleName = "nvvm-reflect"- , AST.moduleSourceFileName = BS.empty- , AST.moduleDataLayout = targetDataLayout (undefined::PTX)- , AST.moduleTargetTriple = targetTriple (undefined::PTX)- , AST.moduleDefinitions = [AST.GlobalDefinition $ AST.G.functionDefaults- { AST.G.name = AST.Name "__nvvm_reflect"- , AST.G.returnType = downcast (integralType :: IntegralType Int32)- , AST.G.parameters = ( [ptrParameter scalarType (UnName 0 :: Name (Ptr Int8))], False )- , AST.G.functionAttributes = map Right [AST.NoUnwind, AST.ReadNone, AST.AlwaysInline]- , AST.G.basicBlocks = []- }]- }----- Lower the given NVVM Reflect module into bitcode.----nvvmReflectBitcode :: AST.Module -> Q (TExp (String, ByteString))-nvvmReflectBitcode mdl = do- let name = "__nvvm_reflect"- --- bs <- runIO $ LLVM.withContext $ \ctx -> do- LLVM.withModuleFromAST ctx mdl LLVM.moduleLLVMAssembly- be <- bsToExp bs- return . TExp $ TupE [ LitE (StringL name), be ]----- Load the libdevice bitcode file for the given compute architecture. The name--- of the bitcode files follows the format:------ libdevice.compute_XX.YY.bc------ Where XX represents the compute capability, and YY represents a version(?) We--- search the libdevice PATH for all files of the appropriate compute capability--- and load the "most recent" (by sort order).----libdeviceBitcode :: Compute -> Q (TExp (String, ByteString))-libdeviceBitcode (Compute m n) = do- let basename- | CUDA.libraryVersion < 9000 = printf "libdevice.compute_%d%d" m n- | otherwise = "libdevice"- --- err = $internalError "libdevice" (printf "not found: %s.YY.bc" basename)- best f = basename `isPrefixOf` f && takeExtension f == ".bc"- base = cudaInstallPath </> "nvvm" </> "libdevice"- --- files <- runIO $ getDirectoryContents base- --- let name = fromMaybe err . listToMaybe . sortBy (flip compare) $ filter best files- path = base </> name- --- bc <- embedFile path- return . TExp $ TupE [ LitE (StringL name), bc ]----- Determine the location of the libdevice bitcode libraries. We search for the--- location of the 'nvcc' executable in the PATH. From that, we assume the--- location of the libdevice bitcode files.------ libdevicePath :: IO FilePath--- libdevicepath = do--- nvcc <- fromMaybe (error "could not find 'nvcc' in PATH") `fmap` findExecutable "nvcc"--- ----- let ccvn = reverse (splitPath nvcc)--- dir = "libdevice" : "nvvm" : drop 2 ccvn--- ----- return (joinPath (reverse dir))----- With these instances it is possible to also write TH function to raise the--- libNVVM modules to an AST. However, generating those large ASTs results in--- awful compile times.------ $( deriveLift ''AST.AddrSpace )--- $( deriveLift ''AST.AlignType )--- $( deriveLift ''AST.AlignmentInfo )--- $( deriveLift ''AST.BasicBlock )--- $( deriveLift ''AST.CallingConvention )--- $( deriveLift ''AST.Constant )--- $( deriveLift ''AST.DataLayout )--- $( deriveLift ''AST.Definition )--- $( deriveLift ''AST.Dialect )--- $( deriveLift ''AST.Endianness )--- $( deriveLift ''AST.FastMathFlags )--- $( deriveLift ''AST.FloatingPointFormat )--- $( deriveLift ''AST.FloatingPointPredicate )--- $( deriveLift ''AST.FunctionAttribute )--- $( deriveLift ''AST.Global )--- $( deriveLift ''AST.GroupID )--- $( deriveLift ''AST.InlineAssembly )--- $( deriveLift ''AST.Instruction )--- $( deriveLift ''AST.IntegerPredicate )--- $( deriveLift ''AST.LandingPadClause )--- $( deriveLift ''AST.Linkage )--- $( deriveLift ''AST.Mangling )--- $( deriveLift ''AST.MemoryOrdering )--- $( deriveLift ''AST.Metadata )--- $( deriveLift ''AST.MetadataNode )--- $( deriveLift ''AST.MetadataNodeID )--- $( deriveLift ''AST.Model )--- $( deriveLift ''AST.Module )--- $( deriveLift ''AST.Name )--- $( deriveLift ''AST.Named )--- $( deriveLift ''AST.Operand )--- $( deriveLift ''AST.Parameter )--- $( deriveLift ''AST.ParameterAttribute )--- $( deriveLift ''AST.RMWOperation )--- $( deriveLift ''AST.SelectionKind )--- $( deriveLift ''AST.SomeFloat )--- $( deriveLift ''AST.StorageClass )--- $( deriveLift ''AST.SynchronizationScope )--- $( deriveLift ''AST.TailCallKind )--- $( deriveLift ''AST.Terminator )--- $( deriveLift ''AST.Type )--- $( deriveLift ''AST.UnnamedAddr )--- $( deriveLift ''AST.Visibility )--- $( deriveLift ''NonEmpty )-
− Data/Array/Accelerate/LLVM/PTX/Context.hs
@@ -1,147 +0,0 @@-{-# LANGUAGE RecordWildCards #-}--- |--- Module : Data.Array.Accelerate.LLVM.PTX.Context--- Copyright : [2014..2017] Trevor L. McDonell--- [2014..2014] Vinod Grover (NVIDIA Corporation)--- License : BSD3------ Maintainer : Trevor L. McDonell <tmcdonell@cse.unsw.edu.au>--- Stability : experimental--- Portability : non-portable (GHC extensions)-----module Data.Array.Accelerate.LLVM.PTX.Context (-- Context(..),- new, raw, withContext,--) where--import Data.Array.Accelerate.Lifetime-import Data.Array.Accelerate.LLVM.PTX.Analysis.Device-import qualified Data.Array.Accelerate.LLVM.PTX.Debug as Debug--import qualified Foreign.CUDA.Analysis as CUDA-import qualified Foreign.CUDA.Driver as CUDA-import qualified Foreign.CUDA.Driver.Device as CUDA--import Control.Exception-import Control.Monad-import Text.PrettyPrint----- | An execution context, which is tied to a specific device and CUDA execution--- context.----data Context = Context {- deviceProperties :: {-# UNPACK #-} !CUDA.DeviceProperties -- information on hardware resources- , deviceContext :: {-# UNPACK #-} !(Lifetime CUDA.Context) -- device execution context- }--instance Eq Context where- c1 == c2 = deviceContext c1 == deviceContext c2----- | Create a new CUDA execution context----new :: CUDA.Device- -> CUDA.DeviceProperties- -> [CUDA.ContextFlag]- -> IO Context-new dev prp flags = do- ctx <- raw dev prp =<< CUDA.create dev flags- _ <- CUDA.pop- return ctx---- | Wrap a raw CUDA execution context----raw :: CUDA.Device- -> CUDA.DeviceProperties- -> CUDA.Context- -> IO Context-raw dev prp ctx = do- lft <- newLifetime ctx- addFinalizer lft $ do- message $ "finalise context " ++ showContext ctx- CUDA.destroy ctx-- -- The kernels don't use much shared memory, so for devices that support it- -- prefer using those memory banks as an L1 cache.- --- -- TLM: Is this a good idea? For example, external libraries such as cuBLAS- -- rely heavily on shared memory and thus this could adversely affect- -- performance. Perhaps we should use 'setCacheConfigFun' for individual- -- functions which might benefit from this.- --- when (CUDA.computeCapability prp >= CUDA.Compute 2 0)- (CUDA.setCache CUDA.PreferL1)-- -- Display information about the selected device- Debug.traceIO Debug.verbose (deviceInfo dev prp)-- return $! Context prp lft----- | Push the context onto the CPUs thread stack of current contexts and execute--- some operation.----{-# INLINE withContext #-}-withContext :: Context -> IO a -> IO a-withContext Context{..} action =- withLifetime deviceContext $ \ctx ->- bracket_ (push ctx) pop action--{-# INLINE push #-}-push :: CUDA.Context -> IO ()-push ctx = do- message $ "push context: " ++ showContext ctx- CUDA.push ctx--{-# INLINE pop #-}-pop :: IO ()-pop = do- ctx <- CUDA.pop- message $ "pop context: " ++ showContext ctx----- Debugging--- ------------- Nicely format a summary of the selected CUDA device, example:------ Device 0: GeForce 9600M GT (compute capability 1.1)--- 4 multiprocessors @ 1.25GHz (32 cores), 512MB global memory----deviceInfo :: CUDA.Device -> CUDA.DeviceProperties -> String-deviceInfo dev prp = render $ reset <>- devID <> colon <+> vcat [ name <+> parens compute- , processors <+> at <+> text clock <+> parens cores <> comma <+> memory- ]- where- name = text (CUDA.deviceName prp)- compute = text "compute capatability" <+> text (show $ CUDA.computeCapability prp)- devID = text "Device" <+> int (fromIntegral $ CUDA.useDevice dev) -- hax- processors = int (CUDA.multiProcessorCount prp) <+> text "multiprocessors"- cores = int (CUDA.multiProcessorCount prp * coresPerMultiProcessor prp) <+> text "cores"- memory = text mem <+> text "global memory"- --- clock = Debug.showFFloatSIBase (Just 2) 1000 (fromIntegral $ CUDA.clockRate prp * 1000 :: Double) "Hz"- mem = Debug.showFFloatSIBase (Just 0) 1024 (fromIntegral $ CUDA.totalGlobalMem prp :: Double) "B"- at = char '@'- reset = zeroWidthText "\r"---{-# INLINE trace #-}-trace :: String -> IO a -> IO a-trace msg next = do- Debug.traceIO Debug.dump_gc ("gc: " ++ msg)- next--{-# INLINE message #-}-message :: String -> IO ()-message s = s `trace` return ()--{-# INLINE showContext #-}-showContext :: CUDA.Context -> String-showContext (CUDA.Context c) = show c-
− Data/Array/Accelerate/LLVM/PTX/Debug.hs
@@ -1,101 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE TypeOperators #-}--- |--- Module : Data.Array.Accelerate.LLVM.PTX.Debug--- Copyright : [2014..2017] Trevor L. McDonell--- [2014..2014] Vinod Grover (NVIDIA Corporation)--- License : BSD3------ Maintainer : Trevor L. McDonell <tmcdonell@cse.unsw.edu.au>--- Stability : experimental--- Portability : non-portable (GHC extensions)-----module Data.Array.Accelerate.LLVM.PTX.Debug (-- module Data.Array.Accelerate.Debug,- module Data.Array.Accelerate.LLVM.PTX.Debug,--) where--import Data.Array.Accelerate.Debug hiding ( timed, elapsed )--import Foreign.CUDA.Driver.Stream ( Stream )-import qualified Foreign.CUDA.Driver.Event as Event--import Control.Concurrent-import Data.Label-import Data.Time.Clock-import System.CPUTime-import Text.Printf--import GHC.Float----- | Execute an action and time the results. The second argument specifies how--- to format the output string given elapsed GPU and CPU time respectively----timed- :: (Flags :-> Bool)- -> (Double -> Double -> Double -> String)- -> Maybe Stream- -> IO ()- -> IO ()-{-# INLINE timed #-}-timed f msg =- monitorProcTime (queryFlag f) (\t1 t2 t3 -> traceIO f (msg t1 t2 t3))--monitorProcTime- :: IO Bool- -> (Double -> Double -> Double -> IO ())- -> Maybe Stream- -> IO ()- -> IO ()-{-# INLINE monitorProcTime #-}-#if ACCELERATE_DEBUG-monitorProcTime enabled display stream action = do- yes <- enabled- if yes- then do- gpuBegin <- Event.create []- gpuEnd <- Event.create []- wallBegin <- getCurrentTime- cpuBegin <- getCPUTime- Event.record gpuBegin stream- action- Event.record gpuEnd stream- cpuEnd <- getCPUTime- wallEnd <- getCurrentTime-- -- Wait for the GPU to finish executing then display the timing execution- -- message. Do this in a separate thread so that the remaining kernels can- -- be queued asynchronously.- --- _ <- forkIO $ do- Event.block gpuEnd- diff <- Event.elapsedTime gpuBegin gpuEnd- let gpuTime = float2Double $ diff * 1E-3 -- milliseconds- cpuTime = fromIntegral (cpuEnd - cpuBegin) * 1E-12 -- picoseconds- wallTime = realToFrac (diffUTCTime wallEnd wallBegin)-- Event.destroy gpuBegin- Event.destroy gpuEnd- --- display wallTime cpuTime gpuTime- --- return ()-- else- action-#else-monitorProcTime _ _ _ action = action-#endif--{-# INLINE elapsed #-}-elapsed :: Double -> Double -> Double -> String-elapsed wallTime cpuTime gpuTime =- printf "%s (wall), %s (cpu), %s (gpu)"- (showFFloatSIBase (Just 3) 1000 wallTime "s")- (showFFloatSIBase (Just 3) 1000 cpuTime "s")- (showFFloatSIBase (Just 3) 1000 gpuTime "s")-
− Data/Array/Accelerate/LLVM/PTX/Embed.hs
@@ -1,86 +0,0 @@-{-# LANGUAGE QuasiQuotes #-}-{-# LANGUAGE TemplateHaskell #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}--- |--- Module : Data.Array.Accelerate.LLVM.PTX.Embed--- Copyright : [2017] Trevor L. McDonell--- License : BSD3------ Maintainer : Trevor L. McDonell <tmcdonell@cse.unsw.edu.au>--- Stability : experimental--- Portability : non-portable (GHC extensions)-----module Data.Array.Accelerate.LLVM.PTX.Embed (-- module Data.Array.Accelerate.LLVM.Embed,--) where--import Data.ByteString.Short.Char8 as S8-import Data.ByteString.Short.Internal as BS--import Data.Array.Accelerate.Lifetime--import Data.Array.Accelerate.LLVM.Compile-import Data.Array.Accelerate.LLVM.Embed--import Data.Array.Accelerate.LLVM.PTX.Compile-import Data.Array.Accelerate.LLVM.PTX.Link-import Data.Array.Accelerate.LLVM.PTX.Target-import Data.Array.Accelerate.LLVM.PTX.Context---- import qualified Foreign.CUDA.Analysis as CUDA-import qualified Foreign.CUDA.Driver as CUDA--import Foreign.Ptr-import GHC.Ptr ( Ptr(..) )-import Language.Haskell.TH ( Q, TExp )-import System.IO.Unsafe-import qualified Data.ByteString as B-import qualified Data.ByteString.Unsafe as B-import qualified Language.Haskell.TH as TH-import qualified Language.Haskell.TH.Syntax as TH---instance Embed PTX where- embedForTarget = embed---- Embed the given object code and set up to be reloaded at execution time.----embed :: PTX -> ObjectR PTX -> Q (TExp (ExecutableR PTX))-embed target (ObjectR _ cfg obj) = do- -- Load the module to recover information such as number of registers and- -- bytes of shared memory. It may be possible to do this without requiring an- -- active CUDA context.- kmd <- TH.runIO $ withContext (ptxContext target) $ do- jit <- B.unsafeUseAsCString obj $ \p -> CUDA.loadDataFromPtrEx (castPtr p) []- ks <- mapM (uncurry (linkFunctionQ (CUDA.jitModule jit))) cfg- CUDA.unload (CUDA.jitModule jit)- return ks-- -- Generate the embedded kernel executable. This will load the embedded object- -- code into the current (at execution time) context.- [|| unsafePerformIO $ do- jit <- CUDA.loadDataFromPtrEx $$( TH.unsafeTExpCoerce [| Ptr $(TH.litE (TH.StringPrimL (B.unpack obj))) |] ) []- fun <- newLifetime (FunctionTable $$(listE (map (linkQ 'jit) kmd)))- return $ PTXR fun- ||]- where- linkQ :: TH.Name -> (Kernel, Q (TExp (Int -> Int))) -> Q (TExp Kernel)- linkQ jit (Kernel name _ dsmem cta _, grid) =- [|| unsafePerformIO $ do- f <- CUDA.getFun (CUDA.jitModule $$(TH.unsafeTExpCoerce (TH.varE jit))) $$(TH.unsafeTExpCoerce (TH.lift (S8.unpack name)))- return $ Kernel $$(liftSBS name) f dsmem cta $$grid- ||]-- listE :: [Q (TExp a)] -> Q (TExp [a])- listE xs = TH.unsafeTExpCoerce (TH.listE (map TH.unTypeQ xs))-- liftSBS :: ShortByteString -> Q (TExp ShortByteString)- liftSBS bs =- let bytes = BS.unpack bs- len = BS.length bs- in- [|| unsafePerformIO $ BS.createFromPtr $$( TH.unsafeTExpCoerce [| Ptr $(TH.litE (TH.StringPrimL bytes)) |]) len ||]-
− Data/Array/Accelerate/LLVM/PTX/Execute.hs
@@ -1,595 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE TypeOperators #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}--- |--- Module : Data.Array.Accelerate.LLVM.PTX.Execute--- Copyright : [2014..2017] Trevor L. McDonell--- [2014..2014] Vinod Grover (NVIDIA Corporation)--- License : BSD3------ Maintainer : Trevor L. McDonell <tmcdonell@cse.unsw.edu.au>--- Stability : experimental--- Portability : non-portable (GHC extensions)-----module Data.Array.Accelerate.LLVM.PTX.Execute (-- executeAcc, executeAfun,- executeOpenAcc,--) where---- accelerate-import Data.Array.Accelerate.Analysis.Match-import Data.Array.Accelerate.Array.Sugar-import Data.Array.Accelerate.Error-import Data.Array.Accelerate.Lifetime--import Data.Array.Accelerate.LLVM.Analysis.Match-import Data.Array.Accelerate.LLVM.Execute-import Data.Array.Accelerate.LLVM.State--import Data.Array.Accelerate.LLVM.PTX.Analysis.Launch ( multipleOf )-import Data.Array.Accelerate.LLVM.PTX.Array.Data-import Data.Array.Accelerate.LLVM.PTX.Array.Prim ( memsetArrayAsync )-import Data.Array.Accelerate.LLVM.PTX.Execute.Async-import Data.Array.Accelerate.LLVM.PTX.Execute.Environment-import Data.Array.Accelerate.LLVM.PTX.Execute.Marshal-import Data.Array.Accelerate.LLVM.PTX.Link-import Data.Array.Accelerate.LLVM.PTX.Target-import qualified Data.Array.Accelerate.LLVM.PTX.Debug as Debug--import Data.Range.Range ( Range(..) )-import Control.Parallel.Meta ( runExecutable )---- cuda-import qualified Foreign.CUDA.Driver as CUDA---- library-import Control.Monad ( when )-import Control.Monad.State ( gets, liftIO )-import Data.ByteString.Short.Char8 ( ShortByteString, unpack )-import Data.Int ( Int32 )-import Data.List ( find )-import Data.Maybe ( fromMaybe )-import Data.Word ( Word32 )-import Text.Printf ( printf )-import Prelude hiding ( exp, map, sum, scanl, scanr )-import qualified Prelude as P----- Array expression evaluation--- ------------------------------- Computations are evaluated by traversing the AST bottom up, and for each node--- distinguishing between three cases:------ 1. If it is a Use node, we return a reference to the array data. The data--- will already have been copied to the device during compilation of the--- kernels.------ 2. If it is a non-skeleton node, such as a let binding or shape conversion,--- then execute directly by updating the environment or similar.------ 3. If it is a skeleton node, then we need to execute the generated LLVM--- code.----instance Execute PTX where- map = simpleOp- generate = simpleOp- transform = simpleOp- backpermute = simpleOp- fold = foldOp- fold1 = fold1Op- foldSeg = foldSegOp- fold1Seg = foldSegOp- scanl = scanOp- scanl1 = scan1Op- scanl' = scan'Op- scanr = scanOp- scanr1 = scan1Op- scanr' = scan'Op- permute = permuteOp- stencil1 = stencil1Op- stencil2 = stencil2Op----- Skeleton implementation--- --------------------------- Simple kernels just need to know the shape of the output array----simpleOp- :: (Shape sh, Elt e)- => ExecutableR PTX- -> Gamma aenv- -> Aval aenv- -> Stream- -> sh- -> LLVM PTX (Array sh e)-simpleOp exe gamma aenv stream sh = withExecutable exe $ \ptxExecutable -> do- let kernel = case functionTable ptxExecutable of- k:_ -> k- _ -> $internalError "simpleOp" "no kernels found"- --- out <- allocateRemote sh- ptx <- gets llvmTarget- liftIO $ executeOp ptx kernel gamma aenv stream (IE 0 (size sh)) out- return out--simpleNamed- :: (Shape sh, Elt e)- => ShortByteString- -> ExecutableR PTX- -> Gamma aenv- -> Aval aenv- -> Stream- -> sh- -> LLVM PTX (Array sh e)-simpleNamed fun exe gamma aenv stream sh = withExecutable exe $ \ptxExecutable -> do- out <- allocateRemote sh- ptx <- gets llvmTarget- liftIO $ executeOp ptx (ptxExecutable !# fun) gamma aenv stream (IE 0 (size sh)) out- return out----- There are two flavours of fold operation:------ 1. If we are collapsing to a single value, then multiple thread blocks are--- working together. Since thread blocks synchronise with each other via--- kernel launches, each block computes a partial sum and the kernel is--- launched recursively until the final value is reached.------ 2. If this is a multidimensional reduction, then each inner dimension is--- handled by a single thread block, so no global communication is--- necessary. Furthermore are two kernel flavours: each innermost dimension--- can be cooperatively reduced by (a) a thread warp; or (b) a thread--- block. Currently we always use the first, but require benchmarking to--- determine when to select each.----fold1Op- :: (Shape sh, Elt e)- => ExecutableR PTX- -> Gamma aenv- -> Aval aenv- -> Stream- -> (sh :. Int)- -> LLVM PTX (Array sh e)-fold1Op exe gamma aenv stream sh@(sx :. sz)- = $boundsCheck "fold1" "empty array" (sz > 0)- $ case size sh of- 0 -> allocateRemote sx -- empty, but possibly with one or more non-zero dimensions- _ -> foldCore exe gamma aenv stream sh--foldOp- :: (Shape sh, Elt e)- => ExecutableR PTX- -> Gamma aenv- -> Aval aenv- -> Stream- -> (sh :. Int)- -> LLVM PTX (Array sh e)-foldOp exe gamma aenv stream sh@(sx :. _)- = case size sh of- 0 -> simpleNamed "generate" exe gamma aenv stream (listToShape (P.map (max 1) (shapeToList sx)))- _ -> foldCore exe gamma aenv stream sh--foldCore- :: forall aenv sh e. (Shape sh, Elt e)- => ExecutableR PTX- -> Gamma aenv- -> Aval aenv- -> Stream- -> (sh :. Int)- -> LLVM PTX (Array sh e)-foldCore exe gamma aenv stream sh- | Just Refl <- matchShapeType (undefined::sh) (undefined::Z)- = foldAllOp exe gamma aenv stream sh- --- | otherwise- = foldDimOp exe gamma aenv stream sh---foldAllOp- :: forall aenv e. Elt e- => ExecutableR PTX- -> Gamma aenv- -> Aval aenv- -> Stream- -> DIM1- -> LLVM PTX (Scalar e)-foldAllOp exe gamma aenv stream (Z :. n) = withExecutable exe $ \ptxExecutable -> do- ptx <- gets llvmTarget- let- ks = ptxExecutable !# "foldAllS"- km1 = ptxExecutable !# "foldAllM1"- km2 = ptxExecutable !# "foldAllM2"- --- if kernelThreadBlocks ks n == 1- then do- -- The array is small enough that we can compute it in a single step- out <- allocateRemote Z- liftIO $ executeOp ptx ks gamma aenv stream (IE 0 n) out- return out-- else do- -- Multi-kernel reduction to a single element. The first kernel integrates- -- any delayed elements, and the second is called recursively until- -- reaching a single element.- let- rec :: Vector e -> LLVM PTX (Scalar e)- rec tmp@(Array ((),m) adata)- | m <= 1 = return $ Array () adata- | otherwise = do- let s = m `multipleOf` kernelThreadBlockSize km2- out <- allocateRemote (Z :. s)- liftIO $ executeOp ptx km2 gamma aenv stream (IE 0 s) (tmp, out)- rec out- --- let s = n `multipleOf` kernelThreadBlockSize km1- tmp <- allocateRemote (Z :. s)- liftIO $ executeOp ptx km1 gamma aenv stream (IE 0 s) tmp- rec tmp---foldDimOp- :: forall aenv sh e. (Shape sh, Elt e)- => ExecutableR PTX- -> Gamma aenv- -> Aval aenv- -> Stream- -> (sh :. Int)- -> LLVM PTX (Array sh e)-foldDimOp exe gamma aenv stream (sh :. sz) = withExecutable exe $ \ptxExecutable -> do- let- kernel = if sz > 0- then ptxExecutable !# "fold"- else ptxExecutable !# "generate"- --- out <- allocateRemote sh- ptx <- gets llvmTarget- liftIO $ executeOp ptx kernel gamma aenv stream (IE 0 (size sh)) out- return out---foldSegOp- :: (Shape sh, Elt e)- => ExecutableR PTX- -> Gamma aenv- -> Aval aenv- -> Stream- -> (sh :. Int)- -> (Z :. Int)- -> LLVM PTX (Array (sh :. Int) e)-foldSegOp exe gamma aenv stream (sh :. sz) (Z :. ss) = withExecutable exe $ \ptxExecutable -> do- let- n = ss - 1 -- segments array has been 'scanl (+) 0'`ed- m = size sh * n- foldseg = if (sz`quot`ss) < (2 * kernelThreadBlockSize foldseg_cta)- then foldseg_warp- else foldseg_cta- --- foldseg_cta = ptxExecutable !# "foldSeg_block"- foldseg_warp = ptxExecutable !# "foldSeg_warp"- -- qinit = ptxExecutable !# "qinit"- --- out <- allocateRemote (sh :. n)- ptx <- gets llvmTarget- liftIO $ executeOp ptx foldseg gamma aenv stream (IE 0 m) out- return out---scanOp- :: (Shape sh, Elt e)- => ExecutableR PTX- -> Gamma aenv- -> Aval aenv- -> Stream- -> sh :. Int- -> LLVM PTX (Array (sh:.Int) e)-scanOp exe gamma aenv stream (sz :. n) =- case n of- 0 -> simpleNamed "generate" exe gamma aenv stream (sz :. 1)- _ -> scanCore exe gamma aenv stream sz n (n+1)--scan1Op- :: (Shape sh, Elt e)- => ExecutableR PTX- -> Gamma aenv- -> Aval aenv- -> Stream- -> sh :. Int- -> LLVM PTX (Array (sh:.Int) e)-scan1Op exe gamma aenv stream (sz :. n)- = $boundsCheck "scan1" "empty array" (n > 0)- $ scanCore exe gamma aenv stream sz n n--scanCore- :: forall aenv sh e. (Shape sh, Elt e)- => ExecutableR PTX- -> Gamma aenv- -> Aval aenv- -> Stream- -> sh- -> Int -- input size- -> Int -- output size- -> LLVM PTX (Array (sh:.Int) e)-scanCore exe gamma aenv stream sz n m- | Just Refl <- matchShapeType (undefined::sh) (undefined::Z)- = scanAllOp exe gamma aenv stream n m- --- | otherwise- = scanDimOp exe gamma aenv stream sz m---scanAllOp- :: forall aenv e. Elt e- => ExecutableR PTX- -> Gamma aenv- -> Aval aenv- -> Stream- -> Int -- input size- -> Int -- output size- -> LLVM PTX (Vector e)-scanAllOp exe gamma aenv stream n m = withExecutable exe $ \ptxExecutable -> do- let- k1 = ptxExecutable !# "scanP1"- k2 = ptxExecutable !# "scanP2"- k3 = ptxExecutable !# "scanP3"- --- c = kernelThreadBlockSize k1- s = n `multipleOf` c- --- ptx <- gets llvmTarget- out <- allocateRemote (Z :. m)-- -- Step 1: Independent thread-block-wide scans of the input. Small arrays- -- which can be computed by a single thread block will require no- -- additional work.- tmp <- allocateRemote (Z :. s) :: LLVM PTX (Vector e)- liftIO $ executeOp ptx k1 gamma aenv stream (IE 0 s) (tmp, out)-- -- Step 2: Multi-block reductions need to compute the per-block prefix,- -- then apply those values to the partial results.- when (s > 1) $ do- liftIO $ executeOp ptx k2 gamma aenv stream (IE 0 s) tmp- liftIO $ executeOp ptx k3 gamma aenv stream (IE 0 (s-1)) (tmp, out, i32 c)-- return out---scanDimOp- :: forall aenv sh e. (Shape sh, Elt e)- => ExecutableR PTX- -> Gamma aenv- -> Aval aenv- -> Stream- -> sh- -> Int- -> LLVM PTX (Array (sh:.Int) e)-scanDimOp exe gamma aenv stream sz m = withExecutable exe $ \ptxExecutable -> do- ptx <- gets llvmTarget- out <- allocateRemote (sz :. m)- liftIO $ executeOp ptx (ptxExecutable !# "scan") gamma aenv stream (IE 0 (size sz)) out- return out---scan'Op- :: forall aenv sh e. (Shape sh, Elt e)- => ExecutableR PTX- -> Gamma aenv- -> Aval aenv- -> Stream- -> sh :. Int- -> LLVM PTX (Array (sh:.Int) e, Array sh e)-scan'Op exe gamma aenv stream sh@(sz :. n) =- case n of- 0 -> do out <- allocateRemote (sz :. 0)- sum <- simpleNamed "generate" exe gamma aenv stream sz- return (out, sum)- _ -> scan'Core exe gamma aenv stream sh--scan'Core- :: forall aenv sh e. (Shape sh, Elt e)- => ExecutableR PTX- -> Gamma aenv- -> Aval aenv- -> Stream- -> sh :. Int- -> LLVM PTX (Array (sh:.Int) e, Array sh e)-scan'Core exe gamma aenv stream sh- | Just Refl <- matchShapeType (undefined::sh) (undefined::Z)- = scan'AllOp exe gamma aenv stream sh- --- | otherwise- = scan'DimOp exe gamma aenv stream sh--scan'AllOp- :: forall aenv e. Elt e- => ExecutableR PTX- -> Gamma aenv- -> Aval aenv- -> Stream- -> DIM1- -> LLVM PTX (Vector e, Scalar e)-scan'AllOp exe gamma aenv stream (Z :. n) = withExecutable exe $ \ptxExecutable -> do- let- k1 = ptxExecutable !# "scanP1"- k2 = ptxExecutable !# "scanP2"- k3 = ptxExecutable !# "scanP3"- --- c = kernelThreadBlockSize k1- s = n `multipleOf` c- --- ptx <- gets llvmTarget- out <- allocateRemote (Z :. n)- tmp <- allocateRemote (Z :. s) :: LLVM PTX (Vector e)-- -- Step 1: independent thread-block-wide scans. Each block stores its partial- -- sum to a temporary array.- liftIO $ executeOp ptx k1 gamma aenv stream (IE 0 s) (tmp, out)-- -- If this was a small array that was processed by a single thread block then- -- we are done, otherwise compute the per-block prefix and apply those values- -- to the partial results.- if s == 1- then case tmp of- Array _ ad -> return (out, Array () ad)- else do- sum <- allocateRemote Z- liftIO $ executeOp ptx k2 gamma aenv stream (IE 0 s) (tmp, sum)- liftIO $ executeOp ptx k3 gamma aenv stream (IE 0 (s-1)) (tmp, out, i32 c)- return (out, sum)---scan'DimOp- :: forall aenv sh e. (Shape sh, Elt e)- => ExecutableR PTX- -> Gamma aenv- -> Aval aenv- -> Stream- -> sh :. Int- -> LLVM PTX (Array (sh:.Int) e, Array sh e)-scan'DimOp exe gamma aenv stream sh@(sz :. _) = withExecutable exe $ \ptxExecutable -> do- ptx <- gets llvmTarget- out <- allocateRemote sh- sum <- allocateRemote sz- liftIO $ executeOp ptx (ptxExecutable !# "scan") gamma aenv stream (IE 0 (size sz)) (out,sum)- return (out,sum)---permuteOp- :: (Shape sh, Shape sh', Elt e)- => ExecutableR PTX- -> Gamma aenv- -> Aval aenv- -> Stream- -> Bool- -> sh- -> Array sh' e- -> LLVM PTX (Array sh' e)-permuteOp exe gamma aenv stream inplace shIn dfs = withExecutable exe $ \ptxExecutable -> do- let- n = size shIn- m = size (shape dfs)- kernel = case functionTable ptxExecutable of- k:_ -> k- _ -> $internalError "permute" "no kernels found"- --- ptx <- gets llvmTarget- out <- if inplace- then return dfs- else cloneArrayAsync stream dfs- --- case kernelName kernel of- "permute_rmw" -> liftIO $ executeOp ptx kernel gamma aenv stream (IE 0 n) out- "permute_mutex" -> do- barrier@(Array _ ad) <- allocateRemote (Z :. m) :: LLVM PTX (Vector Word32)- memsetArrayAsync stream m 0 ad- liftIO $ executeOp ptx kernel gamma aenv stream (IE 0 n) (out, barrier)- _ -> $internalError "permute" "unexpected kernel image"- --- return out----- Using the defaulting instances for stencil operations (for now).----stencil1Op- :: (Shape sh, Elt b)- => ExecutableR PTX- -> Gamma aenv- -> Aval aenv- -> Stream- -> Array sh a- -> LLVM PTX (Array sh b)-stencil1Op exe gamma aenv stream arr =- simpleOp exe gamma aenv stream (shape arr)--stencil2Op- :: (Shape sh, Elt c)- => ExecutableR PTX- -> Gamma aenv- -> Aval aenv- -> Stream- -> Array sh a- -> Array sh b- -> LLVM PTX (Array sh c)-stencil2Op exe gamma aenv stream arr brr =- simpleOp exe gamma aenv stream (shape arr `intersect` shape brr)----- Skeleton execution--- ---------------------- TODO: Calculate this from the device properties, say [a multiple of] the--- maximum number of in-flight threads that the device supports.----defaultPPT :: Int-defaultPPT = 32768--{-# INLINE i32 #-}-i32 :: Int -> Int32-i32 = fromIntegral---- | Retrieve the named kernel----(!#) :: FunctionTable -> ShortByteString -> Kernel-(!#) exe name- = fromMaybe ($internalError "lookupFunction" ("function not found: " ++ unpack name))- $ lookupKernel name exe--lookupKernel :: ShortByteString -> FunctionTable -> Maybe Kernel-lookupKernel name ptxExecutable =- find (\k -> kernelName k == name) (functionTable ptxExecutable)----- Execute the function implementing this kernel.----executeOp- :: Marshalable args- => PTX- -> Kernel- -> Gamma aenv- -> Aval aenv- -> Stream- -> Range- -> args- -> IO ()-executeOp ptx@PTX{..} kernel@Kernel{..} gamma aenv stream r args =- runExecutable fillP kernelName defaultPPT r $ \start end _ -> do- argv <- marshal ptx stream (i32 start, i32 end, args, (gamma,aenv))- launch kernel stream (end-start) argv----- Execute a device function with the given thread configuration and function--- parameters.----launch :: Kernel -> Stream -> Int -> [CUDA.FunParam] -> IO ()-launch Kernel{..} stream n args =- when (n > 0) $- withLifetime stream $ \st ->- Debug.monitorProcTime query msg (Just st) $- CUDA.launchKernel kernelFun grid cta smem (Just st) args- where- cta = (kernelThreadBlockSize, 1, 1)- grid = (kernelThreadBlocks n, 1, 1)- smem = kernelSharedMemBytes-- -- Debugging/monitoring support- query = if Debug.monitoringIsEnabled- then return True- else Debug.queryFlag Debug.dump_exec-- fst3 (x,_,_) = x- msg wall cpu gpu = do- Debug.addProcessorTime Debug.PTX gpu- Debug.traceIO Debug.dump_exec $- printf "exec: %s <<< %d, %d, %d >>> %s"- (unpack kernelName) (fst3 grid) (fst3 cta) smem (Debug.elapsed wall cpu gpu)-
− Data/Array/Accelerate/LLVM/PTX/Execute/Async.hs
@@ -1,63 +0,0 @@-{-# LANGUAGE TypeFamilies #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}--- |--- Module : Data.Array.Accelerate.LLVM.PTX.Execute.Async--- Copyright : [2014..2017] Trevor L. McDonell--- [2014..2014] Vinod Grover (NVIDIA Corporation)--- License : BSD3------ Maintainer : Trevor L. McDonell <tmcdonell@cse.unsw.edu.au>--- Stability : experimental--- Portability : non-portable (GHC extensions)-----module Data.Array.Accelerate.LLVM.PTX.Execute.Async (-- Async, Stream, Event,- module Data.Array.Accelerate.LLVM.Execute.Async,--) where---- accelerate-import Data.Array.Accelerate.LLVM.Execute.Async hiding ( Async )-import qualified Data.Array.Accelerate.LLVM.Execute.Async as A--import Data.Array.Accelerate.LLVM.PTX.Target-import Data.Array.Accelerate.LLVM.PTX.Execute.Event ( Event )-import Data.Array.Accelerate.LLVM.PTX.Execute.Stream ( Stream )-import qualified Data.Array.Accelerate.LLVM.PTX.Execute.Event as Event-import qualified Data.Array.Accelerate.LLVM.PTX.Execute.Stream as Stream---- standard library-import Control.Monad.State----- Asynchronous arrays in the CUDA backend are tagged with an Event that will be--- filled once the kernel implementing that array has completed.----type Async a = AsyncR PTX a--instance A.Async PTX where- type StreamR PTX = Stream- type EventR PTX = Event-- {-# INLINEABLE fork #-}- fork =- Stream.create-- {-# INLINEABLE join #-}- join stream =- liftIO $! Stream.destroy stream-- {-# INLINEABLE checkpoint #-}- checkpoint stream =- Event.waypoint stream-- {-# INLINEABLE after #-}- after stream event =- liftIO $! Event.after event stream-- {-# INLINEABLE block #-}- block event =- liftIO $! Event.block event-
− Data/Array/Accelerate/LLVM/PTX/Execute/Environment.hs
@@ -1,22 +0,0 @@--- |--- Module : Data.Array.Accelerate.LLVM.PTX.Execute.Environment--- Copyright : [2014..2017] Trevor L. McDonell--- [2014..2014] Vinod Grover (NVIDIA Corporation)--- License : BSD3------ Maintainer : Trevor L. McDonell <tmcdonell@cse.unsw.edu.au>--- Stability : experimental--- Portability : non-portable (GHC extensions)-----module Data.Array.Accelerate.LLVM.PTX.Execute.Environment (-- Aval, aprj--) where--import Data.Array.Accelerate.LLVM.PTX.Target-import Data.Array.Accelerate.LLVM.Execute.Environment--type Aval = AvalR PTX-
− Data/Array/Accelerate/LLVM/PTX/Execute/Event.hs
@@ -1,162 +0,0 @@-{-# LANGUAGE NamedFieldPuns #-}--- |--- Module : Data.Array.Accelerate.LLVM.PTX.Execute.Event--- Copyright : [2014..2017] Trevor L. McDonell--- [2014..2014] Vinod Grover (NVIDIA Corporation)--- License : BSD3------ Maintainer : Trevor L. McDonell <tmcdonell@cse.unsw.edu.au>--- Stability : experimental--- Portability : non-portable (GHC extensions)-----module Data.Array.Accelerate.LLVM.PTX.Execute.Event (-- Event,- create, destroy, query, waypoint, after, block,--) where---- accelerate-import Data.Array.Accelerate.Lifetime-import qualified Data.Array.Accelerate.Array.Remote.LRU as Remote--import Data.Array.Accelerate.LLVM.PTX.Array.Remote ( )-import Data.Array.Accelerate.LLVM.PTX.Target ( PTX(..) )-import Data.Array.Accelerate.LLVM.State-import qualified Data.Array.Accelerate.LLVM.PTX.Debug as Debug-import {-# SOURCE #-} Data.Array.Accelerate.LLVM.PTX.Execute.Stream---- cuda-import Foreign.CUDA.Driver.Error-import qualified Foreign.CUDA.Driver.Event as Event-import qualified Foreign.CUDA.Driver.Stream as Stream--import Control.Exception-import Control.Monad.State----- | Events can be used for efficient device-side synchronisation between--- execution streams and between the host.----type Event = Lifetime Event.Event----- | Create a new event. It will not be automatically garbage collected, and is--- not suitable for timing purposes.----{-# INLINEABLE create #-}-create :: LLVM PTX Event-create = do- e <- create'- event <- liftIO $ newLifetime e- liftIO $ addFinalizer event $ do message $ "destroy " ++ showEvent e- Event.destroy e- return event--create' :: LLVM PTX Event.Event-create' = do- PTX{ptxMemoryTable} <- gets llvmTarget- me <- attempt "create/new" (liftIO . catchOOM $ Event.create [Event.DisableTiming])- `orElse` do- Remote.reclaim ptxMemoryTable- liftIO $ do- message "create/new: failed (purging)"- catchOOM $ Event.create [Event.DisableTiming]- case me of- Just e -> return e- Nothing -> liftIO $ do- message "create/new: failed (non-recoverable)"- throwIO (ExitCode OutOfMemory)-- where- catchOOM :: IO a -> IO (Maybe a)- catchOOM it =- liftM Just it `catch` \e -> case e of- ExitCode OutOfMemory -> return Nothing- _ -> throwIO e-- attempt :: MonadIO m => String -> m (Maybe a) -> m (Maybe a)- attempt msg ea = do- ma <- ea- case ma of- Nothing -> return Nothing- Just a -> do liftIO (message msg)- return (Just a)-- orElse :: MonadIO m => m (Maybe a) -> m (Maybe a) -> m (Maybe a)- orElse ea eb = do- ma <- ea- case ma of- Just a -> return (Just a)- Nothing -> eb----- | Delete an event----{-# INLINEABLE destroy #-}-destroy :: Event -> IO ()-destroy = finalize---- | Create a new event marker that will be filled once execution in the--- specified stream has completed all previously submitted work.----{-# INLINEABLE waypoint #-}-waypoint :: Stream -> LLVM PTX Event-waypoint stream = do- event <- create- liftIO $- withLifetime stream $ \s -> do- withLifetime event $ \e -> do- message $ "add waypoint " ++ showEvent e ++ " in stream " ++ showStream s- Event.record e (Just s)- return event---- | Make all future work submitted to the given stream wait until the event--- reports completion before beginning execution.----{-# INLINEABLE after #-}-after :: Event -> Stream -> IO ()-after event stream =- withLifetime stream $ \s ->- withLifetime event $ \e -> do- message $ "after " ++ showEvent e ++ " in stream " ++ showStream s- Event.wait e (Just s) []---- | Block the calling thread until the event is recorded----{-# INLINEABLE block #-}-block :: Event -> IO ()-block event =- withLifetime event $ \e -> do- message $ "blocked on event " ++ showEvent e- Event.block e---- | Test whether an event has completed----{-# INLINEABLE query #-}-query :: Event -> IO Bool-query event = withLifetime event Event.query----- Debug--- -------{-# INLINE trace #-}-trace :: String -> IO a -> IO a-trace msg next = do- Debug.traceIO Debug.dump_sched ("event: " ++ msg)- next--{-# INLINE message #-}-message :: String -> IO ()-message s = s `trace` return ()--{-# INLINE showEvent #-}-showEvent :: Event.Event -> String-showEvent (Event.Event e) = show e--{-# INLINE showStream #-}-showStream :: Stream.Stream -> String-showStream (Stream.Stream s) = show s-
− Data/Array/Accelerate/LLVM/PTX/Execute/Event.hs-boot
@@ -1,26 +0,0 @@--- |--- Module : Data.Array.Accelerate.LLVM.PTX.Execute.Event-boot--- Copyright : [2016..2017] Trevor L. McDonell--- License : BSD3------ Maintainer : Trevor L. McDonell <tmcdonell@cse.unsw.edu.au>--- Stability : experimental--- Portability : non-portable (GHC extensions)-----module Data.Array.Accelerate.LLVM.PTX.Execute.Event (-- Event,- query, block--) where--import Data.Array.Accelerate.Lifetime-import qualified Foreign.CUDA.Driver.Event as Event---type Event = Lifetime Event.Event--query :: Event -> IO Bool-block :: Event -> IO ()-
− Data/Array/Accelerate/LLVM/PTX/Execute/Marshal.hs
@@ -1,147 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE ConstraintKinds #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeFamilies #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}-#if __GLASGOW_HASKELL__ <= 708-{-# LANGUAGE OverlappingInstances #-}-{-# OPTIONS_GHC -fno-warn-unrecognised-pragmas #-}-#endif--- |--- Module : Data.Array.Accelerate.LLVM.PTX.Execute.Marshal--- Copyright : [2014..2017] Trevor L. McDonell--- [2014..2014] Vinod Grover (NVIDIA Corporation)--- License : BSD3------ Maintainer : Trevor L. McDonell <tmcdonell@cse.unsw.edu.au>--- Stability : experimental--- Portability : non-portable (GHC extensions)-----module Data.Array.Accelerate.LLVM.PTX.Execute.Marshal (-- Marshalable, M.marshal--) where---- accelerate-import Data.Array.Accelerate.LLVM.CodeGen.Environment ( Gamma, Idx'(..) )-import qualified Data.Array.Accelerate.LLVM.Execute.Marshal as M--import Data.Array.Accelerate.LLVM.PTX.State-import Data.Array.Accelerate.LLVM.PTX.Target-import Data.Array.Accelerate.LLVM.PTX.Array.Data-import Data.Array.Accelerate.LLVM.PTX.Execute.Async ( Async, AsyncR(..) )-import Data.Array.Accelerate.LLVM.PTX.Execute.Event ( after )-import Data.Array.Accelerate.LLVM.PTX.Execute.Environment-import qualified Data.Array.Accelerate.LLVM.PTX.Array.Prim as Prim---- cuda-import qualified Foreign.CUDA.Driver as CUDA---- libraries-import Control.Monad-import Data.Int-import Data.DList ( DList )-import Data.Typeable-import Foreign.Ptr-import Foreign.Storable ( Storable )-import qualified Data.DList as DL-import qualified Data.IntMap as IM----- Instances for the PTX backend----type Marshalable args = M.Marshalable PTX args-type instance M.ArgR PTX = CUDA.FunParam----- Instances for handling concrete types in this backend, namely shapes and--- array data.----instance M.Marshalable PTX Int where- marshal' _ _ x = return $ DL.singleton (CUDA.VArg x)--instance M.Marshalable PTX Int32 where- marshal' _ _ x = return $ DL.singleton (CUDA.VArg x)--instance {-# OVERLAPS #-} M.Marshalable PTX (Gamma aenv, Aval aenv) where- marshal' ptx stream (gamma, aenv)- = fmap DL.concat- $ mapM (\(_, Idx' idx) -> M.marshal' ptx stream =<< sync (aprj idx aenv)) (IM.elems gamma)- where- -- HAXORZ~ D:- --- -- The 'Async' class functions need to run in the LLVM monad, but the- -- marshalling functions must run in IO because they will be executed in- -- the lower-level scheduling code.- --- -- We hack around this impedance mismatch by calling the 'after'- -- implementation directly.- --- sync :: Async a -> IO a- sync (AsyncR event arr) = after event stream >> return arr--instance ArrayElt e => M.Marshalable PTX (ArrayData e) where- marshal' ptx _ adata = do- let marshalP :: forall e' a. (ArrayElt e', ArrayPtrs e' ~ Ptr a, Typeable e', Typeable a, Storable a)- => ArrayData e'- -> IO (DList CUDA.FunParam)- marshalP ad =- fmap (DL.singleton . CUDA.VArg)- (unsafeGetDevicePtr ptx ad :: IO (CUDA.DevicePtr a))-- marshalR :: ArrayEltR e' -> ArrayData e' -> IO (DList CUDA.FunParam)- marshalR ArrayEltRunit _ = return DL.empty- marshalR (ArrayEltRpair aeR1 aeR2) ad =- return DL.append `ap` marshalR aeR1 (fstArrayData ad)- `ap` marshalR aeR2 (sndArrayData ad)- marshalR ArrayEltRint ad = marshalP ad- marshalR ArrayEltRint8 ad = marshalP ad- marshalR ArrayEltRint16 ad = marshalP ad- marshalR ArrayEltRint32 ad = marshalP ad- marshalR ArrayEltRint64 ad = marshalP ad- marshalR ArrayEltRword ad = marshalP ad- marshalR ArrayEltRword8 ad = marshalP ad- marshalR ArrayEltRword16 ad = marshalP ad- marshalR ArrayEltRword32 ad = marshalP ad- marshalR ArrayEltRword64 ad = marshalP ad- marshalR ArrayEltRfloat ad = marshalP ad- marshalR ArrayEltRdouble ad = marshalP ad- marshalR ArrayEltRchar ad = marshalP ad- marshalR ArrayEltRcshort ad = marshalP ad- marshalR ArrayEltRcushort ad = marshalP ad- marshalR ArrayEltRcint ad = marshalP ad- marshalR ArrayEltRcuint ad = marshalP ad- marshalR ArrayEltRclong ad = marshalP ad- marshalR ArrayEltRculong ad = marshalP ad- marshalR ArrayEltRcllong ad = marshalP ad- marshalR ArrayEltRcullong ad = marshalP ad- marshalR ArrayEltRcchar ad = marshalP ad- marshalR ArrayEltRcschar ad = marshalP ad- marshalR ArrayEltRcuchar ad = marshalP ad- marshalR ArrayEltRcfloat ad = marshalP ad- marshalR ArrayEltRcdouble ad = marshalP ad- marshalR ArrayEltRbool ad = marshalP ad-- marshalR arrayElt adata----- TODO FIXME !!!------ We will probably need to change marshal to be a bracketed function. We may--- also want to reconsider whether to continue to restrict it to IO.----unsafeGetDevicePtr- :: (ArrayElt e, ArrayPtrs e ~ Ptr a, Typeable e, Typeable a, Storable a)- => PTX- -> ArrayData e- -> IO (CUDA.DevicePtr a)-unsafeGetDevicePtr !ptx !ad =- evalPTX ptx $ Prim.withDevicePtr ad (\p -> return (Nothing,p))-
− Data/Array/Accelerate/LLVM/PTX/Execute/Stream.hs
@@ -1,181 +0,0 @@-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE MagicHash #-}-{-# LANGUAGE RecordWildCards #-}--- |--- Module : Data.Array.Accelerate.LLVM.PTX.Execute.Stream--- Copyright : [2014..2017] Trevor L. McDonell--- [2014..2014] Vinod Grover (NVIDIA Corporation)--- License : BSD3------ Maintainer : Trevor L. McDonell <tmcdonell@cse.unsw.edu.au>--- Stability : experimental--- Portability : non-portable (GHC extensions)-----module Data.Array.Accelerate.LLVM.PTX.Execute.Stream (-- Reservoir, new,- Stream, create, destroy, streaming,--) where---- accelerate-import Data.Array.Accelerate.Lifetime-import qualified Data.Array.Accelerate.Array.Remote.LRU as Remote--import Data.Array.Accelerate.LLVM.PTX.Array.Remote ( )-import Data.Array.Accelerate.LLVM.PTX.Execute.Event ( Event )-import Data.Array.Accelerate.LLVM.PTX.Target ( PTX(..) )-import Data.Array.Accelerate.LLVM.State-import qualified Data.Array.Accelerate.LLVM.PTX.Debug as Debug-import qualified Data.Array.Accelerate.LLVM.PTX.Execute.Event as Event-import Data.Array.Accelerate.LLVM.PTX.Execute.Stream.Reservoir as RSV---- cuda-import Foreign.CUDA.Driver.Error-import qualified Foreign.CUDA.Driver.Stream as Stream---- standard library-import Control.Exception-import Control.Monad.State----- | A 'Stream' represents an independent sequence of computations executed on--- the GPU. Operations in different streams may be executed concurrently with--- each other, but operations in the same stream can never overlap.--- 'Data.Array.Accelerate.LLVM.PTX.Execute.Event.Event's can be used for--- efficient cross-stream synchronisation.----type Stream = Lifetime Stream.Stream----- Executing operations in streams--- ----------------------------------- | Execute an operation in a unique execution stream. The (asynchronous)--- result is passed to a second operation together with an event that will be--- signalled once the operation is complete. The stream and event are released--- after the second operation completes.----{-# INLINEABLE streaming #-}-streaming- :: (Stream -> LLVM PTX a)- -> (Event -> a -> LLVM PTX b)- -> LLVM PTX b-streaming !action !after = do- PTX{..} <- gets llvmTarget- stream <- create- first <- action stream- end <- Event.waypoint stream- final <- after end first- liftIO $ do- destroy stream- Event.destroy end- return final----- Primitive operations--- ----------------------{----- | Delete all execution streams from the reservoir----{-# INLINEABLE flush #-}-flush :: Context -> Reservoir -> IO ()-flush !Context{..} !ref = do- mc <- deRefWeak weakContext- case mc of- Nothing -> message "delete reservoir/dead context"- Just ctx -> do- message "flush reservoir"- old <- swapMVar ref Seq.empty- bracket_ (CUDA.push ctx) CUDA.pop $ Seq.mapM_ Stream.destroy old---}----- | Create a CUDA execution stream. If an inactive stream is available for use,--- use that, otherwise generate a fresh stream.------ Note: [Finalising execution streams]------ We don't actually ensure that the stream has executed all of its operations--- to completion before attempting to return it to the reservoir for reuse.--- Doing so increases overhead of the LLVM RTS due to 'forkIO', and consumes CPU--- time as 'Stream.block' busy-waits for the stream to complete. It is quicker--- to optimistically return the streams to the end of the reservoir immediately,--- and just check whether the stream is done before reusing it.------ > void . forkIO $ do--- > Stream.block stream--- > modifyMVar_ ref $ \rsv -> return (rsv Seq.|> stream)----{-# INLINEABLE create #-}-create :: LLVM PTX Stream-create = do- PTX{..} <- gets llvmTarget- s <- create'- stream <- liftIO $ newLifetime s- liftIO $ addFinalizer stream (RSV.insert ptxStreamReservoir s)- return stream--create' :: LLVM PTX Stream.Stream-create' = do- PTX{..} <- gets llvmTarget- ms <- attempt "create/reservoir" (liftIO $ RSV.malloc ptxStreamReservoir)- `orElse`- attempt "create/new" (liftIO . catchOOM $ Stream.create [])- `orElse` do- Remote.reclaim ptxMemoryTable- liftIO $ do- message "create/new: failed (purging)"- catchOOM $ Stream.create []- case ms of- Just s -> return s- Nothing -> liftIO $ do- message "create/new: failed (non-recoverable)"- throwIO (ExitCode OutOfMemory)-- where- catchOOM :: IO a -> IO (Maybe a)- catchOOM it =- liftM Just it `catch` \e -> case e of- ExitCode OutOfMemory -> return Nothing- _ -> throwIO e-- attempt :: MonadIO m => String -> m (Maybe a) -> m (Maybe a)- attempt msg ea = do- ma <- ea- case ma of- Nothing -> return Nothing- Just a -> do liftIO (message msg)- return (Just a)-- orElse :: MonadIO m => m (Maybe a) -> m (Maybe a) -> m (Maybe a)- orElse ea eb = do- ma <- ea- case ma of- Just a -> return (Just a)- Nothing -> eb----- | Merge a stream back into the reservoir. This must only be done once all--- pending operations in the stream have completed.----{-# INLINEABLE destroy #-}-destroy :: Stream -> IO ()-destroy = finalize----- Debug--- -------{-# INLINE trace #-}-trace :: String -> IO a -> IO a-trace msg next = do- Debug.traceIO Debug.dump_sched ("stream: " ++ msg)- next--{-# INLINE message #-}-message :: String -> IO ()-message s = s `trace` return ()-
− Data/Array/Accelerate/LLVM/PTX/Execute/Stream.hs-boot
@@ -1,32 +0,0 @@--- |--- Module : Data.Array.Accelerate.LLVM.PTX.Execute.Stream-boot--- Copyright : [2016..2017] Trevor L. McDonell--- License : BSD3------ Maintainer : Trevor L. McDonell <tmcdonell@cse.unsw.edu.au>--- Stability : experimental--- Portability : non-portable (GHC extensions)-----module Data.Array.Accelerate.LLVM.PTX.Execute.Stream (-- Stream,- streaming,--) where--import Data.Array.Accelerate.Lifetime ( Lifetime )-import Data.Array.Accelerate.LLVM.State ( LLVM )-import Data.Array.Accelerate.LLVM.PTX.Target ( PTX )-import {-# SOURCE #-} Data.Array.Accelerate.LLVM.PTX.Execute.Event--import qualified Foreign.CUDA.Driver.Stream as Stream---type Stream = Lifetime Stream.Stream--streaming- :: (Stream -> LLVM PTX a)- -> (Event -> a -> LLVM PTX b)- -> LLVM PTX b-
− Data/Array/Accelerate/LLVM/PTX/Execute/Stream/Reservoir.hs
@@ -1,102 +0,0 @@-{-# LANGUAGE BangPatterns #-}--- |--- Module : Data.Array.Accelerate.LLVM.PTX.Execute.Stream.Reservoir--- Copyright : [2016..2017] Trevor L. McDonell--- License : BSD3------ Maintainer : Trevor L. McDonell <tmcdonell@cse.unsw.edu.au>--- Stability : experimental--- Portability : non-portable (GHC extensions)-----module Data.Array.Accelerate.LLVM.PTX.Execute.Stream.Reservoir (-- Reservoir,- new, malloc, insert,--) where--import Data.Array.Accelerate.LLVM.PTX.Context ( Context )-import qualified Data.Array.Accelerate.LLVM.PTX.Debug as Debug--import Control.Concurrent.MVar-import Data.Sequence ( Seq )-import qualified Data.Sequence as Seq-import qualified Foreign.CUDA.Driver.Stream as Stream----- | The reservoir is a place to store CUDA execution streams that are currently--- inactive. When a new stream is requested one is provided from the reservoir--- if available, otherwise a fresh execution stream is created.----type Reservoir = MVar (Seq Stream.Stream)----- | Generate a new empty reservoir. It is not necessary to pre-populate it with--- any streams because stream creation does not cause a device synchronisation.------ Additionally, we do not need to finalise any of the streams. A reservoir is--- tied to a specific execution context, so when the reservoir dies it is--- because the PTX state and contained CUDA context have died, so there is--- nothing more to do.----{-# INLINEABLE new #-}-new :: Context -> IO Reservoir-new _ctx = newMVar Seq.empty----- | Retrieve an execution stream from the reservoir, if one is available.------ Since we put streams back onto the reservoir once we have finished adding--- work to them, not once they have completed execution of the tasks, we must--- check for one which has actually completed.------ See note: [Finalising execution streams]----{-# INLINEABLE malloc #-}-malloc :: Reservoir -> IO (Maybe Stream.Stream)-malloc !ref =- modifyMVar ref (search Seq.empty)- where- -- scan through the streams in the reservoir looking for the first inactive- -- one. Optimistically adding the streams to the end of the reservoir as- -- soon as we stop assigning new work to them (c.f. async), and just- -- checking they have completed before reusing them, is quicker than having- -- a finaliser thread block until completion before retiring them.- --- search !acc !rsv =- case Seq.viewl rsv of- Seq.EmptyL -> return (acc, Nothing)- s Seq.:< ss -> do- done <- Stream.finished s- case done of- True -> return (acc Seq.>< ss, Just s)- False -> search (acc Seq.|> s) ss----- | Add a stream to the reservoir----{-# INLINEABLE insert #-}-insert :: Reservoir -> Stream.Stream -> IO ()-insert !ref !stream = do- message ("stash stream " ++ showStream stream)- modifyMVar_ ref $ \rsv -> return (rsv Seq.|> stream)----- Debug--- -------{-# INLINE trace #-}-trace :: String -> IO a -> IO a-trace msg next = do- Debug.traceIO Debug.dump_sched ("stream: " ++ msg)- next--{-# INLINE message #-}-message :: String -> IO ()-message s = s `trace` return ()--{-# INLINE showStream #-}-showStream :: Stream.Stream -> String-showStream (Stream.Stream s) = show s-
− Data/Array/Accelerate/LLVM/PTX/Foreign.hs
@@ -1,81 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE StandaloneDeriving #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}--- |--- Module : Data.Array.Accelerate.LLVM.PTX.Foreign--- Copyright : [2016..2017] Trevor L. McDonell--- License : BSD3------ Maintainer : Trevor L. McDonell <tmcdonell@cse.unsw.edu.au>--- Stability : experimental--- Portability : non-portable (GHC extensions)-----module Data.Array.Accelerate.LLVM.PTX.Foreign (-- -- Foreign functions- ForeignAcc(..),- ForeignExp(..),-- -- useful re-exports- LLVM,- PTX(..),- Context(..),- liftIO,- withDevicePtr,- module Data.Array.Accelerate.LLVM.PTX.Array.Data,- module Data.Array.Accelerate.LLVM.PTX.Execute.Async,--) where--import qualified Data.Array.Accelerate.Array.Sugar as S--import Data.Array.Accelerate.LLVM.State-import Data.Array.Accelerate.LLVM.CodeGen.Sugar--import Data.Array.Accelerate.LLVM.Foreign-import Data.Array.Accelerate.LLVM.PTX.Array.Data-import Data.Array.Accelerate.LLVM.PTX.Array.Prim-import Data.Array.Accelerate.LLVM.PTX.Context-import Data.Array.Accelerate.LLVM.PTX.Execute.Async-import Data.Array.Accelerate.LLVM.PTX.Target--import Control.Monad.State-import Data.Typeable---instance Foreign PTX where- foreignAcc _ (ff :: asm (a -> b))- | Just (ForeignAcc _ asm :: ForeignAcc (a -> b)) <- cast ff = Just asm- | otherwise = Nothing-- foreignExp _ (ff :: asm (x -> y))- | Just (ForeignExp _ asm :: ForeignExp (x -> y)) <- cast ff = Just asm- | otherwise = Nothing--instance S.Foreign ForeignAcc where- strForeign (ForeignAcc s _) = s--instance S.Foreign ForeignExp where- strForeign (ForeignExp s _) = s----- Foreign functions in the PTX backend.----data ForeignAcc f where- ForeignAcc :: String- -> (Stream -> a -> LLVM PTX b)- -> ForeignAcc (a -> b)---- Foreign expressions in the PTX backend.----data ForeignExp f where- ForeignExp :: String- -> IRFun1 PTX () (x -> y)- -> ForeignExp (x -> y)--deriving instance Typeable ForeignAcc-deriving instance Typeable ForeignExp-
− Data/Array/Accelerate/LLVM/PTX/Link.hs
@@ -1,153 +0,0 @@-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE TypeFamilies #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}--- |--- Module : Data.Array.Accelerate.LLVM.PTX.Link--- Copyright : [2017] Trevor L. McDonell--- License : BSD3------ Maintainer : Trevor L. McDonell <tmcdonell@cse.unsw.edu.au>--- Stability : experimental--- Portability : non-portable (GHC extensions)-----module Data.Array.Accelerate.LLVM.PTX.Link (-- module Data.Array.Accelerate.LLVM.Link,- ExecutableR(..), FunctionTable(..), Kernel(..), ObjectCode,- withExecutable,- linkFunctionQ,--) where--import Data.Array.Accelerate.Lifetime--import Data.Array.Accelerate.LLVM.Link-import Data.Array.Accelerate.LLVM.State--import Data.Array.Accelerate.LLVM.PTX.Analysis.Launch-import Data.Array.Accelerate.LLVM.PTX.Compile-import Data.Array.Accelerate.LLVM.PTX.Context-import Data.Array.Accelerate.LLVM.PTX.Link.Cache-import Data.Array.Accelerate.LLVM.PTX.Link.Object-import Data.Array.Accelerate.LLVM.PTX.Target-import qualified Data.Array.Accelerate.LLVM.PTX.Debug as Debug---- cuda-import qualified Foreign.CUDA.Analysis as CUDA-import qualified Foreign.CUDA.Driver as CUDA---- standard library-import Control.Monad.State-import Data.ByteString.Short.Char8 ( ShortByteString, unpack )-import Foreign.Ptr-import Language.Haskell.TH-import Text.Printf ( printf )-import qualified Data.ByteString.Unsafe as B-import Prelude as P hiding ( lookup )---instance Link PTX where- data ExecutableR PTX = PTXR { ptxExecutable :: {-# UNPACK #-} !(Lifetime FunctionTable)- }- linkForTarget = link----- | Load the generated object code into the current CUDA context.----link :: ObjectR PTX -> LLVM PTX (ExecutableR PTX)-link (ObjectR uid cfg obj) = do- target <- gets llvmTarget- cache <- gets ptxKernelTable- funs <- liftIO $ dlsym uid cache $ do- -- Load the SASS object code into the current CUDA context- jit <- B.unsafeUseAsCString obj $ \p -> CUDA.loadDataFromPtrEx (castPtr p) []- let mdl = CUDA.jitModule jit-- -- Extract the kernel functions- nm <- FunctionTable `fmap` mapM (uncurry (linkFunction mdl)) cfg- oc <- newLifetime mdl-- -- Finalise the module by unloading it from the CUDA context- addFinalizer oc $ do- Debug.traceIO Debug.dump_gc ("gc: unload module: " ++ show nm)- withContext (ptxContext target) (CUDA.unload mdl)-- return (nm, oc)- --- return $! PTXR funs----- | Extract the named function from the module and package into a Kernel--- object, which includes meta-information on resource usage.------ If we are in debug mode, print statistics on kernel resource usage, etc.----linkFunction- :: CUDA.Module -- the compiled module- -> ShortByteString -- __global__ entry function name- -> LaunchConfig -- launch configuration for this global function- -> IO Kernel-linkFunction mdl name configure =- fst `fmap` linkFunctionQ mdl name configure--linkFunctionQ- :: CUDA.Module- -> ShortByteString- -> LaunchConfig- -> IO (Kernel, Q (TExp (Int -> Int)))-linkFunctionQ mdl name configure = do- f <- CUDA.getFun mdl (unpack name)- regs <- CUDA.requires f CUDA.NumRegs- ssmem <- CUDA.requires f CUDA.SharedSizeBytes- cmem <- CUDA.requires f CUDA.ConstSizeBytes- lmem <- CUDA.requires f CUDA.LocalSizeBytes- maxt <- CUDA.requires f CUDA.MaxKernelThreadsPerBlock-- let- (occ, cta, grid, dsmem, gridQ) = configure maxt regs ssmem-- msg1, msg2 :: String- msg1 = printf "kernel function '%s' used %d registers, %d bytes smem, %d bytes lmem, %d bytes cmem"- (unpack name) regs (ssmem + dsmem) lmem cmem-- msg2 = printf "multiprocessor occupancy %.1f %% : %d threads over %d warps in %d blocks"- (CUDA.occupancy100 occ)- (CUDA.activeThreads occ)- (CUDA.activeWarps occ)- (CUDA.activeThreadBlocks occ)-- Debug.traceIO Debug.dump_cc (printf "cc: %s\n ... %s" msg1 msg2)- return (Kernel name f dsmem cta grid, gridQ)----- | Execute some operation with the supplied executable functions----withExecutable :: ExecutableR PTX -> (FunctionTable -> LLVM PTX b) -> LLVM PTX b-withExecutable PTXR{..} f = do- r <- f (unsafeGetValue ptxExecutable)- liftIO $ touchLifetime ptxExecutable- return r---{----- | Extract the names of the function definitions from the module.------ Note: [Extracting global function names]------ It is important to run this on the module given to us by code generation.--- After combining modules with 'libdevice', extra function definitions,--- corresponding to basic maths operations, will be added to the module. These--- functions will not be callable as __global__ functions.------ The list of names will be exported in the order that they appear in the--- module.----globalFunctions :: [Definition] -> [String]-globalFunctions defs =- [ n | GlobalDefinition Function{..} <- defs- , not (null basicBlocks)- , let Name n = name- ]---}-
− Data/Array/Accelerate/LLVM/PTX/Link/Cache.hs
@@ -1,22 +0,0 @@--- |--- Module : Data.Array.Accelerate.LLVM.PTX.Link.Cache--- Copyright : [2017] Trevor L. McDonell--- License : BSD3------ Maintainer : Trevor L. McDonell <tmcdonell@cse.unsw.edu.au>--- Stability : experimental--- Portability : non-portable (GHC extensions)-----module Data.Array.Accelerate.LLVM.PTX.Link.Cache (-- KernelTable,- LC.new, LC.dlsym,--) where--import Data.Array.Accelerate.LLVM.PTX.Link.Object-import qualified Data.Array.Accelerate.LLVM.Link.Cache as LC--type KernelTable = LC.LinkCache FunctionTable ObjectCode-
− Data/Array/Accelerate/LLVM/PTX/Link/Object.hs
@@ -1,41 +0,0 @@--- |--- Module : Data.Array.Accelerate.LLVM.PTX.Link.Object--- Copyright : [2017] Trevor L. McDonell--- License : BSD3------ Maintainer : Trevor L. McDonell <tmcdonell@cse.unsw.edu.au>--- Stability : experimental--- Portability : non-portable (GHC extensions)-----module Data.Array.Accelerate.LLVM.PTX.Link.Object- where--import Data.Array.Accelerate.Lifetime-import Data.ByteString.Short.Char8 ( ShortByteString, unpack )-import Data.List-import qualified Foreign.CUDA.Driver as CUDA----- | The kernel function table is a list of the kernels implemented by a given--- CUDA device module----data FunctionTable = FunctionTable { functionTable :: [Kernel] }-data Kernel = Kernel- { kernelName :: {-# UNPACK #-} !ShortByteString- , kernelFun :: {-# UNPACK #-} !CUDA.Fun- , kernelSharedMemBytes :: {-# UNPACK #-} !Int- , kernelThreadBlockSize :: {-# UNPACK #-} !Int- , kernelThreadBlocks :: (Int -> Int)- }--instance Show FunctionTable where- showsPrec _ f- = showString "<<"- . showString (intercalate "," [ unpack (kernelName k) | k <- functionTable f ])- . showString ">>"---- | Object code consists of executable code in the device address space----type ObjectCode = Lifetime CUDA.Module-
− Data/Array/Accelerate/LLVM/PTX/State.hs
@@ -1,111 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE TemplateHaskell #-}--- |--- Module : Data.Array.Accelerate.LLVM.PTX.State--- Copyright : [2014..2017] Trevor L. McDonell--- [2014..2014] Vinod Grover (NVIDIA Corporation)--- License : BSD3------ Maintainer : Trevor L. McDonell <tmcdonell@cse.unsw.edu.au>--- Stability : experimental--- Portability : non-portable (GHC extensions)-----module Data.Array.Accelerate.LLVM.PTX.State (-- evalPTX,- createTargetForDevice, createTargetFromContext, defaultTarget--) where---- accelerate-import Data.Array.Accelerate.Error--import Data.Array.Accelerate.LLVM.PTX.Analysis.Device-import Data.Array.Accelerate.LLVM.PTX.Target-import Data.Array.Accelerate.LLVM.State-import qualified Data.Array.Accelerate.LLVM.PTX.Array.Table as MT-import qualified Data.Array.Accelerate.LLVM.PTX.Context as CT-import qualified Data.Array.Accelerate.LLVM.PTX.Debug as Debug-import qualified Data.Array.Accelerate.LLVM.PTX.Execute.Stream as ST-import qualified Data.Array.Accelerate.LLVM.PTX.Link.Cache as LC--import Data.Range.Range ( Range(..) )-import Control.Parallel.Meta ( Executable(..) )---- standard library-import Control.Concurrent ( runInBoundThread )-import Control.Exception ( catch )-import System.IO.Unsafe ( unsafePerformIO )-import Foreign.CUDA.Driver.Error-import qualified Foreign.CUDA.Driver as CUDA-import qualified Foreign.CUDA.Driver.Context as Context----- | Execute a PTX computation----evalPTX :: PTX -> LLVM PTX a -> IO a-evalPTX ptx acc =- runInBoundThread (CT.withContext (ptxContext ptx) (evalLLVM ptx acc))- `catch`- \e -> $internalError "unhandled" (show (e :: CUDAException))----- | Create a new PTX execution target for the given device----createTargetForDevice- :: CUDA.Device- -> CUDA.DeviceProperties- -> [CUDA.ContextFlag]- -> IO PTX-createTargetForDevice dev prp flags = do- ctx <- CT.new dev prp flags- mt <- MT.new ctx- lc <- LC.new- st <- ST.new ctx- return $! PTX ctx mt lc st simpleIO----- | Create a PTX execute target for the given device context----createTargetFromContext- :: CUDA.Context- -> IO PTX-createTargetFromContext ctx' = do- dev <- Context.device- prp <- CUDA.props dev- ctx <- CT.raw dev prp ctx'- mt <- MT.new ctx- lc <- LC.new- st <- ST.new ctx- return $! PTX ctx mt lc st simpleIO---{-# INLINE simpleIO #-}-simpleIO :: Executable-simpleIO = Executable $ \_name _ppt range action ->- case range of- Empty -> return ()- IE u v -> action u v 0----- Top-level mutable state--- ----------------------------- It is important to keep some information alive for the entire run of the--- program, not just a single execution. These tokens use 'unsafePerformIO' to--- ensure they are executed only once, and reused for subsequent invocations.------- | Select and initialise the default CUDA device, and create a new target--- context. The device is selected based on compute capability and estimated--- maximum throughput.----{-# NOINLINE defaultTarget #-}-defaultTarget :: PTX-defaultTarget = unsafePerformIO $ do- Debug.traceIO Debug.dump_gc "gc: initialise default PTX target"- CUDA.initialise []- (dev,prp) <- selectBestDevice- createTargetForDevice dev prp [CUDA.SchedAuto]-
− Data/Array/Accelerate/LLVM/PTX/Target.hs
@@ -1,182 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE EmptyDataDecls #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE TypeFamilies #-}--- |--- Module : Data.Array.Accelerate.LLVM.PTX.Target--- Copyright : [2014..2017] Trevor L. McDonell--- [2014..2014] Vinod Grover (NVIDIA Corporation)--- License : BSD3------ Maintainer : Trevor L. McDonell <tmcdonell@cse.unsw.edu.au>--- Stability : experimental--- Portability : non-portable (GHC extensions)-----module Data.Array.Accelerate.LLVM.PTX.Target (-- module Data.Array.Accelerate.LLVM.Target,- module Data.Array.Accelerate.LLVM.PTX.Target,--) where---- llvm-general-import LLVM.AST.AddrSpace-import LLVM.AST.DataLayout-import LLVM.Target hiding ( Target )-import qualified LLVM.Target as LLVM-import qualified LLVM.Relocation as R-import qualified LLVM.CodeModel as CM-import qualified LLVM.CodeGenOpt as CGO---- accelerate-import Data.Array.Accelerate.Error--import Data.Array.Accelerate.LLVM.Target-import Data.Array.Accelerate.LLVM.Util--import Control.Parallel.Meta ( Executable )-import Data.Array.Accelerate.LLVM.PTX.Array.Table ( MemoryTable )-import Data.Array.Accelerate.LLVM.PTX.Context ( Context, deviceProperties )-import Data.Array.Accelerate.LLVM.PTX.Execute.Stream.Reservoir ( Reservoir )-import Data.Array.Accelerate.LLVM.PTX.Link.Cache ( KernelTable )---- CUDA-import qualified Foreign.CUDA.Driver as CUDA---- standard library-import Data.ByteString ( ByteString )-import Data.ByteString.Short ( ShortByteString )-import Data.String-import System.IO.Unsafe-import Text.Printf-import qualified Data.Map as Map-import qualified Data.Set as Set----- | The PTX execution target for NVIDIA GPUs.------ The execution target carries state specific for the current execution--- context. The data here --- device memory and execution streams --- are--- implicitly tied to this CUDA execution context.------ Don't store anything here that is independent of the context, for example--- state related to [persistent] kernel caching should _not_ go here.----data PTX = PTX {- ptxContext :: {-# UNPACK #-} !Context- , ptxMemoryTable :: {-# UNPACK #-} !MemoryTable- , ptxKernelTable :: {-# UNPACK #-} !KernelTable- , ptxStreamReservoir :: {-# UNPACK #-} !Reservoir- , fillP :: {-# UNPACK #-} !Executable- }--instance Target PTX where- targetTriple _ = Just ptxTargetTriple-#if ACCELERATE_USE_NVVM- targetDataLayout _ = Nothing -- see note: [NVVM and target data layout]-#else- targetDataLayout _ = Just ptxDataLayout-#endif----- | Extract the properties of the device the current PTX execution state is--- executing on.----ptxDeviceProperties :: PTX -> CUDA.DeviceProperties-ptxDeviceProperties = deviceProperties . ptxContext----- | A description of the various data layout properties that may be used during--- optimisation. For CUDA the following data layouts are supported:------ 32-bit:--- e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v16:16:16-v32:32:32-v64:64:64-v128:128:128-n16:32:64------ 64-bit:--- e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v16:16:16-v32:32:32-v64:64:64-v128:128:128-n16:32:64------ Thus, only the size of the pointer layout changes depending on the host--- architecture.----ptxDataLayout :: DataLayout-ptxDataLayout = DataLayout- { endianness = LittleEndian- , mangling = Nothing- , aggregateLayout = AlignmentInfo 0 64- , stackAlignment = Nothing- , pointerLayouts = Map.fromList- [ (AddrSpace 0, (wordSize, AlignmentInfo wordSize wordSize)) ]- , typeLayouts = Map.fromList $- [ ((IntegerAlign, 1), AlignmentInfo 8 8) ] ++- [ ((IntegerAlign, i), AlignmentInfo i i) | i <- [8,16,32,64]] ++- [ ((VectorAlign, v), AlignmentInfo v v) | v <- [16,32,64,128]] ++- [ ((FloatAlign, f), AlignmentInfo f f) | f <- [32,64] ]- , nativeSizes = Just $ Set.fromList [ 16,32,64 ]- }- where- wordSize = bitSize (undefined :: Int)----- | String that describes the target host.----ptxTargetTriple :: ShortByteString-ptxTargetTriple =- case bitSize (undefined::Int) of- 32 -> "nvptx-nvidia-cuda"- 64 -> "nvptx64-nvidia-cuda"- _ -> $internalError "ptxTargetTriple" "I don't know what architecture I am"----- | Bracket creation and destruction of the NVVM TargetMachine.----withPTXTargetMachine- :: CUDA.DeviceProperties- -> (TargetMachine -> IO a)- -> IO a-withPTXTargetMachine dev go =- let CUDA.Compute m n = CUDA.computeCapability dev- isa = CPUFeature (ptxISAVersion m n)- sm = fromString (printf "sm_%d%d" m n)- in- withTargetOptions $ \options -> do- withTargetMachine- ptxTarget- ptxTargetTriple- sm- (Map.singleton isa True) -- CPU features- options -- target options- R.Default -- relocation model- CM.Default -- code model- CGO.Default -- optimisation level- go---- Some libdevice functions require at least ptx40, even though devices at--- that compute capability also accept older ISA versions.------ https://github.com/llvm-mirror/llvm/blob/master/lib/Target/NVPTX/NVPTX.td#L72----ptxISAVersion :: Int -> Int -> ByteString-ptxISAVersion 2 _ = "ptx40"-ptxISAVersion 3 7 = "ptx41"-ptxISAVersion 3 _ = "ptx40"-ptxISAVersion 5 0 = "ptx40"-ptxISAVersion 5 2 = "ptx41"-ptxISAVersion 5 3 = "ptx42"-ptxISAVersion 6 _ = "ptx50"-ptxISAVersion 7 _ = "ptx60"-ptxISAVersion _ _ = "ptx40"----- | The NVPTX target for this host.------ The top-level 'unsafePerformIO' is so that 'initializeAllTargets' is run once--- per program execution (although that might not be necessary?)----{-# NOINLINE ptxTarget #-}-ptxTarget :: LLVM.Target-ptxTarget = unsafePerformIO $ do- initializeAllTargets- fst `fmap` lookupTarget Nothing ptxTargetTriple-
LICENSE view
@@ -1,4 +1,4 @@-Copyright (c) [2014..2017] The Accelerate Team. All rights reserved.+Copyright (c) [2014..2020] The Accelerate Team. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
README.md view
@@ -1,14 +1,18 @@-An LLVM backend for the Accelerate Array Language-=================================================+<div align="center">+<img width="450" src="https://github.com/AccelerateHS/accelerate/raw/master/images/accelerate-logo-text-v.png?raw=true" alt="henlo, my name is Theia"/> -[](https://travis-ci.org/AccelerateHS/accelerate-llvm)+# LLVM backends for the Accelerate array language++[](https://github.com/AccelerateHS/accelerate-llvm/actions/workflows/ci.yml)+[](https://gitter.im/AccelerateHS/Lobby) [](https://hackage.haskell.org/package/accelerate-llvm)-[](https://hub.docker.com/r/tmcdonell/accelerate-llvm/)-[](https://microbadger.com/images/tmcdonell/accelerate-llvm) +</div>+ This package compiles Accelerate code to LLVM IR, and executes that code on-multicore CPUs as well as NVIDIA GPUs. This avoids the need to go through `nvcc`-or `clang`. For details on Accelerate, refer to the [main repository][GitHub].+multicore CPUs as well as NVIDIA GPUs. This avoids the need to go through+`nvcc` or write C++ code. For details on Accelerate, refer to the [main+repository][GitHub]. We love all kinds of contributions, so feel free to open issues for missing features as well as report (or fix!) bugs on the [issue tracker][Issues].@@ -18,171 +22,143 @@ * [Dependencies](#dependencies)- * [Docker](#docker)- * [Installing LLVM](#installing-llvm)- * [Homebrew](#homebrew)+ * [macOS](#macos) * [Debian/Ubuntu](#debianubuntu)- * [Building from source](#building-from-source)- * [Installing Accelerate-LLVM](#installing-accelerate-llvm)- * [libNVVM](#libNVVM)+ * [Arch Linux](#archlinux)+ * [Windows](#windows) Dependencies ------------ -Haskell dependencies are available from Hackage, but there are several external+Haskell dependencies are available from Hackage, but there are some external library dependencies that you will need to install as well: - * [`LLVM`](http://llvm.org)- * [`libFFI`](http://sourceware.org/libffi/) (if using the `accelerate-llvm-native` backend for multicore CPUs)- * [`CUDA`](https://developer.nvidia.com/cuda-downloads) (if using the `accelerate-llvm-ptx` backend for NVIDIA GPUs)---Docker---------A [docker](https://www.docker.com) container is provided with this package-preinstalled (via stack) at `/opt/accelerate-llvm`. Note that if you wish to use-the `accelerate-llvm-ptx` GPU backend, you will need to install the [NVIDIA-docker](https://github.com/NVIDIA/nvidia-docker) plugin; see that page for more-information.--```sh-$ docker run -it tmcdonell/accelerate-llvm-```---Installing LLVM------------------When installing LLVM, make sure that it includes the `libLLVM` shared library.-If you want to use the GPU targeting `accelerate-llvm-ptx` backend, make sure-you install (or build) LLVM with the 'nvptx' target.+- if using `accelerate-llvm-native` for multicore CPU:+ [`libFFI`](http://sourceware.org/libffi/)+- if using `accelerate-llvm-ptx` for GPU:+ [`CUDA`](https://developer.nvidia.com/cuda-downloads);+ [Note that not all versions of CUDA support all NVIDIA GPUs](https://en.wikipedia.org/wiki/CUDA#GPUs_supported)+- [`clang`](https://clang.llvm.org/) (if using `accelerate-llvm-ptx`: version+ 16 or higher, built with support for the `nvptx` backend). `accelerate-llvm`+ uses the command-line tool as a way to be compatible with many different LLVM+ versions, not to compile C code. (Accelerate passes LLVM IR to `clang`.) -## Homebrew+Below are some OS-specific instructions. If anything here is wrong or out of+date, please file an issue. -Example using [Homebrew](http://brew.sh) on macOS:+## macOS -```sh-$ brew install llvm-hs/homebrew-llvm/llvm-4.0-```+To get `libFFI`, run `brew install libffi`. `clang` is already provided with+macOS (you may need to `xcode-select --install`), and CUDA is not supported on+macOS. ## Debian/Ubuntu -For Debian/Ubuntu based Linux distributions, the LLVM.org website provides-binary distribution packages. Check [apt.llvm.org](http://apt.llvm.org) for-instructions for adding the correct package database for your OS version, and-then:--```sh-$ apt-get install llvm-4.0-dev-```--## Building from source--If your OS does not have an appropriate LLVM distribution available, you can also build from source. Detailed build instructions are available on the [LLVM.org website](http://releases.llvm.org/4.0.0/docs/CMake.html). Note that you will require at least [CMake 3.4.3](http://www.cmake.org/cmake/resources/software.html) and a recent C++ compiler; at least Clang 3.1, GCC 4.8, or Visual Studio 2015 (update 3).-- 1. Download and unpack the [LLVM-4.0 source code](http://releases.llvm.org/4.0.0/llvm-4.0.0.src.tar.xz). We'll refer to- the path that the source tree was unpacked to as `LLVM_SRC`. Only the main- LLVM source tree is required, but you can optionally add other components- such as the Clang compiler or Polly loop optimiser. See the [LLVM releases](http://releases.llvm.org/download.html#4.0.0)- page for the complete list.-- 2. Create a temporary build directory and `cd` into it, for example:- ```sh- $ mkdir /tmp/build- $ cd /tmp/build- ```-- 3. Execute the following to configure the build. Here `INSTALL_PREFIX` is- where LLVM is to be installed, for example `/usr/local` or- `$HOME/opt/llvm`:- ```sh- $ cmake $LLVM_SRC -DCMAKE_INSTALL_PREFIX=$INSTALL_PREFIX -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_ASSERTIONS=ON -DLLVM_BUILD_LLVM_DYLIB=ON -DLLVM_LINK_LLVM_DYLIB=ON- ```- See [options and variables](http://llvm.org/docs/CMake.html#options-and-variables)- for a list of additional build parameters you can specify.-- 4. Build and install:- ```sh- $ cmake --build .- $ cmake --build . --target install- ```-- 5. For macOS only, some additional steps are useful to work around issues related- to [System Integrity Protection](https://en.wikipedia.org/wiki/System_Integrity_Protection):- ```sh- cd $INSTALL_PREFIX/lib- ln -s libLLVM.dylib libLLVM-4.0.dylib- install_name_tool -id $PWD/libLTO.dylib libLTO.dylib- install_name_tool -id $PWD/libLLVM.dylib libLLVM.dylib- install_name_tool -change '@rpath/libLLVM.dylib' $PWD/libLLVM.dylib libLTO.dylib- ```+For `clang`:+- On Ubuntu 24.04 (noble) / Debian trixie or higher: `sudo apt install clang`.+- Otherwise, if you need only the CPU backend (`accelerate-llvm-native`):+ `sudo apt install clang` will give you an old version of `clang`, but the CPU+ backend is likely to work fine.+- If you are on an older distro and need the GPU backend+ (`accelerate-llvm-ptx`): `clang` version 16 or higher is required.+ Add the apt source from [apt.llvm.org](https://apt.llvm.org/). The neatest+ way to do this is to create a file `/etc/apt/sources.list.d/llvm.list` (the+ precise file name does not matter) and put in it, for Ubuntu (change "jammy"+ as appropriate): + deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy main+ deb-src http://apt.llvm.org/jammy/ llvm-toolchain-jammy main -Installing Accelerate-LLVM---------------------------+ or for Debian (change "bookworm" as appropriate): -Once the dependencies are installed, we are ready to install `accelerate-llvm`.+ deb http://apt.llvm.org/bookworm/ llvm-toolchain-bookworm main+ deb-src http://apt.llvm.org/bookworm/ llvm-toolchain-bookworm main -For example, installation using [`stack`](http://docs.haskellstack.org/en/stable/README.html)-just requires you to point it to the appropriate configuration file:-```sh-$ ln -s stack-8.0.yaml stack.yaml-$ stack setup-$ stack install-```+ and `sudo apt update; sudo apt install clang`. This gets you the latest+ version of `clang`; different sources are also available for specific+ versions (see [apt.llvm.org](https://apt.llvm.org)). -Note that the version of [`llvm-hs`](https://hackage.haskell.org/package/llvm-hs)-used must match the installed version of LLVM, which is currently 4.0.+To use the CPU backend (`accelerate-llvm-native`), install `libFFI` using+`sudo apt install libffi-dev`. +To use the GPU backend (`accelerate-llvm-ptx`), install CUDA from+[here](https://developer.nvidia.com/cuda-downloads?target_os=Linux)+("deb (network)" is smoother than the "deb (local)" option). -## libNVVM+## Arch Linux -The `accelerate-llvm-ptx` backend can optionally be compiled to generate GPU-code using the `libNVVM` library, rather than LLVM's inbuilt NVPTX code-generator. `libNVVM` is a closed-source library distributed as part of the-NVIDIA CUDA toolkit, and is what the `nvcc` compiler itself uses internally when-compiling CUDA C code.+Run `sudo pacman -S clang`. To use the CPU backend (`accelerate-llvm-native`),+additionally run `sudo pacman -S libffi`. To use the GPU backend+(`accelerate-llvm-ptx`), additionally run `sudo pacman -S cuda`. -Using `libNVVM` _may_ improve GPU performance compared to the code generator-built in to LLVM. One difficulty with using it however is that since `libNVVM`-is also based on LLVM, and typically lags LLVM by several releases, you must-install `accelerate-llvm` with a "compatible" version of LLVM, which will depend-on the version of the CUDA toolkit you have installed. The following table shows-some combinations:+## Windows -| | LLVM-3.3 | LLVM-3.4 | LLVM-3.5 | LLVM-3.8 | LLVM-3.9 | LLVM-4.0 |-|:------------:|:--------:|:--------:|:--------:|:--------:|:--------:|:--------:|-| **CUDA-7.0** | ⭕ | ❌ | | | | |-| **CUDA-7.5** | | ⭕ | ⭕ | ❌ | | |-| **CUDA-8.0** | | | ⭕ | ⭕ | ❌ | ❌ |+We recommend WSL2 (not WSL1, WSL2!) and following the Ubuntu instructions+above. The remainder of this text attemps to give you a working system on+Windows native. -Where ⭕ = Works, and ❌ = Does not work.+Install `clang`; you have two options:+1. Using+ [WinGet](https://learn.microsoft.com/en-us/windows/package-manager/winget/):+ `winget install LLVM.LLVM`+2. By downloading the installer directly (WinGet just runs the same installer)+ from [here](https://github.com/llvm/llvm-project/releases) (choose+ "LLVM-<version>-win64.exe" from the latest release; you may need to click+ "Show all 57 assets").+This will also give you `libFFI`. -Note that the above restrictions on CUDA and LLVM version exist _only_ if you-want to use the NVVM component. Otherwise, you should be free to use any-combination of CUDA and LLVM.+<details><summary>Optionally, add <code>clang</code> (and more) to your system path. Click to see how.</summary> -Also note that `accelerate-llvm-ptx` itself currently requires at least LLVM-3.5.+Accelerate should be able to find `clang` automatically even if you do not do+this. However, for easy access to `clang` and all other LLVM executables, add+`C:\Program Files\LLVM\bin` to the system path as follows:+1. Search for "environment variables" in the start menu+2. Click "Edit the system environment variables"+3. Click on "Environment Variables..."+4. Double-click on the user variable called "Path"+5. And add a new entry containing `C:\Program Files\LLVM\bin`. -Using `stack`, either edit the `stack.yaml` and add the following section:+Note that if you add an entry here manually, it is a good idea to clean it up+again if you uninstall LLVM/clang. (Leaving it there is not very harmful,+however.) -```yaml-flags:- accelerate-llvm-ptx:- nvvm: true-```+You may find that the LLVM/clang installer has already added the Path entry+automatically (it did not for us); if so, no need to add a second entry. -Or install using the following option on the command line:+——+</details> -```sh-$ stack install accelerate-llvm-ptx --flag accelerate-llvm-ptx:nvvm-```+You may additionally need the VS Build Tools, if you have not yet installed and+set up Visual Studio otherwise. You need this if `clang` complains that it is+`unable to find a Visual Studio installation; try running Clang from a developer command prompt`. -If installing via `cabal`:+1. If you already have the Visual Studio Installer on your system, open it and+ check if you already have Visual Studio (Community) installed. Note that+ this is completely unrelated to VS _Code_.+ - If you already have VS (Community): inside the Visual Studio Installer,+ click on "Modify" in the VS (Community) box. This should get you a screen+ with "workloads" you can select.+ - If you do not yet have VS (Community), install the VS Build Tools: go to+ https://visualstudio.microsoft.com/downloads, scroll down to "All+ Downloads", open "Tools for Visual Studio", and select "Build Tools for+ Visual Studio". If you run the installer, you should get a screen with+ "workloads" you can select.+2. Under the Workloads tab, choose the "Desktop development with C++" workload.+ If you want to save a bit of disk space (not much), keep only the following+ two options selected:+ - "MSVC v143 - VE 2022 C++ x64/x86 build tools (Latest)"+ - "Windows 11 SDK (…)" (choose the latest option). The attentive reader may+ note that the wizard also offers Clang; we recommend a separate Clang+ install for Accelerate because the one from VS somehow doesn’t seem to+ work properly with Accelerate. If you find out why, please let us know.+3. Install that. This takes a while. -```sh-$ cabal install accelerate-llvm-ptx -fnvvm-```+It turns out that having both Visual Studio and the Build Tools installed+results in Clang getting confused between the two (it appears that Visual+Studio is 64-bit (x64) and the Build Tools are 32-bit (x86)). If Clang+complains about the bit-ness of your system libraries, double-check that you+haven’t installed both simultaneously. +The GPU backend (`accelerate-llvm-ptx`) probably doesn't work on Windows; in+any case, it is untested.
− Setup.hs
@@ -1,2 +0,0 @@-import Distribution.Simple-main = defaultMain
accelerate-llvm-ptx.cabal view
@@ -1,108 +1,39 @@+cabal-version: 2.2+ name: accelerate-llvm-ptx-version: 1.1.0.1-cabal-version: >= 1.10-tested-with: GHC >= 7.10+version: 1.4.0.0+tested-with: GHC >= 9.4 build-type: Simple synopsis: Accelerate backend for NVIDIA GPUs description: This library implements a backend for the /Accelerate/ language which- generates LLVM-IR targeting CUDA capable GPUs. For further information,+ generates LLVM IR targeting CUDA capable GPUs. For further information, refer to the main <http://hackage.haskell.org/package/accelerate accelerate> package. . [/Dependencies/] . Haskell dependencies are available from Hackage. The following external- libraries are alse required:- .- * <http://llvm.org LLVM>- .- * <https://developer.nvidia.com/cuda-downloads CUDA>- .- [/Installing LLVM/]- .- /Homebrew/- .- Example using Homebrew on macOS:- .- > brew install llvm-hs/homebrew-llvm/llvm-5.0- .- /Debian & Ubuntu/- .- For Debian/Ubuntu based Linux distributions, the LLVM.org website provides- binary distribution packages. Check <http://apt.llvm.org apt.llvm.org> for- instructions for adding the correct package database for your OS version,- and then:- .- > apt-get install llvm-5.0-dev- .- /Building from source/- .- If your OS does not have an appropriate LLVM distribution available, you can- also build from source. Detailed build instructions are available on- <http://releases.llvm.org/5.0.0/docs/CMake.html LLVM.org>. Make sure to- include the cmake build options- @-DLLVM_BUILD_LLVM_DYLIB=ON -DLLVM_LINK_LLVM_DYLIB=ON@ so that the @libLLVM@- shared library will be built. Also ensure that the @LLVM_TARGETS_TO_BUILD@- option includes the @NVPTX@ target (if not specified all targets are built).- .- [/Installing accelerate-llvm/]- .- To use @accelerate-llvm@ it is important that the @llvm-hs@ package is- installed against the @libLLVM@ shared library, rather than statically- linked, so that we can use LLVM from GHCi and Template Haskell. This is the- default configuration, but you can also enforce this explicitly by adding- the following to your @stack.yaml@ file:- .- > flags:- > llvm-hs:- > shared-llvm: true- .- Or by specifying the @shared-llvm@ flag to cabal:+ dependencies are also required: .- > cabal install llvm-hs -fshared-llvm+ * <https://clang.llvm.org/ clang> (not used to compile C code, but to compile generated LLVM IR via a mostly LLVM-version-independent interface)+ * <https://developer.nvidia.com/cuda-downloads CUDA> .+ For installation instructions, see the <https://github.com/AccelerateHS/accelerate-llvm#readme README>. -license: BSD3+license: BSD-3-Clause license-file: LICENSE author: Trevor L. McDonell-maintainer: Trevor L. McDonell <tmcdonell@cse.unsw.edu.au>+maintainer: Trevor L. McDonell <trevor.mcdonell@gmail.com> bug-reports: https://github.com/AccelerateHS/accelerate/issues-category: Compilers/Interpreters, Concurrency, Data, Parallelism+category: Accelerate, Compilers/Interpreters, Concurrency, Data, Parallelism -extra-source-files:+extra-doc-files: CHANGELOG.md README.md --- Configuration flags--- ---------------------Flag nvvm- Default: False- Description: Use the NVVM library to generate optimised PTX--Flag debug- Default: False- Description:- Enable debug tracing message flags. Note that 'debug' must be enabled in the- base 'accelerate' package as well. See the 'accelerate' package for usage- and available options.--Flag bounds-checks- Default: True- Description: Enable bounds checking--Flag unsafe-checks- Default: False- Description: Enable bounds checking in unsafe operations--Flag internal-checks- Default: False- Description: Enable internal consistency checks-- -- Build configuration -- ------------------- @@ -120,11 +51,15 @@ Data.Array.Accelerate.LLVM.PTX.Array.Table Data.Array.Accelerate.LLVM.PTX.Context Data.Array.Accelerate.LLVM.PTX.Debug+ Data.Array.Accelerate.LLVM.PTX.Pool Data.Array.Accelerate.LLVM.PTX.State Data.Array.Accelerate.LLVM.PTX.Target Data.Array.Accelerate.LLVM.PTX.Execute Data.Array.Accelerate.LLVM.PTX.Execute.Async+ Data.Array.Accelerate.LLVM.PTX.Execute.Environment+ Data.Array.Accelerate.LLVM.PTX.Execute.Event+ Data.Array.Accelerate.LLVM.PTX.Execute.Marshal Data.Array.Accelerate.LLVM.PTX.Execute.Stream Data.Array.Accelerate.LLVM.PTX.Execute.Stream.Reservoir @@ -137,14 +72,13 @@ Data.Array.Accelerate.LLVM.PTX.CodeGen.Loop Data.Array.Accelerate.LLVM.PTX.CodeGen.Map Data.Array.Accelerate.LLVM.PTX.CodeGen.Permute- Data.Array.Accelerate.LLVM.PTX.CodeGen.Queue Data.Array.Accelerate.LLVM.PTX.CodeGen.Scan+ Data.Array.Accelerate.LLVM.PTX.CodeGen.Stencil+ Data.Array.Accelerate.LLVM.PTX.CodeGen.Transform Data.Array.Accelerate.LLVM.PTX.Compile Data.Array.Accelerate.LLVM.PTX.Compile.Cache- Data.Array.Accelerate.LLVM.PTX.Compile.Libdevice Data.Array.Accelerate.LLVM.PTX.Compile.Libdevice.Load- Data.Array.Accelerate.LLVM.PTX.Compile.Libdevice.TH Data.Array.Accelerate.LLVM.PTX.Link Data.Array.Accelerate.LLVM.PTX.Link.Cache@@ -152,67 +86,93 @@ Data.Array.Accelerate.LLVM.PTX.Embed - Data.Array.Accelerate.LLVM.PTX.Execute.Environment- Data.Array.Accelerate.LLVM.PTX.Execute.Event- Data.Array.Accelerate.LLVM.PTX.Execute.Marshal+ System.Process.Extra+ GHC.Heap.NormalForm Paths_accelerate_llvm_ptx + autogen-modules:+ Paths_accelerate_llvm_ptx+ build-depends:- base >= 4.7 && < 4.11- , accelerate == 1.1.*- , accelerate-llvm == 1.1.*+ base >= 4.10 && < 5+ , accelerate == 1.4.*+ , accelerate-llvm == 1.4.* , bytestring >= 0.10.4- , containers >= 0.5 && <0.6- , cuda >= 0.9+ , containers >= 0.5 && < 0.9+ , cuda >= 0.10 , deepseq >= 1.3 , directory >= 1.0 , dlist >= 0.6- , fclabels >= 2.0 , file-embed >= 0.0.8 , filepath >= 1.0+ , formatting >= 7.0 , hashable >= 1.2- , llvm-hs >= 4.1 && < 5.2- , llvm-hs-pure >= 4.1 && < 5.2+ -- , llvm-pretty >= 0.12 , mtl >= 2.2.1- , nvvm >= 0.7.5- , pretty >= 1.1+ -- only used to render llvm-pretty output+ , pretty+ , prettyprinter >= 1.7+ , primitive >= 0.7 , process >= 1.4.3 , template-haskell- , time >= 1.4+ , text >= 1.2 , unordered-containers >= 0.2 - default-language:- Haskell2010+ if impl(ghc >= 8.6)+ build-depends:+ ghc-heap+ else+ build-depends:+ ghc+ , ghci - ghc-options: -O2 -Wall -fwarn-tabs+ hs-source-dirs:+ src - if impl(ghc >= 8.0)- ghc-options: -Wmissed-specialisations+ default-language:+ Haskell2010 - if flag(nvvm)- cpp-options: -DACCELERATE_USE_NVVM+ ghc-options:+ -O2+ -Wall+ -fwarn-tabs - if flag(debug)- cpp-options: -DACCELERATE_DEBUG - if flag(bounds-checks)- cpp-options: -DACCELERATE_BOUNDS_CHECKS+test-suite nofib-llvm-ptx+ type: exitcode-stdio-1.0+ hs-source-dirs: test/nofib+ main-is: Main.hs+ other-modules:+ Data.Array.Accelerate.LLVM.PTX.NoFib.RunQ - if flag(unsafe-checks)- cpp-options: -DACCELERATE_UNSAFE_CHECKS+ build-depends:+ base >= 4.10+ , accelerate+ , accelerate-llvm-ptx+ , tasty+ , tasty-hunit - if flag(internal-checks)- cpp-options: -DACCELERATE_INTERNAL_CHECKS+ default-language:+ Haskell2010 + ghc-options:+ -Wall+ -O2+ -threaded+ -rtsopts+ -with-rtsopts=-A128M+ -with-rtsopts=-n4M+ -- -with-rtsopts=-N + source-repository head type: git location: https://github.com/AccelerateHS/accelerate-llvm.git source-repository this type: git- tag: 1.1.0.1-ptx+ tag: v1.4.0.0 location: https://github.com/AccelerateHS/accelerate-llvm.git -- vim: nospell
+ src/Data/Array/Accelerate/LLVM/PTX.hs view
@@ -0,0 +1,561 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE TypeSynonymInstances #-}+-- |+-- Module : Data.Array.Accelerate.LLVM.PTX+-- Copyright : [2014..2020] The Accelerate Team+-- License : BSD3+--+-- Maintainer : Trevor L. McDonell <trevor.mcdonell@gmail.com>+-- Stability : experimental+-- Portability : non-portable (GHC extensions)+--+-- This module implements a backend for the /Accelerate/ language targeting+-- NVPTX for execution on NVIDIA GPUs. Expressions are on-line translated into+-- LLVM code, which is just-in-time executed in parallel on the GPU.+--+-- * Concurrent kernel execution+--+-- Accelerate enables copies to and from device memory to occur concurrently+-- with kernel execution. However, in order for kernels to be executed+-- concurrently, you must create your own 'PTX' execution target and pass it to+-- the 'runWith'-style functions. For example:+--+-- > import Data.Array.Accelerate as A+-- > import qualified Data.Array.Accelerate.LLVM.PTX as PTX+-- > import qualified Foreign.CUDA.Driver as C+-- >+-- > dotp :: Acc (Vector Float) -> Acc (Vector Float) -> Acc (Scalar Float)+-- > dotp xs ys = A.fold (+) 0 (A.zipWith (*) xs ys)+-- >+-- > runDotP = do+-- > C.initialise []+-- > -- Assuming just one CUDA device+-- > dev0 <- C.device 0+-- > dev0Props <- C.props dev0+-- > ptx <- PTX.createTargetForDevice dev0 dev0Props []+-- > -- Using runAsyncWith+-- > let xs = fromList (Z:.10) [0..] :: Vector Float+-- > ys = fromList (Z:.10) [1,3..] :: Vector Float+-- > asyncDotP <- PTX.runAsyncWith ptx (dotp (use+-- > ...+-- > dotP <- PTX.wait asyncDotP+--+-- The CUDA scheduler will only execute kernels concurrently if there are+-- sufficient resources available. Concurrency is limited by the number of+-- available threads, blocks, warps and registers; a kernel occupancy of \(n\)+-- does not always imply that \(1-n\) device resources are available.++module Data.Array.Accelerate.LLVM.PTX (++ Acc, Arrays,+ Afunction, AfunctionR,++ -- * Synchronous execution+ run, runWith,+ run1, run1With,+ runN, runNWith,+ stream, streamWith,++ -- * Asynchronous execution+ Async,+ wait, poll, cancel,++ runAsync, runAsyncWith,+ run1Async, run1AsyncWith,+ runNAsync, runNAsyncWith,++ -- * Ahead-of-time compilation+ runQ, runQWith,+ runQAsync, runQAsyncWith,++ -- * Execution targets+ PTX, createTargetForDevice, createTargetFromContext,++ -- * Controlling host-side allocation+ registerPinnedAllocatorWith,++) where++import Data.Array.Accelerate.AST ( PreOpenAfun(..), arraysR, liftALeftHandSide )+import Data.Array.Accelerate.AST.LeftHandSide ( lhsToTupR )+import Data.Array.Accelerate.Array.Data+import Data.Array.Accelerate.Async ( Async, asyncBound, wait, poll, cancel )+import Data.Array.Accelerate.Error+import Data.Array.Accelerate.Representation.Array ( liftArraysR )+import Data.Array.Accelerate.Smart ( Acc )+import Data.Array.Accelerate.Sugar.Array ( Arrays, toArr, fromArr, ArraysR )+import Data.Array.Accelerate.Trafo+import Data.Array.Accelerate.Trafo.Delayed+import Data.Array.Accelerate.Trafo.Sharing ( Afunction(..), AfunctionRepr(..), afunctionRepr )+import qualified Data.Array.Accelerate.Sugar.Array as Sugar++import Data.Array.Accelerate.LLVM.PTX.Array.Data+import Data.Array.Accelerate.LLVM.PTX.Compile+import Data.Array.Accelerate.LLVM.PTX.Context+import Data.Array.Accelerate.LLVM.PTX.Debug+import Data.Array.Accelerate.LLVM.PTX.Embed+import Data.Array.Accelerate.LLVM.PTX.Execute+import Data.Array.Accelerate.LLVM.PTX.Execute.Async ( Par, evalPar, getArrays )+import Data.Array.Accelerate.LLVM.PTX.Execute.Environment+import Data.Array.Accelerate.LLVM.PTX.Link+import Data.Array.Accelerate.LLVM.PTX.State+import Data.Array.Accelerate.LLVM.PTX.Target++import Foreign.CUDA.Driver as CUDA ( CUDAException, mallocHostForeignPtr )++import Control.Exception+import Control.Monad.Trans+import Data.Maybe+import Formatting ( shown )+import System.IO.Unsafe+import qualified Data.Array.Accelerate.TH.Compat as TH+++-- Accelerate: LLVM backend for NVIDIA GPUs+-- ----------------------------------------++-- | Compile and run a complete embedded array program.+--+-- This will execute using the first available CUDA device. If you wish to run+-- on a specific device, use 'runWith'.+--+-- The result is copied back to the host only once the arrays are demanded (or+-- the result is forced to normal form). For results consisting of multiple+-- components (a tuple of arrays or array of tuples) this applies per primitive+-- array. Evaluating the result of 'run' to WHNF will initiate the computation,+-- but does not copy the results back from the device.+--+-- /NOTE:/ it is recommended to use 'runN' or 'runQ' whenever possible.+--+run :: Arrays a => Acc a -> a+run a = unsafePerformIO (runIO a)++-- | As 'run', but execute using the specified target rather than using the+-- default, automatically selected device.+--+runWith :: Arrays a => PTX -> Acc a -> a+runWith target a = unsafePerformIO (runWithIO target a)++-- | As 'run', but run the computation asynchronously and return immediately+-- without waiting for the result. The status of the computation can be queried+-- using 'wait', 'poll', and 'cancel'.+--+-- This will run on the first available CUDA device. If you wish to run on+-- a specific device, use 'runAsyncWith'.+--+runAsync :: Arrays a => Acc a -> IO (Async a)+runAsync a = asyncBound (runIO a)++-- | As 'runWith', but execute asynchronously. Be sure not to destroy the context,+-- or attempt to attach it to a different host thread, before all outstanding+-- operations have completed.+--+runAsyncWith :: Arrays a => PTX -> Acc a -> IO (Async a)+runAsyncWith target a = asyncBound (runWithIO target a)+++runIO :: Arrays a => Acc a -> IO a+runIO a = withPool defaultTargetPool (\target -> runWithIO target a)++runWithIO :: forall a. Arrays a => PTX -> Acc a -> IO a+runWithIO target a = execute+ where+ !acc = convertAcc a+ execute = do+ dumpGraph acc+ evalPTX target $ do+ build <- phase Compile (compileAcc acc) >>= dumpStats+ exec <- phase Link (linkAcc build)+ res <- phase Execute (evalPar (executeAcc exec >>= copyToHostLazy (Sugar.arraysR @a)))+ return $ toArr res+++-- | This is 'runN', specialised to an array program of one argument.+--+run1 :: (Arrays a, Arrays b) => (Acc a -> Acc b) -> a -> b+run1 = runN++-- | As 'run1', but execute using the specified target rather than using the+-- default, automatically selected device.+--+run1With :: (Arrays a, Arrays b) => PTX -> (Acc a -> Acc b) -> a -> b+run1With = runNWith+++-- | Prepare and execute an embedded array program.+--+-- This function can be used to improve performance in cases where the array+-- program is constant between invocations, because it enables us to bypass+-- front-end conversion stages and move directly to the execution phase. If you+-- have a computation applied repeatedly to different input data, use this,+-- specifying any changing aspects of the computation via the input parameters.+-- If the function is only evaluated once, this is equivalent to 'run'.+--+-- In order to use 'runN' you must express your Accelerate program as a function+-- of array terms:+--+-- > f :: (Arrays a, Arrays b, ... Arrays c) => Acc a -> Acc b -> ... -> Acc c+--+-- This function then returns the compiled version of 'f':+--+-- > runN f :: (Arrays a, Arrays b, ... Arrays c) => a -> b -> ... -> c+--+-- At an example, rather than:+--+-- > step :: Acc (Vector a) -> Acc (Vector b)+-- > step = ...+-- >+-- > simulate :: Vector a -> Vector b+-- > simulate xs = run $ step (use xs)+--+-- Instead write:+--+-- > simulate = runN step+--+-- You can use the debugging options to check whether this is working+-- successfully. For example, running with the @-ddump-phases@ flag should show+-- that the compilation steps only happen once, not on the second and subsequent+-- invocations of 'simulate'. Note that this typically relies on GHC knowing+-- that it can lift out the function returned by 'runN' and reuse it.+--+-- As with 'run', the resulting array(s) are only copied back to the host once+-- they are actually demanded (forced to normal form). Thus, splitting a program+-- into multiple 'runN' steps does not imply transferring intermediate+-- computations back and forth between host and device. However note that+-- Accelerate is not able to optimise (fuse) across separate 'runN' invocations.+--+-- See the programs in the 'accelerate-examples' package for examples.+--+-- See also 'runQ', which compiles the Accelerate program at _Haskell_ compile+-- time, thus eliminating the runtime overhead altogether.+--+runN :: forall f. Afunction f => f -> AfunctionR f+runN f = exec+ where+ !acc = convertAfun f+ !exec = unsafeWithPool defaultTargetPool+ $ \target -> fromJust (lookup (ptxContext target) afun)++ -- Lazily cache the compiled function linked for each execution context.+ -- This includes specialisation for different compute capabilities and+ -- device-side memory management.+ --+ -- Perhaps this implicit version of 'runN' is not a good idea then, because+ -- we might need to migrate data between devices between iterations+ -- depending on which GPU gets scheduled.+ --+ !afun = flip map (unmanaged defaultTargetPool)+ $ \target -> (ptxContext target, runNWith' @f target acc)+++-- | As 'runN', but execute using the specified target device.+--+runNWith :: forall f. Afunction f => PTX -> f -> AfunctionR f+runNWith target f = exec+ where+ !acc = convertAfun f+ !exec = runNWith' @f target acc++runNWith' :: forall f. Afunction f => PTX -> DelayedAfun (ArraysFunctionR f) -> AfunctionR f+runNWith' target acc = go (afunctionRepr @f) afun (return Empty)+ where+ !afun = unsafePerformIO $ do+ dumpGraph acc+ evalPTX target $ do+ build <- phase Compile (compileAfun acc) >>= dumpStats+ link <- phase Link (linkAfun build)+ return link++ go :: forall aenv t r trepr.+ AfunctionRepr t r trepr+ -> ExecOpenAfun PTX aenv trepr+ -> Par PTX (Val aenv)+ -> r+ go (AfunctionReprLam repr) (Alam lhs l) k = \ !arrs ->+ let k' = do aenv <- k+ a <- useRemoteAsync (lhsToTupR lhs) $ fromArr arrs+ return (aenv `push` (lhs, a))+ in go repr l k'+ go AfunctionReprBody (Abody b) k = unsafePerformIO . phase Execute . evalPTX target . evalPar $ do+ aenv <- k+ fut <- executeOpenAcc b aenv+ toArr <$> copyToHostLazy (Sugar.arraysR @r) fut+ go _ _ _ = error "But that's not right, oh, no, what's the story?"+++-- | As 'run1', but the computation is executed asynchronously.+--+run1Async :: (Arrays a, Arrays b) => (Acc a -> Acc b) -> a -> IO (Async b)+run1Async = runNAsync++-- | As 'run1With', but execute asynchronously.+--+run1AsyncWith :: (Arrays a, Arrays b) => PTX -> (Acc a -> Acc b) -> a -> IO (Async b)+run1AsyncWith = runNAsyncWith+++-- | As 'runN', but execute asynchronously.+--+runNAsync :: (Afunction f, RunAsync r, ArraysFunctionR f ~ RunAsyncR r) => f -> r+runNAsync f = exec+ where+ !acc = convertAfun f+ !exec = unsafeWithPool defaultTargetPool+ $ \target -> fromJust (lookup (ptxContext target) afun)++ !afun = flip map (unmanaged defaultTargetPool)+ $ \target -> (ptxContext target, runNAsyncWith' target acc)+++-- | As 'runNWith', but execute asynchronously.+--+runNAsyncWith :: (Afunction f, RunAsync r, ArraysFunctionR f ~ RunAsyncR r) => PTX -> f -> r+runNAsyncWith target f = exec+ where+ !acc = convertAfun f+ !exec = runNAsyncWith' target acc++runNAsyncWith' :: RunAsync f => PTX -> DelayedAfun (RunAsyncR f) -> f+runNAsyncWith' target acc = exec+ where+ !afun = unsafePerformIO $ do+ dumpGraph acc+ evalPTX target $ do+ build <- phase Compile (compileAfun acc) >>= dumpStats+ link <- phase Link (linkAfun build)+ return link+ !exec = runAsync' target afun (return Empty)++class RunAsync f where+ type RunAsyncR f+ runAsync' :: PTX -> ExecOpenAfun PTX aenv (RunAsyncR f) -> Par PTX (Val aenv) -> f++instance (Arrays a, RunAsync b) => RunAsync (a -> b) where+ type RunAsyncR (a -> b) = ArraysR a -> RunAsyncR b+ runAsync' _ Abody{} _ _ = error "runAsync: function oversaturated"+ runAsync' target (Alam lhs l) k !arrs =+ let k' = do aenv <- k+ a <- useRemoteAsync (Sugar.arraysR @a) $ fromArr arrs+ return (aenv `push` (lhs, a))+ in runAsync' target l k'++instance Arrays b => RunAsync (IO (Async b)) where+ type RunAsyncR (IO (Async b)) = ArraysR b+ runAsync' _ Alam{} _ = error "runAsync: function not fully applied"+ runAsync' target (Abody b) k = asyncBound . phase Execute . evalPTX target . evalPar $ do+ aenv <- k+ ans <- executeOpenAcc b aenv+ arrs <- getArrays (arraysR b) ans+ return $ toArr arrs+++-- | Stream a lazily read list of input arrays through the given program,+-- collecting results as we go.+--+stream :: (Arrays a, Arrays b) => (Acc a -> Acc b) -> [a] -> [b]+stream f arrs = map go arrs+ where+ !go = run1 f++-- | As 'stream', but execute using the specified target.+--+streamWith :: (Arrays a, Arrays b) => PTX -> (Acc a -> Acc b) -> [a] -> [b]+streamWith target f arrs = map go arrs+ where+ !go = run1With target f+++-- | Ahead-of-time compilation for an embedded array program.+--+-- This function will generate, compile, and link into the final executable,+-- code to execute the given Accelerate computation /at Haskell compile time/.+-- This eliminates any runtime overhead associated with the other @run*@+-- operations. The generated code will be compiled for the current (default) GPU+-- architecture.+--+-- Since the Accelerate program will be generated at Haskell compile time,+-- construction of the Accelerate program, in particular via meta-programming,+-- will be limited to operations available to that phase. Also note that any+-- arrays which are embedded into the program via 'Data.Array.Accelerate.use'+-- will be stored as part of the final executable.+--+-- Usage of this function in your program is similar to that of 'runN'. First,+-- express your Accelerate program as a function of array terms:+--+-- > f :: (Arrays a, Arrays b, ... Arrays c) => Acc a -> Acc b -> ... -> Acc c+--+-- This function then returns a compiled version of @f@ as a Template Haskell+-- splice, to be added into your program at Haskell compile time:+--+-- > {-# LANGUAGE TemplateHaskell #-}+-- >+-- > f' :: a -> b -> ... -> c+-- > f' = $( runQ f )+--+-- Note that at the splice point the usage of @f@ must monomorphic; i.e. the+-- types @a@, @b@ and @c@ must be at some known concrete type.+--+-- See the <https://github.com/tmcdonell/lulesh-accelerate lulesh-accelerate>+-- project for an example.+--+-- [/Note:/]+--+-- Due to <https://ghc.haskell.org/trac/ghc/ticket/13587 GHC#13587>, this+-- currently must be as an /untyped/ splice.+--+-- The correct type of this function is similar to that of 'runN':+--+-- > runQ :: Afunction f => f -> Q (TExp (AfunctionR f))+--+-- @since 1.1.0.0+--+runQ :: Afunction f => f -> TH.ExpQ+runQ = runQ' [| unsafePerformIO |]++-- | Ahead-of-time analogue of 'runNWith'. See 'runQ' for more information.+--+-- /NOTE:/ The supplied (at runtime) target must be compatible with the+-- architecture that this function was compiled for (the 'defaultTarget' of the+-- compiling machine). Running on a device with the same compute capability is+-- best, but this should also be forward compatible to newer architectures.+--+-- The correct type of this function is:+--+-- > runQWith :: Afunction f => f -> Q (TExp (PTX -> AfunctionR f))+--+-- @since 1.1.0.0+--+runQWith :: Afunction f => f -> TH.ExpQ+runQWith f = do+ target <- TH.newName "target"+ TH.lamE [TH.varP target] (runQWith' [| unsafePerformIO |] (TH.varE target) f)+++-- | Ahead-of-time analogue of 'runNAsync'. See 'runQ' for more information.+--+-- The correct type of this function is:+--+-- > runQAsync :: (Afunction f, RunAsync r, AfunctionR f ~ RunAsyncR r) => f -> Q (TExp r)+--+-- @since 1.1.0.0+--+runQAsync :: Afunction f => f -> TH.ExpQ+runQAsync = runQ' [| asyncBound |]++-- | Ahead-of-time analogue of 'runNAsyncWith'. See 'runQWith' for more information.+--+-- The correct type of this function is:+--+-- > runQAsyncWith :: (Afunction f, RunAsync r, AfunctionR f ~ RunAsyncR r) => f -> Q (TExp (PTX -> r))+--+-- @since 1.1.0.0+--+runQAsyncWith :: Afunction f => f -> TH.ExpQ+runQAsyncWith f = do+ target <- TH.newName "target"+ TH.lamE [TH.varP target] (runQWith' [| asyncBound |] (TH.varE target) f)+++runQ' :: Afunction f => TH.ExpQ -> f -> TH.ExpQ+runQ' using = runQ'_ using (\go -> [| withPool defaultTargetPool (\target -> evalPTX target (evalPar $go)) |])++runQWith' :: Afunction f => TH.ExpQ -> TH.ExpQ -> f -> TH.ExpQ+runQWith' using target = runQ'_ using (\go -> [| evalPTX $target (evalPar $go) |])++-- Generate a template haskell expression for the given function to be embedded+-- into the current program. The supplied continuation specifies how to execute+-- the given body expression (e.g. using 'evalPTX')+--+-- NOTE:+--+-- * Can we do this without requiring an active GPU context? This should be+-- possible with only the DeviceProperties, but we would have to be a little+-- careful if we pass invalid values for the other state components. If we+-- attempt this, at minimum we need to parse the generated .sass to extract+-- resource usage information, rather than loading the module and probing+-- directly.+--+-- * What happens if we execute this code on a different architecture revision?+-- With runN this will automatically be recompiled for each new architecture+-- (at runtime).+--+runQ'_ :: Afunction f => TH.ExpQ -> (TH.ExpQ -> TH.ExpQ) -> f -> TH.ExpQ+runQ'_ using k f = do+ afun <- let acc = convertAfun f+ in TH.runIO $ do+ dumpGraph acc+ evalPTX defaultTarget $+ phase Compile (compileAfun acc) >>= dumpStats+ let+ go :: CompiledOpenAfun PTX aenv t -> [TH.PatQ] -> [TH.ExpQ] -> [TH.StmtQ] -> TH.ExpQ+ go (Alam lhs l) xs as stmts = do+ x <- TH.newName "x" -- lambda bound variable+ a <- TH.newName "a" -- local array name+ s <- TH.bindS (TH.varP a) [| useRemoteAsync $(TH.unTypeCode $ liftArraysR (lhsToTupR lhs)) (fromArr $(TH.varE x)) |]+ go l (TH.bangP (TH.varP x) : xs) ([| ($(TH.unTypeCode $ liftALeftHandSide lhs), $(TH.varE a)) |] : as) (return s : stmts)++ go (Abody b) xs as stmts = do+ r <- TH.newName "r" -- result+ s <- TH.newName "s"+ let+ aenv = foldr (\a gamma -> [| $gamma `push` $a |] ) [| Empty |] as+ body = embedOpenAcc defaultTarget b+ --+ TH.lamE (reverse xs)+ [| $using (phase Execute $(k (+ TH.doE ( reverse stmts +++ [ TH.bindS (TH.varP r) [| executeOpenAcc $(TH.unTypeCode body) $aenv |]+ , TH.bindS (TH.varP s) [| copyToHostLazy $(TH.unTypeCode (liftArraysR (arraysR b))) $(TH.varE r) |]+ , TH.noBindS [| return $ toArr $(TH.varE s) |]+ ]))))+ |]+ --+ go afun [] [] []+++-- Controlling host-side allocation+-- --------------------------------++-- | Configure the default execution target to allocate all future host-side+-- arrays using (CUDA) pinned memory. Any newly allocated arrays will be+-- page-locked and directly accessible from the device, enabling high-speed+-- (asynchronous) DMA.+--+-- Note that since the amount of available pageable memory will be reduced,+-- overall system performance can suffer.+--+-- registerPinnedAllocator :: IO ()+-- registerPinnedAllocator = registerPinnedAllocatorWith defaultTarget+++-- | All future array allocations will use pinned memory associated with the+-- given execution context. These arrays will be directly accessible from the+-- device, enabling high-speed asynchronous DMA.+--+-- Note that since the amount of available pageable memory will be reduced,+-- overall system performance can suffer.+--+registerPinnedAllocatorWith :: HasCallStack => PTX -> IO ()+registerPinnedAllocatorWith target =+ registerForeignPtrAllocator $ \bytes ->+ withContext (ptxContext target) (CUDA.mallocHostForeignPtr [] bytes)+ `catch`+ \e -> internalError shown (e :: CUDAException)+++-- Debugging+-- =========++dumpStats :: MonadIO m => a -> m a+dumpStats x = liftIO dumpSimplStats >> return x+
+ src/Data/Array/Accelerate/LLVM/PTX/Analysis/Device.hs view
@@ -0,0 +1,79 @@+{-# LANGUAGE ScopedTypeVariables #-}+-- |+-- Module : Data.Array.Accelerate.LLVM.PTX.Analysis.Device+-- Copyright : [2008..2020] The Accelerate Team+-- License : BSD3+--+-- Maintainer : Trevor L. McDonell <trevor.mcdonell@gmail.com>+-- Stability : experimental+-- Portability : non-portable (GHC extensions)+--++module Data.Array.Accelerate.LLVM.PTX.Analysis.Device+ where++import Control.Exception+import Data.Function+import Data.List+import Data.Ord+import Foreign.CUDA.Analysis.Device+import Foreign.CUDA.Driver.Context ( Context )+import Foreign.CUDA.Driver.Device+import Foreign.CUDA.Driver.Error+import qualified Foreign.CUDA.Driver as CUDA+++-- Select the best of the available CUDA capable devices. This prefers devices+-- with higher compute capability, followed by maximum throughput.+--+-- For hosts with multiple devices in Exclusive Process mode, this will select+-- the first of the _available_ devices. If no devices are available, an+-- exception is thrown indicating that no devices are available.+--+selectBestDevice :: IO (Device, DeviceProperties, Context)+selectBestDevice = select =<< enumerateDevices+ where+ select :: [(Device, DeviceProperties)] -> IO (Device, DeviceProperties, Context)+ select [] = cudaErrorIO "No CUDA-capable devices are available"+ select ((dev,prp):rest) = do+ r <- try $ CUDA.create dev [CUDA.SchedAuto]+ case r of+ Right ctx -> return (dev,prp,ctx)+ Left (_::CUDAException) -> select rest+++-- Return the list of all connected CUDA devices, sorted by compute+-- compatibility, followed by maximum throughput.+--+-- Strictly speaking this may not necessary, as the default device enumeration+-- appears to be sorted by some metric already.+--+-- Ignore the possibility of emulation-mode devices, as this has been deprecated+-- as of CUDA v3.0 (compute-capability == 9999.9999)+--+enumerateDevices :: IO [(Device, DeviceProperties)]+enumerateDevices = do+ devs <- mapM CUDA.device . enumFromTo 0 . subtract 1 =<< CUDA.count+ prps <- mapM CUDA.props devs+ return $ sortBy (flip compareDevices `on` snd) (zip devs prps)+++-- Return a ordering of two device with respect to (estimated) performance+--+compareDevices :: DeviceProperties -> DeviceProperties -> Ordering+compareDevices = cmp+ where+ compute = computeCapability+ flops d = multiProcessorCount d * coresPerMultiProcessor d * clockRate d+ cmp x y+ | compute x == compute y = comparing flops x y+ | otherwise = comparing compute x y+++-- Number of CUDA cores per streaming multiprocessor for a given architecture+-- revision. This is the number of SIMD arithmetic units per multiprocessor,+-- executing in lockstep in half-warp groupings (16 ALUs).+--+coresPerMultiProcessor :: DeviceProperties -> Int+coresPerMultiProcessor = coresPerMP . deviceResources+
+ src/Data/Array/Accelerate/LLVM/PTX/Analysis/Launch.hs view
@@ -0,0 +1,74 @@+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE TemplateHaskell #-}+-- |+-- Module : Data.Array.Accelerate.LLVM.PTX.Analysis.Launch+-- Copyright : [2008..2020] The Accelerate Team+-- License : BSD3+--+-- Maintainer : Trevor L. McDonell <trevor.mcdonell@gmail.com>+-- Stability : experimental+-- Portability : non-portable (GHC extensions)+--++module Data.Array.Accelerate.LLVM.PTX.Analysis.Launch (++ DeviceProperties, Occupancy, LaunchConfig,+ simpleLaunchConfig, launchConfig,+ multipleOf, multipleOfQ,++) where++import Foreign.CUDA.Analysis as CUDA+import Data.Array.Accelerate.TH.Compat+++-- | Given information about the resource usage of the compiled kernel,+-- determine the optimum launch parameters.+--+type LaunchConfig+ = Int -- maximum #threads per block+ -> Int -- #registers per thread+ -> Int -- #bytes of static shared memory+ -> ( Occupancy+ , Int -- thread block size+ , Int -> Int -- grid size required to process the given input size+ , Int -- #bytes dynamic shared memory+ , CodeQ (Int -> Int)+ )++-- | Analytics for a simple kernel which requires no additional shared memory or+-- have other constraints on launch configuration. The smallest thread block+-- size, in increments of a single warp, with the highest occupancy is used.+--+simpleLaunchConfig :: DeviceProperties -> LaunchConfig+simpleLaunchConfig dev = launchConfig dev (decWarp dev) (const 0) multipleOf multipleOfQ+++-- | Determine the optimal kernel launch configuration for a kernel.+--+launchConfig+ :: DeviceProperties -- ^ Device architecture to optimise for+ -> [Int] -- ^ Thread block sizes to consider+ -> (Int -> Int) -- ^ Shared memory (#bytes) as a function of thread block size+ -> (Int -> Int -> Int) -- ^ Determine grid size for input size 'n' (first arg) over thread blocks of size 'm' (second arg)+ -> CodeQ (Int -> Int -> Int)+ -> LaunchConfig+launchConfig dev candidates dynamic_smem grid_size grid_sizeQ maxThreads registers static_smem =+ let+ (cta, occ) = optimalBlockSizeOf dev (filter (<= maxThreads) candidates) (const registers) smem+ maxGrid = multiProcessorCount dev * activeThreadBlocks occ+ grid n = maxGrid `min` grid_size n cta+ smem n = static_smem + dynamic_smem n+ gridQ = [|| \n -> (maxGrid::Int) `min` $$grid_sizeQ (n::Int) (cta::Int) ||]+ in+ ( occ, cta, grid, dynamic_smem cta, gridQ )+++-- | The next highest multiple of 'y' from 'x'.+--+multipleOf :: Int -> Int -> Int+multipleOf x y = ((x + y - 1) `quot` y)++multipleOfQ :: CodeQ (Int -> Int -> Int)+multipleOfQ = [|| multipleOf ||]+
+ src/Data/Array/Accelerate/LLVM/PTX/Array/Data.hs view
@@ -0,0 +1,205 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE MagicHash #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE UnboxedTuples #-}+{-# OPTIONS_GHC -Wno-orphans #-}+-- |+-- Module : Data.Array.Accelerate.LLVM.PTX.Array.Data+-- Copyright : [2014..2020] The Accelerate Team+-- License : BSD3+--+-- Maintainer : Trevor L. McDonell <trevor.mcdonell@gmail.com>+-- Stability : experimental+-- Portability : non-portable (GHC extensions)+--++module Data.Array.Accelerate.LLVM.PTX.Array.Data (++ module Data.Array.Accelerate.LLVM.Array.Data,+ module Data.Array.Accelerate.LLVM.PTX.Array.Data,++) where++import Data.Array.Accelerate.Array.Data+import Data.Array.Accelerate.Array.Unique+import Data.Array.Accelerate.Error+import Data.Array.Accelerate.Lifetime+import Data.Array.Accelerate.Representation.Array+import Data.Array.Accelerate.Representation.Shape+import Data.Array.Accelerate.Representation.Type+import Data.Array.Accelerate.Type++import Data.Array.Accelerate.LLVM.Array.Data+import Data.Array.Accelerate.LLVM.State++import Data.Array.Accelerate.LLVM.PTX.State+import Data.Array.Accelerate.LLVM.PTX.Target+import Data.Array.Accelerate.LLVM.PTX.Execute.Async+import qualified Data.Array.Accelerate.LLVM.PTX.Array.Prim as Prim++import Control.Applicative+import Control.Monad+import Control.Monad.IO.Class ( liftIO )+import Control.Monad.Reader ( asks )+import System.IO.Unsafe+import Prelude++import GHC.Heap.NormalForm+++-- | Remote memory management for the PTX target. Data can be copied+-- asynchronously using multiple execution engines whenever possible.+--+instance Remote PTX where+ {-# INLINEABLE allocateRemote #-}+ {-# INLINEABLE indexRemoteAsync #-}+ {-# INLINEABLE useRemoteR #-}+ {-# INLINEABLE copyToHostR #-}+ {-# INLINEABLE copyToRemoteR #-}+ allocateRemote repr@(ArrayR shr tp) !sh = do+ let !n = size shr sh+ arr <- liftIO $ allocateArray repr sh -- shadow array on the host+ liftPar $ runArray tp arr (\m t ad -> Prim.mallocArray t (n*m) ad >> return ad)++ indexRemoteAsync = runIndexArrayAsync Prim.indexArrayAsync+ useRemoteR = Prim.useArrayAsync+ copyToHostR = Prim.peekArrayAsync+ copyToRemoteR = Prim.pokeArrayAsync+ copyToPeerR = internalError "not supported yet"+++-- | Copy an array from the remote device to the host. Although the Accelerate+-- program is hyper-strict and will evaluate the computation as soon as any part+-- of it is demanded, the individual array payloads are copied back to the host+-- _only_ as they are demanded by the Haskell program. This has several+-- consequences:+--+-- 1. If the device has multiple memcpy engines, only one will be used. The+-- transfers are however associated with a non-default stream.+--+-- 2. Using 'seq' to force an Array to head-normal form will initiate the+-- computation, but not transfer the results back to the host. Requesting+-- an array element or using 'deepseq' to force to normal form is required+-- to actually transfer the data.+--+{-# INLINEABLE copyToHostLazy #-}+copyToHostLazy+ :: HasCallStack+ => ArraysR arrs+ -> FutureArraysR PTX arrs+ -> Par PTX arrs+copyToHostLazy TupRunit () = return ()+copyToHostLazy (TupRpair r1 r2) (f1, f2) = do+ a1 <- copyToHostLazy r1 f1+ a2 <- copyToHostLazy r2 f2+ return (a1, a2)+copyToHostLazy (TupRsingle (ArrayR shr tp)) future = do+ ptx <- asks llvmTarget+ liftIO $ do+ Array sh adata <- wait future++ -- Note: [Lazy device-host transfers]+ --+ -- This needs must be non-strict at the leaves of the datatype (that+ -- is, the UniqueArray pointers). This means we can traverse the+ -- ArrayData constructors (in particular, the spine defined by Unit+ -- and Pair) until we reach the array we care about, without forcing+ -- the other fields.+ --+ -- https://github.com/AccelerateHS/accelerate/issues/437+ --+ -- Furthermore, we only want to transfer the data if the host pointer+ -- is currently unevaluated. This situation can occur for example if+ -- the argument to 'use' or 'unit' is returned as part of the result+ -- of a 'run'. Peek at GHC's underlying closure representation and+ -- check whether the pointer is a thunk, and only initiate the+ -- transfer if so.+ --+ let+ peekR :: SingleType e+ -> ArrayData e+ -> Int+ -> IO (ArrayData e)+ peekR t ad m+ | SingleArrayDict <- singleArrayDict t+ , UniqueArray uid (Lifetime lft weak fp) <- ad+ = unsafeInterleaveIO $ do+ yes <- isNormalForm fp+ fp' <- if yes+ then return fp+ else unsafeInterleaveIO . evalPTX ptx . evalPar $ do+ !_ <- block =<< Prim.peekArrayAsync t m ad+ return fp+ --+ return $ UniqueArray uid (Lifetime lft weak fp')++ n = size shr sh++ runR :: TypeR e -> ArrayData e -> IO (ArrayData e)+ runR TupRunit !() = return ()+ runR (TupRpair !t1 !t2) (!ad1, !ad2) = (,) <$> runR t1 ad1 <*> runR t2 ad2+ runR (TupRsingle !t) !ad =+ case t of+ SingleScalarType s -> peekR s ad n+ VectorScalarType (VectorType w s)+ | SingleArrayDict <- singleArrayDict s -> peekR s ad (n * w)++ Array sh <$> runR tp adata++-- | Clone an array into a newly allocated array on the device.+--+cloneArrayAsync+ :: ArrayR (Array sh e)+ -> Array sh e+ -> Par PTX (Future (Array sh e))+cloneArrayAsync repr@(ArrayR shr tp) arr@(Array _ src) = do+ Array _ dst <- allocateRemote repr sh+ Array sh `liftF` copyR tp src dst+ where+ sh = shape arr+ n = size shr sh++ copyR :: TypeR s -> ArrayData s -> ArrayData s -> Par PTX (Future (ArrayData s))+ copyR TupRunit !_ !_ = newFull ()+ copyR (TupRpair !t1 !t2) !(ad1, ad2) !(ad1', ad2') = liftF2 (,) (copyR t1 ad1 ad1') (copyR t2 ad2 ad2')+ copyR (TupRsingle !t) !ad !ad' =+ case t of+ SingleScalarType s -> copyPrim s ad ad' n+ VectorScalarType (VectorType w s)+ | SingleArrayDict <- singleArrayDict s -> copyPrim s ad ad' (n * w)++ copyPrim+ :: SingleType s+ -> ArrayData s+ -> ArrayData s+ -> Int+ -> Par PTX (Future (ArrayData s))+ copyPrim !s !a1 !a2 !m = Prim.copyArrayAsync s m a1 a2++ liftF :: Async arch+ => (a -> b)+ -> Par arch (FutureR arch a)+ -> Par arch (FutureR arch b)+ liftF f x = do+ r <- new+ x' <- x+ put r . f =<< get x' -- don't create a new execution stream for this+ return r++ liftF2 :: Async arch+ => (a -> b -> c)+ -> Par arch (FutureR arch a)+ -> Par arch (FutureR arch b)+ -> Par arch (FutureR arch c)+ liftF2 f x y = do+ r <- new+ x' <- spawn x+ y' <- spawn y+ fork $ put r =<< liftM2 f (get x') (get y')+ return r+
+ src/Data/Array/Accelerate/LLVM/PTX/Array/Prim.hs view
@@ -0,0 +1,391 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE MagicHash #-}+{-# LANGUAGE MagicHash #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UnboxedTuples #-}+-- |+-- Module : Data.Array.Accelerate.LLVM.PTX.Array.Prim+-- Copyright : [2014..2020] The Accelerate Team+-- License : BSD3+--+-- Maintainer : Trevor L. McDonell <trevor.mcdonell@gmail.com>+-- Stability : experimental+-- Portability : non-portable (GHC extensions)+--++module Data.Array.Accelerate.LLVM.PTX.Array.Prim (++ mallocArray,+ useArrayAsync,+ indexArrayAsync,+ peekArrayAsync,+ pokeArrayAsync,+ copyArrayAsync,+ -- copyArrayPeerAsync,+ memsetArrayAsync,+ withDevicePtr,++) where++import Data.Array.Accelerate.Array.Data+import Data.Array.Accelerate.Array.Unique+import Data.Array.Accelerate.Error+import Data.Array.Accelerate.Lifetime hiding ( withLifetime )+import Data.Array.Accelerate.Representation.Elt+import Data.Array.Accelerate.Representation.Type+import Data.Array.Accelerate.Type++import Data.Array.Accelerate.LLVM.State++import Data.Array.Accelerate.LLVM.PTX.Target+import Data.Array.Accelerate.LLVM.PTX.Execute.Async+import Data.Array.Accelerate.LLVM.PTX.Execute.Event+import Data.Array.Accelerate.LLVM.PTX.Execute.Stream+import Data.Array.Accelerate.LLVM.PTX.Array.Remote as Remote+import qualified Data.Array.Accelerate.LLVM.PTX.Debug as Debug++import qualified Foreign.CUDA.Driver as CUDA+import qualified Foreign.CUDA.Driver.Stream as CUDA++import Control.Monad+import Control.Monad.Reader+import Data.IORef+import Data.Text.Lazy.Builder+import Formatting hiding ( bytes )+import qualified Formatting as F+import Prelude++import GHC.Base ( IO(..), Int(..), Double(..), touch#, int2Double# )+++-- | Allocate a device-side array associated with the given host array. If the+-- allocation fails due to a memory error, we attempt some last-ditch memory+-- cleanup before trying again. If it still fails; error.+--+{-# INLINEABLE mallocArray #-}+mallocArray+ :: HasCallStack+ => SingleType e+ -> Int+ -> ArrayData e+ -> LLVM PTX ()+mallocArray !t !n !ad = do+ message ("mallocArray: " % formatBytes) (n * bytesElt (TupRsingle (SingleScalarType t)))+ void $ Remote.malloc t ad n False+++-- | A combination of 'mallocArray' and 'pokeArray', that allocates remotes+-- memory and uploads an existing array. This is specialised because we tell the+-- allocator that the host-side array is frozen, and thus it is safe to evict+-- the remote memory and re-upload the data at any time.+--+{-# INLINEABLE useArrayAsync #-}+useArrayAsync+ :: HasCallStack+ => SingleType e+ -> Int+ -> ArrayData e+ -> Par PTX (Future (ArrayData e))+useArrayAsync !t !n !ad = do+ message ("useArrayAsync: " % formatBytes) (n * bytesElt (TupRsingle (SingleScalarType t)))+ alloc <- liftPar $ Remote.malloc t ad n True+ if alloc+ then pokeArrayAsync t n ad+ else newFull ad+++-- | Copy data from the host to an existing array on the device+--+{-# INLINEABLE pokeArrayAsync #-}+pokeArrayAsync+ :: HasCallStack+ => SingleType e+ -> Int+ -> ArrayData e+ -> Par PTX (Future (ArrayData e))+pokeArrayAsync !t !n !ad+ | SingleArrayDict <- singleArrayDict t+ , SingleDict <- singleDict t+ = do+ let !src = CUDA.HostPtr (unsafeUniqueArrayPtr ad)+ !bytes = n * bytesElt (TupRsingle (SingleScalarType t))+ --+ stream <- asksParState ptxStream+ result <- liftPar $+ withLifetime stream $ \st ->+ withDevicePtr t ad $ \dst ->+ nonblocking stream $ do+ transfer "pokeArray" bytes (Just st) $ do+ CUDA.pokeArrayAsync n src dst (Just st)+ Debug.memcpy_to_remote bytes+ return ad+ --+ return result+++-- | Read an element from an array at the given row-major index.+--+-- This copies the data via a temporary array on the host, so that packed AoS+-- elements can be copied in a single transfer.+--+{-# INLINEABLE indexArrayAsync #-}+indexArrayAsync+ :: HasCallStack+ => Int -- actual number of values per element (i.e. this is >1 for SIMD types)+ -> SingleType e+ -> ArrayData e+ -> Int -- element index+ -> Par PTX (Future (ArrayData e))+indexArrayAsync !n !t !ad_src !i+ | SingleArrayDict <- singleArrayDict t+ , SingleDict <- singleDict t+ = do+ ad_dst <- liftIO $ newArrayData (TupRsingle $ SingleScalarType t) n+ let !bytes = n * bytesElt (TupRsingle (SingleScalarType t))+ !dst = CUDA.HostPtr (unsafeUniqueArrayPtr ad_dst)+ --+ stream <- asksParState ptxStream+ result <- liftPar $+ withLifetime stream $ \st ->+ withDevicePtr t ad_src $ \src ->+ nonblocking stream $ do+ transfer "indexArray" bytes (Just st) $ do+ CUDA.peekArrayAsync n (src `CUDA.advanceDevPtr` (i*n)) dst (Just st)+ Debug.memcpy_from_remote bytes+ return ad_dst+ --+ return result+++-- | Copy data from the device into the associated host-side Accelerate array+--+{-# INLINEABLE peekArrayAsync #-}+peekArrayAsync+ :: HasCallStack+ => SingleType e+ -> Int+ -> ArrayData e+ -> Par PTX (Future (ArrayData e))+peekArrayAsync !t !n !ad+ | SingleArrayDict <- singleArrayDict t+ , SingleDict <- singleDict t+ = do+ let !bytes = n * bytesElt (TupRsingle (SingleScalarType t))+ !dst = CUDA.HostPtr (unsafeUniqueArrayPtr ad)+ --+ stream <- asksParState ptxStream+ result <- liftPar $+ withLifetime stream $ \st ->+ withDevicePtr t ad $ \src ->+ nonblocking stream $ do+ transfer "peekArray" bytes (Just st) $ do+ CUDA.peekArrayAsync n src dst (Just st)+ Debug.memcpy_from_remote bytes+ return ad+ --+ return result+++-- | Copy data between arrays in the same context+--+{-# INLINEABLE copyArrayAsync #-}+copyArrayAsync+ :: HasCallStack+ => SingleType e+ -> Int+ -> ArrayData e+ -> ArrayData e+ -> Par PTX (Future (ArrayData e))+copyArrayAsync !t !n !ad_src !ad_dst+ | SingleArrayDict <- singleArrayDict t+ , SingleDict <- singleDict t+ = do+ let !bytes = n * bytesElt (TupRsingle (SingleScalarType t))+ --+ stream <- asksParState ptxStream+ result <- liftPar $+ withLifetime stream $ \st ->+ withDevicePtr t ad_src $ \src ->+ withDevicePtr t ad_dst $ \dst -> do+ (e,r) <- nonblocking stream $ do+ transfer "copyArray" bytes (Just st) $ CUDA.copyArrayAsync n src dst (Just st)+ return ad_dst+ return (e, (e,r))+ --+ return result+++{--+-- | Copy data from one device context into a _new_ array on the second context.+-- It is an error if the destination array already exists.+--+{-# INLINEABLE copyArrayPeerAsync #-}+copyArrayPeerAsync+ :: SingleType e+ -> Context -- destination context+ -> MemoryTable -- destination memory table+ -> Stream+ -> Int+ -> ArrayData e+ -> LLVM PTX ()+copyArrayPeerAsync = error "copyArrayPeerAsync"+{--+copyArrayPeerAsync !t !ctx2 !mt2 !st !n !ad = do+ let !bytes = n * sizeOfSingleType t+ src <- devicePtr mt1 ad+ dst <- mallocArray ctx2 mt2 n ad+ transfer "copyArrayPeer" bytes (Just st) $+ CUDA.copyArrayPeerAsync n src (deviceContext ctx1) dst (deviceContext ctx2) (Just st)+--}++-- | Copy part of an array from one device context to another. Both source and+-- destination arrays must exist.+--+{-# INLINEABLE copyArrayPeerAsyncR #-}+copyArrayPeerAsync+ :: SingleType e+ -> Context -- destination context+ -> MemoryTable -- destination memory table+ -> Stream+ -> Int+ -> Int+ -> ArrayData e+ -> LLVM PTX ()+copyArrayPeerAsync = error "copyArrayPeerAsyncR"+{--+copyArrayPeerAsyncR !t !ctx2 !mt2 !st !from !n !ad = do+ let !bytes = n * sizeOfSingleType t+ !offset = from * sizeOfSingleType t+ src <- devicePtr mt1 ad :: IO (CUDA.DevicePtr a)+ dst <- devicePtr mt2 ad :: IO (CUDA.DevicePtr a)+ transfer "copyArrayPeer" bytes (Just st) $+ CUDA.copyArrayPeerAsync n (src `CUDA.plusDevPtr` offset) (deviceContext ctx1)+ (dst `CUDA.plusDevPtr` offset) (deviceContext ctx2) (Just st)+--}+--}++-- | Set elements of the array to the specified value. Only 8-, 16-, and 32-bit+-- values are supported.+--+{-# INLINEABLE memsetArrayAsync #-}+memsetArrayAsync+ :: HasCallStack+ => SingleType e+ -> Int+ -> ScalarArrayDataR e+ -> ArrayData e+ -> Par PTX (Future (ArrayData e))+memsetArrayAsync !t !n !v !ad+ | SingleArrayDict <- singleArrayDict t+ , SingleDict <- singleDict t+ = do+ let !bytes = n * bytesElt (TupRsingle (SingleScalarType t))+ --+ stream <- asksParState ptxStream+ result <- liftPar $+ withLifetime stream $ \st ->+ withDevicePtr t ad $ \ptr ->+ nonblocking stream $ do+ transfer "memset" bytes (Just st) $ CUDA.memsetAsync ptr n v (Just st)+ return ad+ --+ return result+++-- Auxiliary+-- ---------++-- | Lookup the device memory associated with a given host array and do+-- something with it.+--+{-# INLINEABLE withDevicePtr #-}+withDevicePtr+ :: HasCallStack+ => SingleType e+ -> ArrayData e+ -> (CUDA.DevicePtr (ScalarArrayDataR e) -> LLVM PTX (Maybe Event, r))+ -> LLVM PTX r+withDevicePtr !t !ad !f = do+ mr <- withRemote t ad f+ case mr of+ Nothing -> internalError "array does not exist on the device"+ Just r -> return r++{--+-- | Lookup the device memory associated with a given host array+--+{-# INLINEABLE devicePtr #-}+devicePtr+ :: (ArrayElt e, ArrayPtrs e ~ Ptr a, Typeable a, Typeable b)+ => ArrayData e+ -> LLVM PTX (CUDA.DevicePtr b)+devicePtr !ad = do+ undefined+ {--+ mv <- Table.lookup mt ad+ case mv of+ Just v -> return v+ Nothing -> $internalError "devicePtr" "lost device memory"+ --}+--}++-- | Execute a (presumable asynchronous) operation and return the result+-- together with an event recorded immediately afterwards in the given stream.+--+{-# INLINE nonblocking #-}+nonblocking :: Stream -> LLVM PTX a -> LLVM PTX (Maybe Event, Future a)+nonblocking !stream !action = do+ result <- action+ event <- waypoint stream+ ready <- liftIO (query event)+ if ready+ then do+ future <- Future <$> liftIO (newIORef (Full result))+ return (Nothing, future)++ else do+ future <- Future <$> liftIO (newIORef (Pending event (return ()) result))+ return (Just event, future)++{-# INLINE withLifetime #-}+withLifetime :: MonadIO m => Lifetime a -> (a -> m b) -> m b+withLifetime (Lifetime ref _ a) f = do+ r <- f a+ liftIO (touchIORef ref)+ return r++{-# INLINE touchIORef #-}+touchIORef :: IORef a -> IO ()+touchIORef r = IO $ \s -> case touch# r s of s' -> (# s', () #)+++-- Debug+-- -----++{-# INLINE double #-}+double :: Int -> Double+double (I# i#) = D# (int2Double# i#)++{-# INLINE formatBytes #-}+formatBytes :: Format r (Int -> r)+formatBytes = F.bytes @Double shortest++{-# INLINE message #-}+message :: MonadIO m => Format (m ()) a -> a+message fmt = Debug.traceM Debug.dump_gc ("gc: " % fmt)++{-# INLINE transfer #-}+transfer :: MonadIO m => Builder -> Int -> Maybe CUDA.Stream -> IO () -> m ()+transfer name bytes stream action =+ let fmt wall cpu gpu =+ message (builder % ": " % F.bytes @Double shortest % " @ " % Debug.formatSIBase (Just 3) 1024 % "B/s, " % Debug.elapsed)+ name bytes (double bytes / wall) wall cpu gpu+ in+ liftIO (Debug.timed Debug.dump_gc fmt stream action)+
+ src/Data/Array/Accelerate/LLVM/PTX/Array/Remote.hs view
@@ -0,0 +1,167 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MagicHash #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}+-- |+-- Module : Data.Array.Accelerate.LLVM.PTX.Array.Remote+-- Copyright : [2014..2020] The Accelerate Team+-- License : BSD3+--+-- Maintainer : Trevor L. McDonell <trevor.mcdonell@gmail.com>+-- Stability : experimental+-- Portability : non-portable (GHC extensions)+--++module Data.Array.Accelerate.LLVM.PTX.Array.Remote (++ withRemote, malloc,++) where++import Data.Array.Accelerate.LLVM.State+import Data.Array.Accelerate.LLVM.PTX.Target+import {-# SOURCE #-} Data.Array.Accelerate.LLVM.PTX.Execute.Event+import {-# SOURCE #-} Data.Array.Accelerate.LLVM.PTX.Execute.Stream++import Data.Array.Accelerate.Array.Data+import Data.Array.Accelerate.Array.Unique+import Data.Array.Accelerate.Lifetime+import Data.Array.Accelerate.Representation.Elt+import Data.Array.Accelerate.Representation.Type+import Data.Array.Accelerate.Type+import qualified Data.Array.Accelerate.Array.Remote as Remote+import qualified Data.Array.Accelerate.LLVM.PTX.Debug as Debug++import Foreign.CUDA.Driver.Error+import qualified Foreign.CUDA.Ptr as CUDA+import qualified Foreign.CUDA.Driver as CUDA+import qualified Foreign.CUDA.Driver.Stream as CUDA++import Control.Exception+import Control.Monad.Reader+import Data.Text.Lazy.Builder+import Formatting hiding ( bytes )+import qualified Formatting as F++import GHC.Base ( Int(..), Double(..), int2Double# )+++-- Events signal once a computation has completed+--+instance Remote.Task (Maybe Event) where+ completed Nothing = return True+ completed (Just e) = query e++instance Remote.RemoteMemory (LLVM PTX) where+ type RemotePtr (LLVM PTX) = CUDA.DevicePtr+ --+ mallocRemote n+ | n <= 0 = return (Just CUDA.nullDevPtr)+ | otherwise = do+ name <- asks ptxDeviceName+ liftIO $ do+ ep <- try (CUDA.mallocArray n)+ case ep of+ Right p -> do Debug.remote_memory_alloc name (CUDA.useDevicePtr p) n+ return (Just p)+ Left (ExitCode OutOfMemory) -> do return Nothing+ Left e -> do message ("malloc failed with error: " % shown) e+ throwIO e++ peekRemote t n src ad+ | SingleArrayDict <- singleArrayDict t+ , SingleDict <- singleDict t+ = let bytes = n * bytesElt (TupRsingle (SingleScalarType t))+ dst = CUDA.HostPtr (unsafeUniqueArrayPtr ad)+ in+ blocking $ \stream ->+ withLifetime stream $ \st -> do+ Debug.memcpy_from_remote bytes+ transfer "peekRemote" bytes (Just st) $ CUDA.peekArrayAsync n src dst (Just st)++ pokeRemote t n dst ad+ | SingleArrayDict <- singleArrayDict t+ , SingleDict <- singleDict t+ = let bytes = n * bytesElt (TupRsingle (SingleScalarType t))+ src = CUDA.HostPtr (unsafeUniqueArrayPtr ad)+ in+ blocking $ \stream ->+ withLifetime stream $ \st -> do+ Debug.memcpy_to_remote bytes+ transfer "pokeRemote" bytes (Just st) $ CUDA.pokeArrayAsync n src dst (Just st)++ castRemotePtr = CUDA.castDevPtr+ availableRemoteMem = liftIO $ fst `fmap` CUDA.getMemInfo+ totalRemoteMem = liftIO $ snd `fmap` CUDA.getMemInfo+ remoteAllocationSize = return 4096++++-- | Allocate an array in the remote memory space sufficient to hold the given+-- number of elements, and associated with the given host side array. Space will+-- be freed from the remote device if necessary.+--+{-# INLINEABLE malloc #-}+malloc+ :: SingleType e+ -> ArrayData e+ -> Int+ -> Bool+ -> LLVM PTX Bool+malloc !tp !ad !n !frozen = do+ PTX{..} <- asks llvmTarget+ Remote.malloc ptxMemoryTable tp ad frozen n+++-- | Lookup up the remote array pointer for the given host-side array+--+{-# INLINEABLE withRemote #-}+withRemote+ :: SingleType e+ -> ArrayData e+ -> (CUDA.DevicePtr (ScalarArrayDataR e) -> LLVM PTX (Maybe Event, r))+ -> LLVM PTX (Maybe r)+withRemote !tp !ad !f = do+ PTX{..} <- asks llvmTarget+ Remote.withRemote ptxMemoryTable tp ad f+++-- Auxiliary+-- ---------++-- | Execute the given operation in a new stream, and wait for the operation to+-- complete before returning.+--+{-# INLINE blocking #-}+blocking :: (Stream -> IO a) -> LLVM PTX a+blocking !fun =+ streaming (liftIO . fun) $ \e r -> do+ liftIO $ block e+ return r++{-# INLINE double #-}+double :: Int -> Double+double (I# i#) = D# (int2Double# i#)+++-- Debugging+-- ---------++{-# INLINE message #-}+message :: Format (IO ()) a -> a+message fmt = Debug.traceM Debug.dump_gc ("gc: " % fmt)++{-# INLINE transfer #-}+transfer :: Builder -> Int -> Maybe CUDA.Stream -> IO () -> IO ()+transfer name bytes stream action =+ let fmt wall cpu gpu =+ message (builder % ": " % F.bytes @Double shortest % " @ " % Debug.formatSIBase (Just 3) 1024 % "B/s, " % Debug.elapsed)+ name bytes (double bytes / wall) wall cpu gpu+ in+ Debug.timed Debug.dump_gc fmt stream action+
+ src/Data/Array/Accelerate/LLVM/PTX/Array/Table.hs view
@@ -0,0 +1,58 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE OverloadedStrings #-}+-- |+-- Module : Data.Array.Accelerate.LLVM.PTX.Array.Table+-- Copyright : [2014..2020] The Accelerate Team+-- License : BSD3+--+-- Maintainer : Trevor L. McDonell <trevor.mcdonell@gmail.com>+-- Stability : experimental+-- Portability : non-portable (GHC extensions)+--++module Data.Array.Accelerate.LLVM.PTX.Array.Table (++ MemoryTable,+ new,++) where++import Data.Array.Accelerate.LLVM.PTX.Context ( Context, withContext )+import qualified Data.Array.Accelerate.LLVM.PTX.Context as Context+import qualified Data.Array.Accelerate.Array.Remote as Remote+import qualified Data.Array.Accelerate.LLVM.PTX.Debug as Debug+import {-# SOURCE #-} Data.Array.Accelerate.LLVM.PTX.Execute.Event++import qualified Foreign.CUDA.Ptr as CUDA+import qualified Foreign.CUDA.Driver as CUDA++import Formatting+++-- Remote memory tables. This builds upon the LRU-cached memory tables provided+-- by the base Accelerate package.+--+type MemoryTable = Remote.MemoryTable CUDA.DevicePtr (Maybe Event)+++-- | Create a new PTX memory table. This is specific to a given PTX target, as+-- devices arrays are unique to a CUDA context.+--+{-# INLINEABLE new #-}+new :: Context -> IO MemoryTable+new !ctx = Remote.new freeRemote+ where+ freeRemote :: CUDA.DevicePtr a -> IO ()+ freeRemote !ptr = do+ message ("freeRemote " % shown) ptr+ Context.contextFinalizeResource ctx $+ withContext ctx (CUDA.free ptr)+++-- Debugging+-- ---------++{-# INLINE message #-}+message :: Format (IO ()) a -> a+message fmt = Debug.traceM Debug.dump_gc ("gc: " % fmt)+
+ src/Data/Array/Accelerate/LLVM/PTX/CodeGen.hs view
@@ -0,0 +1,45 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}+-- |+-- Module : Data.Array.Accelerate.LLVM.PTX.CodeGen+-- Copyright : [2014..2020] The Accelerate Team+-- License : BSD3+--+-- Maintainer : Trevor L. McDonell <trevor.mcdonell@gmail.com>+-- Stability : experimental+-- Portability : non-portable (GHC extensions)+--++module Data.Array.Accelerate.LLVM.PTX.CodeGen (++ KernelMetadata(..),++) where++-- accelerate+import Data.Array.Accelerate.LLVM.CodeGen++import Data.Array.Accelerate.LLVM.PTX.CodeGen.Base+import Data.Array.Accelerate.LLVM.PTX.CodeGen.Fold+import Data.Array.Accelerate.LLVM.PTX.CodeGen.FoldSeg+import Data.Array.Accelerate.LLVM.PTX.CodeGen.Generate+import Data.Array.Accelerate.LLVM.PTX.CodeGen.Intrinsic ()+import Data.Array.Accelerate.LLVM.PTX.CodeGen.Map+import Data.Array.Accelerate.LLVM.PTX.CodeGen.Permute+import Data.Array.Accelerate.LLVM.PTX.CodeGen.Scan+import Data.Array.Accelerate.LLVM.PTX.CodeGen.Stencil+import Data.Array.Accelerate.LLVM.PTX.CodeGen.Transform+import Data.Array.Accelerate.LLVM.PTX.Target+++instance Skeleton PTX where+ map = mkMap+ generate = mkGenerate+ transform = mkTransform+ fold = mkFold+ foldSeg = mkFoldSeg+ scan = mkScan+ scan' = mkScan'+ permute = mkPermute+ stencil1 = mkStencil1+ stencil2 = mkStencil2+
+ src/Data/Array/Accelerate/LLVM/PTX/CodeGen/Base.hs view
@@ -0,0 +1,825 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE MultiWayIf #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE ViewPatterns #-}+{-# OPTIONS_GHC -Wno-orphans #-}+-- |+-- Module : Data.Array.Accelerate.LLVM.PTX.CodeGen.Base+-- Copyright : [2014..2020] The Accelerate Team+-- License : BSD3+--+-- Maintainer : Trevor L. McDonell <trevor.mcdonell@gmail.com>+-- Stability : experimental+-- Portability : non-portable (GHC extensions)+--++module Data.Array.Accelerate.LLVM.PTX.CodeGen.Base (++ -- Types+ DeviceProperties, KernelMetadata(..),++ -- Thread identifiers+ blockDim, gridDim, threadIdx, blockIdx, warpSize,+ gridSize, globalThreadIdx,++ -- Other intrinsics+ laneId, warpId,+ laneMask_eq, laneMask_lt, laneMask_le, laneMask_gt, laneMask_ge,+ atomicAdd_f,+ nanosleep,++ -- Barriers and synchronisation+ __syncthreads, __syncthreads_count, __syncthreads_and, __syncthreads_or,+ __syncwarp, __syncwarp_mask,+ __threadfence_block, __threadfence_grid,++ -- Warp shuffle instructions+ __shfl_up, __shfl_down, __shfl_idx, __broadcast,++ -- Shared memory+ staticSharedMem,+ sharedMemorySizeAdd,+ dynamicSharedMem,+ sharedMemAddrSpace, sharedMemVolatility,++ -- Kernel definitions+ (+++),+ makeOpenAcc, makeOpenAccWith,++) where++import Data.Primitive.Vec+import Data.Array.Accelerate.Error+import Data.Array.Accelerate.LLVM.CodeGen.Arithmetic as A+import Data.Array.Accelerate.LLVM.CodeGen.Base+import Data.Array.Accelerate.LLVM.CodeGen.IR+import Data.Array.Accelerate.LLVM.CodeGen.Module+import Data.Array.Accelerate.LLVM.CodeGen.Monad+import Data.Array.Accelerate.LLVM.CodeGen.Ptr+import Data.Array.Accelerate.LLVM.CodeGen.Sugar+import Data.Array.Accelerate.LLVM.Compile.Cache+import Data.Array.Accelerate.LLVM.PTX.Analysis.Launch+import Data.Array.Accelerate.LLVM.PTX.Target+import Data.Array.Accelerate.Representation.Array+import Data.Array.Accelerate.Representation.Elt+import Data.Array.Accelerate.Representation.Shape+import Data.Array.Accelerate.Representation.Type+import qualified Data.Array.Accelerate.LLVM.CodeGen.Constant as A++import Foreign.CUDA.Analysis ( Compute(..), computeCapability )+import qualified Foreign.CUDA.Analysis as CUDA++import LLVM.AST.Type.Constant+import LLVM.AST.Type.Downcast+import LLVM.AST.Type.Function+import LLVM.AST.Type.GetElementPtr+import LLVM.AST.Type.Instruction+import LLVM.AST.Type.Instruction.Atomic+import qualified LLVM.AST.Type.Instruction.RMW as RMW+import LLVM.AST.Type.Instruction.Volatile+import LLVM.AST.Type.Metadata+import LLVM.AST.Type.Name+import LLVM.AST.Type.Operand+import LLVM.AST.Type.Representation+import qualified Data.Array.Accelerate.LLVM.Internal.LLVMPretty as LP++import Control.Applicative+import Control.Monad ( void )+import Control.Monad.Reader ( asks )+import Data.Bits+import Data.Proxy+import Data.String+import Foreign.Storable+import Prelude as P++import GHC.TypeLits++++-- Thread identifiers+-- ------------------++-- | Read the builtin registers that store CUDA thread and grid identifiers+--+-- <https://github.com/llvm-mirror/llvm/blob/master/include/llvm/IR/IntrinsicsNVVM.td>+--+specialPTXReg :: Label -> CodeGen PTX (Operands Int32)+specialPTXReg f =+ call (Body type' (Just Tail) f) [NoUnwind, ReadNone]++blockDim, gridDim, threadIdx, blockIdx, warpSize :: CodeGen PTX (Operands Int32)+blockDim = specialPTXReg "llvm.nvvm.read.ptx.sreg.ntid.x"+gridDim = specialPTXReg "llvm.nvvm.read.ptx.sreg.nctaid.x"+threadIdx = specialPTXReg "llvm.nvvm.read.ptx.sreg.tid.x"+blockIdx = specialPTXReg "llvm.nvvm.read.ptx.sreg.ctaid.x"+warpSize = specialPTXReg "llvm.nvvm.read.ptx.sreg.warpsize"++laneId :: CodeGen PTX (Operands Int32)+laneId = specialPTXReg "llvm.nvvm.read.ptx.sreg.laneid"++laneMask_eq, laneMask_lt, laneMask_le, laneMask_gt, laneMask_ge :: CodeGen PTX (Operands Int32)+laneMask_eq = specialPTXReg "llvm.nvvm.read.ptx.sreg.lanemask.eq"+laneMask_lt = specialPTXReg "llvm.nvvm.read.ptx.sreg.lanemask.lt"+laneMask_le = specialPTXReg "llvm.nvvm.read.ptx.sreg.lanemask.le"+laneMask_gt = specialPTXReg "llvm.nvvm.read.ptx.sreg.lanemask.gt"+laneMask_ge = specialPTXReg "llvm.nvvm.read.ptx.sreg.lanemask.ge"+++-- | NOTE: The special register %warpid as volatile value and is not guaranteed+-- to be constant over the lifetime of a thread or thread block.+--+-- http://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#sm-id-and-warp-id+--+-- http://docs.nvidia.com/cuda/parallel-thread-execution/index.html#special-registers-warpid+--+warpId :: CodeGen PTX (Operands Int32)+warpId = do+ dev <- liftCodeGen $ asks ptxDeviceProperties+ tid <- threadIdx+ A.quot integralType tid (A.liftInt32 (P.fromIntegral (CUDA.warpSize dev)))++_warpId :: CodeGen PTX (Operands Int32)+_warpId = specialPTXReg "llvm.ptx.read.warpid"+++-- | The size of the thread grid+--+-- > gridDim.x * blockDim.x+--+gridSize :: CodeGen PTX (Operands Int32)+gridSize = do+ ncta <- gridDim+ nt <- blockDim+ mul numType ncta nt+++-- | The global thread index+--+-- > blockDim.x * blockIdx.x + threadIdx.x+--+globalThreadIdx :: CodeGen PTX (Operands Int32)+globalThreadIdx = do+ ntid <- blockDim+ ctaid <- blockIdx+ tid <- threadIdx+ --+ u <- mul numType ntid ctaid+ v <- add numType tid u+ return v+++{--+-- | Generate function parameters that will specify the first and last (linear)+-- index of the array this kernel should evaluate.+--+gangParam :: (Operands Int, Operands Int, [LLVM.Parameter])+gangParam =+ let start = "ix.start"+ end = "ix.end"+ in+ (local start, local end, parameter start ++ parameter end )+--}+++-- Barriers and synchronisation+-- ----------------------------++-- | Call a built-in CUDA synchronisation intrinsic+--+barrier :: Label -> CodeGen PTX ()+barrier f = void $ call (Body VoidType (Just Tail) f) [NoUnwind, NoDuplicate, Convergent]++barrier_op :: Label -> Operands Int32 -> CodeGen PTX (Operands Int32)+barrier_op f x = call (Lam primType (op integralType x) (Body type' (Just Tail) f)) [NoUnwind, NoDuplicate, Convergent]+++-- | Wait until all threads in the thread block have reached this point, and all+-- global and shared memory accesses made by these threads prior to the+-- __syncthreads() are visible to all threads in the block.+--+-- <http://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#synchronization-functions>+--+__syncthreads :: CodeGen PTX ()+__syncthreads = barrier "llvm.nvvm.barrier0"++-- | Identical to __syncthreads() with the additional feature that it returns+-- the number of threads in the block for which the predicate evaluates to+-- non-zero.+--+__syncthreads_count :: Operands Int32 -> CodeGen PTX (Operands Int32)+__syncthreads_count = barrier_op "llvm.nvvm.barrier0.popc"++-- | Identical to __syncthreads() with the additional feature that it returns+-- non-zero iff the predicate evaluates to non-zero for all threads in the+-- block.+--+__syncthreads_and :: Operands Int32 -> CodeGen PTX (Operands Int32)+__syncthreads_and = barrier_op "llvm.nvvm.barrier0.and"++-- | Identical to __syncthreads() with the additional feature that it returns+-- non-zero iff the predicate evaluates to non-zero for any thread in the block.+--+__syncthreads_or :: Operands Int32 -> CodeGen PTX (Operands Int32)+__syncthreads_or = barrier_op "llvm.nvvm.barrier0.or"+++-- | Wait until all warp lanes have reached this point.+--+__syncwarp :: HasCallStack => CodeGen PTX ()+__syncwarp = __syncwarp_mask (liftWord32 0xffffffff)++-- | Wait until all warp lanes named in the mask have executed a __syncwarp()+-- with the same mask. All non-exited threads named in the mask must execute+-- a corresponding __syncwarp with the same mask, or the result is undefined.+--+-- This guarantees memory ordering among threads participating in the barrier.+--+-- Requires LLVM-6.0 or higher.+-- Only required for devices of SM7 and later.+--+__syncwarp_mask :: HasCallStack => Operands Word32 -> CodeGen PTX ()+__syncwarp_mask mask = do+ llvmver <- getLLVMversion+ dev <- liftCodeGen $ asks ptxDeviceProperties+ case (computeCapability dev >= Compute 7 0, llvmver >= 6) of+ (True, True) -> void $ call (Lam primType (op primType mask) (Body VoidType (Just Tail) "llvm.nvvm.bar.warp.sync")) [NoUnwind, NoDuplicate, Convergent]+ (True, False) -> internalError "LLVM-6.0 or above is required for Volta devices and later"+ (False, _) -> return ()+++-- | Ensure that all writes to shared and global memory before the call to+-- __threadfence_block() are observed by all threads in the *block* of the+-- calling thread as occurring before all writes to shared and global memory+-- made by the calling thread after the call.+--+-- <http://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#memory-fence-functions>+--+__threadfence_block :: CodeGen PTX ()+__threadfence_block = barrier "llvm.nvvm.membar.cta"+++-- | As __threadfence_block(), but the synchronisation is for *all* thread blocks.+-- In CUDA this is known simply as __threadfence().+--+__threadfence_grid :: CodeGen PTX ()+__threadfence_grid = barrier "llvm.nvvm.membar.gl"+++-- Atomic functions+-- ----------------++-- LLVM provides atomic instructions for integer arguments only. CUDA provides+-- additional support for atomic add on floating point types, which can be+-- accessed through the following intrinsics.+--+-- Double precision is supported on Compute 6.0 devices and later. Half+-- precision is supported on Compute 7.0 devices and later.+--+-- LLVM-4.0 currently lacks support for this intrinsic, however it is+-- accessible via inline assembly.+--+-- LLVM-9 integrated floating-point atomic operations into the AtomicRMW+-- instruction, but this functionality is missing from llvm-hs-9. We access+-- it via inline assembly..+--+-- <https://github.com/AccelerateHS/accelerate/issues/363>+--+atomicAdd_f :: HasCallStack => FloatingType a -> Operand (Ptr a) -> Operand a -> CodeGen PTX ()+atomicAdd_f t addr val = do+ llvmver <- getLLVMversion+ if | llvmver >= 10 ->+ void . instr' $ AtomicRMW (FloatingNumType t) NonVolatile RMW.Add addr val (CrossThread, AcquireRelease)++ | otherwise ->+ internalError "LLVM < 10 not supported"+++-- Warp shuffle functions+-- ----------------------+--+-- Exchange a variable between threads within a warp. Requires compute+-- capability 3.0 or higher.+--+-- <https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#warp-shuffle-functions>+--+-- Each of the shfl primitives also exists in ".p" form. This version+-- returns, alongside the normal value, a boolean that returns whether the+-- source lane was in range. This could be useful when doing bounds checks+-- in folds and scans.+--++data ShuffleOp+ = Idx -- ^ Direct copy from indexed lane+ | Up -- ^ Copy from a lane with lower ID relative to the caller+ | Down -- ^ Copy from a lane with higher ID relative to the caller+ | XOR -- ^ Copy from a lane based on bitwise XOR of own lane ID++-- | Each thread gets the value provided by lower threads+--+__shfl_up :: TypeR a -> Operands a -> Operands Word32 -> CodeGen PTX (Operands a)+__shfl_up = shfl Up++-- | Each thread gets the value provided by higher threads+--+__shfl_down :: TypeR a -> Operands a -> Operands Word32 -> CodeGen PTX (Operands a)+__shfl_down = shfl Down++-- | shfl_idx takes an argument representing the source lane index.+--+__shfl_idx :: TypeR a -> Operands a -> Operands Word32 -> CodeGen PTX (Operands a)+__shfl_idx = shfl Idx++-- | Distribute the value from lane 0 across the warp+--+__broadcast :: TypeR a -> Operands a -> CodeGen PTX (Operands a)+__broadcast aR a = __shfl_idx aR a (liftWord32 0)+++shfl :: ShuffleOp+ -> TypeR a+ -> Operands a+ -> Operands Word32+ -> CodeGen PTX (Operands a)+shfl sop tR val delta = go tR val+ where+ delta' :: Operand Word32+ delta' = op integralType delta++ go :: TypeR s -> Operands s -> CodeGen PTX (Operands s)+ go TupRunit OP_Unit = return OP_Unit+ go (TupRpair aR bR) (OP_Pair a b) = OP_Pair <$> go aR a <*> go bR b+ go (TupRsingle t) a = scalar t a++ scalar :: ScalarType s -> Operands s -> CodeGen PTX (Operands s)+ scalar (SingleScalarType t) = single t+ scalar (VectorScalarType t) = vector t++ single :: SingleType s -> Operands s -> CodeGen PTX (Operands s)+ single (NumSingleType t) = num t++ vector :: forall n s. VectorType (Vec n s) -> Operands (Vec n s) -> CodeGen PTX (Operands (Vec n s))+ vector v@(VectorType w t) a+ | SingleDict <- singleDict t+ = let bytes = sizeOf (undefined::s)+ (m,r) = P.quotRem (w * bytes) 4++ withSomeNat :: Int -> (forall m. KnownNat m => Proxy m -> b) -> b+ withSomeNat n k =+ case someNatVal (toInteger n) of+ Nothing -> error "Welcome to overthinkers club. The first rule of overthinkers club is: yet to be decided."+ Just (SomeNat p) -> k p+ in+ if r == 0+ -- bitcast into a <m x i32> vector+ -- special case for a single element vector+ then+ if m == 1+ then do+ b <- A.bitcast (VectorScalarType v) (scalarType @Int32) a+ c <- integral (integralType @Int32) b+ d <- A.bitcast scalarType (VectorScalarType v) c+ return d++ else+ let+ vec :: forall m. KnownNat m => Proxy m -> CodeGen PTX (Operands (Vec n s))+ vec _ = do+ let v' = VectorType m (singleType @Int32)++ b <- A.bitcast (VectorScalarType v) (VectorScalarType v') a++ let c = op v' b++ repack :: Int32 -> CodeGen PTX (Operands (Vec m Int32))+ repack 0 = return $ ir v' (A.undef (VectorScalarType v'))+ repack i = do+ d <- instr $ ExtractElement (i-1) c+ e <- integral integralType d+ f <- repack (i-1)+ g <- instr $ InsertElement (i-1) (op v' f) (op integralType e)+ return g++ h <- repack (P.fromIntegral m)+ i <- A.bitcast (VectorScalarType v') (VectorScalarType v) h+ return i+ in+ withSomeNat m vec++ -- Round up to the next multiple of 32:+ --+ -- 1. bitcast to an integer of the same number of bits: e.g. bitcast <3 x i16> i48+ -- 2. extend that to the next multiple of 32: e.g. zext i48 i64+ -- 3. bitcast to <m+1 x i32>: e.g. bitcast i64 <2 x i32>+ --+ else+ let raw :: LP.Type -> LP.Instr -> CodeGen PTX (LP.Typed LP.Value)+ raw ty ins = do+ name <- freshLocalName+ instr_ (LP.Result (nameToPrettyI name) ins [] [])+ return (LP.Typed ty (LP.ValIdent (nameToPrettyI name)))++ rawUp :: Type u -> LP.Instr -> CodeGen PTX (Operand u)+ rawUp ty ins = do+ name <- freshLocalName+ instr_ (LP.Result (nameToPrettyI name) ins [] [])+ return (LocalReference ty name)+++ vec :: forall m. KnownNat m => Proxy m -> CodeGen PTX (Operands (Vec n s))+ vec _ = do+ let t0Up :: Type (Vec n s)+ t0Up = PrimType (ScalarPrimType (VectorScalarType v))+ t0 = downcast t0Up++ t1 = LP.PrimType (LP.Integer (P.fromIntegral ((w*bytes) * 8)))+ t2 = LP.PrimType (LP.Integer (P.fromIntegral ((m+1) * 4 * 8)))++ v' :: VectorType (Vec m Int32)+ v' = VectorType (m+1) (singleType @Int32)+ t3Up :: Type (Vec m Int32)+ t3Up = PrimType (ScalarPrimType (VectorScalarType v'))+ t3 = downcast t3Up++ b <- raw t1 (LP.Conv LP.BitCast (downcast (op v a)) t1)+ c <- raw t2 (LP.Conv (LP.ZExt False) b t2)+ d <- rawUp t3Up (LP.Conv LP.BitCast c t3)+ e <- vector v' (ir v' d)+ f <- raw t2 (LP.Conv LP.BitCast (downcast (op v' e)) t2)+ g <- raw t1 (LP.Conv (LP.Trunc False False) f t1)+ h <- rawUp t0Up (LP.Conv LP.BitCast g t0)+ return (ir v h)+ in+ withSomeNat (m+1) vec++ num :: NumType s -> Operands s -> CodeGen PTX (Operands s)+ num (IntegralNumType t) = integral t+ num (FloatingNumType t) = floating t++ integral :: forall s. IntegralType s -> Operands s -> CodeGen PTX (Operands s)+ integral TypeInt32 a = shfl_op sop ShuffleInt32 delta' a+ integral t a+ | IntegralDict <- integralDict t+ = case finiteBitSize (undefined::s) of+ 64 -> do+ let ta = SingleScalarType (NumSingleType (IntegralNumType t))+ tb = scalarType @(Vec 2 Int32)+ --+ b <- A.bitcast ta tb a+ c <- vector (VectorType 2 singleType) b+ d <- A.bitcast tb ta c+ return d++ _ -> do+ b <- A.fromIntegral t (numType @Int32) a+ c <- integral integralType b+ d <- A.fromIntegral integralType (IntegralNumType t) c+ return d++ floating :: FloatingType s -> Operands s -> CodeGen PTX (Operands s)+ floating TypeFloat a = shfl_op sop ShuffleFloat delta' a+ floating TypeDouble a = do+ b <- A.bitcast scalarType (scalarType @(Vec 2 Int32)) a+ c <- vector (VectorType 2 singleType) b+ d <- A.bitcast scalarType (scalarType @Double) c+ return d+ floating TypeHalf a = do+ b <- A.bitcast scalarType (scalarType @Int16) a+ c <- integral integralType b+ d <- A.bitcast scalarType (scalarType @Half) c+ return d+++data ShuffleType a where+ ShuffleInt32 :: ShuffleType Int32+ ShuffleFloat :: ShuffleType Float++shfl_op+ :: forall a.+ ShuffleOp+ -> ShuffleType a+ -> Operand Word32 -- delta+ -> Operands a -- value to give+ -> CodeGen PTX (Operands a) -- value received+shfl_op sop t delta val = do+ dev <- liftCodeGen $ asks ptxDeviceProperties++ let+ -- The CUDA __shfl* instruction take an optional final parameter+ -- which is the warp size. Setting this value to something (always+ -- a power-of-two) other than 32 emulates the shfl behaviour at that+ -- warp size. Behind the scenes, a bunch of instructions happen with+ -- this width parameter before they get passed into the actual shfl+ -- instruction. Here, we have to directly set them to the 'actual'+ -- width parameter. The formula that clang compiles to is in the+ -- comments+ --+ width :: Operand Int32+ width = case sop of+ Up -> A.integral integralType 0 -- ((32 - warpSize) `shiftL` 8)+ Down -> A.integral integralType 31 -- ((32 - warpSize) `shiftL` 8) `or` 31+ Idx -> A.integral integralType 31 -- ((32 - warpSize) `shiftL` 8) `or` 31+ XOR -> A.integral integralType 31 -- ((32 - warpSize) `shiftL` 8) `or` 31++ -- Starting CUDA 9.0, the normal `shfl` primitives are deprecated in+ -- favour of the newer `shfl_sync` primitives. They behave the same+ -- way, except they start with a 'mask' argument specifying which+ -- threads participate in the shuffle.+ --+ mask :: Operand Int32+ mask = A.integral integralType (-1) -- all threads participate++ useSyncShfl = CUDA.computeCapability dev >= Compute 7 0++ call' = if useSyncShfl+ then call . Lam primType mask+ else call++ sync = if useSyncShfl then "sync." else ""+ asm = "llvm.nvvm.shfl."+ <> sync+ <> case sop of+ Idx -> "idx."+ Up -> "up."+ Down -> "down."+ XOR -> "bfly."+ <> case t of+ ShuffleInt32 -> "i32"+ ShuffleFloat -> "f32"++ t_val = case t of+ ShuffleInt32 -> primType :: PrimType Int32+ ShuffleFloat -> primType :: PrimType Float++ call' (Lam t_val (op t_val val) (Lam primType delta (Lam primType width (Body (PrimType t_val) (Just Tail) asm)))) [Convergent, NoUnwind, InaccessibleMemOnly]+++-- Shared memory+-- -------------++sharedMemAddrSpace :: AddrSpace+sharedMemAddrSpace = AddrSpace 3++sharedMemVolatility :: Volatility+sharedMemVolatility = Volatile+++-- Declare a new statically allocated array in the __shared__ memory address+-- space, with enough storage to contain the given number of elements.+--+-- Previously, like initialiseDynamicSharedMemory, this function declared an+-- external global, e.g. for 1 i64:+-- @sdata = external addrspace(3) global [1 x i64], align 8+-- This would correspond to the following CUDA source:+-- extern __shared__ int64_t sdata[1];+--+-- But this CUDA C++ is rejected by Clang. When LLVM is fed LLVM IR, however,+-- things are more subtle; in the old llvm-hs backend where we linked against+-- LLVM, with LLVM 15, the above IR (defining @0) was accepted. However,+-- passing this same IR to Clang 18 with the llvm-pretty backend (yes I'm aware+-- the clang version is also changing here), clang first calls ptxas and then+-- nvlink; nvlink complains:+-- Undefined reference to 'sdata' in '/tmp/test-409abe.cubin'+-- When linking against LLVM 15, nvlink is never invoked, but instead ptxas is+-- _not_ given the -c flag and it immediately produces a SASS file.+--+-- Because Clang doesn't even accept the corresponding C++ code, but does+-- accept this:+-- __shared__ int64_t sdata[1];+-- the global created in this function was changed to be of internal linkage+-- instead. The assigned value is 'undef', just like what Clang generates for+-- the internal sdata C++ declaration.+staticSharedMem+ :: TypeR e+ -> Word64+ -> CodeGen PTX (IRArray (Vector e))+staticSharedMem tp n = do+ ad <- go tp+ return $ IRArray { irArrayRepr = ArrayR dim1 tp+ , irArrayShape = OP_Pair OP_Unit $ OP_Int $ A.integral integralType $ P.fromIntegral n+ , irArrayData = ad+ , irArrayAddrSpace = sharedMemAddrSpace+ , irArrayVolatility = sharedMemVolatility+ }+ where+ go :: TypeR s -> CodeGen PTX (Operands s)+ go TupRunit = return OP_Unit+ go (TupRpair t1 t2) = OP_Pair <$> go t1 <*> go t2+ go tt@(TupRsingle t) = do+ -- Declare a new global reference for the statically allocated array+ -- located in the __shared__ memory space.+ nm <- freshGlobalName+ let arrt = ArrayPrimType n t+ ptrarrt = PrimType (PtrPrimType arrt sharedMemAddrSpace)+ sm <- return $ ConstantOperand $ GlobalReference ptrarrt nm+ declareGlobalVar $ LP.Global+ { LP.globalSym = nameToPrettyS nm+ , LP.globalAttrs = LP.GlobalAttrs+ { LP.gaLinkage = Just LP.Internal+ , LP.gaVisibility = Nothing+ , LP.gaAddrSpace = sharedMemAddrSpace+ , LP.gaConstant = False }+ , LP.globalType = LP.Array n (downcast t)+ , LP.globalValue = Just LP.ValUndef+ , LP.globalAlign = Just (4 `P.max` P.fromIntegral (bytesElt tt))+ , LP.globalMetadata = mempty+ }++ -- Return a pointer to the first element of the __shared__ memory array.+ -- We do this rather than just returning the global reference directly due+ -- to how __shared__ memory needs to be indexed with the GEP instruction.+ p <- instr' $ GetElementPtr (GEP (PrimType arrt) sm (A.num numType 0 :: Operand Int32)+ (GEPArray (A.num numType 0 :: Operand Int32) (GEPEmpty (ScalarPrimType t))))+ q <- instr' $ PtrCast (PtrPrimType (ScalarPrimType t) sharedMemAddrSpace) p++ return $ ir t (unPtr q)+++-- External declaration in shared memory address space. This must be declared in+-- order to access memory allocated dynamically by the CUDA driver. This results+-- in the following global declaration:+--+-- > @__shared__ = external addrspace(3) global [0 x i8]+--+initialiseDynamicSharedMemory :: CodeGen PTX (Operand (Ptr Int8))+initialiseDynamicSharedMemory = do+ declareGlobalVar $ LP.Global+ { LP.globalSym = LP.Symbol "__shared__"+ , LP.globalAttrs = LP.GlobalAttrs+ { LP.gaLinkage = Just LP.External+ , LP.gaVisibility = Nothing+ , LP.gaAddrSpace = sharedMemAddrSpace+ , LP.gaConstant = False }+ , LP.globalType = LP.Array 0 (LP.PrimType (LP.Integer 8))+ , LP.globalValue = Nothing+ , LP.globalAlign = Nothing+ , LP.globalMetadata = mempty+ }+ return $ ConstantOperand+ $ ConstantGetElementPtr (GEP (PrimType (ArrayPrimType 0 (scalarType @Int8)))+ (GlobalReference (PrimType (PtrPrimType (ArrayPrimType 0 scalarType) sharedMemAddrSpace)) "__shared__")+ (ScalarConstant (scalarType @Int32) 0)+ (GEPArray (ScalarConstant (scalarType @Int32) 0) (GEPEmpty primType)))++sharedMemorySizeAdd+ :: TypeR e+ -> Int -- number of array elements+ -> Int -- #bytes of shared memory the have already been allocated+ -> Int+sharedMemorySizeAdd tp n i = case tp of+ TupRunit -> i+ TupRpair t2 t1 ->+ -- First handle the second element of the tuple, then the first,+ -- to match the behaviour of dynamicSharedMem+ sharedMemorySizeAdd t2 n $ sharedMemorySizeAdd t1 n i+ TupRsingle t ->+ let+ bytes = bytesElt tp+ -- Align 'i' to the alignment of t+ aligned = alignTo (scalarAlignment t) i+ in+ aligned + bytes * n++-- Declare a new dynamically allocated array in the __shared__ memory space+-- with enough space to contain the given number of elements.+--+dynamicSharedMem+ :: forall e int.+ TypeR e+ -> IntegralType int+ -> Operands int -- number of array elements+ -> Operands int -- #bytes of shared memory that have already been allocated+ -> CodeGen PTX (IRArray (Vector e))+dynamicSharedMem tp int n@(op int -> m) (op int -> offset)+ | IntegralDict <- integralDict int = do+ smem <- initialiseDynamicSharedMemory+ let+ numTp = IntegralNumType int++ go :: TypeR s -> Operand int -> CodeGen PTX (Operand int, Operands s)+ go TupRunit i = return (i, OP_Unit)+ go (TupRpair t2 t1) i0 = do+ (i1, p1) <- go t1 i0+ (i2, p2) <- go t2 i1+ return $ (i2, OP_Pair p2 p1)+ go (TupRsingle t) i = do+ let bytes = bytesElt (TupRsingle t)+ let align = scalarAlignment t+ i' <- instr' $ Add numTp i (A.integral int $ P.fromIntegral $ align - 1)+ aligned <- instr' $ BAnd int i' (A.integral int $ P.fromIntegral $ Data.Bits.complement $ align - 1)+ p <- instr' $ GetElementPtr (GEP1 scalarType smem aligned)+ q <- instr' $ PtrCast (PtrPrimType (ScalarPrimType t) sharedMemAddrSpace) p+ a <- instr' $ Mul numTp m (A.integral int (P.fromIntegral bytes))+ b <- instr' $ Add numTp aligned a+ return (b, ir t (unPtr q))+ --+ (_, ad) <- go tp offset+ sz <- A.fromIntegral int (numType :: NumType Int) n+ return $ IRArray { irArrayRepr = ArrayR dim1 tp+ , irArrayShape = OP_Pair OP_Unit sz+ , irArrayData = ad+ , irArrayAddrSpace = sharedMemAddrSpace+ , irArrayVolatility = sharedMemVolatility+ }+++-- Other functions+-- ---------------++-- Sleep the thread for (approximately) the given number of nanoseconds.+-- Requires compute capability >= 7.0+--+nanosleep :: Operands Int32 -> CodeGen PTX ()+nanosleep ns = do+ -- This is an acc prelude function because it requires inline assembly, and+ -- llvm-pretty does not yet support caling inline assembly snippets. Thus we+ -- manually wrap the assembly in an inlineable function and call that.+ let label = makeAccPreludeLabel "nanosleep"+ void $ instr (Call (Lam primType (op integralType ns) (Body VoidType (Just Tail) (Right label))))+++-- Global kernel definitions+-- -------------------------++data instance KernelMetadata PTX = KM_PTX LaunchConfig++-- | Combine kernels into a single program+--+(+++) :: IROpenAcc PTX aenv a -> IROpenAcc PTX aenv a -> IROpenAcc PTX aenv a+IROpenAcc k1 +++ IROpenAcc k2 = IROpenAcc (k1 ++ k2)+++-- | Create a single kernel program with the default launch configuration.+--+makeOpenAcc+ :: UID+ -> Label+ -> [LP.Typed LP.Ident]+ -> CodeGen PTX ()+ -> CodeGen PTX (IROpenAcc PTX aenv a)+makeOpenAcc uid name param kernel = do+ dev <- liftCodeGen $ asks ptxDeviceProperties+ makeOpenAccWith (simpleLaunchConfig dev) uid name param kernel++-- | Create a single kernel program with the given launch analysis information.+--+makeOpenAccWith+ :: LaunchConfig+ -> UID+ -> Label+ -> [LP.Typed LP.Ident]+ -> CodeGen PTX ()+ -> CodeGen PTX (IROpenAcc PTX aenv a)+makeOpenAccWith config uid name param kernel = do+ body <- makeKernel config (name <> fromString ('_' : show uid)) param kernel+ return $ IROpenAcc [body]++-- | Create a complete kernel function by running the code generation process+-- specified in the final parameter.+--+makeKernel+ :: LaunchConfig+ -> Label+ -> [LP.Typed LP.Ident]+ -> CodeGen PTX ()+ -> CodeGen PTX (Kernel PTX aenv a)+makeKernel config name param kernel = do+ _ <- kernel+ code <- createBlocks+ let define = LP.Define+ { LP.defLinkage = Nothing+ , LP.defVisibility = Nothing+ , LP.defRetType = LP.PrimType LP.Void+ , LP.defName = labelToPrettyS name+ , LP.defArgs = param+ , LP.defVarArgs = False+ , LP.defAttrs = []+ , LP.defSection = Nothing+ , LP.defGC = Nothing+ , LP.defBody = code+ , LP.defMetadata = mempty+ , LP.defComdat = Nothing+ }+ addMetadata "nvvm.annotations"+ [ Just . MetadataConstantOperand+ $ LP.Typed (LP.defFunType define) (LP.ValSymbol (labelToPrettyS name))+ , Just . MetadataStringOperand $ "kernel"+ , Just . MetadataConstantOperand $ LP.Typed (LP.PrimType (LP.Integer 32)) (LP.ValInteger 1)+ ]+ return $ Kernel+ { kernelMetadata = KM_PTX config+ , unKernel = define+ }++scalarAlignment :: ScalarType t -> Int+scalarAlignment t@(SingleScalarType _) = bytesElt (TupRsingle t)+scalarAlignment (VectorScalarType (VectorType _ t)) = bytesElt (TupRsingle $ SingleScalarType t)++-- Align 'ptr' to the given alignment.+-- Assumes 'align' is a power of 2.+alignTo :: Int -> Int -> Int+alignTo align ptr = (ptr + align - 1) .&. Data.Bits.complement (align - 1)
+ src/Data/Array/Accelerate/LLVM/PTX/CodeGen/Fold.hs view
@@ -0,0 +1,616 @@+{-# LANGUAGE GADTs #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RebindableSyntax #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE ViewPatterns #-}+-- |+-- Module : Data.Array.Accelerate.LLVM.PTX.CodeGen.Fold+-- Copyright : [2016..2020] The Accelerate Team+-- License : BSD3+--+-- Maintainer : Trevor L. McDonell <trevor.mcdonell@gmail.com>+-- Stability : experimental+-- Portability : non-portable (GHC extensions)+--++module Data.Array.Accelerate.LLVM.PTX.CodeGen.Fold+ where++import Data.Array.Accelerate.Representation.Array+import Data.Array.Accelerate.Representation.Shape hiding ( size )+import Data.Array.Accelerate.Representation.Type++import Data.Array.Accelerate.LLVM.CodeGen.Arithmetic as A+import Data.Array.Accelerate.LLVM.CodeGen.Array+import Data.Array.Accelerate.LLVM.CodeGen.Base+import Data.Array.Accelerate.LLVM.CodeGen.Constant+import Data.Array.Accelerate.LLVM.CodeGen.Environment+import Data.Array.Accelerate.LLVM.CodeGen.Exp+import Data.Array.Accelerate.LLVM.CodeGen.IR+import Data.Array.Accelerate.LLVM.CodeGen.Loop as Loop+import Data.Array.Accelerate.LLVM.CodeGen.Monad+import Data.Array.Accelerate.LLVM.CodeGen.Sugar+import Data.Array.Accelerate.LLVM.Compile.Cache++import Data.Array.Accelerate.LLVM.PTX.Analysis.Launch+import Data.Array.Accelerate.LLVM.PTX.CodeGen.Base+import Data.Array.Accelerate.LLVM.PTX.CodeGen.Generate+import Data.Array.Accelerate.LLVM.PTX.Target++import LLVM.AST.Type.Representation++import qualified Foreign.CUDA.Analysis as CUDA++import Control.Monad ( (>=>) )+import Control.Monad.Reader ( asks )+import Data.String ( fromString )+import Data.Bits as P+import Prelude as P+++-- Reduce an array along the innermost dimension. The reduction function must be+-- associative to allow for an efficient parallel implementation, but the+-- initial element does /not/ need to be a neutral element of operator.+--+-- TODO: Specialise for commutative operations (such as (+)) and those with+-- a neutral element {(+), 0}+--+mkFold+ :: forall aenv sh e.+ UID+ -> Gamma aenv+ -> ArrayR (Array sh e)+ -> IRFun2 PTX aenv (e -> e -> e)+ -> Maybe (IRExp PTX aenv e)+ -> MIRDelayed PTX aenv (Array (sh, Int) e)+ -> CodeGen PTX (IROpenAcc PTX aenv (Array sh e))+mkFold uid aenv repr f z acc = case z of+ Just z' -> (+++) <$> codeFold <*> mkFoldFill uid aenv repr z'+ Nothing -> codeFold+ where+ codeFold = case repr of+ ArrayR ShapeRz tp -> mkFoldAll uid aenv tp f z acc+ _ -> mkFoldDim uid aenv repr f z acc+++-- Reduce an array to a single element.+--+-- Since reductions consume arrays that have been fused into them, parallel+-- reduction requires two separate kernels. At an example, take vector dot+-- product:+--+-- > dotp xs ys = fold (+) 0 (zipWith (*) xs ys)+--+-- 1. The first pass reads in the fused array data, in this case corresponding+-- to the function (\i -> (xs!i) * (ys!i)).+--+-- 2. The second pass reads in the manifest array data from the first step and+-- directly reduces the array. This can be done recursively in-place until+-- only a single element remains.+--+-- In both phases, thread blocks cooperatively reduce a stripe of the input (one+-- element per thread) to a single element, which is stored to the output array.+--+mkFoldAll+ :: forall aenv e.+ UID+ -> Gamma aenv -- ^ array environment+ -> TypeR e+ -> IRFun2 PTX aenv (e -> e -> e) -- ^ combination function+ -> MIRExp PTX aenv e -- ^ (optional) initial element for exclusive reductions+ -> MIRDelayed PTX aenv (Vector e) -- ^ input data+ -> CodeGen PTX (IROpenAcc PTX aenv (Scalar e))+mkFoldAll uid aenv tp combine mseed macc = do+ dev <- liftCodeGen $ asks ptxDeviceProperties+ foldr1 (+++) <$> sequence [ mkFoldAllS uid dev aenv tp combine mseed macc+ , mkFoldAllM1 uid dev aenv tp combine macc+ , mkFoldAllM2 uid dev aenv tp combine mseed+ ]+++-- Reduction to an array to a single element, for small arrays which can be+-- processed by a single thread block.+--+mkFoldAllS+ :: forall aenv e.+ UID+ -> DeviceProperties -- ^ properties of the target GPU+ -> Gamma aenv -- ^ array environment+ -> TypeR e+ -> IRFun2 PTX aenv (e -> e -> e) -- ^ combination function+ -> MIRExp PTX aenv e -- ^ (optional) initial element for exclusive reductions+ -> MIRDelayed PTX aenv (Vector e) -- ^ input data+ -> CodeGen PTX (IROpenAcc PTX aenv (Scalar e))+mkFoldAllS uid dev aenv tp combine mseed marr =+ let+ (arrOut, paramOut) = mutableArray (ArrayR dim0 tp) "out"+ (arrIn, paramIn) = delayedArray "in" marr+ paramEnv = envParam aenv+ --+ config = launchConfig dev (CUDA.incWarp dev) smem multipleOf multipleOfQ+ smem n = sharedMemorySizeAdd tp warps 0+ where+ ws = CUDA.warpSize dev+ warps = n `P.quot` ws+ in+ makeOpenAccWith config uid "foldAllS" (paramOut ++ paramIn ++ paramEnv) $ do++ tid <- threadIdx+ bd <- blockDim++ sh <- delayedExtent arrIn+ end <- shapeSize dim1 sh++ -- We can assume that there is only a single thread block+ start' <- return (liftInt32 0)+ end' <- i32 end+ i0 <- A.add numType start' tid+ sz <- A.sub numType end' start'+ when (A.lt singleType i0 sz) $ do++ -- Thread reads initial element and then participates in block-wide+ -- reduction.+ x0 <- app1 (delayedLinearIndex arrIn) =<< int i0+ r0 <- if (tp, A.eq singleType sz bd)+ then reduceBlock dev tp combine Nothing x0+ else reduceBlock dev tp combine (Just sz) x0++ when (A.eq singleType tid (liftInt32 0)) $+ writeArray TypeInt32 arrOut tid =<<+ case mseed of+ Nothing -> return r0+ Just z -> flip (app2 combine) r0 =<< z -- Note: initial element on the left++ return_+++-- Reduction of an entire array to a single element. This kernel implements step+-- one for reducing large arrays which must be processed by multiple thread+-- blocks.+--+mkFoldAllM1+ :: forall aenv e.+ UID+ -> DeviceProperties -- ^ properties of the target GPU+ -> Gamma aenv -- ^ array environment+ -> TypeR e+ -> IRFun2 PTX aenv (e -> e -> e) -- ^ combination function+ -> MIRDelayed PTX aenv (Vector e) -- ^ input data+ -> CodeGen PTX (IROpenAcc PTX aenv (Scalar e))+mkFoldAllM1 uid dev aenv tp combine marr =+ let+ (arrTmp, paramTmp) = mutableArray (ArrayR dim1 tp) "tmp"+ (arrIn, paramIn) = delayedArray "in" marr+ paramEnv = envParam aenv+ start = liftInt 0+ --+ config = launchConfig dev (CUDA.incWarp dev) smem const [|| const ||]+ smem n = sharedMemorySizeAdd tp warps 0+ where+ ws = CUDA.warpSize dev+ warps = n `P.quot` ws+ in+ makeOpenAccWith config uid "foldAllM1" (paramTmp ++ paramIn ++ paramEnv) $ do++ -- Each thread block cooperatively reduces a stripe of the input and stores+ -- that value into a temporary array at a corresponding index. Since the+ -- order of operations remains fixed, this method supports non-commutative+ -- reductions.+ --+ tid <- threadIdx+ bd <- int =<< blockDim+ sz <- indexHead <$> delayedExtent arrIn+ end <- shapeSize dim1 (irArrayShape arrTmp)++ imapFromTo start end $ \seg -> do++ -- Wait for all threads to catch up before beginning the stripe+ __syncthreads++ -- Bounds of the input array we will reduce between+ from <- A.mul numType seg bd+ step <- A.add numType from bd+ to <- A.min singleType sz step++ -- Threads cooperatively reduce this stripe+ reduceFromTo dev tp from to combine+ (app1 (delayedLinearIndex arrIn))+ (when (A.eq singleType tid (liftInt32 0)) . writeArray TypeInt arrTmp seg)++ return_+++-- Reduction of an array to a single element, (recursive) step 2 of multi-block+-- reduction algorithm.+--+mkFoldAllM2+ :: forall aenv e.+ UID+ -> DeviceProperties+ -> Gamma aenv+ -> TypeR e+ -> IRFun2 PTX aenv (e -> e -> e)+ -> MIRExp PTX aenv e+ -> CodeGen PTX (IROpenAcc PTX aenv (Scalar e))+mkFoldAllM2 uid dev aenv tp combine mseed =+ let+ (arrTmp, paramTmp) = mutableArray (ArrayR dim1 tp) "tmp"+ (arrOut, paramOut) = mutableArray (ArrayR dim1 tp) "out"+ paramEnv = envParam aenv+ start = liftInt 0+ --+ config = launchConfig dev (CUDA.incWarp dev) smem const [|| const ||]+ smem n = sharedMemorySizeAdd tp warps 0+ where+ ws = CUDA.warpSize dev+ warps = n `P.quot` ws+ in+ makeOpenAccWith config uid "foldAllM2" (paramTmp ++ paramOut ++ paramEnv) $ do++ -- Threads cooperatively reduce a stripe of the input (temporary) array+ -- output from the first phase, storing the results into another temporary.+ -- When only a single thread block remains, we have reached the final+ -- reduction step and add the initial element (for exclusive reductions).+ --+ tid <- threadIdx+ gd <- gridDim+ bd <- int =<< blockDim+ sz <- return $ indexHead (irArrayShape arrTmp)+ end <- shapeSize dim1 (irArrayShape arrOut)++ imapFromTo start end $ \seg -> do++ -- Wait for all threads to catch up before beginning the stripe+ __syncthreads++ -- Bounds of the input we will reduce between+ from <- A.mul numType seg bd+ step <- A.add numType from bd+ to <- A.min singleType sz step++ -- Threads cooperatively reduce this stripe+ reduceFromTo dev tp from to combine (readArray TypeInt arrTmp) $ \r ->+ when (A.eq singleType tid (liftInt32 0)) $+ writeArray TypeInt arrOut seg =<<+ case mseed of+ Nothing -> return r+ Just z -> if (tp, A.eq singleType gd (liftInt32 1))+ then flip (app2 combine) r =<< z -- Note: initial element on the left+ else return r++ return_+++-- Reduce an array of arbitrary rank along the innermost dimension only.+--+-- For simplicity, each element of the output (reduction along an+-- innermost-dimension index) is computed by a single thread block, meaning we+-- don't have to worry about inter-block synchronisation. A more balanced method+-- would be a segmented reduction (specialised, since the length of each segment+-- is known a priori).+--+mkFoldDim+ :: forall aenv sh e.+ UID+ -> Gamma aenv -- ^ array environment+ -> ArrayR (Array sh e)+ -> IRFun2 PTX aenv (e -> e -> e) -- ^ combination function+ -> MIRExp PTX aenv e -- ^ (optional) seed element, if this is an exclusive reduction+ -> MIRDelayed PTX aenv (Array (sh, Int) e) -- ^ input data+ -> CodeGen PTX (IROpenAcc PTX aenv (Array sh e))+mkFoldDim uid aenv repr@(ArrayR shr tp) combine mseed marr = do+ dev <- liftCodeGen $ asks ptxDeviceProperties+ --+ let+ (arrOut, paramOut) = mutableArray repr "out"+ (arrIn, paramIn) = delayedArray "in" marr+ paramEnv = envParam aenv+ --+ config = launchConfig dev (CUDA.incWarp dev) smem const [|| const ||]+ smem n = sharedMemorySizeAdd tp warps 0+ where+ ws = CUDA.warpSize dev+ warps = n `P.quot` ws+ --+ makeOpenAccWith config uid "fold" (paramOut ++ paramIn ++ paramEnv) $ do++ -- If the innermost dimension is smaller than the number of threads in the+ -- block, those threads will never contribute to the output.+ tid <- threadIdx+ sz <- indexHead <$> delayedExtent arrIn+ sz' <- i32 sz++ when (A.lt singleType tid sz') $ do++ start <- return (liftInt 0)+ end <- shapeSize shr (irArrayShape arrOut)++ -- Thread blocks iterate over the outer dimensions, each thread block+ -- cooperatively reducing along each outermost index to a single value.+ --+ imapFromTo start end $ \seg -> do++ -- Wait for threads to catch up before starting this segment. We could+ -- also place this at the bottom of the loop, but here allows threads to+ -- exit quickly on the last iteration.+ __syncthreads++ -- Step 1: initialise local sums+ from <- A.mul numType seg sz -- first linear index this block will reduce+ to <- A.add numType from sz -- last linear index this block will reduce (exclusive)++ i0 <- A.add numType from =<< int tid+ x0 <- app1 (delayedLinearIndex arrIn) i0+ bd <- blockDim+ r0 <- if (tp, A.gte singleType sz' bd)+ then reduceBlock dev tp combine Nothing x0+ else reduceBlock dev tp combine (Just sz') x0++ -- Step 2: keep walking over the input+ bd' <- int bd+ next <- A.add numType from bd'+ r <- iterFromStepTo tp next bd' to r0 $ \offset r -> do++ -- Wait for all threads to catch up before starting the next stripe+ __syncthreads++ -- Threads cooperatively reduce this stripe of the input+ i <- A.add numType offset =<< int tid+ v' <- A.sub numType to offset+ r' <- if (tp, A.gte singleType v' bd')+ -- All threads of the block are valid, so we can avoid+ -- bounds checks.+ then do+ x <- app1 (delayedLinearIndex arrIn) i+ y <- reduceBlock dev tp combine Nothing x+ return y++ -- Otherwise, we require bounds checks when reading the input+ -- and during the reduction. Note that even though only the+ -- valid threads will contribute useful work in the+ -- reduction, we must still have all threads enter the+ -- reduction procedure to avoid synchronisation divergence.+ else do+ x <- if (tp, A.lt singleType i to)+ then app1 (delayedLinearIndex arrIn) i+ else let+ go :: TypeR a -> Operands a+ go TupRunit = OP_Unit+ go (TupRpair a b) = OP_Pair (go a) (go b)+ go (TupRsingle t) = ir t (undef t)+ in+ return $ go tp++ v <- i32 v'+ y <- reduceBlock dev tp combine (Just v) x+ return y++ if (tp, A.eq singleType tid (liftInt32 0))+ then app2 combine r r'+ else return r'++ -- Step 3: Thread 0 writes the aggregate reduction of this dimension to+ -- memory. If this is an exclusive fold, combine with the initial element.+ --+ when (A.eq singleType tid (liftInt32 0)) $+ writeArray TypeInt arrOut seg =<<+ case mseed of+ Nothing -> return r+ Just z -> flip (app2 combine) r =<< z -- Note: initial element on the left++ return_+++-- Exclusive reductions over empty arrays (of any dimension) fill the lower+-- dimensions with the initial element.+--+mkFoldFill+ :: UID+ -> Gamma aenv+ -> ArrayR (Array sh e)+ -> IRExp PTX aenv e+ -> CodeGen PTX (IROpenAcc PTX aenv (Array sh e))+mkFoldFill uid aenv repr seed =+ mkGenerate uid aenv repr (IRFun1 (const seed))+++-- Efficient threadblock-wide reduction using the specified operator. The+-- aggregate reduction value is stored in thread zero. Supports non-commutative+-- operators.+--+-- Requires dynamically allocated memory: (#warps * (1 + 1.5 * warp size)).+--+-- Example: https://github.com/NVlabs/cub/blob/1.5.2/cub/block/specializations/block_reduce_warp_reductions.cuh+--+reduceBlock+ :: forall aenv e.+ DeviceProperties -- ^ properties of the target device+ -> TypeR e+ -> IRFun2 PTX aenv (e -> e -> e) -- ^ combination function+ -> Maybe (Operands Int32) -- ^ number of valid elements (may be less than block size)+ -> Operands e -- ^ calling thread's input element+ -> CodeGen PTX (Operands e) -- ^ thread-block-wide reduction using the specified operator (lane 0 only)+reduceBlock dev tp combine size = warpReduce >=> warpAggregate+ where+ int32 :: Integral a => a -> Operands Int32+ int32 = liftInt32 . P.fromIntegral++ -- Step 1: Reduction in every warp+ --+ warpReduce :: Operands e -> CodeGen PTX (Operands e)+ warpReduce input = do+ wid <- warpId+ -- Are we doing bounds checking for this warp?+ case size of+ -- The entire thread block is valid, so skip bounds checks.+ Nothing -> reduceWarp dev tp combine Nothing input++ -- Otherwise check how many elements are valid for this warp. If it is+ -- full then we can still skip bounds checks for it.+ Just n -> do+ offset <- A.mul numType wid (int32 (CUDA.warpSize dev))+ valid <- A.sub numType n offset+ if (tp, A.gte singleType valid (int32 (CUDA.warpSize dev)))+ then reduceWarp dev tp combine Nothing input+ else reduceWarp dev tp combine (Just valid) input++ -- Step 2: Aggregate per-warp reductions+ --+ warpAggregate :: Operands e -> CodeGen PTX (Operands e)+ warpAggregate input = do+ -- Allocate #warps elements of shared memory+ bd <- blockDim+ warps <- A.quot integralType bd (int32 (CUDA.warpSize dev))+ smem <- dynamicSharedMem tp TypeInt32 warps (liftInt32 0)++ -- Share the per-lane aggregates+ wid <- warpId+ lane <- laneId+ when (A.eq singleType lane (liftInt32 0)) $ do+ writeArray TypeInt32 smem wid input++ -- Wait for each warp to finish its local reduction+ __syncthreads++ -- -- Now, warp 0 will reduce all the warp-wide results.+ -- -- TODO: this means that the block can only consist of 32 warps,+ -- -- reducing 1024 elements total. Check if this gets handled by callers!+ -- if (tp, A.eq singleType wid (liftInt32 0))+ -- then warpReduce input+ -- else+ -- return input++ -- Update the total aggregate. Thread 0 just does this sequentially (as is+ -- done in CUB), but we could also do this cooperatively (better for+ -- larger thread blocks?)+ --+ tid <- threadIdx+ if (tp, A.eq singleType tid (liftInt32 0))+ then do+ steps <- case size of+ Nothing -> return warps+ Just n -> do+ a <- A.add numType n (int32 (CUDA.warpSize dev - 1))+ b <- A.quot integralType a (int32 (CUDA.warpSize dev))+ return b+ iterFromStepTo tp (liftInt32 1) (liftInt32 1) steps input $ \step x ->+ app2 combine x =<< readArray TypeInt32 smem step+ else+ return input+++-- Efficient warp-wide reduction using shared memory. The aggregate reduction+-- value for the warp is stored in thread lane zero.+--+-- Each warp requires 48 (1.5 x warp size) elements of shared memory. The+-- routine assumes that is is allocated individually per-warp (i.e. can be+-- indexed in the range [0,warp size)).+--+-- Example: https://github.com/NVlabs/cub/blob/1.5.2/cub/warp/specializations/warp_reduce_smem.cuh#L128+--+reduceWarp+ :: forall e aenv. DeviceProperties+ -> TypeR e+ -> IRFun2 PTX aenv (e -> e -> e) -- ^ combination function+ -> Maybe (Operands Int32) -- ^ number of items that will be reduced by this warp, otherwise all lanes are valid+ -> Operands e -- ^ this thread's input value+ -> CodeGen PTX (Operands e) -- ^ final result+reduceWarp dev typer combine size = reduce 0+ where+ log2 :: Double -> Double+ log2 = P.logBase 2++ -- Number steps required to reduce warp+ steps = P.floor . log2 . P.fromIntegral . CUDA.warpSize $ dev++ valid offset = do+ lane <- laneId+ i <- A.add numType lane (liftInt32 offset)+ case size of+ Nothing -> A.lt singleType i (liftInt32 (P.fromIntegral (CUDA.warpSize dev)))+ Just n -> A.lt singleType i n++ -- Unfold the reduction as a recursive code generation function.+ reduce :: Int -> Operands e -> CodeGen PTX (Operands e)+ reduce step x+ | step > steps = return x+ | otherwise = do+ let+ offset :: (Bits i, Integral i) => i+ offset = 1 `P.shiftL` step++ y <- __shfl_down typer x (liftWord32 offset)+ x' <- if (typer, valid offset)+ then app2 combine x y+ else return x+ reduce (step + 1) x'++++-- Reduction loops+-- ---------------++reduceFromTo+ :: DeviceProperties+ -> TypeR a+ -> Operands Int -- ^ starting index+ -> Operands Int -- ^ final index (exclusive)+ -> (IRFun2 PTX aenv (a -> a -> a)) -- ^ combination function+ -> (Operands Int -> CodeGen PTX (Operands a)) -- ^ function to retrieve element at index+ -> (Operands a -> CodeGen PTX ()) -- ^ what to do with the value+ -> CodeGen PTX ()+reduceFromTo dev tp from to combine get set = do++ tid <- int =<< threadIdx+ bd <- int =<< blockDim++ valid <- A.sub numType to from+ i <- A.add numType from tid++ _ <- if (TupRunit, A.gte singleType valid bd)+ then do+ -- All threads in the block will participate in the reduction, so+ -- we can avoid bounds checks+ x <- get i+ r <- reduceBlock dev tp combine Nothing x+ set r++ return (lift TupRunit ())+ else do+ -- Only in-bounds threads can read their input and participate in+ -- the reduction+ when (A.lt singleType i to) $ do+ x <- get i+ v <- i32 valid+ r <- reduceBlock dev tp combine (Just v) x+ set r++ return (lift TupRunit ())++ return ()+++-- Utilities+-- ---------++i32 :: Operands Int -> CodeGen PTX (Operands Int32)+i32 = A.fromIntegral integralType numType++int :: Operands Int32 -> CodeGen PTX (Operands Int)+int = A.fromIntegral integralType numType++imapFromTo+ :: Operands Int+ -> Operands Int+ -> (Operands Int -> CodeGen PTX ())+ -> CodeGen PTX ()+imapFromTo start end body = do+ bid <- int =<< blockIdx+ gd <- int =<< gridDim+ i0 <- A.add numType start bid+ imapFromStepTo i0 gd end body+
+ src/Data/Array/Accelerate/LLVM/PTX/CodeGen/FoldSeg.hs view
@@ -0,0 +1,476 @@+{-# LANGUAGE GADTs #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RebindableSyntax #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE ViewPatterns #-}+-- |+-- Module : Data.Array.Accelerate.LLVM.PTX.CodeGen.FoldSeg+-- Copyright : [2016..2020] The Accelerate Team+-- License : BSD3+--+-- Maintainer : Trevor L. McDonell <trevor.mcdonell@gmail.com>+-- Stability : experimental+-- Portability : non-portable (GHC extensions)+--++module Data.Array.Accelerate.LLVM.PTX.CodeGen.FoldSeg+ where++import Data.Array.Accelerate.Representation.Array+import Data.Array.Accelerate.Representation.Shape+import Data.Array.Accelerate.Representation.Type++import Data.Array.Accelerate.LLVM.CodeGen.Arithmetic as A+import Data.Array.Accelerate.LLVM.CodeGen.Array+import Data.Array.Accelerate.LLVM.CodeGen.Base+import Data.Array.Accelerate.LLVM.CodeGen.Constant+import Data.Array.Accelerate.LLVM.CodeGen.Environment+import Data.Array.Accelerate.LLVM.CodeGen.Exp+import Data.Array.Accelerate.LLVM.CodeGen.IR+import Data.Array.Accelerate.LLVM.CodeGen.Loop as Loop+import Data.Array.Accelerate.LLVM.CodeGen.Monad+import Data.Array.Accelerate.LLVM.CodeGen.Sugar+import Data.Array.Accelerate.LLVM.Compile.Cache++import Data.Array.Accelerate.LLVM.PTX.Analysis.Launch+import Data.Array.Accelerate.LLVM.PTX.CodeGen.Base+import qualified Data.Array.Accelerate.LLVM.PTX.CodeGen.Fold as Fold ( reduceBlock, reduceWarp, imapFromTo )+import Data.Array.Accelerate.LLVM.PTX.Target++import LLVM.AST.Type.Representation++import qualified Foreign.CUDA.Analysis as CUDA++import Control.Monad ( void )+import Control.Monad.Reader ( asks )+import Data.String ( fromString )+import Prelude as P+++-- Segmented reduction along the innermost dimension of an array. Performs one+-- reduction per segment of the source array.+--+mkFoldSeg+ :: forall aenv sh i e.+ UID+ -> Gamma aenv+ -> ArrayR (Array (sh, Int) e)+ -> IntegralType i+ -> IRFun2 PTX aenv (e -> e -> e)+ -> Maybe (IRExp PTX aenv e)+ -> MIRDelayed PTX aenv (Array (sh, Int) e)+ -> MIRDelayed PTX aenv (Segments i)+ -> CodeGen PTX (IROpenAcc PTX aenv (Array (sh, Int) e))+mkFoldSeg uid aenv repr intTp combine seed arr seg =+ (+++) <$> mkFoldSegP_block uid aenv repr intTp combine seed arr seg+ <*> mkFoldSegP_warp uid aenv repr intTp combine seed arr seg+++-- This implementation assumes that the segments array represents the offset+-- indices to the source array, rather than the lengths of each segment. The+-- segment-offset approach is required for parallel implementations.+--+-- Each segment is computed by a single thread block, meaning we don't have to+-- worry about inter-block synchronisation.+--+mkFoldSegP_block+ :: forall aenv sh i e.+ UID+ -> Gamma aenv+ -> ArrayR (Array (sh, Int) e)+ -> IntegralType i+ -> IRFun2 PTX aenv (e -> e -> e)+ -> MIRExp PTX aenv e+ -> MIRDelayed PTX aenv (Array (sh, Int) e)+ -> MIRDelayed PTX aenv (Segments i)+ -> CodeGen PTX (IROpenAcc PTX aenv (Array (sh, Int) e))+mkFoldSegP_block uid aenv repr@(ArrayR shr tp) intTp combine mseed marr mseg = do+ dev <- liftCodeGen $ asks ptxDeviceProperties+ --+ let+ (arrOut, paramOut) = mutableArray repr "out"+ (arrIn, paramIn) = delayedArray "in" marr+ (arrSeg, paramSeg) = delayedArray "seg" mseg+ paramEnv = envParam aenv+ --+ config = launchConfig dev (CUDA.decWarp dev) dsmem const [|| const ||]+ dsmem n = sharedMemorySizeAdd tp warps 0+ where+ ws = CUDA.warpSize dev+ warps = n `P.quot` ws+ --+ makeOpenAccWith config uid "foldSeg_block" (paramOut ++ paramIn ++ paramSeg ++ paramEnv) $ do++ -- We use a dynamically scheduled work queue in order to evenly distribute+ -- the uneven workload, due to the variable length of each segment, over the+ -- available thread blocks.+ -- queue <- globalWorkQueue++ -- All threads in the block need to know what the start and end indices of+ -- this segment are in order to participate in the reduction. We use+ -- variables in __shared__ memory to communicate these values between+ -- threads in the block. Furthermore, by using a 2-element array, we can+ -- have the first two threads of the block read the start and end indices as+ -- a single coalesced read, since they will be sequential in the+ -- segment-offset array.+ --+ smem <- staticSharedMem (TupRsingle scalarTypeInt) 2++ -- Compute the number of segments and size of the innermost dimension. These+ -- are required if we are reducing a rank-2 or higher array, to properly+ -- compute the start and end indices of the portion of the array this thread+ -- block reduces. Note that this is a segment-offset array computed by+ -- 'scanl (+) 0' of the segment length array, so its size has increased by+ -- one.+ --+ sz <- indexHead <$> delayedExtent arrIn+ ss <- do n <- indexHead <$> delayedExtent arrSeg+ A.sub numType n (liftInt 1)++ -- Each thread block cooperatively reduces a segment.+ -- s0 <- dequeue queue (lift 1)+ -- for s0 (\s -> A.lt singleType s end) (\_ -> dequeue queue (lift 1)) $ \s -> do++ start <- return (liftInt 0)+ end <- shapeSize shr (irArrayShape arrOut)++ Fold.imapFromTo start end $ \s -> do++ -- The first two threads of the block determine the indices of the+ -- segments array that we will reduce between and distribute those values+ -- to the other threads in the block.+ tid <- threadIdx+ when (A.lt singleType tid (liftInt32 2)) $ do+ i <- case shr of+ ShapeRsnoc ShapeRz -> return s+ _ -> A.rem integralType s ss+ j <- A.add numType i =<< int tid+ v <- app1 (delayedLinearIndex arrSeg) j+ writeArray TypeInt32 smem tid =<< A.fromIntegral intTp numType v++ -- Once all threads have caught up, begin work on the new segment.+ __syncthreads++ u <- readArray TypeInt32 smem (liftInt32 0)+ v <- readArray TypeInt32 smem (liftInt32 1)++ -- Determine the index range of the input array we will reduce over.+ -- Necessary for multidimensional segmented reduction.+ (inf,sup) <- A.unpair <$> case shr of+ ShapeRsnoc ShapeRz -> return (A.pair u v)+ _ -> do q <- A.quot integralType s ss+ a <- A.mul numType q sz+ A.pair <$> A.add numType u a+ <*> A.add numType v a++ void $+ if (TupRunit, A.eq singleType inf sup)+ -- This segment is empty. If this is an exclusive reduction the+ -- first thread writes out the initial element for this segment.+ then do+ case mseed of+ Nothing -> return (lift TupRunit ())+ Just z -> do+ when (A.eq singleType tid (liftInt32 0)) $ writeArray TypeInt arrOut s =<< z+ return (lift TupRunit ())++ -- This is a non-empty segment.+ else do+ -- Step 1: initialise local sums+ --+ -- NOTE: We require all threads to enter this branch and execute the+ -- first step, even if they do not have a valid element and must+ -- return 'undef'. If we attempt to skip this entire section for+ -- non-participating threads (i.e. 'when (i0 < sup)'), it seems that+ -- those threads die and will not participate in the computation of+ -- _any_ further segment. I'm not sure if this is a CUDA oddity+ -- (e.g. we must have all threads convergent on __syncthreads) or+ -- a bug in NVPTX / ptxas.+ --+ i0 <- A.add numType inf =<< int tid+ x0 <- if (tp, A.lt singleType i0 sup)+ then app1 (delayedLinearIndex arrIn) i0+ else let+ go :: TypeR a -> Operands a+ go TupRunit = OP_Unit+ go (TupRpair a b) = OP_Pair (go a) (go b)+ go (TupRsingle t) = ir t (undef t)+ in+ return $ go tp++ bd <- int =<< blockDim+ v0 <- A.sub numType sup inf+ v0' <- i32 v0+ r0 <- if (tp, A.gte singleType v0 bd)+ then Fold.reduceBlock dev tp combine Nothing x0+ else Fold.reduceBlock dev tp combine (Just v0') x0++ -- Step 2: keep walking over the input+ nxt <- A.add numType inf bd+ r <- iterFromStepTo tp nxt bd sup r0 $ \offset r -> do++ -- Wait for threads to catch up before starting the next stripe+ __syncthreads++ i' <- A.add numType offset =<< int tid+ v' <- A.sub numType sup offset+ r' <- if (tp, A.gte singleType v' bd)+ -- All threads in the block are in bounds, so we+ -- can avoid bounds checks.+ then do+ x <- app1 (delayedLinearIndex arrIn) i'+ y <- Fold.reduceBlock dev tp combine Nothing x+ return y++ -- Not all threads are valid. Note that we still+ -- have all threads enter the reduction procedure+ -- to avoid thread divergence on synchronisation+ -- points, similar to the above NOTE.+ else do+ x <- if (tp, A.lt singleType i' sup)+ then app1 (delayedLinearIndex arrIn) i'+ else let+ go :: TypeR a -> Operands a+ go TupRunit = OP_Unit+ go (TupRpair a b) = OP_Pair (go a) (go b)+ go (TupRsingle t) = ir t (undef t)+ in+ return $ go tp++ z <- i32 v'+ y <- Fold.reduceBlock dev tp combine (Just z) x+ return y++ -- first thread incorporates the result from the previous+ -- iteration+ if (tp, A.eq singleType tid (liftInt32 0))+ then app2 combine r r'+ else return r'++ -- Step 3: Thread zero writes the aggregate reduction for this+ -- segment to memory. If this is an exclusive fold combine with the+ -- initial element as well.+ when (A.eq singleType tid (liftInt32 0)) $+ writeArray TypeInt arrOut s =<<+ case mseed of+ Nothing -> return r+ Just z -> flip (app2 combine) r =<< z -- Note: initial element on the left++ return (lift TupRunit ())++ return_+++-- This implementation assumes that the segments array represents the offset+-- indices to the source array, rather than the lengths of each segment. The+-- segment-offset approach is required for parallel implementations.+--+-- Each segment is computed by a single warp, meaning we don't have to worry+-- about inter- or intra-block synchronisation.+--+mkFoldSegP_warp+ :: forall aenv sh i e.+ UID+ -> Gamma aenv+ -> ArrayR (Array (sh, Int) e)+ -> IntegralType i+ -> IRFun2 PTX aenv (e -> e -> e)+ -> MIRExp PTX aenv e+ -> MIRDelayed PTX aenv (Array (sh, Int) e)+ -> MIRDelayed PTX aenv (Segments i)+ -> CodeGen PTX (IROpenAcc PTX aenv (Array (sh, Int) e))+mkFoldSegP_warp uid aenv repr@(ArrayR shr tp) intTp combine mseed marr mseg = do+ dev <- liftCodeGen $ asks ptxDeviceProperties+ --+ let+ (arrOut, paramOut) = mutableArray repr "out"+ (arrIn, paramIn) = delayedArray "in" marr+ (arrSeg, paramSeg) = delayedArray "seg" mseg+ paramEnv = envParam aenv+ --+ config = launchConfig dev (CUDA.decWarp dev) dsmem grid gridQ+ where dsmem _n = 0+ --+ grid n m = multipleOf n (m `P.quot` ws)+ gridQ = [|| \n m -> $$multipleOfQ n (m `P.quot` ws) ||]+ --+ ws = CUDA.warpSize dev++ int32 :: Integral a => a -> Operands Int32+ int32 = liftInt32 . P.fromIntegral+ --+ makeOpenAccWith config uid "foldSeg_warp" (paramOut ++ paramIn ++ paramSeg ++ paramEnv) $ do++ -- Each warp works independently.+ -- Determine the ID of this warp within the thread block.+ tid <- threadIdx+ wid <- A.quot integralType tid (int32 ws)++ -- Number of warps per thread block+ bd <- blockDim+ wpb <- A.quot integralType bd (int32 ws)++ -- ID of this warp within the grid+ bid <- blockIdx+ gwid <- do a <- A.mul numType bid wpb+ b <- A.add numType wid a+ return b++ -- Compute the number of segments and size of the innermost dimension. These+ -- are required if we are reducing a rank-2 or higher array, to properly+ -- compute the start and end indices of the portion of the array this warp+ -- reduces. Note that this is a segment-offset array computed by 'scanl (+) 0'+ -- of the segment length array, so its size has increased by one.+ --+ sz <- indexHead <$> delayedExtent arrIn+ ss <- do a <- indexHead <$> delayedExtent arrSeg+ b <- A.sub numType a (liftInt 1)+ return b++ -- Each thread reduces a segment independently+ s0 <- int gwid+ gd <- int =<< gridDim+ wpb' <- int wpb+ step <- A.mul numType wpb' gd+ end <- shapeSize shr (irArrayShape arrOut)+ imapFromStepTo s0 step end $ \s -> do++ __syncwarp++ -- The first two threads of the warp determine the indices of the segments+ -- array that we will reduce between and distribute those values to the+ -- other threads in the warp+ lane <- laneId+ idx <- if (TupRsingle scalarTypeInt, A.lt singleType lane (liftInt32 2))+ then do+ a <- case shr of+ ShapeRsnoc ShapeRz -> return s+ _ -> A.rem integralType s ss+ b <- A.add numType a =<< int lane+ c <- app1 (delayedLinearIndex arrSeg) b+ d <- A.fromIntegral intTp numType c+ return d+ else+ return (ir integralType (undef scalarType))++ __syncwarp++ -- Determine the index range of the input array we will reduce over.+ -- Necessary for multidimensional segmented reduction.+ (inf,sup) <- do+ u <- __shfl_idx (TupRsingle scalarTypeInt) idx (liftWord32 0)++ v <- __shfl_idx (TupRsingle scalarTypeInt) idx (liftWord32 1)+ A.unpair <$> case shr of+ ShapeRsnoc ShapeRz -> return (A.pair u v)+ _ -> do q <- A.quot integralType s ss+ a <- A.mul numType q sz+ A.pair <$> A.add numType u a+ <*> A.add numType v a++ __syncwarp++ void $+ if (TupRunit, A.eq singleType inf sup)+ -- This segment is empty. If this is an exclusive reduction the first+ -- lane writes out the initial element for this segment.+ then do+ case mseed of+ Nothing -> return (lift TupRunit ())+ Just z -> do+ when (A.eq singleType lane (liftInt32 0)) $ writeArray TypeInt arrOut s =<< z+ return (lift TupRunit ())++ -- This is a non-empty segment.+ else do+ -- Step 1: initialise local sums+ --+ -- See comment above why we initialise the loop in this way+ --+ i0 <- A.add numType inf =<< int lane+ x0 <- if (tp, A.lt singleType i0 sup)+ then app1 (delayedLinearIndex arrIn) i0+ else let+ go :: TypeR a -> Operands a+ go TupRunit = OP_Unit+ go (TupRpair a b) = OP_Pair (go a) (go b)+ go (TupRsingle t) = ir t (undef t)+ in+ return $ go tp++ v0 <- A.sub numType sup inf+ v0' <- i32 v0+ r0 <- if (tp, A.gte singleType v0 (liftInt ws))+ then reduceWarp dev tp combine Nothing x0+ else reduceWarp dev tp combine (Just v0') x0++ -- Step 2: Keep walking over the rest of the segment+ nx <- A.add numType inf (liftInt ws)+ r <- iterFromStepTo tp nx (liftInt ws) sup r0 $ \offset r -> do++ -- __syncwarp+ __syncthreads -- TLM: why is this necessary?++ i' <- A.add numType offset =<< int lane+ v' <- A.sub numType sup offset+ r' <- if (tp, A.gte singleType v' (liftInt ws))+ then do+ -- All lanes are in bounds, so avoid bounds checks+ x <- app1 (delayedLinearIndex arrIn) i'+ y <- reduceWarp dev tp combine Nothing x+ return y++ else do+ x <- if (tp, A.lt singleType i' sup)+ then app1 (delayedLinearIndex arrIn) i'+ else let+ go :: TypeR a -> Operands a+ go TupRunit = OP_Unit+ go (TupRpair a b) = OP_Pair (go a) (go b)+ go (TupRsingle t) = ir t (undef t)+ in+ return $ go tp++ z <- i32 v'+ y <- reduceWarp dev tp combine (Just z) x+ return y++ -- The first lane incorporates the result from the previous+ -- iteration+ if (tp, A.eq singleType lane (liftInt32 0))+ then app2 combine r r'+ else return r'++ -- Step 3: Lane zero writes the aggregate reduction for this+ -- segment to memory. If this is an exclusive reduction, also+ -- combine with the initial element+ when (A.eq singleType lane (liftInt32 0)) $+ writeArray TypeInt arrOut s =<<+ case mseed of+ Nothing -> return r+ Just z -> flip (app2 combine) r =<< z -- Note: initial element on the left++ return (lift TupRunit ())++ return_+++i32 :: IsIntegral i => Operands i -> CodeGen PTX (Operands Int32)+i32 = A.fromIntegral integralType numType++int :: IsIntegral i => Operands i -> CodeGen PTX (Operands Int)+int = A.fromIntegral integralType numType++reduceWarp+ :: forall aenv e.+ DeviceProperties -- ^ properties of the target device+ -> TypeR e+ -> IRFun2 PTX aenv (e -> e -> e) -- ^ combination function+ -> Maybe (Operands Int32) -- ^ number of items that will be reduced by this warp, otherwise all lanes are valid+ -> Operands e -- ^ calling thread's input element+ -> CodeGen PTX (Operands e) -- ^ warp-wide reduction using the specified operator (lane 0 only)+reduceWarp dev t c = Fold.reduceWarp dev t c+
+ src/Data/Array/Accelerate/LLVM/PTX/CodeGen/Generate.hs view
@@ -0,0 +1,62 @@+{-# LANGUAGE GADTs #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+-- |+-- Module : Data.Array.Accelerate.LLVM.PTX.CodeGen.Generate+-- Copyright : [2014..2020] The Accelerate Team+-- License : BSD3+--+-- Maintainer : Trevor L. McDonell <trevor.mcdonell@gmail.com>+-- Stability : experimental+-- Portability : non-portable (GHC extensions)+--++module Data.Array.Accelerate.LLVM.PTX.CodeGen.Generate+ where++import Prelude hiding ( fromIntegral )++-- accelerate+import Data.Array.Accelerate.Representation.Array+import Data.Array.Accelerate.Type++import Data.Array.Accelerate.LLVM.CodeGen.Arithmetic+import Data.Array.Accelerate.LLVM.CodeGen.Array+import Data.Array.Accelerate.LLVM.CodeGen.Base+import Data.Array.Accelerate.LLVM.CodeGen.Environment+import Data.Array.Accelerate.LLVM.CodeGen.Exp+import Data.Array.Accelerate.LLVM.CodeGen.Monad+import Data.Array.Accelerate.LLVM.CodeGen.Sugar+import Data.Array.Accelerate.LLVM.Compile.Cache++import Data.Array.Accelerate.LLVM.PTX.CodeGen.Base+import Data.Array.Accelerate.LLVM.PTX.CodeGen.Loop+import Data.Array.Accelerate.LLVM.PTX.Target ( PTX )+++-- Construct a new array by applying a function to each index. Each thread+-- processes multiple adjacent elements.+--+mkGenerate+ :: UID+ -> Gamma aenv+ -> ArrayR (Array sh e)+ -> IRFun1 PTX aenv (sh -> e)+ -> CodeGen PTX (IROpenAcc PTX aenv (Array sh e))+mkGenerate uid aenv repr@(ArrayR shr _) apply =+ let+ (arrOut, paramOut) = mutableArray repr "out"+ paramEnv = envParam aenv+ in+ makeOpenAcc uid "generate" (paramOut ++ paramEnv) $ do++ start <- return (liftInt 0)+ end <- shapeSize shr (irArrayShape arrOut)++ imapFromTo start end $ \i -> do+ ix <- indexOfInt shr (irArrayShape arrOut) i -- convert to multidimensional index+ r <- app1 apply ix -- apply generator function+ writeArray TypeInt arrOut i r -- store result++ return_+
+ src/Data/Array/Accelerate/LLVM/PTX/CodeGen/Intrinsic.hs view
@@ -0,0 +1,360 @@+{-# LANGUAGE OverloadedStrings #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}+-- |+-- Module : Data.Array.Accelerate.LLVM.PTX.CodeGen.Intrinsic+-- Copyright : [2017..2020] The Accelerate Team+-- License : BSD3+--+-- Maintainer : Trevor L. McDonell <trevor.mcdonell@gmail.com>+-- Stability : experimental+-- Portability : non-portable (GHC extensions)+--++module Data.Array.Accelerate.LLVM.PTX.CodeGen.Intrinsic ( )+ where++import LLVM.AST.Type.Name ( Label(..) )++import Data.Array.Accelerate.LLVM.CodeGen.Intrinsic+import Data.Array.Accelerate.LLVM.PTX.Target++import Data.ByteString.Short ( ShortByteString )+import Data.HashMap.Strict ( HashMap )+import Data.Monoid+import qualified Data.HashMap.Strict as HashMap+import Prelude as P+++instance Intrinsic PTX where+ intrinsicForTarget = libdeviceIndex++-- The list of functions implemented by libdevice. These are all more-or-less+-- named consistently based on the standard mathematical functions they+-- implement, with the "__nv_" prefix stripped.+--+libdeviceIndex :: HashMap ShortByteString Label+libdeviceIndex =+ let nv base = (base, Label $ "__nv_" <> base)+ in+ HashMap.fromList $ map nv+ [ "abs"+ , "acos"+ , "acosf"+ , "acosh"+ , "acoshf"+ , "asin"+ , "asinf"+ , "asinh"+ , "asinhf"+ , "atan"+ , "atan2"+ , "atan2f"+ , "atanf"+ , "atanh"+ , "atanhf"+ , "brev"+ , "brevll"+ , "byte_perm"+ , "cbrt"+ , "cbrtf"+ , "ceil"+ , "ceilf"+ , "clz"+ , "clzll"+ , "copysign"+ , "copysignf"+ , "cos"+ , "cosf"+ , "cosh"+ , "coshf"+ , "cospi"+ , "cospif"+ , "dadd_rd"+ , "dadd_rn"+ , "dadd_ru"+ , "dadd_rz"+ , "ddiv_rd"+ , "ddiv_rn"+ , "ddiv_ru"+ , "ddiv_rz"+ , "dmul_rd"+ , "dmul_rn"+ , "dmul_ru"+ , "dmul_rz"+ , "double2float_rd"+ , "double2float_rn"+ , "double2float_ru"+ , "double2float_rz"+ , "double2hiint"+ , "double2int_rd"+ , "double2int_rn"+ , "double2int_ru"+ , "double2int_rz"+ , "double2ll_rd"+ , "double2ll_rn"+ , "double2ll_ru"+ , "double2ll_rz"+ , "double2loint"+ , "double2uint_rd"+ , "double2uint_rn"+ , "double2uint_ru"+ , "double2uint_rz"+ , "double2ull_rd"+ , "double2ull_rn"+ , "double2ull_ru"+ , "double2ull_rz"+ , "double_as_longlong"+ , "drcp_rd"+ , "drcp_rn"+ , "drcp_ru"+ , "drcp_rz"+ , "dsqrt_rd"+ , "dsqrt_rn"+ , "dsqrt_ru"+ , "dsqrt_rz"+ , "erf"+ , "erfc"+ , "erfcf"+ , "erfcinv"+ , "erfcinvf"+ , "erfcx"+ , "erfcxf"+ , "erff"+ , "erfinv"+ , "erfinvf"+ , "exp"+ , "exp10"+ , "exp10f"+ , "exp2"+ , "exp2f"+ , "expf"+ , "expm1"+ , "expm1f"+ , "fabs"+ , "fabsf"+ , "fadd_rd"+ , "fadd_rn"+ , "fadd_ru"+ , "fadd_rz"+ , "fast_cosf"+ , "fast_exp10f"+ , "fast_expf"+ , "fast_fdividef"+ , "fast_log10f"+ , "fast_log2f"+ , "fast_logf"+ , "fast_powf"+ , "fast_sincosf"+ , "fast_sinf"+ , "fast_tanf"+ , "fdim"+ , "fdimf"+ , "fdiv_rd"+ , "fdiv_rn"+ , "fdiv_ru"+ , "fdiv_rz"+ , "ffs"+ , "ffsll"+ , "finitef"+ , "float2half_rn"+ , "float2int_rd"+ , "float2int_rn"+ , "float2int_ru"+ , "float2int_rz"+ , "float2ll_rd"+ , "float2ll_rn"+ , "float2ll_ru"+ , "float2ll_rz"+ , "float2uint_rd"+ , "float2uint_rn"+ , "float2uint_ru"+ , "float2uint_rz"+ , "float2ull_rd"+ , "float2ull_rn"+ , "float2ull_ru"+ , "float2ull_rz"+ , "float_as_int"+ , "floor"+ , "floorf"+ , "fma"+ , "fma_rd"+ , "fma_rn"+ , "fma_ru"+ , "fma_rz"+ , "fmaf"+ , "fmaf_rd"+ , "fmaf_rn"+ , "fmaf_ru"+ , "fmaf_rz"+ , "fmax"+ , "fmaxf"+ , "fmin"+ , "fminf"+ , "fmod"+ , "fmodf"+ , "fmul_rd"+ , "fmul_rn"+ , "fmul_ru"+ , "fmul_rz"+ , "frcp_rd"+ , "frcp_rn"+ , "frcp_ru"+ , "frcp_rz"+ , "frexp"+ , "frexpf"+ , "frsqrt_rn"+ , "fsqrt_rd"+ , "fsqrt_rn"+ , "fsqrt_ru"+ , "fsqrt_rz"+ , "fsub_rd"+ , "fsub_rn"+ , "fsub_ru"+ , "fsub_rz"+ , "hadd"+ , "half2float"+ , "hiloint2double"+ , "hypot"+ , "hypotf"+ , "ilogb"+ , "ilogbf"+ , "int2double_rn"+ , "int2float_rd"+ , "int2float_rn"+ , "int2float_ru"+ , "int2float_rz"+ , "int_as_float"+ , "isfinited"+ , "isinfd"+ , "isinff"+ , "isnand"+ , "isnanf"+ , "j0"+ , "j0f"+ , "j1"+ , "j1f"+ , "jn"+ , "jnf"+ , "ldexp"+ , "ldexpf"+ , "lgamma"+ , "lgammaf"+ , "ll2double_rd"+ , "ll2double_rn"+ , "ll2double_ru"+ , "ll2double_rz"+ , "ll2float_rd"+ , "ll2float_rn"+ , "ll2float_ru"+ , "ll2float_rz"+ , "llabs"+ , "llmax"+ , "llmin"+ , "llrint"+ , "llrintf"+ , "llround"+ , "llroundf"+ , "log"+ , "log10"+ , "log10f"+ , "log1p"+ , "log1pf"+ , "log2"+ , "log2f"+ , "logb"+ , "logbf"+ , "logf"+ , "longlong_as_double"+ , "max"+ , "min"+ , "modf"+ , "modff"+ , "mul24"+ , "mul64hi"+ , "mulhi"+ , "nan"+ , "nanf"+ , "nearbyint"+ , "nearbyintf"+ , "nextafter"+ , "nextafterf"+ , "normcdf"+ , "normcdff"+ , "normcdfinv"+ , "normcdfinvf"+ , "popc"+ , "popcll"+ , "pow"+ , "powf"+ , "powi"+ , "powif"+ , "rcbrt"+ , "rcbrtf"+ , "remainder"+ , "remainderf"+ , "remquo"+ , "remquof"+ , "rhadd"+ , "rint"+ , "rintf"+ , "round"+ , "roundf"+ , "rsqrt"+ , "rsqrtf"+ , "sad"+ , "saturatef"+ , "scalbn"+ , "scalbnf"+ , "signbitd"+ , "signbitf"+ , "sin"+ , "sincos"+ , "sincosf"+ , "sincospi"+ , "sincospif"+ , "sinf"+ , "sinh"+ , "sinhf"+ , "sinpi"+ , "sinpif"+ , "sqrt"+ , "sqrtf"+ , "tan"+ , "tanf"+ , "tanh"+ , "tanhf"+ , "tgamma"+ , "tgammaf"+ , "trunc"+ , "truncf"+ , "uhadd"+ , "uint2double_rn"+ , "uint2float_rd"+ , "uint2float_rn"+ , "uint2float_ru"+ , "uint2float_rz"+ , "ull2double_rd"+ , "ull2double_rn"+ , "ull2double_ru"+ , "ull2double_rz"+ , "ull2float_rd"+ , "ull2float_rn"+ , "ull2float_ru"+ , "ull2float_rz"+ , "ullmax"+ , "ullmin"+ , "umax"+ , "umin"+ , "umul24"+ , "umul64hi"+ , "umulhi"+ , "urhadd"+ , "usad"+ , "y0"+ , "y0f"+ , "y1"+ , "y1f"+ , "yn"+ , "ynf"+ ]+
+ src/Data/Array/Accelerate/LLVM/PTX/CodeGen/Loop.hs view
@@ -0,0 +1,48 @@+-- |+-- Module : Data.Array.Accelerate.LLVM.PTX.CodeGen.Loop+-- Copyright : [2015..2020] The Accelerate Team+-- License : BSD3+--+-- Maintainer : Trevor L. McDonell <trevor.mcdonell@gmail.com>+-- Stability : experimental+-- Portability : non-portable (GHC extensions)+--++module Data.Array.Accelerate.LLVM.PTX.CodeGen.Loop+ where++-- accelerate+import Data.Array.Accelerate.Type++import Data.Array.Accelerate.LLVM.CodeGen.Arithmetic as A+import Data.Array.Accelerate.LLVM.CodeGen.IR+import Data.Array.Accelerate.LLVM.CodeGen.Monad+import qualified Data.Array.Accelerate.LLVM.CodeGen.Loop as Loop++import Data.Array.Accelerate.LLVM.PTX.CodeGen.Base+import Data.Array.Accelerate.LLVM.PTX.Target+++-- | A standard loop where the CUDA threads cooperatively step over an index+-- space from the start to end indices. The threads stride the array in a way+-- that maintains memory coalescing.+--+-- The start and end array indices are given as natural array indexes, and the+-- thread specific indices are calculated by the loop.+--+-- > for ( int i = blockDim.x * blockIdx.x + threadIdx.x + start+-- > ; i < end+-- > ; i += blockDim.x * gridDim.x )+--+-- TODO: This assumes that the starting offset retains alignment to the warp+-- boundary. This might not always be the case, so provide a version that+-- explicitly aligns reads to the warp boundary.+--+imapFromTo :: Operands Int -> Operands Int -> (Operands Int -> CodeGen PTX ()) -> CodeGen PTX ()+imapFromTo start end body = do+ step <- A.fromIntegral integralType numType =<< gridSize+ tid <- A.fromIntegral integralType numType =<< globalThreadIdx+ i0 <- add numType tid start+ --+ Loop.imapFromStepTo i0 step end body+
+ src/Data/Array/Accelerate/LLVM/PTX/CodeGen/Map.hs view
@@ -0,0 +1,63 @@+{-# LANGUAGE GADTs #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+-- |+-- Module : Data.Array.Accelerate.LLVM.PTX.CodeGen.Map+-- Copyright : [2014..2020] The Accelerate Team+-- License : BSD3+--+-- Maintainer : Trevor L. McDonell <trevor.mcdonell@gmail.com>+-- Stability : experimental+-- Portability : non-portable (GHC extensions)+--++module Data.Array.Accelerate.LLVM.PTX.CodeGen.Map+ where++-- accelerate+import Data.Array.Accelerate.Representation.Array+import Data.Array.Accelerate.Representation.Type+import Data.Array.Accelerate.Type++import Data.Array.Accelerate.LLVM.CodeGen.Arithmetic+import Data.Array.Accelerate.LLVM.CodeGen.Array+import Data.Array.Accelerate.LLVM.CodeGen.Base+import Data.Array.Accelerate.LLVM.CodeGen.Environment+import Data.Array.Accelerate.LLVM.CodeGen.Exp+import Data.Array.Accelerate.LLVM.CodeGen.Monad+import Data.Array.Accelerate.LLVM.CodeGen.Sugar+import Data.Array.Accelerate.LLVM.Compile.Cache++import Data.Array.Accelerate.LLVM.PTX.CodeGen.Base+import Data.Array.Accelerate.LLVM.PTX.CodeGen.Loop+import Data.Array.Accelerate.LLVM.PTX.Target ( PTX )+++-- Apply a unary function to each element of an array. Each thread processes+-- multiple elements, striding the array by the grid size.+--+mkMap :: UID+ -> Gamma aenv+ -> ArrayR (Array sh a)+ -> TypeR b+ -> IRFun1 PTX aenv (a -> b)+ -> CodeGen PTX (IROpenAcc PTX aenv (Array sh b))+mkMap uid aenv repr@(ArrayR shr _) tp' apply =+ let+ (arrOut, paramOut) = mutableArray (ArrayR shr tp') "out"+ (arrIn, paramIn) = mutableArray repr "in"+ paramEnv = envParam aenv+ in+ makeOpenAcc uid "map" (paramOut ++ paramIn ++ paramEnv) $ do++ start <- return (liftInt 0)+ end <- shapeSize shr (irArrayShape arrIn)++ imapFromTo start end $ \i -> do+ xs <- readArray TypeInt arrIn i+ ys <- app1 apply xs+ writeArray TypeInt arrOut i ys++ return_+
+ src/Data/Array/Accelerate/LLVM/PTX/CodeGen/Permute.hs view
@@ -0,0 +1,439 @@+{-# LANGUAGE GADTs #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE ViewPatterns #-}+-- |+-- Module : Data.Array.Accelerate.LLVM.PTX.CodeGen.Permute+-- Copyright : [2016..2020] The Accelerate Team+-- License : BSD3+--+-- Maintainer : Trevor L. McDonell <trevor.mcdonell@gmail.com>+-- Stability : experimental+-- Portability : non-portable (GHC extensions)+--++module Data.Array.Accelerate.LLVM.PTX.CodeGen.Permute (++ mkPermute,++) where++import Data.Array.Accelerate.AST+import Data.Array.Accelerate.Error+import Data.Array.Accelerate.Representation.Array+import Data.Array.Accelerate.Representation.Elt+import Data.Array.Accelerate.Representation.Shape+import Data.Array.Accelerate.Representation.Type++import Data.Array.Accelerate.LLVM.CodeGen.Arithmetic as A+import Data.Array.Accelerate.LLVM.CodeGen.Array+import Data.Array.Accelerate.LLVM.CodeGen.Base+import Data.Array.Accelerate.LLVM.CodeGen.Constant+import Data.Array.Accelerate.LLVM.CodeGen.Environment+import Data.Array.Accelerate.LLVM.CodeGen.Exp+import Data.Array.Accelerate.LLVM.CodeGen.IR+import Data.Array.Accelerate.LLVM.CodeGen.Monad+import Data.Array.Accelerate.LLVM.CodeGen.Permute+import Data.Array.Accelerate.LLVM.CodeGen.Ptr+import Data.Array.Accelerate.LLVM.CodeGen.Sugar+import Data.Array.Accelerate.LLVM.Compile.Cache++import Data.Array.Accelerate.LLVM.PTX.CodeGen.Base+import Data.Array.Accelerate.LLVM.PTX.CodeGen.Loop+import Data.Array.Accelerate.LLVM.PTX.Target++import LLVM.AST.Type.Instruction+import LLVM.AST.Type.Instruction.Atomic+import LLVM.AST.Type.Instruction.RMW as RMW+import LLVM.AST.Type.Instruction.Volatile+import LLVM.AST.Type.GetElementPtr+import LLVM.AST.Type.Operand+import LLVM.AST.Type.Representation++import Foreign.CUDA.Analysis++import Control.Monad ( void )+import Control.Monad.Reader ( asks )+import Prelude+++-- Forward permutation specified by an indexing mapping. The resulting array is+-- initialised with the given defaults, and any further values that are permuted+-- into the result array are added to the current value using the combination+-- function.+--+-- The combination function must be /associative/ and /commutative/. Elements+-- that are mapped to the magic index 'ignore' are dropped.+--+-- Parallel forward permutation has to take special care because different+-- threads could concurrently try to update the same memory location. Where+-- available we make use of special atomic instructions and other optimisations,+-- but in the general case each element of the output array has a lock which+-- must be obtained by the thread before it can update that memory location.+--+-- TODO: After too many failures to acquire the lock on an element, the thread+-- should back off and try a different element, adding this failed element to+-- a queue or some such.+--+mkPermute+ :: HasCallStack+ => UID+ -> Gamma aenv+ -> ArrayR (Array sh e)+ -> ShapeR sh'+ -> IRPermuteFun PTX aenv (e -> e -> e)+ -> IRFun1 PTX aenv (sh -> PrimMaybe sh')+ -> MIRDelayed PTX aenv (Array sh e)+ -> CodeGen PTX (IROpenAcc PTX aenv (Array sh' e))+mkPermute uid aenv repr shr' IRPermuteFun{..} project arr =+ case atomicRMW of+ Just (rmw, f) -> mkPermute_rmw uid aenv repr shr' rmw f project arr+ _ -> mkPermute_mutex uid aenv repr shr' combine project arr+++-- Parallel forward permutation function which uses atomic instructions to+-- implement lock-free array updates.+--+-- Atomic instruction support on CUDA devices is a bit patchy, so depending on+-- the element type and compute capability of the target hardware we may need to+-- emulate the operation using atomic compare-and-swap.+--+-- Int32 Int64 Float16 Float32 Float64+-- +-------------------------------------------------+-- (+) | 2.0 2.0 7.0 2.0 6.0+-- (-) | 2.0 2.0 x x x+-- (.&.) | 2.0 3.2+-- (.|.) | 2.0 3.2+-- xor | 2.0 3.2+-- min | 2.0 3.2 x x x+-- max | 2.0 3.2 x x x+-- CAS | 2.0 2.0+--+-- Note that NVPTX requires at least compute 2.0, so we can always implement the+-- lockfree update operations in terms of compare-and-swap.+--+mkPermute_rmw+ :: HasCallStack+ => UID+ -> Gamma aenv+ -> ArrayR (Array sh e)+ -> ShapeR sh'+ -> RMWOperation+ -> IRFun1 PTX aenv (e -> e)+ -> IRFun1 PTX aenv (sh -> PrimMaybe sh')+ -> MIRDelayed PTX aenv (Array sh e)+ -> CodeGen PTX (IROpenAcc PTX aenv (Array sh' e))+mkPermute_rmw uid aenv (ArrayR shr tp) shr' rmw update project marr = do+ dev <- liftCodeGen $ asks ptxDeviceProperties+ --+ let+ outR = ArrayR shr' tp+ (arrOut, paramOut) = mutableArray outR "out"+ (arrIn, paramIn) = delayedArray "in" marr+ paramEnv = envParam aenv+ start = liftInt 0+ --+ bytes = bytesElt tp+ compute = computeCapability dev+ compute32 = Compute 3 2+ compute60 = Compute 6 0+ compute70 = Compute 7 0+ --+ makeOpenAcc uid "permute_rmw" (paramOut ++ paramIn ++ paramEnv) $ do++ shIn <- delayedExtent arrIn+ end <- shapeSize shr shIn++ imapFromTo start end $ \i -> do++ ix <- indexOfInt shr shIn i+ ix' <- app1 project ix++ when (isJust ix') $ do+ j <- intOfIndex shr' (irArrayShape arrOut) =<< fromJust ix'+ x <- app1 (delayedLinearIndex arrIn) i+ r <- app1 update x++ case rmw of+ Exchange+ -> writeArray TypeInt arrOut j r+ --+ _ | TupRsingle (SingleScalarType s) <- tp+ , adata <- irArrayData arrOut+ -> do+ addr <- instr' $ GetElementPtr $ GEP1 (SingleScalarType s) (asPtr defaultAddrSpace (op s adata)) (op integralType j)+ --+ let+ rmw_integral :: IntegralType t -> Operand (Ptr t) -> Operand t -> CodeGen PTX ()+ rmw_integral t ptr val+ | primOk = void . instr' $ AtomicRMW (IntegralNumType t) NonVolatile rmw ptr val (CrossThread, AcquireRelease)+ | otherwise =+ case rmw of+ RMW.And -> atomicCAS_rmw s' (A.band t (ir t val)) ptr+ RMW.Or -> atomicCAS_rmw s' (A.bor t (ir t val)) ptr+ RMW.Xor -> atomicCAS_rmw s' (A.xor t (ir t val)) ptr+ RMW.Min -> atomicCAS_cmp s' A.lt ptr val+ RMW.Max -> atomicCAS_cmp s' A.gt ptr val+ _ -> internalError "unexpected transition"+ where+ s' = NumSingleType (IntegralNumType t)+ primOk = compute >= compute32+ || bytes == 4+ || case rmw of+ RMW.Add -> True+ RMW.Sub -> True+ _ -> False++ rmw_floating :: FloatingType t -> Operand (Ptr t) -> Operand t -> CodeGen PTX ()+ rmw_floating t ptr val =+ case rmw of+ RMW.Min -> atomicCAS_cmp s' A.lt ptr val+ RMW.Max -> atomicCAS_cmp s' A.gt ptr val+ RMW.Sub -> atomicCAS_rmw s' (A.sub n (ir t val)) ptr+ RMW.Add+ | primAdd -> atomicAdd_f t ptr val+ | otherwise -> atomicCAS_rmw s' (A.add n (ir t val)) ptr+ _ -> internalError "unexpected transition"+ where+ n = FloatingNumType t+ s' = NumSingleType n+ primAdd =+ case t of+ TypeHalf -> compute >= compute70+ TypeFloat -> True+ TypeDouble -> compute >= compute60+ case s of+ NumSingleType (IntegralNumType t) -> rmw_integral t addr (op t r)+ NumSingleType (FloatingNumType t) -> rmw_floating t addr (op t r)+ --+ _ -> internalError "unexpected transition"++ return_+++-- Parallel forward permutation function which uses a spinlock to acquire+-- a mutex before updating the value at that location.+--+mkPermute_mutex+ :: UID+ -> Gamma aenv+ -> ArrayR (Array sh e)+ -> ShapeR sh'+ -> IRFun2 PTX aenv (e -> e -> e)+ -> IRFun1 PTX aenv (sh -> PrimMaybe sh')+ -> MIRDelayed PTX aenv (Array sh e)+ -> CodeGen PTX (IROpenAcc PTX aenv (Array sh' e))+mkPermute_mutex uid aenv (ArrayR shr tp) shr' combine project marr =+ let+ outR = ArrayR shr' tp+ lockR = ArrayR (ShapeRsnoc ShapeRz) (TupRsingle scalarTypeWord32)+ (arrOut, paramOut) = mutableArray outR "out"+ (arrLock, paramLock) = mutableArray lockR "lock"+ (arrIn, paramIn) = delayedArray "in" marr+ paramEnv = envParam aenv+ start = liftInt 0+ in+ makeOpenAcc uid "permute_mutex" (paramOut ++ paramLock ++ paramIn ++ paramEnv) $ do++ shIn <- delayedExtent arrIn+ end <- shapeSize shr shIn++ imapFromTo start end $ \i -> do++ ix <- indexOfInt shr shIn i+ ix' <- app1 project ix++ -- project element onto the destination array and (atomically) update+ when (isJust ix') $ do+ j <- intOfIndex shr' (irArrayShape arrOut) =<< fromJust ix'+ x <- app1 (delayedLinearIndex arrIn) i++ atomically arrLock j $ do+ y <- readArray TypeInt arrOut j+ r <- app2 combine x y+ writeArray TypeInt arrOut j r++ return_+++-- Atomically execute the critical section only when the lock at the given+-- array indexed is obtained.+--+atomically+ :: IRArray (Vector Word32)+ -> Operands Int+ -> CodeGen PTX a+ -> CodeGen PTX a+atomically barriers i action = do+ dev <- liftCodeGen $ asks ptxDeviceProperties+ if computeCapability dev >= Compute 7 0+ then atomically_thread barriers i action+ else atomically_warp barriers i action+++-- Atomically execute the critical section only when the lock at the given+-- array index is obtained. The thread spins waiting for the lock to be+-- released with exponential backoff on failure in case the lock is+-- contended.+--+-- > uint32_t ns = 8;+-- > while ( atomic_exchange(&lock[i], 1) == 1 ) {+-- > __nanosleep(ns);+-- > if ( ns < 256 ) {+-- > ns *= 2;+-- > }+-- > }+--+-- Requires independent thread scheduling features of SM7+.+--+atomically_thread+ :: IRArray (Vector Word32)+ -> Operands Int+ -> CodeGen PTX a+ -> CodeGen PTX a+atomically_thread barriers i action = do+ let+ lock = integral integralType 1+ unlock = integral integralType 0+ unlock' = ir TypeWord32 unlock+ i32 = TupRsingle scalarTypeInt32+ --+ entry <- newBlock "spinlock.entry"+ sleep <- newBlock "spinlock.backoff"+ moar <- newBlock "spinlock.backoff-moar"+ start <- newBlock "spinlock.critical-start"+ end <- newBlock "spinlock.critical-end"+ exit <- newBlock "spinlock.exit"+ ns <- fresh i32++ addr <- instr' $ GetElementPtr $ GEP1 scalarType (asPtr defaultAddrSpace (op integralType (irArrayData barriers))) (op integralType i)+ top <- br entry++ -- Loop until this thread has completed its critical section. If the slot+ -- was unlocked we just acquired the lock and the thread can perform its+ -- critical section, otherwise sleep the thread and try again later.+ setBlock entry+ old <- instr $ AtomicRMW numType NonVolatile Exchange addr lock (CrossThread, Acquire)+ ok <- A.eq singleType old unlock'+ _ <- cbr ok start sleep++ -- We did not acquire the lock. Sleep the thread for a small amount of+ -- time and (possibly) increase the sleep duration for the next round+ setBlock sleep+ _ <- nanosleep ns+ p <- A.lt singleType ns (ir TypeInt32 (integral integralType 256))+ _ <- cbr p moar entry++ setBlock moar+ ns' <- A.mul numType ns (ir TypeInt32 (integral integralType 2))+ _ <- phi' i32 entry ns [(ir TypeInt32 (integral (integralType) 8), top), (ns, sleep), (ns', moar)]+ _ <- br entry++ -- If we just acquired the lock, execute the critical section, then+ -- release the lock and continue with your day.+ setBlock start+ r <- action+ _ <- br end++ setBlock end+ _ <- instr $ AtomicRMW numType NonVolatile Exchange addr unlock (CrossThread, AcquireRelease)+ _ <- __threadfence_grid -- TODO: why is this required?+ _ <- br exit++ setBlock exit+ return r+++-- Atomically execute the critical section only when the lock at the given array+-- index is obtained. The thread spins waiting for the lock to be released and+-- there is no backoff strategy in case the lock is contended.+--+-- The canonical implementation of a spin-lock looks like this:+--+-- > do {+-- > old = atomic_exchange(&lock[i], 1);+-- > } while (old == 1);+-- >+-- > /* critical section */+-- >+-- > atomic_exchange(&lock[i], 0);+--+-- The initial loop repeatedly attempts to take the lock by writing a 1 (locked)+-- into the lock slot. Once the 'old' state of the lock returns 0 (unlocked),+-- then we just acquired the lock and the atomic section can be computed.+-- Finally, the lock is released by writing 0 back to the lock slot.+--+-- However, there is a complication with CUDA devices because all threads in+-- a warp must execute in lockstep (with predicated execution). In the above+-- setup, once a thread acquires a lock, then it will be disabled and stop+-- participating in the loop, waiting for all other threads (to acquire their+-- locks) before continuing program execution. If two threads in the same warp+-- attempt to acquire the same lock, then once the lock is acquired by one+-- thread then it will sit idle waiting while the second thread spins attempting+-- to grab a lock that will never be released because the first thread (which+-- holds the lock) can not make progress. DEADLOCK.+--+-- To prevent this situation we must invert the algorithm so that threads can+-- always make progress, until each warp in the thread has committed their+-- result.+--+-- > done = 0;+-- > do {+-- > if ( atomic_exchange(&lock[i], 1) == 0 ) {+-- >+-- > /* critical section */+-- >+-- > done = 1;+-- > atomic_exchange(&lock[i], 0);+-- > }+-- > } while ( done == 0 );+--+atomically_warp+ :: IRArray (Vector Word32)+ -> Operands Int+ -> CodeGen PTX a+ -> CodeGen PTX a+atomically_warp barriers i action = do+ let+ lock = integral integralType 1+ unlock = integral integralType 0+ unlock' = ir TypeWord32 unlock+ --+ entry <- newBlock "spinlock.entry"+ start <- newBlock "spinlock.critical-start"+ end <- newBlock "spinlock.critical-end"+ exit <- newBlock "spinlock.exit"++ addr <- instr' $ GetElementPtr $ GEP1 scalarType (asPtr defaultAddrSpace (op integralType (irArrayData barriers))) (op integralType i)+ _ <- br entry++ -- Loop until this thread has completed its critical section. If the slot was+ -- unlocked then we just acquired the lock and the thread can perform the+ -- critical section, otherwise skip to the bottom of the critical section.+ setBlock entry+ old <- instr $ AtomicRMW numType NonVolatile Exchange addr lock (CrossThread, Acquire)+ ok <- A.eq singleType old unlock'+ no <- cbr ok start end++ -- If we just acquired the lock, execute the critical section+ setBlock start+ r <- action+ _ <- instr $ AtomicRMW numType NonVolatile Exchange addr unlock (CrossThread, AcquireRelease)+ yes <- br end++ -- At the base of the critical section, threads participate in a memory fence+ -- to ensure the lock state is committed to memory. Depending on which+ -- incoming edge the thread arrived at this block from determines whether they+ -- have completed their critical section.+ setBlock end+ res <- freshLocalName+ done <- phi1 end res [(boolean True, yes), (boolean False, no)]++ __syncthreads+ _ <- cbr (OP_Bool done) exit entry++ setBlock exit+ return r+
+ src/Data/Array/Accelerate/LLVM/PTX/CodeGen/Scan.hs view
@@ -0,0 +1,1300 @@+{-# LANGUAGE GADTs #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE PatternGuards #-}+{-# LANGUAGE RebindableSyntax #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE ViewPatterns #-}+-- |+-- Module : Data.Array.Accelerate.LLVM.PTX.CodeGen.Scan+-- Copyright : [2016..2020] The Accelerate Team+-- License : BSD3+--+-- Maintainer : Trevor L. McDonell <trevor.mcdonell@gmail.com>+-- Stability : experimental+-- Portability : non-portable (GHC extensions)+--++module Data.Array.Accelerate.LLVM.PTX.CodeGen.Scan (++ mkScan, mkScan',++) where++import Data.Array.Accelerate.AST ( Direction(..) )+import Data.Array.Accelerate.Representation.Array+import Data.Array.Accelerate.Representation.Shape+import Data.Array.Accelerate.Representation.Type++import Data.Array.Accelerate.LLVM.CodeGen.Arithmetic as A+import Data.Array.Accelerate.LLVM.CodeGen.Array+import Data.Array.Accelerate.LLVM.CodeGen.Base+import Data.Array.Accelerate.LLVM.CodeGen.Constant+import Data.Array.Accelerate.LLVM.CodeGen.Environment+import Data.Array.Accelerate.LLVM.CodeGen.Exp+import Data.Array.Accelerate.LLVM.CodeGen.IR+import Data.Array.Accelerate.LLVM.CodeGen.Loop+import Data.Array.Accelerate.LLVM.CodeGen.Monad+import Data.Array.Accelerate.LLVM.CodeGen.Sugar+import Data.Array.Accelerate.LLVM.Compile.Cache+import Data.Array.Accelerate.LLVM.PTX.Analysis.Launch+import Data.Array.Accelerate.LLVM.PTX.CodeGen.Base+import Data.Array.Accelerate.LLVM.PTX.CodeGen.Generate+import Data.Array.Accelerate.LLVM.PTX.Target++import LLVM.AST.Type.Representation++import qualified Foreign.CUDA.Analysis as CUDA++import Control.Applicative+import Control.Monad ( (>=>), void )+import Control.Monad.Reader ( asks )+import Data.String ( fromString )+import Data.Coerce as Safe+import Data.Bits as P+import Prelude as P hiding ( last )++++-- 'Data.List.scanl' or 'Data.List.scanl1' style exclusive scan, but with the+-- restriction that the combination function must be associative to enable+-- efficient parallel implementation.+--+-- > scanl (+) 10 (use $ fromList (Z :. 10) [0..])+-- >+-- > ==> Array (Z :. 11) [10,10,11,13,16,20,25,31,38,46,55]+--+mkScan+ :: forall aenv sh e.+ UID+ -> Gamma aenv+ -> ArrayR (Array (sh, Int) e)+ -> Direction+ -> IRFun2 PTX aenv (e -> e -> e)+ -> Maybe (IRExp PTX aenv e)+ -> MIRDelayed PTX aenv (Array (sh, Int) e)+ -> CodeGen PTX (IROpenAcc PTX aenv (Array (sh, Int) e))+mkScan uid aenv repr dir combine seed arr+ = foldr1 (+++) <$> sequence (codeScan ++ codeFill)++ where+ codeScan = case repr of+ ArrayR (ShapeRsnoc ShapeRz) tp -> [ mkScanAllP1 dir uid aenv tp combine seed arr+ , mkScanAllP2 dir uid aenv tp combine+ , mkScanAllP3 dir uid aenv tp combine seed+ ]+ _ -> [ mkScanDim dir uid aenv repr combine seed arr+ ]+ codeFill = case seed of+ Just s -> [ mkScanFill uid aenv repr s ]+ Nothing -> []+++-- Variant of 'scanl' where the final result is returned in a separate array.+--+-- > scanr' (+) 10 (use $ fromList (Z :. 10) [0..])+-- >+-- > ==> ( Array (Z :. 10) [10,10,11,13,16,20,25,31,38,46]+-- , Array Z [55]+-- )+--+mkScan'+ :: forall aenv sh e.+ UID+ -> Gamma aenv+ -> ArrayR (Array (sh, Int) e)+ -> Direction+ -> IRFun2 PTX aenv (e -> e -> e)+ -> IRExp PTX aenv e+ -> MIRDelayed PTX aenv (Array (sh, Int) e)+ -> CodeGen PTX (IROpenAcc PTX aenv (Array (sh, Int) e, Array sh e))+mkScan' uid aenv repr dir combine seed arr+ | ArrayR (ShapeRsnoc ShapeRz) tp <- repr+ = foldr1 (+++) <$> sequence [ mkScan'AllP1 dir uid aenv tp combine seed arr+ , mkScan'AllP2 dir uid aenv tp combine+ , mkScan'AllP3 dir uid aenv tp combine+ , mkScan'Fill uid aenv repr seed+ ]+ --+ | otherwise+ = (+++) <$> mkScan'Dim dir uid aenv repr combine seed arr+ <*> mkScan'Fill uid aenv repr seed+++-- Device wide scans+-- -----------------+--+-- This is a classic two-pass algorithm which proceeds in two phases and+-- requires ~4n data movement to global memory. In future we would like to+-- replace this with a single pass algorithm.+--++-- Parallel scan, step 1.+--+-- Threads scan a stripe of the input into a temporary array, incorporating the+-- initial element and any fused functions on the way. The final reduction+-- result of this chunk is written to a separate array.+--+mkScanAllP1+ :: forall aenv e.+ Direction+ -> UID+ -> Gamma aenv -- ^ array environment+ -> TypeR e+ -> IRFun2 PTX aenv (e -> e -> e) -- ^ combination function+ -> MIRExp PTX aenv e -- ^ seed element, if this is an exclusive scan+ -> MIRDelayed PTX aenv (Vector e) -- ^ input data+ -> CodeGen PTX (IROpenAcc PTX aenv (Vector e))+mkScanAllP1 dir uid aenv tp combine mseed marr = do+ dev <- liftCodeGen $ asks ptxDeviceProperties+ --+ let+ (arrOut, paramOut) = mutableArray (ArrayR dim1 tp) "out"+ (arrTmp, paramTmp) = mutableArray (ArrayR dim1 tp) "tmp"+ (arrIn, paramIn) = delayedArray "in" marr+ end = indexHead (irArrayShape arrTmp)+ paramEnv = envParam aenv+ --+ config = launchConfig dev (CUDA.incWarp dev) (scanSMemSize dev tp) const [|| const ||]+ --+ makeOpenAccWith config uid "scanP1" (paramTmp ++ paramOut ++ paramIn ++ paramEnv) $ do++ -- Size of the input array+ sz <- indexHead <$> delayedExtent arrIn++ -- A thread block scans a non-empty stripe of the input, storing the final+ -- block-wide aggregate into a separate array+ --+ -- For exclusive scans, thread 0 of segment 0 must incorporate the initial+ -- element into the input and output. Threads shuffle their indices+ -- appropriately.+ --+ bid <- blockIdx+ gd <- gridDim+ gd' <- int gd+ s0 <- int bid++ -- iterating over thread-block-wide segments+ -- Note that 'end' is a multiple of the gd', and the control flow is thus uniform in the loop.+ -- This is set in scanAllOp in Data.Array.Accelerate.LLVM.PTX.Execute.+ -- Hence we can run __syncthreads safely.+ imapFromStepTo s0 gd' end $ \chunk -> do++ -- Make sure all threads have finished previous iterations,+ -- so we can reuse (and overwrite) shared memory.+ __syncthreads++ bd <- blockDim+ bd' <- int bd+ inf <- A.mul numType chunk bd'++ -- index i* is the index that this thread will read data from. Recall that+ -- the supremum index is exclusive+ tid <- threadIdx+ tid' <- int tid+ i0 <- case dir of+ LeftToRight -> A.add numType inf tid'+ RightToLeft -> do x <- A.sub numType sz inf+ y <- A.sub numType x tid'+ z <- A.sub numType y (liftInt 1)+ return z++ -- index j* is the index that we write to. Recall that for exclusive scans+ -- the output array is one larger than the input; the initial element will+ -- be written into this spot by thread 0 of the first thread block.+ j0 <- case mseed of+ Nothing -> return i0+ Just _ -> case dir of+ LeftToRight -> A.add numType i0 (liftInt 1)+ RightToLeft -> return i0++ -- If this thread has input, read data and participate in thread-block scan+ let valid i = case dir of+ LeftToRight -> A.lt singleType i sz+ RightToLeft -> A.gte singleType i (liftInt 0)++ when (valid i0) $ do+ x0 <- app1 (delayedLinearIndex arrIn) i0+ x1 <- case mseed of+ Nothing -> return x0+ Just seed ->+ if (tp, A.eq singleType tid (liftInt32 0) `A.land'` A.eq singleType chunk (liftInt 0))+ then do+ z <- seed+ case dir of+ LeftToRight -> writeArray TypeInt32 arrOut (liftInt32 0) z >> app2 combine z x0+ RightToLeft -> writeArray TypeInt arrOut sz z >> app2 combine x0 z+ else+ return x0++ n <- A.sub numType sz inf+ n' <- i32 n+ x2 <- if (tp, A.gte singleType n bd')+ then scanBlock dir dev tp combine Nothing x1+ else scanBlock dir dev tp combine (Just n') x1++ -- Write this thread's scan result to memory+ writeArray TypeInt arrOut j0 x2++ -- The last thread also writes its result---the aggregate for this+ -- thread block---to the temporary partial sums array. This is only+ -- necessary for full blocks in a multi-block scan; the final+ -- partially-full tile does not have a successor block.+ last <- A.sub numType bd (liftInt32 1)+ when (A.gt singleType gd (liftInt32 1) `land'` A.eq singleType tid last) $+ case dir of+ LeftToRight -> writeArray TypeInt arrTmp chunk x2+ RightToLeft -> do u <- A.sub numType end chunk+ v <- A.sub numType u (liftInt 1)+ writeArray TypeInt arrTmp v x2++ return_+++-- Parallel scan, step 2+--+-- A single thread block performs a scan of the per-block aggregates computed in+-- step 1. This gives the per-block prefix which must be added to each element+-- in step 3.+--+mkScanAllP2+ :: forall aenv e.+ Direction+ -> UID+ -> Gamma aenv -- ^ array environment+ -> TypeR e+ -> IRFun2 PTX aenv (e -> e -> e) -- ^ combination function+ -> CodeGen PTX (IROpenAcc PTX aenv (Vector e))+mkScanAllP2 dir uid aenv tp combine = do+ dev <- liftCodeGen $ asks ptxDeviceProperties+ --+ let+ (arrTmp, paramTmp) = mutableArray (ArrayR dim1 tp) "tmp"+ paramEnv = envParam aenv+ start = liftInt 0+ end = indexHead (irArrayShape arrTmp)+ --+ config = launchConfig dev (CUDA.incWarp dev) (scanSMemSize dev tp) grid gridQ+ grid _ _ = 1+ gridQ = [|| \_ _ -> 1 ||]+ --+ makeOpenAccWith config uid "scanP2" (paramTmp ++ paramEnv) $ do++ -- The first and last threads of the block need to communicate the+ -- block-wide aggregate as a carry-in value across iterations.+ --+ -- TODO: We could optimise this a bit if we can get access to the shared+ -- memory area used by 'scanBlockSMem', and from there directly read the+ -- value computed by the last thread.+ carry <- staticSharedMem tp 1++ bd <- blockDim+ bd' <- int bd++ imapFromStepTo start bd' end $ \offset -> do++ -- Index of the partial sums array that this thread will process.+ tid <- threadIdx+ tid' <- int tid+ i0 <- case dir of+ LeftToRight -> A.add numType offset tid'+ RightToLeft -> do x <- A.sub numType end offset+ y <- A.sub numType x tid'+ z <- A.sub numType y (liftInt 1)+ return z++ let valid i = case dir of+ LeftToRight -> A.lt singleType i end+ RightToLeft -> A.gte singleType i start++ -- wait for the carry-in value to be updated+ __syncthreads++ when (valid i0) $ do+ x0 <- readArray TypeInt arrTmp i0+ x1 <- if (tp, A.gt singleType offset (liftInt 0) `land'` A.eq singleType tid (liftInt32 0))+ then do+ c <- readArray TypeInt32 carry (liftInt32 0)+ case dir of+ LeftToRight -> app2 combine c x0+ RightToLeft -> app2 combine x0 c+ else do+ return x0++ n <- A.sub numType end offset+ n' <- i32 n+ x2 <- if (tp, A.gte singleType n bd')+ then scanBlock dir dev tp combine Nothing x1+ else scanBlock dir dev tp combine (Just n') x1++ -- Update the temporary array with this thread's result+ writeArray TypeInt arrTmp i0 x2++ -- The last thread writes the carry-out value. If the last thread is not+ -- active, then this must be the last stripe anyway.+ last <- A.sub numType bd (liftInt32 1)+ when (A.eq singleType tid last) $+ writeArray TypeInt32 carry (liftInt32 0) x2++ return_+++-- Parallel scan, step 3.+--+-- Threads combine every element of the partial block results with the carry-in+-- value computed in step 2.+--+mkScanAllP3+ :: forall aenv e.+ Direction+ -> UID+ -> Gamma aenv -- ^ array environment+ -> TypeR e+ -> IRFun2 PTX aenv (e -> e -> e) -- ^ combination function+ -> MIRExp PTX aenv e -- ^ seed element, if this is an exclusive scan+ -> CodeGen PTX (IROpenAcc PTX aenv (Vector e))+mkScanAllP3 dir uid aenv tp combine mseed = do+ dev <- liftCodeGen $ asks ptxDeviceProperties+ --+ let+ (arrOut, paramOut) = mutableArray (ArrayR dim1 tp) "out"+ (arrTmp, paramTmp) = mutableArray (ArrayR dim1 tp) "tmp"+ paramEnv = envParam aenv+ --+ stride = local (TupRsingle scalarTypeInt) "ix.stride"+ paramStride = parameter (TupRsingle scalarTypeInt) "ix.stride"+ --+ config = launchConfig dev (CUDA.incWarp dev) (const 0) const [|| const ||]+ --+ makeOpenAccWith config uid "scanP3" (paramTmp ++ paramOut ++ paramStride ++ paramEnv) $ do++ sz <- return $ indexHead (irArrayShape arrOut)+ tid <- int =<< threadIdx++ -- Threads that will never contribute can just exit immediately. The size of+ -- each chunk is set by the block dimension of the step 1 kernel, which may+ -- be different from the block size of this kernel.+ when (A.lt singleType tid stride) $ do++ -- Iterate over the segments computed in phase 1. Note that we have one+ -- fewer chunk to process because the first has no carry-in.+ bid <- int =<< blockIdx+ gd <- int =<< gridDim+ end <- A.sub numType (indexHead (irArrayShape arrTmp)) (liftInt 1)++ imapFromStepTo bid gd end $ \chunk -> do++ -- Determine the start and end indicies of this chunk to which we will+ -- carry-in the value. Returned for left-to-right traversal.+ (inf,sup) <- case dir of+ LeftToRight -> do+ a <- A.add numType chunk (liftInt 1)+ b <- A.mul numType stride a+ case mseed of+ Just{} -> do+ c <- A.add numType b (liftInt 1)+ d <- A.add numType c stride+ e <- A.min singleType d sz+ return (c,e)+ Nothing -> do+ c <- A.add numType b stride+ d <- A.min singleType c sz+ return (b,d)+ RightToLeft -> do+ a <- A.sub numType end chunk+ b <- A.mul numType stride a+ c <- A.sub numType sz b+ case mseed of+ Just{} -> do+ d <- A.sub numType c (liftInt 1)+ e <- A.sub numType d stride+ f <- A.max singleType e (liftInt 0)+ return (f,d)+ Nothing -> do+ d <- A.sub numType c stride+ e <- A.max singleType d (liftInt 0)+ return (e,c)++ -- Read the carry-in value+ carry <- case dir of+ LeftToRight -> readArray TypeInt arrTmp chunk+ RightToLeft -> do+ a <- A.add numType chunk (liftInt 1)+ b <- readArray TypeInt arrTmp a+ return b++ -- Apply the carry-in value to each element in the chunk+ bd <- int =<< blockDim+ i0 <- A.add numType inf tid+ imapFromStepTo i0 bd sup $ \i -> do+ v <- readArray TypeInt arrOut i+ u <- case dir of+ LeftToRight -> app2 combine carry v+ RightToLeft -> app2 combine v carry+ writeArray TypeInt arrOut i u++ return_+++-- Parallel scan', step 1.+--+-- Similar to mkScanAllP1. Threads scan a stripe of the input into a temporary+-- array, incorporating the initial element and any fused functions on the way.+-- The final reduction result of this chunk is written to a separate array.+--+mkScan'AllP1+ :: forall aenv e.+ Direction+ -> UID+ -> Gamma aenv+ -> TypeR e+ -> IRFun2 PTX aenv (e -> e -> e)+ -> IRExp PTX aenv e+ -> MIRDelayed PTX aenv (Vector e)+ -> CodeGen PTX (IROpenAcc PTX aenv (Vector e, Scalar e))+mkScan'AllP1 dir uid aenv tp combine seed marr = do+ dev <- liftCodeGen $ asks ptxDeviceProperties+ --+ let+ (arrOut, paramOut) = mutableArray (ArrayR dim1 tp) "out"+ (arrTmp, paramTmp) = mutableArray (ArrayR dim1 tp) "tmp"+ (arrIn, paramIn) = delayedArray "in" marr+ end = indexHead (irArrayShape arrTmp)+ paramEnv = envParam aenv+ --+ config = launchConfig dev (CUDA.incWarp dev) (scanSMemSize dev tp) const [|| const ||]+ --+ makeOpenAccWith config uid "scanP1" (paramTmp ++ paramOut ++ paramIn ++ paramEnv) $ do++ -- Size of the input array+ sz <- indexHead <$> delayedExtent arrIn++ -- A thread block scans a non-empty stripe of the input, storing the partial+ -- result and the final block-wide aggregate+ bid <- int =<< blockIdx+ gd <- int =<< gridDim++ -- iterate over thread-block wide segments+ -- Note that 'end' is a multiple of the gd', and the control flow is thus uniform in the loop.+ -- This is set in scan'AllOp in Data.Array.Accelerate.LLVM.PTX.Execute.+ -- Hence we can run __syncthreads safely.+ imapFromStepTo bid gd end $ \seg -> do++ -- Make sure all threads have finished previous iterations,+ -- so we can reuse (and overwrite) shared memory.+ __syncthreads++ bd <- int =<< blockDim+ inf <- A.mul numType seg bd++ -- i* is the index that this thread will read data from+ tid <- int =<< threadIdx+ i0 <- case dir of+ LeftToRight -> A.add numType inf tid+ RightToLeft -> do x <- A.sub numType sz inf+ y <- A.sub numType x tid+ z <- A.sub numType y (liftInt 1)+ return z++ -- j* is the index this thread will write to. This is just shifted by one+ -- to make room for the initial element+ j0 <- case dir of+ LeftToRight -> A.add numType i0 (liftInt 1)+ RightToLeft -> A.sub numType i0 (liftInt 1)++ -- If this thread has input it participates in the scan+ let valid i = case dir of+ LeftToRight -> A.lt singleType i sz+ RightToLeft -> A.gte singleType i (liftInt 0)++ when (valid i0) $ do+ x0 <- app1 (delayedLinearIndex arrIn) i0++ -- Thread 0 of the first segment must also evaluate and store the+ -- initial element+ ti <- threadIdx+ x1 <- if (tp, A.eq singleType ti (liftInt32 0) `A.land'` A.eq singleType seg (liftInt 0))+ then do+ z <- seed+ writeArray TypeInt arrOut i0 z+ case dir of+ LeftToRight -> app2 combine z x0+ RightToLeft -> app2 combine x0 z+ else+ return x0++ -- Block-wide scan+ n <- A.sub numType sz inf+ n' <- i32 n+ x2 <- if (tp, A.gte singleType n bd)+ then scanBlock dir dev tp combine Nothing x1+ else scanBlock dir dev tp combine (Just n') x1++ -- Write this thread's scan result to memory. Recall that we had to make+ -- space for the initial element, so the very last thread does not store+ -- its result here.+ case dir of+ LeftToRight -> when (A.lt singleType j0 sz) $ writeArray TypeInt arrOut j0 x2+ RightToLeft -> when (A.gte singleType j0 (liftInt 0)) $ writeArray TypeInt arrOut j0 x2++ -- Last active thread writes its result to the partial sums array. These+ -- will be used to compute the carry-in value in step 2.+ m <- do x <- A.min singleType n bd+ y <- A.sub numType x (liftInt 1)+ return y+ when (A.eq singleType tid m) $+ case dir of+ LeftToRight -> writeArray TypeInt arrTmp seg x2+ RightToLeft -> do x <- A.sub numType end seg+ y <- A.sub numType x (liftInt 1)+ writeArray TypeInt arrTmp y x2++ return_+++-- Parallel scan', step 2+--+-- A single thread block performs an inclusive scan of the partial sums array to+-- compute the per-block carry-in values, as well as the final reduction result.+--+mkScan'AllP2+ :: forall aenv e.+ Direction+ -> UID+ -> Gamma aenv+ -> TypeR e+ -> IRFun2 PTX aenv (e -> e -> e)+ -> CodeGen PTX (IROpenAcc PTX aenv (Vector e, Scalar e))+mkScan'AllP2 dir uid aenv tp combine = do+ dev <- liftCodeGen $ asks ptxDeviceProperties+ --+ let+ (arrTmp, paramTmp) = mutableArray (ArrayR dim1 tp) "tmp"+ (arrSum, paramSum) = mutableArray (ArrayR dim0 tp) "sum"+ paramEnv = envParam aenv+ start = liftInt 0+ end = indexHead (irArrayShape arrTmp)+ --+ config = launchConfig dev (CUDA.incWarp dev) (scanSMemSize dev tp) grid gridQ+ grid _ _ = 1+ gridQ = [|| \_ _ -> 1 ||]+ --+ makeOpenAccWith config uid "scanP2" (paramTmp ++ paramSum ++ paramEnv) $ do++ -- The first and last threads of the block need to communicate the+ -- block-wide aggregate as a carry-in value across iterations.+ carry <- staticSharedMem tp 1++ -- A single thread block iterates over the per-block partial results from+ -- step 1+ tid <- threadIdx+ tid' <- int tid+ bd <- int =<< blockDim++ imapFromStepTo start bd end $ \offset -> do++ i0 <- case dir of+ LeftToRight -> A.add numType offset tid'+ RightToLeft -> do x <- A.sub numType end offset+ y <- A.sub numType x tid'+ z <- A.sub numType y (liftInt 1)+ return z++ let valid i = case dir of+ LeftToRight -> A.lt singleType i end+ RightToLeft -> A.gte singleType i start++ -- wait for the carry-in value to be updated+ __syncthreads++ x0 <- if (tp, valid i0)+ then readArray TypeInt arrTmp i0+ else+ return $ tupUndef tp++ x1 <- if (tp, A.gt singleType offset (liftInt 0) `A.land'` A.eq singleType tid (liftInt32 0))+ then do+ c <- readArray TypeInt32 carry (liftInt32 0)+ case dir of+ LeftToRight -> app2 combine c x0+ RightToLeft -> app2 combine x0 c+ else+ return x0++ n <- A.sub numType end offset+ n' <- i32 n+ x2 <- if (tp, A.gte singleType n bd)+ then scanBlock dir dev tp combine Nothing x1+ else scanBlock dir dev tp combine (Just n') x1++ -- Update the partial results array+ when (valid i0) $+ writeArray TypeInt arrTmp i0 x2++ -- The last active thread saves its result as the carry-out value.+ m <- do x <- A.min singleType bd n+ y <- A.sub numType x (liftInt 1)+ z <- i32 y+ return z+ when (A.eq singleType tid m) $+ writeArray TypeInt32 carry (liftInt32 0) x2++ -- First thread stores the final carry-out values at the final reduction+ -- result for the entire array+ __syncthreads++ when (A.eq singleType tid (liftInt32 0)) $+ writeArray TypeInt32 arrSum (liftInt32 0) =<< readArray TypeInt32 carry (liftInt32 0)++ return_+++-- Parallel scan', step 3.+--+-- Threads combine every element of the partial block results with the carry-in+-- value computed in step 2.+--+mkScan'AllP3+ :: forall aenv e.+ Direction+ -> UID+ -> Gamma aenv -- ^ array environment+ -> TypeR e+ -> IRFun2 PTX aenv (e -> e -> e) -- ^ combination function+ -> CodeGen PTX (IROpenAcc PTX aenv (Vector e, Scalar e))+mkScan'AllP3 dir uid aenv tp combine = do+ dev <- liftCodeGen $ asks ptxDeviceProperties+ --+ let+ (arrOut, paramOut) = mutableArray (ArrayR dim1 tp) "out"+ (arrTmp, paramTmp) = mutableArray (ArrayR dim1 tp) "tmp"+ paramEnv = envParam aenv+ --+ stride = local (TupRsingle scalarTypeInt) "ix.stride"+ paramStride = parameter (TupRsingle scalarTypeInt) "ix.stride"+ --+ config = launchConfig dev (CUDA.incWarp dev) (const 0) const [|| const ||]+ --+ makeOpenAccWith config uid "scanP3" (paramTmp ++ paramOut ++ paramStride ++ paramEnv) $ do++ sz <- return $ indexHead (irArrayShape arrOut)+ tid <- int =<< threadIdx++ when (A.lt singleType tid stride) $ do++ bid <- int =<< blockIdx+ gd <- int =<< gridDim+ end <- A.sub numType (indexHead (irArrayShape arrTmp)) (liftInt 1)++ imapFromStepTo bid gd end $ \chunk -> do++ (inf,sup) <- case dir of+ LeftToRight -> do+ a <- A.add numType chunk (liftInt 1)+ b <- A.mul numType stride a+ c <- A.add numType b (liftInt 1)+ d <- A.add numType c stride+ e <- A.min singleType d sz+ return (c,e)+ RightToLeft -> do+ a <- A.sub numType end chunk+ b <- A.mul numType stride a+ c <- A.sub numType sz b+ d <- A.sub numType c (liftInt 1)+ e <- A.sub numType d stride+ f <- A.max singleType e (liftInt 0)+ return (f,d)++ carry <- case dir of+ LeftToRight -> readArray TypeInt arrTmp chunk+ RightToLeft -> do+ a <- A.add numType chunk (liftInt 1)+ b <- readArray TypeInt arrTmp a+ return b++ -- Apply the carry-in value to each element in the chunk+ bd <- int =<< blockDim+ i0 <- A.add numType inf tid+ imapFromStepTo i0 bd sup $ \i -> do+ v <- readArray TypeInt arrOut i+ u <- case dir of+ LeftToRight -> app2 combine carry v+ RightToLeft -> app2 combine v carry+ writeArray TypeInt arrOut i u++ return_+++-- Multidimensional scans+-- ----------------------++-- Multidimensional scan along the innermost dimension+--+-- A thread block individually computes along each innermost dimension. This is+-- a single-pass operation.+--+-- * We can assume that the array is non-empty; exclusive scans with empty+-- innermost dimension will be instead filled with the seed element via+-- 'mkScanFill'.+--+-- * Small but non-empty innermost dimension arrays (size << thread+-- block size) will have many threads which do no work.+--+mkScanDim+ :: forall aenv sh e.+ Direction+ -> UID+ -> Gamma aenv -- ^ array environment+ -> ArrayR (Array (sh, Int) e)+ -> IRFun2 PTX aenv (e -> e -> e) -- ^ combination function+ -> MIRExp PTX aenv e -- ^ seed element, if this is an exclusive scan+ -> MIRDelayed PTX aenv (Array (sh, Int) e) -- ^ input data+ -> CodeGen PTX (IROpenAcc PTX aenv (Array (sh, Int) e))+mkScanDim dir uid aenv repr@(ArrayR (ShapeRsnoc shr) tp) combine mseed marr = do+ dev <- liftCodeGen $ asks ptxDeviceProperties+ --+ let+ (arrOut, paramOut) = mutableArray repr "out"+ (arrIn, paramIn) = delayedArray "in" marr+ paramEnv = envParam aenv+ --+ config = launchConfig dev (CUDA.incWarp dev) (scanSMemSize dev tp) const [|| const ||]+ --+ makeOpenAccWith config uid "scan" (paramOut ++ paramIn ++ paramEnv) $ do++ -- The first and last threads of the block need to communicate the+ -- block-wide aggregate as a carry-in value across iterations.+ --+ -- TODO: we could optimise this a bit if we can get access to the shared+ -- memory area used by 'scanBlockSMem', and from there directly read the+ -- value computed by the last thread.+ carry <- staticSharedMem tp 1++ -- Size of the input array+ sz <- indexHead <$> delayedExtent arrIn++ -- Thread blocks iterate over the outer dimensions. Threads in a block+ -- cooperatively scan along one dimension, but thread blocks do not+ -- communicate with each other.+ --+ bid <- int =<< blockIdx+ gd <- int =<< gridDim+ end <- shapeSize shr (indexTail (irArrayShape arrOut))++ -- Iterate over the outer dimensions (all but the innermost dimension).+ -- Within this loop we perform a scan over a row (innermost dimension) of+ -- the input.+ --+ -- Since 'bid', 'gd' and 'end' are uniform, the control flow within this+ -- loop is also uniform. We can thus perform __syncthreads in the loop.+ imapFromStepTo bid gd end $ \seg -> do++ -- Make sure all threads have finished previous iterations,+ -- so we can reuse (and overwrite) shared memory.+ __syncthreads++ -- Index this thread reads from+ tid <- threadIdx+ tid' <- int tid+ i0 <- case dir of+ LeftToRight -> do x <- A.mul numType seg sz+ y <- A.add numType x tid'+ return y++ RightToLeft -> do x <- A.add numType seg (liftInt 1)+ y <- A.mul numType x sz+ z <- A.sub numType y tid'+ w <- A.sub numType z (liftInt 1)+ return w++ -- Index this thread writes to+ j0 <- case mseed of+ Nothing -> return i0+ Just{} -> do szp1 <- return $ indexHead (irArrayShape arrOut)+ case dir of+ LeftToRight -> do x <- A.mul numType seg szp1+ y <- A.add numType x tid'+ return y++ RightToLeft -> do x <- A.add numType seg (liftInt 1)+ y <- A.mul numType x szp1+ z <- A.sub numType y tid'+ w <- A.sub numType z (liftInt 1)+ return w++ -- Stride indices by block dimension+ bd <- blockDim+ bd' <- int bd+ let next ix = case dir of+ LeftToRight -> A.add numType ix bd'+ RightToLeft -> A.sub numType ix bd'++ -- Initialise this scan segment+ --+ -- If this is an exclusive scan then the first thread just evaluates the+ -- seed element and stores this value into the carry-in slot. All threads+ -- shift their write-to index (j) by one, to make space for this element.+ --+ -- If this is an inclusive scan then do a block-wide scan. The last thread+ -- in the block writes the carry-in value.+ --+ r <-+ case mseed of+ Just seed -> do+ when (A.eq singleType tid (liftInt32 0)) $ do+ z <- seed+ writeArray TypeInt arrOut j0 z+ writeArray TypeInt32 carry (liftInt32 0) z+ j1 <- case dir of+ LeftToRight -> A.add numType j0 (liftInt 1)+ RightToLeft -> A.sub numType j0 (liftInt 1)+ return $ A.trip sz i0 j1++ Nothing -> do+ -- We cannot call scanBlock under non-uniform control flow.+ -- Instead, we conditionally read the input, and then+ -- unconditionally call scanBlock.+ x0 <- if (tp, A.lt singleType tid' sz)+ then app1 (delayedLinearIndex arrIn) i0+ else return $ tupUndef tp+ n' <- i32 sz++ r0 <- if (tp, A.gte singleType sz bd')+ then scanBlock dir dev tp combine Nothing x0+ else scanBlock dir dev tp combine (Just n') x0++ when (A.lt singleType tid' sz) $ do+ writeArray TypeInt arrOut j0 r0++ ll <- A.sub numType bd (liftInt32 1)+ when (A.eq singleType tid ll) $+ writeArray TypeInt32 carry (liftInt32 0) r0++ n1 <- A.sub numType sz bd'+ i1 <- next i0+ j1 <- next j0+ return $ A.trip n1 i1 j1++ -- Iterate over the remaining elements in this segment+ -- The loop condition uses the first triple of the state, 'n'.+ -- This variable is uniform (the same for all threads in the thread+ -- block), since it is initialized as 'sz' or 'sz - bd', and lowered by+ -- 'bd' each iteration. Hence the control flow in this loop is uniform,+ -- and we can thus call __syncthreads within the loop.+ void $ while+ (TupRunit `TupRpair` TupRsingle scalarTypeInt `TupRpair` TupRsingle scalarTypeInt `TupRpair` TupRsingle scalarTypeInt)+ (\(A.fst3 -> n) -> A.gt singleType n (liftInt 0))+ (\(A.untrip -> (n,i,j)) -> do++ -- Wait for the carry-in value from the previous iteration to be updated+ __syncthreads++ -- Compute and store the next element of the scan+ --+ -- NOTE: As with 'foldSeg' we require all threads to participate in+ -- every iteration of the loop otherwise they will die prematurely.+ -- Out-of-bounds threads return 'undef' at this point, which is really+ -- unfortunate ):+ --+ x <- if (tp, A.lt singleType tid' n)+ then app1 (delayedLinearIndex arrIn) i+ else return $ tupUndef tp++ -- Thread zero incorporates the carry-in element+ y <- if (tp, A.eq singleType tid (liftInt32 0))+ then do+ c <- readArray TypeInt32 carry (liftInt32 0)+ case dir of+ LeftToRight -> app2 combine c x+ RightToLeft -> app2 combine x c+ else+ return x++ -- Perform the scan and write the result to memory+ m <- i32 n+ z <- if (tp, A.gte singleType n bd')+ then scanBlock dir dev tp combine Nothing y+ else scanBlock dir dev tp combine (Just m) y++ when (A.lt singleType tid' n) $ do+ writeArray TypeInt arrOut j z++ -- The last thread of the block writes its result as the carry-out+ -- value. If this thread is not active then we are on the last+ -- iteration of the loop and it will not be needed.+ w <- A.sub numType bd (liftInt32 1)+ when (A.eq singleType tid w) $+ writeArray TypeInt32 carry (liftInt32 0) z++ -- Update indices for the next iteration+ n' <- A.sub numType n bd'+ i' <- next i+ j' <- next j+ return $ A.trip n' i' j')+ r++ return_+++-- Multidimensional scan' along the innermost dimension+--+-- A thread block individually computes along each innermost dimension. This is+-- a single-pass operation.+--+-- * We can assume that the array is non-empty; exclusive scans with empty+-- innermost dimension will be instead filled with the seed element via+-- 'mkScan'Fill'.+--+-- * Small but non-empty innermost dimension arrays (size << thread+-- block size) will have many threads which do no work.+--+mkScan'Dim+ :: forall aenv sh e.+ Direction+ -> UID+ -> Gamma aenv -- ^ array environment+ -> ArrayR (Array (sh, Int) e)+ -> IRFun2 PTX aenv (e -> e -> e) -- ^ combination function+ -> IRExp PTX aenv e -- ^ seed element+ -> MIRDelayed PTX aenv (Array (sh, Int) e) -- ^ input data+ -> CodeGen PTX (IROpenAcc PTX aenv (Array (sh, Int) e, Array sh e))+mkScan'Dim dir uid aenv repr@(ArrayR (ShapeRsnoc shr) tp) combine seed marr = do+ dev <- liftCodeGen $ asks ptxDeviceProperties+ --+ let+ (arrSum, paramSum) = mutableArray (reduceRank repr) "sum"+ (arrOut, paramOut) = mutableArray repr "out"+ (arrIn, paramIn) = delayedArray "in" marr+ paramEnv = envParam aenv+ --+ config = launchConfig dev (CUDA.incWarp dev) (scanSMemSize dev tp) const [|| const ||]+ --+ makeOpenAccWith config uid "scan" (paramOut ++ paramSum ++ paramIn ++ paramEnv) $ do++ -- The first and last threads of the block need to communicate the+ -- block-wide aggregate as a carry-in value across iterations.+ --+ -- TODO: we could optimise this a bit if we can get access to the shared+ -- memory area used by 'scanBlockSMem', and from there directly read the+ -- value computed by the last thread.+ carry <- staticSharedMem tp 1++ -- Size of the input array+ sz <- indexHead <$> delayedExtent arrIn++ -- If the innermost dimension is smaller than the number of threads in the+ -- block, those threads will never contribute to the output.+ tid <- threadIdx+ tid' <- int tid+ when (A.lte singleType tid' sz) $ do++ -- Thread blocks iterate over the outer dimensions, each thread block+ -- cooperatively scanning along each outermost index.+ bid <- int =<< blockIdx+ gd <- int =<< gridDim+ end <- shapeSize shr (irArrayShape arrSum)++ imapFromStepTo bid gd end $ \seg -> do++ -- Not necessary to wait for threads to catch up before starting this segment+ -- __syncthreads++ -- Linear index bounds for this segment+ inf <- A.mul numType seg sz+ sup <- A.add numType inf sz++ -- Index that this thread will read from. Recall that the supremum index+ -- is exclusive.+ i0 <- case dir of+ LeftToRight -> A.add numType inf tid'+ RightToLeft -> do x <- A.sub numType sup tid'+ y <- A.sub numType x (liftInt 1)+ return y++ -- The index that this thread will write to. This is just shifted along+ -- by one to make room for the initial element.+ j0 <- case dir of+ LeftToRight -> A.add numType i0 (liftInt 1)+ RightToLeft -> A.sub numType i0 (liftInt 1)++ -- Evaluate the initial element. Store it into the carry-in slot as well+ -- as to the array as the first element. This is always valid because if+ -- the input array is empty then we will be evaluating via mkScan'Fill.+ when (A.eq singleType tid (liftInt32 0)) $ do+ z <- seed+ writeArray TypeInt arrOut i0 z+ writeArray TypeInt32 carry (liftInt32 0) z++ bd <- blockDim+ bd' <- int bd+ let next ix = case dir of+ LeftToRight -> A.add numType ix bd'+ RightToLeft -> A.sub numType ix bd'++ -- Now, threads iterate over the elements along the innermost dimension.+ -- At each iteration the first thread incorporates the carry-in value+ -- from the previous step.+ --+ -- The index tracks how many elements remain for the thread block, since+ -- indices i* and j* are local to each thread+ n0 <- A.sub numType sup inf+ void $ while+ (TupRunit `TupRpair` TupRsingle scalarTypeInt `TupRpair` TupRsingle scalarTypeInt `TupRpair` TupRsingle scalarTypeInt)+ (\(A.fst3 -> n) -> A.gt singleType n (liftInt 0))+ (\(A.untrip -> (n,i,j)) -> do++ -- Wait for threads to catch up to ensure the carry-in value from+ -- the last iteration has been updated+ __syncthreads++ -- If all threads in the block will participate this round we can+ -- avoid (almost) all bounds checks.+ _ <- if (TupRunit, A.gte singleType n bd')+ -- All threads participate. No bounds checks required but+ -- the last thread needs to update the carry-in value.+ then do+ x <- app1 (delayedLinearIndex arrIn) i+ y <- if (tp, A.eq singleType tid (liftInt32 0))+ then do+ c <- readArray TypeInt32 carry (liftInt32 0)+ case dir of+ LeftToRight -> app2 combine c x+ RightToLeft -> app2 combine x c+ else+ return x+ z <- scanBlock dir dev tp combine Nothing y++ -- Write results to the output array. Note that if we+ -- align directly on the boundary of the array this is not+ -- valid for the last thread.+ case dir of+ LeftToRight -> when (A.lt singleType j sup) $ writeArray TypeInt arrOut j z+ RightToLeft -> when (A.gte singleType j inf) $ writeArray TypeInt arrOut j z++ -- Last thread of the block also saves its result as the+ -- carry-in value+ bd1 <- A.sub numType bd (liftInt32 1)+ when (A.eq singleType tid bd1) $+ writeArray TypeInt32 carry (liftInt32 0) z++ return (lift TupRunit ())++ -- Only threads that are in bounds can participate. This is+ -- the last iteration of the loop. The last active thread+ -- still needs to store its value into the carry-in slot.+ --+ -- Note that all threads must call the block-wide scan.+ -- SEE: [Synchronisation problems with SM_70 and greater]+ else do+ x <- if (tp, A.lt singleType tid' n)+ then do+ x <- app1 (delayedLinearIndex arrIn) i+ y <- if (tp, A.eq singleType tid (liftInt32 0))+ then do+ c <- readArray TypeInt32 carry (liftInt32 0)+ case dir of+ LeftToRight -> app2 combine c x+ RightToLeft -> app2 combine x c+ else+ return x+ return y+ else+ return $ tupUndef tp++ l <- i32 n+ y <- scanBlock dir dev tp combine (Just l) x++ m <- A.sub numType l (liftInt32 1)+ when (A.lt singleType tid m) $ writeArray TypeInt arrOut j y+ when (A.eq singleType tid m) $ writeArray TypeInt32 carry (liftInt32 0) y++ return (lift TupRunit ())++ A.trip <$> A.sub numType n bd' <*> next i <*> next j)+ (A.trip n0 i0 j0)++ -- Wait for the carry-in value to be updated+ __syncthreads++ -- Store the carry-in value to the separate final results array+ when (A.eq singleType tid (liftInt32 0)) $+ writeArray TypeInt arrSum seg =<< readArray TypeInt32 carry (liftInt32 0)++ return_++++-- Parallel scan, auxiliary+--+-- If this is an exclusive scan of an empty array, we just fill the result with+-- the seed element.+--+mkScanFill+ :: UID+ -> Gamma aenv+ -> ArrayR (Array sh e)+ -> IRExp PTX aenv e+ -> CodeGen PTX (IROpenAcc PTX aenv (Array sh e))+mkScanFill uid aenv repr seed =+ mkGenerate uid aenv repr (IRFun1 (const seed))++mkScan'Fill+ :: UID+ -> Gamma aenv+ -> ArrayR (Array (sh, Int) e)+ -> IRExp PTX aenv e+ -> CodeGen PTX (IROpenAcc PTX aenv (Array (sh, Int) e, Array sh e))+mkScan'Fill uid aenv repr seed =+ Safe.coerce <$> mkGenerate uid aenv (reduceRank repr) (IRFun1 (const seed))++scanSMemSize :: DeviceProperties -> TypeR e -> Int -> Int+scanSMemSize dev tp n = sharedMemorySizeAdd tp warps 0+ where+ ws = CUDA.warpSize dev+ warps = n `P.quot` ws++-- Block wide scan+-- ---------------++-- Efficient block-wide (inclusive) scan using the specified operator.+--+-- Each block requires (#warps * (1 + 1.5*warp size)) elements of dynamically+-- allocated shared memory.+--+-- Example: https://github.com/NVlabs/cub/blob/1.5.4/cub/block/specializations/block_scan_warp_scans.cuh+--+-- Must be called under uniform control flow within a thread block+-- (as this function may use __syncthreads)+scanBlock+ :: forall aenv e.+ Direction+ -> DeviceProperties -- ^ properties of the target device+ -> TypeR e+ -> IRFun2 PTX aenv (e -> e -> e) -- ^ combination function+ -> Maybe (Operands Int32) -- ^ number of valid elements (may be less than block size)+ -> Operands e -- ^ calling thread's input element+ -> CodeGen PTX (Operands e)+scanBlock dir dev tp combine nelem = warpScan >=> warpPrefix+ where+ int32 :: Integral a => a -> Operands Int32+ int32 = liftInt32 . P.fromIntegral++ -- Temporary storage required for each warp+ -- warp_smem_elems = CUDA.warpSize dev + (CUDA.warpSize dev `P.quot` 2)+ -- warp_smem_bytes = warp_smem_elems * bytesElt tp++ -- Step 1: Scan in every warp+ warpScan :: Operands e -> CodeGen PTX (Operands e)+ warpScan = scanWarp dir dev tp combine++ -- Step 2: Collect the aggregate results of each warp to compute the prefix+ -- values for each warp and combine with the partial result to compute each+ -- thread's final value.+ warpPrefix :: Operands e -> CodeGen PTX (Operands e)+ warpPrefix input = do+ -- Allocate #warps elements of shared memory+ bd <- blockDim+ warps <- A.quot integralType bd (int32 (CUDA.warpSize dev))+ smem <- dynamicSharedMem tp TypeInt32 warps (liftInt32 0)++ -- Share warp aggregates+ wid <- warpId+ lane <- laneId+ when (A.eq singleType lane (int32 (CUDA.warpSize dev - 1))) $ do+ writeArray TypeInt32 smem wid input++ -- Wait for each warp to finish its local scan and share the aggregate+ __syncthreads++ -- Compute the prefix value for this warp and add to the partial result.+ -- This step is not required for the first warp, which has no carry-in.+ if (tp, A.eq singleType wid (liftInt32 0))+ then return input+ else do+ -- Every thread sequentially scans the warp aggregates to compute+ -- their prefix value. We do this sequentially, but could also have+ -- warp 0 do it cooperatively if we limit thread block sizes to+ -- (warp size ^ 2).+ steps <- case nelem of+ Nothing -> return wid+ Just n -> A.min singleType wid =<< A.quot integralType n (int32 (CUDA.warpSize dev))++ p0 <- readArray TypeInt32 smem (liftInt32 0)+ prefix <- iterFromStepTo tp (liftInt32 1) (liftInt32 1) steps p0 $ \step x -> do+ y <- readArray TypeInt32 smem step+ case dir of+ LeftToRight -> app2 combine x y+ RightToLeft -> app2 combine y x++ case dir of+ LeftToRight -> app2 combine prefix input+ RightToLeft -> app2 combine input prefix+++-- Warp-wide scan+-- --------------++-- Efficient warp-wide (inclusive) scan using the specified operator.+--+-- Each warp requires 48 (1.5 x warp size) elements of shared memory. The+-- routine assumes that it is allocated individually per-warp (i.e. can be+-- indexed in the range [0, warp size)).+--+-- Example: https://github.com/NVlabs/cub/blob/1.5.4/cub/warp/specializations/warp_scan_smem.cuh+--+scanWarp+ :: forall aenv e.+ Direction+ -> DeviceProperties -- ^ properties of the target device+ -> TypeR e+ -> IRFun2 PTX aenv (e -> e -> e) -- ^ combination function+ -> Operands e -- ^ calling thread's input element+ -> CodeGen PTX (Operands e)+scanWarp dir dev tp combine = scan 0+ where+ log2 :: Double -> Double+ log2 = P.logBase 2++ -- Number of steps required to scan warp+ steps = P.floor (log2 (P.fromIntegral (CUDA.warpSize dev)))++ -- Unfold the scan as a recursive code generation function+ scan :: Int -> Operands e -> CodeGen PTX (Operands e)+ scan step x+ | step >= steps = return x+ | otherwise = do+ let offset = 1 `P.shiftL` step++ -- share partial result through shared memory buffer+ y <- __shfl_up tp x (liftWord32 offset)+ lane <- laneId++ -- update partial result if in range+ x' <- if (tp, A.gte singleType lane (liftInt32 . P.fromIntegral $ offset))+ then do+ case dir of+ LeftToRight -> app2 combine y x+ RightToLeft -> app2 combine x y++ else+ return x++ scan (step+1) x'++tupUndef :: TypeR a -> Operands a+tupUndef TupRunit = OP_Unit+tupUndef (TupRpair a b) = OP_Pair (tupUndef a) (tupUndef b)+tupUndef (TupRsingle t) = ir t (undef t)++-- Utilities+-- ---------++i32 :: Operands Int -> CodeGen PTX (Operands Int32)+i32 = A.fromIntegral integralType numType++int :: Operands Int32 -> CodeGen PTX (Operands Int)+int = A.fromIntegral integralType numType+
+ src/Data/Array/Accelerate/LLVM/PTX/CodeGen/Stencil.hs view
@@ -0,0 +1,190 @@+{-# LANGUAGE GADTs #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeApplications #-}+-- |+-- Module : Data.Array.Accelerate.LLVM.PTX.CodeGen.Stencil+-- Copyright : [2018..2020] The Accelerate Team+-- License : BSD3+--+-- Maintainer : Trevor L. McDonell <trevor.mcdonell@gmail.com>+-- Stability : experimental+-- Portability : non-portable (GHC extensions)+--++module Data.Array.Accelerate.LLVM.PTX.CodeGen.Stencil (++ mkStencil1,+ mkStencil2,++) where++import Data.Array.Accelerate.Representation.Array+import Data.Array.Accelerate.Representation.Shape+import Data.Array.Accelerate.Representation.Stencil+import Data.Array.Accelerate.Representation.Type+import Data.Array.Accelerate.Type++import Data.Array.Accelerate.LLVM.CodeGen.Arithmetic+import Data.Array.Accelerate.LLVM.CodeGen.Array+import Data.Array.Accelerate.LLVM.CodeGen.Base+import Data.Array.Accelerate.LLVM.CodeGen.Environment+import Data.Array.Accelerate.LLVM.CodeGen.Exp+import Data.Array.Accelerate.LLVM.CodeGen.IR+import Data.Array.Accelerate.LLVM.CodeGen.Monad+import Data.Array.Accelerate.LLVM.CodeGen.Stencil+import Data.Array.Accelerate.LLVM.CodeGen.Sugar+import Data.Array.Accelerate.LLVM.Compile.Cache++import Data.Array.Accelerate.LLVM.PTX.CodeGen.Base+import Data.Array.Accelerate.LLVM.PTX.CodeGen.Loop+import Data.Array.Accelerate.LLVM.PTX.Target ( PTX )++import qualified Data.Array.Accelerate.LLVM.Internal.LLVMPretty.AST as LP++import Control.Monad+++-- The stencil function is similar to a map, but has access to surrounding+-- elements as specified by the stencil pattern.+--+-- This generates two functions:+--+-- * stencil_inside: does not apply boundary conditions, assumes all element+-- accesses are valid+--+-- * stencil_border: applies boundary condition check to each array access+--+mkStencil1+ :: UID+ -> Gamma aenv+ -> StencilR sh a stencil+ -> TypeR b+ -> IRFun1 PTX aenv (stencil -> b)+ -> IRBoundary PTX aenv (Array sh a)+ -> MIRDelayed PTX aenv (Array sh a)+ -> CodeGen PTX (IROpenAcc PTX aenv (Array sh b))+mkStencil1 uid aenv stencil tp fun bnd marr =+ let repr = ArrayR shr tp+ (shr, halo) = stencilHalo stencil+ (arrIn, paramIn) = delayedArray "in" marr+ in+ (+++) <$> mkInside uid aenv repr halo (IRFun1 $ app1 fun <=< stencilAccess stencil Nothing arrIn) paramIn+ <*> mkBorder uid aenv repr (IRFun1 $ app1 fun <=< stencilAccess stencil (Just bnd) arrIn) paramIn+++mkStencil2+ :: UID+ -> Gamma aenv+ -> StencilR sh a stencil1+ -> StencilR sh b stencil2+ -> TypeR c+ -> IRFun2 PTX aenv (stencil1 -> stencil2 -> c)+ -> IRBoundary PTX aenv (Array sh a)+ -> MIRDelayed PTX aenv (Array sh a)+ -> IRBoundary PTX aenv (Array sh b)+ -> MIRDelayed PTX aenv (Array sh b)+ -> CodeGen PTX (IROpenAcc PTX aenv (Array sh c))+mkStencil2 uid aenv stencil1 stencil2 tp f bnd1 marr1 bnd2 marr2 =+ let+ repr = ArrayR shr tp+ (arrIn1, paramIn1) = delayedArray "in1" marr1+ (arrIn2, paramIn2) = delayedArray "in2" marr2++ inside = IRFun1 $ \ix -> do+ s1 <- stencilAccess stencil1 Nothing arrIn1 ix+ s2 <- stencilAccess stencil2 Nothing arrIn2 ix+ app2 f s1 s2+ --+ border = IRFun1 $ \ix -> do+ s1 <- stencilAccess stencil1 (Just bnd1) arrIn1 ix+ s2 <- stencilAccess stencil2 (Just bnd2) arrIn2 ix+ app2 f s1 s2++ (shr, halo1) = stencilHalo stencil1+ (_, halo2) = stencilHalo stencil2+ halo = union shr halo1 halo2+ in+ (+++) <$> mkInside uid aenv repr halo inside (paramIn1 ++ paramIn2)+ <*> mkBorder uid aenv repr border (paramIn1 ++ paramIn2)+++mkInside+ :: UID+ -> Gamma aenv+ -> ArrayR (Array sh e)+ -> sh+ -> IRFun1 PTX aenv (sh -> e)+ -> [LP.Typed LP.Ident]+ -> CodeGen PTX (IROpenAcc PTX aenv (Array sh e))+mkInside uid aenv repr@(ArrayR shr _) halo apply paramIn =+ let+ (arrOut, paramOut) = mutableArray repr "out"+ paramInside = parameter (shapeType shr) "shInside"+ shInside = local (shapeType shr) "shInside"+ shOut = irArrayShape arrOut+ paramEnv = envParam aenv+ in+ makeOpenAcc uid "stencil_inside" (paramInside ++ paramOut ++ paramIn ++ paramEnv) $ do++ start <- return (liftInt 0)+ end <- shapeSize shr shInside++ -- iterate over the inside region as a linear index space+ --+ imapFromTo start end $ \i -> do++ ixIn <- indexOfInt shr shInside i -- convert to multidimensional index of inside region+ ixOut <- offset shr ixIn (lift (shapeType shr) halo) -- shift to multidimensional index of outside region+ r <- app1 apply ixOut -- apply generator function+ j <- intOfIndex shr shOut ixOut+ writeArray TypeInt arrOut j r++ return_+++mkBorder+ :: UID+ -> Gamma aenv+ -> ArrayR (Array sh e)+ -> IRFun1 PTX aenv (sh -> e)+ -> [LP.Typed LP.Ident]+ -> CodeGen PTX (IROpenAcc PTX aenv (Array sh e))+mkBorder uid aenv repr@(ArrayR shr _) apply paramIn =+ let+ (arrOut, paramOut) = mutableArray repr "out"+ paramFrom = parameter (shapeType shr) "shFrom"+ shFrom = local (shapeType shr) "shFrom"+ paramInside = parameter (shapeType shr) "shInside"+ shInside = local (shapeType shr) "shInside"+ shOut = irArrayShape arrOut+ paramEnv = envParam aenv+ in+ makeOpenAcc uid "stencil_border" (paramFrom ++ paramInside ++ paramOut ++ paramIn ++ paramEnv) $ do++ start <- return (liftInt 0)+ end <- shapeSize shr shInside++ imapFromTo start end $ \i -> do++ ixIn <- indexOfInt shr shInside i -- convert to multidimensional index of inside region+ ixOut <- offset shr ixIn shFrom -- shift to multidimensional index of outside region+ r <- app1 apply ixOut -- apply generator function+ j <- intOfIndex shr shOut ixOut+ writeArray TypeInt arrOut j r++ return_+++offset :: ShapeR sh -> Operands sh -> Operands sh -> CodeGen PTX (Operands sh)+offset shr sh1 sh2 = go shr sh1 sh2+ where+ go :: ShapeR t -> Operands t -> Operands t -> CodeGen PTX (Operands t)+ go ShapeRz OP_Unit OP_Unit+ = return OP_Unit++ go (ShapeRsnoc t) (OP_Pair sa1 sb1) (OP_Pair sa2 sb2)+ = do x <- add (numType :: NumType Int) sb1 sb2+ OP_Pair <$> go t sa1 sa2 <*> return x+
+ src/Data/Array/Accelerate/LLVM/PTX/CodeGen/Transform.hs view
@@ -0,0 +1,67 @@+{-# LANGUAGE GADTs #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+-- |+-- Module : Data.Array.Accelerate.LLVM.PTX.CodeGen.Transform+-- Copyright : [2014..2020] The Accelerate Team+-- License : BSD3+--+-- Maintainer : Trevor L. McDonell <trevor.mcdonell@gmail.com>+-- Stability : experimental+-- Portability : non-portable (GHC extensions)+--++module Data.Array.Accelerate.LLVM.PTX.CodeGen.Transform+ where++-- accelerate+import Data.Array.Accelerate.Representation.Array+import Data.Array.Accelerate.Type++import Data.Array.Accelerate.LLVM.CodeGen.Arithmetic+import Data.Array.Accelerate.LLVM.CodeGen.Array+import Data.Array.Accelerate.LLVM.CodeGen.Base+import Data.Array.Accelerate.LLVM.CodeGen.Environment+import Data.Array.Accelerate.LLVM.CodeGen.Exp+import Data.Array.Accelerate.LLVM.CodeGen.Monad+import Data.Array.Accelerate.LLVM.CodeGen.Sugar+import Data.Array.Accelerate.LLVM.Compile.Cache++import Data.Array.Accelerate.LLVM.PTX.CodeGen.Base+import Data.Array.Accelerate.LLVM.PTX.CodeGen.Loop+import Data.Array.Accelerate.LLVM.PTX.Target ( PTX )+++-- Apply a unary function to each element of an array. Each thread processes+-- multiple elements, striding the array by the grid size.+--+mkTransform+ :: UID+ -> Gamma aenv+ -> ArrayR (Array sh a)+ -> ArrayR (Array sh' b)+ -> IRFun1 PTX aenv (sh' -> sh)+ -> IRFun1 PTX aenv (a -> b)+ -> CodeGen PTX (IROpenAcc PTX aenv (Array sh' b))+mkTransform uid aenv repr@(ArrayR shr _) repr'@(ArrayR shr' _) p f =+ let+ (arrOut, paramOut) = mutableArray repr' "out"+ (arrIn, paramIn) = mutableArray repr "in"+ paramEnv = envParam aenv+ in+ makeOpenAcc uid "transform" (paramOut ++ paramIn ++ paramEnv) $ do++ let start = liftInt 0+ end <- shapeSize shr' (irArrayShape arrOut)++ imapFromTo start end $ \i' -> do+ ix' <- indexOfInt shr' (irArrayShape arrOut) i'+ ix <- app1 p ix'+ i <- intOfIndex shr (irArrayShape arrIn) ix+ a <- readArray TypeInt arrIn i+ b <- app1 f a+ writeArray TypeInt arrOut i' b++ return_+
+ src/Data/Array/Accelerate/LLVM/PTX/Compile.hs view
@@ -0,0 +1,283 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeFamilies #-}+{-# OPTIONS_GHC -Wno-orphans #-}+-- |+-- Module : Data.Array.Accelerate.LLVM.PTX.Compile+-- Copyright : [2014..2020] The Accelerate Team+-- License : BSD3+--+-- Maintainer : Trevor L. McDonell <trevor.mcdonell@gmail.com>+-- Stability : experimental+-- Portability : non-portable (GHC extensions)+--++module Data.Array.Accelerate.LLVM.PTX.Compile (++ module Data.Array.Accelerate.LLVM.Compile,+ ObjectR(..),++) where++import Data.Array.Accelerate.AST ( PreOpenAcc )+import Data.Array.Accelerate.Error+import Data.Array.Accelerate.Trafo.Delayed++import Data.Array.Accelerate.LLVM.CodeGen ( llvmOfPreOpenAcc )+import Data.Array.Accelerate.LLVM.CodeGen.Environment ( Gamma )+import Data.Array.Accelerate.LLVM.CodeGen.Module ( Module(..) )+import Data.Array.Accelerate.LLVM.Compile+import Data.Array.Accelerate.LLVM.State+import Data.Array.Accelerate.LLVM.Target.ClangInfo ( hostLLVMVersion, llvmverFromTuple, clangExePath, clangExePathEnvironment )++import Data.Array.Accelerate.LLVM.PTX.Analysis.Launch+import Data.Array.Accelerate.LLVM.PTX.CodeGen+import Data.Array.Accelerate.LLVM.PTX.Compile.Cache+import Data.Array.Accelerate.LLVM.PTX.Compile.Libdevice.Load+import Data.Array.Accelerate.LLVM.PTX.Foreign ( )+import Data.Array.Accelerate.LLVM.PTX.Target+import qualified Data.Array.Accelerate.LLVM.PTX.Debug as Debug++import Foreign.CUDA.Path ( cudaInstallPath )+import qualified Foreign.CUDA.Analysis as CUDA++import qualified LLVM.AST.Type.Name as LLVM++import qualified Data.Array.Accelerate.LLVM.Internal.LLVMPretty as LP+import qualified Data.Array.Accelerate.LLVM.Internal.LLVMPretty.PP as LP+import qualified Text.PrettyPrint as Pretty++import Control.Monad ( when )+import Control.Monad.Reader+import Data.ByteString.Short ( ShortByteString )+import Data.List ( intercalate )+import qualified Data.List.NonEmpty as NE+import Data.Foldable ( toList )+import GHC.IO.Exception ( IOErrorType(OtherError) )+import Formatting+import System.Directory+import System.Exit ( ExitCode(..) )+import System.IO ( hPutStrLn, stderr )+import System.IO.Error ( mkIOError )+import System.IO.Unsafe+import System.Process+import Text.Printf ( printf )+import qualified Data.ByteString.Short.Char8 as SBS8+import qualified Data.Map.Strict as Map+++instance Compile PTX where+ data ObjectR PTX = ObjectR { objId :: {-# UNPACK #-} !UID+ , -- | Config for each exported kernel (symbol)+ ptxConfig :: ![(ShortByteString, LaunchConfig)]+ , objPath :: {- LAZY -} FilePath+ }+ compileForTarget = compile+++-- | Compile an Accelerate expression to object code.+--+-- This generates the target code together with a list of each kernel function+-- defined in the module paired with its occupancy information.+--+compile :: HasCallStack => PreOpenAcc DelayedOpenAcc aenv a -> Gamma aenv -> LLVM PTX (ObjectR PTX)+compile pacc aenv = do++ -- Generate code for this Acc operation+ --+ dev <- asks ptxDeviceProperties+ let CUDA.Compute m n = CUDA.computeCapability dev+ let arch = printf "sm_%d%d" m n+ (uid, cacheFile) <- cacheOfPreOpenAcc pacc+ Module ast md <- llvmOfPreOpenAcc uid pacc aenv+ let config = [ (SBS8.pack f, x) | (LP.Symbol f, KM_PTX x) <- Map.toList md ]++ libdevice_bc <- liftIO libdeviceBitcodePath++ case isDeviceSupported (CUDA.computeCapability dev) of+ Nothing -> return () -- all fine+ Just err -> internalError string err++ -- Lower the generated LLVM into a CUBIN object code.+ --+ -- The 'objData' field is lazily evaluated since the object code might have+ -- already been loaded into the current context from a different function, in+ -- which case it will be found by the linker cache.+ --+ cubin <- liftIO . unsafeInterleaveIO $ do+ exists <- doesFileExist cacheFile+ recomp <- if Debug.debuggingIsEnabled then Debug.getFlag Debug.force_recomp else return False+ if exists && not recomp+ then do+ Debug.traceM Debug.dump_cc ("cc: found cached object code " % shown) uid++ else do+ -- Detect LLVM version+ -- Note: this LLVM version is incorporated in the cache path, so we're safe detecting it at runtime.+ let prettyHostLLVMVersion = intercalate "." (map show (toList hostLLVMVersion))+ llvmver <- case llvmverFromTuple hostLLVMVersion of+ Just llvmver -> return llvmver+ Nothing -> internalError ("accelerate-llvm-ptx: Unsupported LLVM version: " % string)+ prettyHostLLVMVersion+ Debug.traceM Debug.dump_cc ("Using Clang at " % string % " version " % shown) clangExePath prettyHostLLVMVersion++ when (NE.head hostLLVMVersion < 16) $+ case clangExePathEnvironment of+ Nothing -> do+ hPutStrLn stderr $+ "[accelerate-llvm-ptx] Clang version 16 or newer is required for the Nvidia PTX " +++ "backend, but version " ++ prettyHostLLVMVersion ++ " was found at '" +++ clangExePath ++ "'. To override this choice, set the ACCELERATE_LLVM_CLANG_PATH " +++ "environment variable to point to the desired clang executable."+ -- not an IOError because we're in unsafePerformIO, somewhere up the call chain+ errorWithoutStackTrace $+ "accelerate-llvm-ptx: Clang version " ++ prettyHostLLVMVersion +++ " found but >=16 required (set ACCELERATE_LLVM_CLANG_PATH to override)"+ Just{} -> -- If an explicit path was given, let's just continue and see what happens.+ return ()++ -- Convert module to llvm-pretty format so that we can print it+ let unoptimisedText = Pretty.renderStyle+ Pretty.style { Pretty.lineLength = maxBound `div` 2 }+ (LP.ppLLVM llvmver (LP.ppModule ast))+ ++ "\n\n" ++ accPreludePTX+ Debug.when Debug.verbose $ do+ Debug.traceM Debug.dump_cc ("Unoptimised LLVM IR:\n" % string) unoptimisedText++ isVerboseFlagSet <- Debug.getFlag Debug.verbose+ let clangArgs = ["-O3", "--target=nvptx64-nvidia-cuda", "-march=" ++ arch+ ,"-o", cacheFile+ ,"-Wno-override-module"+ ,"--cuda-path=" ++ cudaInstallPath+ ,"-x", "ir", "-"+ -- See Note [Internalizing Libdevice]+ -- TODO: only link in libdevice if we're actually using __nv_ functions!+ ,"-Xclang", "-mlink-builtin-bitcode", "-Xclang", libdevice_bc]+ ++ (if isVerboseFlagSet then ["-v"] else [])++ Debug.traceM Debug.dump_cc ("Arguments to clang: " % shown) clangArgs++ -- Remove some diagnostics from clang (and subprocesses) output that we+ -- know are fine. See filterClangStderr. Unfortunately, System.Process+ -- does not have a combinator for "give me stdout and stderr but throw+ -- exception on ExitFailure", so we do it manually.+ (clangEC, clangOut, clangErr) <- readProcessWithExitCode clangExePath clangArgs unoptimisedText+ putStr clangOut+ putStr (filterClangStderr clangErr)+ case clangEC of+ ExitSuccess -> return ()+ ExitFailure code -> do+ let msg = "clang returned non-zero exit code: " ++ show code +++ " (invocation: " ++ show (clangExePath : clangArgs) ++ ")"+ ioError $ mkIOError OtherError msg Nothing Nothing++ Debug.traceM Debug.dump_cc ("Written PTX to: " % string) cacheFile++ return cacheFile++ return $! ObjectR uid config cubin+++{- Note [Internalizing Libdevice]++"Libdevice" refers to $CUDAPATH/nvvm/libdevice/libdevice.XX.bc, an LLVM bitcode+file that (reportedly) contains definitions of various math functions for use+in NVIDIA PTX. Most interesting primitive arithmetic operations on+floating-point numbers get compiled to calls to functions from libdevice, so it+is essential that we link it into any kernel that we create (or at least, any+kernel that references functions from libdevice).++However, libdevice is quite large; it is 473 KB of LLVM bitcode for cuda 12.6+on my machine, and clang takes >1 second to compile it on my (5 GHz Intel)+machine. Indeed, the LLVM NVPTX usage guide [1] recommends _internalizing_ the+symbols from libdevice after linking it with the kernel module; more precisely,+it recommends to first link the kernel module with libdevice, and subsequently+internalize all functions that we don't explicitly want exported (the public+kernel functions).++Clang doesn't have a command-line option to internalize symbols. Indeed, it+would be somewhat ambiguous when in the compilation process to do said+internalization. The LLVM command-line tool that _can_ do internalization is+`llvm-link`, the tool for linking LLVM modules together (and doing little+else). So translating the recommended [1] strategy to command-line tools+(because linking with LLVM through bindings is a version nightmare -- been+there, done that, not again), we get the following sensible procedure:++$ llvm-link --internalize kernel.ll libdevice.bc -o kernel-linked.bc+$ clang --target=... kernel-linked.bc -o kernel.sass++However, llvm-link is not clang, and we'd very much like to depend _only_ on+clang, not on the full LLVM suite of tools. Especially not for this vexingly+small bit of functionality! But clang is huge, and surely it can do+internalization somehow?++It turns out it can, but they did their absolute best to hide it. (All+references in this paragraph are to LLVM HEAD on 2024-12-04: 7954a0514ba7de.)+The workhorse function, called from `llvm-link`, is internalizeModule(). This+function is also called from clang in BackendConsumer::LinkInModules() in+clang/lib/CodeGen/CodeGenAction.cpp, but only if .Internalize is set on the+CodeGenAction::LinkModule in question. In CompilerInvocation::ParseCodeGenArgs+(clang/lib/Frontend/CompilerInvocation.cpp), we see that _some_ field called+"Internalize" is set on _something_ (not a LinkModule, but whatever?) if the+OPT_mlink_builtin_bitcode flag is set. Of course, no documentation anywhere+explains what this option does; the only mention I could find anywhere is here+[2], as well as some mailing list posts / issue tracker comments mentioning it.+How do we use the option? Well, it's not a clang option, it's actually a (I+think!) cc1 option, so you have to do:++$ clang -Xclang -mlink-builtin-bitcode -Xclang libdevice.bc++This makes clang internalize everything in that module that is not globally+exported (I think), which is what we want.++[1]: https://releases.llvm.org/19.1.0/docs/NVPTXUsage.html#linking-with-libdevice+[2]: https://clang.llvm.org/docs/OffloadingDesign.html#offload-device-compilation+-}++filterClangStderr :: String -> String+filterClangStderr = unlines . filter (not . isShflSyncWarn) . lines+ where+ -- ptxas warns about use of shfl instructions without the .sync suffix on+ -- CC 6.0, because such non-sync shuffles are deprecated (and indeed+ -- removed in CC 7.0). We still use them in CC 6.0 (and not any more in CC+ -- 7.0) because the shfl.sync in CC 6.0 has restrictions:+ --+ -- > For .target `sm_6x` or below, all threads in `membermask` must execute+ -- > the same `shfl.sync` instruction in convergence, and only threads+ -- > belonging to some `membermask` can be active when the `shfl.sync`+ -- > instruction is executed. Otherwise, the behavior is undefined.+ -- (https://docs.nvidia.com/cuda/parallel-thread-execution/index.html#data-movement-and-conversion-instructions-shfl-sync)+ --+ -- Perhaps we do use the shuffles in convergence, but we don't want to risk+ -- it. Hence in CC 6.0, we still use non-sync shuffles.+ --+ -- The ptxas warning cannot be turned off, however, and is **incredibly**+ -- noisy (there's a warning for every single shfl instruction). Hence we+ -- filter them out here.+ --+ -- > ptxas /tmp/--f8a421.s, line 119; warning : Instruction 'shfl' without '.sync' is deprecated since PTX ISA version 6.0 and will be discontinued in a future PTX ISA version+ isShflSyncWarn line =+ let (presemi, postsemi) = break (== ';') line+ in takeWhile (/= ' ') presemi == "ptxas" &&+ postsemi == "; warning : Instruction 'shfl' without '.sync' is deprecated since " +++ "PTX ISA version 6.0 and will be discontinued in a future PTX ISA version"++-- | Returns a human-readable error message in case the device is unsupported,+-- and Nothing if everything is alright.+isDeviceSupported :: CUDA.Compute -> Maybe String+isDeviceSupported cc@(CUDA.Compute m _)+ -- We require shfl instructions which are available only from CC 3.0.+ | m >= 3 = Nothing+ | otherwise = Just $+ "Your GPU has compute capability " ++ show cc ++ ", but only >= 3.0 is supported."++accPreludePTX :: String+accPreludePTX = unlines+ -- see Data.Array.Accelerate.LLVM.PTX.CodeGen.Base.nanosleep for why this is a hand-written function+ ["define private void @" ++ name_nanosleep ++ "(i32 noundef %0) alwaysinline convergent nounwind {"+ ," tail call void asm sideeffect \"nanosleep.u32 $0;\", \"r\"(i32 %0)"+ ," ret void"+ ,"}"]+ where+ name_nanosleep = let LLVM.Label name = LLVM.makeAccPreludeLabel "nanosleep" in SBS8.unpack name
+ src/Data/Array/Accelerate/LLVM/PTX/Compile/Cache.hs view
@@ -0,0 +1,42 @@+{-# OPTIONS_GHC -Wno-orphans #-}+-- |+-- Module : Data.Array.Accelerate.LLVM.PTX.Compile.Cache+-- Copyright : [2017..2020] The Accelerate Team+-- License : BSD3+--+-- Maintainer : Trevor L. McDonell <trevor.mcdonell@gmail.com>+-- Stability : experimental+-- Portability : non-portable (GHC extensions)+--++module Data.Array.Accelerate.LLVM.PTX.Compile.Cache (++ module Data.Array.Accelerate.LLVM.Compile.Cache++) where++import Data.Array.Accelerate.LLVM.Compile.Cache+import Data.Array.Accelerate.LLVM.PTX.Target+import Data.Array.Accelerate.LLVM.Target.ClangInfo ( hostLLVMVersion )++import Control.Monad.Reader+import Data.Foldable ( toList )+import Data.List ( intercalate )+import Data.Version+import Foreign.CUDA.Analysis+import System.FilePath+import Text.Printf+import qualified Data.ByteString.Short.Char8 as S8++import Paths_accelerate_llvm_ptx+++instance Persistent PTX where+ targetCacheTemplate = do+ Compute m n <- asks (computeCapability . ptxDeviceProperties)+ return $ "accelerate-llvm-ptx-" ++ showVersion version+ </> "llvmpr-" ++ intercalate "." (map show (toList hostLLVMVersion))+ </> S8.unpack ptxTargetTriple+ </> printf "sm%d%d" m n+ </> "morp.sass"+
+ src/Data/Array/Accelerate/LLVM/PTX/Compile/Libdevice/Load.hs view
@@ -0,0 +1,60 @@+{-# LANGUAGE OverloadedStrings #-}++-- |+-- Module : Data.Array.Accelerate.LLVM.PTX.Compile.Libdevice.Load+-- Copyright : [2014..2020] The Accelerate Team+-- License : BSD3+--+-- Maintainer : Trevor L. McDonell <trevor.mcdonell@gmail.com>+-- Stability : experimental+-- Portability : non-portable (GHC extensions)+--++module Data.Array.Accelerate.LLVM.PTX.Compile.Libdevice.Load (++ libdeviceBitcodePath,++) where++import Data.Array.Accelerate.Error+import Data.Array.Accelerate.LLVM.PTX.Execute.Event ( ) -- GHC#1012+import Data.Array.Accelerate.LLVM.PTX.Execute.Stream ( ) -- GHC#1012++import qualified Foreign.CUDA.Driver as CUDA++import Foreign.CUDA.Path++import Data.List ( isPrefixOf, sortBy )+import System.Directory+import System.FilePath+++-- libdevice+-- ---------++-- Compatible version of libdevice for a given compute capability should be+-- listed here:+--+-- https://github.com/llvm/llvm-project/blob/master/lib/Target/NVPTX/NVPTX.td++-- | Find the libdevice bitcode file for the given compute architecture. The name+-- of the bitcode file follows the format @libdevice.XX.bc@, where XX+-- represents a version(?). We search the libdevice path for all files of the+-- appropriate compute capability and load the "most recent" (by sort order).+libdeviceBitcodePath :: HasCallStack => IO FilePath+libdeviceBitcodePath+ | CUDA.libraryVersion < 9000 =+ -- There is some support code for cuda < 9 in an earlier version of these+ -- files; in particular, look at commit+ -- 2b5d69448557e89002c0179ea1aaf59bb757a6e3 (2023-08-22)+ -- for original llvm-hs code.+ internalError "Cuda < 9 is unsupported."+ | otherwise = do+ let nvvm = cudaInstallPath </> "nvvm" </> "libdevice"++ files <- getDirectoryContents nvvm++ let matches f = "libdevice" `isPrefixOf` f && takeExtension f == ".bc"+ return $ case sortBy (flip compare) (filter matches files) of+ name : _ -> nvvm </> name+ [] -> internalError "not found: libdevice.XX.bc"
+ src/Data/Array/Accelerate/LLVM/PTX/Context.hs view
@@ -0,0 +1,269 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MagicHash #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+-- |+-- Module : Data.Array.Accelerate.LLVM.PTX.Context+-- Copyright : [2014..2020] The Accelerate Team+-- License : BSD3+--+-- Maintainer : Trevor L. McDonell <trevor.mcdonell@gmail.com>+-- Stability : experimental+-- Portability : non-portable (GHC extensions)+--++module Data.Array.Accelerate.LLVM.PTX.Context (++ Context(..),+ new, raw, withContext,+ contextFinalizeResource,++) where++import Data.Array.Accelerate.Lifetime+import Data.Array.Accelerate.LLVM.PTX.Analysis.Device+import qualified Data.Array.Accelerate.LLVM.PTX.Debug as Debug++import qualified Foreign.CUDA.Driver.Device as CUDA+import qualified Foreign.CUDA.Driver.Context as CUDA++import Control.Concurrent+import Control.Exception+import Control.Monad+import Data.Char+import Data.Hashable+import Data.Int+import Data.IORef+import Data.Primitive.ByteArray+import Data.Text.Lazy.Builder+import Data.Word+import Formatting+import Prettyprinter+import Prettyprinter.Internal+import Prettyprinter.Render.Util.Panic+import Text.Printf+import qualified Data.Text.Lazy.Builder as TLB+import Prelude hiding ( (<>) )++import GHC.Base ( Int(..), addr2Int#, )+import GHC.Ptr ( Ptr(..) )+++-- | An execution context, which is tied to a specific device and CUDA execution+-- context.+--+data Context = Context {+ deviceProperties :: {-# UNPACK #-} !CUDA.DeviceProperties -- information on hardware resources+ , deviceName :: {-# UNPACK #-} !ByteArray -- device name, used for profiling+ , deviceContext :: {-# UNPACK #-} !(Lifetime CUDA.Context) -- device execution context++ -- | The number of finalizers currently using the context to free resources,+ -- plus 1 if the Context finalizer does not yet want to destroy the context+ -- itself. See Note: [Finalizing a CUDA Context].+ , deviceFinalizerRefcount :: {-# UNPACK #-} !(IORef Int)+ }++instance Eq Context where+ c1 == c2 = deviceContext c1 == deviceContext c2++instance Hashable Context where+ hashWithSalt salt =+ let+ ptrToInt :: Ptr a -> Int+ ptrToInt (Ptr addr#) = I# (addr2Int# addr#)+ in+ hashWithSalt salt . ptrToInt . CUDA.useContext . unsafeGetValue . deviceContext+++-- | Create a new CUDA execution context+--+new :: CUDA.Device+ -> CUDA.DeviceProperties+ -> [CUDA.ContextFlag]+ -> IO Context+new dev prp flags = do+ ctx <- raw dev prp =<< CUDA.create dev flags+ _ <- CUDA.pop+ return ctx++-- | Wrap a raw CUDA execution context+--+raw :: CUDA.Device+ -> CUDA.DeviceProperties+ -> CUDA.Context+ -> IO Context+raw dev prp ctx = do+ refcountVar <- newIORef 1 -- there is one user of the context: the context itself++ -- The kernels don't use much shared memory, so for devices that support it+ -- prefer using those memory banks as an L1 cache.+ --+ -- TLM: Is this a good idea? For example, external libraries such as cuBLAS+ -- rely heavily on shared memory and thus this could adversely affect+ -- performance. Perhaps we should use 'setCacheConfigFun' for individual+ -- functions which might benefit from this.+ --+ when (CUDA.computeCapability prp >= CUDA.Compute 2 0)+ (CUDA.setCache CUDA.PreferL1)++ -- Generate the context name+ let str = printf "[%d] %s\0" (fromIntegral (CUDA.useDevice dev) :: Int) (CUDA.deviceName prp)+ mba <- newPinnedByteArray (length str)++ let go !_ [] = unsafeFreezeByteArray mba+ go !i (x:xs) = do+ writeByteArray mba i (fromIntegral (ord x) :: Word8)+ go (i+1) xs++ nm <- go 0 str++ -- Display information about the selected device+ Debug.traceM Debug.dump_phases builder (deviceInfo dev prp)++ lft <- newLifetime ctx -- on the CUDA context+ let !result = Context prp nm lft refcountVar+ addFinalizer lft $ decrementContext result++ return result+++-- | Push the context onto the CPUs thread stack of current contexts and execute+-- some operation.+--+{-# INLINE withContext #-}+withContext :: Context -> IO a -> IO a+withContext Context{..} action+ = runInBoundThread+ $ withLifetime deviceContext $ \ctx ->+ bracket_ (push ctx) pop action++{-# INLINE push #-}+push :: CUDA.Context -> IO ()+push ctx = do+ message ("push context: " % formatContext) ctx+ CUDA.push ctx++{-# INLINE pop #-}+pop :: IO ()+pop = do+ ctx <- CUDA.pop+ message ("pop context: " % formatContext) ctx++decrementContext :: Context -> IO ()+decrementContext ctx = do+ newCount <- atomicModifyIORef' (deviceFinalizerRefcount ctx) (\i -> (i - 1, i - 1))+ message ("decrement context " % formatContext % " to " % shown) (unsafeGetValue (deviceContext ctx)) newCount+ when (newCount == 0) $ CUDA.destroy (unsafeGetValue (deviceContext ctx))++-- | If the underlying CUDA context is already slated for destruction entirely+-- (or has already been destroyed) by its finalizer, this function does+-- nothing. If the CUDA context will live on (for now), the passed @IO@ action+-- is invoked with a lock held so that the CUDA context will not be destroyed+-- while your action runs. Use this in finalizers of CUDA resources such as+-- arrays and linked modules.+--+-- The context is not automatically pushed in the action; if you need to+-- 'withContext', do it yourself.+contextFinalizeResource :: Context -> IO () -> IO ()+contextFinalizeResource ctx action =+ -- See Note: [Finalizing a CUDA Context]+ bracket+ (do newCount <- atomicModifyIORef' (deviceFinalizerRefcount ctx) $ \i ->+ if i == 0 then (0, 0) else (i + 1, i + 1)+ message ("increment context " % formatContext % " to " % shown) (unsafeGetValue (deviceContext ctx)) newCount+ return (newCount > 0))+ (\contextStillLive ->+ when contextStillLive (decrementContext ctx))+ (\contextStillLive ->+ when contextStillLive action)+++-- Debugging+-- ---------++-- Nicely format a summary of the selected CUDA device, example:+--+-- Device 0: GeForce 9600M GT (compute capability 1.1), 4 multiprocessors @ 1.25GHz (32 cores), 512MB global memory+--+deviceInfo :: CUDA.Device -> CUDA.DeviceProperties -> Builder+deviceInfo dev prp = go $ layoutPretty defaultLayoutOptions $+ devID <> colon <+> name <+> parens compute+ <> comma <+> processors <+> at <+> pretty clock <+> parens cores+ <> comma <+> memory+ where+ name = pretty (CUDA.deviceName prp)+ compute = "compute capability" <+> unsafeViaShow (CUDA.computeCapability prp)+ devID = "device" <+> unsafeViaShow (CUDA.useDevice dev)+ processors = pretty (CUDA.multiProcessorCount prp) <+> "multiprocessors"+ cores = pretty (CUDA.multiProcessorCount prp * coresPerMultiProcessor prp) <+> "cores"+ memory = pretty mem <+> "global memory"+ ----+ clock = toLazyText $ Debug.showFFloatSIBase (Just 2) 1000 (fromIntegral $ CUDA.clockRate prp * 1000 :: Double) "Hz"+ mem = toLazyText $ Debug.showFFloatSIBase (Just 0) 1024 (fromIntegral $ CUDA.totalGlobalMem prp :: Double) "B"+ at = pretty '@'++ go = \case+ SFail -> panicUncaughtFail+ SEmpty -> mempty+ SChar c rest -> TLB.singleton c <> go rest+ SText _l t rest -> TLB.fromText t <> go rest+ SLine i rest -> TLB.singleton '\n' <> (TLB.fromText (textSpaces i) <> go rest)+ SAnnPush _ann rest -> go rest+ SAnnPop rest -> go rest+++{-# INLINE message #-}+message :: Format (IO ()) a -> a+message fmt = Debug.traceM Debug.dump_gc ("gc: " % fmt)++{-# INLINE formatContext #-}+formatContext :: Format r (CUDA.Context -> r)+formatContext = later $ \(CUDA.Context c) -> bformat shown c+++-- Note: [Finalizing a CUDA Context]+--+-- Both a CUDA context and the resources we allocate within such a context+-- (currently, arrays, executable modules and events) are freed with+-- finalizers; these are invoked by the GC when it detects (after a GC pass)+-- that the Haskell heap objects are no longer reachable.+--+-- In our case, finalizers are attached to 'Lifetime' objects. The problem is+-- that even if a finalizer for Lifetime 1 refers to Lifetime 2, the GC does+-- not guarantee that the finalizer for Lifetime 1 runs to completion before the+-- finalizer of Lifetime 2 starts. (See the documentation for 'touchForeignPtr'+-- in base.) This is a problem for us because we are in this situation: to free+-- a resource within a CUDA context, we need a reference to that context and+-- the context needs to be alive. Thus finalizers for e.g. arrays reference the+-- Lifetime for the CUDA context.+--+-- If we just leave GHC to do finalization as it wishes, this means that a CUDA+-- context may well be destroyed before the resourcees in it are finalized,+-- leading to use-after-free errors and segfaults. We have two choices here:+-- 1. either we let the finalizer for a Context wait until the other finalizers+-- have run, or+-- 2. we free the Context when first we can, and let resource finalizers that+-- come later, do nothing.+-- We choose option 2 because destroying a CUDA context already frees the+-- resources in it, so there is no need to do meticulous manual cleanup here.+--+-- To accomplish this, we use reference counting. The context itself being+-- alive (precisely: its finalizer not yet having run) counts for one "use";+-- the only other "uses" are the finalizers for the CUDA resources in the+-- context. (There is no need to track anything before we start finalizing, so+-- this is enough.) The Context finalizer does nothing more than a decrement on+-- the refcount to release the "use" of the CUDA context by the Context object+-- itself.+--+-- To complete the picture:+-- - When decrementing, if we decremented to zero, we destroy the CUDA context.+-- - When incrementing, we are apparently in a resource finalizer, because that+-- is the only place where we increment. If here we see that the refcount is+-- already zero, we simply do nothing (and *skip* the resource finalizer+-- entirely): the CUDA context has already been destroyed, taking this+-- resource with it, so nothing more needs to be done.+--+-- The resource finalizers use 'contextFinalizeResource' to access this+-- functionality.+
+ src/Data/Array/Accelerate/LLVM/PTX/Debug.hs view
@@ -0,0 +1,113 @@+{-# LANGUAGE ForeignFunctionInterface #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}+-- |+-- Module : Data.Array.Accelerate.LLVM.PTX.Debug+-- Copyright : [2014..2020] The Accelerate Team+-- License : BSD3+--+-- Maintainer : Trevor L. McDonell <trevor.mcdonell@gmail.com>+-- Stability : experimental+-- Portability : non-portable (GHC extensions)+--++module Data.Array.Accelerate.LLVM.PTX.Debug (++ module Data.Array.Accelerate.Debug.Internal,+ module Data.Array.Accelerate.LLVM.PTX.Debug,++) where++import Data.Array.Accelerate.Debug.Internal hiding ( timed, elapsed )+import qualified Data.Array.Accelerate.Debug.Internal as D++import Foreign.CUDA.Driver.Stream ( Stream )+import qualified Foreign.CUDA.Driver.Event as Event++import Control.Concurrent+import Control.Monad.Trans+import Data.Text.Lazy.Builder+import Formatting+import System.CPUTime++import GHC.Float+++-- | Execute an action and time the results. The second argument specifies how+-- to format the output string given elapsed GPU and CPU time respectively+--+timed+ :: MonadIO m+ => Flag+ -> (Double -> Double -> Double -> IO ())+ -> Maybe Stream+ -> m a+ -> m a+{-# INLINE timed #-}+timed f fmt =+ monitorProcTime (getFlag f) fmt++monitorProcTime+ :: MonadIO m+ => IO Bool+ -> (Double -> Double -> Double -> IO ())+ -> Maybe Stream+ -> m a+ -> m a+{-# INLINE monitorProcTime #-}+monitorProcTime enabled display stream action = do+ yes <- if debuggingIsEnabled then liftIO enabled else return False+ if yes+ then do+ gpuBegin <- liftIO $ Event.create []+ gpuEnd <- liftIO $ Event.create []+ wallBegin <- liftIO $ getMonotonicTime+ cpuBegin <- liftIO $ getCPUTime+ _ <- liftIO $ Event.record gpuBegin stream+ result <- action+ _ <- liftIO $ Event.record gpuEnd stream+ cpuEnd <- liftIO $ getCPUTime+ wallEnd <- liftIO $ getMonotonicTime++ -- Wait for the GPU to finish executing then display the timing execution+ -- message. Do this in a separate thread so that the remaining kernels can+ -- be queued asynchronously.+ --+ _ <- liftIO . forkIO $ do+ Event.block gpuEnd+ diff <- Event.elapsedTime gpuBegin gpuEnd+ let gpuTime = float2Double $ diff * 1E-3 -- milliseconds+ cpuTime = fromIntegral (cpuEnd - cpuBegin) * 1E-12 -- picoseconds+ wallTime = wallEnd - wallBegin -- seconds++ Event.destroy gpuBegin+ Event.destroy gpuEnd+ --+ display wallTime cpuTime gpuTime+ --+ return result++ else+ action+++{-# INLINE elapsed #-}+elapsed :: Format r (Double -> Double -> Double -> r)+elapsed = formatSIBase (Just 3) 1000 % "s (wall), "+ % formatSIBase (Just 3) 1000 % "s (cpu), "+ % formatSIBase (Just 3) 1000 % "s (gpu)"++-- accelerate/cbits/clock.c+foreign import ccall unsafe "clock_gettime_monotonic_seconds" getMonotonicTime :: IO Double++data Phase = Compile | Link | Execute++buildPhase :: Phase -> Builder+buildPhase = \case+ Compile -> "compile"+ Link -> "link"+ Execute -> "execute"++phase :: MonadIO m => Phase -> m a -> m a+phase p = D.timed dump_phases (now ("phase " <> buildPhase p <> ": ") % D.elapsed)+
+ src/Data/Array/Accelerate/LLVM/PTX/Embed.hs view
@@ -0,0 +1,83 @@+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE TemplateHaskell #-}+{-# OPTIONS_GHC -Wno-orphans #-}+-- |+-- Module : Data.Array.Accelerate.LLVM.PTX.Embed+-- Copyright : [2017..2020] The Accelerate Team+-- License : BSD3+--+-- Maintainer : Trevor L. McDonell <trevor.mcdonell@gmail.com>+-- Stability : experimental+-- Portability : non-portable (GHC extensions)+--++module Data.Array.Accelerate.LLVM.PTX.Embed (++ module Data.Array.Accelerate.LLVM.Embed,++) where++import Data.ByteString.Short.Extra as BS++import Data.Array.Accelerate.Lifetime++import Data.Array.Accelerate.LLVM.Compile+import Data.Array.Accelerate.LLVM.Embed++import Data.Array.Accelerate.LLVM.PTX.Compile+import Data.Array.Accelerate.LLVM.PTX.Link+import Data.Array.Accelerate.LLVM.PTX.Target+import Data.Array.Accelerate.LLVM.PTX.Context++import qualified Foreign.CUDA.Driver as CUDA++import Control.Monad.IO.Class ( liftIO )+import Foreign.Ptr+import GHC.Ptr ( Ptr(..) )+import Data.Array.Accelerate.TH.Compat ( CodeQ )+import System.IO.Unsafe+import qualified Data.ByteString as B+import qualified Data.ByteString.Unsafe as B+import qualified Data.Array.Accelerate.TH.Compat as TH+++instance Embed PTX where+ embedForTarget = embed++-- Embed the given object code and set up to be reloaded at execution time.+--+embed :: PTX -> ObjectR PTX -> CodeQ (ExecutableR PTX)+embed target (ObjectR _ cfg objFname) = do+ -- Generate the embedded kernel executable. This will load the embedded object+ -- code into the current (at execution time) context.+ loadQ `TH.bindCode` \kmd ->+ [|| unsafePerformIO $ do+ jit <- CUDA.loadDataFromPtrEx+ $$( liftIO (B.readFile objFname) `TH.bindCode` \obj ->+ TH.unsafeCodeCoerce [| Ptr $(TH.litE (TH.StringPrimL (B.unpack obj))) |] )+ []+ fun <- newLifetime (FunctionTable $$(listE (map (linkQ 'jit) kmd)))+ return $ PTXR fun+ ||]+ where+ -- Load the module to recover information such as number of registers+ -- and bytes of shared memory. It may be possible to do this without+ -- requiring an active CUDA context.+ loadQ :: TH.Q [(Kernel, CodeQ (Int -> Int))]+ loadQ = TH.runIO $ withContext (ptxContext target) $ do+ obj <- B.readFile objFname+ jit <- B.unsafeUseAsCString obj $ \p -> CUDA.loadDataFromPtrEx (castPtr p) []+ ks <- mapM (uncurry (linkFunctionQ (CUDA.jitModule jit))) cfg+ CUDA.unload (CUDA.jitModule jit)+ return ks++ linkQ :: TH.Name -> (Kernel, CodeQ (Int -> Int)) -> CodeQ Kernel+ linkQ jit (Kernel name _ dsmem cta _, grid) =+ [|| unsafePerformIO $ do+ f <- CUDA.getFun (CUDA.jitModule $$(TH.unsafeCodeCoerce (TH.varE jit))) $$(liftSBS name)+ return $ Kernel $$(liftSBS name) f dsmem cta $$grid+ ||]++ listE :: [CodeQ a] -> CodeQ [a]+ listE xs = TH.unsafeCodeCoerce (TH.listE (map TH.unTypeCode xs))+
+ src/Data/Array/Accelerate/LLVM/PTX/Execute.hs view
@@ -0,0 +1,882 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE ViewPatterns #-}+{-# OPTIONS_GHC -Wno-orphans #-}+-- |+-- Module : Data.Array.Accelerate.LLVM.PTX.Execute+-- Copyright : [2014..2020] The Accelerate Team+-- License : BSD3+--+-- Maintainer : Trevor L. McDonell <trevor.mcdonell@gmail.com>+-- Stability : experimental+-- Portability : non-portable (GHC extensions)+--++module Data.Array.Accelerate.LLVM.PTX.Execute (++ executeAcc,+ executeOpenAcc,++) where++import Data.Array.Accelerate.Analysis.Match+import Data.Array.Accelerate.Error+import Data.Array.Accelerate.Lifetime+import Data.Array.Accelerate.Representation.Array+import Data.Array.Accelerate.Representation.Shape+import Data.Array.Accelerate.Representation.Type+import Data.Array.Accelerate.Type++import Data.Array.Accelerate.LLVM.Execute++import Data.Array.Accelerate.LLVM.PTX.Analysis.Launch ( multipleOf )+import Data.Array.Accelerate.LLVM.PTX.Array.Data+import Data.Array.Accelerate.LLVM.PTX.Array.Prim ( memsetArrayAsync )+import Data.Array.Accelerate.LLVM.PTX.Execute.Async+import Data.Array.Accelerate.LLVM.PTX.Execute.Environment+import Data.Array.Accelerate.LLVM.PTX.Execute.Marshal+import Data.Array.Accelerate.LLVM.PTX.Execute.Stream ( Stream )+import Data.Array.Accelerate.LLVM.PTX.Link+import Data.Array.Accelerate.LLVM.PTX.Target+import qualified Data.Array.Accelerate.LLVM.PTX.Debug as Debug+import qualified Data.Array.Accelerate.LLVM.PTX.Execute.Event as Event++import qualified Foreign.CUDA.Driver as CUDA++import Control.Monad ( forM_ )+import Control.Monad.State ( liftIO )+import Data.ByteString.Short.Char8 ( ShortByteString, unpack )+import Data.List ( find )+import Data.Maybe ( fromMaybe )+import Formatting+import Prelude hiding ( exp, map, sum, scanl, scanr )+import qualified Data.ByteString.Short as S+import qualified Data.ByteString.Short.Extra as SE+import qualified Data.DList as DL+++{-# SPECIALISE INLINE executeAcc :: ExecAcc PTX a -> Par PTX (FutureArraysR PTX a) #-}+{-# SPECIALISE INLINE executeOpenAcc :: ExecOpenAcc PTX aenv a -> Val aenv -> Par PTX (FutureArraysR PTX a) #-}++-- Array expression evaluation+-- ---------------------------++-- Computations are evaluated by traversing the AST bottom up, and for each node+-- distinguishing between three cases:+--+-- 1. If it is a Use node, we return a reference to the array data. The data+-- will already have been copied to the device during compilation of the+-- kernels.+--+-- 2. If it is a non-skeleton node, such as a let binding or shape conversion,+-- then execute directly by updating the environment or similar.+--+-- 3. If it is a skeleton node, then we need to execute the generated LLVM+-- code.+--+instance Execute PTX where+ {-# INLINE map #-}+ {-# INLINE generate #-}+ {-# INLINE transform #-}+ {-# INLINE backpermute #-}+ {-# INLINE fold #-}+ {-# INLINE foldSeg #-}+ {-# INLINE scan #-}+ {-# INLINE scan' #-}+ {-# INLINE permute #-}+ {-# INLINE stencil1 #-}+ {-# INLINE stencil2 #-}+ {-# INLINE aforeign #-}+ map = mapOp+ generate = generateOp+ transform = transformOp+ backpermute = backpermuteOp+ fold True = foldOp+ fold False = fold1Op+ foldSeg i _ = foldSegOp i+ scan _ True = scanOp+ scan _ False = scan1Op+ scan' _ = scan'Op+ permute = permuteOp+ stencil1 = stencil1Op+ stencil2 = stencil2Op+ aforeign = aforeignOp+++-- Skeleton implementation+-- -----------------------++-- Simple kernels just need to know the shape of the output array+--+{-# INLINE simpleOp #-}+simpleOp+ :: HasCallStack+ => ShortByteString+ -> ArrayR (Array sh e)+ -> ExecutableR PTX+ -> Gamma aenv+ -> Val aenv+ -> sh+ -> Par PTX (Future (Array sh e))+simpleOp name repr exe gamma aenv sh =+ withExecutable exe $ \ptxExecutable -> do+ future <- new+ result <- allocateRemote repr sh+ --+ let paramR = TupRsingle $ ParamRarray repr+ cleanup <- executeOp (ptxExecutable !# name) gamma aenv (arrayRshape repr) sh paramR result+ putCleanup future cleanup result+ return future++-- Mapping over an array can ignore the dimensionality of the array and+-- treat it as its underlying linear representation.+--+{-# INLINE mapOp #-}+mapOp+ :: HasCallStack+ => Maybe (a :~: b)+ -> ArrayR (Array sh a)+ -> TypeR b+ -> ExecutableR PTX+ -> Gamma aenv+ -> Val aenv+ -> Array sh a+ -> Par PTX (Future (Array sh b))+mapOp inplace repr tp exe gamma aenv input@(shape -> sh) =+ withExecutable exe $ \ptxExecutable -> do+ let reprOut = ArrayR (arrayRshape repr) tp+ future <- new+ result <- case inplace of+ Just Refl -> return input+ Nothing -> allocateRemote reprOut sh+ --+ let paramsR = TupRsingle (ParamRarray reprOut) `TupRpair` TupRsingle (ParamRarray repr)+ cleanup <- executeOp (ptxExecutable !# "map") gamma aenv (arrayRshape repr) sh paramsR (result, input)+ putCleanup future cleanup result+ return future++{-# INLINE generateOp #-}+generateOp+ :: HasCallStack+ => ArrayR (Array sh e)+ -> ExecutableR PTX+ -> Gamma aenv+ -> Val aenv+ -> sh+ -> Par PTX (Future (Array sh e))+generateOp = simpleOp "generate"++{-# INLINE transformOp #-}+transformOp+ :: HasCallStack+ => ArrayR (Array sh a)+ -> ArrayR (Array sh' b)+ -> ExecutableR PTX+ -> Gamma aenv+ -> Val aenv+ -> sh'+ -> Array sh a+ -> Par PTX (Future (Array sh' b))+transformOp repr repr' exe gamma aenv sh' input =+ withExecutable exe $ \ptxExecutable -> do+ future <- new+ result <- allocateRemote repr' sh'+ let paramsR = TupRsingle (ParamRarray repr') `TupRpair` TupRsingle (ParamRarray repr)+ cleanup <- executeOp (ptxExecutable !# "transform") gamma aenv (arrayRshape repr') sh' paramsR (result, input)+ putCleanup future cleanup result+ return future++{-# INLINE backpermuteOp #-}+backpermuteOp+ :: HasCallStack+ => ArrayR (Array sh e)+ -> ShapeR sh'+ -> ExecutableR PTX+ -> Gamma aenv+ -> Val aenv+ -> sh'+ -> Array sh e+ -> Par PTX (Future (Array sh' e))+backpermuteOp (ArrayR shr tp) shr' = transformOp (ArrayR shr tp) (ArrayR shr' tp)++-- There are two flavours of fold operation:+--+-- 1. If we are collapsing to a single value, then multiple thread blocks are+-- working together. Since thread blocks synchronise with each other via+-- kernel launches, each block computes a partial sum and the kernel is+-- launched recursively until the final value is reached.+--+-- 2. If this is a multidimensional reduction, then each inner dimension is+-- handled by a single thread block, so no global communication is+-- necessary. Furthermore are two kernel flavours: each innermost dimension+-- can be cooperatively reduced by (a) a thread warp; or (b) a thread+-- block. Currently we always use the first, but require benchmarking to+-- determine when to select each.+--+{-# INLINE fold1Op #-}+fold1Op+ :: HasCallStack+ => ArrayR (Array sh e)+ -> ExecutableR PTX+ -> Gamma aenv+ -> Val aenv+ -> Delayed (Array (sh, Int) e)+ -> Par PTX (Future (Array sh e))+fold1Op repr exe gamma aenv arr@(delayedShape -> sh@(sx, sz))+ = boundsCheck "empty array" (sz > 0)+ $ case size (ShapeRsnoc $ arrayRshape repr) sh of+ 0 -> newFull =<< allocateRemote repr sx -- empty, but possibly with one or more non-zero dimensions+ _ -> foldCore repr exe gamma aenv arr++{-# INLINE foldOp #-}+foldOp+ :: HasCallStack+ => ArrayR (Array sh e)+ -> ExecutableR PTX+ -> Gamma aenv+ -> Val aenv+ -> Delayed (Array (sh, Int) e)+ -> Par PTX (Future (Array sh e))+foldOp repr exe gamma aenv arr@(delayedShape -> sh@(sx, _))+ = case size (ShapeRsnoc $ arrayRshape repr) sh of+ 0 -> generateOp repr exe gamma aenv sx+ _ -> foldCore repr exe gamma aenv arr++{-# INLINE foldCore #-}+foldCore+ :: HasCallStack+ => ArrayR (Array sh e)+ -> ExecutableR PTX+ -> Gamma aenv+ -> Val aenv+ -> Delayed (Array (sh, Int) e)+ -> Par PTX (Future (Array sh e))+foldCore repr exe gamma aenv arr+ | ArrayR ShapeRz tp <- repr+ = foldAllOp tp exe gamma aenv arr+ --+ | otherwise+ = foldDimOp repr exe gamma aenv arr++{-# INLINE foldAllOp #-}+foldAllOp+ :: forall aenv e. HasCallStack+ => TypeR e+ -> ExecutableR PTX+ -> Gamma aenv+ -> Val aenv+ -> Delayed (Vector e)+ -> Par PTX (Future (Scalar e))+foldAllOp tp exe gamma aenv input =+ withExecutable exe $ \ptxExecutable -> do+ future <- new+ let+ ks = ptxExecutable !# "foldAllS"+ km1 = ptxExecutable !# "foldAllM1"+ km2 = ptxExecutable !# "foldAllM2"+ sh@((), n) = delayedShape input+ paramsRinput = TupRsingle $ ParamRmaybe $ ParamRarray $ ArrayR dim1 tp+ paramsRdim0 = TupRsingle $ ParamRarray $ ArrayR dim0 tp+ paramsRdim1 = TupRsingle $ ParamRarray $ ArrayR dim1 tp+ --+ if kernelThreadBlocks ks n == 1+ then do+ -- The array is small enough that we can compute it in a single step+ result <- allocateRemote (ArrayR dim0 tp) ()+ let paramsR = paramsRdim0 `TupRpair` paramsRinput+ cleanup <- executeOp ks gamma aenv dim1 sh paramsR (result, manifest input)+ putCleanup future cleanup result++ else do+ -- Multi-kernel reduction to a single element. The first kernel integrates+ -- any delayed elements, and the second is called recursively until+ -- reaching a single element.+ -- The cleanup function is accumulated.+ let+ rec :: Vector e -> IO () -> Par PTX ()+ rec tmp@(Array ((),m) adata) cleanup+ | m <= 1 = putCleanup future cleanup (Array () adata)+ | otherwise = do+ let sh' = ((), m `multipleOf` kernelThreadBlockSize km2)+ out <- allocateRemote (ArrayR dim1 tp) sh'+ let paramsR2 = paramsRdim1 `TupRpair` paramsRdim1+ cleanup2 <- executeOp km2 gamma aenv dim1 sh' paramsR2 (tmp, out)+ rec out (cleanup >> cleanup2)+ --+ let sh' = ((), n `multipleOf` kernelThreadBlockSize km1)+ tmp <- allocateRemote (ArrayR dim1 tp) sh'+ let paramsR1 = paramsRdim1 `TupRpair` paramsRinput+ cleanup <- executeOp km1 gamma aenv dim1 sh' paramsR1 (tmp, manifest input)+ rec tmp cleanup+ --+ return future+++{-# INLINE foldDimOp #-}+foldDimOp+ :: HasCallStack+ => ArrayR (Array sh e)+ -> ExecutableR PTX+ -> Gamma aenv+ -> Val aenv+ -> Delayed (Array (sh, Int) e)+ -> Par PTX (Future (Array sh e))+foldDimOp repr@(ArrayR shr tp) exe gamma aenv input@(delayedShape -> (sh, sz))+ | sz == 0 = generateOp repr exe gamma aenv sh+ | otherwise =+ withExecutable exe $ \ptxExecutable -> do+ future <- new+ result <- allocateRemote repr sh+ --+ let paramsR = TupRsingle (ParamRarray repr) `TupRpair` TupRsingle (ParamRmaybe $ ParamRarray $ ArrayR (ShapeRsnoc shr) tp)+ cleanup <- executeOp (ptxExecutable !# "fold") gamma aenv shr sh paramsR (result, manifest input)+ putCleanup future cleanup result+ return future+++{-# INLINE foldSegOp #-}+foldSegOp+ :: HasCallStack+ => IntegralType i+ -> ArrayR (Array (sh, Int) e)+ -> ExecutableR PTX+ -> Gamma aenv+ -> Val aenv+ -> Delayed (Array (sh, Int) e)+ -> Delayed (Segments i)+ -> Par PTX (Future (Array (sh, Int) e))+foldSegOp intTp repr exe gamma aenv input@(delayedShape -> (sh, sz)) segments@(delayedShape -> ((), ss)) =+ withExecutable exe $ \ptxExecutable -> do+ let+ ArrayR (ShapeRsnoc shr') _ = repr+ reprSeg = ArrayR dim1 $ TupRsingle $ SingleScalarType $ NumSingleType $ IntegralNumType intTp+ n = ss - 1 -- segments array has been 'scanl (+) 0'`ed+ m = size shr' sh * n+ foldseg = if (sz`quot`ss) < (2 * kernelThreadBlockSize foldseg_cta)+ then foldseg_warp+ else foldseg_cta+ --+ foldseg_cta = ptxExecutable !# "foldSeg_block"+ foldseg_warp = ptxExecutable !# "foldSeg_warp"+ -- qinit = ptxExecutable !# "qinit"+ --+ future <- new+ result <- allocateRemote repr (sh, n)+ let paramsR = TupRsingle (ParamRarray repr) `TupRpair` TupRsingle (ParamRmaybe $ ParamRarray repr) `TupRpair` TupRsingle (ParamRmaybe $ ParamRarray reprSeg)+ cleanup <- executeOp foldseg gamma aenv dim1 ((), m) paramsR ((result, manifest input), manifest segments)+ putCleanup future cleanup result+ return future+++{-# INLINE scanOp #-}+scanOp+ :: HasCallStack+ => ArrayR (Array (sh, Int) e)+ -> ExecutableR PTX+ -> Gamma aenv+ -> Val aenv+ -> Delayed (Array (sh, Int) e)+ -> Par PTX (Future (Array (sh, Int) e))+scanOp repr exe gamma aenv input@(delayedShape -> (sz, n)) =+ case n of+ 0 -> generateOp repr exe gamma aenv (sz, 1)+ _ -> scanCore repr exe gamma aenv (n+1) input++{-# INLINE scan1Op #-}+scan1Op+ :: HasCallStack+ => ArrayR (Array (sh, Int) e)+ -> ExecutableR PTX+ -> Gamma aenv+ -> Val aenv+ -> Delayed (Array (sh, Int) e)+ -> Par PTX (Future (Array (sh, Int) e))+scan1Op repr exe gamma aenv input@(delayedShape -> sh@(_, n)) =+ case n of+ 0 -> newFull =<< allocateRemote repr sh+ _ -> scanCore repr exe gamma aenv n input++{-# INLINE scanCore #-}+scanCore+ :: HasCallStack+ => ArrayR (Array (sh, Int) e)+ -> ExecutableR PTX+ -> Gamma aenv+ -> Val aenv+ -> Int -- output size of innermost dimension+ -> Delayed (Array (sh, Int) e)+ -> Par PTX (Future (Array (sh, Int) e))+scanCore repr exe gamma aenv m input+ | ArrayR (ShapeRsnoc ShapeRz) tp <- repr+ = scanAllOp tp exe gamma aenv m input+ --+ | otherwise+ = scanDimOp repr exe gamma aenv m input++{-# INLINE scanAllOp #-}+scanAllOp+ :: HasCallStack+ => TypeR e+ -> ExecutableR PTX+ -> Gamma aenv+ -> Val aenv+ -> Int -- output size+ -> Delayed (Vector e)+ -> Par PTX (Future (Vector e))+scanAllOp tp exe gamma aenv m input@(delayedShape -> ((), n)) =+ withExecutable exe $ \ptxExecutable -> do+ let+ k1 = ptxExecutable !# "scanP1"+ k2 = ptxExecutable !# "scanP2"+ k3 = ptxExecutable !# "scanP3"+ --+ c = kernelThreadBlockSize k1+ s = n `multipleOf` c+ --+ repr = ArrayR dim1 tp+ paramR = TupRsingle $ ParamRarray repr+ paramsR1 = paramR `TupRpair` paramR `TupRpair` TupRsingle (ParamRmaybe $ ParamRarray repr)+ paramsR3 = paramR `TupRpair` paramR `TupRpair` TupRsingle ParamRint+ --+ future <- new+ result <- allocateRemote repr ((), m)++ -- Step 1: Independent thread-block-wide scans of the input. Small arrays+ -- which can be computed by a single thread block will require no+ -- additional work.+ tmp <- allocateRemote repr ((), s)+ cleanup1 <- executeOp k1 gamma aenv dim1 ((), s) paramsR1 ((tmp, result), manifest input)++ -- Step 2: Multi-block reductions need to compute the per-block prefix,+ -- then apply those values to the partial results.+ cleanup2 <-+ if s > 1+ then do+ cleanup2a <- executeOp k2 gamma aenv dim1 ((), s) paramR tmp+ cleanup2b <- executeOp k3 gamma aenv dim1 ((), s-1) paramsR3 ((tmp, result), c)+ return (cleanup2a >> cleanup2b)+ else+ return (return ())++ putCleanup future (cleanup1 >> cleanup2) result+ return future++{-# INLINE scanDimOp #-}+scanDimOp+ :: HasCallStack+ => ArrayR (Array (sh, Int) e)+ -> ExecutableR PTX+ -> Gamma aenv+ -> Val aenv+ -> Int+ -> Delayed (Array (sh, Int) e)+ -> Par PTX (Future (Array (sh, Int) e))+scanDimOp repr exe gamma aenv m input@(delayedShape -> (sz, _)) =+ withExecutable exe $ \ptxExecutable -> do+ let ArrayR (ShapeRsnoc shr') _ = repr+ future <- new+ result <- allocateRemote repr (sz, m)+ let paramsR = TupRsingle (ParamRarray repr) `TupRpair` TupRsingle (ParamRmaybe $ ParamRarray repr)+ cleanup <- executeOp (ptxExecutable !# "scan") gamma aenv dim1 ((), size shr' sz) paramsR (result, manifest input)+ putCleanup future cleanup result+ return future+++{-# INLINE scan'Op #-}+scan'Op+ :: HasCallStack+ => ArrayR (Array (sh, Int) e)+ -> ExecutableR PTX+ -> Gamma aenv+ -> Val aenv+ -> Delayed (Array (sh, Int) e)+ -> Par PTX (Future (Array (sh, Int) e, Array sh e))+scan'Op repr exe gamma aenv input@(delayedShape -> (sz, n)) =+ case n of+ 0 -> do+ future <- new+ result <- allocateRemote repr (sz, 0)+ sums <- generateOp (reduceRank repr) exe gamma aenv sz+ fork $ do sums' <- get sums+ put future (result, sums')+ return future+ --+ _ -> scan'Core repr exe gamma aenv input++{-# INLINE scan'Core #-}+scan'Core+ :: HasCallStack+ => ArrayR (Array (sh, Int) e)+ -> ExecutableR PTX+ -> Gamma aenv+ -> Val aenv+ -> Delayed (Array (sh, Int) e)+ -> Par PTX (Future (Array (sh, Int) e, Array sh e))+scan'Core repr exe gamma aenv input+ | ArrayR (ShapeRsnoc ShapeRz) tp <- repr+ = scan'AllOp tp exe gamma aenv input+ --+ | otherwise+ = scan'DimOp repr exe gamma aenv input++{-# INLINE scan'AllOp #-}+scan'AllOp+ :: HasCallStack+ => TypeR e+ -> ExecutableR PTX+ -> Gamma aenv+ -> Val aenv+ -> Delayed (Vector e)+ -> Par PTX (Future (Vector e, Scalar e))+scan'AllOp tp exe gamma aenv input@(delayedShape -> ((), n)) =+ withExecutable exe $ \ptxExecutable -> do+ let+ repr = ArrayR dim1 tp+ paramRdim0 = TupRsingle $ ParamRarray $ ArrayR dim0 tp+ paramRdim1 = TupRsingle $ ParamRarray repr+ k1 = ptxExecutable !# "scanP1"+ k2 = ptxExecutable !# "scanP2"+ k3 = ptxExecutable !# "scanP3"+ --+ c = kernelThreadBlockSize k1+ s = n `multipleOf` c+ --+ future <- new+ result <- allocateRemote repr ((), n)+ tmp <- allocateRemote repr ((), s)++ -- Step 1: independent thread-block-wide scans. Each block stores its partial+ -- sum to a temporary array.+ let paramsR1 = paramRdim1 `TupRpair` paramRdim1 `TupRpair` TupRsingle (ParamRmaybe $ ParamRarray repr)+ cleanup1 <- executeOp k1 gamma aenv dim1 ((), s) paramsR1 ((tmp, result), manifest input)++ -- If this was a small array that was processed by a single thread block then+ -- we are done, otherwise compute the per-block prefix and apply those values+ -- to the partial results.+ if s == 1+ then+ case tmp of+ Array _ ad -> putCleanup future cleanup1 (result, Array () ad)++ else do+ sums <- allocateRemote (ArrayR dim0 tp) ()+ let paramsR2 = paramRdim1 `TupRpair` paramRdim0+ let paramsR3 = paramRdim1 `TupRpair` paramRdim1 `TupRpair` TupRsingle ParamRint+ cleanup2 <- executeOp k2 gamma aenv dim1 ((), s) paramsR2 (tmp, sums)+ cleanup3 <- executeOp k3 gamma aenv dim1 ((), s-1) paramsR3 ((tmp, result), c)+ putCleanup future (cleanup1 >> cleanup2 >> cleanup3) (result, sums)+ --+ return future++{-# INLINE scan'DimOp #-}+scan'DimOp+ :: HasCallStack+ => ArrayR (Array (sh, Int) e)+ -> ExecutableR PTX+ -> Gamma aenv+ -> Val aenv+ -> Delayed (Array (sh, Int) e)+ -> Par PTX (Future (Array (sh, Int) e, Array sh e))+scan'DimOp repr@(ArrayR (ShapeRsnoc shr') _) exe gamma aenv input@(delayedShape -> sh@(sz, _)) =+ withExecutable exe $ \ptxExecutable -> do+ future <- new+ result <- allocateRemote repr sh+ sums <- allocateRemote (reduceRank repr) sz+ let paramsR = TupRsingle (ParamRarray repr) `TupRpair` TupRsingle (ParamRarray $ reduceRank repr) `TupRpair` TupRsingle (ParamRmaybe $ ParamRarray repr)+ cleanup <- executeOp (ptxExecutable !# "scan") gamma aenv dim1 ((), size shr' sz) paramsR ((result, sums), manifest input)+ putCleanup future cleanup (result, sums)+ return future+++{-# INLINE permuteOp #-}+permuteOp+ :: HasCallStack+ => Bool+ -> ArrayR (Array sh e)+ -> ShapeR sh'+ -> ExecutableR PTX+ -> Gamma aenv+ -> Val aenv+ -> Array sh' e+ -> Delayed (Array sh e)+ -> Par PTX (Future (Array sh' e))+permuteOp inplace repr@(ArrayR shr tp) shr' exe gamma aenv defaults@(shape -> shOut) input@(delayedShape -> shIn) =+ withExecutable exe $ \ptxExecutable -> do+ let+ n = size shr shIn+ m = size shr' shOut+ repr' = ArrayR shr' tp+ reprLock = ArrayR dim1 $ TupRsingle $ scalarTypeWord32+ paramR = TupRsingle $ ParamRmaybe $ ParamRarray repr+ paramR' = TupRsingle $ ParamRarray repr'+ kernel = case functionTable ptxExecutable of+ k:_ -> k+ _ -> internalError "no kernels found"+ --+ future <- new+ result <- if inplace+ then Debug.trace Debug.dump_exec "exec: permute/inplace" $ return defaults+ else Debug.trace Debug.dump_exec "exec: permute/clone" $ get =<< cloneArrayAsync repr' defaults+ --+ let kernelName' =+ let kn = kernelName kernel+ in SE.take (S.length kn - 65) kn+ cleanup <- case kernelName' of+ -- execute directly using atomic operations+ "permute_rmw" ->+ let paramsR = paramR' `TupRpair` paramR+ in executeOp kernel gamma aenv dim1 ((), n) paramsR (result, manifest input)++ -- a temporary array is required for spin-locks around the critical section+ "permute_mutex" -> do+ barrier <- new :: Par PTX (Future (Vector Word32))+ Array _ ad <- allocateRemote reprLock ((), m)+ fork $ do fill <- memsetArrayAsync (NumSingleType $ IntegralNumType TypeWord32) m 0 ad+ put barrier . Array ((), m) =<< get fill+ --+ let paramsR = paramR' `TupRpair` TupRsingle (ParamRfuture $ ParamRarray reprLock) `TupRpair` paramR+ executeOp kernel gamma aenv dim1 ((), n) paramsR ((result, barrier), manifest input)++ _ -> internalError "unexpected kernel image"+ --+ putCleanup future cleanup result+ return future+++{-# INLINE stencil1Op #-}+stencil1Op+ :: HasCallStack+ => TypeR a+ -> ArrayR (Array sh b)+ -> sh+ -> ExecutableR PTX+ -> Gamma aenv+ -> Val aenv+ -> Delayed (Array sh a)+ -> Par PTX (Future (Array sh b))+stencil1Op tp repr@(ArrayR shr _) halo exe gamma aenv input@(delayedShape -> sh) =+ stencilCore repr exe gamma aenv halo sh paramsR (manifest input)+ where paramsR = TupRsingle $ ParamRmaybe $ ParamRarray $ ArrayR shr tp++-- Using the defaulting instances for stencil operations (for now).+--+{-# INLINE stencil2Op #-}+stencil2Op+ :: HasCallStack+ => TypeR a+ -> TypeR b+ -> ArrayR (Array sh c)+ -> sh+ -> ExecutableR PTX+ -> Gamma aenv+ -> Val aenv+ -> Delayed (Array sh a)+ -> Delayed (Array sh b)+ -> Par PTX (Future (Array sh c))+stencil2Op tpA tpB repr@(ArrayR shr _) halo exe gamma aenv input1@(delayedShape -> sh1) input2@(delayedShape -> sh2) =+ stencilCore repr exe gamma aenv halo (intersect (arrayRshape repr) sh1 sh2) paramsR (manifest input1, manifest input2)+ where paramsR = TupRsingle (ParamRmaybe $ ParamRarray $ ArrayR shr tpA) `TupRpair` TupRsingle (ParamRmaybe $ ParamRarray $ ArrayR shr tpB)++{-# INLINE stencilCore #-}+stencilCore+ :: forall aenv sh e params. HasCallStack+ => ArrayR (Array sh e)+ -> ExecutableR PTX+ -> Gamma aenv+ -> Val aenv+ -> sh -- border dimensions (i.e. index of first interior element)+ -> sh -- output array size+ -> ParamsR PTX params+ -> params+ -> Par PTX (Future (Array sh e))+stencilCore repr@(ArrayR shr _) exe gamma aenv halo shOut paramsR params =+ withExecutable exe $ \ptxExecutable -> do+ let+ inside = ptxExecutable !# "stencil_inside"+ border = ptxExecutable !# "stencil_border"++ shIn :: sh+ shIn = trav (\x y -> x - 2*y) shOut halo++ trav :: (Int -> Int -> Int) -> sh -> sh -> sh+ trav f a b = go (arrayRshape repr) a b+ where+ go :: ShapeR t -> t -> t -> t+ go ShapeRz () () = ()+ go (ShapeRsnoc shr') (xa,xb) (ya,yb) = (go shr' xa ya, f xb yb)+ --+ future <- new+ result <- allocateRemote repr shOut+ parent <- asksParState ptxStream+ parentStartPoint <- liftPar (Event.waypoint parent)++ -- interior (no bounds checking)+ let paramsRinside = TupRsingle (ParamRshape shr) `TupRpair` TupRsingle (ParamRarray repr) `TupRpair` paramsR+ cleanup1 <- executeOp inside gamma aenv shr shIn paramsRinside ((shIn, result), params)++ -- halo regions (bounds checking)+ -- executed in separate streams so that they might overlap the main stencil+ -- and each other, as individually they will not saturate the device+ forM_ (stencilBorders (arrayRshape repr) shOut halo) $ \(u, v) ->+ fork $ do+ -- synchronise with start of stencil computation, so that the arguments+ -- are available+ child <- asksParState ptxStream+ liftIO (Event.after parentStartPoint child)++ -- launch in a separate stream+ let sh = trav (-) v u+ let paramsRborder = TupRsingle (ParamRshape shr) `TupRpair` TupRsingle (ParamRshape shr)+ `TupRpair` TupRsingle (ParamRarray repr)+ `TupRpair` paramsR+ cleanup2 <- executeOp border gamma aenv shr sh paramsRborder (((u, sh), result), params)+ addCleanup future cleanup2++ -- make remainder of the parent stream depend on the border results+ event <- liftPar (Event.waypoint child)+ ready <- liftIO (Event.query event)+ if ready then return ()+ else liftIO (Event.after event parent)++ putCleanup future cleanup1 result+ return future++-- Compute the stencil border regions, where we may need to evaluate the+-- boundary conditions.+--+{-# INLINE stencilBorders #-}+stencilBorders+ :: forall sh. HasCallStack+ => ShapeR sh+ -> sh+ -> sh+ -> [(sh, sh)]+stencilBorders shr sh halo = [ face i | i <- [0 .. (2 * rank shr - 1)] ]+ where+ face :: Int -> (sh, sh)+ face n = go n shr sh halo++ go :: Int -> ShapeR t -> t -> t -> (t, t)+ go _ ShapeRz () () = ((), ())+ go n (ShapeRsnoc shr') (sha, sza) (shb, szb)+ = let+ (sha', shb') = go (n-2) shr' sha shb+ (sza', szb')+ | n < 0 = (0, sza)+ | n == 0 = (0, szb)+ | n == 1 = (sza-szb, sza)+ | otherwise = (szb, sza-szb)+ in+ ((sha', sza'), (shb', szb'))+++-- Foreign functions+--+{-# INLINE aforeignOp #-}+aforeignOp+ :: HasCallStack+ => String+ -> ArraysR as+ -> ArraysR bs+ -> (as -> Par PTX (Future bs))+ -> as+ -> Par PTX (Future bs)+aforeignOp name _ _ asm arr = do+ stream <- asksParState ptxStream+ Debug.monitorProcTime query msg (Just (unsafeGetValue stream)) (asm arr)+ where+ msg = Debug.traceM Debug.dump_exec ("exec: " % string % " " % Debug.elapsed) name+ query = if Debug.debuggingIsEnabled+ then return True+ else liftIO $ Debug.getFlag Debug.dump_exec++++-- Skeleton execution+-- ------------------++-- | Retrieve the named kernel+--+(!#) :: HasCallStack => FunctionTable -> ShortByteString -> Kernel+(!#) exe name+ = fromMaybe (internalError ("function not found: " % string) (unpack name))+ $ lookupKernel name exe++lookupKernel :: ShortByteString -> FunctionTable -> Maybe Kernel+lookupKernel name ptxExecutable =+ find (\k -> let n = kernelName k in SE.take (S.length n - 65) n == name) (functionTable ptxExecutable)++delayedShape :: Delayed (Array sh e) -> sh+delayedShape (Delayed sh) = sh+delayedShape (Manifest a) = shape a++manifest :: Delayed (Array sh e) -> Maybe (Array sh e)+manifest (Manifest a) = Just a+manifest Delayed{} = Nothing++-- | Execute some operation with the supplied executable functions+--+withExecutable :: HasCallStack => ExecutableR PTX -> (FunctionTable -> Par PTX b) -> Par PTX b+withExecutable PTXR{..} f =+ localParState (\(s,_) -> (s,Just ptxExecutable)) $ do+ r <- f (unsafeGetValue ptxExecutable)+ liftIO $ touchLifetime ptxExecutable+ return r+++-- Execute the function implementing this kernel.+--+executeOp+ :: HasCallStack+ => Kernel+ -> Gamma aenv+ -> Val aenv+ -> ShapeR sh+ -> sh+ -> ParamsR PTX params+ -> params+ -> Par PTX (IO ())+executeOp kernel gamma aenv shr sh paramsR params =+ let n = size shr sh+ in if n > 0+ then do+ stream <- asksParState ptxStream+ (argv, cleanup) <- marshalParams' @PTX (paramsR `TupRpair` TupRsingle (ParamRenv gamma)) (params, aenv)+ liftIO $ launch kernel stream n $ DL.toList argv+ return cleanup+ else+ return (return ())+++-- Execute a device function with the given thread configuration and function+-- parameters.+--+launch :: HasCallStack => Kernel -> Stream -> Int -> [CUDA.FunParam] -> IO ()+launch Kernel{..} stream n args =+ withLifetime stream $ \st ->+ Debug.monitorProcTime query msg (Just st) $+ CUDA.launchKernel kernelFun grid cta smem (Just st) args+ where+ cta = (kernelThreadBlockSize, 1, 1)+ grid = (kernelThreadBlocks n, 1, 1)+ smem = kernelSharedMemBytes++ -- Debugging/monitoring support+ query = if Debug.debuggingIsEnabled+ then return True+ else Debug.getFlag Debug.dump_exec++ fst3 (x,_,_) = x+ msg wall cpu gpu = do+ verbose <- Debug.getFlag Debug.verbose+ let kernelName' | verbose = kernelName+ | otherwise = SE.take (S.length kernelName - 65) kernelName+ Debug.traceM Debug.dump_exec ("exec: " % string % " <<< " % int % ", " % int % ", " % int % " >>> " % Debug.elapsed)+ (unpack kernelName') (fst3 grid) (fst3 cta) smem wall cpu gpu+
+ src/Data/Array/Accelerate/LLVM/PTX/Execute/Async.hs view
@@ -0,0 +1,197 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeSynonymInstances #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}+-- |+-- Module : Data.Array.Accelerate.LLVM.PTX.Execute.Async+-- Copyright : [2014..2020] The Accelerate Team+-- License : BSD3+--+-- Maintainer : Trevor L. McDonell <trevor.mcdonell@gmail.com>+-- Stability : experimental+-- Portability : non-portable (GHC extensions)+--++module Data.Array.Accelerate.LLVM.PTX.Execute.Async (++ module Data.Array.Accelerate.LLVM.Execute.Async,+ module Data.Array.Accelerate.LLVM.PTX.Execute.Async,++) where++import Data.Array.Accelerate.Error+import Data.Array.Accelerate.Lifetime++import Data.Array.Accelerate.LLVM.Execute.Async+import Data.Array.Accelerate.LLVM.State++import Data.Array.Accelerate.LLVM.PTX.Target+import Data.Array.Accelerate.LLVM.PTX.Execute.Event ( Event )+import Data.Array.Accelerate.LLVM.PTX.Execute.Stream ( Stream )+import Data.Array.Accelerate.LLVM.PTX.Link.Object ( FunctionTable )+import qualified Data.Array.Accelerate.LLVM.PTX.Execute.Event as Event+import qualified Data.Array.Accelerate.LLVM.PTX.Execute.Stream as Stream++import Control.Monad.Reader+import Data.IORef+++-- | Evaluate a parallel computation+--+{-# INLINE evalPar #-}+evalPar :: Par PTX a -> LLVM PTX a+evalPar p = do+ s <- Stream.create+ r <- runReaderT (runPar p) (s, Nothing)+ return r+++type ParState = (Stream, Maybe (Lifetime FunctionTable))++ptxStream :: ParState -> Stream+ptxStream = fst++ptxKernel :: ParState -> Maybe (Lifetime FunctionTable)+ptxKernel = snd+++-- Implementation+-- --------------++data Future a = Future {-# UNPACK #-} !(IORef (IVar a))++data IVar a+ = Full !a+ | Pending {-# UNPACK #-} !Event !(IO ()) !a+ | Empty !(IO ())+++askParState :: Par PTX ParState+askParState = Par ask++asksParState :: (ParState -> a) -> Par PTX a+asksParState f = Par (asks f)++localParState :: (ParState -> ParState) -> Par PTX a -> Par PTX a+localParState f (Par m) = Par (local f m)++instance MonadReader PTX (Par PTX) where+ ask = Par (lift ask)+ local f (Par (ReaderT g)) = Par (ReaderT (\parstate -> local f (g parstate)))++instance Async PTX where+ type FutureR PTX = Future++ newtype Par PTX a = Par { runPar :: ReaderT ParState (LLVM PTX) a }+ deriving ( Functor, Applicative, Monad, MonadIO )++ {-# INLINEABLE new #-}+ {-# INLINEABLE newFull #-}+ new = Future <$> liftIO (newIORef (Empty (return ())))+ newFull v = Future <$> liftIO (newIORef (Full v))++ {-# INLINEABLE spawn #-}+ spawn m = do+ s' <- liftPar Stream.create+ r <- localParState (const (s', Nothing)) m+ liftIO (Stream.destroy s')+ return r++ {-# INLINEABLE fork #-}+ fork m = do+ s' <- liftPar (Stream.create)+ () <- localParState (const (s', Nothing)) m+ liftIO (Stream.destroy s')++ -- When we call 'put' the actual work may not have been evaluated yet; get+ -- a new event in the current execution stream and once that is filled we can+ -- transition the IVar to Full.+ --+ {-# INLINEABLE put #-}+ put (Future ref) v = do+ stream <- asksParState ptxStream+ kernel <- asksParState ptxKernel+ event <- liftPar (Event.waypoint stream)+ ready <- liftIO (Event.query event)+ let cleanupK = case kernel of+ Just k -> touchLifetime k+ Nothing -> return ()+ liftIO . atomicModifyIORef' ref $ \case+ Empty cleanup -> if ready then (Full v, ())+ else (Pending event (cleanup >> cleanupK) v, ())+ _ -> internalError "multiple put"++ -- Get the value of Future. Since the actual cross-stream synchronisation+ -- happens on the device, we should never have to block/reschedule the main+ -- thread waiting on a value; if we get an empty IVar at this point, something+ -- has gone wrong.+ --+ {-# INLINEABLE get #-}+ get (Future ref) = do+ stream <- asksParState ptxStream+ liftIO $ do+ ivar <- readIORef ref+ case ivar of+ Full v -> return v+ Pending event cleanup v -> do+ ready <- Event.query event+ if ready+ then do+ writeIORef ref (Full v)+ cleanup+ else+ Event.after event stream+ return v+ Empty _ -> internalError "blocked on an IVar"++ {-# INLINEABLE block #-}+ block = liftIO . wait++ {-# INLINE liftPar #-}+ liftPar = Par . lift+++-- | Block the calling _host_ thread until the value offered by the future is+-- available.+--+{-# INLINEABLE wait #-}+wait :: Future a -> IO a+wait (Future ref) = do+ ivar <- readIORef ref+ case ivar of+ Full v -> return v+ Pending event cleanup v -> do+ Event.block event+ writeIORef ref (Full v)+ cleanup+ return v+ Empty _ -> internalError "blocked on an IVar"++{-# INLINEABLE putCleanup #-}+putCleanup :: HasCallStack => FutureR PTX a -> IO () -> a -> Par PTX ()+putCleanup (Future ref) cleanup v = do+ stream <- asksParState ptxStream+ kernel <- asksParState ptxKernel+ event <- liftPar (Event.waypoint stream)+ ready <- liftIO (Event.query event)+ let cleanupK = case kernel of+ Just k -> touchLifetime k+ Nothing -> return ()+ liftIO . atomicModifyIORef' ref $ \case+ Empty cleanup2 -> if ready then (Full v, ())+ else (Pending event (cleanup2 >> cleanup >> cleanupK) v, ())+ _ -> internalError "multiple put"++{-# INLINEABLE addCleanup #-}+addCleanup :: HasCallStack => FutureR PTX a -> IO () -> Par PTX ()+addCleanup (Future ref) cleanup = liftIO $ do+ toRunNow <- atomicModifyIORef' ref $ \case+ Full v -> (Full v, cleanup)+ Pending event cleanup2 v -> (Pending event (cleanup2 >> cleanup) v, return ())+ Empty cleanup2 -> (Empty (cleanup2 >> cleanup), return ())+ toRunNow+
+ src/Data/Array/Accelerate/LLVM/PTX/Execute/Environment.hs view
@@ -0,0 +1,22 @@+-- |+-- Module : Data.Array.Accelerate.LLVM.PTX.Execute.Environment+-- Copyright : [2014..2020] The Accelerate Team+-- License : BSD3+--+-- Maintainer : Trevor L. McDonell <trevor.mcdonell@gmail.com>+-- Stability : experimental+-- Portability : non-portable (GHC extensions)+--++module Data.Array.Accelerate.LLVM.PTX.Execute.Environment (++ module Data.Array.Accelerate.LLVM.Execute.Environment,+ module Data.Array.Accelerate.LLVM.PTX.Execute.Environment,++) where++import Data.Array.Accelerate.LLVM.PTX.Target+import Data.Array.Accelerate.LLVM.Execute.Environment++type Val = ValR PTX+
+ src/Data/Array/Accelerate/LLVM/PTX/Execute/Event.hs view
@@ -0,0 +1,161 @@+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE OverloadedStrings #-}+-- |+-- Module : Data.Array.Accelerate.LLVM.PTX.Execute.Event+-- Copyright : [2014..2020] The Accelerate Team+-- License : BSD3+--+-- Maintainer : Trevor L. McDonell <trevor.mcdonell@gmail.com>+-- Stability : experimental+-- Portability : non-portable (GHC extensions)+--++module Data.Array.Accelerate.LLVM.PTX.Execute.Event (++ Event,+ create, destroy, query, waypoint, after, block,++) where++import Data.Array.Accelerate.Lifetime+import qualified Data.Array.Accelerate.Array.Remote.LRU as Remote++import Data.Array.Accelerate.LLVM.PTX.Array.Remote ( )+import qualified Data.Array.Accelerate.LLVM.PTX.Context as Context+import Data.Array.Accelerate.LLVM.PTX.Target ( PTX(..) )+import Data.Array.Accelerate.LLVM.State+import qualified Data.Array.Accelerate.LLVM.PTX.Debug as Debug+import {-# SOURCE #-} Data.Array.Accelerate.LLVM.PTX.Execute.Stream++import Foreign.CUDA.Driver.Error+import qualified Foreign.CUDA.Driver.Event as Event+import qualified Foreign.CUDA.Driver.Stream as Stream++import Control.Exception+import Control.Monad+import Control.Monad.Reader+import Data.Text.Lazy.Builder+import Formatting+++-- | Events can be used for efficient device-side synchronisation between+-- execution streams and between the host.+--+type Event = Lifetime Event.Event+++-- | Create a new event. It will not be automatically garbage collected, and is+-- not suitable for timing purposes.+--+{-# INLINEABLE create #-}+create :: LLVM PTX Event+create = do+ ctx <- asks ptxContext+ e <- create'+ event <- liftIO $ newLifetime e+ liftIO $ addFinalizer event $ do+ message ("destroy " % formatEvent) e+ Context.contextFinalizeResource ctx $+ Event.destroy e+ return event++create' :: LLVM PTX Event.Event+create' = do+ PTX{ptxMemoryTable} <- asks llvmTarget+ me <- attempt "create/new" (liftIO . catchOOM $ Event.create [Event.DisableTiming])+ `orElse` do+ Remote.reclaim ptxMemoryTable+ liftIO $ do+ message "create/new: failed (purging)"+ catchOOM $ Event.create [Event.DisableTiming]+ case me of+ Just e -> return e+ Nothing -> liftIO $ do+ message "create/new: failed (non-recoverable)"+ throwIO (ExitCode OutOfMemory)++ where+ catchOOM :: IO a -> IO (Maybe a)+ catchOOM it =+ liftM Just it `catch` \e -> case e of+ ExitCode OutOfMemory -> return Nothing+ _ -> throwIO e++ attempt :: MonadIO m => Builder -> m (Maybe a) -> m (Maybe a)+ attempt msg ea = do+ ma <- ea+ case ma of+ Nothing -> return Nothing+ Just a -> do message builder msg+ return (Just a)++ orElse :: MonadIO m => m (Maybe a) -> m (Maybe a) -> m (Maybe a)+ orElse ea eb = do+ ma <- ea+ case ma of+ Just a -> return (Just a)+ Nothing -> eb+++-- | Delete an event+--+{-# INLINEABLE destroy #-}+destroy :: Event -> IO ()+destroy = finalize++-- | Create a new event marker that will be filled once execution in the+-- specified stream has completed all previously submitted work.+--+{-# INLINEABLE waypoint #-}+waypoint :: Stream -> LLVM PTX Event+waypoint stream = do+ event <- create+ liftIO $+ withLifetime stream $ \s -> do+ withLifetime event $ \e -> do+ message ("add waypoint " % formatEvent % " in stream " % formatStream) e s+ Event.record e (Just s)+ return event++-- | Make all future work submitted to the given stream wait until the event+-- reports completion before beginning execution.+--+{-# INLINEABLE after #-}+after :: Event -> Stream -> IO ()+after event stream =+ withLifetime stream $ \s ->+ withLifetime event $ \e -> do+ message ("after " % formatEvent % " in stream " % formatStream) e s+ Event.wait e (Just s) []++-- | Block the calling thread until the event is recorded+--+{-# INLINEABLE block #-}+block :: Event -> IO ()+block event =+ withLifetime event $ \e -> do+ message ("blocked on event " % formatEvent) e+ Event.block e++-- | Test whether an event has completed+--+{-# INLINEABLE query #-}+query :: Event -> IO Bool+query event = withLifetime event Event.query+++-- Debug+-- -----++{-# INLINE message #-}+message :: MonadIO m => Format (m ()) a -> a+message fmt = Debug.traceM Debug.dump_sched ("event: " % fmt)++{-# INLINE formatEvent #-}+formatEvent :: Format r (Event.Event -> r)+formatEvent = later $ \(Event.Event e) -> bformat shown e++{-# INLINE formatStream #-}+formatStream :: Format r (Stream.Stream -> r)+formatStream = later $ \(Stream.Stream s) -> bformat shown s+
+ src/Data/Array/Accelerate/LLVM/PTX/Execute/Event.hs-boot view
@@ -0,0 +1,26 @@+-- |+-- Module : Data.Array.Accelerate.LLVM.PTX.Execute.Event-boot+-- Copyright : [2016..2020] The Accelerate Team+-- License : BSD3+--+-- Maintainer : Trevor L. McDonell <trevor.mcdonell@gmail.com>+-- Stability : experimental+-- Portability : non-portable (GHC extensions)+--++module Data.Array.Accelerate.LLVM.PTX.Execute.Event (++ Event,+ query, block++) where++import Data.Array.Accelerate.Lifetime+import qualified Foreign.CUDA.Driver.Event as Event+++type Event = Lifetime Event.Event++query :: Event -> IO Bool+block :: Event -> IO ()+
+ src/Data/Array/Accelerate/LLVM/PTX/Execute/Marshal.hs view
@@ -0,0 +1,108 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}+-- |+-- Module : Data.Array.Accelerate.LLVM.PTX.Execute.Marshal+-- Copyright : [2014..2020] The Accelerate Team+-- License : BSD3+--+-- Maintainer : Trevor L. McDonell <trevor.mcdonell@gmail.com>+-- Stability : experimental+-- Portability : non-portable (GHC extensions)+--++module Data.Array.Accelerate.LLVM.PTX.Execute.Marshal (++ module Data.Array.Accelerate.LLVM.Execute.Marshal++) where++import Data.Array.Accelerate.LLVM.State+import Data.Array.Accelerate.LLVM.Execute.Marshal++import Data.Array.Accelerate.LLVM.PTX.Target+import Data.Array.Accelerate.LLVM.PTX.Execute.Async+import qualified Data.Array.Accelerate.LLVM.PTX.Array.Prim as Prim++import Data.Array.Accelerate.Type+import Data.Array.Accelerate.Array.Data++import qualified Foreign.CUDA.Driver as CUDA++import Control.Concurrent+import Control.Monad.IO.Class (liftIO)+import qualified Data.DList as DL+++instance Marshal PTX where+ type ArgR PTX = CUDA.FunParam+ type MarshalCleanup PTX = IO ()++ marshalInt = CUDA.VArg+ marshalScalarData' t+ | SingleArrayDict <- singleArrayDict t+ = liftPar . fmap (\(ptr, cleanup) -> (DL.singleton (CUDA.VArg ptr), cleanup)) . getCudaDevicePtr t++-- | Return the CUDA device pointer corresponding to the given array, as well+-- as a cleanup IO action that __MUST__ be run once you are done with the+-- pointer (i.e. the GPU kernel has completed). Not calling the cleanup action+-- will result in leaked memory and resources. Calling the action twice will+-- block indefinitely on an MVar.+--+-- This function is a hack. Prim.withDevicePtr is intended to be a wrapping+-- function that retains the resource while the callback is running and+-- releases it when the callback returns. This is all nice, but since the PTX+-- Accelerate runtime is asynchronous, uses of withDevicePtr would not all be+-- neatly nested: the actual array lifetimes are haphazard intervals during+-- program execution.+--+-- Originally, this function just gave up and extracted the DevicePtr by+-- calling withDevicePtr with a trivial body that simply leaks p; this is+-- unsound (which was acknowledged by a 'fixme' comment...) and appears to have+-- been the cause of silent incorrect results (!) on a GTX 1050 Ti on the+-- adbench-gmmgrad test in accelerate-tests [1].+--+-- [1]: https://github.com/tomsmeding/accelerate-tests/blob/master/src/Data/Array/Accelerate/Tests/Prog/ADBenchGMMGrad.hs+--+-- Fortunately, it turns out that the MemoryTable implementation underlying+-- withDevicePtr does not in fact assume lexical nesting of array usages. Thus+-- we can use a hack to let the callback of withDevicePtr live for the correct+-- amount of time without needing to rearchitect the entire PTX backend: let+-- the call run in a forkIO thread and use MVars to communicate when it should+-- return. This means that we now have the possibility to return a+-- self-contained "cleanup" handler from getCudaDevicePtr that does nothing but+-- signal to the withDevicePtr callback that the array's lifetime has ended and+-- the scope can close. All this is possible because the 'LLVM' monad is just a+-- reader monad over IO, so we can unlift it into IO.+--+-- As a final, questionable improvement, we let the "cleanup" handler wait+-- until withDevicePtr has properly returned so that we know the array's+-- refcount has been properly decremented and memory has been released if+-- possible.+getCudaDevicePtr+ :: SingleType e+ -> ArrayData e+ -> LLVM PTX (CUDA.DevicePtr (ScalarArrayDataR e), IO ())+getCudaDevicePtr !t !ad = do+ ptrVar <- liftIO newEmptyMVar+ doneVar <- liftIO newEmptyMVar+ releasedVar <- liftIO newEmptyMVar++ _ <- unliftIOLLVM $ \inLLVM -> forkIO $ inLLVM $ do+ Prim.withDevicePtr t ad $ \p -> liftIO $ do+ putMVar ptrVar p+ takeMVar doneVar+ return (Nothing, ())+ liftIO $ putMVar releasedVar ()++ ptr <- liftIO $ readMVar ptrVar+ return (ptr, putMVar doneVar () >> readMVar releasedVar)+
+ src/Data/Array/Accelerate/LLVM/PTX/Execute/Stream.hs view
@@ -0,0 +1,174 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE MagicHash #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+-- |+-- Module : Data.Array.Accelerate.LLVM.PTX.Execute.Stream+-- Copyright : [2014..2020] The Accelerate Team+-- License : BSD3+--+-- Maintainer : Trevor L. McDonell <trevor.mcdonell@gmail.com>+-- Stability : experimental+-- Portability : non-portable (GHC extensions)+--++module Data.Array.Accelerate.LLVM.PTX.Execute.Stream (++ Reservoir, new,+ Stream, create, destroy, streaming,++) where++import Data.Array.Accelerate.Lifetime+import qualified Data.Array.Accelerate.Array.Remote.LRU as Remote++import Data.Array.Accelerate.LLVM.PTX.Array.Remote ( )+import Data.Array.Accelerate.LLVM.PTX.Execute.Event ( Event )+import Data.Array.Accelerate.LLVM.PTX.Target ( PTX(..) )+import Data.Array.Accelerate.LLVM.State+import qualified Data.Array.Accelerate.LLVM.PTX.Debug as Debug+import qualified Data.Array.Accelerate.LLVM.PTX.Execute.Event as Event+import Data.Array.Accelerate.LLVM.PTX.Execute.Stream.Reservoir as RSV++import Foreign.CUDA.Driver.Error+import qualified Foreign.CUDA.Driver.Stream as Stream++import Control.Exception+import Control.Monad+import Control.Monad.Reader+import Data.Text.Lazy.Builder+import Formatting+++-- | A 'Stream' represents an independent sequence of computations executed on+-- the GPU. Operations in different streams may be executed concurrently with+-- each other, but operations in the same stream can never overlap.+-- 'Data.Array.Accelerate.LLVM.PTX.Execute.Event.Event's can be used for+-- efficient cross-stream synchronisation.+--+type Stream = Lifetime Stream.Stream+++-- Executing operations in streams+-- -------------------------------++-- | Execute an operation in a unique execution stream. The (asynchronous)+-- result is passed to a second operation together with an event that will be+-- signalled once the operation is complete. The stream and event are released+-- after the second operation completes.+--+{-# INLINEABLE streaming #-}+streaming+ :: (Stream -> LLVM PTX a)+ -> (Event -> a -> LLVM PTX b)+ -> LLVM PTX b+streaming !action !after = do+ stream <- create+ first <- action stream+ end <- Event.waypoint stream+ final <- after end first+ liftIO $ do+ destroy stream+ Event.destroy end+ return final+++-- Primitive operations+-- --------------------++{--+-- | Delete all execution streams from the reservoir+--+{-# INLINEABLE flush #-}+flush :: Context -> Reservoir -> IO ()+flush !Context{..} !ref = do+ mc <- deRefWeak weakContext+ case mc of+ Nothing -> message "delete reservoir/dead context"+ Just ctx -> do+ message "flush reservoir"+ old <- swapMVar ref Seq.empty+ bracket_ (CUDA.push ctx) CUDA.pop $ Seq.mapM_ Stream.destroy old+--}+++-- | Create a CUDA execution stream. If an inactive stream is available for use,+-- use that, otherwise generate a fresh stream.+--+-- Note: [Finalising execution streams]+--+-- We don't actually ensure that the stream has executed all of its operations+-- to completion before attempting to return it to the reservoir for reuse.+-- Doing so increases overhead of the LLVM RTS due to 'forkIO', and consumes CPU+-- time as 'Stream.block' busy-waits for the stream to complete. It is quicker+-- to optimistically return the streams to the end of the reservoir immediately,+-- and just check whether the stream is done before reusing it.+--+-- > void . forkIO $ do+-- > Stream.block stream+-- > modifyMVar_ ref $ \rsv -> return (rsv Seq.|> stream)+--+{-# INLINEABLE create #-}+create :: LLVM PTX Stream+create = do+ PTX{..} <- asks llvmTarget+ s <- create'+ stream <- liftIO $ newLifetime s+ liftIO $ addFinalizer stream (RSV.insert ptxStreamReservoir s)+ return stream++create' :: LLVM PTX Stream.Stream+create' = do+ PTX{..} <- asks llvmTarget+ ms <- attempt "create/reservoir" (liftIO $ RSV.malloc ptxStreamReservoir)+ `orElse`+ attempt "create/new" (liftIO . catchOOM $ Stream.create [])+ `orElse` do+ Remote.reclaim ptxMemoryTable+ liftIO $ do+ message "create/new: failed (purging)"+ catchOOM $ Stream.create []+ case ms of+ Just s -> return s+ Nothing -> liftIO $ do+ message "create/new: failed (non-recoverable)"+ throwIO (ExitCode OutOfMemory)++ where+ catchOOM :: IO a -> IO (Maybe a)+ catchOOM it =+ liftM Just it `catch` \e -> case e of+ ExitCode OutOfMemory -> return Nothing+ _ -> throwIO e++ attempt :: MonadIO m => Builder -> m (Maybe a) -> m (Maybe a)+ attempt msg ea = do+ ma <- ea+ case ma of+ Nothing -> return Nothing+ Just a -> do message builder msg+ return (Just a)++ orElse :: MonadIO m => m (Maybe a) -> m (Maybe a) -> m (Maybe a)+ orElse ea eb = do+ ma <- ea+ case ma of+ Just a -> return (Just a)+ Nothing -> eb+++-- | Merge a stream back into the reservoir. This must only be done once all+-- pending operations in the stream have completed.+--+{-# INLINEABLE destroy #-}+destroy :: Stream -> IO ()+destroy = finalize+++-- Debug+-- -----++{-# INLINE message #-}+message :: MonadIO m => Format (m ()) a -> a+message fmt = Debug.traceM Debug.dump_sched ("stream: " % fmt)+
+ src/Data/Array/Accelerate/LLVM/PTX/Execute/Stream.hs-boot view
@@ -0,0 +1,32 @@+-- |+-- Module : Data.Array.Accelerate.LLVM.PTX.Execute.Stream-boot+-- Copyright : [2016..2020] The Accelerate Team+-- License : BSD3+--+-- Maintainer : Trevor L. McDonell <trevor.mcdonell@gmail.com>+-- Stability : experimental+-- Portability : non-portable (GHC extensions)+--++module Data.Array.Accelerate.LLVM.PTX.Execute.Stream (++ Stream,+ streaming,++) where++import Data.Array.Accelerate.Lifetime ( Lifetime )+import Data.Array.Accelerate.LLVM.State ( LLVM )+import Data.Array.Accelerate.LLVM.PTX.Target ( PTX )+import {-# SOURCE #-} Data.Array.Accelerate.LLVM.PTX.Execute.Event++import qualified Foreign.CUDA.Driver.Stream as Stream+++type Stream = Lifetime Stream.Stream++streaming+ :: (Stream -> LLVM PTX a)+ -> (Event -> a -> LLVM PTX b)+ -> LLVM PTX b+
+ src/Data/Array/Accelerate/LLVM/PTX/Execute/Stream/Reservoir.hs view
@@ -0,0 +1,98 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE OverloadedStrings #-}+-- |+-- Module : Data.Array.Accelerate.LLVM.PTX.Execute.Stream.Reservoir+-- Copyright : [2016..2020] The Accelerate Team+-- License : BSD3+--+-- Maintainer : Trevor L. McDonell <trevor.mcdonell@gmail.com>+-- Stability : experimental+-- Portability : non-portable (GHC extensions)+--++module Data.Array.Accelerate.LLVM.PTX.Execute.Stream.Reservoir (++ Reservoir,+ new, malloc, insert,++) where++import Data.Array.Accelerate.LLVM.PTX.Context ( Context )+import qualified Data.Array.Accelerate.LLVM.PTX.Debug as Debug++import Control.Concurrent.MVar+import Data.Sequence ( Seq )+import Formatting+import qualified Data.Sequence as Seq+import qualified Foreign.CUDA.Driver.Stream as Stream+++-- | The reservoir is a place to store CUDA execution streams that are currently+-- inactive. When a new stream is requested one is provided from the reservoir+-- if available, otherwise a fresh execution stream is created.+--+type Reservoir = MVar (Seq Stream.Stream)+++-- | Generate a new empty reservoir. It is not necessary to pre-populate it with+-- any streams because stream creation does not cause a device synchronisation.+--+-- Additionally, we do not need to finalise any of the streams. A reservoir is+-- tied to a specific execution context, so when the reservoir dies it is+-- because the PTX state and contained CUDA context have died, so there is+-- nothing more to do.+--+{-# INLINEABLE new #-}+new :: Context -> IO Reservoir+new _ctx = newMVar Seq.empty+++-- | Retrieve an execution stream from the reservoir, if one is available.+--+-- Since we put streams back onto the reservoir once we have finished adding+-- work to them, not once they have completed execution of the tasks, we must+-- check for one which has actually completed.+--+-- See note: [Finalising execution streams]+--+{-# INLINEABLE malloc #-}+malloc :: Reservoir -> IO (Maybe Stream.Stream)+malloc !ref =+ modifyMVar ref (search Seq.empty)+ where+ -- scan through the streams in the reservoir looking for the first inactive+ -- one. Optimistically adding the streams to the end of the reservoir as+ -- soon as we stop assigning new work to them (c.f. async), and just+ -- checking they have completed before reusing them, is quicker than having+ -- a finaliser thread block until completion before retiring them.+ --+ search !acc !rsv =+ case Seq.viewl rsv of+ Seq.EmptyL -> return (acc, Nothing)+ s Seq.:< ss -> do+ done <- Stream.finished s+ case done of+ True -> return (acc Seq.>< ss, Just s)+ False -> search (acc Seq.|> s) ss+++-- | Add a stream to the reservoir+--+{-# INLINEABLE insert #-}+insert :: Reservoir -> Stream.Stream -> IO ()+insert !ref !stream = do+ message ("stash stream " % formatStream) stream+ modifyMVar_ ref $ \rsv -> return (rsv Seq.|> stream)+++-- Debug+-- -----++{-# INLINE message #-}+message :: Format (IO ()) a -> a+message fmt = Debug.traceM Debug.dump_sched ("stream: " % fmt)++{-# INLINE formatStream #-}+formatStream :: Format r (Stream.Stream -> r)+formatStream = later $ \(Stream.Stream s) -> bformat shown s+
+ src/Data/Array/Accelerate/LLVM/PTX/Foreign.hs view
@@ -0,0 +1,88 @@+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TypeApplications #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}+-- |+-- Module : Data.Array.Accelerate.LLVM.PTX.Foreign+-- Copyright : [2016..2020] The Accelerate Team+-- License : BSD3+--+-- Maintainer : Trevor L. McDonell <trevor.mcdonell@gmail.com>+-- Stability : experimental+-- Portability : non-portable (GHC extensions)+--++module Data.Array.Accelerate.LLVM.PTX.Foreign (++ -- Foreign functions+ ForeignAcc(..),+ ForeignExp(..),++ -- useful re-exports+ LLVM,+ PTX(..),+ Context(..),+ liftIO,+ withDevicePtr,+ module Data.Array.Accelerate.LLVM.PTX.Array.Data,+ module Data.Array.Accelerate.LLVM.PTX.Execute.Async,+ module Data.Array.Accelerate.LLVM.PTX.Execute.Event,+ module Data.Array.Accelerate.LLVM.PTX.Execute.Stream,++) where++import qualified Data.Array.Accelerate.Sugar.Foreign as S++import Data.Array.Accelerate.LLVM.State+import Data.Array.Accelerate.LLVM.CodeGen.Sugar++import Data.Array.Accelerate.LLVM.Foreign+import Data.Array.Accelerate.LLVM.PTX.Array.Data+import Data.Array.Accelerate.LLVM.PTX.Array.Prim+import Data.Array.Accelerate.LLVM.PTX.Context+import Data.Array.Accelerate.LLVM.PTX.Execute.Async+import Data.Array.Accelerate.LLVM.PTX.Target+import Data.Array.Accelerate.LLVM.PTX.Execute.Stream ( Stream )+import Data.Array.Accelerate.LLVM.PTX.Execute.Event ( Event, waypoint, query )++import Control.Monad.State+import Data.Typeable+++instance Foreign PTX where+ foreignAcc (ff :: asm (a -> b))+ | Just Refl <- eqT @asm @ForeignAcc+ , ForeignAcc _ asm <- ff = Just asm+ | otherwise = Nothing++ foreignExp (ff :: asm (x -> y))+ | Just Refl <- eqT @asm @ForeignExp+ , ForeignExp _ asm <- ff = Just asm+ | otherwise = Nothing++instance S.Foreign ForeignAcc where+ strForeign (ForeignAcc s _) = s++instance S.Foreign ForeignExp where+ strForeign (ForeignExp s _) = s+++-- Foreign functions in the PTX backend.+--+data ForeignAcc f where+ ForeignAcc :: String+ -> (a -> Par PTX (Future b))+ -> ForeignAcc (a -> b)++-- Foreign expressions in the PTX backend.+--+data ForeignExp f where+ ForeignExp :: String+ -> IRFun1 PTX () (x -> y)+ -> ForeignExp (x -> y)++deriving instance Typeable ForeignAcc+deriving instance Typeable ForeignExp+
+ src/Data/Array/Accelerate/LLVM/PTX/Link.hs view
@@ -0,0 +1,144 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TypeFamilies #-}+{-# OPTIONS_GHC -Wno-orphans #-}+-- |+-- Module : Data.Array.Accelerate.LLVM.PTX.Link+-- Copyright : [2017..2020] The Accelerate Team+-- License : BSD3+--+-- Maintainer : Trevor L. McDonell <trevor.mcdonell@gmail.com>+-- Stability : experimental+-- Portability : non-portable (GHC extensions)+--++module Data.Array.Accelerate.LLVM.PTX.Link (++ module Data.Array.Accelerate.LLVM.Link,+ ExecutableR(..), FunctionTable(..), Kernel(..), ObjectCode,+ linkFunctionQ,++) where++import Data.Array.Accelerate.Lifetime++import Data.Array.Accelerate.LLVM.Link+import Data.Array.Accelerate.LLVM.State++import Data.Array.Accelerate.LLVM.PTX.Analysis.Launch+import Data.Array.Accelerate.LLVM.PTX.Compile+import Data.Array.Accelerate.LLVM.PTX.Context+import Data.Array.Accelerate.LLVM.PTX.Link.Cache+import Data.Array.Accelerate.LLVM.PTX.Link.Object+import Data.Array.Accelerate.LLVM.PTX.Target+import qualified Data.Array.Accelerate.LLVM.PTX.Debug as Debug++import qualified Foreign.CUDA.Analysis as CUDA+import qualified Foreign.CUDA.Driver as CUDA++import Control.Monad.Reader+import Data.ByteString.Short.Char8 ( ShortByteString, unpack )+import Formatting+import Foreign.Ptr+import Data.Array.Accelerate.TH.Compat+import qualified Data.ByteString as B+import qualified Data.ByteString.Unsafe as B+import Prelude as P hiding ( lookup )+++instance Link PTX where+ data ExecutableR PTX = PTXR { ptxExecutable :: {-# UNPACK #-} !(Lifetime FunctionTable)+ }+ linkForTarget = link+++-- | Load the generated object code into the current CUDA context.+--+link :: ObjectR PTX -> LLVM PTX (ExecutableR PTX)+link (ObjectR uid cfg objFname) = do+ target <- asks llvmTarget+ cache <- asks ptxKernelTable+ funs <- liftIO $ dlsym uid cache $ do+ -- Load the SASS object code into the current CUDA context+ obj <- B.readFile objFname+ jit <- B.unsafeUseAsCString obj $ \p -> CUDA.loadDataFromPtrEx (castPtr p) []+ let mdl = CUDA.jitModule jit++ -- Extract the kernel functions+ nm <- FunctionTable `fmap` mapM (uncurry (linkFunction mdl)) cfg+ oc <- newLifetime mdl++ -- Finalise the module by unloading it from the CUDA context+ addFinalizer oc $ do+ Debug.traceM Debug.dump_ld ("ld: unload module: " % formatFunctionTable) nm+ contextFinalizeResource (ptxContext target) $+ withContext (ptxContext target) (CUDA.unload mdl)++ return (nm, oc)+ --+ return $! PTXR funs+++-- | Extract the named function from the module and package into a Kernel+-- object, which includes meta-information on resource usage.+--+-- If we are in debug mode, print statistics on kernel resource usage, etc.+--+linkFunction+ :: CUDA.Module -- the compiled module+ -> ShortByteString -- __global__ entry function name+ -> LaunchConfig -- launch configuration for this global function+ -> IO Kernel+linkFunction mdl name configure =+ fst `fmap` linkFunctionQ mdl name configure++linkFunctionQ+ :: CUDA.Module+ -> ShortByteString+ -> LaunchConfig+ -> IO (Kernel, CodeQ (Int -> Int))+linkFunctionQ mdl name configure = do+ f <- CUDA.getFun mdl name+ regs <- CUDA.requires f CUDA.NumRegs+ ssmem <- CUDA.requires f CUDA.SharedSizeBytes+ cmem <- CUDA.requires f CUDA.ConstSizeBytes+ lmem <- CUDA.requires f CUDA.LocalSizeBytes+ maxt <- CUDA.requires f CUDA.MaxKernelThreadsPerBlock++ let+ (occ, cta, grid, dsmem, gridQ) = configure maxt regs ssmem++ msg1 = bformat ("kernel function " % squoted string % " used " % int % " registers, " % int % " bytes smem, " % int % " bytes lmem, " % int % " bytes cmem")+ (unpack name) regs (ssmem + dsmem) lmem cmem++ msg2 = bformat ("multiprocessor occupancy " % fixed 1 % "% : " % int % " threads over " % int % " warps in " % int % " blocks")+ (CUDA.occupancy100 occ)+ (CUDA.activeThreads occ)+ (CUDA.activeWarps occ)+ (CUDA.activeThreadBlocks occ)++ Debug.traceM Debug.dump_cc ("cc: " % builder % "\n " % builder) msg1 msg2+ return (Kernel name f dsmem cta grid, gridQ)+++{--+-- | Extract the names of the function definitions from the module.+--+-- Note: [Extracting global function names]+--+-- It is important to run this on the module given to us by code generation.+-- After combining modules with 'libdevice', extra function definitions,+-- corresponding to basic maths operations, will be added to the module. These+-- functions will not be callable as __global__ functions.+--+-- The list of names will be exported in the order that they appear in the+-- module.+--+globalFunctions :: [Definition] -> [String]+globalFunctions defs =+ [ n | GlobalDefinition Function{..} <- defs+ , not (null basicBlocks)+ , let Name n = name+ ]+--}+
+ src/Data/Array/Accelerate/LLVM/PTX/Link/Cache.hs view
@@ -0,0 +1,22 @@+-- |+-- Module : Data.Array.Accelerate.LLVM.PTX.Link.Cache+-- Copyright : [2017..2020] The Accelerate Team+-- License : BSD3+--+-- Maintainer : Trevor L. McDonell <trevor.mcdonell@gmail.com>+-- Stability : experimental+-- Portability : non-portable (GHC extensions)+--++module Data.Array.Accelerate.LLVM.PTX.Link.Cache (++ KernelTable,+ LC.new, LC.dlsym,++) where++import Data.Array.Accelerate.LLVM.PTX.Link.Object+import qualified Data.Array.Accelerate.LLVM.Link.Cache as LC++type KernelTable = LC.LinkCache FunctionTable ObjectCode+
+ src/Data/Array/Accelerate/LLVM/PTX/Link/Object.hs view
@@ -0,0 +1,46 @@+-- |+-- Module : Data.Array.Accelerate.LLVM.PTX.Link.Object+-- Copyright : [2017..2020] The Accelerate Team+-- License : BSD3+--+-- Maintainer : Trevor L. McDonell <trevor.mcdonell@gmail.com>+-- Stability : experimental+-- Portability : non-portable (GHC extensions)+--++module Data.Array.Accelerate.LLVM.PTX.Link.Object+ where++import Data.Array.Accelerate.Lifetime+import Data.ByteString.Short.Char8 ( ShortByteString, unpack )+import Data.List+import Formatting+import qualified Foreign.CUDA.Driver as CUDA+++-- | The kernel function table is a list of the kernels implemented by a given+-- CUDA device module+--+data FunctionTable = FunctionTable { functionTable :: [Kernel] }+data Kernel = Kernel+ { kernelName :: {-# UNPACK #-} !ShortByteString+ , kernelFun :: {-# UNPACK #-} !CUDA.Fun+ , kernelSharedMemBytes :: {-# UNPACK #-} !Int+ , kernelThreadBlockSize :: {-# UNPACK #-} !Int+ , kernelThreadBlocks :: (Int -> Int)+ }++instance Show FunctionTable where+ showsPrec _ f+ = showString "<<"+ . showString (intercalate "," [ unpack (kernelName k) | k <- functionTable f ])+ . showString ">>"++formatFunctionTable :: Format r (FunctionTable -> r)+formatFunctionTable = later $ \f ->+ bformat (angled (angled (commaSep string))) [ unpack (kernelName k) | k <- functionTable f ]++-- | Object code consists of executable code in the device address space+--+type ObjectCode = Lifetime CUDA.Module+
+ src/Data/Array/Accelerate/LLVM/PTX/Pool.hs view
@@ -0,0 +1,193 @@+{-# LANGUAGE ScopedTypeVariables #-}+-- |+-- Module : Data.Array.Accelerate.LLVM.PTX.Pool+-- Copyright : [2017..2020] The Accelerate Team+-- License : BSD3+--+-- Maintainer : Trevor L. McDonell <trevor.mcdonell@gmail.com>+-- Stability : experimental+-- Portability : non-portable (GHC extensions)+--++module Data.Array.Accelerate.LLVM.PTX.Pool (++ Pool,+ create, with, take, put,+ unsafeWith,++) where++import Control.Concurrent.MVar+import Control.Exception+import Data.Maybe+import System.IO.Unsafe+import Prelude hiding ( take )++import Data.Sequence ( Seq )+import qualified Data.Sequence as Seq+++-- | An item pool+--+-- Based on 'Control.Concurrent.QSem'+--+data Pool a = Pool {-# UNPACK #-} !(MVar ([a], Seq (MVar a)))++-- The semaphore state (as, bs):+--+-- * as the currently available resources+--+-- * bs is the queue of blocked threads, stored in FIFO order. New threads are+-- queued onto the right, and threads are woken up from the left.+--+-- A blocked thread is represented by an empty (MVar a). To unblock the thread,+-- we give it a resource via its MVar.+--+-- A thread can deque itself by also putting () into the MVar, which it must do+-- if it receives an exception while blocked in 'take'. This means that when+-- unblocking a thread in 'put' we must first check whether the MVar is already+-- full; the MVar lock on the semaphore itself resolves race conditions between+-- put and a thread attempting to deque itself.+--++-- | Build a new pool with the supplied initial quantity.+--+create :: [a] -> IO (Pool a)+create initial =+ Pool <$> newMVar (initial, Seq.empty)++-- | Wait for a unit of the resource to become available, and run the supplied+-- action given that resource.+--+with :: Pool a -> (a -> IO b) -> IO b+with pool action =+ bracket (take pool) (put pool) action++unsafeWith :: Pool a -> (a -> b) -> b+unsafeWith pool action =+ unsafePerformIO $ with pool (evaluate . action)+++-- | Wait for an item from the pool to become available.+--+take :: Pool a -> IO a+take (Pool ref) =+ mask_ $ do+ (r, bs) <- takeMVar ref+ case r of+ [] -> do+ b <- newEmptyMVar+ putMVar ref (r, bs Seq.|> b)+ wait b++ (a:as) -> do+ putMVar ref (as, bs)+ return a+ where+ wait b =+ takeMVar b `catch` \(e :: SomeException) ->+ uninterruptibleMask_ $ do -- Note [signal interruptible]+ r <- takeMVar ref+ ma <- tryTakeMVar b+ r' <- case ma of+ Just a -> signal a r -- make sure we don't lose the resource+ Nothing -> do putMVar b (throw e) -- unblock the thread??+ return r+ putMVar ref r'+ throwIO e+++-- | Return a unit of the resource to the pool.+--+put :: Pool a -> a -> IO ()+put (Pool ref) a =+ uninterruptibleMask_ $ do -- Note [signal interruptible]+ r <- takeMVar ref+ r' <- signal a r+ putMVar ref r'+++-- Note [signal interruptible]+--+-- If we have:+--+-- > bracket take put (...)+--+-- and an exception arrives at the put, then we must not lose the resource. The+-- put is masked by bracket, but taking the MVar might block, and so it would be+-- interruptible. Hence we need an uninterruptibleMask here.+--+signal :: a -> ([a], Seq (MVar a)) -> IO ([a], Seq (MVar a))+signal a (as, blocked) =+ if null as+ then loop blocked -- there may be waiting threads; wake one up+ else return (a:as, blocked) -- nobody waiting+ where+ loop blocked' =+ case Seq.viewl blocked' of+ Seq.EmptyL -> return ([a], Seq.empty)+ b Seq.:< bs -> do+ r <- tryPutMVar b a+ if r then return ([], bs) -- we woke up a thread+ else loop bs -- already unblocked; drop from the queue++{--+-- | An item pool+--+data Pool a = Pool {-# UNPACK #-} !(MVar (NonEmpty a))+++-- | Create a new pooled resource containing the given items+--+create :: [a] -> IO (Pool a)+create [] = Pool <$> newEmptyMVar+create (x:xs) = Pool <$> newMVar (x :| xs)+++-- | Execute an operation using an item from the pool. Like 'take', the function+-- blocks until one becomes available.+--+with :: Pool a -> (a -> IO b) -> IO b+with pool action =+ bracket (take pool) (put pool) action++unsafeWith :: Pool a -> (a -> b) -> b+unsafeWith pool action =+ unsafePerformIO $ with pool (pure . action)+++-- | Take an item from the pool. This will block until one is available.+--+take :: Pool a -> IO a+take (Pool ref) = do+ x :| xs <- takeMVar ref -- blocking+ case xs of+ [] -> return () -- leave the pool empty; subsequent 'take's will block+ (a:as) -> mask_ $ do r <- tryTakeMVar ref+ case r of+ Nothing -> putMVar ref (a :| as)+ Just (b :| bs) -> putMVar ref (a :| b : bs ++ as)+ return x+++-- | Return an item back to the pool for later reuse. This should be+-- a non-blocking operation.+--+put :: Pool a -> a -> IO ()+put (Pool ref) a =+ mask_ $ do+ it <- tryTakeMVar ref+ case it of+ Just (b :| bs) -> putMVar ref (a :| b : bs)+ Nothing -> putMVar ref (a :| [])+++#if __GLASGOW_HASKELL__ < 800+-- | Non-empty (and non-strict) list type.+--+infixr 5 :|+data NonEmpty a = a :| [a]+ deriving ( Eq, Ord, Show, Read )+#endif+--}+
+ src/Data/Array/Accelerate/LLVM/PTX/State.hs view
@@ -0,0 +1,176 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+-- |+-- Module : Data.Array.Accelerate.LLVM.PTX.State+-- Copyright : [2014..2020] The Accelerate Team+-- License : BSD3+--+-- Maintainer : Trevor L. McDonell <trevor.mcdonell@gmail.com>+-- Stability : experimental+-- Portability : non-portable (GHC extensions)+--++module Data.Array.Accelerate.LLVM.PTX.State (++ evalPTX,+ createTargetForDevice, createTargetFromContext,++ Pool(..),+ withPool, unsafeWithPool,+ defaultTarget,+ defaultTargetPool,++) where++import Data.Array.Accelerate.Error++import Data.Array.Accelerate.LLVM.State+import Data.Array.Accelerate.LLVM.PTX.Target+import qualified Data.Array.Accelerate.LLVM.PTX.Array.Table as MT+import qualified Data.Array.Accelerate.LLVM.PTX.Context as CT+import qualified Data.Array.Accelerate.LLVM.PTX.Debug as Debug+import qualified Data.Array.Accelerate.LLVM.PTX.Execute.Stream as ST+import qualified Data.Array.Accelerate.LLVM.PTX.Link.Cache as LC+import qualified Data.Array.Accelerate.LLVM.PTX.Pool as Pool++import Foreign.CUDA.Driver.Error+import qualified Foreign.CUDA.Driver as CUDA+import qualified Foreign.CUDA.Driver.Context as Context++import Control.Concurrent+import Control.Exception ( try, catch )+import Data.Maybe ( fromMaybe, catMaybes )+import Formatting+import System.Environment ( lookupEnv )+import System.IO.Unsafe ( unsafePerformIO, unsafeInterleaveIO )+import Text.Read ( readMaybe )+++-- | Execute a PTX computation+--+evalPTX :: PTX -> LLVM PTX a -> IO a+evalPTX ptx acc =+ CT.withContext (ptxContext ptx) (evalLLVM ptx acc)+ `catch`+ \e -> internalError shown (e :: CUDAException)+++-- | Create a new PTX execution target for the given device+--+createTargetForDevice+ :: CUDA.Device+ -> CUDA.DeviceProperties+ -> [CUDA.ContextFlag]+ -> IO PTX+createTargetForDevice dev prp flags = do+ raw <- CUDA.create dev flags+ ptx <- createTarget dev prp raw+ _ <- CUDA.pop+ return ptx+++-- | Create a PTX execute target for the given device context+--+createTargetFromContext+ :: CUDA.Context+ -> IO PTX+createTargetFromContext raw = do+ dev <- Context.device+ prp <- CUDA.props dev+ createTarget dev prp raw+++-- | Create a PTX execution target+--+createTarget+ :: CUDA.Device+ -> CUDA.DeviceProperties+ -> CUDA.Context+ -> IO PTX+createTarget dev prp raw = do+ ctx <- CT.raw dev prp raw+ mt <- MT.new ctx+ lc <- LC.new+ st <- ST.new ctx+ return $! PTX ctx mt lc st+++-- Shared execution contexts+-- -------------------------++-- In order to implement runN, we need to keep track of all available contexts,+-- as well as the managed resource pool.+--+data Pool a = Pool+ { managed :: {-# UNPACK #-} !(Pool.Pool a)+ , unmanaged :: [a]+ }++-- Evaluate a thing given an execution context from the default pool+--+withPool :: Pool a -> (a -> IO b) -> IO b+withPool p = Pool.with (managed p)++unsafeWithPool :: Pool a -> (a -> b) -> b+unsafeWithPool p = Pool.unsafeWith (managed p)+++-- Top-level mutable state+-- -----------------------+--+-- It is important to keep some information alive for the entire run of the+-- program, not just a single execution. These tokens use 'unsafePerformIO' to+-- ensure they are executed only once, and reused for subsequent invocations.+--++-- | Select a device from the default pool.+--+{-# NOINLINE defaultTarget #-}+defaultTarget :: PTX+defaultTarget = case unmanaged defaultTargetPool of+ ptx : _ -> ptx+ _ -> error "impossible" -- ensured by defaultTargetPool++-- | Create a shared resource pool of the available CUDA devices.+--+-- This globally shared resource pool is auto-initialised on startup. It will+-- consist of every currently available device, or those specified by the value+-- of the environment variable @ACCELERATE_LLVM_PTX_DEVICES@ (as a list of+-- device ordinals).+--+{-# NOINLINE defaultTargetPool #-}+defaultTargetPool :: Pool PTX+defaultTargetPool = unsafePerformIO $! do+ Debug.traceM Debug.dump_gc "gc: initialise default PTX pool"+ CUDA.initialise []++ -- Figure out which GPUs we should put into the execution pool+ --+ ngpu <- CUDA.count+ menv <- (readMaybe =<<) <$> lookupEnv "ACCELERATE_LLVM_PTX_DEVICES"++ let ids = fromMaybe [0..ngpu-1] menv++ -- Spin up the GPU at the given ordinal.+ --+ boot :: Int -> IO (Maybe PTX)+ boot i = unsafeInterleaveIO $ runInBoundThread $ do+ dev <- CUDA.device i+ prp <- CUDA.props dev+ r <- try $ createTargetForDevice dev prp [CUDA.SchedAuto]+ case r of+ Right ptx -> return (Just ptx)+ Left (e::CUDAException) -> do+ Debug.traceM Debug.dump_gc ("gc: failed to initialise device " % int % ": " % shown) i e+ return Nothing++ -- Create the pool from the available devices, which get spun-up lazily as+ -- required (due to the implementation of the Pool, we will look ahead by one+ -- each time one device is requested).+ --+ devices <- catMaybes <$> mapM boot ids+ if null devices+ then error "No CUDA-capable devices are available"+ else Pool <$> Pool.create devices+ <*> return devices+
+ src/Data/Array/Accelerate/LLVM/PTX/Target.hs view
@@ -0,0 +1,83 @@+{-# LANGUAGE EmptyDataDecls #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-}+-- |+-- Module : Data.Array.Accelerate.LLVM.PTX.Target+-- Copyright : [2014..2020] The Accelerate Team+-- License : BSD3+--+-- Maintainer : Trevor L. McDonell <trevor.mcdonell@gmail.com>+-- Stability : experimental+-- Portability : non-portable (GHC extensions)+--++module Data.Array.Accelerate.LLVM.PTX.Target (++ module Data.Array.Accelerate.LLVM.Target,+ module Data.Array.Accelerate.LLVM.PTX.Target,++) where++-- accelerate+import Data.Array.Accelerate.Error++import Data.Array.Accelerate.LLVM.Extra+import Data.Array.Accelerate.LLVM.Target++import Data.Array.Accelerate.LLVM.PTX.Array.Table ( MemoryTable )+import Data.Array.Accelerate.LLVM.PTX.Context ( Context, deviceProperties, deviceName )+import Data.Array.Accelerate.LLVM.PTX.Execute.Stream.Reservoir ( Reservoir )+import Data.Array.Accelerate.LLVM.PTX.Link.Cache ( KernelTable )++-- CUDA+import Foreign.CUDA.Analysis.Device ( DeviceProperties )++-- standard library+import Data.ByteString.Short ( ShortByteString )+import Data.Primitive.ByteArray+import Foreign.C.String+import Foreign.Ptr+++-- | The PTX execution target for NVIDIA GPUs.+--+-- The execution target carries state specific for the current execution+-- context. The data here --- device memory and execution streams --- are+-- implicitly tied to this CUDA execution context.+--+-- Don't store anything here that is independent of the context, for example+-- state related to [persistent] kernel caching should _not_ go here.+--+data PTX = PTX {+ ptxContext :: {-# UNPACK #-} !Context+ , ptxMemoryTable :: {-# UNPACK #-} !MemoryTable+ , ptxKernelTable :: {-# UNPACK #-} !KernelTable+ , ptxStreamReservoir :: {-# UNPACK #-} !Reservoir+ }++instance Target PTX where+ targetTriple = Just ptxTargetTriple+ targetDataLayout = Nothing+++-- | Extract the properties of the device the current PTX execution state is+-- executing on.+--+ptxDeviceProperties :: PTX -> DeviceProperties+ptxDeviceProperties = deviceProperties . ptxContext++-- | Extract the name of the device of the current execution context+--+ptxDeviceName :: PTX -> CString+ptxDeviceName = castPtr . byteArrayContents . deviceName . ptxContext+++-- | String that describes the target host.+--+ptxTargetTriple :: HasCallStack => ShortByteString+ptxTargetTriple =+ case bitSize (undefined::Int) of+ 32 -> "nvptx-nvidia-cuda"+ 64 -> "nvptx64-nvidia-cuda"+ _ -> internalError "I don't know what architecture I am"
+ src/GHC/Heap/NormalForm.hs view
@@ -0,0 +1,93 @@+-- |+-- Module : GHC.Heap.NormalForm+-- Copyright : [2020] The Accelerate Team+-- License : BSD3+--+-- Maintainer : Trevor L. McDonell <trevor.mcdonell@gmail.com>+-- Stability : experimental+-- Portability : non-portable (GHC extensions)+--+-- https://github.com/input-output-hk/cardano-prelude/blob/96e8dcb29dc3c29eee99c0d020152fad6071af6d/src/Cardano/Prelude/GHC/Heap/NormalForm.hs+--+-- This code has been adapted from the module "GHC.AssertNF" of the package+-- <http://hackage.haskell.org/package/ghc-heap-view ghc-heap-view>+-- (<https://github.com/nomeata/ghc-heap-view GitHub>) authored by+-- Joachim Breitner.+--+-- To avoid space leaks and unwanted evaluation behaviour, the programmer+-- might want his data to be fully evaluated at certain positions in the+-- code. This can be enforced, for example, by ample use of+-- "Control.DeepSeq", but this comes at a cost.+--+-- Experienced users hence use 'Control.DeepSeq.deepseq' only to find out+-- about the existence of space leaks and optimize their code to not create+-- the thunks in the first place, until the code no longer shows better+-- performance with 'deepseq'.+--++module GHC.Heap.NormalForm (++ isHeadNormalForm,+ isNormalForm,++) where++import GHC.Exts.Heap++-- Everything is in normal form, unless it is a thunk explicitly marked as+-- such. Indirection are also considered to be in HNF.+--+isHeadNormalForm :: Closure -> IO Bool+isHeadNormalForm c = do+ case c of+ ThunkClosure{} -> return False+ APClosure{} -> return False+ SelectorClosure{} -> return False+ BCOClosure{} -> return False+ _ -> return True++-- | The function 'isNormalForm' checks whether its argument is fully evaluated+-- and deeply evaluated.+--+-- NOTE 1: If you want to override the behaviour of 'isNormalForm' for specific+-- types (in particular, for specific types that may be /nested/ somewhere+-- inside the @a@), consider using+-- 'Cardano.Prelude.GHC.Heap.NormalForm.Classy.noUnexpectedThunks' instead.+--+-- NOTE 2: The normal form check can be quite brittle, especially with @-O0@.+-- For example, writing something like+--+-- > let !(Value x) = ... in ....+--+-- might translate to+--+-- > let !.. = ... in ... (case ... of Value x -> x)+--+-- which would trivially be @False@. In general, 'isNormalForm' should probably+-- only be used with @-O1@, but even then the answer may still depend on+-- internal decisions made by ghc during compilation.+--+isNormalForm :: a -> IO Bool+isNormalForm x = isNormalFormBoxed (asBox x)++isNormalFormBoxed :: Box -> IO Bool+isNormalFormBoxed b = do+ c <- getBoxedClosureData b+ nf <- isHeadNormalForm c+ if nf+ then do+ c' <- getBoxedClosureData b+ allM isNormalFormBoxed (allClosures c')+ else do+ return False++-- From Control.Monad.Loops in monad-loops+--+allM :: Monad m => (a -> m Bool) -> [a] -> m Bool+allM _ [] = return True+allM p (x : xs) = do+ q <- p x+ if q+ then allM p xs+ else return False+
+ src/System/Process/Extra.hs view
@@ -0,0 +1,48 @@+{-# LANGUAGE RecordWildCards #-}+-- |+-- Module : System.Process.Extra+-- Copyright : [2017..2020] The Accelerate Team+-- License : BSD3+--+-- Maintainer : Trevor L. McDonell <trevor.mcdonell@gmail.com>+-- Stability : experimental+-- Portability : non-portable (GHC extensions)+--++module System.Process.Extra+ where++-- standard library+import Control.Concurrent+import Control.Exception+import Foreign.C ( Errno(..), ePIPE )+import GHC.IO.Exception ( IOErrorType(..), IOException(..) )+++-- | Fork a thread while doing something else, but kill it if there's an+-- exception.+--+-- This is important because we want to kill the thread that is holding the+-- Handle lock, because when we clean up the process we try to close that+-- handle, which could otherwise deadlock.+--+-- Stolen from the 'process' package.+--+withForkWait :: IO () -> (IO () -> IO a) -> IO a+withForkWait async body = do+ waitVar <- newEmptyMVar :: IO (MVar (Either SomeException ()))+ mask $ \restore -> do+ tid <- forkIO $ try (restore async) >>= putMVar waitVar+ let wait = takeMVar waitVar >>= either throwIO return+ restore (body wait) `onException` killThread tid++ignoreSIGPIPE :: IO () -> IO ()+ignoreSIGPIPE =+ handle $ \e ->+ case e of+ IOError{..} | ResourceVanished <- ioe_type+ , Just ioe <- ioe_errno+ , Errno ioe == ePIPE+ -> return ()+ _ -> throwIO e+
+ test/nofib/Data/Array/Accelerate/LLVM/PTX/NoFib/RunQ.hs view
@@ -0,0 +1,31 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE TemplateHaskell #-}+module Data.Array.Accelerate.LLVM.PTX.NoFib.RunQ where++import qualified Data.Array.Accelerate as A+import qualified Data.Array.Accelerate.LLVM.PTX as GPU++import Test.Tasty+import Test.Tasty.HUnit+++-- WARNING: This module is duplicated (apart from Native/PTX) between the+-- accelerate-llvm-native and accelerate-llvm-ptx backends. This code is not+-- included in the main Accelerate nofib testsuite because of staging issues:+-- the test can only be defined after runQ is known, and runQ is only built+-- after the 'accelerate' package has already finished building. It would be+-- possible to deduplicate the little Accelerate program in there, but that was+-- not deemed worth the effort.+++test_runq :: TestTree+test_runq =+ testGroup "runQ"+ [ testCase "simple" test_simple ]++test_simple :: Assertion+test_simple = do+ let prog :: A.Vector Int -> A.Scalar Int+ !prog = $(GPU.runQ $ \a -> A.sum (A.map (+1) (a :: A.Acc (A.Vector Int))))+ let n = 10000+ prog (A.fromList (A.Z A.:. 10000) [1..]) @=? A.fromList A.Z [n * (n + 1) `div` 2 + n]
+ test/nofib/Main.hs view
@@ -0,0 +1,19 @@+-- |+-- Module : nofib-llvm-ptx+-- Copyright : [2017..2020] The Accelerate Team+-- License : BSD3+--+-- Maintainer : Trevor L. McDonell <trevor.mcdonell@gmail.com>+-- Stability : experimental+-- Portability : non-portable (GHC extensions)+--++module Main where++import Data.Array.Accelerate.Test.NoFib+import Data.Array.Accelerate.LLVM.PTX+import Data.Array.Accelerate.LLVM.PTX.NoFib.RunQ++main :: IO ()+main = nofib runN test_runq+