halide-haskell-0.0.2.0: src/Language/Halide/Target.hs
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
-- |
-- Module : Language.Halide.Target
-- Description : Compilation targets and their features
-- Copyright : (c) Tom Westerhout, 2023
--
-- This module defines a data type 'Target' that represents the compilation target.
-- This could be the host target, or a CUDA device with CUDA capability of at least 7,
-- or an OpenCL device, etc.
--
-- You would typically start with 'hostTarget' and then extend it using various 'TargetFeature's.
-- This can be done with the 'setFeature' function.
module Language.Halide.Target
( Target (..)
, hostTarget
, gpuTarget
, hostSupportsTargetDevice
, setFeature
, hasGpuFeature
, TargetFeature (..)
, DeviceAPI (..)
-- , targetFeatureForDeviceAPI
, deviceAPIForTarget
-- * Internal
, withCxxTarget
, testCUDA
, testOpenCL
)
where
import Data.Text (unpack)
import Foreign.ForeignPtr
import Foreign.Ptr (Ptr)
import GHC.IO (unsafePerformIO)
import Language.C.Inline qualified as C
import Language.C.Inline.Cpp.Exception qualified as C
import Language.C.Inline.Unsafe qualified as CU
import Language.Halide.Context
import Language.Halide.Type
import Language.Halide.Utils
import Prelude hiding (tail)
importHalide
-- | The compilation target.
--
-- This is the Haskell counterpart of [@Halide::Target@](https://halide-lang.org/docs/struct_halide_1_1_target.html).
newtype Target = Target (ForeignPtr CxxTarget)
instance Eq Target where
(==) target1 target2 =
toEnum . fromIntegral . unsafePerformIO $
withCxxTarget target1 $ \t1 ->
withCxxTarget target2 $ \t2 ->
[CU.exp| bool { *$(const Halide::Target* t1) == *$(const Halide::Target* t2) } |]
instance Show Target where
show target =
unpack . unsafePerformIO $ withCxxTarget target $ \t ->
peekAndDeleteCxxString
=<< [CU.exp| std::string* {
new std::string{$(const Halide::Target* t)->to_string()} } |]
-- | Return the target that Halide will use by default.
--
-- If the @HL_TARGET@ environment variable is set, it uses that. Otherwise, it
-- returns the target corresponding to the host machine.
hostTarget :: Target
hostTarget =
unsafePerformIO $
wrapCxxTarget
=<< [CU.exp| Halide::Target* { new Halide::Target{Halide::get_target_from_environment()} } |]
{-# NOINLINE hostTarget #-}
-- | Get the default GPU target. We first check for CUDA and then for OpenCL.
-- If neither of the two is usable, 'Nothing' is returned.
gpuTarget :: Maybe Target
gpuTarget
| hostSupportsTargetDevice cudaTarget = Just cudaTarget
| hostSupportsTargetDevice openCLTarget = Just openCLTarget
| otherwise = Nothing
where
openCLTarget = setFeature FeatureOpenCL hostTarget
cudaTarget = setFeature FeatureCUDA hostTarget
-- | Attempt to sniff whether a given 'Target' (and its implied 'DeviceAPI') is usable on the
-- current host.
--
-- __Note__ that a return value of @True@ does not guarantee that future usage of that device will
-- succeed; it is intended mainly as a simple diagnostic to allow early-exit when a desired device
-- is definitely not usable.
--
-- Also note that this call is __NOT threadsafe__, as it temporarily redirects various global
-- error-handling hooks in Halide.
hostSupportsTargetDevice
:: Target
-> Bool
-- ^ Whether the target appears to be usable
hostSupportsTargetDevice target =
unsafePerformIO . fmap (toEnum . fromIntegral) $
withCxxTarget target $ \t ->
[CU.exp| bool { Halide::host_supports_target_device(*$(Halide::Target* t)) } |]
-- | Add a feature to target.
setFeature
:: TargetFeature
-- ^ Feature to add
-> Target
-- ^ Initial target
-> Target
-- ^ New target
setFeature feature target = unsafePerformIO $
withCxxTarget target $ \t ->
wrapCxxTarget
=<< [CU.exp| Halide::Target* {
new Halide::Target{$(Halide::Target* t)->with_feature(
static_cast<Halide::Target::Feature>($(int f)))}
} |]
where
f = fromIntegral . fromEnum $ feature
-- | Return whether a GPU compute runtime is enabled.
--
-- Checks whether 'Language.Halide.Func.gpuBlocks' and similar are going to work.
--
-- For more info, see [@Target::has_gpu_feature@](https://halide-lang.org/docs/struct_halide_1_1_target.html#a22bf80aa6dc3a700c9732050d2341a80).
hasGpuFeature :: Target -> Bool
hasGpuFeature target =
unsafePerformIO . fmap (toEnum . fromIntegral) $
withCxxTarget target $ \t ->
[CU.exp| bool { $(Halide::Target* t)->has_gpu_feature() } |]
-- | An enum describing the type of device API.
--
-- This is the Haskell counterpart of [@Halide::DeviceAPI@](https://halide-lang.org/docs/namespace_halide.html#aa26c7f430d2b1c44ba3e1d3f6df2ba6e).
data DeviceAPI
= DeviceNone
| DeviceHost
| DeviceDefaultGPU
| DeviceCUDA
| DeviceOpenCL
| DeviceOpenGLCompute
| DeviceMetal
| DeviceHexagon
| DeviceHexagonDma
| DeviceD3D12Compute
deriving stock (Show, Eq, Ord)
instance Enum DeviceAPI where
fromEnum =
fromIntegral . \case
DeviceNone -> [CU.pure| int { static_cast<int>(Halide::DeviceAPI::None) } |]
DeviceHost -> [CU.pure| int { static_cast<int>(Halide::DeviceAPI::Host) } |]
DeviceDefaultGPU -> [CU.pure| int { static_cast<int>(Halide::DeviceAPI::Default_GPU) } |]
DeviceCUDA -> [CU.pure| int { static_cast<int>(Halide::DeviceAPI::CUDA) } |]
DeviceOpenCL -> [CU.pure| int { static_cast<int>(Halide::DeviceAPI::OpenCL) } |]
DeviceOpenGLCompute -> [CU.pure| int { static_cast<int>(Halide::DeviceAPI::OpenGLCompute) } |]
DeviceMetal -> [CU.pure| int { static_cast<int>(Halide::DeviceAPI::Metal) } |]
DeviceHexagon -> [CU.pure| int { static_cast<int>(Halide::DeviceAPI::Hexagon) } |]
DeviceHexagonDma -> [CU.pure| int { static_cast<int>(Halide::DeviceAPI::HexagonDma) } |]
DeviceD3D12Compute -> [CU.pure| int { static_cast<int>(Halide::DeviceAPI::D3D12Compute) } |]
toEnum k
| fromIntegral k == [CU.pure| int { static_cast<int>(Halide::DeviceAPI::None) } |] = DeviceNone
| fromIntegral k == [CU.pure| int { static_cast<int>(Halide::DeviceAPI::Host) } |] = DeviceHost
| fromIntegral k == [CU.pure| int { static_cast<int>(Halide::DeviceAPI::Default_GPU) } |] = DeviceDefaultGPU
| fromIntegral k == [CU.pure| int { static_cast<int>(Halide::DeviceAPI::CUDA) } |] = DeviceCUDA
| fromIntegral k == [CU.pure| int { static_cast<int>(Halide::DeviceAPI::OpenCL) } |] = DeviceOpenCL
| fromIntegral k == [CU.pure| int { static_cast<int>(Halide::DeviceAPI::OpenGLCompute) } |] = DeviceOpenGLCompute
| fromIntegral k == [CU.pure| int { static_cast<int>(Halide::DeviceAPI::Metal) } |] = DeviceMetal
| fromIntegral k == [CU.pure| int { static_cast<int>(Halide::DeviceAPI::Hexagon) } |] = DeviceHexagon
| fromIntegral k == [CU.pure| int { static_cast<int>(Halide::DeviceAPI::HexagonDma) } |] = DeviceHexagonDma
| fromIntegral k == [CU.pure| int { static_cast<int>(Halide::DeviceAPI::D3D12Compute) } |] = DeviceD3D12Compute
| fromIntegral k == [CU.pure| int { static_cast<int>(Halide::DeviceAPI::D3D12Compute) } |] = DeviceD3D12Compute
| otherwise = error $ "invalid DeviceAPI: " <> show k
wrapCxxTarget :: Ptr CxxTarget -> IO Target
wrapCxxTarget = fmap Target . newForeignPtr deleter
where
deleter = [C.funPtr| void deleteTarget(Halide::Target* p) { delete p; } |]
-- | Convert 'Target' into @Halide::Target*@ and use it in an 'IO' action.
withCxxTarget :: Target -> (Ptr CxxTarget -> IO a) -> IO a
withCxxTarget (Target fp) = withForeignPtr fp
deviceAPIForTarget :: Target -> DeviceAPI
deviceAPIForTarget target = unsafePerformIO $
withCxxTarget target $ \target' ->
toEnum . fromIntegral
<$> [CU.exp| int { static_cast<int>(Halide::get_default_device_api_for_target(
*$(Halide::Target* target'))) } |]
-- targetFeatureForDeviceAPI :: DeviceAPI -> Maybe TargetFeature
-- targetFeatureForDeviceAPI deviceAPI =
-- toFeature . unsafePerformIO $
-- [CU.block| int {
-- auto feature = Halide::target_feature_for_device_api(
-- static_cast<Halide::DeviceAPI>($(int api)));
-- return (feature == Halide::Target::FeatureEnd) ? (-1) : static_cast<int>(feature);
-- } |]
-- where
-- api = fromIntegral . fromEnum $ deviceAPI
-- toFeature n
-- | n > 0 = Just . toEnum . fromIntegral $ n
-- | otherwise = Nothing
-- | A test that tries to compile and run a Halide pipeline using 'FeatureCUDA'.
--
-- This is implemented fully in C++ to make sure that we test the installation
-- rather than our Haskell code.
--
-- On non-NixOS systems one should do the following:
--
-- > nixGLNvidia cabal repl --ghc-options='-fobject-code -O0'
-- > ghci> testCUDA
testCUDA :: IO ()
testCUDA = do
[C.throwBlock| void {
handle_halide_exceptions([](){
// Define a gradient function.
Halide::Func f;
Halide::Var x, y, xo, xi, yo, yi;
f(x, y) = x + y;
// Schedule f on the GPU in 16x16 tiles.
f.gpu_tile(x, y, xo, yo, xi, yi, 16, 16);
// Construct a target that uses the GPU.
Halide::Target target = Halide::get_host_target();
// Set CUDA as the GPU backend.
target.set_feature(Halide::Target::CUDA);
// Enable debugging so that you can see what CUDA API calls we do.
target.set_feature(Halide::Target::Debug);
// JIT-compile the pipeline.
f.compile_jit(target);
// Run it.
Halide::Buffer<int> result = f.realize({32, 32});
// Check correctness
for (int y = 0; y < result.height(); y++) {
for (int x = 0; x < result.width(); x++) {
if (result(x, y) != x + y) {
printf("result(%d, %d) = %d instead of %d\n",
x, y, result(x, y), x + y);
}
}
}
});
} |]
-- | Similar to 'testCUDA' but for 'FeatureOpenCL'.
testOpenCL :: IO ()
testOpenCL = do
[C.throwBlock| void {
handle_halide_exceptions([](){
Halide::Func f;
Halide::Var x, y, xo, xi, yo, yi;
f(x, y) = x + y;
f.gpu_tile(x, y, xo, yo, xi, yi, 4, 4);
Halide::Target target = Halide::get_host_target();
target.set_feature(Halide::Target::OpenCL);
target.set_feature(Halide::Target::Debug);
fprintf(stderr, "Compiling ...\n");
f.compile_jit(target);
fprintf(stderr, "Running on OpenCL ...\n");
Halide::Buffer<int> result = f.realize({32, 32});
for (int y = 0; y < result.height(); y++) {
for (int x = 0; x < result.width(); x++) {
if (result(x, y) != x + y) {
printf("result(%d, %d) = %d instead of %d\n",
x, y, result(x, y), x + y);
}
}
}
});
} |]
-- |
--
-- Note: generated automatically using
--
-- > cat $HALIDE_PATH/include/Halide.h | \
-- > grep -E '.* = halide_target_feature_.*' | \
-- > sed -E 's/^\s*(.*) = .*$/ | \1/g' | \
-- > grep -v FeatureEnd
data TargetFeature
= FeatureJIT
| FeatureDebug
| FeatureNoAsserts
| FeatureNoBoundsQuery
| FeatureSSE41
| FeatureAVX
| FeatureAVX2
| FeatureFMA
| FeatureFMA4
| FeatureF16C
| FeatureARMv7s
| FeatureNoNEON
| FeatureVSX
| FeaturePOWER_ARCH_2_07
| FeatureCUDA
| FeatureCUDACapability30
| FeatureCUDACapability32
| FeatureCUDACapability35
| FeatureCUDACapability50
| FeatureCUDACapability61
| FeatureCUDACapability70
| FeatureCUDACapability75
| FeatureCUDACapability80
| FeatureCUDACapability86
| FeatureOpenCL
| FeatureCLDoubles
| FeatureCLHalf
| FeatureCLAtomics64
| FeatureOpenGLCompute
| FeatureEGL
| FeatureUserContext
| FeatureProfile
| FeatureNoRuntime
| FeatureMetal
| FeatureCPlusPlusMangling
| FeatureLargeBuffers
| FeatureHexagonDma
| FeatureHVX_128
| FeatureHVX_v62
| FeatureHVX_v65
| FeatureHVX_v66
| -- Removed in upstream Halide FeatureHVX_shared_object
FeatureFuzzFloatStores
| FeatureSoftFloatABI
| FeatureMSAN
| FeatureAVX512
| FeatureAVX512_KNL
| FeatureAVX512_Skylake
| FeatureAVX512_Cannonlake
| FeatureAVX512_SapphireRapids
| FeatureTraceLoads
| FeatureTraceStores
| FeatureTraceRealizations
| FeatureTracePipeline
| FeatureD3D12Compute
| FeatureStrictFloat
| FeatureTSAN
| FeatureASAN
| FeatureCheckUnsafePromises
| FeatureEmbedBitcode
| FeatureEnableLLVMLoopOpt
| FeatureWasmSimd128
| FeatureWasmSignExt
| FeatureWasmSatFloatToInt
| FeatureWasmThreads
| FeatureWasmBulkMemory
| FeatureSVE
| FeatureSVE2
| FeatureARMDotProd
| FeatureARMFp16
| FeatureRVV
| FeatureARMv81a
| FeatureSanitizerCoverage
| FeatureProfileByTimer
| FeatureSPIRV
-- removed in v15.0.0
-- \| FeatureSemihosting
deriving stock (Eq, Show, Ord)
instance Enum TargetFeature where
-- Generated using
-- cat $HALIDE_PATH/include/Halide.h | grep -E '.* = halide_target_feature_.*' | grep -v 'FeatureEnd' | sed -E 's/[ \t]+(.*) = ([^,]*).*/ Feature\1 -> [CU.pure| int { \2 } |]/'
fromEnum =
fromIntegral . \case
FeatureJIT -> [CU.pure| int { halide_target_feature_jit } |]
FeatureDebug -> [CU.pure| int { halide_target_feature_debug } |]
FeatureNoAsserts -> [CU.pure| int { halide_target_feature_no_asserts } |]
FeatureNoBoundsQuery -> [CU.pure| int { halide_target_feature_no_bounds_query } |]
FeatureSSE41 -> [CU.pure| int { halide_target_feature_sse41 } |]
FeatureAVX -> [CU.pure| int { halide_target_feature_avx } |]
FeatureAVX2 -> [CU.pure| int { halide_target_feature_avx2 } |]
FeatureFMA -> [CU.pure| int { halide_target_feature_fma } |]
FeatureFMA4 -> [CU.pure| int { halide_target_feature_fma4 } |]
FeatureF16C -> [CU.pure| int { halide_target_feature_f16c } |]
FeatureARMv7s -> [CU.pure| int { halide_target_feature_armv7s } |]
FeatureNoNEON -> [CU.pure| int { halide_target_feature_no_neon } |]
FeatureVSX -> [CU.pure| int { halide_target_feature_vsx } |]
FeaturePOWER_ARCH_2_07 -> [CU.pure| int { halide_target_feature_power_arch_2_07 } |]
FeatureCUDA -> [CU.pure| int { halide_target_feature_cuda } |]
FeatureCUDACapability30 -> [CU.pure| int { halide_target_feature_cuda_capability30 } |]
FeatureCUDACapability32 -> [CU.pure| int { halide_target_feature_cuda_capability32 } |]
FeatureCUDACapability35 -> [CU.pure| int { halide_target_feature_cuda_capability35 } |]
FeatureCUDACapability50 -> [CU.pure| int { halide_target_feature_cuda_capability50 } |]
FeatureCUDACapability61 -> [CU.pure| int { halide_target_feature_cuda_capability61 } |]
FeatureCUDACapability70 -> [CU.pure| int { halide_target_feature_cuda_capability70 } |]
FeatureCUDACapability75 -> [CU.pure| int { halide_target_feature_cuda_capability75 } |]
FeatureCUDACapability80 -> [CU.pure| int { halide_target_feature_cuda_capability80 } |]
FeatureCUDACapability86 -> [CU.pure| int { halide_target_feature_cuda_capability86 } |]
FeatureOpenCL -> [CU.pure| int { halide_target_feature_opencl } |]
FeatureCLDoubles -> [CU.pure| int { halide_target_feature_cl_doubles } |]
FeatureCLHalf -> [CU.pure| int { halide_target_feature_cl_half } |]
FeatureCLAtomics64 -> [CU.pure| int { halide_target_feature_cl_atomic64 } |]
FeatureOpenGLCompute -> [CU.pure| int { halide_target_feature_openglcompute } |]
FeatureEGL -> [CU.pure| int { halide_target_feature_egl } |]
FeatureUserContext -> [CU.pure| int { halide_target_feature_user_context } |]
FeatureProfile -> [CU.pure| int { halide_target_feature_profile } |]
FeatureNoRuntime -> [CU.pure| int { halide_target_feature_no_runtime } |]
FeatureMetal -> [CU.pure| int { halide_target_feature_metal } |]
FeatureCPlusPlusMangling -> [CU.pure| int { halide_target_feature_c_plus_plus_mangling } |]
FeatureLargeBuffers -> [CU.pure| int { halide_target_feature_large_buffers } |]
FeatureHexagonDma -> [CU.pure| int { halide_target_feature_hexagon_dma } |]
FeatureHVX_128 -> [CU.pure| int { halide_target_feature_hvx_128 } |]
FeatureHVX_v62 -> [CU.pure| int { halide_target_feature_hvx_v62 } |]
FeatureHVX_v65 -> [CU.pure| int { halide_target_feature_hvx_v65 } |]
FeatureHVX_v66 -> [CU.pure| int { halide_target_feature_hvx_v66 } |]
-- FeatureHVX_shared_object -> [CU.pure| int { halide_target_feature_hvx_use_shared_object } |]
FeatureFuzzFloatStores -> [CU.pure| int { halide_target_feature_fuzz_float_stores } |]
FeatureSoftFloatABI -> [CU.pure| int { halide_target_feature_soft_float_abi } |]
FeatureMSAN -> [CU.pure| int { halide_target_feature_msan } |]
FeatureAVX512 -> [CU.pure| int { halide_target_feature_avx512 } |]
FeatureAVX512_KNL -> [CU.pure| int { halide_target_feature_avx512_knl } |]
FeatureAVX512_Skylake -> [CU.pure| int { halide_target_feature_avx512_skylake } |]
FeatureAVX512_Cannonlake -> [CU.pure| int { halide_target_feature_avx512_cannonlake } |]
FeatureAVX512_SapphireRapids -> [CU.pure| int { halide_target_feature_avx512_sapphirerapids } |]
FeatureTraceLoads -> [CU.pure| int { halide_target_feature_trace_loads } |]
FeatureTraceStores -> [CU.pure| int { halide_target_feature_trace_stores } |]
FeatureTraceRealizations -> [CU.pure| int { halide_target_feature_trace_realizations } |]
FeatureTracePipeline -> [CU.pure| int { halide_target_feature_trace_pipeline } |]
FeatureD3D12Compute -> [CU.pure| int { halide_target_feature_d3d12compute } |]
FeatureStrictFloat -> [CU.pure| int { halide_target_feature_strict_float } |]
FeatureTSAN -> [CU.pure| int { halide_target_feature_tsan } |]
FeatureASAN -> [CU.pure| int { halide_target_feature_asan } |]
FeatureCheckUnsafePromises -> [CU.pure| int { halide_target_feature_check_unsafe_promises } |]
FeatureEmbedBitcode -> [CU.pure| int { halide_target_feature_embed_bitcode } |]
FeatureEnableLLVMLoopOpt -> [CU.pure| int { halide_target_feature_enable_llvm_loop_opt } |]
FeatureWasmSimd128 -> [CU.pure| int { halide_target_feature_wasm_simd128 } |]
FeatureWasmSignExt -> [CU.pure| int { halide_target_feature_wasm_signext } |]
FeatureWasmSatFloatToInt -> [CU.pure| int { halide_target_feature_wasm_sat_float_to_int } |]
FeatureWasmThreads -> [CU.pure| int { halide_target_feature_wasm_threads } |]
FeatureWasmBulkMemory -> [CU.pure| int { halide_target_feature_wasm_bulk_memory } |]
FeatureSVE -> [CU.pure| int { halide_target_feature_sve } |]
FeatureSVE2 -> [CU.pure| int { halide_target_feature_sve2 } |]
FeatureARMDotProd -> [CU.pure| int { halide_target_feature_arm_dot_prod } |]
FeatureARMFp16 -> [CU.pure| int { halide_target_feature_arm_fp16 } |]
FeatureRVV -> [CU.pure| int { halide_target_feature_rvv } |]
FeatureARMv81a -> [CU.pure| int { halide_target_feature_armv81a } |]
FeatureSanitizerCoverage -> [CU.pure| int { halide_target_feature_sanitizer_coverage } |]
FeatureProfileByTimer -> [CU.pure| int { halide_target_feature_profile_by_timer } |]
FeatureSPIRV -> [CU.pure| int { halide_target_feature_spirv } |]
-- FeatureSemihosting -> [CU.pure| int { halide_target_feature_semihosting } |]
toEnum k
| fromIntegral k == [CU.pure| int { halide_target_feature_jit } |] = FeatureJIT
| fromIntegral k == [CU.pure| int { halide_target_feature_debug } |] = FeatureDebug
| fromIntegral k == [CU.pure| int { halide_target_feature_no_asserts } |] = FeatureNoAsserts
| fromIntegral k == [CU.pure| int { halide_target_feature_no_bounds_query } |] = FeatureNoBoundsQuery
| fromIntegral k == [CU.pure| int { halide_target_feature_sse41 } |] = FeatureSSE41
| fromIntegral k == [CU.pure| int { halide_target_feature_avx } |] = FeatureAVX
| fromIntegral k == [CU.pure| int { halide_target_feature_avx2 } |] = FeatureAVX2
| fromIntegral k == [CU.pure| int { halide_target_feature_fma } |] = FeatureFMA
| fromIntegral k == [CU.pure| int { halide_target_feature_fma4 } |] = FeatureFMA4
| fromIntegral k == [CU.pure| int { halide_target_feature_f16c } |] = FeatureF16C
| fromIntegral k == [CU.pure| int { halide_target_feature_armv7s } |] = FeatureARMv7s
| fromIntegral k == [CU.pure| int { halide_target_feature_no_neon } |] = FeatureNoNEON
| fromIntegral k == [CU.pure| int { halide_target_feature_vsx } |] = FeatureVSX
| fromIntegral k == [CU.pure| int { halide_target_feature_power_arch_2_07 } |] = FeaturePOWER_ARCH_2_07
| fromIntegral k == [CU.pure| int { halide_target_feature_cuda } |] = FeatureCUDA
| fromIntegral k == [CU.pure| int { halide_target_feature_cuda_capability30 } |] = FeatureCUDACapability30
| fromIntegral k == [CU.pure| int { halide_target_feature_cuda_capability32 } |] = FeatureCUDACapability32
| fromIntegral k == [CU.pure| int { halide_target_feature_cuda_capability35 } |] = FeatureCUDACapability35
| fromIntegral k == [CU.pure| int { halide_target_feature_cuda_capability50 } |] = FeatureCUDACapability50
| fromIntegral k == [CU.pure| int { halide_target_feature_cuda_capability61 } |] = FeatureCUDACapability61
| fromIntegral k == [CU.pure| int { halide_target_feature_cuda_capability70 } |] = FeatureCUDACapability70
| fromIntegral k == [CU.pure| int { halide_target_feature_cuda_capability75 } |] = FeatureCUDACapability75
| fromIntegral k == [CU.pure| int { halide_target_feature_cuda_capability80 } |] = FeatureCUDACapability80
| fromIntegral k == [CU.pure| int { halide_target_feature_cuda_capability86 } |] = FeatureCUDACapability86
| fromIntegral k == [CU.pure| int { halide_target_feature_opencl } |] = FeatureOpenCL
| fromIntegral k == [CU.pure| int { halide_target_feature_cl_doubles } |] = FeatureCLDoubles
| fromIntegral k == [CU.pure| int { halide_target_feature_cl_half } |] = FeatureCLHalf
| fromIntegral k == [CU.pure| int { halide_target_feature_cl_atomic64 } |] = FeatureCLAtomics64
| fromIntegral k == [CU.pure| int { halide_target_feature_openglcompute } |] = FeatureOpenGLCompute
| fromIntegral k == [CU.pure| int { halide_target_feature_egl } |] = FeatureEGL
| fromIntegral k == [CU.pure| int { halide_target_feature_user_context } |] = FeatureUserContext
| fromIntegral k == [CU.pure| int { halide_target_feature_profile } |] = FeatureProfile
| fromIntegral k == [CU.pure| int { halide_target_feature_no_runtime } |] = FeatureNoRuntime
| fromIntegral k == [CU.pure| int { halide_target_feature_metal } |] = FeatureMetal
| fromIntegral k == [CU.pure| int { halide_target_feature_c_plus_plus_mangling } |] = FeatureCPlusPlusMangling
| fromIntegral k == [CU.pure| int { halide_target_feature_large_buffers } |] = FeatureLargeBuffers
| fromIntegral k == [CU.pure| int { halide_target_feature_hexagon_dma } |] = FeatureHexagonDma
| fromIntegral k == [CU.pure| int { halide_target_feature_hvx_128 } |] = FeatureHVX_128
| fromIntegral k == [CU.pure| int { halide_target_feature_hvx_v62 } |] = FeatureHVX_v62
| fromIntegral k == [CU.pure| int { halide_target_feature_hvx_v65 } |] = FeatureHVX_v65
| fromIntegral k == [CU.pure| int { halide_target_feature_hvx_v66 } |] = FeatureHVX_v66
-- \| fromIntegral k == [CU.pure| int { halide_target_feature_hvx_use_shared_object } |] = FeatureHVX_shared_object
| fromIntegral k == [CU.pure| int { halide_target_feature_fuzz_float_stores } |] = FeatureFuzzFloatStores
| fromIntegral k == [CU.pure| int { halide_target_feature_soft_float_abi } |] = FeatureSoftFloatABI
| fromIntegral k == [CU.pure| int { halide_target_feature_msan } |] = FeatureMSAN
| fromIntegral k == [CU.pure| int { halide_target_feature_avx512 } |] = FeatureAVX512
| fromIntegral k == [CU.pure| int { halide_target_feature_avx512_knl } |] = FeatureAVX512_KNL
| fromIntegral k == [CU.pure| int { halide_target_feature_avx512_skylake } |] = FeatureAVX512_Skylake
| fromIntegral k == [CU.pure| int { halide_target_feature_avx512_cannonlake } |] = FeatureAVX512_Cannonlake
| fromIntegral k == [CU.pure| int { halide_target_feature_avx512_sapphirerapids } |] = FeatureAVX512_SapphireRapids
| fromIntegral k == [CU.pure| int { halide_target_feature_trace_loads } |] = FeatureTraceLoads
| fromIntegral k == [CU.pure| int { halide_target_feature_trace_stores } |] = FeatureTraceStores
| fromIntegral k == [CU.pure| int { halide_target_feature_trace_realizations } |] = FeatureTraceRealizations
| fromIntegral k == [CU.pure| int { halide_target_feature_trace_pipeline } |] = FeatureTracePipeline
| fromIntegral k == [CU.pure| int { halide_target_feature_d3d12compute } |] = FeatureD3D12Compute
| fromIntegral k == [CU.pure| int { halide_target_feature_strict_float } |] = FeatureStrictFloat
| fromIntegral k == [CU.pure| int { halide_target_feature_tsan } |] = FeatureTSAN
| fromIntegral k == [CU.pure| int { halide_target_feature_asan } |] = FeatureASAN
| fromIntegral k == [CU.pure| int { halide_target_feature_check_unsafe_promises } |] = FeatureCheckUnsafePromises
| fromIntegral k == [CU.pure| int { halide_target_feature_embed_bitcode } |] = FeatureEmbedBitcode
| fromIntegral k == [CU.pure| int { halide_target_feature_enable_llvm_loop_opt } |] = FeatureEnableLLVMLoopOpt
| fromIntegral k == [CU.pure| int { halide_target_feature_wasm_simd128 } |] = FeatureWasmSimd128
| fromIntegral k == [CU.pure| int { halide_target_feature_wasm_signext } |] = FeatureWasmSignExt
| fromIntegral k == [CU.pure| int { halide_target_feature_wasm_sat_float_to_int } |] = FeatureWasmSatFloatToInt
| fromIntegral k == [CU.pure| int { halide_target_feature_wasm_threads } |] = FeatureWasmThreads
| fromIntegral k == [CU.pure| int { halide_target_feature_wasm_bulk_memory } |] = FeatureWasmBulkMemory
| fromIntegral k == [CU.pure| int { halide_target_feature_sve } |] = FeatureSVE
| fromIntegral k == [CU.pure| int { halide_target_feature_sve2 } |] = FeatureSVE2
| fromIntegral k == [CU.pure| int { halide_target_feature_arm_dot_prod } |] = FeatureARMDotProd
| fromIntegral k == [CU.pure| int { halide_target_feature_arm_fp16 } |] = FeatureARMFp16
| fromIntegral k == [CU.pure| int { halide_target_feature_rvv } |] = FeatureRVV
| fromIntegral k == [CU.pure| int { halide_target_feature_armv81a } |] = FeatureARMv81a
| fromIntegral k == [CU.pure| int { halide_target_feature_sanitizer_coverage } |] = FeatureSanitizerCoverage
| fromIntegral k == [CU.pure| int { halide_target_feature_profile_by_timer } |] = FeatureProfileByTimer
| fromIntegral k == [CU.pure| int { halide_target_feature_spirv } |] = FeatureSPIRV
-- \| fromIntegral k == [CU.pure| int { halide_target_feature_semihosting } |] = FeatureSemihosting
| otherwise = error $ "unknown Target feature: " <> show k