Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
Implement a new algorithm for SMPI_SHARED_MALLOC: global
authorMartin Quinson <martin.quinson@loria.fr>
Thu, 16 Feb 2017 21:42:29 +0000 (22:42 +0100)
committerMartin Quinson <martin.quinson@loria.fr>
Thu, 16 Feb 2017 21:42:35 +0000 (22:42 +0100)
This maps any new block onto a single file in memory.
The results will be awfully disfigured but that is damn efficient in
memory.

doc/doxygen/options.doc
src/simgrid/sg_config.cpp
src/smpi/private.h
src/smpi/smpi_bench.cpp
src/smpi/smpi_global.cpp

index 68228d0..4b16add 100644 (file)
@@ -1046,7 +1046,7 @@ Here is an example:
 
 \subsection options_model_smpi_shared_malloc smpi/shared-malloc: Factorize malloc()s
 
-\b Default: yes
+\b Default: global
 
 If your simulation consumes too much memory, you may want to modify
 your code so that the working areas are shared by all MPI ranks. For
@@ -1057,14 +1057,25 @@ lot of memory so this is still desirable for some studies. For more on
 the motivation for that feature, please refer to the 
 <a href="https://simgrid.github.io/SMPI_CourseWare/topic_understanding_performance/matrixmultiplication/">relevant
 section</a> of the SMPI CourseWare (see Activity #2.2 of the pointed
-assignment).
-
-SMPI can use shared memory by calling shm_* functions; this might speed up the simulation.
-This opens or creates a new POSIX shared memory object, kept in RAM, in /dev/shm.
-
-If you want to disable this behavior (for example for debugging
-purposes), set its value to "no" or "0".
-
+assignment). In practice, change the call to malloc() and free() into
+SMPI_SHARED_MALLOC() and SMPI_SHARED_FREE().
+
+SMPI provides 2 algorithms for this feature. The first one, called \c
+local, allocates one bloc per call to SMPI_SHARED_MALLOC() in your
+code (each call location gets its own bloc) and this bloc is shared
+amongst all MPI ranks.  This is implemented with the shm_* functions
+to create a new POSIX shared memory object (kept in RAM, in /dev/shm)
+for each shared bloc.
+
+With the \c global algorithm, each call to SMPI_SHARED_MALLOC()
+returns a new adress, but it only points to a shadow bloc: its memory
+area is mapped on a 1MiB file on disk. If the returned bloc is of size
+N MiB, then the same file is mapped N times to cover the whole bloc. 
+At the end, no matter how many SMPI_SHARED_MALLOC you do, this will
+only consume 1 MiB in memory.
+
+You can disable this behavior and come back to regular mallocs (for
+example for debugging purposes) using \c "no" as a value.
 
 \subsection options_model_smpi_wtime smpi/wtime: Inject constant times for calls to MPI_Wtime
 
index 3a084c2..d961fbc 100644 (file)
@@ -539,8 +539,8 @@ void sg_config_init(int *argc, char **argv)
     xbt_cfg_register_boolean("smpi/simulate-computation", "yes", nullptr, "Whether the computational part of the simulated application should be simulated.");
     xbt_cfg_register_alias("smpi/simulate-computation","smpi/simulate_computation");
 
-    xbt_cfg_register_boolean("smpi/shared-malloc", "yes", nullptr,
-                             "Whether SMPI_SHARED_MALLOC is enabled. Disable it for debugging purposes.");
+    xbt_cfg_register_string("smpi/shared-malloc", "global", nullptr,
+                            "Whether SMPI_SHARED_MALLOC is enabled. Disable it for debugging purposes.");
     xbt_cfg_register_alias("smpi/shared-malloc", "smpi/use-shared-malloc");
     xbt_cfg_register_alias("smpi/shared-malloc", "smpi/use_shared_malloc");
 
index 161703d..a6dbcc4 100644 (file)
@@ -413,7 +413,9 @@ extern XBT_PRIVATE double smpi_host_speed;
 extern XBT_PRIVATE bool smpi_privatize_global_variables;
 extern XBT_PRIVATE char* smpi_start_data_exe; //start of the data+bss segment of the executable
 extern XBT_PRIVATE int smpi_size_data_exe; //size of the data+bss segment of the executable
