{-# LANGUAGE QuasiQuotes #-}

-- | Code generation for standalone executables.
module Futhark.CodeGen.Backends.GenericC.CLI
  ( cliDefs,
  )
where

import Data.List (unzip5)
import Data.Map qualified as M
import Data.Text qualified as T
import Futhark.CodeGen.Backends.GenericC.Options
import Futhark.CodeGen.Backends.GenericC.Pretty
import Futhark.CodeGen.Backends.SimpleRep
  ( cproduct,
    escapeName,
    primAPIType,
    primStorageType,
    scalarToPrim,
  )
import Futhark.CodeGen.RTS.C (tuningH, valuesH)
import Futhark.Manifest
import Futhark.Util.Pretty (prettyString)
import Language.C.Quote.OpenCL qualified as C
import Language.C.Syntax qualified as C

genericOptions :: [Option]
genericOptions :: [Option]
genericOptions =
  [ Option
      { optionLongName :: String
optionLongName = String
"write-runtime-to",
        optionShortName :: Maybe Char
optionShortName = forall a. a -> Maybe a
Just Char
't',
        optionArgument :: OptionArgument
optionArgument = String -> OptionArgument
RequiredArgument String
"FILE",
        optionDescription :: String
optionDescription = String
"Print the time taken to execute the program to the indicated file, an integral number of microseconds.",
        optionAction :: Stm
optionAction = Stm
set_runtime_file
      },
    Option
      { optionLongName :: String
optionLongName = String
"runs",
        optionShortName :: Maybe Char
optionShortName = forall a. a -> Maybe a
Just Char
'r',
        optionArgument :: OptionArgument
optionArgument = String -> OptionArgument
RequiredArgument String
"INT",
        optionDescription :: String
optionDescription = String
"Perform NUM runs of the program.",
        optionAction :: Stm
optionAction = Stm
set_num_runs
      },
    Option
      { optionLongName :: String
optionLongName = String
"debugging",
        optionShortName :: Maybe Char
optionShortName = forall a. a -> Maybe a
Just Char
'D',
        optionArgument :: OptionArgument
optionArgument = OptionArgument
NoArgument,
        optionDescription :: String
optionDescription = String
"Perform possibly expensive internal correctness checks and verbose logging.",
        optionAction :: Stm
optionAction =
          [C.cstm|{futhark_context_config_set_debugging(cfg, 1);
                                print_report = 1;
                               }|]
      },
    Option
      { optionLongName :: String
optionLongName = String
"log",
        optionShortName :: Maybe Char
optionShortName = forall a. a -> Maybe a
Just Char
'L',
        optionArgument :: OptionArgument
optionArgument = OptionArgument
NoArgument,
        optionDescription :: String
optionDescription = String
"Print various low-overhead logging information to stderr while running.",
        optionAction :: Stm
optionAction =
          [C.cstm|{futhark_context_config_set_logging(cfg, 1);
                   print_report = 1;
                  }|]
      },
    Option
      { optionLongName :: String
optionLongName = String
"entry-point",
        optionShortName :: Maybe Char
optionShortName = forall a. a -> Maybe a
Just Char
'e',
        optionArgument :: OptionArgument
optionArgument = String -> OptionArgument
RequiredArgument String
"NAME",
        optionDescription :: String
optionDescription = String
"The entry point to run. Defaults to main.",
        optionAction :: Stm
optionAction = [C.cstm|if (entry_point != NULL) entry_point = optarg;|]
      },
    Option
      { optionLongName :: String
optionLongName = String
"binary-output",
        optionShortName :: Maybe Char
optionShortName = forall a. a -> Maybe a
Just Char
'b',
        optionArgument :: OptionArgument
optionArgument = OptionArgument
NoArgument,
        optionDescription :: String
optionDescription = String
"Print the program result in the binary output format.",
        optionAction :: Stm
optionAction = [C.cstm|binary_output = 1;|]
      },
    Option
      { optionLongName :: String
optionLongName = String
"no-print-result",
        optionShortName :: Maybe Char
optionShortName = forall a. a -> Maybe a
Just Char
'n',
        optionArgument :: OptionArgument
optionArgument = OptionArgument
NoArgument,
        optionDescription :: String
optionDescription = String
"Do not print the program result.",
        optionAction :: Stm
optionAction = [C.cstm|print_result = 0;|]
      },
    Option
      { optionLongName :: String
optionLongName = String
"help",
        optionShortName :: Maybe Char
optionShortName = forall a. a -> Maybe a
Just Char
'h',
        optionArgument :: OptionArgument
optionArgument = OptionArgument
NoArgument,
        optionDescription :: String
optionDescription = String
"Print help information and exit.",
        optionAction :: Stm
optionAction =
          [C.cstm|{
                   printf("Usage: %s [OPTION]...\nOptions:\n\n%s\nFor more information, consult the Futhark User's Guide or the man pages.\n",
                          fut_progname, option_descriptions);
                   exit(0);
                  }|]
      },
    Option
      { optionLongName :: String
optionLongName = String
"print-params",
        optionShortName :: Maybe Char
optionShortName = forall a. Maybe a
Nothing,
        optionArgument :: OptionArgument
optionArgument = OptionArgument
NoArgument,
        optionDescription :: String
optionDescription = String
"Print all tuning parameters that can be set with --param or --tuning.",
        optionAction :: Stm
optionAction =
          [C.cstm|{
                int n = futhark_get_tuning_param_count();
                for (int i = 0; i < n; i++) {
                  printf("%s (%s)\n", futhark_get_tuning_param_name(i),
                                      futhark_get_tuning_param_class(i));
                }
                exit(0);
              }|]
      },
    Option
      { optionLongName :: String
optionLongName = String
"param",
        optionShortName :: Maybe Char
optionShortName = forall a. Maybe a
Nothing,
        optionArgument :: OptionArgument
optionArgument = String -> OptionArgument
RequiredArgument String
"ASSIGNMENT",
        optionDescription :: String
optionDescription = String
"Set a tuning parameter to the given value.",
        optionAction :: Stm
optionAction =
          [C.cstm|{
                char *name = optarg;
                char *equals = strstr(optarg, "=");
                char *value_str = equals != NULL ? equals+1 : optarg;
                int value = atoi(value_str);
                if (equals != NULL) {
                  *equals = 0;
                  if (futhark_context_config_set_tuning_param(cfg, name, (size_t)value) != 0) {
                    futhark_panic(1, "Unknown size: %s\n", name);
                  }
                } else {
                  futhark_panic(1, "Invalid argument for size option: %s\n", optarg);
                }}|]
      },
    Option
      { optionLongName :: String
optionLongName = String
"tuning",
        optionShortName :: Maybe Char
optionShortName = forall a. Maybe a
Nothing,
        optionArgument :: OptionArgument
optionArgument = String -> OptionArgument
RequiredArgument String
"FILE",
        optionDescription :: String
optionDescription = String
"Read size=value assignments from the given file.",
        optionAction :: Stm
optionAction =
          [C.cstm|{
                char *ret = load_tuning_file(optarg, cfg, (int(*)(void*, const char*, size_t))
                                                          futhark_context_config_set_tuning_param);
                if (ret != NULL) {
                  futhark_panic(1, "When loading tuning from '%s': %s\n", optarg, ret);
                }}|]
      },
    Option
      { optionLongName :: String
optionLongName = String
"cache-file",
        optionShortName :: Maybe Char
optionShortName = forall a. Maybe a
Nothing,
        optionArgument :: OptionArgument
optionArgument = String -> OptionArgument
RequiredArgument String
"FILE",
        optionDescription :: String
optionDescription = String
"Store program cache here.",
        optionAction :: Stm
optionAction =
          [C.cstm|futhark_context_config_set_cache_file(cfg, optarg);|]
      }
  ]
  where
    set_runtime_file :: Stm
set_runtime_file =
      [C.cstm|{
          runtime_file = fopen(optarg, "w");
          if (runtime_file == NULL) {
            futhark_panic(1, "Cannot open %s: %s\n", optarg, strerror(errno));
          }
        }|]
    set_num_runs :: Stm
set_num_runs =
      [C.cstm|{
          num_runs = atoi(optarg);
          perform_warmup = 1;
          if (num_runs <= 0) {
            futhark_panic(1, "Need a positive number of runs, not %s\n", optarg);
          }
        }|]

readInput :: Manifest -> Int -> T.Text -> ([C.BlockItem], C.Stm, C.Stm, C.Stm, C.Exp)
readInput :: Manifest -> Int -> TypeName -> ([BlockItem], Stm, Stm, Stm, Exp)
readInput Manifest
manifest Int
i TypeName
tname =
  case forall k a. Ord k => k -> Map k a -> Maybe a
M.lookup TypeName
tname forall a b. (a -> b) -> a -> b
$ Manifest -> Map TypeName Type
manifestTypes Manifest
manifest of
    Maybe Type
Nothing ->
      let (Signedness
_, PrimType
t) = TypeName -> (Signedness, PrimType)
scalarToPrim TypeName
tname
          dest :: String
dest = String
"read_value_" forall a. [a] -> [a] -> [a]
++ forall a. Show a => a -> String
show Int
i
          info :: String
info = TypeName -> String
T.unpack TypeName
tname forall a. Semigroup a => a -> a -> a
<> String
"_info"
       in ( [C.citems|
             $ty:(primStorageType t) $id:dest;
             if (read_scalar(stdin, &$id:info, &$id:dest) != 0) {
             futhark_panic(1, "Error when reading input #%d of type %s (errno: %s).\n",
                           $int:i,
                           $string:(T.unpack tname),
                           strerror(errno));
                           };|],
            [C.cstm|;|],
            [C.cstm|;|],
            [C.cstm|;|],
            [C.cexp|$id:dest|]
          )
    Just (TypeOpaque TypeName
desc OpaqueOps
_ Maybe RecordOps
_) ->
      ( [C.citems|futhark_panic(1, "Cannot read input #%d of type %s\n", $int:i, $string:(T.unpack desc));|],
        [C.cstm|;|],
        [C.cstm|;|],
        [C.cstm|;|],
        [C.cexp|NULL|]
      )
    Just (TypeArray TypeName
t TypeName
et Int
rank ArrayOps
ops) ->
      let dest :: String
dest = String
"read_value_" forall a. [a] -> [a] -> [a]
++ forall a. Show a => a -> String
show Int
i
          shape :: String
shape = String
"read_shape_" forall a. [a] -> [a] -> [a]
++ forall a. Show a => a -> String
show Int
i
          arr :: String
arr = String
"read_arr_" forall a. [a] -> [a] -> [a]
++ forall a. Show a => a -> String
show Int
i

          ty :: Type
ty = [C.cty|typename $id:t|]
          dims_exps :: [Exp]
dims_exps = [[C.cexp|$id:shape[$int:j]|] | Int
j <- [Int
0 .. Int
rank forall a. Num a => a -> a -> a
- Int
1]]
          t' :: Type
t' = forall a b c. (a -> b -> c) -> (a, b) -> c
uncurry Signedness -> PrimType -> Type
primAPIType forall a b. (a -> b) -> a -> b
$ TypeName -> (Signedness, PrimType)
scalarToPrim TypeName
et

          new_array :: TypeName
new_array = ArrayOps -> TypeName
arrayNew ArrayOps
ops
          free_array :: TypeName
free_array = ArrayOps -> TypeName
arrayFree ArrayOps
ops
          info :: String
info = TypeName -> String
T.unpack TypeName
et forall a. Semigroup a => a -> a -> a
<> String
"_info"

          items :: [BlockItem]
items =
            [C.citems|
               $ty:ty $id:dest;
               typename int64_t $id:shape[$int:rank];
               $ty:t' *$id:arr = NULL;
               errno = 0;
               if (read_array(stdin,
                              &$id:info,
                              (void**) &$id:arr,
                              $id:shape,
                              $int:rank)
                   != 0) {
                 futhark_panic(1, "Cannot read input #%d of type %s (errno: %s).\n",
                               $int:i,
                               $string:(T.unpack tname),
                               strerror(errno));
               }|]
       in ( [BlockItem]
items,
            [C.cstm|assert(($id:dest = $id:new_array(ctx, $id:arr, $args:dims_exps)) != NULL);|],
            [C.cstm|assert($id:free_array(ctx, $id:dest) == 0);|],
            [C.cstm|free($id:arr);|],
            [C.cexp|$id:dest|]
          )

readInputs :: Manifest -> [T.Text] -> [([C.BlockItem], C.Stm, C.Stm, C.Stm, C.Exp)]
readInputs :: Manifest -> [TypeName] -> [([BlockItem], Stm, Stm, Stm, Exp)]
readInputs Manifest
manifest = forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
zipWith (Manifest -> Int -> TypeName -> ([BlockItem], Stm, Stm, Stm, Exp)
readInput Manifest
manifest) [Int
0 ..]

prepareOutputs :: Manifest -> [T.Text] -> [(C.BlockItem, C.Exp, C.Stm)]
prepareOutputs :: Manifest -> [TypeName] -> [(BlockItem, Exp, Stm)]
prepareOutputs Manifest
manifest = forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
zipWith forall {p}. Show p => p -> TypeName -> (BlockItem, Exp, Stm)
prepareResult [(Int
0 :: Int) ..]
  where
    prepareResult :: p -> TypeName -> (BlockItem, Exp, Stm)
prepareResult p
i TypeName
tname = do
      let result :: String
result = String
"result_" forall a. [a] -> [a] -> [a]
++ forall a. Show a => a -> String
show p
i

      case forall k a. Ord k => k -> Map k a -> Maybe a
M.lookup TypeName
tname forall a b. (a -> b) -> a -> b
$ Manifest -> Map TypeName Type
manifestTypes Manifest
manifest of
        Maybe Type
Nothing ->
          let (Signedness
s, PrimType
pt) = TypeName -> (Signedness, PrimType)
scalarToPrim TypeName
tname
              ty :: Type
ty = Signedness -> PrimType -> Type
primAPIType Signedness
s PrimType
pt
           in ( [C.citem|$ty:ty $id:result;|],
                [C.cexp|$id:result|],
                [C.cstm|;|]
              )
        Just (TypeArray TypeName
t TypeName
_ Int
_ ArrayOps
ops) ->
          ( [C.citem|typename $id:t $id:result;|],
            [C.cexp|$id:result|],
            [C.cstm|assert($id:(arrayFree ops)(ctx, $id:result) == 0);|]
          )
        Just (TypeOpaque TypeName
t OpaqueOps
ops Maybe RecordOps
_) ->
          ( [C.citem|typename $id:t $id:result;|],
            [C.cexp|$id:result|],
            [C.cstm|assert($id:(opaqueFree ops)(ctx, $id:result) == 0);|]
          )

-- | Return a statement printing the given external value.
printStm :: Manifest -> T.Text -> C.Exp -> C.Stm
printStm :: Manifest -> TypeName -> Exp -> Stm
printStm Manifest
manifest TypeName
tname Exp
e =
  case forall k a. Ord k => k -> Map k a -> Maybe a
M.lookup TypeName
tname forall a b. (a -> b) -> a -> b
$ Manifest -> Map TypeName Type
manifestTypes Manifest
manifest of
    Maybe Type
Nothing ->
      let info :: TypeName
info = TypeName
tname forall a. Semigroup a => a -> a -> a
<> TypeName
"_info"
       in [C.cstm|write_scalar(stdout, binary_output, &$id:info, &$exp:e);|]
    Just (TypeOpaque TypeName
desc OpaqueOps
_ Maybe RecordOps
_) ->
      [C.cstm|{
         fprintf(stderr, "Values of type \"%s\" have no external representation.\n", $string:(T.unpack desc));
         retval = 1;
         goto print_end;
       }|]
    Just (TypeArray TypeName
_ TypeName
et Int
rank ArrayOps
ops) ->
      let et' :: Type
et' = forall a b c. (a -> b -> c) -> (a, b) -> c
uncurry Signedness -> PrimType -> Type
primAPIType forall a b. (a -> b) -> a -> b
$ TypeName -> (Signedness, PrimType)
scalarToPrim TypeName
et
          values_array :: TypeName
values_array = ArrayOps -> TypeName
arrayValues ArrayOps
ops
          shape_array :: TypeName
shape_array = ArrayOps -> TypeName
arrayShape ArrayOps
ops
          num_elems :: Exp
num_elems =
            [Exp] -> Exp
cproduct [[C.cexp|$id:shape_array(ctx, $exp:e)[$int:i]|] | Int
i <- [Int
0 .. Int
rank forall a. Num a => a -> a -> a
- Int
1]]
          info :: TypeName
info = TypeName
et forall a. Semigroup a => a -> a -> a
<> TypeName
"_info"
       in [C.cstm|{
                 $ty:et' *arr = calloc($exp:num_elems, $id:info.size);
                 assert(arr != NULL);
                 assert($id:values_array(ctx, $exp:e, arr) == 0);
                 assert(futhark_context_sync(ctx) == 0);
                 write_array(stdout, binary_output, &$id:info, arr,
                             $id:shape_array(ctx, $exp:e), $int:rank);
                 free(arr);
                 }|]

printResult :: Manifest -> [(T.Text, C.Exp)] -> [C.Stm]
printResult :: Manifest -> [(TypeName, Exp)] -> [Stm]
printResult Manifest
manifest = forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (TypeName, Exp) -> [Stm]
f
  where
    f :: (TypeName, Exp) -> [Stm]
f (TypeName
v, Exp
e) = [Manifest -> TypeName -> Exp -> Stm
printStm Manifest
manifest TypeName
v Exp
e, [C.cstm|printf("\n");|]]

cliEntryPoint ::
  Manifest -> T.Text -> EntryPoint -> (C.Definition, C.Initializer)
cliEntryPoint :: Manifest -> TypeName -> EntryPoint -> (Definition, Initializer)
cliEntryPoint Manifest
manifest TypeName
entry_point_name (EntryPoint TypeName
cfun [Output]
outputs [Input]
inputs) =
  let ([[BlockItem]]
input_items, [Stm]
pack_input, [Stm]
free_input, [Stm]
free_parsed, [Exp]
input_args) =
        forall a b c d e. [(a, b, c, d, e)] -> ([a], [b], [c], [d], [e])
unzip5 forall a b. (a -> b) -> a -> b
$ Manifest -> [TypeName] -> [([BlockItem], Stm, Stm, Stm, Exp)]
readInputs Manifest
manifest forall a b. (a -> b) -> a -> b
$ forall a b. (a -> b) -> [a] -> [b]
map Input -> TypeName
inputType [Input]
inputs

      ([BlockItem]
output_decls, [Exp]
output_vals, [Stm]
free_outputs) =
        forall a b c. [(a, b, c)] -> ([a], [b], [c])
unzip3 forall a b. (a -> b) -> a -> b
$ Manifest -> [TypeName] -> [(BlockItem, Exp, Stm)]
prepareOutputs Manifest
manifest forall a b. (a -> b) -> a -> b
$ forall a b. (a -> b) -> [a] -> [b]
map Output -> TypeName
outputType [Output]
outputs

      printstms :: [Stm]
printstms =
        Manifest -> [(TypeName, Exp)] -> [Stm]
printResult Manifest
manifest forall a b. (a -> b) -> a -> b
$ forall a b. [a] -> [b] -> [(a, b)]
zip (forall a b. (a -> b) -> [a] -> [b]
map Output -> TypeName
outputType [Output]
outputs) [Exp]
output_vals

      cli_entry_point_function_name :: String
cli_entry_point_function_name =
        String
"futrts_cli_entry_" forall a. Semigroup a => a -> a -> a
<> TypeName -> String
T.unpack (TypeName -> TypeName
escapeName TypeName
entry_point_name)

      pause_profiling :: TypeName
pause_profiling = TypeName
"futhark_context_pause_profiling" :: T.Text
      unpause_profiling :: TypeName
unpause_profiling = TypeName
"futhark_context_unpause_profiling" :: T.Text

      addrOf :: a -> Exp
addrOf a
e = [C.cexp|&$exp:e|]

      run_it :: [BlockItem]
run_it =
        [C.citems|
                int r;
                // Run the program once.
                $stms:pack_input
                if (futhark_context_sync(ctx) != 0) {
                  futhark_panic(1, "%s", futhark_context_get_error(ctx));
                };
                // Only profile last run.
                if (profile_run) {
                  $id:unpause_profiling(ctx);
                }
                t_start = get_wall_time();
                r = $id:cfun(ctx,
                             $args:(map addrOf output_vals),
                             $args:input_args);
                if (r != 0) {
                  futhark_panic(1, "%s", futhark_context_get_error(ctx));
                }
                if (futhark_context_sync(ctx) != 0) {
                  futhark_panic(1, "%s", futhark_context_get_error(ctx));
                };
                if (profile_run) {
                  $id:pause_profiling(ctx);
                }
                t_end = get_wall_time();
                long int elapsed_usec = t_end - t_start;
                if (time_runs && runtime_file != NULL) {
                  fprintf(runtime_file, "%lld\n", (long long) elapsed_usec);
                  fflush(runtime_file);
                }
                $stms:free_input
              |]
   in ( [C.cedecl|
   static int $id:cli_entry_point_function_name(struct futhark_context *ctx) {
     typename int64_t t_start, t_end;
     int time_runs = 0, profile_run = 0;
     int retval = 0;

     // We do not want to profile all the initialisation.
     $id:pause_profiling(ctx);

     // Declare and read input.
     set_binary_mode(stdin);
     $items:(mconcat input_items)

     if (end_of_input(stdin) != 0) {
       futhark_panic(1, "Expected EOF on stdin after reading input for \"%s\".\n", $string:(prettyString entry_point_name));
     }

     $items:output_decls

     // Warmup run
     if (perform_warmup) {
       $items:run_it
       $stms:free_outputs
     }
     time_runs = 1;
     // Proper run.
     for (int run = 0; run < num_runs; run++) {
       // Only profile last run.
       profile_run = run == num_runs -1;
       $items:run_it
       if (run < num_runs-1) {
         $stms:free_outputs
       }
     }

     // Free the parsed input.
     $stms:free_parsed

     if (print_result) {
       // Print the final result.
       if (binary_output) {
         set_binary_mode(stdout);
       }
       $stms:printstms
     }
     print_end: {}
     $stms:free_outputs
     return retval;
   }|],
        [C.cinit|{ .name = $string:(T.unpack entry_point_name),
                   .fun = $id:cli_entry_point_function_name }|]
      )

{-# NOINLINE cliDefs #-}

-- | Generate Futhark standalone executable code.
cliDefs :: [Option] -> Manifest -> T.Text
cliDefs :: [Option] -> Manifest -> TypeName
cliDefs [Option]
options Manifest
manifest =
  let option_parser :: Func
option_parser =
        String -> [Option] -> Func
generateOptionParser String
"parse_options" forall a b. (a -> b) -> a -> b
$ [Option]
genericOptions forall a. [a] -> [a] -> [a]
++ [Option]
options
      ([Definition]
cli_entry_point_decls, [Initializer]
entry_point_inits) =
        forall a b. [(a, b)] -> ([a], [b])
unzip forall a b. (a -> b) -> a -> b
$
          forall a b. (a -> b) -> [a] -> [b]
map (forall a b c. (a -> b -> c) -> (a, b) -> c
uncurry (Manifest -> TypeName -> EntryPoint -> (Definition, Initializer)
cliEntryPoint Manifest
manifest)) forall a b. (a -> b) -> a -> b
$
            forall k a. Map k a -> [(k, a)]
M.toList forall a b. (a -> b) -> a -> b
$
              Manifest -> Map TypeName EntryPoint
manifestEntryPoints Manifest
manifest
   in [Definition] -> TypeName
definitionsText
        [C.cunit|
$esc:("#include <getopt.h>")
$esc:("#include <ctype.h>")
$esc:("#include <inttypes.h>")
$esc:("#include <unistd.h>")

$esc:(T.unpack valuesH)

static int binary_output = 0;
static int print_result = 1;
static int print_report = 0;
static typename FILE *runtime_file;
static int perform_warmup = 0;
static int num_runs = 1;
// If the entry point is NULL, the program will terminate after doing initialisation and such.
static const char *entry_point = "main";

$esc:(T.unpack tuningH)

$func:option_parser

$edecls:cli_entry_point_decls

typedef int entry_point_fun(struct futhark_context*);

struct entry_point_entry {
  const char *name;
  entry_point_fun *fun;
};

int main(int argc, char** argv) {
  int retval = 0;
  fut_progname = argv[0];

  struct futhark_context_config *cfg = futhark_context_config_new();
  assert(cfg != NULL);

  int parsed_options = parse_options(cfg, argc, argv);
  argc -= parsed_options;
  argv += parsed_options;

  if (argc != 0) {
    futhark_panic(1, "Excess non-option: %s\n", argv[0]);
  }

  struct futhark_context *ctx = futhark_context_new(cfg);
  assert (ctx != NULL);

  char* error = futhark_context_get_error(ctx);
  if (error != NULL) {
    futhark_panic(1, "%s", error);
  }

  struct entry_point_entry entry_points[] = {
    $inits:entry_point_inits
  };

  if (entry_point != NULL) {
    int num_entry_points = sizeof(entry_points) / sizeof(entry_points[0]);
    entry_point_fun *entry_point_fun = NULL;
    for (int i = 0; i < num_entry_points; i++) {
      if (strcmp(entry_points[i].name, entry_point) == 0) {
        entry_point_fun = entry_points[i].fun;
        break;
      }
    }

    if (entry_point_fun == NULL) {
      fprintf(stderr, "No entry point '%s'.  Select another with --entry-point.  Options are:\n",
                      entry_point);
      for (int i = 0; i < num_entry_points; i++) {
        fprintf(stderr, "%s\n", entry_points[i].name);
      }
      return 1;
    }

    if (isatty(fileno(stdin))) {
      fprintf(stderr, "Reading input from TTY.\n");
      fprintf(stderr, "Send EOF (CTRL-d) after typing all input values.\n");
    }

    retval = entry_point_fun(ctx);

    if (runtime_file != NULL) {
      fclose(runtime_file);
    }

    if (print_report) {
      char *report = futhark_context_report(ctx);
      fputs(report, stderr);
      free(report);
    }
  }

  futhark_context_free(ctx);
  futhark_context_config_free(cfg);
  return retval;
}|]