Runtime Model

With the following CMake options enabled:

$ cmake .. -DENABLE_FLOG=ON -DENABLE_BOOST=ON -DENABLE_GRAPHVIZ=ON

an executable compiled with FleCSI will have several command-line options available. For example, running the color unit test from its location in test/execution with the -h flag, will produce the following output:

$ ./color -h
  color:
  -h [ --help ]            Print this message and exit.
  -t [ --tags ] [=arg(=0)] Enable the specified output tags, e.g.,
                           --tags=tag1,tag2. Passing --tags by itself will
                           print the available tags.
  --control-model          Output the current control model and exit.

The –tags option allows users to control logging output, e.g., by turning on or off certain guarded outputs. This is a feature provided by the FleCSI Logging Utility. The –control-model option instructs the executable to output a .dot file of the control-flow graph of the control model. The FleCSI Control Model allows users to define the structure of execution of a program. Additional options may be added in the future, and will be documented in this guide.

Custom Handlers

Because the FleCSI programming system requires, in most cases, that the user relinquish direct control of the main function, we have provided a runtime interface that allows fairly fine-grained customization of the initialization, execution, and finalization of a program. Formally, the runtime model is part of the Cinch build system utility that was developed to support FleCSI. The runtime interface allows users to register additional control-point actions, i.e., initialization, and finalization, in addition to adding new command-line options. This system allows easy extensibility to accommodate new runtime systems that may be required by computing architectures in the future.

As a simple example, consider the following code block:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
using namespace cinch;
using namespace boost::program_options;

inline void custom_add_options(options_descriptions & desc) {

  desc.add_options()("flag,f", "Custom command-line option");

} // custom_add_options