-extern XBT_PRIVATE bool smpi_cfg_shared_malloc; // Whether to activate shared malloc
+
+typedef enum { shmalloc_none, shmalloc_local, shmalloc_global } shared_malloc_type;
+extern XBT_PRIVATE shared_malloc_type smpi_cfg_shared_malloc; // Whether to activate shared malloc
 
 XBT_PRIVATE void smpi_switch_data_segment(int dest);
 XBT_PRIVATE void smpi_really_switch_data_segment(int dest);
index 496809d..95a1e4e 100644 (file)
@@ -85,7 +85,7 @@ int smpi_loaded_page = -1;
 char* smpi_start_data_exe = nullptr;
 int smpi_size_data_exe = 0;
 bool smpi_privatize_global_variables;
-bool smpi_cfg_shared_malloc    = true;
+shared_malloc_type smpi_cfg_shared_malloc = shmalloc_global;
 double smpi_total_benched_time = 0;
 smpi_privatisation_region_t smpi_privatisation_regions;
 
@@ -98,60 +98,54 @@ namespace {
 class smpi_source_location {
 public:
   smpi_source_location(const char* filename, int line)
-    : filename(xbt_strdup(filename)), filename_length(strlen(filename)), line(line) {}
+      : filename(xbt_strdup(filename)), filename_length(strlen(filename)), line(line)
+  {
+  }
 
   /** Pointer to a static string containing the file name */
-  char* filename = nullptr;
+  char* filename      = nullptr;
   int filename_length = 0;
-  int line = 0;
+  int line            = 0;
 
   bool operator==(smpi_source_location const& that) const
   {
-    return filename_length == that.filename_length
-      && line == that.line
-      && std::memcmp(filename, that.filename, filename_length) == 0;
-  }
-  bool operator!=(smpi_source_location const& that) const
-  {
-    return !(*this == that);
+    return filename_length == that.filename_length && line == that.line &&
+           std::memcmp(filename, that.filename, filename_length) == 0;
   }
+  bool operator!=(smpi_source_location const& that) const { return !(*this == that); }
 };
-
 }
 
 namespace std {
 
-template<>
-class hash<smpi_source_location> {
+template <> class hash<smpi_source_location> {
 public:
   typedef smpi_source_location argument_type;
   typedef std::size_t result_type;
   result_type operator()(smpi_source_location const& loc) const
   {
-    return xbt_str_hash_ext(loc.filename, loc.filename_length)
-      ^ xbt_str_hash_ext((const char*) &loc.line, sizeof(loc.line));
+    return xbt_str_hash_ext(loc.filename, loc.filename_length) ^
+           xbt_str_hash_ext((const char*)&loc.line, sizeof(loc.line));
   }
 };
-
 }
 
 namespace {
 
 typedef struct {
-  int fd = -1;
+  int fd    = -1;
   int count = 0;
 } shared_data_t;
 
 std::unordered_map<smpi_source_location, shared_data_t> allocs;
 typedef std::unordered_map<smpi_source_location, shared_data_t>::value_type shared_data_key_type;
 
-typedef struct  {
+typedef struct {
   size_t size;
   shared_data_key_type* data;
 } shared_metadata_t;
 
 std::unordered_map<void*, shared_metadata_t> allocs_metadata;
-
 }
 
 static size_t shm_size(int fd) {
@@ -169,7 +163,7 @@ static void* shm_map(int fd, size_t size, shared_data_key_type* data) {
   shared_metadata_t meta;
 
   if(size > shm_size(fd) && (ftruncate(fd, static_cast<off_t>(size)) < 0)) {
-      xbt_die("Could not truncate fd %d to %zu: %s", fd, size, strerror(errno));
+    xbt_die("Could not truncate fd %d to %zu: %s", fd, size, strerror(errno));
   }
 
   void* mem = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
@@ -234,8 +228,8 @@ void smpi_execute(double duration)
     TRACE_smpi_computing_out(rank);
 
   } else {
-    XBT_DEBUG("Real computation took %g while option smpi/cpu_threshold is set to %g => ignore it",
-              duration, smpi_cpu_threshold);
+    XBT_DEBUG("Real computation took %g while option smpi/cpu_threshold is set to %g => ignore it", duration,
+              smpi_cpu_threshold);
   }
 }
 
@@ -304,7 +298,7 @@ void smpi_bench_end()
   }
 
   if (xbt_cfg_get_string("smpi/comp-adjustment-file")[0] != '\0') { // Maybe we need to artificially speed up or slow
-                                                         // down our computation based on our statistical analysis.
+    // down our computation based on our statistical analysis.
 
     smpi_trace_call_location_t* loc                            = smpi_process_get_call_location();
     std::string key                                            = loc->get_composed_key();
@@ -313,7 +307,7 @@ void smpi_bench_end()
       speedup = it->second;
     }
   }
