set(CMAKE_SMPI_COMMAND "${CMAKE_SMPI_COMMAND}:${NS3_LIBRARY_PATH}")
endif()
set(CMAKE_SMPI_COMMAND "${CMAKE_SMPI_COMMAND}:\${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}\"")
+set(SMPIMAIN smpimain)
configure_file(${CMAKE_HOME_DIRECTORY}/include/smpi/mpif.h.in ${CMAKE_BINARY_DIR}/include/smpi/mpif.h @ONLY)
foreach(script cc cxx ff f90 run)
set(CMAKE_SMPI_COMMAND "${CMAKE_SMPI_COMMAND}:${NS3_LIBRARY_PATH}")
endif()
set(CMAKE_SMPI_COMMAND "${CMAKE_SMPI_COMMAND}:\${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}\"")
+set(SMPIMAIN ${CMAKE_BINARY_DIR}/bin/smpimain)
foreach(script cc cxx ff f90 run)
configure_file(${CMAKE_HOME_DIRECTORY}/src/smpi/smpi${script}.in ${CMAKE_BINARY_DIR}/smpi_script/bin/smpi${script} @ONLY)
/* Fortran specific stuff */
-XBT_PUBLIC(int) __attribute__((weak)) smpi_simulated_main_(int argc, char** argv);
-XBT_PUBLIC(int) __attribute__((weak)) MAIN__();
-XBT_PUBLIC(int) smpi_main(int (*realmain) (int argc, char *argv[]),int argc, char *argv[]);
-XBT_PUBLIC(void) __attribute__((weak)) user_main_();
+XBT_PUBLIC(int) smpi_main(const char* program, int argc, char *argv[]);
XBT_PUBLIC(int) smpi_process_index();
XBT_PUBLIC(void) smpi_process_init(int *argc, char ***argv);
close(this->memory_file);
if (this->unw_underlying_addr_space != unw_local_addr_space) {
- unw_destroy_addr_space(this->unw_underlying_addr_space);
- _UPT_destroy(this->unw_underlying_context);
+ if (this->unw_underlying_addr_space)
+ unw_destroy_addr_space(this->unw_underlying_addr_space);
+ if (this->unw_underlying_context)
+ _UPT_destroy(this->unw_underlying_context);
}
unw_destroy_addr_space(this->unw_addr_space);
{
std::unique_ptr<simgrid::mc::Process> process(new simgrid::mc::Process(pid, socket));
// TODO, automatic detection of the config from the process
- process->privatized(
- xbt_cfg_get_boolean("smpi/privatize-global-variables"));
+ process->privatized(smpi_privatize_global_variables != SMPI_PRIVATIZE_NONE);
modelChecker_ = std::unique_ptr<ModelChecker>(
new simgrid::mc::ModelChecker(std::move(process)));
xbt_assert(mc_model_checker == nullptr);
region.size = size;
region.block = ((char*)stack - (char*)heap->heapbase) / BLOCKSIZE + 1;
#if HAVE_SMPI
- if (smpi_privatize_global_variables && process)
+ if (smpi_privatize_global_variables == SMPI_PRIVATIZE_MMAP && process)
region.process_index = smpi_process_index_of_smx_process(process);
else
#endif
xbt_cfg_register_alias("smpi/send-is-detached-thresh","smpi/send_is_detached_thresh");
xbt_cfg_register_alias("smpi/send-is-detached-thresh","smpi/send_is_detached_thres");
- xbt_cfg_register_boolean("smpi/privatize-global-variables", "no", nullptr, "Whether we should privatize global variable at runtime.");
+ const char* default_privatization = std::getenv("SMPI_PRIVATIZATION");
+ if (default_privatization == nullptr)
+ default_privatization = "no";
+
+ xbt_cfg_register_string("smpi/privatize-global-variables", default_privatization, nullptr, "Whether we should privatize global variable at runtime (no, yes, mmap, dlopen).");
+
xbt_cfg_register_alias("smpi/privatize-global-variables", "smpi/privatize_global_variables");
xbt_cfg_register_boolean("smpi/grow-injected-times", "yes", nullptr, "Whether we want to make the injected time in MPI_Iprobe and MPI_Test grow, to allow faster simulation. This can make simulation less precise, though.");
process->ppid = parent_process->pid;
/* SMPI process have their own data segment and each other inherit from their father */
#if HAVE_SMPI
- if (smpi_privatize_global_variables) {
+ if (smpi_privatize_global_variables == SMPI_PRIVATIZE_MMAP) {
if (parent_process->pid != 0) {
SIMIX_segment_index_set(process, parent_process->segment_index);
} else {
process->ppid = parent_process->pid;
/* SMPI process have their own data segment and each other inherit from their father */
#if HAVE_SMPI
- if (smpi_privatize_global_variables) {
+ if (smpi_privatize_global_variables == SMPI_PRIVATIZE_MMAP) {
if (parent_process->pid != 0) {
SIMIX_segment_index_set(process, parent_process->segment_index);
} else {
} else if (siginfo->si_signo == SIGSEGV) {
fprintf(stderr, "Segmentation fault.\n");
#if HAVE_SMPI
- if (smpi_enabled() && !smpi_privatize_global_variables) {
+ if (smpi_enabled() && smpi_privatize_global_variables == SMPI_PRIVATIZE_NONE) {
#if HAVE_PRIVATIZATION
fprintf(stderr, "Try to enable SMPI variable privatization with --cfg=smpi/privatize-global-variables:yes.\n");
#else
// utilities
extern XBT_PRIVATE double smpi_cpu_threshold;
extern XBT_PRIVATE double smpi_host_speed;
-extern XBT_PRIVATE bool smpi_privatize_global_variables;
+
+#define SMPI_PRIVATIZE_NONE 0
+#define SMPI_PRIVATIZE_MMAP 1
+#define SMPI_PRIVATIZE_DLOPEN 2
+#define SMPI_PRIVATIZE_DEFAULT SMPI_PRIVATIZE_MMAP
+extern XBT_PRIVATE int 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
void smpi_bench_begin()
{
- if (smpi_privatize_global_variables) {
+ if (smpi_privatize_global_variables == SMPI_PRIVATIZE_MMAP) {
smpi_switch_data_segment(smpi_process()->index());
}
}
int Comm::dup(MPI_Comm* newcomm){
- if(smpi_privatize_global_variables){ //we need to switch as the called function may silently touch global variables
+ if(smpi_privatize_global_variables == SMPI_PRIVATIZE_MMAP){ //we need to switch as the called function may silently touch global variables
smpi_switch_data_segment(smpi_process()->index());
}
MPI_Group cp = new Group(this->group());
smpi_process()->set_replaying(false);
}
- if(smpi_privatize_global_variables){ //we need to switch as the called function may silently touch global variables
+ if(smpi_privatize_global_variables == SMPI_PRIVATIZE_MMAP){ //we need to switch as the called function may silently touch global variables
smpi_switch_data_segment(smpi_process()->index());
}
//identify neighbours in comm
Coll_allgather_mpich::allgather(&leader, 1, MPI_INT , leaders_map, 1, MPI_INT, this);
- if(smpi_privatize_global_variables){ //we need to switch as the called function may silently touch global variables
+ if(smpi_privatize_global_variables == SMPI_PRIVATIZE_MMAP){ //we need to switch as the called function may silently touch global variables
smpi_switch_data_segment(smpi_process()->index());
}
}
Coll_bcast_mpich::bcast(&(is_uniform_),1, MPI_INT, 0, comm_intra );
- if(smpi_privatize_global_variables){ //we need to switch as the called function may silently touch global variables
+ if(smpi_privatize_global_variables == SMPI_PRIVATIZE_MMAP){ //we need to switch as the called function may silently touch global variables
smpi_switch_data_segment(smpi_process()->index());
}
// Are the ranks blocked ? = allocated contiguously on the SMP nodes
XBT_DEBUG("Copy output buf %p is shared. Let's ignore it.", recvbuf);
}
- if(smpi_privatize_global_variables){
+ if(smpi_privatize_global_variables == SMPI_PRIVATIZE_MMAP){
smpi_switch_data_segment(smpi_process()->index());
}
/* First check if we really have something to do */
/* This program is free software; you can redistribute it and/or modify it
* under the terms of the license (GNU LGPL) which comes with this package. */
+#include <spawn.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <dlfcn.h>
+
#include "mc/mc.h"
#include "private.h"
#include "private.hpp"
#include <stdio.h>
#include <stdlib.h>
#include <string>
+#include <utility>
#include <vector>
+#include <memory>
XBT_LOG_NEW_DEFAULT_SUBCATEGORY(smpi_kernel, smpi, "Logging specific to SMPI (kernel)");
#include <boost/tokenizer.hpp>
#include <boost/algorithm/string.hpp> /* trim_right / trim_left */
+#ifndef RTLD_DEEPBIND
+/* RTLD_DEEPBIND is a bad idea of GNU ld that obviously does not exist on other platforms
+ * See https://www.akkadia.org/drepper/dsohowto.pdf
+ * and https://lists.freebsd.org/pipermail/freebsd-current/2016-March/060284.html
+*/
+#define RTLD_DEEPBIND 0
+#endif
+
+/* Mac OSX does not have any header file providing that definition so we have to duplicate it here. Bummers. */
+extern char** environ; /* we use it in posix_spawnp below */
+
#if HAVE_PAPI
#include "papi.h"
const char* papi_default_config_name = "default";
XBT_DEBUG("Receiver %p is shared. Let's ignore it.", (char*)comm->dst_buff);
}else{
void* tmpbuff=buff;
- if((smpi_privatize_global_variables) && (static_cast<char*>(buff) >= smpi_start_data_exe)
+ if((smpi_privatize_global_variables == SMPI_PRIVATIZE_MMAP) && (static_cast<char*>(buff) >= smpi_start_data_exe)
&& (static_cast<char*>(buff) < smpi_start_data_exe + smpi_size_data_exe )
){
XBT_DEBUG("Privatization : We are copying from a zone inside global memory... Saving data to temp buffer !");
memcpy(tmpbuff, buff, buff_size);
}
- if((smpi_privatize_global_variables) && ((char*)comm->dst_buff >= smpi_start_data_exe)
+ if((smpi_privatize_global_variables == SMPI_PRIVATIZE_MMAP) && ((char*)comm->dst_buff >= smpi_start_data_exe)
&& ((char*)comm->dst_buff < smpi_start_data_exe + smpi_size_data_exe )){
XBT_DEBUG("Privatization : We are copying to a zone inside global memory - Switch data segment");
smpi_switch_data_segment(
}
xbt_free(index_to_process_data);
- if(smpi_privatize_global_variables)
+ if(smpi_privatize_global_variables == SMPI_PRIVATIZE_MMAP)
smpi_destroy_global_memory_segments();
smpi_free_static();
}
extern "C" {
-#ifndef WIN32
-
-void __attribute__ ((weak)) user_main_()
-{
- xbt_die("Should not be in this smpi_simulated_main");
-}
-
-int __attribute__ ((weak)) smpi_simulated_main_(int argc, char **argv)
-{
- simgrid::smpi::Process::init(&argc, &argv);
- user_main_();
- return 0;
-}
-
-inline static int smpi_main_wrapper(int argc, char **argv){
- int ret = smpi_simulated_main_(argc,argv);
- if(ret !=0){
- XBT_WARN("SMPI process did not return 0. Return value : %d", ret);
- smpi_process()->set_return_value(ret);
- }
- return 0;
-}
-
-int __attribute__ ((weak)) main(int argc, char **argv)
-{
- return smpi_main(smpi_main_wrapper, argc, argv);
-}
-
-#endif
-
static void smpi_init_logs(){
/* Connect log categories. See xbt/log.c */
simgrid::smpi::Colls::smpi_coll_cleanup_callback=nullptr;
smpi_cpu_threshold = xbt_cfg_get_double("smpi/cpu-threshold");
smpi_host_speed = xbt_cfg_get_double("smpi/host-speed");
- smpi_privatize_global_variables = xbt_cfg_get_boolean("smpi/privatize-global-variables");
+ const char* smpi_privatize_option = xbt_cfg_get_string("smpi/privatize-global-variables");
+ if (std::strcmp(smpi_privatize_option, "no") == 0)
+ smpi_privatize_global_variables = SMPI_PRIVATIZE_NONE;
+ else if (std::strcmp(smpi_privatize_option, "yes") == 0)
+ smpi_privatize_global_variables = SMPI_PRIVATIZE_DEFAULT;
+ else if (std::strcmp(smpi_privatize_option, "mmap") == 0)
+ smpi_privatize_global_variables = SMPI_PRIVATIZE_MMAP;
+ else if (std::strcmp(smpi_privatize_option, "dlopen") == 0)
+ smpi_privatize_global_variables = SMPI_PRIVATIZE_DLOPEN;
+
+ // Some compatibility stuff:
+ else if (std::strcmp(smpi_privatize_option, "1") == 0)
+ smpi_privatize_global_variables = SMPI_PRIVATIZE_DEFAULT;
+ else if (std::strcmp(smpi_privatize_option, "0") == 0)
+ smpi_privatize_global_variables = SMPI_PRIVATIZE_NONE;
+
+ else
+ xbt_die("Invalid value for smpi/privatize-global-variables: %s",
+ smpi_privatize_option);
+
if (smpi_cpu_threshold < 0)
smpi_cpu_threshold = DBL_MAX;
}
}
-int smpi_main(int (*realmain) (int argc, char *argv[]), int argc, char *argv[])
+static int execute_command(const char * const argv[])
+{
+ pid_t pid;
+ int status;
+ if (posix_spawnp(&pid, argv[0], nullptr, nullptr, (char* const*) argv, environ) != 0)
+ return 127;
+ if (waitpid(pid, &status, 0) != pid)
+ return 127;
+ return status;
+}
+
+typedef std::function<int(int argc, char *argv[])> smpi_entry_point_type;
+typedef int (* smpi_c_entry_point_type)(int argc, char **argv);
+typedef void (* smpi_fortran_entry_point_type)(void);
+
+static int smpi_run_entry_point(smpi_entry_point_type entry_point, std::vector<std::string> args)
+{
+ const int argc = args.size();
+ std::unique_ptr<char*[]> argv(new char*[argc + 1]);
+ for (int i = 0; i != argc; ++i)
+ argv[i] = args[i].empty() ? const_cast<char*>(""): &args[i].front();
+ argv[argc] = nullptr;
+
+ int res = entry_point(argc, argv.get());
+ if (res != 0){
+ XBT_WARN("SMPI process did not return 0. Return value : %d", res);
+ smpi_process()->set_return_value(res);
+ }
+ return 0;
+}
+
+// TODO, remove the number of functions involved here
+static smpi_entry_point_type smpi_resolve_function(void* handle)
+{
+ smpi_fortran_entry_point_type entry_point2 =
+ (smpi_fortran_entry_point_type) dlsym(handle, "user_main_");
+ if (entry_point2 != nullptr) {
+ // fprintf(stderr, "EP user_main_=%p\n", entry_point2);
+ return [entry_point2](int argc, char** argv) {
+ smpi_process_init(&argc, &argv);
+ entry_point2();
+ return 0;
+ };
+ }
+
+ smpi_c_entry_point_type entry_point = (smpi_c_entry_point_type) dlsym(handle, "main");
+ if (entry_point != nullptr) {
+ // fprintf(stderr, "EP main=%p\n", entry_point);
+ return entry_point;
+ }
+
+ return smpi_entry_point_type();
+}
+
+int smpi_main(const char* executable, int argc, char *argv[])
{
srand(SMPI_RAND_SEED);
// parse the platform file: get the host list
SIMIX_create_environment(argv[1]);
- SIMIX_comm_set_copy_data_callback(smpi_comm_copy_data_callback);
- SIMIX_function_register_default(realmain);
+ SIMIX_comm_set_copy_data_callback(smpi_comm_copy_buffer_callback);
+
+ static std::size_t rank = 0;
+
+ if (smpi_privatize_global_variables == SMPI_PRIVATIZE_DLOPEN) {
+
+ std::string executable_copy = executable;
+ simix_global->default_function = [executable_copy](std::vector<std::string> args) {
+ return std::function<void()>([executable_copy, args] {
+
+ // Copy the dynamic library:
+ std::string target_executable = executable_copy
+ + "_" + std::to_string(getpid())
+ + "_" + std::to_string(rank++) + ".so";
+ // TODO, execute directly instead of relying on cp
+ const char* command1 [] = {
+ "cp", "--reflink=auto", "--", executable_copy.c_str(), target_executable.c_str(),
+ nullptr
+ };
+ const char* command2 [] = {
+ "cp", "--", executable_copy.c_str(), target_executable.c_str(),
+ nullptr
+ };
+ if (execute_command(command1) != 0 && execute_command(command2) != 0)
+ xbt_die("copy failed");
+
+ // Load the copy and resolve the entry point:
+ void* handle = dlopen(target_executable.c_str(), RTLD_LAZY | RTLD_LOCAL | RTLD_DEEPBIND);
+ unlink(target_executable.c_str());
+ if (handle == nullptr)
+ xbt_die("dlopen failed");
+ smpi_entry_point_type entry_point = smpi_resolve_function(handle);
+ if (!entry_point)
+ xbt_die("Could not resolve entry point");
+
+ smpi_run_entry_point(entry_point, args);
+ });
+ };
+
+ }
+ else {
+
+ // Load the dynamic library and resolve the entry point:
+ void* handle = dlopen(executable, RTLD_LAZY | RTLD_LOCAL | RTLD_DEEPBIND);
+ if (handle == nullptr)
+ xbt_die("dlopen failed for %s", executable);
+ smpi_entry_point_type entry_point = smpi_resolve_function(handle);
+ if (!entry_point)
+ xbt_die("main not found in %s", executable);
+ // TODO, register the executable for SMPI privatization
+
+ // Execute the same entry point for each simulated process:
+ simix_global->default_function = [entry_point](std::vector<std::string> args) {
+ return std::function<void()>([entry_point, args] {
+ smpi_run_entry_point(entry_point, args);
+ });
+ };
+
+ }
+
SIMIX_launch_application(argv[2]);
smpi_global_init();
smpi_check_options();
- if(smpi_privatize_global_variables)
+ if(smpi_privatize_global_variables == SMPI_PRIVATIZE_MMAP)
smpi_initialize_global_memory_segments();
/* Clean IO before the run */
smpi_check_options();
if (TRACE_is_enabled() && TRACE_is_configured())
TRACE_smpi_alloc();
- if(smpi_privatize_global_variables)
+ if(smpi_privatize_global_variables == SMPI_PRIVATIZE_MMAP)
smpi_initialize_global_memory_segments();
}
int smpi_loaded_page = -1;
char* smpi_start_data_exe = nullptr;
int smpi_size_data_exe = 0;
-bool smpi_privatize_global_variables;
+int smpi_privatize_global_variables;
static const int PROT_RWX = (PROT_READ | PROT_WRITE | PROT_EXEC);
static const int PROT_RW = (PROT_READ | PROT_WRITE );
void Process::destroy()
{
- if(smpi_privatize_global_variables){
+ if(smpi_privatize_global_variables == SMPI_PRIVATIZE_MMAP){
smpi_switch_data_segment(index_);
}
state_ = SMPI_FINALIZED;
int rank = xbt_str_parse_int((*argv)[2], "Invalid rank: %s");
smpi_deployment_register_process(instance_id, rank, index);
- if(smpi_privatize_global_variables){
+ if(smpi_privatize_global_variables == SMPI_PRIVATIZE_MMAP){
/* Now using segment index of the process */
index = proc->segment_index;
/* Done at the process's creation */
if(!(old_type_->flags() & DT_FLAG_DERIVED)){
oldbuf = buf_;
if (!process->replaying() && oldbuf != nullptr && size_!=0){
- if((smpi_privatize_global_variables != 0)
+ if((smpi_privatize_global_variables == SMPI_PRIVATIZE_MMAP)
&& (static_cast<char*>(buf_) >= smpi_start_data_exe)
&& (static_cast<char*>(buf_) < smpi_start_data_exe + smpi_size_data_exe )){
XBT_DEBUG("Privatization : We are sending from a zone inside global memory. Switch data segment ");
if((((req->flags_ & ACCUMULATE) != 0) || (datatype->flags() & DT_FLAG_DERIVED)) && (!smpi_is_shared(req->old_buf_))){
if (!smpi_process()->replaying()){
- if( smpi_privatize_global_variables != 0 && (static_cast<char*>(req->old_buf_) >= smpi_start_data_exe)
+ if( smpi_privatize_global_variables == SMPI_PRIVATIZE_MMAP && (static_cast<char*>(req->old_buf_) >= smpi_start_data_exe)
&& ((char*)req->old_buf_ < smpi_start_data_exe + smpi_size_data_exe )){
XBT_VERB("Privatization : We are unserializing to a zone in global memory Switch data segment ");
smpi_switch_data_segment(smpi_process()->index());
list_set CFLAGS
list_set LINKARGS
if [ "@WIN32@" != "1" ]; then
- list_add CFLAGS "-Dmain=smpi_simulated_main_"
- list_add LINKARGS "-lsimgrid"
+ # list_add CFLAGS "-Dmain=smpi_simulated_main_"
+ list_add CFLAGS "-fpic"
+ list_add LINKARGS "-shared" "-lsimgrid"
else
list_add CFLAGS "-include" "@includedir@/smpi/smpi_main.h"
list_add LINKARGS "@libdir@\libsimgrid.dll"
list_set CXXFLAGS
list_set LINKARGS
if [ "@WIN32@" != "1" ]; then
- list_add CXXFLAGS "-Dmain=smpi_simulated_main_"
- list_add LINKARGS "-lsimgrid"
+ # list_add CXXFLAGS "-Dmain=smpi_simulated_main_"
+ list_add CXXFLAGS "-fpic"
+ list_add LINKARGS "-shared" "-lsimgrid"
else
list_add CXXFLAGS "-include" "@includedir@/smpi/smpi_main.h"
list_add LINKARGS "@libdir@\libsimgrid.dll"
@SMPITOOLS_SH@
-list_set FFLAGS "-ff2c" "-fno-second-underscore"
-list_set LINKARGS "-lsimgrid" "-lm" "-lgfortran"
+list_set FFLAGS "-fpic" "-ff2c" "-fno-second-underscore"
+list_set LINKARGS "-shared" "-lsimgrid" "-lm" "-lgfortran"
list_set TMPFILES
main_name=main
@SMPITOOLS_SH@
-list_set FFLAGS "-ff2c" "-fno-second-underscore"
-list_set LINKARGS "-lsimgrid" "-lm" "-lgfortran"
+list_set FFLAGS "-fpic" "-ff2c" "-fno-second-underscore"
+list_set LINKARGS "-shared" "-lsimgrid" "-lm" "-lgfortran"
list_set TMPFILES
main_name=main
exit
fi
-if [ -n "$WRAPPER" ]; then
- EXEC="$WRAPPER $1"
-else
- EXEC="$1"
-fi
+EXEC="$1"
shift
# steel --cfg and --logs options
# * The FD 3 is used to temporarily store FD 1. This is because the shell connects FD 1 to /dev/null when the command
# is launched in the background: this can be overriden in bash but not in standard bourne shell.
exec 3<&0
-${EXEC} ${PRIVATIZE} ${TRACEOPTIONS} ${SIMOPTS} ${PLATFORMTMP} ${APPLICATIONTMP} <&3 3>&- &
+${WRAPPER} "@SMPIMAIN@" ${EXEC} ${TRACEOPTIONS} ${SIMOPTS} ${PLATFORMTMP} ${APPLICATIONTMP} <&3 3>&- &
pid=$!
exec 3>&-
wait $pid
${CMAKE_BINARY_DIR}/bin/smpicc
${CMAKE_BINARY_DIR}/bin/smpicxx
${CMAKE_BINARY_DIR}/bin/smpirun
+ ${CMAKE_BINARY_DIR}/bin/smpimain
DESTINATION $ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/bin/)
if(SMPI_FORTRAN)
install(PROGRAMS
# Compute the dependencies of SMPI
##################################
+
+if(enable_smpi)
+ set(SIMGRID_DEP "${SIMGRID_DEP} ${DL_LIBRARY}") # for privatization
+ add_executable(smpimain src/smpi/smpi_main.c)
+ target_link_libraries(smpimain simgrid)
+ set_target_properties(smpimain
+ PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
+endif()
+
if(enable_smpi AND APPLE)
set(SIMGRID_DEP "${SIMGRID_DEP} -Wl,-U -Wl,_smpi_simulated_main")
endif()