inline int custom_initialization(int argc, char ** argv,
  variables_map & vm) {

  if(vm.count("flag") {
    std::cout << "Custom flag passed to program!" << std::endl;
  }

  return 0;
} // custom_initialization

inline int custom_finalization(int argc, char ** argv,
  exit_mode_t mode) {

  return 0;
} // custom_finalization

inline runtime_handler_t custom_handler {
  custom_initialization, custom_finalization, custom_add_options
};

cinch_append_runtime_handler(custom_handler);

This code defines three functions:

  • custom_add_options
    This function provides a mechanism to add additional command-line options to the main Boost options descriptor. The interface for the options_descriptions type is documented here.

  • custom_initialization
    This function will be invoked during the initialization phase of the Cinch runtime main function (The full code of runtime.cc is included below.) Users can test command-line options or invoke initialization of additional low-level runtime systems here. Non-zero returns from this function will cause the top-level execution to exit.

  • custom_finalization
    This function will be invoked during the shutdown phase of the Cinch runtime main function. The exit mode of the top-level runtime is passed into this function through the mode argument. Users can adapt shutdown of additional low-level runtime systems based on the exit status of the top-level execution.

Note

The names of the custom handler functions are arbitrary, and should reflect the user’s requirements.

After defining add_options, initialization, and finalization functions, the user can create a handler object (highlighted lines), and register it with the Cinch runtime system.

The following code block shows the actual main function implementation of the Cinch runtime. Lines 39, 69, 71, and 81 are highlighted to identify the call sites of user-registered handlers:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
/*
    :::::::: ::::::::::: ::::    :::  ::::::::  :::    :::
   :+:    :+:    :+:     :+:+:   :+: :+:    :+: :+:    :+:
   +:+           +:+     :+:+:+  +:+ +:+        +:+    +:+
   +#+           +#+     +#+ +:+ +#+ +#+        +#++:++#++
   +#+           +#+     +#+  +#+#+# +#+        +#+    +#+
   #+#    #+#    #+#     #+#   #+#+# #+#    #+# #+#    #+#
    ######## ########### ###    ####  ########  ###    ###

   Copyright (c) 2016, Los Alamos National Security, LLC
   All rights reserved.
                                                                              */

#include <cinch-config.h>
#include <cinch/runtime.h>

#include <iostream>
#include <string>

#if defined(CINCH_ENABLE_BOOST)
  #include <boost/program_options.hpp>
  using namespace boost::program_options;
#endif

using namespace cinch;

int main(int argc, char ** argv) {

  runtime_t & runtime_ = runtime_t::instance();

#if defined(CINCH_ENABLE_BOOST)
  std::string program(argv[0]);
  options_description desc(program.substr(program.rfind('/')+1).c_str());

  // Add help option
  desc.add_options()("help,h", "Print this message and exit.");

  // Invoke add options functions
  runtime_.add_options(desc);

  variables_map vm;
  parsed_options parsed =
    command_line_parser(argc, argv).options(desc).allow_unregistered().run();
  store(parsed, vm);

  notify(vm);

  // Gather the unregistered options, if there are any, print a help message
  // and die nicely.
  std::vector<std::string> unrecog_options =
    collect_unrecognized(parsed.options, include_positional);
  if(unrecog_options.size()) {
    std::cout << std::endl << "Unrecognized options: ";
    for ( int i=0; i<unrecog_options.size(); ++i ) {
      std::cout << unrecog_options[i] << " ";
    }
    std::cout << std::endl << std::endl << desc << std::endl;
  } // if

  if(vm.count("help")) {
    std::cout << desc << std::endl;
    return 1;
  } // if
#endif

  // Invoke registered runtime initializations
  if(
#if defined(CINCH_ENABLE_BOOST)
    runtime_.initialize_runtimes(argc, argv, vm)
#else
    runtime_.initialize_runtimes(argc, argv)
#endif
  ) {
    std::exit(1);
  } // if

  // Invoke the primary callback
  int result = runtime_.driver()(argc, argv);

  // Invoke registered runtime finalizations
  if(runtime_.finalize_runtimes(argc, argv, exit_mode_t::success)) {
    std::exit(1);
  } // if

  return result;
} // main

This code block shows the implementation of the registration interface:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
/*
    :::::::: ::::::::::: ::::    :::  ::::::::  :::    :::
   :+:    :+:    :+:     :+:+:   :+: :+:    :+: :+:    :+:
   +:+           +:+     :+:+:+  +:+ +:+        +:+    +:+
   +#+           +#+     +#+ +:+ +#+ +#+        +#++:++#++
   +#+           +#+     +#+  +#+#+# +#+        +#+    +#+
   #+#    #+#    #+#     #+#   #+#+# #+#    #+# #+#    #+#
    ######## ########### ###    ####  ########  ###    ###

   Copyright (c) 2016, Los Alamos National Security, LLC
   All rights reserved.
                                                                              */
#pragma once

/*! @file */

#include <cinch-config.h>

#if defined(CINCH_ENABLE_BOOST)
  #include <boost/program_options.hpp>
  using namespace boost::program_options;
#endif

#include <functional>
#include <string>
#include <vector>

namespace cinch {

enum exit_mode_t : size_t {
  success,
  unrecognized_option,
  help
}; // enum exit_mode_t

/*!
  Type to define runtime initialization and finalization handlers.
 */

struct runtime_handler_t {
#if defined(CINCH_ENABLE_BOOST)
  std::function<int(int, char **, variables_map &)> initialize;
#else
  std::function<int(int, char **)> initialize;
#endif
  std::function<int(int, char **, exit_mode_t)> finalize;
#if defined(CINCH_ENABLE_BOOST)
  std::function<void(options_description &)> add_options =
    [](options_description &){};
#endif
}; // struct runtime_handler_t

/*!
  The runtime_t type provides a stateful interface for registering and
  executing user-defined actions at initialization and finalization
  control points.
 */

struct runtime_t {

  static runtime_t & instance() {
    static runtime_t r;
    return r;
  } // instance

  std::string const & program() const { return program_; }
  std::string & program() { return program_; }

  bool register_driver(std::function<int(int, char **)> const & driver) {
    driver_ = driver;
    return true;
  } // register_driver

  std::function<int(int, char **)> const & driver() const {
    return driver_;
  } // driver

  /*!
    Append the given runtime handler to the vector of handlers. Handlers
    will be executed in the order in which they are appended.
   */

  bool append_runtime_handler(runtime_handler_t const & handler) {
    handlers_.push_back(handler);
    return true;
  } // register_runtime_handler

  /*!
    Access the runtime handler vector.
   */

  std::vector<runtime_handler_t> & runtimes() {
    return handlers_;
  } // runtimes

  /*!
    Invoke runtime options callbacks.
   */

#if defined(CINCH_ENABLE_BOOST)
  void add_options(options_description & desc) {
    for(auto r: handlers_) {
      r.add_options(desc);
    } // for
  } // add_options
#endif // CINCH_ENABLE_BOOST

  /*!
    Invoke runtime intiailzation callbacks.
   */

#if defined(CINCH_ENABLE_BOOST)
  int initialize_runtimes(int argc, char ** argv, variables_map & vm) {
    int result{0};

    for(auto r: handlers_) {
      result |= r.initialize(argc, argv, vm);
    } // for

    return result;
  } // initialize_runtimes
#else
  int initialize_runtimes(int argc, char ** argv) {
    int result{0};

    for(auto r: handlers_) {
      result |= r.initialize(argc, argv);
    } // for

    return result;
  } // initialize_runtimes
#endif

  /*!
    Invoke runtime finalization callbacks.
   */

  int finalize_runtimes(int argc, char ** argv, exit_mode_t mode) {
    int result{0};

    for(auto r: handlers_) {
      result |= r.finalize(argc, argv, mode);
    } // for

    return result;
  } // finalize_runtimes

private:

  runtime_t() {}

  ~runtime_t() {}

  // These are deleted because this type is a singleton, i.e.,
  // we don't want anyone to be able to make copies or references.

  runtime_t(const runtime_t &) = delete;
  runtime_t & operator=(const runtime_t &) = delete;
  runtime_t(runtime_t &&) = delete;
  runtime_t & operator=(runtime_t &&) = delete;

  std::string program_;
  std::function<int(int, char **)> driver_;
  std::vector<runtime_handler_t> handlers_;

}; // runtime_t

} // namespace cinch

/*!
  @def cinch_register_runtime_driver(driver)

  Register the primary runtime driver function.

  @param driver The primary driver with a 'int(int, char **)' signature
                that should be invoked by the FleCSI runtime.
 */

#define cinch_register_runtime_driver(driver)                                  \
  /* MACRO IMPLEMENTATION */                                                   \
                                                                               \
  inline bool cinch_registered_driver_##driver =                               \
    cinch::runtime_t::instance().register_driver(driver)

/*!
  @def cinch_register_runtime_handler(handler)

  Register a runtime handler with the FleCSI runtime. Runtime handlers
  are invoked at fixed control points in the FleCSI control model for
  add options, initialization, and finalization. The finalization function
  has an additional argument that specifies the exit mode. Adding options
  is only enabled with CINCH_ENABLE_BOOST.

  @param handler A runtime_handler_t that references the appropriate
                 initialize, finalize, and add_options functions.
 */

#define cinch_append_runtime_handler(handler)                                  \
  /* MACRO DEFINITION */                                                       \
                                                                               \
  inline bool cinch_append_runtime_handler_##handler =                         \
    cinch::runtime_t::instance().append_runtime_handler(handler)