-  
+
   // Simulate the benchmarked computation unless disabled via command-line argument
   if (xbt_cfg_get_boolean("smpi/simulate-computation")) {
     smpi_execute(xbt_os_timer_elapsed(timer)/speedup);
@@ -485,15 +479,15 @@ void smpi_sample_1(int global, const char *file, int line, int iters, double thr
     if (data->iters != iters || data->threshold != threshold) {
       XBT_ERROR("Asked to bench block %s with different settings %d, %f is not %d, %f. "
                 "How did you manage to give two numbers at the same line??",
-                loc, data->iters, data->threshold, iters,threshold);
+                loc, data->iters, data->threshold, iters, threshold);
       THROW_IMPOSSIBLE;
     }
 
     // if we already have some data, check whether sample_2 should get one more bench or whether it should emulate
     // the computation instead
     data->benching = (sample_enough_benchs(data) == 0);
-    XBT_DEBUG("XXXX Re-entering the benched nest %s. %s",loc,
-             (data->benching?"more benching needed":"we have enough data, skip computes"));
+    XBT_DEBUG("XXXX Re-entering the benched nest %s. %s", loc,
+              (data->benching ? "more benching needed" : "we have enough data, skip computes"));
   }
   xbt_free(loc);
 }
@@ -517,7 +511,8 @@ int smpi_sample_2(int global, const char *file, int line)
     // Enough data, no more bench (either we got enough data from previous visits to this benched nest, or we just
     //ran one bench and need to bail out now that our job is done). Just sleep instead
     XBT_DEBUG("No benchmark (either no need, or just ran one): count >= iter (%d >= %d) or stderr<thres (%f<=%f)."
-              " apply the %fs delay instead", data->count, data->iters, data->relstderr, data->threshold, data->mean);
+              " apply the %fs delay instead",
+              data->count, data->iters, data->relstderr, data->threshold, data->mean);
     smpi_execute(data->mean);
     smpi_process_set_sampling(0);
     res = 0; // prepare to capture future, unrelated computations
@@ -551,7 +546,7 @@ void smpi_sample_3(int global, const char *file, int line)
   data->relstderr = sqrt((data->sum_pow2 / n - data->mean * data->mean) / n) / data->mean;
   if (sample_enough_benchs(data)==0) {
     data->mean = sample; // Still in benching process; We want sample_2 to simulate the exact time of this loop
-                         // occurrence before leaving, not the mean over the history
+    // occurrence before leaving, not the mean over the history
   }
   XBT_DEBUG("Average mean after %d steps is %f, relative standard error is %f (sample was %f)", data->count,
       data->mean, data->relstderr, sample);
@@ -561,12 +556,12 @@ void smpi_sample_3(int global, const char *file, int line)
 }
 
 #ifndef WIN32
-
+static int smpi_shared_malloc_bogusfile           = -1;
+static unsigned long smpi_shared_malloc_blocksize = 1UL << 20;
 void *smpi_shared_malloc(size_t size, const char *file, int line)
 {
   void* mem;
-  if (size > 0 && smpi_cfg_shared_malloc) {
-    int fd;
+  if (size > 0 && smpi_cfg_shared_malloc == shmalloc_local) {
     smpi_source_location loc(file, line);
     auto res = allocs.insert(std::make_pair(loc, shared_data_t()));
     auto data = res.first;
@@ -575,9 +570,9 @@ void *smpi_shared_malloc(size_t size, const char *file, int line)
       // Generate a shared memory name from the address of the shared_data:
       char shmname[32]; // cannot be longer than PSHMNAMLEN = 31 on Mac OS X (shm_open raises ENAMETOOLONG otherwise)
       snprintf(shmname, 31, "/shmalloc%p", &*data);
-      fd = shm_open(shmname, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+      int fd = shm_open(shmname, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
       if (fd < 0) {
-        if(errno==EEXIST)
+        if (errno == EEXIST)
           xbt_die("Please cleanup /dev/shm/%s", shmname);
         else
           xbt_die("An unhandled error occurred while opening %s. shm_open: %s", shmname, strerror(errno));
@@ -594,6 +589,47 @@ void *smpi_shared_malloc(size_t size, const char *file, int line)
       data->second.count++;
     }
     XBT_DEBUG("Shared malloc %zu in %p (metadata at %p)", size, mem, &*data);
+
+  } else if (smpi_cfg_shared_malloc == shmalloc_global) {
+    /* First reserve memory area */
+    mem = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+
+    xbt_assert(mem != MAP_FAILED, "Failed to allocate %luMiB of memory. Run \"sysctl vm.overcommit_memory=1\" as root "
+                                  "to allow big allocations.\n",
+               (unsigned long)(size >> 20));
+
+    /* Create bogus file if not done already */
+    if (smpi_shared_malloc_bogusfile == -1) {
+      /* Create a fd to a new file on disk, make it smpi_shared_malloc_blocksize big, and unlink it.
+       * It still exists in memory but not in the file system (thus it cannot be leaked). */
+      char* name                   = xbt_strdup("/tmp/simgrid-shmalloc-XXXXXX");
+      smpi_shared_malloc_bogusfile = mkstemp(name);
+      unlink(name);
+      free(name);
+      char* dumb = (char*)calloc(1, smpi_shared_malloc_blocksize);
+      write(smpi_shared_malloc_bogusfile, dumb, smpi_shared_malloc_blocksize);
+      free(dumb);
+    }
+
+    /* Map the bogus file in place of the anonymous memory */
+    unsigned int i;
+    for (i = 0; i < size / smpi_shared_malloc_blocksize; i++) {
+      void* pos = (void*)((unsigned long)mem + i * smpi_shared_malloc_blocksize);
+      void* res = mmap(pos, smpi_shared_malloc_blocksize, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_SHARED | MAP_POPULATE,
+                       smpi_shared_malloc_bogusfile, 0);
+      xbt_assert(res == pos, "Could not map folded virtual memory (%s). Do you perhaps need to increase the "
+                             "STARPU_MALLOC_SIMULATION_FOLD environment variable or the sysctl vm.max_map_count?",
+                 strerror(errno));
+    }
+    if (size % smpi_shared_malloc_blocksize) {
+      void* pos = (void*)((unsigned long)mem + i * smpi_shared_malloc_blocksize);
+      void* res = mmap(pos, size % smpi_shared_malloc_blocksize, PROT_READ | PROT_WRITE,
+                       MAP_FIXED | MAP_SHARED | MAP_POPULATE, smpi_shared_malloc_bogusfile, 0);
+      xbt_assert(res == pos, "Could not map folded virtual memory (%s). Do you perhaps need to increase the "
+                             "STARPU_MALLOC_SIMULATION_FOLD environment variable or the sysctl vm.max_map_count?",
+                 strerror(errno));
+    }
+
   } else {
     mem = xbt_malloc(size);
     XBT_DEBUG("Classic malloc %zu in %p", size, mem);
@@ -604,9 +640,8 @@ void *smpi_shared_malloc(size_t size, const char *file, int line)
 
 void smpi_shared_free(void *ptr)
 {
-  char loc[PTR_STRLEN];
-
-  if (smpi_cfg_shared_malloc) {
+  if (smpi_cfg_shared_malloc == shmalloc_local) {
+    char loc[PTR_STRLEN];
     snprintf(loc, PTR_STRLEN, "%p", ptr);
     auto meta = allocs_metadata.find(ptr);
     if (meta == allocs_metadata.end()) {
@@ -622,10 +657,14 @@ void smpi_shared_free(void *ptr)
       close(data->fd);
       allocs.erase(allocs.find(meta->second.data->first));
       XBT_DEBUG("Shared free - with removal - of %p", ptr);
-    }else{
+    } else {
       XBT_DEBUG("Shared free - no removal - of %p, count = %d", ptr, data->count);
     }
-  }else{
+
+  } else if (smpi_cfg_shared_malloc == shmalloc_global) {
+    munmap(ptr, 0); // the POSIX says that I should not give 0 as a length, but it seems to work OK
+
+  } else {
     XBT_DEBUG("Classic free of %p", ptr);
     xbt_free(ptr);
   }
@@ -658,23 +697,23 @@ int smpi_shared_known_call(const char* func, const char* input)
 }
 
 void* smpi_shared_get_call(const char* func, const char* input) {
-   char* loc = bprintf("%s:%s", func, input);
+  char* loc = bprintf("%s:%s", func, input);
 
-   if (calls==nullptr)
-      calls = xbt_dict_new_homogeneous(nullptr);
-   void* data = xbt_dict_get(calls, loc);
-   xbt_free(loc);
-   return data;
+  if (calls == nullptr)
+    calls    = xbt_dict_new_homogeneous(nullptr);
+  void* data = xbt_dict_get(calls, loc);
+  xbt_free(loc);
+  return data;
 }
 
 void* smpi_shared_set_call(const char* func, const char* input, void* data) {
-   char* loc = bprintf("%s:%s", func, input);
+  char* loc = bprintf("%s:%s", func, input);
 
-   if (calls==nullptr)
-      calls = xbt_dict_new_homogeneous(nullptr);
-   xbt_dict_set(calls, loc, data, nullptr);
-   xbt_free(loc);
-   return data;
+  if (calls == nullptr)
+    calls = xbt_dict_new_homogeneous(nullptr);
+  xbt_dict_set(calls, loc, data, nullptr);
+  xbt_free(loc);
+  return data;
 }
 
 
@@ -707,8 +746,8 @@ void smpi_really_switch_data_segment(int dest)
   // FIXME, cross-process support (mmap across process when necessary)
   int current = smpi_privatisation_regions[dest].file_descriptor;
   XBT_DEBUG("Switching data frame to the one of process %d", dest);
-  void* tmp = mmap (TOPAGE(smpi_start_data_exe), smpi_size_data_exe,
-    PROT_READ | PROT_WRITE, MAP_FIXED | MAP_SHARED, current, 0);
+  void* tmp =
+      mmap(TOPAGE(smpi_start_data_exe), smpi_size_data_exe, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_SHARED, current, 0);
   if (tmp != TOPAGE(smpi_start_data_exe))
     xbt_die("Couldn't map the new region");
   smpi_loaded_page = dest;
@@ -738,23 +777,23 @@ void smpi_initialize_global_memory_segments()
     return;
   }
 
-  smpi_privatisation_regions =
-    static_cast<smpi_privatisation_region_t>( xbt_malloc(smpi_process_count() * sizeof(struct s_smpi_privatisation_region)));
+  smpi_privatisation_regions = static_cast<smpi_privatisation_region_t>(
+      xbt_malloc(smpi_process_count() * sizeof(struct s_smpi_privatisation_region)));
 
   for (int i=0; i< smpi_process_count(); i++){
-      //create SIMIX_process_count() mappings of this size with the same data inside
-      int file_descriptor;
-      void *address = nullptr;
-      char path[24];
-      int status;
-
-      do {
-        snprintf(path, sizeof(path), "/smpi-buffer-%06x", rand()%0xffffff);
-        file_descriptor = shm_open(path, O_RDWR|O_CREAT|O_EXCL, S_IRUSR|S_IWUSR);
-      } while (file_descriptor == -1 && errno == EEXIST);
-      if (file_descriptor < 0) {
-        if (errno==EMFILE) {
-          xbt_die("Impossible to create temporary file for memory mapping: %s\n\
+    // create SIMIX_process_count() mappings of this size with the same data inside
+    int file_descriptor;
+    void* address = nullptr;
+    char path[24];
+    int status;
+
+    do {
+      snprintf(path, sizeof(path), "/smpi-buffer-%06x", rand() % 0xffffff);
+      file_descriptor = shm_open(path, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
+    } while (file_descriptor == -1 && errno == EEXIST);
+    if (file_descriptor < 0) {
+      if (errno == EMFILE) {
+        xbt_die("Impossible to create temporary file for memory mapping: %s\n\
 The open() system call failed with the EMFILE error code (too many files). \n\n\
 This means that you reached the system limits concerning the amount of files per process. \
 This is not a surprise if you are trying to virtualize many processes on top of SMPI. \
@@ -766,31 +805,30 @@ First, check what your limits are:\n\
   cat /proc/self/limits     # Displays any per-process limitation (including the one given above)\n\n\
 If one of these values is less than the amount of MPI processes that you try to run, then you got the explanation of this error. \
 Ask the Internet about tutorials on how to increase the files limit such as: https://rtcamp.com/tutorials/linux/increase-open-files-limit/",
-             strerror(errno));
-        }
-        xbt_die("Impossible to create temporary file for memory mapping: %s",
-            strerror(errno));
+                strerror(errno));
       }
+      xbt_die("Impossible to create temporary file for memory mapping: %s", strerror(errno));
+    }
 
-      status = ftruncate(file_descriptor, smpi_size_data_exe);
-      if(status)
-        xbt_die("Impossible to set the size of the temporary file for memory mapping");
+    status = ftruncate(file_descriptor, smpi_size_data_exe);
+    if (status)
+      xbt_die("Impossible to set the size of the temporary file for memory mapping");
 
-      /* Ask for a free region */
-      address = mmap (nullptr, smpi_size_data_exe, PROT_READ | PROT_WRITE, MAP_SHARED, file_descriptor, 0);
-      if (address == MAP_FAILED)
-        xbt_die("Couldn't find a free region for memory mapping");
+    /* Ask for a free region */
+    address = mmap(nullptr, smpi_size_data_exe, PROT_READ | PROT_WRITE, MAP_SHARED, file_descriptor, 0);
+    if (address == MAP_FAILED)
+      xbt_die("Couldn't find a free region for memory mapping");
 
-      status = shm_unlink(path);
-      if (status)
-        xbt_die("Impossible to unlink temporary file for memory mapping");
+    status = shm_unlink(path);
+    if (status)
+      xbt_die("Impossible to unlink temporary file for memory mapping");
 
-      //initialize the values
-      memcpy(address, TOPAGE(smpi_start_data_exe), smpi_size_data_exe);
+    // initialize the values
+    memcpy(address, TOPAGE(smpi_start_data_exe), smpi_size_data_exe);
 
-      //store the address of the mapping for further switches
-      smpi_privatisation_regions[i].file_descriptor = file_descriptor;
-      smpi_privatisation_regions[i].address = address;
+    // store the address of the mapping for further switches
+    smpi_privatisation_regions[i].file_descriptor = file_descriptor;
+    smpi_privatisation_regions[i].address         = address;
   }
 #endif
 }
index 3933638..4170805 100644 (file)
@@ -770,7 +770,18 @@ static void smpi_init_options(){
     smpi_privatize_global_variables = xbt_cfg_get_boolean("smpi/privatize-global-variables");
     if (smpi_cpu_threshold < 0)
       smpi_cpu_threshold = DBL_MAX;
-    smpi_cfg_shared_malloc = xbt_cfg_get_boolean("smpi/shared-malloc");
+
+    char* val = xbt_cfg_get_string("smpi/shared-malloc");
+    if (!strcasecmp(val, "yes") || !strcmp(val, "1") || !strcasecmp(val, "on") || !strcasecmp(val, "global")) {
+      smpi_cfg_shared_malloc = shmalloc_global;
+    } else if (!strcasecmp(val, "local")) {
+      smpi_cfg_shared_malloc = shmalloc_local;
+    } else if (!strcasecmp(val, "no") || !strcmp(val, "0") || !strcasecmp(val, "off")) {
+      smpi_cfg_shared_malloc = shmalloc_none;
+    } else {
+      xbt_die("Invalid value '%s' for option smpi/shared-malloc. Possible values: 'on' or 'global', 'local', 'off'",
+              val);
+    }
 }
 
 int smpi_main(int (*realmain) (int argc, char *argv[]), int argc, char *argv[])