From fcc23e94f0c0b99a249d74a5393074ccab280fff Mon Sep 17 00:00:00 2001 From: Maxwell Pirtle Date: Fri, 19 Apr 2024 18:31:04 -0400 Subject: [PATCH 001/161] Ensure volatile_mem_stream works with an example This commit introduces an example program that tests some of the functionality of `volatile_mem_stream`. The changes introduced will be used in McMini later on --- docs/design/CMakeLists.txt | 3 +- .../mcmini/coordinator/coordinator.hpp | 13 +++--- .../mcmini/detail/volatile_mem_stream.hpp | 42 +++++++++++++------ docs/design/include/mcmini/model/state.hpp | 4 ++ .../mcmini/model/transition_registry.hpp | 3 +- .../model/transitions/thread/thread_start.hpp | 2 +- .../mcmini/model_checking/algorithm.hpp | 1 + .../include/mcmini/real_world/process.hpp | 17 ++++++-- .../process/fork_process_source.hpp | 2 +- .../process/local_linux_process.hpp | 16 +++---- .../mcmini/real_world/process_source.hpp | 20 +++++++++ .../include/mcmini/real_world/runner.hpp | 8 ---- docs/design/include/mcmini/real_world/shm.hpp | 7 +--- docs/design/src/examples/CMakeLists.txt | 2 + .../src/examples/transition-api-demo.cpp | 2 +- .../src/examples/volatile_mem_stream.cpp | 36 ++++++++++++++++ .../src/mcmini/coordinator/coordinator.cpp | 24 +++++++++-- docs/design/src/mcmini/mcmini.cpp | 9 +--- .../algorithms/classic_dpor.cpp | 10 ++++- .../mcmini/real_world/local_linux_process.cpp | 39 ++++++++++++++--- docs/design/src/mcmini/real_world/shm.cpp | 4 -- 21 files changed, 190 insertions(+), 74 deletions(-) create mode 100644 docs/design/src/examples/volatile_mem_stream.cpp diff --git a/docs/design/CMakeLists.txt b/docs/design/CMakeLists.txt index de7598cc..8b509daa 100644 --- a/docs/design/CMakeLists.txt +++ b/docs/design/CMakeLists.txt @@ -35,7 +35,6 @@ set(LIBMCMINI_C_SRC src/libmcmini/entry.c src/libmcmini/wrappers.c ) -set(LIBMCMINI_CPP_SRC) # -Wall -> be strict with warnings set(LIBMCMINI_EXTRA_COMPILER_FLAGS -Wall -Werror) @@ -48,7 +47,7 @@ set(LIBMCMINI_EXTRA_COMPILER_DEFINITIONS MC_SHARED_LIBRARY=1) set(LIBMCMINI_EXTRA_LINK_FLAGS -lrt -pthread -lm -ldl) # libmcmini.so -> the dylib which is loaded -add_library(libmcmini SHARED "${LIBMCMINI_CPP_SRC}""${LIBMCMINI_C_SRC}") +add_library(libmcmini SHARED "${LIBMCMINI_C_SRC}") add_library(McMini::Dylib ALIAS libmcmini) set_target_properties(libmcmini PROPERTIES OUTPUT_NAME "mcmini") diff --git a/docs/design/include/mcmini/coordinator/coordinator.hpp b/docs/design/include/mcmini/coordinator/coordinator.hpp index d4bbe1ce..a195bb52 100644 --- a/docs/design/include/mcmini/coordinator/coordinator.hpp +++ b/docs/design/include/mcmini/coordinator/coordinator.hpp @@ -108,11 +108,12 @@ class coordinator { } /** - * @brief Returns the number of steps into the program the coordinator has - * directed programs. + * @brief Returns the number of steps (thread routine/visible operation calls) + * intercepted by `libmcmini.so` loaded into the current live process the + * coordinator is managing. * * The depth into the program is the number of transitions which have been - * executed by the coordinator. + * scheduled for execution by the coordinator. */ uint32_t get_depth_into_program() const { return current_program_model.get_trace().count(); @@ -128,8 +129,8 @@ class coordinator { * * The method has no effect if `n == get_depth_into_program()`. * - * @throws an exception is raised if the step `n` r - * + * @throws an exception is raised if the step `n` is greater than then the + * execution depth of the current program. */ void return_to_depth(uint32_t n); @@ -156,6 +157,8 @@ class coordinator { * newly discovered. After execution, any such objects will be recorded. * * @param id the runner (thread) which should run. + * @param execution_exception is raised if the runner cannot be executed by + * the coordinator. */ void execute_runner(runner::runner_id_t id); diff --git a/docs/design/include/mcmini/detail/volatile_mem_stream.hpp b/docs/design/include/mcmini/detail/volatile_mem_stream.hpp index 2e22d652..e6787523 100644 --- a/docs/design/include/mcmini/detail/volatile_mem_stream.hpp +++ b/docs/design/include/mcmini/detail/volatile_mem_stream.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include "mcmini/real_world/shm.hpp" @@ -17,9 +18,6 @@ struct volatile_mem_stream : public std::streambuf { } void reset() { - std::copy(read_write_region->byte_stream(), - read_write_region->byte_stream() + read_write_region->size(), - volatile_cache); setg(volatile_cache, volatile_cache, volatile_cache + read_write_region->size()); setp(volatile_cache, volatile_cache + read_write_region->size()); @@ -28,18 +26,36 @@ struct volatile_mem_stream : public std::streambuf { ~volatile_mem_stream() { delete[] volatile_cache; } protected: - std::streamsize xsputn(const char *s, std::streamsize n) override { - std::ptrdiff_t n_bounded = n; // std::min(egptr() - gptr(), n); - std::copy(s, s + n_bounded, pptr()); - pbump(n_bounded); /* Move the write head */ - return n_bounded; + int_type underflow() override { + std::copy(read_write_region->byte_stream(), + read_write_region->byte_stream() + read_write_region->size(), + volatile_cache); + setg(volatile_cache, volatile_cache, + volatile_cache + read_write_region->size()); + return this->gptr() != this->egptr() ? traits_type::to_int_type(*gptr()) + : traits_type::eof(); } - std::streamsize xsgetn(char *s, std::streamsize n) override { - std::ptrdiff_t n_bounded = n; // std::min(egptr() - gptr(), n); - std::copy(gptr(), gptr() + n_bounded, s); - gbump(n_bounded); /* Move the read head */ - return n_bounded; + + int_type overflow(int_type ch = traits_type::eof()) override { + std::copy(volatile_cache, volatile_cache + read_write_region->size(), + read_write_region->byte_stream()); + setp(volatile_cache, volatile_cache + read_write_region->size()); + return this->pptr() != this->epptr() ? ch : traits_type::eof(); } + + // std::streamsize xsputn(const char *s, std::streamsize n) override { + // std::ptrdiff_t n_bounded = n; // std::min(egptr() - gptr(), n); + // auto *j = pptr(); + // std::copy(s, s + n_bounded, pptr()); + // pbump(n_bounded); /* Move the write head */ + // return n_bounded; + // } + // std::streamsize xsgetn(char *s, std::streamsize n) override { + // std::ptrdiff_t n_bounded = n; // std::min(egptr() - gptr(), n); + // std::copy(gptr(), gptr() + n_bounded, s); + // gbump(n_bounded); /* Move the read head */ + // return n_bounded; + // } int sync() override { std::copy(volatile_cache, volatile_cache + read_write_region->size(), read_write_region->byte_stream()); diff --git a/docs/design/include/mcmini/model/state.hpp b/docs/design/include/mcmini/model/state.hpp index 2bca97f6..372d05b2 100644 --- a/docs/design/include/mcmini/model/state.hpp +++ b/docs/design/include/mcmini/model/state.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -100,4 +101,7 @@ class mutable_state : public state { } }; +constexpr static auto invalid_obj_id = + std::numeric_limits::max(); + } // namespace model \ No newline at end of file diff --git a/docs/design/include/mcmini/model/transition_registry.hpp b/docs/design/include/mcmini/model/transition_registry.hpp index 13d73347..2e4eee88 100644 --- a/docs/design/include/mcmini/model/transition_registry.hpp +++ b/docs/design/include/mcmini/model/transition_registry.hpp @@ -72,9 +72,10 @@ class transition_registry final { * id this registry assigned. * * @returns a function pointer which can produce a new transition of the type - * assigned id `rttid`. + * assigned id `rttid`, or `nullptr` if no such `rttid` has been registered. */ transition_discovery_callback get_callback_for(runtime_type_id rttid) { + if (this->runtime_callbacks.size() <= rttid) return nullptr; return this->runtime_callbacks.at(rttid); } diff --git a/docs/design/include/mcmini/model/transitions/thread/thread_start.hpp b/docs/design/include/mcmini/model/transitions/thread/thread_start.hpp index 0e098fd7..df402de4 100644 --- a/docs/design/include/mcmini/model/transitions/thread/thread_start.hpp +++ b/docs/design/include/mcmini/model/transitions/thread/thread_start.hpp @@ -25,7 +25,7 @@ struct thread_start : public model::transition { } std::string to_string() const override { - return "thread_start(" + std::to_string(thread_id) + ")"; + return "thread_start(thread:" + std::to_string(thread_id) + ")"; } }; diff --git a/docs/design/include/mcmini/model_checking/algorithm.hpp b/docs/design/include/mcmini/model_checking/algorithm.hpp index cbba328d..86126ff8 100644 --- a/docs/design/include/mcmini/model_checking/algorithm.hpp +++ b/docs/design/include/mcmini/model_checking/algorithm.hpp @@ -17,6 +17,7 @@ class algorithm { // TODO: struct callbacks { public: + virtual void encountered_unknown_error_in(const model::program &) {} virtual void encountered_deadlock_in(const model::program &) {} virtual void encountered_crash_in(const model::program &) {} virtual void encountered_data_race_in(const model::program &) {} diff --git a/docs/design/include/mcmini/real_world/process.hpp b/docs/design/include/mcmini/real_world/process.hpp index d00f8e33..12fd52fc 100644 --- a/docs/design/include/mcmini/real_world/process.hpp +++ b/docs/design/include/mcmini/real_world/process.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -32,14 +33,20 @@ struct process { std::unordered_map> runners; public: - virtual ~process() = default; + struct execution_exception : public std::runtime_error { + explicit execution_exception(const char *c) : std::runtime_error(c) {} + explicit execution_exception(const std::string &s) + : std::runtime_error(s) {} + }; /** * @brief Schedule the runner with id `id` for execution. * * This method signals the proxy process to resume execution of the runner - * with id `mcmini_runner_id`. The method blocks until the runner reaches the - * next semantically interesting point of execution according to that runner. + * with id `mcmini_runner_id`. The method blocks until the runner reaches + the + * next semantically interesting point of execution according to that + runner. * * @note The process may not actually contain a runner with id * `mcmini_runner_id`, or the runner with the id `mcmini_runner_id` may be @@ -53,6 +60,9 @@ struct process { * `model::transition_registry::rttid`. The McMini coordinator will * use this identifier to invoke the appropriate callback function to * transform the remaining contents of the stream into its model. + * @throws an `execution_exception` is raised if the provided runner doesn't + * yet exist or if the runner exists but something went wrong during + * execution. */ virtual std::istream &execute_runner(runner_id_t mcmini_runner_id) = 0; @@ -62,6 +72,7 @@ struct process { // threads, etc.) the idea of "adding" a new slot for a runner dynamically // might be needed. runner_id_t add_runner(std::unique_ptr // new_runner); + virtual ~process() = default; }; } // namespace real_world \ No newline at end of file diff --git a/docs/design/include/mcmini/real_world/process/fork_process_source.hpp b/docs/design/include/mcmini/real_world/process/fork_process_source.hpp index 966038a3..89e21192 100644 --- a/docs/design/include/mcmini/real_world/process/fork_process_source.hpp +++ b/docs/design/include/mcmini/real_world/process/fork_process_source.hpp @@ -18,7 +18,7 @@ class fork_process_source : public process_source { private: // The name of the program which we should exec() into with libmcmini.so // preloaded. - std::string target_program; // TODO: Favor std::filesystem::path if C++17 + std::string target_program; // NOTE: Favor std::filesystem::path if C++17 // is eventually supported // Alternatively, have McMini conditionally // compile a std::filesystem::path e.g. diff --git a/docs/design/include/mcmini/real_world/process/local_linux_process.hpp b/docs/design/include/mcmini/real_world/process/local_linux_process.hpp index bfad4133..4dc50f52 100644 --- a/docs/design/include/mcmini/real_world/process/local_linux_process.hpp +++ b/docs/design/include/mcmini/real_world/process/local_linux_process.hpp @@ -25,20 +25,16 @@ class local_linux_process : public process { // memory region. Even in the runner model, the thread runners would each // share the memory region but it wouldn't be attached to the processes // themselves - - static shared_memory_region read_write_region; + static void initialize_shared_memory(); + static std::unique_ptr rw_region; + static std::unique_ptr vms; + std::istream rw_region_stream; public: local_linux_process() = default; - local_linux_process(pid_t pid) : pid(pid) {} + local_linux_process(pid_t pid); virtual ~local_linux_process(); - std::istream& execute_runner(runner_id_t mcmini_runner_id) override; - - // Initialize the shared memory region - static void initializeSharedMemory(const std::string& shm_file_name, - size_t region_size) { - read_write_region = shared_memory_region(shm_file_name, region_size); - } + std::istream &execute_runner(runner_id_t mcmini_runner_id) override; }; } // namespace real_world diff --git a/docs/design/include/mcmini/real_world/process_source.hpp b/docs/design/include/mcmini/real_world/process_source.hpp index c0a78136..2d405cf8 100644 --- a/docs/design/include/mcmini/real_world/process_source.hpp +++ b/docs/design/include/mcmini/real_world/process_source.hpp @@ -16,6 +16,13 @@ namespace real_world { */ class process_source { public: + struct process_creation_exception : public std::runtime_error { + explicit process_creation_exception(const char *c) + : std::runtime_error(c) {} + explicit process_creation_exception(const std::string &s) + : std::runtime_error(s) {} + }; + /** * @brief Spawn a new process starting from the the fixed point of this * process source. @@ -27,6 +34,19 @@ class process_source { * to tell us what the issue was and would make for better error outputs. */ virtual std::unique_ptr make_new_process() = 0; + + /** + * @brief Attempts to make a new process and raises a + * `process_soprocess_creation_exception` on failure + */ + std::unique_ptr force_new_process() { + auto p = make_new_process(); + if (!p) { + throw process_creation_exception("Process creation failed"); + } + return p; + } + virtual ~process_source() = default; }; diff --git a/docs/design/include/mcmini/real_world/runner.hpp b/docs/design/include/mcmini/real_world/runner.hpp index 3c40e9d3..443a9e64 100644 --- a/docs/design/include/mcmini/real_world/runner.hpp +++ b/docs/design/include/mcmini/real_world/runner.hpp @@ -19,12 +19,4 @@ class runner { private: public: using runner_id_t = uint32_t; - - /** - * - * - */ - virtual std::istream &continue_to_next_transition( - const model::transition ¤t_transition) = 0; - virtual ~runner() = default; }; \ No newline at end of file diff --git a/docs/design/include/mcmini/real_world/shm.hpp b/docs/design/include/mcmini/real_world/shm.hpp index 8c4bb36b..7ed1fb4a 100644 --- a/docs/design/include/mcmini/real_world/shm.hpp +++ b/docs/design/include/mcmini/real_world/shm.hpp @@ -14,17 +14,12 @@ namespace real_world { */ struct shared_memory_region { public: - /** - * @brief Create a new shared memory - */ shared_memory_region(const std::string& shm_file_name, size_t region_size); - virtual ~shared_memory_region(); - - // Ownership can only be passed -- prevent copying to prevent shared_memory_region(const shared_memory_region&) = delete; shared_memory_region(shared_memory_region&&) = default; shared_memory_region& operator=(const shared_memory_region&) = delete; shared_memory_region& operator=(shared_memory_region&&) = default; + virtual ~shared_memory_region(); volatile void* get() const { return this->shm_mmap_region; } volatile void* contents() const { return get(); } diff --git a/docs/design/src/examples/CMakeLists.txt b/docs/design/src/examples/CMakeLists.txt index 5c86ebe2..6ca3e08d 100644 --- a/docs/design/src/examples/CMakeLists.txt +++ b/docs/design/src/examples/CMakeLists.txt @@ -1,2 +1,4 @@ add_executable(hello-world hello-world.c) +add_executable(vms volatile_mem_stream.cpp ../../src/mcmini/real_world/shm.cpp) +target_include_directories(vms PUBLIC ../../include) target_link_libraries(hello-world PUBLIC -pthread) diff --git a/docs/design/src/examples/transition-api-demo.cpp b/docs/design/src/examples/transition-api-demo.cpp index 784a67ce..67f2bbdd 100644 --- a/docs/design/src/examples/transition-api-demo.cpp +++ b/docs/design/src/examples/transition-api-demo.cpp @@ -282,7 +282,7 @@ // int main() { // add_example(); -// add_example(); +// add_example(); // add_example(); // transition* sub1 = new transitionSub1(); diff --git a/docs/design/src/examples/volatile_mem_stream.cpp b/docs/design/src/examples/volatile_mem_stream.cpp new file mode 100644 index 00000000..eb5c5d73 --- /dev/null +++ b/docs/design/src/examples/volatile_mem_stream.cpp @@ -0,0 +1,36 @@ +#include "mcmini/detail/volatile_mem_stream.hpp" + +#include +#include +#include + +#include "mcmini/real_world/shm.hpp" + +int main() { + real_world::shared_memory_region smr{"hello", 100}; + + volatile int *smr_bytes = smr.as_stream_of(); + + // std::memset((void *)smr.get(), 0x22, smr.size()); + volatile_mem_stream vms{&smr}; + + uint32_t my_val = UINT32_MAX; + + auto *j = std::cout.rdbuf(&vms); + std::cout.write((char *)(&my_val), sizeof(my_val)); + std::cout.write((char *)(&my_val), sizeof(my_val)); + std::cout.flush(); + std::cout.rdbuf(j); + + j = std::cin.rdbuf(&vms); + + std::cin.read((char *)(&my_val), sizeof(my_val)); + + std::cin.rdbuf(j); + + std::cout << "Value?: " << std::hex << my_val << " whoaa" << std::endl; + + system("xxd /dev/shm/hello"); + + return 0; +} diff --git a/docs/design/src/mcmini/coordinator/coordinator.cpp b/docs/design/src/mcmini/coordinator/coordinator.cpp index 44347bc4..82303e33 100644 --- a/docs/design/src/mcmini/coordinator/coordinator.cpp +++ b/docs/design/src/mcmini/coordinator/coordinator.cpp @@ -1,5 +1,7 @@ #include "mcmini/coordinator/coordinator.hpp" +#include "mcmini/real_world/process.hpp" + coordinator::coordinator( model::program &&initial_state, model::transition_registry &&runtime_transition_mapping, @@ -7,16 +9,19 @@ coordinator::coordinator( : current_program_model(std::move(initial_state)), runtime_transition_mapping(std::move(runtime_transition_mapping)), process_source(std::move(process_source)) { - // TODO: This may not be appropriate at construction time. Probably we only - // need to do this when we actual ask the coordinator to do anything. - this->current_process_handle = this->process_source->make_new_process(); + this->current_process_handle = this->process_source->force_new_process(); } void coordinator::execute_runner(runner::runner_id_t runner_id) { + if (!current_process_handle) { + throw real_world::process::execution_exception( + "Failed to execute runner with id \"" + std::to_string(runner_id) + + "\": the process is not alive"); + } std::istream &wrapper_response_stream = this->current_process_handle->execute_runner(runner_id); - model::transition_registry::runtime_type_id transition_registry_id; + model::transition_registry::runtime_type_id transition_registry_id; wrapper_response_stream >> transition_registry_id; // TODO: Handle the case where lookup fails (this indicates a failure on the @@ -24,8 +29,19 @@ void coordinator::execute_runner(runner::runner_id_t runner_id) { model_to_system_map remote_address_mapping = model_to_system_map(*this); model::transition_registry::transition_discovery_callback callback_function = runtime_transition_mapping.get_callback_for(transition_registry_id); + if (!callback_function) { + throw real_world::process::execution_exception( + "Execution resulted in a runner scheduled to execute the transition " + "type with the RTTID '" + + std::to_string(transition_registry_id) + + "' but this identifier has not been registered before model checking " + "began. Double check that the coordinator was properly configured " + "before launch; otherwise, please report this as a bug in " + "libmcmini.so with this message."); + } std::unique_ptr transition_encountered_at_runtime = callback_function(wrapper_response_stream, remote_address_mapping); + this->current_program_model.model_executing_runner( runner_id, std::move(transition_encountered_at_runtime)); } diff --git a/docs/design/src/mcmini/mcmini.cpp b/docs/design/src/mcmini/mcmini.cpp index 6cf5b57c..1b3e0768 100644 --- a/docs/design/src/mcmini/mcmini.cpp +++ b/docs/design/src/mcmini/mcmini.cpp @@ -32,7 +32,7 @@ void do_model_checking( using namespace model; using namespace real_world; - detached_state state_of_program_at_main; + state_sequence state_of_program_at_main; pending_transitions initial_first_steps; state::objid_t thread_id = @@ -40,11 +40,6 @@ void do_model_checking( initial_first_steps.displace_transition_for( 0, make_unique(thread_id)); - state_sequence ss; - ss.follow(*initial_first_steps.get_transition_for_runner(0)); - - std::exit(EXIT_SUCCESS); - /* TODO: Complete the initialization of the initial state here, i.e. a single thread "main" that is alive and then running the transition `t` @@ -55,7 +50,7 @@ void do_model_checking( // For "vanilla" model checking where we start at the beginning of the // program, a fork_process_source suffices (fork() + exec() brings us to the // beginning) - auto process_source = make_unique("demo"); + auto process_source = make_unique("hello-world"); coordinator coordinator(std::move(model_for_program_starting_at_main), transition_registry(), std::move(process_source)); diff --git a/docs/design/src/mcmini/model_checking/algorithms/classic_dpor.cpp b/docs/design/src/mcmini/model_checking/algorithms/classic_dpor.cpp index e2eff135..3f29b23a 100644 --- a/docs/design/src/mcmini/model_checking/algorithms/classic_dpor.cpp +++ b/docs/design/src/mcmini/model_checking/algorithms/classic_dpor.cpp @@ -37,8 +37,14 @@ void classic_dpor::verify_using(coordinator &coordinator, // For now, we simply tell the coordinator to run one thread for a few steps, // backtrack once, and then exit - coordinator.execute_runner(0); - coordinator.execute_runner(0); + + // Execution may fail... we should raise an exception in these cases + try { + coordinator.execute_runner(0); + coordinator.execute_runner(0); + } catch (real_world::process::execution_exception &e) { + std::cerr << "Error: " << e.what() << std::endl; + } } // void classic_dpor::dynamicallyUpdateBacktrackSets() { diff --git a/docs/design/src/mcmini/real_world/local_linux_process.cpp b/docs/design/src/mcmini/real_world/local_linux_process.cpp index 5215087f..d56e244a 100644 --- a/docs/design/src/mcmini/real_world/local_linux_process.cpp +++ b/docs/design/src/mcmini/real_world/local_linux_process.cpp @@ -3,10 +3,29 @@ #include #include +#include #include #include +#include + +#include "mcmini/misc/extensions/unique_ptr.hpp" using namespace real_world; +using namespace extensions; + +std::unique_ptr local_linux_process::rw_region = nullptr; +std::unique_ptr local_linux_process::vms = nullptr; + +void local_linux_process::initialize_shared_memory() { + rw_region = make_unique("hello", 100); + vms = make_unique(rw_region.get()); +} + +local_linux_process::local_linux_process(pid_t pid) + : pid(pid), rw_region_stream(nullptr) { + static std::once_flag shm_once_flag; + std::call_once(shm_once_flag, initialize_shared_memory); +} local_linux_process::~local_linux_process() { if (pid <= 0) { @@ -30,14 +49,22 @@ local_linux_process::~local_linux_process() { } } -std::istream &local_linux_process::execute_runner( +std::istream& local_linux_process::execute_runner( runner_id_t mcmini_runner_id) { /* TODO: sem_wait + sem_post pair */ - /* TODO: Reset the std::streambuf wrapper to point to the beginning of the - * read end */ + auto* j = rw_region_stream.rdbuf(vms.get()); + auto* j2 = rw_region_stream.rdbuf(vms.get()); + assert(j2 == vms.get()); + // rw_region_stream.seekg(0); + + if (rw_region_stream.fail()) { + abort(); + } + + // Simulate this for now: write in some value + volatile uint32_t* v = rw_region->as_stream_of(); + std::memset((void*)v, 10, rw_region->size()); - /* TODO: Return the wrapper (obviously not as a dangling reference)*/ - std::istream a(nullptr); - return a; + return rw_region_stream; } \ No newline at end of file diff --git a/docs/design/src/mcmini/real_world/shm.cpp b/docs/design/src/mcmini/real_world/shm.cpp index 6bb755f4..5dbb942e 100644 --- a/docs/design/src/mcmini/real_world/shm.cpp +++ b/docs/design/src/mcmini/real_world/shm.cpp @@ -13,10 +13,6 @@ using namespace real_world; shared_memory_region::shared_memory_region(const std::string &shm_file_name, size_t region_size) : shm_file_name(shm_file_name), region_size(region_size) { - // TODO: We should figure out how to handle errors on failure besides simply - // exiting. Probably we should have a factory that can return a result of - // either the region or some sort of failure condition. - // This creates a file in /dev/shm/ int fd = shm_open(shm_file_name.c_str(), O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); if (fd == -1) { From 7f12a8a430460130ee65fe3b738aea31bf20f784 Mon Sep 17 00:00:00 2001 From: Maxwell Pirtle Date: Fri, 19 Apr 2024 18:36:37 -0400 Subject: [PATCH 002/161] Remove `mcmini/detail` folder and favor `misc` --- docs/design/include/mcmini/{detail => misc}/ddt.hpp | 0 .../include/mcmini/{detail => misc}/volatile_mem_stream.hpp | 0 .../include/mcmini/real_world/process/local_linux_process.hpp | 2 +- docs/design/src/examples/volatile_mem_stream.cpp | 2 +- docs/design/src/mcmini/mcmini.cpp | 2 +- 5 files changed, 3 insertions(+), 3 deletions(-) rename docs/design/include/mcmini/{detail => misc}/ddt.hpp (100%) rename docs/design/include/mcmini/{detail => misc}/volatile_mem_stream.hpp (100%) diff --git a/docs/design/include/mcmini/detail/ddt.hpp b/docs/design/include/mcmini/misc/ddt.hpp similarity index 100% rename from docs/design/include/mcmini/detail/ddt.hpp rename to docs/design/include/mcmini/misc/ddt.hpp diff --git a/docs/design/include/mcmini/detail/volatile_mem_stream.hpp b/docs/design/include/mcmini/misc/volatile_mem_stream.hpp similarity index 100% rename from docs/design/include/mcmini/detail/volatile_mem_stream.hpp rename to docs/design/include/mcmini/misc/volatile_mem_stream.hpp diff --git a/docs/design/include/mcmini/real_world/process/local_linux_process.hpp b/docs/design/include/mcmini/real_world/process/local_linux_process.hpp index 4dc50f52..c4f4dd09 100644 --- a/docs/design/include/mcmini/real_world/process/local_linux_process.hpp +++ b/docs/design/include/mcmini/real_world/process/local_linux_process.hpp @@ -4,7 +4,7 @@ #include #include -#include "mcmini/detail/volatile_mem_stream.hpp" +#include "mcmini/misc/volatile_mem_stream.hpp" #include "mcmini/real_world/process.hpp" #include "mcmini/real_world/shm.hpp" diff --git a/docs/design/src/examples/volatile_mem_stream.cpp b/docs/design/src/examples/volatile_mem_stream.cpp index eb5c5d73..7912511c 100644 --- a/docs/design/src/examples/volatile_mem_stream.cpp +++ b/docs/design/src/examples/volatile_mem_stream.cpp @@ -1,4 +1,4 @@ -#include "mcmini/detail/volatile_mem_stream.hpp" +#include "mcmini/misc/volatile_mem_stream.hpp" #include #include diff --git a/docs/design/src/mcmini/mcmini.cpp b/docs/design/src/mcmini/mcmini.cpp index 1b3e0768..f4ff56b7 100644 --- a/docs/design/src/mcmini/mcmini.cpp +++ b/docs/design/src/mcmini/mcmini.cpp @@ -1,7 +1,7 @@ #include "mcmini/mcmini.hpp" #include "mcmini/coordinator/coordinator.hpp" -#include "mcmini/detail/ddt.hpp" +#include "mcmini/misc/ddt.hpp" #include "mcmini/misc/extensions/unique_ptr.hpp" #include "mcmini/model/state/detached_state.hpp" #include "mcmini/model/transitions/mutex/mutex_init.hpp" From 45147e65329f56002b6e16bec80a099b079d1a1e Mon Sep 17 00:00:00 2001 From: Maxwell Pirtle Date: Fri, 19 Apr 2024 18:59:17 -0400 Subject: [PATCH 003/161] Ensure `remote_address` is hashable This commit adds a partial specialization for `real_world::remote_address` by simply treating it as any normal address. --- .../mcmini/coordinator/coordinator.hpp | 12 +++---- .../include/mcmini/real_world/process.hpp | 19 ++++------- .../mcmini/real_world/remote_address.hpp | 33 +++++++++++++------ .../include/mcmini/real_world/runner.hpp | 22 ------------- .../src/examples/volatile_mem_stream.cpp | 5 ++- .../src/mcmini/coordinator/coordinator.cpp | 4 ++- 6 files changed, 42 insertions(+), 53 deletions(-) delete mode 100644 docs/design/include/mcmini/real_world/runner.hpp diff --git a/docs/design/include/mcmini/coordinator/coordinator.hpp b/docs/design/include/mcmini/coordinator/coordinator.hpp index a195bb52..ca73a657 100644 --- a/docs/design/include/mcmini/coordinator/coordinator.hpp +++ b/docs/design/include/mcmini/coordinator/coordinator.hpp @@ -6,6 +6,7 @@ #include "mcmini/model/transition_registry.hpp" #include "mcmini/model/visible_object.hpp" #include "mcmini/real_world/process_source.hpp" +#include "mcmini/real_world/remote_address.hpp" #include "mcmini/real_world/runner.hpp" /** @@ -160,19 +161,16 @@ class coordinator { * @param execution_exception is raised if the runner cannot be executed by * the coordinator. */ - void execute_runner(runner::runner_id_t id); + void execute_runner(real_world::process::runner_id_t id); private: model::program current_program_model; model::transition_registry runtime_transition_mapping; std::unique_ptr current_process_handle; std::unique_ptr process_source; + std::unordered_map, model::state::objid_t> + system_address_mapping; - /// @brief A mapping between remote addresses in the processes produced by - /// `process_source` and those of the - std::unordered_map system_address_mapping; - - /// Allow modifications through the `model_to_system_map` (implementation - /// detail) + // Allow modifications through the `model_to_system_map` (impl detail) friend model_to_system_map; }; \ No newline at end of file diff --git a/docs/design/include/mcmini/real_world/process.hpp b/docs/design/include/mcmini/real_world/process.hpp index 12fd52fc..7a9a8de5 100644 --- a/docs/design/include/mcmini/real_world/process.hpp +++ b/docs/design/include/mcmini/real_world/process.hpp @@ -5,7 +5,6 @@ #include #include "mcmini/model/transition.hpp" -#include "mcmini/real_world/runner.hpp" namespace real_world { @@ -30,7 +29,6 @@ namespace real_world { struct process { public: using runner_id_t = uint32_t; - std::unordered_map> runners; public: struct execution_exception : public std::runtime_error { @@ -44,9 +42,8 @@ struct process { * * This method signals the proxy process to resume execution of the runner * with id `mcmini_runner_id`. The method blocks until the runner reaches - the - * next semantically interesting point of execution according to that - runner. + * the next semantically interesting point of execution according to that + * runner. * * @note The process may not actually contain a runner with id * `mcmini_runner_id`, or the runner with the id `mcmini_runner_id` may be @@ -63,15 +60,13 @@ struct process { * @throws an `execution_exception` is raised if the provided runner doesn't * yet exist or if the runner exists but something went wrong during * execution. + * TODO: We assume at the moment that the number of runners is fixed and that + * every call to `execute_runner` above is valid. Eventually, to support more + * complicated runners (e.g. entire processes, a runner representing multiple + * threads, etc.) the idea of "adding" a new slot for a runner dynamically + * might be needed. */ virtual std::istream &execute_runner(runner_id_t mcmini_runner_id) = 0; - - // TODO: We assume at the moment that the number of runners is fixed and that - // every call to `execute_runner` above is valid. Eventually, to support more - // complicated runners (e.g. entire processes, a runner representing multiple - // threads, etc.) the idea of "adding" a new slot for a runner dynamically - // might be needed. runner_id_t add_runner(std::unique_ptr - // new_runner); virtual ~process() = default; }; diff --git a/docs/design/include/mcmini/real_world/remote_address.hpp b/docs/design/include/mcmini/real_world/remote_address.hpp index edb5817f..a4a609c6 100644 --- a/docs/design/include/mcmini/real_world/remote_address.hpp +++ b/docs/design/include/mcmini/real_world/remote_address.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include namespace real_world { @@ -8,24 +9,36 @@ namespace real_world { template struct remote_address final { private: - T* remote_addr; + T* remote_addr = nullptr; T*& get_as_ref() { return remote_addr; } - // template - // friend std::ostream& operator<<(std::ostream& os, remote_address& ra) { - // return (os << static_cast(ra.get_as_ref())); - // } - // template - // friend std::istream& operator>>(std::istream& is, remote_address& ra) { - // return (is >>); - // } + template + friend std::ostream& operator<<(std::ostream& os, remote_address& ra) { + return (os << static_cast(ra.get_as_ref())); + } + template + friend std::istream& operator>>(std::istream& is, remote_address& ra) { + return (is >> ra.get_as_ref()); + } public: T* get() const { return remote_addr; } + remote_address(T* p) : remote_addr(p) {} + + bool operator==(const remote_address& o) const { + return o.remote_addr == remote_addr; + } }; template using remote_object = remote_address; -} // namespace real_world \ No newline at end of file +} // namespace real_world + +template +struct std::hash> { + std::size_t operator()(const real_world::remote_address& o) const { + return std::hash{}(o.get()); + } +}; \ No newline at end of file diff --git a/docs/design/include/mcmini/real_world/runner.hpp b/docs/design/include/mcmini/real_world/runner.hpp deleted file mode 100644 index 443a9e64..00000000 --- a/docs/design/include/mcmini/real_world/runner.hpp +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include "mcmini/model/transition.hpp" - -/** - * @brief Manages the runtime execution of entities that exist outside of the - * McMini model checker. - * - * A `runner` is a bridge between the _model_ in McMini's architecture - * execution units it interacts with. It is a proxy for a single unit that - * McMini takes control over during verification. It can represent a single - * thread, multiple threads, a process, multiple processes, or entire machines. - * It is responsible for enacting the transitions whose execution McMini has - * _simulated_ during verification and informing the model checker of where - * execution has subsequently been suspended (when the entity has encountered - * its next transition). - */ -class runner { - private: - public: - using runner_id_t = uint32_t; -}; \ No newline at end of file diff --git a/docs/design/src/examples/volatile_mem_stream.cpp b/docs/design/src/examples/volatile_mem_stream.cpp index 7912511c..228b6bfa 100644 --- a/docs/design/src/examples/volatile_mem_stream.cpp +++ b/docs/design/src/examples/volatile_mem_stream.cpp @@ -1,9 +1,12 @@ + + #include "mcmini/misc/volatile_mem_stream.hpp" #include #include #include +#include "mcmini/real_world/remote_address.hpp" #include "mcmini/real_world/shm.hpp" int main() { @@ -11,7 +14,7 @@ int main() { volatile int *smr_bytes = smr.as_stream_of(); - // std::memset((void *)smr.get(), 0x22, smr.size()); + // std::memset((void *)smr.get(), 0x22, smr.size());q volatile_mem_stream vms{&smr}; uint32_t my_val = UINT32_MAX; diff --git a/docs/design/src/mcmini/coordinator/coordinator.cpp b/docs/design/src/mcmini/coordinator/coordinator.cpp index 82303e33..51687beb 100644 --- a/docs/design/src/mcmini/coordinator/coordinator.cpp +++ b/docs/design/src/mcmini/coordinator/coordinator.cpp @@ -2,6 +2,8 @@ #include "mcmini/real_world/process.hpp" +using namespace real_world; + coordinator::coordinator( model::program &&initial_state, model::transition_registry &&runtime_transition_mapping, @@ -12,7 +14,7 @@ coordinator::coordinator( this->current_process_handle = this->process_source->force_new_process(); } -void coordinator::execute_runner(runner::runner_id_t runner_id) { +void coordinator::execute_runner(process::runner_id_t runner_id) { if (!current_process_handle) { throw real_world::process::execution_exception( "Failed to execute runner with id \"" + std::to_string(runner_id) + From 35bad89fbe1bb3c7a1fbc31d62a5b5b7c6d71434 Mon Sep 17 00:00:00 2001 From: Maxwell Pirtle Date: Sat, 20 Apr 2024 12:44:03 -0400 Subject: [PATCH 004/161] Add working example with dummy wrapper values This commit introduces a "working" example of McMini managing state changes during execution. Here, we simply insert a value into the mailbox and assume that the thread under the control of libmcmini.so properly directed the thread to write into the correct spot. Subsequent commits will add the coordination between libmcmini.so and McMini. --- .../mcmini/coordinator/coordinator.hpp | 1 - docs/design/include/mcmini/defines.h | 36 +++++++++++++ docs/design/include/mcmini/defines.hpp | 9 ---- .../mcmini/misc/volatile_mem_stream.hpp | 49 ++++++----------- docs/design/include/mcmini/model/program.hpp | 6 +-- docs/design/include/mcmini/model/state.hpp | 4 +- .../mcmini/model/state/detached_state.hpp | 19 +++---- .../mcmini/model/state/state_sequence.hpp | 8 ++- .../mcmini/model/transition_registry.hpp | 8 ++- .../include/mcmini/model/visible_object.hpp | 3 +- .../include/mcmini/real_world/process.hpp | 10 ++-- .../process/local_linux_process.hpp | 3 +- .../real_world/runner_mailbox_stream.hpp | 47 ++++++++++++++++ docs/design/include/mcmini/real_world/shm.hpp | 4 +- .../src/examples/volatile_mem_stream.cpp | 3 +- .../src/mcmini/coordinator/coordinator.cpp | 26 ++++----- docs/design/src/mcmini/mcmini.cpp | 27 ++++++---- .../src/mcmini/model/detached_state.cpp | 9 +++- docs/design/src/mcmini/model/program.cpp | 5 +- .../src/mcmini/model/state_sequence.cpp | 53 ++++++++++++------- .../mcmini/real_world/local_linux_process.cpp | 25 +++------ 21 files changed, 218 insertions(+), 137 deletions(-) create mode 100644 docs/design/include/mcmini/defines.h delete mode 100644 docs/design/include/mcmini/defines.hpp create mode 100644 docs/design/include/mcmini/real_world/runner_mailbox_stream.hpp diff --git a/docs/design/include/mcmini/coordinator/coordinator.hpp b/docs/design/include/mcmini/coordinator/coordinator.hpp index ca73a657..3ed6dca2 100644 --- a/docs/design/include/mcmini/coordinator/coordinator.hpp +++ b/docs/design/include/mcmini/coordinator/coordinator.hpp @@ -7,7 +7,6 @@ #include "mcmini/model/visible_object.hpp" #include "mcmini/real_world/process_source.hpp" #include "mcmini/real_world/remote_address.hpp" -#include "mcmini/real_world/runner.hpp" /** * @brief A mechanism which synchronizes a McMini model of a program diff --git a/docs/design/include/mcmini/defines.h b/docs/design/include/mcmini/defines.h new file mode 100644 index 00000000..ce45543f --- /dev/null +++ b/docs/design/include/mcmini/defines.h @@ -0,0 +1,36 @@ +#pragma once + +#include + +#define MCMINI_INLINE +#define MCMINI_LIBRARY_ENTRY_POINT +#define MCMINI_EXPORT __attribute__((visibility(default))) +#define MCMINI_PRIVATE __attribute__((visibility(hidden))) + +#define MAX_TOTAL_THREADS_IN_PROGRAM (20u) +#define MAX_TOTAL_VISIBLE_OBJECTS_IN_PROGRAM (10000u) +#define MAX_SHARED_MEMORY_ALLOCATION (4096u) +#define MAX_TOTAL_STATE_OBJECTS_IN_PROGRAM \ + (MAX_TOTAL_THREADS_IN_PROGRAM + MAX_TOTAL_VISIBLE_OBJECTS_IN_PROGRAM) + +#define MAX_TOTAL_TRANSITIONS_IN_PROGRAM (1500u) +#define MAX_TOTAL_STATES_IN_STATE_STACK (MAX_TOTAL_TRANSITIONS_IN_PROGRAM + 1u) + +typedef uint64_t tid_t; +typedef uint64_t mutid_t; +typedef uint64_t trid_t; +#define TID_MAIN_THREAD (0ul) +#define TID_INVALID (-1ul) // ULONG_MAX +#define TID_PTHREAD_CREATE_FAILED (-2ul) // ULONG_MAX - 1 +#define MUTID_INVALID (-1ul) // ULONG_MAX + +#define FORK_IS_CHILD_PID(pid) ((pid) == 0) +#define FORK_IS_PARENT_PID(pid) (!(FORK_IS_CHILD_PID(pid))) + +#define MC_CONSTRUCTOR __attribute__((constructor)) + +#define PTHREAD_SUCCESS (0) +#define SEM_FLAG_SHARED (1) + +typedef void *(*thread_routine)(void *); +typedef void (*free_function)(void *); diff --git a/docs/design/include/mcmini/defines.hpp b/docs/design/include/mcmini/defines.hpp deleted file mode 100644 index 95b653eb..00000000 --- a/docs/design/include/mcmini/defines.hpp +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#define MCMINI_INLINE -#define MCMINI_LIBRARY_ENTRY_POINT -#define MCMINI_EXPORT __attribute__((visibility(default))) -#define MCMINI_PRIVATE __attribute__((visibility(hidden))) - -// TODO: Other defines here based on system types etc. -// TODO: Add "extern "C" where appropriate \ No newline at end of file diff --git a/docs/design/include/mcmini/misc/volatile_mem_stream.hpp b/docs/design/include/mcmini/misc/volatile_mem_stream.hpp index e6787523..750e1b42 100644 --- a/docs/design/include/mcmini/misc/volatile_mem_stream.hpp +++ b/docs/design/include/mcmini/misc/volatile_mem_stream.hpp @@ -7,58 +7,43 @@ struct volatile_mem_stream : public std::streambuf { private: - const real_world::shared_memory_region *read_write_region; char *volatile_cache; + size_t rw_region_size; + volatile char *rw_region; public: - volatile_mem_stream(const real_world::shared_memory_region *read_write_region) - : read_write_region(read_write_region), - volatile_cache(new char[read_write_region->size()]) { + volatile_mem_stream(const real_world::shared_memory_region &read_write_region) + : volatile_mem_stream(read_write_region.get(), read_write_region.size()) { + reset(); + } + volatile_mem_stream(volatile void *rw_start, size_t rw_size) + : rw_region(static_cast(rw_start)), + rw_region_size(rw_size), + volatile_cache(new char[rw_size]) { reset(); } void reset() { - setg(volatile_cache, volatile_cache, - volatile_cache + read_write_region->size()); - setp(volatile_cache, volatile_cache + read_write_region->size()); + setg(volatile_cache, volatile_cache, volatile_cache + rw_region_size); + setp(volatile_cache, volatile_cache + rw_region_size); } ~volatile_mem_stream() { delete[] volatile_cache; } protected: int_type underflow() override { - std::copy(read_write_region->byte_stream(), - read_write_region->byte_stream() + read_write_region->size(), - volatile_cache); - setg(volatile_cache, volatile_cache, - volatile_cache + read_write_region->size()); + std::copy(rw_region, rw_region + rw_region_size, volatile_cache); + setg(volatile_cache, volatile_cache, volatile_cache + rw_region_size); return this->gptr() != this->egptr() ? traits_type::to_int_type(*gptr()) : traits_type::eof(); } - int_type overflow(int_type ch = traits_type::eof()) override { - std::copy(volatile_cache, volatile_cache + read_write_region->size(), - read_write_region->byte_stream()); - setp(volatile_cache, volatile_cache + read_write_region->size()); + std::copy(volatile_cache, volatile_cache + rw_region_size, rw_region); + setp(volatile_cache, volatile_cache + rw_region_size); return this->pptr() != this->epptr() ? ch : traits_type::eof(); } - - // std::streamsize xsputn(const char *s, std::streamsize n) override { - // std::ptrdiff_t n_bounded = n; // std::min(egptr() - gptr(), n); - // auto *j = pptr(); - // std::copy(s, s + n_bounded, pptr()); - // pbump(n_bounded); /* Move the write head */ - // return n_bounded; - // } - // std::streamsize xsgetn(char *s, std::streamsize n) override { - // std::ptrdiff_t n_bounded = n; // std::min(egptr() - gptr(), n); - // std::copy(gptr(), gptr() + n_bounded, s); - // gbump(n_bounded); /* Move the read head */ - // return n_bounded; - // } int sync() override { - std::copy(volatile_cache, volatile_cache + read_write_region->size(), - read_write_region->byte_stream()); + std::copy(volatile_cache, volatile_cache + rw_region_size, rw_region); return std::streambuf::sync(); } }; \ No newline at end of file diff --git a/docs/design/include/mcmini/model/program.hpp b/docs/design/include/mcmini/model/program.hpp index 12eee32a..7cfcdaa0 100644 --- a/docs/design/include/mcmini/model/program.hpp +++ b/docs/design/include/mcmini/model/program.hpp @@ -52,16 +52,14 @@ class program { public: using runner_id_t = uint32_t; - program(state &&initial_state, pending_transitions &&initial_first_steps); + program(const state &initial_state, + pending_transitions &&initial_first_steps); program(program &&) = default; program(const program &) = delete; const state_sequence &get_state_sequence() const { return this->state_seq; } const transition_sequence &get_trace() const { return this->trace; } - /** - * @brief Returns a list of runners which are currently enabled. - */ std::unordered_set get_enabled_runners() const { std::unordered_set enabled_runners; for (const auto &runner_and_t : this->next_steps) { diff --git a/docs/design/include/mcmini/model/state.hpp b/docs/design/include/mcmini/model/state.hpp index 372d05b2..f41a0494 100644 --- a/docs/design/include/mcmini/model/state.hpp +++ b/docs/design/include/mcmini/model/state.hpp @@ -21,6 +21,8 @@ class state { virtual size_t count() const = 0; virtual bool contains_object_with_id(objid_t id) const = 0; virtual const visible_object_state *get_state_of_object(objid_t id) const = 0; + virtual std::unique_ptr consume_obj( + objid_t id) && = 0; virtual std::unique_ptr mutable_clone() const = 0; // TODO: Potentially provide an interface here that conforms to C++11's @@ -74,7 +76,7 @@ class mutable_state : public state { * every other id assigned to the objects in this state. */ virtual objid_t add_object( - std::unique_ptr initial_state) = 0; + std::unique_ptr initial_state) = 0; /** * @brief Adds the given state _state_ for the object with id _id_. diff --git a/docs/design/include/mcmini/model/state/detached_state.hpp b/docs/design/include/mcmini/model/state/detached_state.hpp index 7d42cdc7..b69c1a2a 100644 --- a/docs/design/include/mcmini/model/state/detached_state.hpp +++ b/docs/design/include/mcmini/model/state/detached_state.hpp @@ -25,15 +25,16 @@ class detached_state : public model::mutable_state { detached_state &operator=(detached_state &&) = default; /* `state` overrrides */ - virtual bool contains_object_with_id(objid_t id) const override; - virtual size_t count() const override { return visible_objects.size(); } - virtual const visible_object_state *get_state_of_object( - objid_t id) const override; - virtual objid_t add_object( - std::unique_ptr initial_state) override; - virtual void add_state_for( - objid_t id, std::unique_ptr new_state) override; - virtual std::unique_ptr mutable_clone() const override; + size_t count() const override { return visible_objects.size(); } + bool contains_object_with_id(objid_t id) const override; + const visible_object_state *get_state_of_object(objid_t id) const override; + objid_t add_object( + std::unique_ptr initial_state) override; + void add_state_for(objid_t id, + std::unique_ptr new_state) override; + std::unique_ptr consume_obj(objid_t id) && + override; + std::unique_ptr mutable_clone() const override; }; } // namespace model \ No newline at end of file diff --git a/docs/design/include/mcmini/model/state/state_sequence.hpp b/docs/design/include/mcmini/model/state/state_sequence.hpp index e24438b4..e51b0a00 100644 --- a/docs/design/include/mcmini/model/state/state_sequence.hpp +++ b/docs/design/include/mcmini/model/state/state_sequence.hpp @@ -54,6 +54,8 @@ class state_sequence : public mutable_state { virtual bool contains_object_with_id(objid_t id) const override; virtual const visible_object_state *get_state_of_object( objid_t id) const override; + virtual std::unique_ptr + consume_obj(objid_t id) && override; virtual std::unique_ptr mutable_clone() const override; }; @@ -73,7 +75,7 @@ class state_sequence : public mutable_state { public: state_sequence(); state_sequence(const state &); - state_sequence(const state &&); + // state_sequence(state &&); state_sequence(state_sequence &) = delete; state_sequence(state_sequence &&) = default; state_sequence(std::vector &&); @@ -87,9 +89,11 @@ class state_sequence : public mutable_state { virtual const visible_object_state *get_state_of_object( objid_t id) const override; virtual objid_t add_object( - std::unique_ptr initial_state) override; + std::unique_ptr initial_state) override; virtual void add_state_for( objid_t id, std::unique_ptr new_state) override; + virtual std::unique_ptr consume_obj(objid_t id) && + override; virtual std::unique_ptr mutable_clone() const override; /* Applying transitions */ diff --git a/docs/design/include/mcmini/model/transition_registry.hpp b/docs/design/include/mcmini/model/transition_registry.hpp index 2e4eee88..e88bb229 100644 --- a/docs/design/include/mcmini/model/transition_registry.hpp +++ b/docs/design/include/mcmini/model/transition_registry.hpp @@ -6,6 +6,7 @@ #include "mcmini/coordinator/coordinator.hpp" #include "mcmini/model/transition.hpp" +#include "mcmini/real_world/runner_mailbox_stream.hpp" namespace model { @@ -26,8 +27,8 @@ class transition_registry final { public: using runtime_type_id = uint32_t; using rttid = runtime_type_id; - using transition_discovery_callback = - std::unique_ptr (*)(std::istream&, model_to_system_map&); + using transition_discovery_callback = std::unique_ptr (*)( + const real_world::runner_mailbox_stream&, model_to_system_map&); /** * @brief Marks the specified transition subclass as possible to encounter at @@ -37,10 +38,7 @@ class transition_registry final { * associate with the returned id * @returns a positive integer which conceptually represents the transition. */ - template runtime_type_id register_transition(transition_discovery_callback callback) { - static_assert(std::is_base_of::value, - "Must be a subclass of `model::transition`"); // TODO: Mapping between types and the serialization // function pointers. For plugins loaded by McMini, each will have the // chance to register the transitions it defines. Here the RTTI needs to diff --git a/docs/design/include/mcmini/model/visible_object.hpp b/docs/design/include/mcmini/model/visible_object.hpp index d2dd020a..5156349a 100644 --- a/docs/design/include/mcmini/model/visible_object.hpp +++ b/docs/design/include/mcmini/model/visible_object.hpp @@ -85,7 +85,8 @@ class visible_object final { } /// @brief Extracts the current state from this object. - /// @return a pointer to the current state of this object + /// @return a pointer to the current state of this object, or `nullptr` is the + /// object contains no states. std::unique_ptr consume_into_current_state() && { if (history.empty()) { return nullptr; diff --git a/docs/design/include/mcmini/real_world/process.hpp b/docs/design/include/mcmini/real_world/process.hpp index 7a9a8de5..fc1beb85 100644 --- a/docs/design/include/mcmini/real_world/process.hpp +++ b/docs/design/include/mcmini/real_world/process.hpp @@ -1,10 +1,9 @@ #pragma once #include -#include #include -#include "mcmini/model/transition.hpp" +#include "mcmini/real_world/runner_mailbox_stream.hpp" namespace real_world { @@ -52,8 +51,8 @@ struct process { * return. It is up to the caller to ensure that scheduling runner * `mcmini_runner_id` for execution will not block forever. * - * @returns a stream containing the serialized response from the process - * represented by this proxy. The stream must contain as its first element a + * @returns a mailbox containing the serialized response from the process + * represented by this proxy. The mailbox must contain as its first element a * `model::transition_registry::rttid`. The McMini coordinator will * use this identifier to invoke the appropriate callback function to * transform the remaining contents of the stream into its model. @@ -66,7 +65,8 @@ struct process { * threads, etc.) the idea of "adding" a new slot for a runner dynamically * might be needed. */ - virtual std::istream &execute_runner(runner_id_t mcmini_runner_id) = 0; + virtual runner_mailbox_stream &execute_runner( + runner_id_t mcmini_runner_id) = 0; virtual ~process() = default; }; diff --git a/docs/design/include/mcmini/real_world/process/local_linux_process.hpp b/docs/design/include/mcmini/real_world/process/local_linux_process.hpp index c4f4dd09..a5b97c22 100644 --- a/docs/design/include/mcmini/real_world/process/local_linux_process.hpp +++ b/docs/design/include/mcmini/real_world/process/local_linux_process.hpp @@ -28,13 +28,12 @@ class local_linux_process : public process { static void initialize_shared_memory(); static std::unique_ptr rw_region; static std::unique_ptr vms; - std::istream rw_region_stream; public: local_linux_process() = default; local_linux_process(pid_t pid); virtual ~local_linux_process(); - std::istream &execute_runner(runner_id_t mcmini_runner_id) override; + runner_mailbox_stream &execute_runner(runner_id_t mcmini_runner_id) override; }; } // namespace real_world diff --git a/docs/design/include/mcmini/real_world/runner_mailbox_stream.hpp b/docs/design/include/mcmini/real_world/runner_mailbox_stream.hpp new file mode 100644 index 00000000..e7a108a7 --- /dev/null +++ b/docs/design/include/mcmini/real_world/runner_mailbox_stream.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include +#include + +namespace real_world { + +/** + * @brief A location for threads to read and write information when + * communicating with `libmcmini.so`. + * + * Threads and operations managed in `libmcmini.so` communicate with McMini via + * the coordinator (see coordinator.hpp). Communication occurs over some shared + * medium between the McMini process and the ephmeral processes McMini spawns as + * it continues exploration during model checking. Threads communicate with + * McMini via a mailbox, which the coordinator accesses as part of its. + */ +struct runner_mailbox_stream { + private: + mutable std::iostream msg_stream; + + public: + runner_mailbox_stream(runner_mailbox_stream &&) = default; + runner_mailbox_stream(std::streambuf *sb) : msg_stream(sb) {} + + template + void read(T *t) const { + static_assert( + std::is_trivially_copyable::value, + "Only trivially-copyable types (i.e. those for which a `std::memcpy` " + "is well-defined) are permitted in thread mailboxes."); + msg_stream.read(reinterpret_cast(t), sizeof(T)); + } + + template + void write(T *t) { + static_assert( + std::is_trivially_copyable::value, + "Only trivially-copyable types (i.e. those for which a `std::memcpy` " + "is well-defined) are permitted in thread mailboxes."); + msg_stream.write(reinterpret_cast(t), sizeof(T)); + msg_stream.flush(); + } + + ~runner_mailbox_stream() { msg_stream.flush(); } +}; +} // namespace real_world \ No newline at end of file diff --git a/docs/design/include/mcmini/real_world/shm.hpp b/docs/design/include/mcmini/real_world/shm.hpp index 7ed1fb4a..9e9996e1 100644 --- a/docs/design/include/mcmini/real_world/shm.hpp +++ b/docs/design/include/mcmini/real_world/shm.hpp @@ -28,7 +28,9 @@ struct shared_memory_region { volatile T* as_stream_of() const { return static_cast(shm_mmap_region); } - volatile char* byte_stream() const { return as_stream_of(); } + volatile char* byte_stream(off64_t off = 0) const { + return as_stream_of() + off; + } size_t size() const { return this->region_size; } private: diff --git a/docs/design/src/examples/volatile_mem_stream.cpp b/docs/design/src/examples/volatile_mem_stream.cpp index 228b6bfa..33b1144f 100644 --- a/docs/design/src/examples/volatile_mem_stream.cpp +++ b/docs/design/src/examples/volatile_mem_stream.cpp @@ -14,8 +14,9 @@ int main() { volatile int *smr_bytes = smr.as_stream_of(); + // std::memset((void *)smr.get(), 0x22, smr.size());q - volatile_mem_stream vms{&smr}; + volatile_mem_stream vms{smr.byte_stream(20), 30}; uint32_t my_val = UINT32_MAX; diff --git a/docs/design/src/mcmini/coordinator/coordinator.cpp b/docs/design/src/mcmini/coordinator/coordinator.cpp index 51687beb..4e076d52 100644 --- a/docs/design/src/mcmini/coordinator/coordinator.cpp +++ b/docs/design/src/mcmini/coordinator/coordinator.cpp @@ -20,32 +20,32 @@ void coordinator::execute_runner(process::runner_id_t runner_id) { "Failed to execute runner with id \"" + std::to_string(runner_id) + "\": the process is not alive"); } - std::istream &wrapper_response_stream = + model::transition_registry::runtime_type_id rttid; + runner_mailbox_stream &mb = this->current_process_handle->execute_runner(runner_id); + mb.read(&rttid); - model::transition_registry::runtime_type_id transition_registry_id; - wrapper_response_stream >> transition_registry_id; - - // TODO: Handle the case where lookup fails (this indicates a failure on the - // end of libmcmini.so... we should probably abort?) - model_to_system_map remote_address_mapping = model_to_system_map(*this); model::transition_registry::transition_discovery_callback callback_function = - runtime_transition_mapping.get_callback_for(transition_registry_id); + runtime_transition_mapping.get_callback_for(rttid); if (!callback_function) { throw real_world::process::execution_exception( "Execution resulted in a runner scheduled to execute the transition " "type with the RTTID '" + - std::to_string(transition_registry_id) + + std::to_string(rttid) + "' but this identifier has not been registered before model checking " "began. Double check that the coordinator was properly configured " "before launch; otherwise, please report this as a bug in " "libmcmini.so with this message."); } - std::unique_ptr transition_encountered_at_runtime = - callback_function(wrapper_response_stream, remote_address_mapping); - + model_to_system_map remote_address_mapping = model_to_system_map(*this); + auto pending_operation = callback_function(mb, remote_address_mapping); + if (!pending_operation) { + throw real_world::process::execution_exception( + "Failed to translate the data written into the mailbox of runner " + + std::to_string(runner_id)); + } this->current_program_model.model_executing_runner( - runner_id, std::move(transition_encountered_at_runtime)); + runner_id, std::move(pending_operation)); } void *model_to_system_map::get_remote_process_handle_for_object( diff --git a/docs/design/src/mcmini/mcmini.cpp b/docs/design/src/mcmini/mcmini.cpp index f4ff56b7..53e840f1 100644 --- a/docs/design/src/mcmini/mcmini.cpp +++ b/docs/design/src/mcmini/mcmini.cpp @@ -3,11 +3,14 @@ #include "mcmini/coordinator/coordinator.hpp" #include "mcmini/misc/ddt.hpp" #include "mcmini/misc/extensions/unique_ptr.hpp" +#include "mcmini/misc/volatile_mem_stream.hpp" #include "mcmini/model/state/detached_state.hpp" #include "mcmini/model/transitions/mutex/mutex_init.hpp" #include "mcmini/model/transitions/thread/thread_start.hpp" #include "mcmini/model_checking/algorithms/classic_dpor.hpp" #include "mcmini/real_world/process/fork_process_source.hpp" +#include "mcmini/real_world/runner_mailbox_stream.hpp" +#include "mcmini/real_world/shm.hpp" #define _XOPEN_SOURCE_EXTENDED 1 @@ -25,6 +28,11 @@ void display_usage() { std::exit(EXIT_FAILURE); } +std::unique_ptr test_callback( + const real_world::runner_mailbox_stream& rms, model_to_system_map& msm) { + return extensions::make_unique(0); +} + void do_model_checking( /* Pass arguments here or rearrange to configure the checker at runtime, e.g. to pick an algorithm, set a max depth, etc. */) { @@ -34,26 +42,25 @@ void do_model_checking( state_sequence state_of_program_at_main; pending_transitions initial_first_steps; + transition_registry tr; - state::objid_t thread_id = + state::objid_t main_thread_id = state_of_program_at_main.add_object(model::objects::thread::make()); initial_first_steps.displace_transition_for( - 0, make_unique(thread_id)); + 0, make_unique(main_thread_id)); - /* - TODO: Complete the initialization of the initial state here, i.e. a - single thread "main" that is alive and then running the transition `t` - */ - program model_for_program_starting_at_main( - std::move(state_of_program_at_main), std::move(initial_first_steps)); + program model_for_program_starting_at_main(state_of_program_at_main, + std::move(initial_first_steps)); // For "vanilla" model checking where we start at the beginning of the // program, a fork_process_source suffices (fork() + exec() brings us to the // beginning) auto process_source = make_unique("hello-world"); + tr.register_transition(&test_callback); + coordinator coordinator(std::move(model_for_program_starting_at_main), - transition_registry(), std::move(process_source)); + std::move(tr), std::move(process_source)); std::unique_ptr classic_dpor_checker = make_unique(); @@ -107,4 +114,4 @@ void do_model_checking_from_dmtcp_ckpt_file(std::string file_name) { std::cerr << "Model checking completed!" << std::endl; } -int main(int argc, char **argv) { do_model_checking(); } +int main(int argc, char** argv) { do_model_checking(); } diff --git a/docs/design/src/mcmini/model/detached_state.cpp b/docs/design/src/mcmini/model/detached_state.cpp index c12955a7..41ec86cf 100644 --- a/docs/design/src/mcmini/model/detached_state.cpp +++ b/docs/design/src/mcmini/model/detached_state.cpp @@ -11,8 +11,8 @@ bool detached_state::contains_object_with_id(state::objid_t id) const { } state::objid_t detached_state::add_object( - std::unique_ptr new_object) { - visible_objects.push_back(visible_object(std::move(new_object))); + std::unique_ptr new_object) { + visible_objects.push_back(std::move(new_object)); return visible_objects.size() - 1; } @@ -26,6 +26,11 @@ const visible_object_state *detached_state::get_state_of_object( return this->visible_objects.at(id).get_current_state(); } +std::unique_ptr detached_state::consume_obj( + objid_t id) && { + return std::move(visible_objects.at(id)).consume_into_current_state(); +} + std::unique_ptr detached_state::mutable_clone() const { return extensions::make_unique(*this); } \ No newline at end of file diff --git a/docs/design/src/mcmini/model/program.cpp b/docs/design/src/mcmini/model/program.cpp index 7a29ea3a..c4a8a848 100644 --- a/docs/design/src/mcmini/model/program.cpp +++ b/docs/design/src/mcmini/model/program.cpp @@ -2,7 +2,6 @@ using namespace model; -program::program(state &&initial_state, +program::program(const state &initial_state, pending_transitions &&initial_first_steps) - : next_steps(std::move(initial_first_steps)), - state_seq(std::move(initial_state)) {} \ No newline at end of file + : next_steps(std::move(initial_first_steps)), state_seq(initial_state) {} \ No newline at end of file diff --git a/docs/design/src/mcmini/model/state_sequence.cpp b/docs/design/src/mcmini/model/state_sequence.cpp index 35b1b9fe..1f366800 100644 --- a/docs/design/src/mcmini/model/state_sequence.cpp +++ b/docs/design/src/mcmini/model/state_sequence.cpp @@ -14,7 +14,7 @@ class state_sequence::diff_state : public mutable_state { const state_sequence &base_state; public: - // Purposely exposed for implementation + // Purposely exposed in this private cpp file for implementation (pimpl). std::unordered_map new_object_states; diff_state(const state_sequence &s) : base_state(s) {} @@ -36,24 +36,23 @@ class state_sequence::diff_state : public mutable_state { } return count; } - virtual const visible_object_state *get_state_of_object( - objid_t id) const override; - virtual objid_t add_object( - std::unique_ptr initial_state) override; - virtual void add_state_for( - objid_t id, std::unique_ptr new_state) override; - virtual std::unique_ptr mutable_clone() const override; + const visible_object_state *get_state_of_object(objid_t id) const override; + objid_t add_object( + std::unique_ptr initial_state) override; + void add_state_for(objid_t id, + std::unique_ptr new_state) override; + std::unique_ptr consume_obj(objid_t id) && + override; + std::unique_ptr mutable_clone() const override; }; state_sequence::state_sequence() { this->push_state_snapshot(); } -state_sequence::state_sequence(const state &initial_state) { - // TODO: Iterate through all the objects and their states to make a clone - // Potentially allow for a move iterator to be constructed. -} - -state_sequence::state_sequence(const state &&state) { - // TODO: Iterate through all the objects. We need to attach +state_sequence::state_sequence(const state &s) { + this->push_state_snapshot(); + const size_t num_objs = s.count(); + for (objid_t i = 0; i < num_objs; i++) + add_object(s.get_state_of_object(i)->clone()); } state_sequence::state_sequence(std::vector &&initial_objects) @@ -62,7 +61,7 @@ state_sequence::state_sequence(std::vector &&initial_objects) } state_sequence::state_sequence(append_only &&ao) - : visible_objects(ao) { + : visible_objects(std::move(ao)) { this->push_state_snapshot(); } @@ -76,12 +75,12 @@ const visible_object_state *state_sequence::get_state_of_object( } state::objid_t state_sequence::add_object( - std::unique_ptr initial_state) { + std::unique_ptr initial_state) { // INVARIANT: The current element needs to update at index `id` to reflect // this new object, as this element effectively represents this state objid_t id = visible_objects.size(); this->states_in_sequence.back().point_to_state_for(id, initial_state.get()); - visible_objects.push_back(visible_object(std::move(initial_state))); + visible_objects.push_back(std::move(initial_state)); return id; } @@ -93,6 +92,11 @@ void state_sequence::add_state_for( this->visible_objects.at(id).push_state(std::move(new_state)); } +std::unique_ptr state_sequence::consume_obj( + objid_t id) && { + return std::move(visible_objects.at(id)).consume_into_current_state(); +} + std::unique_ptr state_sequence::mutable_clone() const { return state::from_visible_objects( this->visible_objects.cbegin(), this->visible_objects.cend()); @@ -155,6 +159,12 @@ const visible_object_state *state_sequence::element::get_state_of_object( return this->visible_object_states.at(id); } +std::unique_ptr +state_sequence::element::consume_obj(objid_t id) && { + throw std::runtime_error( + "Consumption is not permitted on elements of a state sequence"); +} + std::unique_ptr state_sequence::element::mutable_clone() const { auto state = extensions::make_unique(); for (objid_t i = 0; i < this->visible_object_states.size(); i++) @@ -179,7 +189,7 @@ const visible_object_state *state_sequence::diff_state::get_state_of_object( } state::objid_t state_sequence::diff_state::add_object( - std::unique_ptr initial_state) { + std::unique_ptr initial_state) { // The next id that would be assigned if one more than // the largest id available. The last id of the base it `size() - 1` and // we are `new_object_state.size()` elements in @@ -195,6 +205,11 @@ void state_sequence::diff_state::add_state_for( s.push_state(std::move(new_state)); } +std::unique_ptr +state_sequence::diff_state::consume_obj(objid_t id) && { + throw std::runtime_error("Consumption is not permitted on diff states"); +} + std::unique_ptr state_sequence::diff_state::mutable_clone() const { return extensions::make_unique(this->base_state); diff --git a/docs/design/src/mcmini/real_world/local_linux_process.cpp b/docs/design/src/mcmini/real_world/local_linux_process.cpp index d56e244a..e68751fa 100644 --- a/docs/design/src/mcmini/real_world/local_linux_process.cpp +++ b/docs/design/src/mcmini/real_world/local_linux_process.cpp @@ -18,11 +18,10 @@ std::unique_ptr local_linux_process::vms = nullptr; void local_linux_process::initialize_shared_memory() { rw_region = make_unique("hello", 100); - vms = make_unique(rw_region.get()); + vms = make_unique(*rw_region); } -local_linux_process::local_linux_process(pid_t pid) - : pid(pid), rw_region_stream(nullptr) { +local_linux_process::local_linux_process(pid_t pid) : pid(pid) { static std::once_flag shm_once_flag; std::call_once(shm_once_flag, initialize_shared_memory); } @@ -49,22 +48,14 @@ local_linux_process::~local_linux_process() { } } -std::istream& local_linux_process::execute_runner( +runner_mailbox_stream &local_linux_process::execute_runner( runner_id_t mcmini_runner_id) { /* TODO: sem_wait + sem_post pair */ - auto* j = rw_region_stream.rdbuf(vms.get()); - auto* j2 = rw_region_stream.rdbuf(vms.get()); - assert(j2 == vms.get()); - // rw_region_stream.seekg(0); + // TODO: Keep a list of mailboxes and index by the id + static runner_mailbox_stream mb{vms.get()}; - if (rw_region_stream.fail()) { - abort(); - } - - // Simulate this for now: write in some value - volatile uint32_t* v = rw_region->as_stream_of(); - std::memset((void*)v, 10, rw_region->size()); - - return rw_region_stream; + uint32_t j = 0; + mb.write(&j); + return mb; } \ No newline at end of file From 3ed051fdd910418578e2cb05e5f88fa7d0838a6e Mon Sep 17 00:00:00 2001 From: Maxwell Pirtle Date: Sat, 20 Apr 2024 13:47:54 -0400 Subject: [PATCH 005/161] Add `runner_mailbox` C struct in preparation for libmcmini This commit introduces the `runner_mailbox` C struct that will be used to store both the thread-specific payload as well as the pair of binary semaphores that are used to communicate between libmcmini and McMini. --- docs/design/CMakeLists.txt | 4 +- .../mcmini/real_world/runner_mailbox.h | 26 ++++++++++ docs/design/src/common/runner_mailbox.c | 50 +++++++++++++++++++ 3 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 docs/design/include/mcmini/real_world/runner_mailbox.h create mode 100644 docs/design/src/common/runner_mailbox.c diff --git a/docs/design/CMakeLists.txt b/docs/design/CMakeLists.txt index 8b509daa..d3404c6a 100644 --- a/docs/design/CMakeLists.txt +++ b/docs/design/CMakeLists.txt @@ -18,7 +18,9 @@ set(MCMINI_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/include") set(MCMINI_CMAKE_MODULE_DIR "${CMAKE_SOURCE_DIR}/cmake") # Project source files -set(MCMINI_C_SRC ) +set(MCMINI_C_SRC + src/common/runner_mailbox.c +) set(MCMINI_CPP_SRC src/mcmini/mcmini.cpp src/mcmini/visible_object.cpp diff --git a/docs/design/include/mcmini/real_world/runner_mailbox.h b/docs/design/include/mcmini/real_world/runner_mailbox.h new file mode 100644 index 00000000..876ca0d9 --- /dev/null +++ b/docs/design/include/mcmini/real_world/runner_mailbox.h @@ -0,0 +1,26 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +typedef struct { + sem_t model_side_sem; + sem_t child_side_sem; + uint8_t cnts[64]; // TODO: How much space should each thread have to write + // payloads? +} runner_mailbox, *runner_mailbox_ref; + +void mc_runner_mailbox_init(volatile runner_mailbox *); +void mc_runner_mailbox_destroy(volatile runner_mailbox *); +void mc_wait_for_thread(volatile runner_mailbox *); +void mc_wait_for_scheduler(volatile runner_mailbox *); +void mc_wake_thread(volatile runner_mailbox *); +void mc_wake_scheduler(volatile runner_mailbox *); + +#ifdef __cplusplus +} +#endif // extern "C" \ No newline at end of file diff --git a/docs/design/src/common/runner_mailbox.c b/docs/design/src/common/runner_mailbox.c new file mode 100644 index 00000000..2b2572e4 --- /dev/null +++ b/docs/design/src/common/runner_mailbox.c @@ -0,0 +1,50 @@ +#include "mcmini/real_world/runner_mailbox.h" +#include "mcmini/defines.h" +#include "string.h" + +void +mc_runner_mailbox_init(volatile runner_mailbox * r) +{ + runner_mailbox_ref ref = (runner_mailbox_ref)(r); + sem_init(&ref->model_side_sem, SEM_FLAG_SHARED, 0); + sem_init(&ref->child_side_sem, SEM_FLAG_SHARED, 0); + memset(ref->cnts, 0, sizeof(ref->cnts)); +} + +void +mc_runner_mailbox_destroy(volatile runner_mailbox * r) +{ + runner_mailbox_ref ref = (runner_mailbox_ref)(r); + sem_destroy(&ref->model_side_sem); + sem_destroy(&ref->child_side_sem); +} + +void +mc_wait_for_thread(volatile runner_mailbox * r) +{ + runner_mailbox_ref ref = (runner_mailbox_ref)(r); + sem_wait(&ref->model_side_sem); +} + +void +mc_wait_for_scheduler(volatile runner_mailbox * r) +{ + runner_mailbox_ref ref = (runner_mailbox_ref)(r); + sem_wait(&ref->child_side_sem); +} + +void +mc_wake_thread(volatile runner_mailbox * r) +{ + runner_mailbox_ref ref = (runner_mailbox_ref)(r); + sem_post(&ref->child_side_sem); +} + +void +mc_wake_scheduler(volatile runner_mailbox * r) +{ + runner_mailbox_ref ref = (runner_mailbox_ref)(r); + sem_post(&ref->model_side_sem); +} + + From 8bd7b2f5b2761f8c7d97127b64acbaf2d6144465 Mon Sep 17 00:00:00 2001 From: Maxwell Pirtle Date: Sat, 20 Apr 2024 13:55:41 -0400 Subject: [PATCH 006/161] Rename `volatile_mem_stream` to `*_streambuf` This renaming is more in line with the function of the class as it derives from `std::streambuf` and is not itself a stream. --- ...ile_mem_stream.hpp => volatile_mem_streambuf.hpp} | 12 +++++++----- .../real_world/process/local_linux_process.hpp | 4 ++-- docs/design/src/examples/volatile_mem_stream.cpp | 6 ++---- docs/design/src/mcmini/mcmini.cpp | 2 +- .../src/mcmini/real_world/local_linux_process.cpp | 4 ++-- 5 files changed, 14 insertions(+), 14 deletions(-) rename docs/design/include/mcmini/misc/{volatile_mem_stream.hpp => volatile_mem_streambuf.hpp} (76%) diff --git a/docs/design/include/mcmini/misc/volatile_mem_stream.hpp b/docs/design/include/mcmini/misc/volatile_mem_streambuf.hpp similarity index 76% rename from docs/design/include/mcmini/misc/volatile_mem_stream.hpp rename to docs/design/include/mcmini/misc/volatile_mem_streambuf.hpp index 750e1b42..12d65b78 100644 --- a/docs/design/include/mcmini/misc/volatile_mem_stream.hpp +++ b/docs/design/include/mcmini/misc/volatile_mem_streambuf.hpp @@ -5,18 +5,20 @@ #include "mcmini/real_world/shm.hpp" -struct volatile_mem_stream : public std::streambuf { +struct volatile_mem_streambuf : public std::streambuf { private: char *volatile_cache; size_t rw_region_size; volatile char *rw_region; public: - volatile_mem_stream(const real_world::shared_memory_region &read_write_region) - : volatile_mem_stream(read_write_region.get(), read_write_region.size()) { + volatile_mem_streambuf( + const real_world::shared_memory_region &read_write_region) + : volatile_mem_streambuf(read_write_region.get(), + read_write_region.size()) { reset(); } - volatile_mem_stream(volatile void *rw_start, size_t rw_size) + volatile_mem_streambuf(volatile void *rw_start, size_t rw_size) : rw_region(static_cast(rw_start)), rw_region_size(rw_size), volatile_cache(new char[rw_size]) { @@ -28,7 +30,7 @@ struct volatile_mem_stream : public std::streambuf { setp(volatile_cache, volatile_cache + rw_region_size); } - ~volatile_mem_stream() { delete[] volatile_cache; } + ~volatile_mem_streambuf() { delete[] volatile_cache; } protected: int_type underflow() override { diff --git a/docs/design/include/mcmini/real_world/process/local_linux_process.hpp b/docs/design/include/mcmini/real_world/process/local_linux_process.hpp index a5b97c22..947d20dd 100644 --- a/docs/design/include/mcmini/real_world/process/local_linux_process.hpp +++ b/docs/design/include/mcmini/real_world/process/local_linux_process.hpp @@ -4,7 +4,7 @@ #include #include -#include "mcmini/misc/volatile_mem_stream.hpp" +#include "mcmini/misc/volatile_mem_streambuf.hpp" #include "mcmini/real_world/process.hpp" #include "mcmini/real_world/shm.hpp" @@ -27,7 +27,7 @@ class local_linux_process : public process { // themselves static void initialize_shared_memory(); static std::unique_ptr rw_region; - static std::unique_ptr vms; + static std::unique_ptr vms; public: local_linux_process() = default; diff --git a/docs/design/src/examples/volatile_mem_stream.cpp b/docs/design/src/examples/volatile_mem_stream.cpp index 33b1144f..2e5ce9a0 100644 --- a/docs/design/src/examples/volatile_mem_stream.cpp +++ b/docs/design/src/examples/volatile_mem_stream.cpp @@ -1,11 +1,10 @@ -#include "mcmini/misc/volatile_mem_stream.hpp" - #include #include #include +#include "mcmini/misc/volatile_mem_streambuf.hpp" #include "mcmini/real_world/remote_address.hpp" #include "mcmini/real_world/shm.hpp" @@ -14,9 +13,8 @@ int main() { volatile int *smr_bytes = smr.as_stream_of(); - // std::memset((void *)smr.get(), 0x22, smr.size());q - volatile_mem_stream vms{smr.byte_stream(20), 30}; + volatile_mem_streambuf vms{smr.byte_stream(20), 30}; uint32_t my_val = UINT32_MAX; diff --git a/docs/design/src/mcmini/mcmini.cpp b/docs/design/src/mcmini/mcmini.cpp index 53e840f1..5374800c 100644 --- a/docs/design/src/mcmini/mcmini.cpp +++ b/docs/design/src/mcmini/mcmini.cpp @@ -3,7 +3,7 @@ #include "mcmini/coordinator/coordinator.hpp" #include "mcmini/misc/ddt.hpp" #include "mcmini/misc/extensions/unique_ptr.hpp" -#include "mcmini/misc/volatile_mem_stream.hpp" +#include "mcmini/misc/volatile_mem_streambuf.hpp" #include "mcmini/model/state/detached_state.hpp" #include "mcmini/model/transitions/mutex/mutex_init.hpp" #include "mcmini/model/transitions/thread/thread_start.hpp" diff --git a/docs/design/src/mcmini/real_world/local_linux_process.cpp b/docs/design/src/mcmini/real_world/local_linux_process.cpp index e68751fa..568df947 100644 --- a/docs/design/src/mcmini/real_world/local_linux_process.cpp +++ b/docs/design/src/mcmini/real_world/local_linux_process.cpp @@ -14,11 +14,11 @@ using namespace real_world; using namespace extensions; std::unique_ptr local_linux_process::rw_region = nullptr; -std::unique_ptr local_linux_process::vms = nullptr; +std::unique_ptr local_linux_process::vms = nullptr; void local_linux_process::initialize_shared_memory() { rw_region = make_unique("hello", 100); - vms = make_unique(*rw_region); + vms = make_unique(*rw_region); } local_linux_process::local_linux_process(pid_t pid) : pid(pid) { From 23d2f7fdf515e10c3fba2432769f366b1d407a6c Mon Sep 17 00:00:00 2001 From: Maxwell Pirtle Date: Sat, 20 Apr 2024 15:12:37 -0400 Subject: [PATCH 007/161] Prepare mailbox storage in local_linux_process This commit adds a buffer for storing the mailbox data for each thread that will be spawed under the control of `libmcmini.so` --- .../mcmini/misc/volatile_mem_streambuf.hpp | 1 + .../process/local_linux_process.hpp | 1 - docs/design/include/mcmini/real_world/shm.hpp | 6 ++-- .../mcmini/real_world/fork_process_source.cpp | 24 +++++----------- .../mcmini/real_world/local_linux_process.cpp | 28 ++++++++++++------- 5 files changed, 29 insertions(+), 31 deletions(-) diff --git a/docs/design/include/mcmini/misc/volatile_mem_streambuf.hpp b/docs/design/include/mcmini/misc/volatile_mem_streambuf.hpp index 12d65b78..62aa8a89 100644 --- a/docs/design/include/mcmini/misc/volatile_mem_streambuf.hpp +++ b/docs/design/include/mcmini/misc/volatile_mem_streambuf.hpp @@ -12,6 +12,7 @@ struct volatile_mem_streambuf : public std::streambuf { volatile char *rw_region; public: + volatile_mem_streambuf() = default; volatile_mem_streambuf( const real_world::shared_memory_region &read_write_region) : volatile_mem_streambuf(read_write_region.get(), diff --git a/docs/design/include/mcmini/real_world/process/local_linux_process.hpp b/docs/design/include/mcmini/real_world/process/local_linux_process.hpp index 947d20dd..2a5b92fd 100644 --- a/docs/design/include/mcmini/real_world/process/local_linux_process.hpp +++ b/docs/design/include/mcmini/real_world/process/local_linux_process.hpp @@ -27,7 +27,6 @@ class local_linux_process : public process { // themselves static void initialize_shared_memory(); static std::unique_ptr rw_region; - static std::unique_ptr vms; public: local_linux_process() = default; diff --git a/docs/design/include/mcmini/real_world/shm.hpp b/docs/design/include/mcmini/real_world/shm.hpp index 9e9996e1..af62038b 100644 --- a/docs/design/include/mcmini/real_world/shm.hpp +++ b/docs/design/include/mcmini/real_world/shm.hpp @@ -25,11 +25,11 @@ struct shared_memory_region { volatile void* contents() const { return get(); } template - volatile T* as_stream_of() const { - return static_cast(shm_mmap_region); + volatile T* as_stream_of(off64_t off = 0) const { + return static_cast(shm_mmap_region) + off; } volatile char* byte_stream(off64_t off = 0) const { - return as_stream_of() + off; + return as_stream_of(off); } size_t size() const { return this->region_size; } diff --git a/docs/design/src/mcmini/real_world/fork_process_source.cpp b/docs/design/src/mcmini/real_world/fork_process_source.cpp index 45c7c832..4238fe75 100644 --- a/docs/design/src/mcmini/real_world/fork_process_source.cpp +++ b/docs/design/src/mcmini/real_world/fork_process_source.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include "mcmini/misc/extensions/unique_ptr.hpp" @@ -16,34 +17,23 @@ std::unique_ptr fork_process_source::make_new_process() { // or malloc/free _could be_ needed, we'd need to check the man page. As long // as the char * is not actually modified, this is OK and the best way // to interface with the C library routines - setup_ld_preload(); + errno = 0; pid_t child_pid = fork(); if (child_pid == -1) { - perror("fork"); - return nullptr; // Handle fork() failing - } - - if (child_pid == 0) { + throw process_source::process_creation_exception( + "Failed to create a new process (fork(2) failed): " + + std::string(strerror(errno))); + } else if (child_pid == 0) { // TODO: Add additional arguments here if needed char* args[] = {const_cast(this->target_program.c_str()), NULL}; - std::cerr << "About to exec with libmcmini.so loaded! Attempting to run " << this->target_program.c_str() << std::endl; execvp(this->target_program.c_str(), args); - perror("execvp"); // Handle execvp error here - exit(EXIT_FAILURE); - } else { - int status; - waitpid(child_pid, &status, 0); // Wait for the child to exit - if (WIFEXITED(status) && WEXITSTATUS(status) != 0) { - // Handle execvp failing in the fork()-ed child - return nullptr; - } + std::exit(EXIT_FAILURE); } - return extensions::make_unique(child_pid); } diff --git a/docs/design/src/mcmini/real_world/local_linux_process.cpp b/docs/design/src/mcmini/real_world/local_linux_process.cpp index 568df947..f6d0f913 100644 --- a/docs/design/src/mcmini/real_world/local_linux_process.cpp +++ b/docs/design/src/mcmini/real_world/local_linux_process.cpp @@ -9,16 +9,15 @@ #include #include "mcmini/misc/extensions/unique_ptr.hpp" +#include "mcmini/real_world/runner_mailbox.h" using namespace real_world; using namespace extensions; std::unique_ptr local_linux_process::rw_region = nullptr; -std::unique_ptr local_linux_process::vms = nullptr; void local_linux_process::initialize_shared_memory() { rw_region = make_unique("hello", 100); - vms = make_unique(*rw_region); } local_linux_process::local_linux_process(pid_t pid) : pid(pid) { @@ -48,14 +47,23 @@ local_linux_process::~local_linux_process() { } } -runner_mailbox_stream &local_linux_process::execute_runner( - runner_id_t mcmini_runner_id) { - /* TODO: sem_wait + sem_post pair */ +runner_mailbox_stream &local_linux_process::execute_runner(runner_id_t id) { + volatile runner_mailbox *rmb = rw_region->as_stream_of(id); + volatile_mem_streambuf runner_mem_stream{&rmb->cnts, sizeof(&rmb->cnts)}; - // TODO: Keep a list of mailboxes and index by the id - static runner_mailbox_stream mb{vms.get()}; + // TODO: When the `libmcmini.so` portion of the synchronization is complete, + // we can uncomment this without deadlocking + // mc_wake_thread(rmb); + // mc_wait_for_thread(rmb); - uint32_t j = 0; - mb.write(&j); - return mb; + static volatile_mem_streambuf runner_mailbox_bufs[50]; + static runner_mailbox_stream *runner_mailbox_streams[50]; + + if (runner_mailbox_streams[id]) { + delete runner_mailbox_streams[id]; + } + runner_mailbox_bufs[id] = std::move(runner_mem_stream); + runner_mailbox_streams[id] = + new runner_mailbox_stream{&runner_mailbox_bufs[id]}; + return *runner_mailbox_streams[id]; } \ No newline at end of file From f6ebe78364c0b7a948e920a013cdbacf7719779c Mon Sep 17 00:00:00 2001 From: Maxwell Pirtle Date: Sat, 20 Apr 2024 17:27:19 -0400 Subject: [PATCH 008/161] Add implementation for libmcmini.so for initial setup --- docs/design/CMakeLists.txt | 1 + docs/design/include/mcmini/defines.h | 1 + docs/design/include/mcmini/entry.h | 32 +--- docs/design/src/libmcmini/entry.c | 160 ++++++++++-------- .../mcmini/real_world/local_linux_process.cpp | 14 +- 5 files changed, 105 insertions(+), 103 deletions(-) diff --git a/docs/design/CMakeLists.txt b/docs/design/CMakeLists.txt index d3404c6a..6865726c 100644 --- a/docs/design/CMakeLists.txt +++ b/docs/design/CMakeLists.txt @@ -34,6 +34,7 @@ set(MCMINI_CPP_SRC src/mcmini/real_world/shm.cpp ) set(LIBMCMINI_C_SRC + src/common/runner_mailbox.c src/libmcmini/entry.c src/libmcmini/wrappers.c ) diff --git a/docs/design/include/mcmini/defines.h b/docs/design/include/mcmini/defines.h index ce45543f..1884c734 100644 --- a/docs/design/include/mcmini/defines.h +++ b/docs/design/include/mcmini/defines.h @@ -6,6 +6,7 @@ #define MCMINI_LIBRARY_ENTRY_POINT #define MCMINI_EXPORT __attribute__((visibility(default))) #define MCMINI_PRIVATE __attribute__((visibility(hidden))) +#define MCMINI_THREAD_LOCAL _Thread_local #define MAX_TOTAL_THREADS_IN_PROGRAM (20u) #define MAX_TOTAL_VISIBLE_OBJECTS_IN_PROGRAM (10000u) diff --git a/docs/design/include/mcmini/entry.h b/docs/design/include/mcmini/entry.h index 6d2b4373..310b5481 100644 --- a/docs/design/include/mcmini/entry.h +++ b/docs/design/include/mcmini/entry.h @@ -1,33 +1,7 @@ -#ifndef INCLUDE_ENTRY_H -#define INCLUDE_ENTRY_H -#include -#include -#include "mcmini/shared_sem.h" -#include "mcmini/shared_transition.h" -#define MAX_TOTAL_THREADS_IN_PROGRAM (20u) +#pragma once -/** - * @brief The size of the shared memory allocation in bytes - */ -//TODO: What is the size of shared memory,how the transiyion info is stored? -extern const size_t shmAllocationSize; +#include "mcmini/defines.h" -/** - * @brief The address at which the shared memory mailbox begins to - * allow threads in a trace process to communicate with the scheduler - */ -extern void *shmStart; +void mc_exit(int status); -extern shared_sem ( - *trace_list)[MAX_TOTAL_THREADS_IN_PROGRAM]; - -/** - * @brief Initializes the variables in the global `trace_list` - */ -void initialize_trace_list(); - - - - -#endif diff --git a/docs/design/src/libmcmini/entry.c b/docs/design/src/libmcmini/entry.c index 8b050b84..1602ca24 100644 --- a/docs/design/src/libmcmini/entry.c +++ b/docs/design/src/libmcmini/entry.c @@ -8,6 +8,23 @@ #include #include #include +#include + +#include "mcmini/real_world/runner_mailbox.h" +#include "mcmini/entry.h" + + +volatile void *shm_start = NULL; +const static size_t shm_size = sizeof(runner_mailbox) * MAX_TOTAL_THREADS_IN_PROGRAM; +MCMINI_THREAD_LOCAL tid_t tid_self = TID_INVALID; + +tid_t +mc_created_new_thread() +{ + static tid_t tid_next = 0; + tid_self = tid_next++; + return tid_self; +} void mc_get_shm_handle_name(char *dst, size_t sz) @@ -19,67 +36,55 @@ mc_get_shm_handle_name(char *dst, size_t sz) void * mc_allocate_shared_memory_region() { - return NULL; - // TODO: Allocation - // // If the region exists, then this returns a fd for the existing - // // region. Otherwise, it creates a new shared memory region. - // char dpor[100]; - // mc_get_shm_handle_name(dpor, sizeof(dpor)); - - // // This creates a file in /dev/shm/ - // int fd = shm_open(dpor, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); - // if (fd == -1) { - // if (errno == EACCES) { - // fprintf(stderr, - // "Shared memory region '%s' not owned by this process\n", - // dpor); - // } else { - // perror("shm_open"); - // } - // mc_exit(EXIT_FAILURE); - // } - // int rc = ftruncate(fd, shmAllocationSize); - // if (rc == -1) { - // perror("ftruncate"); - // mc_exit(EXIT_FAILURE); - // } - // // We want stack at same address for each process. Otherwise, a - // // pointer - // // to an address in the stack data structure will not work - // // TODO: Would the following suffice instead? - // // - // // static char large_region[1_000_000]; - // // void *shm_map_addr = ; - // // - // // Or do we even need the same location anymore? - // // - // void *stack_address = (void *)0x4444000; - // void *shmStart = - // mmap(stack_address, shmAllocationSize, PROT_READ | PROT_WRITE, - // MAP_SHARED | MAP_FIXED, fd, 0); - // if (shmStart == MAP_FAILED) { - // perror("mmap"); - // mc_exit(EXIT_FAILURE); - // } - // // shm_unlink(dpor); // Don't unlink while child processes need to - // // open this. - // fsync(fd); - // close(fd); - // return shmStart; + char dpor[100]; + mc_get_shm_handle_name(dpor, sizeof(dpor)); + + int fd = shm_open(dpor, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); + if (fd == -1) { + if (errno == EACCES) { + fprintf(stderr, + "Shared memory region '%s' not owned by this process\n", + dpor); + } else { + perror("shm_open"); + } + mc_exit(EXIT_FAILURE); + } + int rc = ftruncate(fd, shm_size); + if (rc == -1) { + perror("ftruncate"); + mc_exit(EXIT_FAILURE); + } + + void *shm_start = + mmap(NULL, shm_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (shm_start == MAP_FAILED) { + perror("mmap"); + mc_exit(EXIT_FAILURE); + } + + /* The parent process will handle shm_unlink() */ + fsync(fd); + close(fd); + return shm_start; } void mc_deallocate_shared_memory_region() { - // TODO: Deallocation - // char shm_file_name[100]; - // mc_get_shm_handle_name(shm_file_name, sizeof(shm_file_name)); - // int rc = munmap(shmStart, shmAllocationSize); - // if (rc == -1) { - // perror("munmap"); - // mc_exit(EXIT_FAILURE); - // } + char shm_file_name[100]; + mc_get_shm_handle_name(shm_file_name, sizeof(shm_file_name)); + if (shm_start) { + int rc = munmap((void*)shm_start, shm_size); + if (rc == -1) { + perror("munmap"); + mc_exit(EXIT_FAILURE); + } + } + // TODO: Unlinking likely shouldn't happen in child + // processes where `libmcmini.so` is loaded; instead, + // that should be left to the McMini process to handle. // rc = shm_unlink(shm_file_name); // if (rc == -1) { // if (errno == EACCES) { @@ -93,12 +98,6 @@ mc_deallocate_shared_memory_region() // } } -void -mc_initialize_shared_memory_globals() -{ - // TODO: We can do this just as in mcmini_private.hpp -} - void mc_exit(int status) { @@ -110,15 +109,36 @@ mc_exit(int status) _Exit(status); } -__attribute__((constructor)) void my_ctor() { - // Do something here with the constructor (e.g. dlsym preparation) - void *buf = malloc(10 * sizeof(char)); - memset(buf, (int)('A'), 10 * sizeof(char)); - - /* Open shm file to discover sem_t region + read/write loc for McMini to read from etc*/ +void +thread_await_scheduler() +{ + assert(tid_self != TID_INVALID); + volatile runner_mailbox *thread_mailbox = ((volatile runner_mailbox*)(shm_start)) + tid_self; + mc_wake_scheduler(thread_mailbox); + mc_wait_for_scheduler(thread_mailbox); +} +void +thread_await_scheduler_for_thread_start_transition() +{ + assert(tid_self != TID_INVALID); + volatile runner_mailbox *thread_mailbox = ((volatile runner_mailbox*)(shm_start)) + tid_self; + mc_wait_for_scheduler(thread_mailbox); +} - ((char *)buf)[9] = 0; - write(STDERR_FILENO, buf, 10); - free(buf); +void +thread_awake_scheduler_for_thread_finish_transition() +{ + assert(tid_self != TID_INVALID); + volatile runner_mailbox *thread_mailbox = ((volatile runner_mailbox*)(shm_start)) + tid_self; + mc_wake_scheduler(thread_mailbox); } + +__attribute__((constructor)) void my_ctor() { + mc_created_new_thread(); + atexit(&mc_deallocate_shared_memory_region); + shm_start = mc_allocate_shared_memory_region(); + printf("YO!\n"); + thread_await_scheduler_for_thread_start_transition(); + printf("YO2!\n"); +} \ No newline at end of file diff --git a/docs/design/src/mcmini/real_world/local_linux_process.cpp b/docs/design/src/mcmini/real_world/local_linux_process.cpp index f6d0f913..0009de7b 100644 --- a/docs/design/src/mcmini/real_world/local_linux_process.cpp +++ b/docs/design/src/mcmini/real_world/local_linux_process.cpp @@ -8,6 +8,7 @@ #include #include +#include "mcmini/defines.h" #include "mcmini/misc/extensions/unique_ptr.hpp" #include "mcmini/real_world/runner_mailbox.h" @@ -18,6 +19,13 @@ std::unique_ptr local_linux_process::rw_region = nullptr; void local_linux_process::initialize_shared_memory() { rw_region = make_unique("hello", 100); + volatile runner_mailbox *mbp = rw_region->as_stream_of(); + + // TODO: This should be a configurable parameter perhaps... + const int max_total_threads = MAX_TOTAL_THREADS_IN_PROGRAM; + for (int i = 0; i < max_total_threads; i++) { + mc_runner_mailbox_init(mbp + i); + } } local_linux_process::local_linux_process(pid_t pid) : pid(pid) { @@ -51,10 +59,8 @@ runner_mailbox_stream &local_linux_process::execute_runner(runner_id_t id) { volatile runner_mailbox *rmb = rw_region->as_stream_of(id); volatile_mem_streambuf runner_mem_stream{&rmb->cnts, sizeof(&rmb->cnts)}; - // TODO: When the `libmcmini.so` portion of the synchronization is complete, - // we can uncomment this without deadlocking - // mc_wake_thread(rmb); - // mc_wait_for_thread(rmb); + mc_wake_thread(rmb); + mc_wait_for_thread(rmb); static volatile_mem_streambuf runner_mailbox_bufs[50]; static runner_mailbox_stream *runner_mailbox_streams[50]; From 41ac667d5d1a508ac1a2591718fcb6e88fc8acf2 Mon Sep 17 00:00:00 2001 From: Maxwell Pirtle Date: Sat, 20 Apr 2024 20:05:19 -0400 Subject: [PATCH 009/161] Add shared memory synchronization between libmcmini + McMini This commit introduces synchronization between libmcmini and the McMini process using the pair of binary semaphores in shared memory. This is equivalent to the current model. However, there is one subtlety. If execvp fails, we currently block indefinitely as the the error is not propagated to the the parent McMini process. We can resolve this using a pipe(2) with FD_CLOEXEC. This will be introduced later --- docs/design/CMakeLists.txt | 2 + .../include/mcmini/MCSharedLibraryWrappers.h | 75 ------------------- .../design/include/mcmini/common/shm_config.h | 17 +++++ .../mcmini/model/transition_registry.hpp | 2 +- .../real_world/{ => mailbox}/runner_mailbox.h | 0 .../{ => mailbox}/runner_mailbox_stream.hpp | 0 .../include/mcmini/real_world/process.hpp | 2 +- .../process/fork_process_source.hpp | 16 +++- .../process/local_linux_process.hpp | 26 ++++--- docs/design/include/mcmini/shared_sem.h | 29 ------- docs/design/src/common/runner_mailbox.c | 2 +- docs/design/src/common/shm_config.c | 19 +++++ docs/design/src/libmcmini/entry.c | 12 +-- docs/design/src/mcmini/mcmini.cpp | 1 - .../mcmini/real_world/fork_process_source.cpp | 48 +++++++++++- .../mcmini/real_world/local_linux_process.cpp | 24 ++---- 16 files changed, 122 insertions(+), 153 deletions(-) delete mode 100644 docs/design/include/mcmini/MCSharedLibraryWrappers.h create mode 100644 docs/design/include/mcmini/common/shm_config.h rename docs/design/include/mcmini/real_world/{ => mailbox}/runner_mailbox.h (100%) rename docs/design/include/mcmini/real_world/{ => mailbox}/runner_mailbox_stream.hpp (100%) delete mode 100644 docs/design/include/mcmini/shared_sem.h create mode 100644 docs/design/src/common/shm_config.c diff --git a/docs/design/CMakeLists.txt b/docs/design/CMakeLists.txt index 6865726c..79021cd0 100644 --- a/docs/design/CMakeLists.txt +++ b/docs/design/CMakeLists.txt @@ -20,6 +20,7 @@ set(MCMINI_CMAKE_MODULE_DIR "${CMAKE_SOURCE_DIR}/cmake") # Project source files set(MCMINI_C_SRC src/common/runner_mailbox.c + src/common/shm_config.c ) set(MCMINI_CPP_SRC src/mcmini/mcmini.cpp @@ -35,6 +36,7 @@ set(MCMINI_CPP_SRC ) set(LIBMCMINI_C_SRC src/common/runner_mailbox.c + src/common/shm_config.c src/libmcmini/entry.c src/libmcmini/wrappers.c ) diff --git a/docs/design/include/mcmini/MCSharedLibraryWrappers.h b/docs/design/include/mcmini/MCSharedLibraryWrappers.h deleted file mode 100644 index 693fcaf4..00000000 --- a/docs/design/include/mcmini/MCSharedLibraryWrappers.h +++ /dev/null @@ -1,75 +0,0 @@ -#ifndef INCLUDE_TRANSITION_WRAPPERS_H -#define INCLUDE_TRANSITION_WRAPPERS_H - -#include -#include -#include -#include -#include - -extern typeof(&pthread_create) pthread_create_ptr; -extern typeof(&pthread_join) pthread_join_ptr; -extern typeof(&pthread_mutex_init) pthread_mutex_init_ptr; -extern typeof(&pthread_mutex_lock) pthread_mutex_lock_ptr; -extern typeof(&pthread_mutex_unlock) pthread_mutex_unlock_ptr; -extern typeof(&sem_wait) sem_wait_ptr; -extern typeof(&sem_post) sem_post_ptr; -extern typeof(&sem_init) sem_init_ptr; -extern typeof(&sem_destroy) sem_destroy_ptr; -extern __attribute__((__noreturn__)) typeof(&exit) exit_ptr; -extern __attribute__((__noreturn__)) typeof(&abort) abort_ptr; -//extern typeof(&pthread_barrier_init) pthread_barrier_init_ptr; -//extern typeof(&pthread_barrier_wait) pthread_barrier_wait_ptr; -extern typeof(&pthread_cond_init) pthread_cond_init_ptr; -extern typeof(&pthread_cond_wait) pthread_cond_wait_ptr; -extern typeof(&pthread_cond_signal) pthread_cond_signal_ptr; -extern typeof(&pthread_cond_broadcast) pthread_cond_broadcast_ptr; -//extern typeof(&pthread_rwlock_init) pthread_rwlock_init_ptr; -//extern typeof(&pthread_rwlock_rdlock) pthread_rwlock_rdlock_ptr; -//extern typeof(&pthread_rwlock_wrlock) pthread_rwlock_wrlock_ptr; -//extern typeof(&pthread_rwlock_unlock) pthread_rwlock_unlock_ptr; -extern typeof(&sleep) sleep_ptr; - -#define __real_pthread_create (*pthread_create_ptr) -#define __real_pthread_join (*pthread_join_ptr) -#define __real_pthread_mutex_init (*pthread_mutex_init_ptr) -#define __real_pthread_mutex_lock (*pthread_mutex_lock_ptr) -#define __real_pthread_mutex_unlock (*pthread_mutex_unlock_ptr) -#define __real_sem_wait (*sem_wait_ptr) -#define __real_sem_post (*sem_post_ptr) -#define __real_sem_init (*sem_init_ptr) -#define __real_sem_destroy (*sem_destroy_ptr) -#define __real_exit (*exit_ptr) -#define __real_abort (*abort_ptr) -#define __real_pthread_barrier_init (*pthread_barrier_init_ptr) -#define __real_pthread_barrier_wait (*pthread_barrier_wait_ptr) -#define __real_pthread_cond_init (*pthread_cond_init_ptr) -#define __real_pthread_cond_wait (*pthread_cond_wait_ptr) -#define __real_pthread_cond_signal (*pthread_cond_signal_ptr) -#define __real_pthread_cond_broadcast (*pthread_cond_broadcast_ptr) -#define __real_pthread_rwlock_init (*pthread_rwlock_init_ptr) -#define __real_pthread_rwlock_rdlock (*pthread_rwlock_rdlock_ptr) -#define __real_pthread_rwlock_wrlock (*pthread_rwlock_wrlock_ptr) -#define __real_pthread_rwlock_unlock (*pthread_rwlock_unlock_ptr) -#define __real_sleep (*sleep_ptr) - -// /** -// * @brief Retrieves the addresses of the symbols exposed by the -// * dynamic libraries that McMini redefines to override the -// * functions' original behavior -// * -// * McMini re-defines and exports symbols defined in other dynamic -// * libraries to change the behavior of a program which is dynamically -// * linked to the libraries containing those symbols. When McMini as a -// * dynamic libarary is pre-loaded into memory, a process making calls -// * to the dynamic library will result in McMini's symbols being -// * dynamically chosen instead. -// * -// * If the original behavior of the intercepted symbols is still -// * needed, they must be retrieved using dlsym(3) and other functions. -// * This function loads those symbols into the variables redefined -// * above. -// */ -void load_intercepted_symbol_addresses(); - -#endif diff --git a/docs/design/include/mcmini/common/shm_config.h b/docs/design/include/mcmini/common/shm_config.h new file mode 100644 index 00000000..ee96f64c --- /dev/null +++ b/docs/design/include/mcmini/common/shm_config.h @@ -0,0 +1,17 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include "mcmini/defines.h" + +const extern size_t shm_size; +void mc_get_shm_handle_name(char *dst, size_t sz); + +#ifdef __cplusplus +} +#endif // extern "C" \ No newline at end of file diff --git a/docs/design/include/mcmini/model/transition_registry.hpp b/docs/design/include/mcmini/model/transition_registry.hpp index e88bb229..a61109a9 100644 --- a/docs/design/include/mcmini/model/transition_registry.hpp +++ b/docs/design/include/mcmini/model/transition_registry.hpp @@ -6,7 +6,7 @@ #include "mcmini/coordinator/coordinator.hpp" #include "mcmini/model/transition.hpp" -#include "mcmini/real_world/runner_mailbox_stream.hpp" +#include "mcmini/real_world/mailbox/runner_mailbox_stream.hpp" namespace model { diff --git a/docs/design/include/mcmini/real_world/runner_mailbox.h b/docs/design/include/mcmini/real_world/mailbox/runner_mailbox.h similarity index 100% rename from docs/design/include/mcmini/real_world/runner_mailbox.h rename to docs/design/include/mcmini/real_world/mailbox/runner_mailbox.h diff --git a/docs/design/include/mcmini/real_world/runner_mailbox_stream.hpp b/docs/design/include/mcmini/real_world/mailbox/runner_mailbox_stream.hpp similarity index 100% rename from docs/design/include/mcmini/real_world/runner_mailbox_stream.hpp rename to docs/design/include/mcmini/real_world/mailbox/runner_mailbox_stream.hpp diff --git a/docs/design/include/mcmini/real_world/process.hpp b/docs/design/include/mcmini/real_world/process.hpp index fc1beb85..bdd2dbd6 100644 --- a/docs/design/include/mcmini/real_world/process.hpp +++ b/docs/design/include/mcmini/real_world/process.hpp @@ -3,7 +3,7 @@ #include #include -#include "mcmini/real_world/runner_mailbox_stream.hpp" +#include "mcmini/real_world/mailbox/runner_mailbox_stream.hpp" namespace real_world { diff --git a/docs/design/include/mcmini/real_world/process/fork_process_source.hpp b/docs/design/include/mcmini/real_world/process/fork_process_source.hpp index 89e21192..c84369aa 100644 --- a/docs/design/include/mcmini/real_world/process/fork_process_source.hpp +++ b/docs/design/include/mcmini/real_world/process/fork_process_source.hpp @@ -2,7 +2,10 @@ #include +#include "mcmini/defines.h" +#include "mcmini/misc/volatile_mem_streambuf.hpp" #include "mcmini/real_world/process_source.hpp" +#include "mcmini/real_world/shm.hpp" namespace real_world { @@ -12,7 +15,7 @@ namespace real_world { * located at `target` with `libmcmini.so` preloaded. * * A `fork_process_source` is responsible for creating new processes by forking - * this process. + * this process and repeatedly `exec()`-ing into a new one. */ class fork_process_source : public process_source { private: @@ -22,12 +25,17 @@ class fork_process_source : public process_source { // is eventually supported // Alternatively, have McMini conditionally // compile a std::filesystem::path e.g. + static std::unique_ptr rw_region; + static volatile_mem_streambuf + runner_mailbox_bufs[MAX_TOTAL_THREADS_IN_PROGRAM]; + static runner_mailbox_stream + *runner_mailbox_streams[MAX_TOTAL_THREADS_IN_PROGRAM]; + static void initialize_shared_memory(); + void setup_ld_preload(); public: - fork_process_source(std::string target_program) - : target_program(std::move(target_program)) {} - + fork_process_source(std::string target_program); std::unique_ptr make_new_process() override; }; diff --git a/docs/design/include/mcmini/real_world/process/local_linux_process.hpp b/docs/design/include/mcmini/real_world/process/local_linux_process.hpp index 2a5b92fd..71fd65e9 100644 --- a/docs/design/include/mcmini/real_world/process/local_linux_process.hpp +++ b/docs/design/include/mcmini/real_world/process/local_linux_process.hpp @@ -14,23 +14,29 @@ namespace real_world { * @brief A proxy for a process running under Linux. * * A `real_world::linux_process` is a local proxy for a process running - * on the same machine. + * on the same machine. Each instance manages a portion of shared memory that it + * uses to communicate with the proxied process. The proxied process is assumed + * to be under the control of `libmcmini.so`; otherwise, executing runners + * results in undefined behavior. */ class local_linux_process : public process { private: pid_t pid; - // TODO: We add the shared memory portion here for now and figure out the - // "runner" bit later... Each local linux process will share the same static - // memory region. Even in the runner model, the thread runners would each - // share the memory region but it wouldn't be attached to the processes - // themselves - static void initialize_shared_memory(); - static std::unique_ptr rw_region; + // A reference to shared memory that outlives this process and within which + // `libmcmini.so` in the proxied process writes its data. + shared_memory_region &shm_slice; + + // NOTE: At the moment, each process has the entire view of the + // shared memory region at its disposal. If desired, an extra layer could be + // added on top which manages allocating slices of a `shared_memory_region` + // and "allocates" them to different processes. This would look similar to + // `malloc()/free()`, where the `free()` would be triggered by the destructor + // of the slice. This is overly complicated at the moment, and we simply + // restrict the number of proxy processes to one. public: - local_linux_process() = default; - local_linux_process(pid_t pid); + local_linux_process(pid_t pid, shared_memory_region &shm_slice); virtual ~local_linux_process(); runner_mailbox_stream &execute_runner(runner_id_t mcmini_runner_id) override; diff --git a/docs/design/include/mcmini/shared_sem.h b/docs/design/include/mcmini/shared_sem.h deleted file mode 100644 index 56a62cc7..00000000 --- a/docs/design/include/mcmini/shared_sem.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef INCLUDE SHARE_SEM_H -#define INCLUDE_SHARE_SEM_H - -#include -#define MC_STRUCT_DECL(type) \ - typedef struct type type; \ - typedef struct type *type##_t; \ - typedef struct type *type##_ref; \ - typedef const struct type *type##_refc; - -MC_STRUCT_DECL(shared_sem) -struct shared_sem { - sem_t runner_sem; //previously named dpor_scheduler_sem - sem_t pthread_sem; -}; - -void shared_sem_init(shared_sem_ref); -void shared_sem_destroy(shared_sem_ref); - -void shared_sem_post_for_thread(shared_sem_ref); -void shared_sem_wait_for_thread(shared_sem_ref); - -void shared_sem_post_for_runner(shared_sem_ref); -void shared_sem_wait_for_runner(shared_sem_ref); - -void shared_sem_wake_runner(shared_sem_ref); -void shared_sem_wake_thread(shared_sem_ref); - -#endif \ No newline at end of file diff --git a/docs/design/src/common/runner_mailbox.c b/docs/design/src/common/runner_mailbox.c index 2b2572e4..9d7a84b3 100644 --- a/docs/design/src/common/runner_mailbox.c +++ b/docs/design/src/common/runner_mailbox.c @@ -1,4 +1,4 @@ -#include "mcmini/real_world/runner_mailbox.h" +#include "mcmini/real_world/mailbox/runner_mailbox.h" #include "mcmini/defines.h" #include "string.h" diff --git a/docs/design/src/common/shm_config.c b/docs/design/src/common/shm_config.c new file mode 100644 index 00000000..c3972e9a --- /dev/null +++ b/docs/design/src/common/shm_config.c @@ -0,0 +1,19 @@ +#include "mcmini/common/shm_config.h" +#include "mcmini/real_world/mailbox/runner_mailbox.h" + + +#include +#include +#include +#include +#include + + +const size_t shm_size = sizeof(runner_mailbox) * MAX_TOTAL_THREADS_IN_PROGRAM; + +void +mc_get_shm_handle_name(char *dst, size_t sz) +{ + snprintf(dst, sz, "/mcmini-%s-%lu", getenv("USER"), (long)getppid()); + dst[sz - 1] = '\0'; +} \ No newline at end of file diff --git a/docs/design/src/libmcmini/entry.c b/docs/design/src/libmcmini/entry.c index 1602ca24..39abff07 100644 --- a/docs/design/src/libmcmini/entry.c +++ b/docs/design/src/libmcmini/entry.c @@ -10,12 +10,11 @@ #include #include -#include "mcmini/real_world/runner_mailbox.h" +#include "mcmini/real_world/mailbox/runner_mailbox.h" #include "mcmini/entry.h" - +#include "mcmini/common/shm_config.h" volatile void *shm_start = NULL; -const static size_t shm_size = sizeof(runner_mailbox) * MAX_TOTAL_THREADS_IN_PROGRAM; MCMINI_THREAD_LOCAL tid_t tid_self = TID_INVALID; tid_t @@ -26,13 +25,6 @@ mc_created_new_thread() return tid_self; } -void -mc_get_shm_handle_name(char *dst, size_t sz) -{ - snprintf(dst, sz, "/mcmini-%s-%lu", getenv("USER"), (long)getpid()); - dst[sz - 1] = '\0'; -} - void * mc_allocate_shared_memory_region() { diff --git a/docs/design/src/mcmini/mcmini.cpp b/docs/design/src/mcmini/mcmini.cpp index 5374800c..d72deaad 100644 --- a/docs/design/src/mcmini/mcmini.cpp +++ b/docs/design/src/mcmini/mcmini.cpp @@ -9,7 +9,6 @@ #include "mcmini/model/transitions/thread/thread_start.hpp" #include "mcmini/model_checking/algorithms/classic_dpor.hpp" #include "mcmini/real_world/process/fork_process_source.hpp" -#include "mcmini/real_world/runner_mailbox_stream.hpp" #include "mcmini/real_world/shm.hpp" #define _XOPEN_SOURCE_EXTENDED 1 diff --git a/docs/design/src/mcmini/real_world/fork_process_source.cpp b/docs/design/src/mcmini/real_world/fork_process_source.cpp index 4238fe75..1c736658 100644 --- a/docs/design/src/mcmini/real_world/fork_process_source.cpp +++ b/docs/design/src/mcmini/real_world/fork_process_source.cpp @@ -6,11 +6,39 @@ #include #include +#include +#include "mcmini/common/shm_config.h" +#include "mcmini/defines.h" #include "mcmini/misc/extensions/unique_ptr.hpp" +#include "mcmini/real_world/mailbox/runner_mailbox.h" #include "mcmini/real_world/process/local_linux_process.hpp" using namespace real_world; +using namespace extensions; + +std::unique_ptr fork_process_source::rw_region = nullptr; + +void fork_process_source::initialize_shared_memory() { + const std::string shm_file_name = "/mcmini-" + std::string(getenv("USER")) + + "-" + std::to_string((long)getpid()); + + rw_region = make_unique(shm_file_name, shm_size); + + volatile runner_mailbox* mbp = rw_region->as_stream_of(); + + // TODO: This should be a configurable parameter perhaps... + const int max_total_threads = MAX_TOTAL_THREADS_IN_PROGRAM; + for (int i = 0; i < max_total_threads; i++) { + mc_runner_mailbox_init(mbp + i); + } +} + +fork_process_source::fork_process_source(std::string target_program) + : target_program(std::move(target_program)) { + static std::once_flag shm_once_flag; + std::call_once(shm_once_flag, initialize_shared_memory); +} std::unique_ptr fork_process_source::make_new_process() { // const_cast<> is needed to call the C-functions here. A new/delete @@ -19,6 +47,22 @@ std::unique_ptr fork_process_source::make_new_process() { // to interface with the C library routines setup_ld_preload(); + { + // Reinitialize the region for the new process, as the contents of the + // memory are dirtied from the last process which used the same memory and + // exited arbitrarily (i.e. in such a way as to leave data in the shared + // memory). + // + // INVARIANT: Only one `local_linux_process` is in existence at any given + // time. + volatile runner_mailbox* mbp = rw_region->as_stream_of(); + const int max_total_threads = MAX_TOTAL_THREADS_IN_PROGRAM; + for (int i = 0; i < max_total_threads; i++) { + mc_runner_mailbox_destroy(mbp + i); + mc_runner_mailbox_init(mbp + i); + } + } + errno = 0; pid_t child_pid = fork(); if (child_pid == -1) { @@ -31,10 +75,10 @@ std::unique_ptr fork_process_source::make_new_process() { std::cerr << "About to exec with libmcmini.so loaded! Attempting to run " << this->target_program.c_str() << std::endl; execvp(this->target_program.c_str(), args); - perror("execvp"); // Handle execvp error here + perror("execvp"); std::exit(EXIT_FAILURE); } - return extensions::make_unique(child_pid); + return extensions::make_unique(child_pid, *rw_region); } void fork_process_source::setup_ld_preload() { diff --git a/docs/design/src/mcmini/real_world/local_linux_process.cpp b/docs/design/src/mcmini/real_world/local_linux_process.cpp index 0009de7b..2c8017be 100644 --- a/docs/design/src/mcmini/real_world/local_linux_process.cpp +++ b/docs/design/src/mcmini/real_world/local_linux_process.cpp @@ -10,28 +10,14 @@ #include "mcmini/defines.h" #include "mcmini/misc/extensions/unique_ptr.hpp" -#include "mcmini/real_world/runner_mailbox.h" +#include "mcmini/real_world/mailbox/runner_mailbox.h" using namespace real_world; using namespace extensions; -std::unique_ptr local_linux_process::rw_region = nullptr; - -void local_linux_process::initialize_shared_memory() { - rw_region = make_unique("hello", 100); - volatile runner_mailbox *mbp = rw_region->as_stream_of(); - - // TODO: This should be a configurable parameter perhaps... - const int max_total_threads = MAX_TOTAL_THREADS_IN_PROGRAM; - for (int i = 0; i < max_total_threads; i++) { - mc_runner_mailbox_init(mbp + i); - } -} - -local_linux_process::local_linux_process(pid_t pid) : pid(pid) { - static std::once_flag shm_once_flag; - std::call_once(shm_once_flag, initialize_shared_memory); -} +local_linux_process::local_linux_process(pid_t pid, + shared_memory_region &shm_slice) + : pid(pid), shm_slice(shm_slice) {} local_linux_process::~local_linux_process() { if (pid <= 0) { @@ -56,7 +42,7 @@ local_linux_process::~local_linux_process() { } runner_mailbox_stream &local_linux_process::execute_runner(runner_id_t id) { - volatile runner_mailbox *rmb = rw_region->as_stream_of(id); + volatile runner_mailbox *rmb = shm_slice.as_stream_of(id); volatile_mem_streambuf runner_mem_stream{&rmb->cnts, sizeof(&rmb->cnts)}; mc_wake_thread(rmb); From b0d2a279231658a5fbacf32d82b26e574fff94b3 Mon Sep 17 00:00:00 2001 From: Maxwell Pirtle Date: Sat, 20 Apr 2024 20:27:18 -0400 Subject: [PATCH 010/161] Add pipe(2) to process execvp errors in fork_process_source This commit introduces a pipe(2) call used to communicate errno codes from execvp in the forked child process to the parent process. The parent uses the presence of a value as an indication of error. When the child successfully runs `execvp()`, and since the write end of the pipe has been marked `FD_CLOEXEC`, the parent will return from `read(2)` with a value of 0 and will continue execution as normal --- .../process/fork_process_source.hpp | 1 + .../mcmini/real_world/fork_process_source.cpp | 75 ++++++++++++++----- 2 files changed, 56 insertions(+), 20 deletions(-) diff --git a/docs/design/include/mcmini/real_world/process/fork_process_source.hpp b/docs/design/include/mcmini/real_world/process/fork_process_source.hpp index c84369aa..f3f1f3f3 100644 --- a/docs/design/include/mcmini/real_world/process/fork_process_source.hpp +++ b/docs/design/include/mcmini/real_world/process/fork_process_source.hpp @@ -33,6 +33,7 @@ class fork_process_source : public process_source { static void initialize_shared_memory(); void setup_ld_preload(); + void reset_binary_semaphores_for_new_process(); public: fork_process_source(std::string target_program); diff --git a/docs/design/src/mcmini/real_world/fork_process_source.cpp b/docs/design/src/mcmini/real_world/fork_process_source.cpp index 1c736658..d26ae5b1 100644 --- a/docs/design/src/mcmini/real_world/fork_process_source.cpp +++ b/docs/design/src/mcmini/real_world/fork_process_source.cpp @@ -1,5 +1,6 @@ #include "mcmini/real_world/process/fork_process_source.hpp" +#include #include #include #include @@ -41,42 +42,60 @@ fork_process_source::fork_process_source(std::string target_program) } std::unique_ptr fork_process_source::make_new_process() { - // const_cast<> is needed to call the C-functions here. A new/delete - // or malloc/free _could be_ needed, we'd need to check the man page. As long - // as the char * is not actually modified, this is OK and the best way - // to interface with the C library routines setup_ld_preload(); + reset_binary_semaphores_for_new_process(); - { - // Reinitialize the region for the new process, as the contents of the - // memory are dirtied from the last process which used the same memory and - // exited arbitrarily (i.e. in such a way as to leave data in the shared - // memory). - // - // INVARIANT: Only one `local_linux_process` is in existence at any given - // time. - volatile runner_mailbox* mbp = rw_region->as_stream_of(); - const int max_total_threads = MAX_TOTAL_THREADS_IN_PROGRAM; - for (int i = 0; i < max_total_threads; i++) { - mc_runner_mailbox_destroy(mbp + i); - mc_runner_mailbox_init(mbp + i); - } + int pipefd[2]; + if (pipe(pipefd) == -1) { + throw std::runtime_error("Failed to open pipe(2)"); } errno = 0; pid_t child_pid = fork(); if (child_pid == -1) { + // fork(2) failed throw process_source::process_creation_exception( "Failed to create a new process (fork(2) failed): " + std::string(strerror(errno))); } else if (child_pid == 0) { + // Child process case + + close(pipefd[0]); + fcntl(pipefd[1], F_SETFD, FD_CLOEXEC); + // TODO: Add additional arguments here if needed + // const_cast<> is needed to call the C-functions here. A new/delete + // or malloc/free _could be_ needed, we'd need to check the man page. As + // long as the char * is not actually modified, this is OK and the best way + // to interface with the C library routines char* args[] = {const_cast(this->target_program.c_str()), NULL}; std::cerr << "About to exec with libmcmini.so loaded! Attempting to run " << this->target_program.c_str() << std::endl; execvp(this->target_program.c_str(), args); - perror("execvp"); - std::exit(EXIT_FAILURE); + + // IF EXECVP fails + int err = errno; + write(pipefd[1], &err, sizeof(err)); + close(pipefd[1]); + exit(EXIT_FAILURE); + } else { + // Parent process case + close(pipefd[1]); // Close write end + + int err = 0; + if (read(pipefd[0], &err, sizeof(err)) > 0) { + // waitpid() ensures that the child's resources are properly reacquired. + if (waitpid(child_pid, nullptr, 0) == -1) { + throw process_source::process_creation_exception( + "Failed to create a cleanup zombied child process (waitpid " + "returned -1): " + + std::string(strerror(errno))); + } + throw process_source::process_creation_exception( + "Failed to create a new process (execvp failed): " + + std::string(strerror(err))); + } + close(pipefd[0]); } return extensions::make_unique(child_pid, *rw_region); } @@ -88,4 +107,20 @@ void fork_process_source::setup_ld_preload() { (getenv("LD_PRELOAD") ? getenv("LD_PRELOAD") : ""), dirname(const_cast(this->target_program.c_str()))); setenv("LD_PRELOAD", buf, 1); +} + +void fork_process_source::reset_binary_semaphores_for_new_process() { + // Reinitialize the region for the new process, as the contents of the + // memory are dirtied from the last process which used the same memory and + // exited arbitrarily (i.e. in such a way as to leave data in the shared + // memory). + // + // INVARIANT: Only one `local_linux_process` is in existence at any given + // time. + volatile runner_mailbox* mbp = rw_region->as_stream_of(); + const int max_total_threads = MAX_TOTAL_THREADS_IN_PROGRAM; + for (int i = 0; i < max_total_threads; i++) { + mc_runner_mailbox_destroy(mbp + i); + mc_runner_mailbox_init(mbp + i); + } } \ No newline at end of file From 0117c7a509156f8ca6fde08b5f9d6857599ee741 Mon Sep 17 00:00:00 2001 From: Maxwell Pirtle Date: Sun, 21 Apr 2024 18:52:32 -0400 Subject: [PATCH 011/161] Prepare to add runner mapping to state This commit prepares the `transition` class to include an `executor` of type `runner_id_t`. Each transition is represented as the application of a partial function of a particular runner. We adopt the abstraction used in Abdulla et al. to represent transitions. --- docs/design/include/mcmini/model/program.hpp | 3 -- docs/design/include/mcmini/model/state.hpp | 1 + .../include/mcmini/model/transition.hpp | 21 +++++++++-- .../model/transitions/mutex/mutex_init.hpp | 3 +- .../model/transitions/mutex/mutex_lock.hpp | 5 +-- .../model/transitions/mutex/mutex_unlock.hpp | 35 +++++++++++++++++++ .../model/transitions/thread/thread_start.hpp | 21 +++++------ 7 files changed, 67 insertions(+), 22 deletions(-) create mode 100644 docs/design/include/mcmini/model/transitions/mutex/mutex_unlock.hpp diff --git a/docs/design/include/mcmini/model/program.hpp b/docs/design/include/mcmini/model/program.hpp index 7cfcdaa0..e4b6bdbf 100644 --- a/docs/design/include/mcmini/model/program.hpp +++ b/docs/design/include/mcmini/model/program.hpp @@ -73,13 +73,10 @@ class program { void model_executing_runner(runner_id_t p, std::unique_ptr new_transition) { const transition *next_s_p = next_steps.get_transition_for_runner(p); - if (next_s_p) { this->state_seq.follow(*next_s_p); this->next_steps.displace_transition_for(p, std::move(new_transition)); } else { - // TODO: Handle the case where `p` doesn't exist. Perhaps this function - // should return a `result<>` type. throw std::runtime_error( "Attempted to execute a runner whose transition was not currently " "enabled"); diff --git a/docs/design/include/mcmini/model/state.hpp b/docs/design/include/mcmini/model/state.hpp index f41a0494..4cb35802 100644 --- a/docs/design/include/mcmini/model/state.hpp +++ b/docs/design/include/mcmini/model/state.hpp @@ -16,6 +16,7 @@ namespace model { class state { public: using objid_t = uint32_t; + using runner_id_t = uint32_t; virtual ~state() = default; virtual size_t count() const = 0; diff --git a/docs/design/include/mcmini/model/transition.hpp b/docs/design/include/mcmini/model/transition.hpp index c1648372..a6332d8e 100644 --- a/docs/design/include/mcmini/model/transition.hpp +++ b/docs/design/include/mcmini/model/transition.hpp @@ -3,7 +3,6 @@ #include #include "mcmini/forwards.hpp" -#include "mcmini/misc/optional.hpp" #include "mcmini/model/state.hpp" namespace model { @@ -41,7 +40,7 @@ namespace model { * `model::transition` allows the transition to produce a state _even if * the process which is set to execute the transition isn't truly in a position * where the transition is being executed_. Transitions perform _look ups_ on - * the particular state they are given + * the particular state they are given. * * For example, consider a transition "post(sem)" which takes a visible object * (a semaphore) with id `sem` as an argument. The sempahore `sem` may exist in @@ -52,10 +51,26 @@ namespace model { * is _not_ executing a `post(sem)`. However, the implementation of "thread 1 * executes post(sem)" _would be defined_ in state `s_2`. In other words, a * `model::state` _excludes_ the state of the _processes_; that is the - * responsibility of `model::program`. + * responsibility of `model::program` to ensure that this transition is indeed + * the next one for the process running it. + * + * @note Our definition of transition is more in line with Abdulla et al. 2017, + * viz. one in which we assume a program is a collection of processes each of + * which can be represented as partial functions executing atomically; + * therefore, every transition in McMini is executed by _some_ runner, even if + * the runner itself is virtual (e.g. a transition that affects multiple + * threads). */ class transition { public: + using runner_id_t = uint32_t; + + /** + * The thread/runner which actually executes this transition. + */ + const runner_id_t executor; + transition(runner_id_t executor) : executor(executor) {} + /** * @brief Attempts to produce a state _s'_ from state _s_ through the * application of this transition function on argument _s_ diff --git a/docs/design/include/mcmini/model/transitions/mutex/mutex_init.hpp b/docs/design/include/mcmini/model/transitions/mutex/mutex_init.hpp index 957d5da8..6cbb2283 100644 --- a/docs/design/include/mcmini/model/transitions/mutex/mutex_init.hpp +++ b/docs/design/include/mcmini/model/transitions/mutex/mutex_init.hpp @@ -11,7 +11,8 @@ struct mutex_init : public model::transition { state::objid_t mutex_id; /* The mutex this transition initializes */ public: - mutex_init(state::objid_t mutex_id) : mutex_id(mutex_id) {} + mutex_init(runner_id_t executor, state::objid_t mutex_id) + : mutex_id(mutex_id), transition(executor) {} ~mutex_init() = default; status modify(model::mutable_state& s) const override { diff --git a/docs/design/include/mcmini/model/transitions/mutex/mutex_lock.hpp b/docs/design/include/mcmini/model/transitions/mutex/mutex_lock.hpp index 799b045c..e1548473 100644 --- a/docs/design/include/mcmini/model/transitions/mutex/mutex_lock.hpp +++ b/docs/design/include/mcmini/model/transitions/mutex/mutex_lock.hpp @@ -8,10 +8,11 @@ namespace transitions { struct mutex_lock : public model::transition { private: - state::objid_t mutex_id; /* The mutex this transition initializes */ + state::objid_t mutex_id; /* The mutex this transition locks */ public: - mutex_lock(state::objid_t mutex_id) : mutex_id(mutex_id) {} + mutex_lock(runner_id_t executor, state::objid_t mutex_id) + : mutex_id(mutex_id), transition(executor) {} ~mutex_lock() = default; status modify(model::mutable_state& s) const override { diff --git a/docs/design/include/mcmini/model/transitions/mutex/mutex_unlock.hpp b/docs/design/include/mcmini/model/transitions/mutex/mutex_unlock.hpp new file mode 100644 index 00000000..9ba0c1c4 --- /dev/null +++ b/docs/design/include/mcmini/model/transitions/mutex/mutex_unlock.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include "mcmini/model/objects/mutex.hpp" +#include "mcmini/model/transition.hpp" + +namespace model { +namespace transitions { + +struct mutex_unlock : public model::transition { + private: + state::objid_t mutex_id; /* The mutex this transition unlocks */ + + public: + mutex_unlock(runner_id_t executor, state::objid_t mutex_id) + : mutex_id(mutex_id), transition(executor) {} + ~mutex_lock() = default; + + status modify(model::mutable_state& s) const override { + using namespace model::objects; + + // A `mutex_lock` cannot be applied to a mutex already locked. + const mutex* ms = s.get_state_of_object(mutex_id); + if (ms->is_locked()) { + return status::disabled; + } + s.add_state_for(mutex_id, mutex::make(mutex::unlocked)); + return status::exists; + } + + std::string to_string() const override { + return "mutex_lock(" + std::to_string(mutex_id) + ")"; + } +}; +} // namespace transitions +} // namespace model \ No newline at end of file diff --git a/docs/design/include/mcmini/model/transitions/thread/thread_start.hpp b/docs/design/include/mcmini/model/transitions/thread/thread_start.hpp index df402de4..0d40a4e2 100644 --- a/docs/design/include/mcmini/model/transitions/thread/thread_start.hpp +++ b/docs/design/include/mcmini/model/transitions/thread/thread_start.hpp @@ -7,26 +7,21 @@ namespace model { namespace transitions { struct thread_start : public model::transition { - private: - state::objid_t thread_id; /* The mutex this transition initializes */ - public: - thread_start(state::objid_t thread_id) : thread_id(thread_id) {} + thread_start(state::runner_id_t executor) : transition(executor) {} ~thread_start() = default; status modify(model::mutable_state& s) const override { - using namespace model::objects; - auto* thread_state = s.get_state_of_object(thread_id); - if (!thread_state->is_embryo()) { - return status::disabled; - } - s.add_state_for(thread_id, thread::make(thread::running)); + // using namespace model::objects; + // auto* thread_state = s.get_state_of_object(thread_id); + // if (!thread_state->is_embryo()) { + // return status::disabled; + // } + // s.add_state_for(thread_id, thread::make(thread::running)); return status::exists; } - std::string to_string() const override { - return "thread_start(thread:" + std::to_string(thread_id) + ")"; - } + std::string to_string() const override { return "starts"; } }; } // namespace transitions From 544f86a60c5548c590c152be316b5d25cf31eb54 Mon Sep 17 00:00:00 2001 From: Maxwell Pirtle Date: Sun, 21 Apr 2024 20:31:03 -0400 Subject: [PATCH 012/161] Add runner mapping management to state/mutable_state This commit introduces methods for managing runners in states. Transitions operate on states and states alone; but they are themselves executed by runners. It is useful, and necessary sometimes (e.g. with the thread_start transition), to identify the modeled states of these runners in the model via the runner id instead of the object id directly. Here we note that it is sensible to add this mapping directly to the state: the transition that these runners are executing is outside the scope of the state, but simply tracking which objects they refer to is sensible. --- docs/design/include/mcmini/model/state.hpp | 23 +- .../mcmini/model/state/detached_state.hpp | 19 +- .../mcmini/model/state/state_sequence.hpp | 80 ++--- .../model/transitions/mutex/mutex_init.hpp | 4 +- .../model/transitions/mutex/mutex_lock.hpp | 4 +- .../model/transitions/mutex/mutex_unlock.hpp | 11 +- .../src/mcmini/model/detached_state.cpp | 43 ++- .../src/mcmini/model/state_sequence.cpp | 290 ++++++++++++++---- 8 files changed, 333 insertions(+), 141 deletions(-) diff --git a/docs/design/include/mcmini/model/state.hpp b/docs/design/include/mcmini/model/state.hpp index 4cb35802..c91631ef 100644 --- a/docs/design/include/mcmini/model/state.hpp +++ b/docs/design/include/mcmini/model/state.hpp @@ -20,8 +20,13 @@ class state { virtual ~state() = default; virtual size_t count() const = 0; + virtual size_t runner_count() const = 0; virtual bool contains_object_with_id(objid_t id) const = 0; + virtual bool contains_runner_with_id(runner_id_t id) const = 0; + virtual objid_t get_objid_for_runner(runner_id_t id) const = 0; virtual const visible_object_state *get_state_of_object(objid_t id) const = 0; + virtual const visible_object_state *get_state_of_runner( + runner_id_t id) const = 0; virtual std::unique_ptr consume_obj( objid_t id) && = 0; virtual std::unique_ptr mutable_clone() const = 0; @@ -79,6 +84,13 @@ class mutable_state : public state { virtual objid_t add_object( std::unique_ptr initial_state) = 0; + /** + * @brief Begin tracking a new visible object, but consider it as the state of + * a new runner. + */ + virtual runner_id_t add_runner( + std::unique_ptr initial_state) = 0; + /** * @brief Adds the given state _state_ for the object with id _id_. * @@ -86,9 +98,18 @@ class mutable_state : public state { * of type `visible_object_state_type`, the behavior of this function is * undefined. */ - virtual void add_state_for( + virtual void add_state_for_obj( objid_t id, std::unique_ptr new_state) = 0; + /** + * @brief Adds the given state _state_ for the runner with id _id_. + * + * This is equivalent to first retrieving the object id of the runner in this + * state and then asking for that object's state. + */ + virtual void add_state_for_runner( + runner_id_t id, std::unique_ptr new_state) = 0; + /** * @brief Creates a copy of the given state. * diff --git a/docs/design/include/mcmini/model/state/detached_state.hpp b/docs/design/include/mcmini/model/state/detached_state.hpp index b69c1a2a..eda89e60 100644 --- a/docs/design/include/mcmini/model/state/detached_state.hpp +++ b/docs/design/include/mcmini/model/state/detached_state.hpp @@ -17,6 +17,10 @@ class detached_state : public model::mutable_state { private: append_only visible_objects; + // INVARIANT: Runner ids are assigned sequentially. A runner with id `id` is + // mapped to the object id at index `id - 1`. + append_only runner_to_obj_map; + public: detached_state() = default; detached_state(const detached_state &) = default; @@ -26,12 +30,21 @@ class detached_state : public model::mutable_state { /* `state` overrrides */ size_t count() const override { return visible_objects.size(); } - bool contains_object_with_id(objid_t id) const override; + size_t runner_count() const override { return runner_to_obj_map.size(); } + objid_t get_objid_for_runner(runner_id_t id) const override; + bool contains_object_with_id(state::objid_t id) const override; + bool contains_runner_with_id(runner_id_t id) const override; const visible_object_state *get_state_of_object(objid_t id) const override; + const visible_object_state *get_state_of_runner( + runner_id_t id) const override; objid_t add_object( std::unique_ptr initial_state) override; - void add_state_for(objid_t id, - std::unique_ptr new_state) override; + runner_id_t add_runner( + std::unique_ptr initial_state) override; + void add_state_for_obj( + objid_t id, std::unique_ptr new_state) override; + void add_state_for_runner( + runner_id_t id, std::unique_ptr new_state) override; std::unique_ptr consume_obj(objid_t id) && override; std::unique_ptr mutable_clone() const override; diff --git a/docs/design/include/mcmini/model/state/state_sequence.hpp b/docs/design/include/mcmini/model/state/state_sequence.hpp index e51b0a00..1077c7da 100644 --- a/docs/design/include/mcmini/model/state/state_sequence.hpp +++ b/docs/design/include/mcmini/model/state/state_sequence.hpp @@ -23,57 +23,27 @@ namespace model { */ class state_sequence : public mutable_state { private: - /** - * @brief An element of a `model::state_sequence` - * - * The `element` and `state_sequence` are tightly intertwined. We allow them - * to work in tandem with one another as an implementation detail to permit - * "views" of the objects the state sequence maintains as new states are added - * to the sequence via transitions - */ - class element : public state { - private: - /// @brief A collection of references to states in the sequence - /// _owning_sequence_ to which this element belongs. - /// - /// Each state in the view - std::unordered_map - visible_object_states; - - element(const state_sequence &owner); - friend state_sequence; - - public: - element() = default; - void point_to_state_for(objid_t id, const visible_object_state *new_state) { - this->visible_object_states[id] = new_state; - } - virtual size_t count() const override { - return visible_object_states.size(); - } - virtual bool contains_object_with_id(objid_t id) const override; - virtual const visible_object_state *get_state_of_object( - objid_t id) const override; - virtual std::unique_ptr - consume_obj(objid_t id) && override; - virtual std::unique_ptr mutable_clone() const override; - }; - class diff_state; + class element; + + /// @brief Inserts an instance of `element` in the `states_in_sequence` + void push_state_snapshot(); - void push_state_snapshot() { - this->states_in_sequence.push_back(element(*this)); - } // INVARIANT: As new states are added to the visible objects in the // mapping `visible_objects`, new state views are also added with the // appropriate object states replaced for the _last element_ of // `states_in_sequence`. When new objects are added to `visible_objects`, a // corresponding object state is added to the _last_ element in the sequence. append_only visible_objects; - append_only states_in_sequence; + append_only states_in_sequence; + + // INVARIANT: Runner ids are assigned sequentially. A runner with id `id` is + // mapped to the object id at index `id - 1`. + append_only runner_to_obj_map; public: state_sequence(); + ~state_sequence(); state_sequence(const state &); // state_sequence(state &&); state_sequence(state_sequence &) = delete; @@ -84,17 +54,25 @@ class state_sequence : public mutable_state { state_sequence &operator=(const state_sequence &) = delete; /* `state` overrrides */ - virtual bool contains_object_with_id(state::objid_t id) const override; - virtual size_t count() const override { return visible_objects.size(); } - virtual const visible_object_state *get_state_of_object( - objid_t id) const override; - virtual objid_t add_object( + size_t count() const override { return visible_objects.size(); } + size_t runner_count() const override { return runner_to_obj_map.size(); } + objid_t get_objid_for_runner(runner_id_t id) const override; + bool contains_object_with_id(state::objid_t id) const override; + bool contains_runner_with_id(runner_id_t id) const override; + const visible_object_state *get_state_of_object(objid_t id) const override; + const visible_object_state *get_state_of_runner( + runner_id_t id) const override; + objid_t add_object( + std::unique_ptr initial_state) override; + runner_id_t add_runner( std::unique_ptr initial_state) override; - virtual void add_state_for( + void add_state_for_obj( objid_t id, std::unique_ptr new_state) override; - virtual std::unique_ptr consume_obj(objid_t id) && + void add_state_for_runner( + runner_id_t id, std::unique_ptr new_state) override; + std::unique_ptr consume_obj(objid_t id) && override; - virtual std::unique_ptr mutable_clone() const override; + std::unique_ptr mutable_clone() const override; /* Applying transitions */ @@ -111,10 +89,8 @@ class state_sequence : public mutable_state { */ transition::status follow(const transition &t); - size_t state_count() const { return this->states_in_sequence.size(); } - const state &state_at(size_t i) const { - return this->states_in_sequence.at(i); - } + size_t state_count() const; + const state &state_at(size_t i) const; /** * @brief Moves the contents from index 0 to index _index_ (inclusive) of this diff --git a/docs/design/include/mcmini/model/transitions/mutex/mutex_init.hpp b/docs/design/include/mcmini/model/transitions/mutex/mutex_init.hpp index 6cbb2283..3cd5e0b0 100644 --- a/docs/design/include/mcmini/model/transitions/mutex/mutex_init.hpp +++ b/docs/design/include/mcmini/model/transitions/mutex/mutex_init.hpp @@ -17,12 +17,12 @@ struct mutex_init : public model::transition { status modify(model::mutable_state& s) const override { using namespace model::objects; - s.add_state_for(mutex_id, mutex::make(mutex::unlocked)); + s.add_state_for_obj(mutex_id, mutex::make(mutex::unlocked)); return status::exists; } std::string to_string() const override { - return "mutex_init(" + std::to_string(mutex_id) + ")"; + return "mutex_init(mutex:" + std::to_string(mutex_id) + ")"; } }; } // namespace transitions diff --git a/docs/design/include/mcmini/model/transitions/mutex/mutex_lock.hpp b/docs/design/include/mcmini/model/transitions/mutex/mutex_lock.hpp index e1548473..e9ca4380 100644 --- a/docs/design/include/mcmini/model/transitions/mutex/mutex_lock.hpp +++ b/docs/design/include/mcmini/model/transitions/mutex/mutex_lock.hpp @@ -23,12 +23,12 @@ struct mutex_lock : public model::transition { if (ms->is_locked()) { return status::disabled; } - s.add_state_for(mutex_id, mutex::make(mutex::locked)); + s.add_state_for_obj(mutex_id, mutex::make(mutex::locked)); return status::exists; } std::string to_string() const override { - return "mutex_lock(" + std::to_string(mutex_id) + ")"; + return "mutex_lock(mutex:" + std::to_string(mutex_id) + ")"; } }; } // namespace transitions diff --git a/docs/design/include/mcmini/model/transitions/mutex/mutex_unlock.hpp b/docs/design/include/mcmini/model/transitions/mutex/mutex_unlock.hpp index 9ba0c1c4..e59806c0 100644 --- a/docs/design/include/mcmini/model/transitions/mutex/mutex_unlock.hpp +++ b/docs/design/include/mcmini/model/transitions/mutex/mutex_unlock.hpp @@ -13,22 +13,17 @@ struct mutex_unlock : public model::transition { public: mutex_unlock(runner_id_t executor, state::objid_t mutex_id) : mutex_id(mutex_id), transition(executor) {} - ~mutex_lock() = default; + ~mutex_unlock() = default; status modify(model::mutable_state& s) const override { using namespace model::objects; - - // A `mutex_lock` cannot be applied to a mutex already locked. const mutex* ms = s.get_state_of_object(mutex_id); - if (ms->is_locked()) { - return status::disabled; - } - s.add_state_for(mutex_id, mutex::make(mutex::unlocked)); + s.add_state_for_obj(mutex_id, mutex::make(mutex::unlocked)); return status::exists; } std::string to_string() const override { - return "mutex_lock(" + std::to_string(mutex_id) + ")"; + return "mutex_unlock(mutex:" + std::to_string(mutex_id) + ")"; } }; } // namespace transitions diff --git a/docs/design/src/mcmini/model/detached_state.cpp b/docs/design/src/mcmini/model/detached_state.cpp index 41ec86cf..5777c529 100644 --- a/docs/design/src/mcmini/model/detached_state.cpp +++ b/docs/design/src/mcmini/model/detached_state.cpp @@ -10,20 +10,51 @@ bool detached_state::contains_object_with_id(state::objid_t id) const { return id < this->visible_objects.size(); } +bool detached_state::contains_runner_with_id(state::runner_id_t id) const { + return id < runner_to_obj_map.size(); +} + +state::objid_t detached_state::get_objid_for_runner(runner_id_t id) const { + return this->contains_runner_with_id(id) ? runner_to_obj_map.at(id) + : model::invalid_obj_id; +} + +const visible_object_state *detached_state::get_state_of_object( + objid_t id) const { + return this->visible_objects.at(id).get_current_state(); +} + +const visible_object_state *detached_state::get_state_of_runner( + runner_id_t id) const { + return this->get_state_of_object(this->get_objid_for_runner(id)); +} + state::objid_t detached_state::add_object( - std::unique_ptr new_object) { - visible_objects.push_back(std::move(new_object)); + std::unique_ptr initial_state) { + // INVARIANT: The current element needs to update at index `id` to reflect + // this new object, as this element effectively represents this state + visible_objects.push_back(std::move(initial_state)); return visible_objects.size() - 1; } -void detached_state::add_state_for( +state::runner_id_t detached_state::add_runner( + std::unique_ptr new_state) { + objid_t id = this->add_object(std::move(new_state)); + this->runner_to_obj_map.push_back(id); + return this->runner_to_obj_map.size() - 1; +} + +void detached_state::add_state_for_obj( objid_t id, std::unique_ptr new_state) { + // INVARIANT: The current element needs to update at index `id` to reflect + // this new state, as this element effectively represents this state this->visible_objects.at(id).push_state(std::move(new_state)); } -const visible_object_state *detached_state::get_state_of_object( - objid_t id) const { - return this->visible_objects.at(id).get_current_state(); +void detached_state::add_state_for_runner( + runner_id_t id, std::unique_ptr new_state) { + return this->add_state_for_obj(this->get_objid_for_runner(id), + std::move(new_state)); } std::unique_ptr detached_state::consume_obj( diff --git a/docs/design/src/mcmini/model/state_sequence.cpp b/docs/design/src/mcmini/model/state_sequence.cpp index 1f366800..79167041 100644 --- a/docs/design/src/mcmini/model/state_sequence.cpp +++ b/docs/design/src/mcmini/model/state_sequence.cpp @@ -1,5 +1,6 @@ #include "mcmini/model/state/state_sequence.hpp" +#include #include #include "mcmini/misc/append-only.hpp" @@ -9,38 +10,45 @@ using namespace model; -class state_sequence::diff_state : public mutable_state { +/** + * @brief An element of a `model::state_sequence` + * + * The `element` and `state_sequence` are tightly intertwined. We allow them + * to work in tandem with one another as an implementation detail to permit + * "views" of the objects the state sequence maintains as new states are added + * to the sequence via transitions + */ +class state_sequence::element : public state { private: - const state_sequence &base_state; + const state_sequence &owner; - public: - // Purposely exposed in this private cpp file for implementation (pimpl). - std::unordered_map new_object_states; + /// @brief A collection of references to states in the sequence + /// _owning_sequence_ to which this element belongs. + /// + /// Each state is a view into a `visible_object` owned by `owner` + std::unordered_map + visible_object_states; - diff_state(const state_sequence &s) : base_state(s) {} - diff_state(const diff_state &ds) - : base_state(ds.base_state), new_object_states(ds.new_object_states) {} - diff_state(detached_state &&) = delete; - diff_state &operator=(const diff_state &) = delete; - detached_state &operator=(detached_state &&) = delete; + // The largest index into the owning state_sequence's runner mapping. That + // this element is aware of. + runner_id_t max_visible_runner_id; - /* `state` overrrides */ - virtual bool contains_object_with_id(objid_t id) const override; - virtual size_t count() const override { - size_t count = this->new_object_states.size() + base_state.count(); - for (const auto p : new_object_states) { - // Each item in `new_object_states` that is also in `base_state` defines - // states for _previously existing_ objects. These objects are accounted - // for in `count` (double-counted), hence the `--` - if (base_state.contains_object_with_id(p.first)) count--; - } - return count; + element(const state_sequence &owner); + friend state_sequence; + + public: + element() = default; + void point_to_state_for(objid_t id, const visible_object_state *new_state) { + this->visible_object_states[id] = new_state; } + size_t count() const override { return visible_object_states.size(); } + size_t runner_count() const override { return max_visible_runner_id; } + objid_t get_objid_for_runner(runner_id_t id) const override; + bool contains_object_with_id(state::objid_t id) const override; + bool contains_runner_with_id(runner_id_t id) const override; const visible_object_state *get_state_of_object(objid_t id) const override; - objid_t add_object( - std::unique_ptr initial_state) override; - void add_state_for(objid_t id, - std::unique_ptr new_state) override; + const visible_object_state *get_state_of_runner( + runner_id_t id) const override; std::unique_ptr consume_obj(objid_t id) && override; std::unique_ptr mutable_clone() const override; @@ -65,33 +73,64 @@ state_sequence::state_sequence(append_only &&ao) this->push_state_snapshot(); } +void state_sequence::push_state_snapshot() { + this->states_in_sequence.push_back(new element(*this)); +} + bool state_sequence::contains_object_with_id(state::objid_t id) const { return id < visible_objects.size(); } +bool state_sequence::contains_runner_with_id(state::runner_id_t id) const { + return id < runner_to_obj_map.size(); +} + +state::objid_t state_sequence::get_objid_for_runner(runner_id_t id) const { + return this->contains_runner_with_id(id) ? runner_to_obj_map.at(id) + : model::invalid_obj_id; +} + const visible_object_state *state_sequence::get_state_of_object( objid_t id) const { return this->visible_objects.at(id).get_current_state(); } +const visible_object_state *state_sequence::get_state_of_runner( + runner_id_t id) const { + return this->get_state_of_object(this->get_objid_for_runner(id)); +} + state::objid_t state_sequence::add_object( std::unique_ptr initial_state) { // INVARIANT: The current element needs to update at index `id` to reflect // this new object, as this element effectively represents this state objid_t id = visible_objects.size(); - this->states_in_sequence.back().point_to_state_for(id, initial_state.get()); + this->states_in_sequence.back()->point_to_state_for(id, initial_state.get()); visible_objects.push_back(std::move(initial_state)); return id; } -void state_sequence::add_state_for( +state::runner_id_t state_sequence::add_runner( + std::unique_ptr new_state) { + objid_t id = this->add_object(std::move(new_state)); + this->runner_to_obj_map.push_back(id); + return this->runner_to_obj_map.size() - 1; +} + +void state_sequence::add_state_for_obj( objid_t id, std::unique_ptr new_state) { // INVARIANT: The current element needs to update at index `id` to reflect // this new state, as this element effectively represents this state - this->states_in_sequence.back().point_to_state_for(id, new_state.get()); + this->states_in_sequence.back()->point_to_state_for(id, new_state.get()); this->visible_objects.at(id).push_state(std::move(new_state)); } +void state_sequence::add_state_for_runner( + runner_id_t id, std::unique_ptr new_state) { + return this->add_state_for_obj(this->get_objid_for_runner(id), + std::move(new_state)); +} + std::unique_ptr state_sequence::consume_obj( objid_t id) && { return std::move(visible_objects.at(id)).consume_into_current_state(); @@ -102,63 +141,62 @@ std::unique_ptr state_sequence::mutable_clone() const { this->visible_objects.cbegin(), this->visible_objects.cend()); } -transition::status state_sequence::follow(const transition &t) { - // Supply the transition a `diff_state` intentionally. We have control over - // the `apply_to` function: it simply returns a clone of the state it was - // provided. A `diff_state` copies only the reference to its underlying - // "backing" state, and only new objects will be - diff_state ds(*this); - auto maybe_diff = t.apply_to(ds); - - if (maybe_diff != nullptr) { - // Apply the diff to the sequence itself and create a new element which - // refers to the latest states of all the objects. The element is a - // placeholder for the state of the sequence as it looks after the - // application of transition `t`. Later if the sequenced is queried for - // the state of the object. - // - // Assumes that the `mutable_clone()` of diff_state is also a - // `diff_state` and that `apply_to()` returns the mutated clone. Since these - // are implementation details under our control, this assumption holds - const diff_state &ds_after_t = static_cast(*maybe_diff); - - for (auto new_state : ds_after_t.new_object_states) - this->visible_objects[new_state.first].push_state( - std::move(new_state.second).consume_into_current_state()); - - this->push_state_snapshot(); - return transition::status::exists; - } - return transition::status::disabled; -} - state_sequence state_sequence::consume_into_subsequence(size_t index) && { - auto elements = append_only( - std::make_move_iterator(this->states_in_sequence.begin()), - std::make_move_iterator(this->states_in_sequence.begin() + index + 1)); + auto elements = append_only( + this->states_in_sequence.begin(), + this->states_in_sequence.begin() + index + 1); auto ss = state_sequence(std::move(this->visible_objects)); ss.states_in_sequence = std::move(elements); return ss; } +const state &state_sequence::state_at(size_t i) const { + return *this->states_in_sequence.at(i); +} + +size_t state_sequence::state_count() const { + return this->states_in_sequence.size(); +} + +state_sequence::~state_sequence() { + // The elements have been dynamically allocated + for (const auto *element : this->states_in_sequence) delete element; +} + //////// state_sequence::element /////// -state_sequence::element::element(const state_sequence &owner) { +state_sequence::element::element(const state_sequence &owner) : owner(owner) { for (objid_t i = 0; i < owner.visible_objects.size(); i++) { this->visible_object_states[i] = owner.visible_objects.at(i).get_current_state(); } + this->max_visible_runner_id = owner.runner_to_obj_map.size(); +} + +state::objid_t state_sequence::element::get_objid_for_runner( + runner_id_t id) const { + return this->contains_runner_with_id(id) ? owner.runner_to_obj_map.at(id) + : model::invalid_obj_id; } bool state_sequence::element::contains_object_with_id(state::objid_t id) const { return id < this->visible_object_states.size(); } +bool state_sequence::element::contains_runner_with_id(state::objid_t id) const { + return id < max_visible_runner_id; +} + const visible_object_state *state_sequence::element::get_state_of_object( state::objid_t id) const { return this->visible_object_states.at(id); } +const visible_object_state *state_sequence::element::get_state_of_runner( + runner_id_t id) const { + return this->get_state_of_object(this->get_objid_for_runner(id)); +} + std::unique_ptr state_sequence::element::consume_obj(objid_t id) && { throw std::runtime_error( @@ -174,11 +212,73 @@ std::unique_ptr state_sequence::element::mutable_clone() const { //////// diff_state /////// +class state_sequence::diff_state : public mutable_state { + private: + const state_sequence &base_state; + + public: + std::unordered_map new_object_states; + std::unordered_map new_runners; + + diff_state(const state_sequence &s) : base_state(s) {} + diff_state(const diff_state &ds) + : base_state(ds.base_state), new_object_states(ds.new_object_states) {} + diff_state(detached_state &&) = delete; + diff_state &operator=(const diff_state &) = delete; + detached_state &operator=(detached_state &&) = delete; + + /* `state` overrrides */ + virtual size_t count() const override { + size_t count = this->new_object_states.size() + base_state.count(); + for (const auto p : new_object_states) { + // Each item in `new_object_states` that is also in `base_state` defines + // states for _previously existing_ objects. These objects are accounted + // for in `count` (double-counted), hence the `--` + if (base_state.contains_object_with_id(p.first)) count--; + } + return count; + } + size_t runner_count() const override { + return base_state.runner_count() + new_runners.size(); + } + objid_t get_objid_for_runner(runner_id_t id) const override; + bool contains_object_with_id(state::objid_t id) const override; + bool contains_runner_with_id(runner_id_t id) const override; + const visible_object_state *get_state_of_object(objid_t id) const override; + const visible_object_state *get_state_of_runner( + runner_id_t id) const override; + objid_t add_object( + std::unique_ptr initial_state) override; + runner_id_t add_runner( + std::unique_ptr initial_state) override; + void add_state_for_obj( + objid_t id, std::unique_ptr new_state) override; + void add_state_for_runner( + runner_id_t id, std::unique_ptr new_state) override; + std::unique_ptr consume_obj(objid_t id) && + override; + std::unique_ptr mutable_clone() const override; +}; + +state::objid_t state_sequence::diff_state::get_objid_for_runner( + runner_id_t id) const { + if (this->new_runners.count(id) > 0) { + return this->new_runners.at(id); + } else { + return this->base_state.get_objid_for_runner(id); + } +} + bool state_sequence::diff_state::contains_object_with_id(objid_t id) const { return this->new_object_states.count(id) > 0 || this->base_state.contains_object_with_id(id); } +bool state_sequence::diff_state::contains_runner_with_id(objid_t id) const { + return this->new_runners.count(id) > 0 || + this->base_state.contains_runner_with_id(id); +} + const visible_object_state *state_sequence::diff_state::get_state_of_object( objid_t id) const { if (this->new_object_states.count(id) > 0) { @@ -188,9 +288,18 @@ const visible_object_state *state_sequence::diff_state::get_state_of_object( } } +const visible_object_state *state_sequence::diff_state::get_state_of_runner( + runner_id_t id) const { + if (this->new_runners.count(id) > 0) { + return this->get_state_of_object(this->new_runners.at(id)); + } else { + return this->base_state.get_state_of_runner(id); + } +} + state::objid_t state_sequence::diff_state::add_object( std::unique_ptr initial_state) { - // The next id that would be assigned if one more than + // The next id that would be assigned is one more than // the largest id available. The last id of the base it `size() - 1` and // we are `new_object_state.size()` elements in state::objid_t next_id = @@ -199,10 +308,27 @@ state::objid_t state_sequence::diff_state::add_object( return next_id; } -void state_sequence::diff_state::add_state_for( +state::runner_id_t state_sequence::diff_state::add_runner( + std::unique_ptr initial_state) { + objid_t objid = this->add_object(std::move(initial_state)); + + // The next runner id would be the current size. + state::objid_t next_runner_id = runner_count(); + this->new_runners.insert({next_runner_id, objid}); + return next_runner_id; +} + +void state_sequence::diff_state::add_state_for_obj( objid_t id, std::unique_ptr new_state) { - auto &s = new_object_states[id]; - s.push_state(std::move(new_state)); + // Here we seek to insert all new states into the local cache instead of + // forwarding them onto the underlying base state. + visible_object &vobj = new_object_states[id]; + vobj.push_state(std::move(new_state)); +} + +void state_sequence::diff_state::add_state_for_runner( + runner_id_t id, std::unique_ptr new_state) { + this->add_state_for_obj(this->get_objid_for_runner(id), std::move(new_state)); } std::unique_ptr @@ -213,4 +339,34 @@ state_sequence::diff_state::consume_obj(objid_t id) && { std::unique_ptr state_sequence::diff_state::mutable_clone() const { return extensions::make_unique(this->base_state); +} + +transition::status state_sequence::follow(const transition &t) { + // Supply the transition a `diff_state` intentionally. We have control over + // the `apply_to` function: it simply returns a clone of the state it was + // provided. A `diff_state` copies only the reference to its underlying + // "backing" state, and only new objects will be + diff_state ds(*this); + auto maybe_diff = t.apply_to(ds); + + if (maybe_diff != nullptr) { + // Apply the diff to the sequence itself and create a new element which + // refers to the latest states of all the objects. The element is a + // placeholder for the state of the sequence as it looks after the + // application of transition `t`. Later if the sequenced is queried for + // the state of the object. + // + // Assumes that the `mutable_clone()` of diff_state is also a + // `diff_state` and that `apply_to()` returns the mutated clone. Since these + // are implementation details under our control, this assumption holds + const diff_state &ds_after_t = static_cast(*maybe_diff); + + for (auto new_state : ds_after_t.new_object_states) + this->visible_objects[new_state.first].push_state( + std::move(new_state.second).consume_into_current_state()); + + this->push_state_snapshot(); + return transition::status::exists; + } + return transition::status::disabled; } \ No newline at end of file From 4c82d9c1836a17c2e6ba02607143777d96497e60 Mon Sep 17 00:00:00 2001 From: Maxwell Pirtle Date: Sun, 21 Apr 2024 20:49:25 -0400 Subject: [PATCH 013/161] Reduce the common code from state_sequence and detached_state --- .../mcmini/model/state/detached_state.hpp | 2 +- .../mcmini/model/state/state_sequence.hpp | 19 +-------- .../src/mcmini/model/detached_state.cpp | 1 - .../src/mcmini/model/state_sequence.cpp | 41 ------------------- 4 files changed, 3 insertions(+), 60 deletions(-) diff --git a/docs/design/include/mcmini/model/state/detached_state.hpp b/docs/design/include/mcmini/model/state/detached_state.hpp index eda89e60..eca7d83c 100644 --- a/docs/design/include/mcmini/model/state/detached_state.hpp +++ b/docs/design/include/mcmini/model/state/detached_state.hpp @@ -14,7 +14,7 @@ namespace model { * context of a sequence; that is, a detached state represents the states */ class detached_state : public model::mutable_state { - private: + protected: append_only visible_objects; // INVARIANT: Runner ids are assigned sequentially. A runner with id `id` is diff --git a/docs/design/include/mcmini/model/state/state_sequence.hpp b/docs/design/include/mcmini/model/state/state_sequence.hpp index 1077c7da..66fa09b1 100644 --- a/docs/design/include/mcmini/model/state/state_sequence.hpp +++ b/docs/design/include/mcmini/model/state/state_sequence.hpp @@ -5,7 +5,7 @@ #include "mcmini/forwards.hpp" #include "mcmini/misc/append-only.hpp" -#include "mcmini/model/state.hpp" +#include "mcmini/model/state/detached_state.hpp" #include "mcmini/model/transition.hpp" namespace model { @@ -21,7 +21,7 @@ namespace model { * exists a sequence of transitions `t_i, ..., t_j` such that `s_j = * t_j(t_{j-1}(...(t_i(s_i))...))` */ -class state_sequence : public mutable_state { +class state_sequence : public detached_state { private: class diff_state; class element; @@ -53,25 +53,10 @@ class state_sequence : public mutable_state { state_sequence &operator=(const state_sequence &&) = delete; state_sequence &operator=(const state_sequence &) = delete; - /* `state` overrrides */ - size_t count() const override { return visible_objects.size(); } - size_t runner_count() const override { return runner_to_obj_map.size(); } - objid_t get_objid_for_runner(runner_id_t id) const override; - bool contains_object_with_id(state::objid_t id) const override; - bool contains_runner_with_id(runner_id_t id) const override; - const visible_object_state *get_state_of_object(objid_t id) const override; - const visible_object_state *get_state_of_runner( - runner_id_t id) const override; objid_t add_object( std::unique_ptr initial_state) override; - runner_id_t add_runner( - std::unique_ptr initial_state) override; void add_state_for_obj( objid_t id, std::unique_ptr new_state) override; - void add_state_for_runner( - runner_id_t id, std::unique_ptr new_state) override; - std::unique_ptr consume_obj(objid_t id) && - override; std::unique_ptr mutable_clone() const override; /* Applying transitions */ diff --git a/docs/design/src/mcmini/model/detached_state.cpp b/docs/design/src/mcmini/model/detached_state.cpp index 5777c529..8b053511 100644 --- a/docs/design/src/mcmini/model/detached_state.cpp +++ b/docs/design/src/mcmini/model/detached_state.cpp @@ -5,7 +5,6 @@ using namespace model; -/* `state` overrrides */ bool detached_state::contains_object_with_id(state::objid_t id) const { return id < this->visible_objects.size(); } diff --git a/docs/design/src/mcmini/model/state_sequence.cpp b/docs/design/src/mcmini/model/state_sequence.cpp index 79167041..f8c7f371 100644 --- a/docs/design/src/mcmini/model/state_sequence.cpp +++ b/docs/design/src/mcmini/model/state_sequence.cpp @@ -77,29 +77,6 @@ void state_sequence::push_state_snapshot() { this->states_in_sequence.push_back(new element(*this)); } -bool state_sequence::contains_object_with_id(state::objid_t id) const { - return id < visible_objects.size(); -} - -bool state_sequence::contains_runner_with_id(state::runner_id_t id) const { - return id < runner_to_obj_map.size(); -} - -state::objid_t state_sequence::get_objid_for_runner(runner_id_t id) const { - return this->contains_runner_with_id(id) ? runner_to_obj_map.at(id) - : model::invalid_obj_id; -} - -const visible_object_state *state_sequence::get_state_of_object( - objid_t id) const { - return this->visible_objects.at(id).get_current_state(); -} - -const visible_object_state *state_sequence::get_state_of_runner( - runner_id_t id) const { - return this->get_state_of_object(this->get_objid_for_runner(id)); -} - state::objid_t state_sequence::add_object( std::unique_ptr initial_state) { // INVARIANT: The current element needs to update at index `id` to reflect @@ -110,13 +87,6 @@ state::objid_t state_sequence::add_object( return id; } -state::runner_id_t state_sequence::add_runner( - std::unique_ptr new_state) { - objid_t id = this->add_object(std::move(new_state)); - this->runner_to_obj_map.push_back(id); - return this->runner_to_obj_map.size() - 1; -} - void state_sequence::add_state_for_obj( objid_t id, std::unique_ptr new_state) { // INVARIANT: The current element needs to update at index `id` to reflect @@ -125,17 +95,6 @@ void state_sequence::add_state_for_obj( this->visible_objects.at(id).push_state(std::move(new_state)); } -void state_sequence::add_state_for_runner( - runner_id_t id, std::unique_ptr new_state) { - return this->add_state_for_obj(this->get_objid_for_runner(id), - std::move(new_state)); -} - -std::unique_ptr state_sequence::consume_obj( - objid_t id) && { - return std::move(visible_objects.at(id)).consume_into_current_state(); -} - std::unique_ptr state_sequence::mutable_clone() const { return state::from_visible_objects( this->visible_objects.cbegin(), this->visible_objects.cend()); From 7b53dd8ead5737794b576048b9eb2ae8d7ecac90 Mon Sep 17 00:00:00 2001 From: Maxwell Pirtle Date: Sun, 21 Apr 2024 21:00:22 -0400 Subject: [PATCH 014/161] Add thread transitions modeling --- docs/design/include/mcmini/model/state.hpp | 20 +++++++++++++ .../model/transitions/thread/thread_exit.hpp | 28 +++++++++++++++++ .../model/transitions/thread/thread_join.hpp | 30 +++++++++++++++++++ .../model/transitions/thread/thread_start.hpp | 12 ++++---- 4 files changed, 84 insertions(+), 6 deletions(-) create mode 100644 docs/design/include/mcmini/model/transitions/thread/thread_exit.hpp create mode 100644 docs/design/include/mcmini/model/transitions/thread/thread_join.hpp diff --git a/docs/design/include/mcmini/model/state.hpp b/docs/design/include/mcmini/model/state.hpp index c91631ef..9c128d7b 100644 --- a/docs/design/include/mcmini/model/state.hpp +++ b/docs/design/include/mcmini/model/state.hpp @@ -68,6 +68,16 @@ class state { return static_cast( this->get_state_of_object(id)); } + + template + const concrete_visible_object_state *get_state_of_runner( + runner_id_t id) const { + static_assert(std::is_base_of::value, + "Concrete type must be a subtype of `visible_object_state`"); + return static_cast( + this->get_state_of_runner(id)); + } }; class mutable_state : public state { @@ -123,6 +133,16 @@ class mutable_state : public state { return (static_cast(this)) ->get_state_of_object(id); } + + template + const concrete_visible_object_state *get_state_of_runner( + runner_id_t id) const { + static_assert(std::is_base_of::value, + "Concrete type must be a subtype of `visible_object_state`"); + return (static_cast(this)) + ->get_state_of_runner(id); + } }; constexpr static auto invalid_obj_id = diff --git a/docs/design/include/mcmini/model/transitions/thread/thread_exit.hpp b/docs/design/include/mcmini/model/transitions/thread/thread_exit.hpp new file mode 100644 index 00000000..229a85f8 --- /dev/null +++ b/docs/design/include/mcmini/model/transitions/thread/thread_exit.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include "mcmini/model/objects/thread.hpp" +#include "mcmini/model/transition.hpp" + +namespace model { +namespace transitions { + +struct thread_exit : public model::transition { + public: + thread_exit(state::runner_id_t executor) : transition(executor) {} + ~thread_exit() = default; + + status modify(model::mutable_state& s) const override { + using namespace model::objects; + auto* thread_state = s.get_state_of_runner(executor); + if (!thread_state->is_running()) { + return status::disabled; + } + s.add_state_for_runner(executor, thread::make(thread::exited)); + return status::exists; + } + + std::string to_string() const override { return "exits"; } +}; + +} // namespace transitions +} // namespace model \ No newline at end of file diff --git a/docs/design/include/mcmini/model/transitions/thread/thread_join.hpp b/docs/design/include/mcmini/model/transitions/thread/thread_join.hpp new file mode 100644 index 00000000..0f16dc9e --- /dev/null +++ b/docs/design/include/mcmini/model/transitions/thread/thread_join.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include "mcmini/model/objects/thread.hpp" +#include "mcmini/model/transition.hpp" + +namespace model { +namespace transitions { + +struct thread_join : public model::transition { + private: + state::runner_id_t target; + + public: + thread_join(state::runner_id_t executor, state::runner_id_t target) + : target(target), transition(executor) {} + ~thread_join() = default; + + status modify(model::mutable_state& s) const override { + using namespace model::objects; + auto* target_state = s.get_state_of_runner(target); + return target_state->has_exited() ? status::exists : status::disabled; + } + + std::string to_string() const override { + return "pthread_join(thread: " + std::to_string(target) + ")"; + } +}; + +} // namespace transitions +} // namespace model \ No newline at end of file diff --git a/docs/design/include/mcmini/model/transitions/thread/thread_start.hpp b/docs/design/include/mcmini/model/transitions/thread/thread_start.hpp index 0d40a4e2..105cdcd5 100644 --- a/docs/design/include/mcmini/model/transitions/thread/thread_start.hpp +++ b/docs/design/include/mcmini/model/transitions/thread/thread_start.hpp @@ -12,12 +12,12 @@ struct thread_start : public model::transition { ~thread_start() = default; status modify(model::mutable_state& s) const override { - // using namespace model::objects; - // auto* thread_state = s.get_state_of_object(thread_id); - // if (!thread_state->is_embryo()) { - // return status::disabled; - // } - // s.add_state_for(thread_id, thread::make(thread::running)); + using namespace model::objects; + auto* thread_state = s.get_state_of_runner(executor); + if (!thread_state->is_embryo()) { + return status::disabled; + } + s.add_state_for_runner(executor, thread::make(thread::running)); return status::exists; } From 9a66aca43bbc0d3f1455993d5ca4351ccf5c65f8 Mon Sep 17 00:00:00 2001 From: Maxwell Pirtle Date: Mon, 22 Apr 2024 11:49:28 -0400 Subject: [PATCH 015/161] Add dlsym loading of pthread symbols in libmcmini.so --- docs/design/CMakeLists.txt | 1 + docs/design/include/mcmini/entry.h | 6 +- docs/design/include/mcmini/mcmini.h | 9 ++ .../design/include/mcmini/shared_transition.h | 82 ++++--------------- .../mcmini/spy/intercept/interception.h | 10 +-- .../include/mcmini/spy/intercept/wrappers.h | 70 ++-------------- docs/design/src/libmcmini/entry.c | 53 ++---------- docs/design/src/libmcmini/interception.c | 46 +++++++++++ docs/design/src/libmcmini/wrappers.c | 39 ++++++++- docs/design/src/mcmini/mcmini.cpp | 4 +- docs/design/src/mcmini/shared_transition.c | 34 -------- 11 files changed, 133 insertions(+), 221 deletions(-) create mode 100644 docs/design/include/mcmini/mcmini.h create mode 100644 docs/design/src/libmcmini/interception.c delete mode 100644 docs/design/src/mcmini/shared_transition.c diff --git a/docs/design/CMakeLists.txt b/docs/design/CMakeLists.txt index 79021cd0..cdb69b2a 100644 --- a/docs/design/CMakeLists.txt +++ b/docs/design/CMakeLists.txt @@ -38,6 +38,7 @@ set(LIBMCMINI_C_SRC src/common/runner_mailbox.c src/common/shm_config.c src/libmcmini/entry.c + src/libmcmini/interception.c src/libmcmini/wrappers.c ) diff --git a/docs/design/include/mcmini/entry.h b/docs/design/include/mcmini/entry.h index 310b5481..04c6bf9a 100644 --- a/docs/design/include/mcmini/entry.h +++ b/docs/design/include/mcmini/entry.h @@ -2,6 +2,10 @@ #include "mcmini/defines.h" -void mc_exit(int status); +extern volatile void *shm_start; +extern MCMINI_THREAD_LOCAL tid_t tid_self; + +void mc_exit(int); + diff --git a/docs/design/include/mcmini/mcmini.h b/docs/design/include/mcmini/mcmini.h new file mode 100644 index 00000000..86955912 --- /dev/null +++ b/docs/design/include/mcmini/mcmini.h @@ -0,0 +1,9 @@ +#pragma once + +#include "mcmini/entry.h" +#include "mcmini/plugins.h" +#include "mcmini/common/shm_config.h" +#include "mcmini/real_world/mailbox/runner_mailbox.h" +#include "mcmini/spy/intercept/interception.h" +#include "mcmini/spy/intercept/wrappers.h" + diff --git a/docs/design/include/mcmini/shared_transition.h b/docs/design/include/mcmini/shared_transition.h index 0146567f..0b69cc03 100644 --- a/docs/design/include/mcmini/shared_transition.h +++ b/docs/design/include/mcmini/shared_transition.h @@ -4,75 +4,21 @@ typedef uint64_t tid_t; -// Define an enumeration for type info -// Enum for TransitionTypeInfo typedef enum { - MutexInit, - MutexLock, - MutexUnlock, - SemaphoreInit, - SemaphoreWait, - SemaphorePost, - ThreadCreate, - ThreadJoin, -} TransitionTypeInfo; - -struct TypeInfo { - TransitionTypeInfo type; -}; - -struct SharedTransition { + mutex_init, + mutex_lock, + mutex_unlock, + sem_init, + sem_wait, + sem_post, + thread_create, + thread_join + //... +} transition_type; + +struct shared_transition { tid_t executor; - struct TypeInfo type; + transition_type type; }; -// Function to create a SharedTransition -struct SharedTransition createSharedTransition(tid_t executor, struct TypeInfo type) { - struct SharedTransition transition; - transition.executor = executor; - transition.type = type; - return transition; -} - -void thread_post_visible_operation_hit(const std::type_info &type); - -void thread_await_scheduler(); -void thread_await_scheduler_for_thread_start_transition(); -void thread_awake_scheduler_for_thread_finish_transition(); -void mc_exit_main_thread(); - -#endif - - -/* -#ifndef INCLUDE_SHARED_TRANSITION_H -#define INCLUDE_SHARED_TRANSITION_H - -#include - -typedef uint64_t tid_t; - -// Define an enumeration for type info -enum TypeInfo { - TypeInfo_Int, - TypeInfo_Char, - // Add other type information as needed -}; - -// Define a structure equivalent to SharedTransition -struct SharedTransition { - tid_t executor; - enum TypeInfo type; // Use enum for type information -}; - -// Function to create a SharedTransition -struct SharedTransition createSharedTransition(tid_t executor, enum TypeInfo type) { - struct SharedTransition transition; - transition.executor = executor; - transition.type = type; - return transition; -} - -#endif - -*/ \ No newline at end of file +#endif \ No newline at end of file diff --git a/docs/design/include/mcmini/spy/intercept/interception.h b/docs/design/include/mcmini/spy/intercept/interception.h index d5a9f6bd..c508ca83 100644 --- a/docs/design/include/mcmini/spy/intercept/interception.h +++ b/docs/design/include/mcmini/spy/intercept/interception.h @@ -1,14 +1,14 @@ #pragma once #define _GNU_SOURCE +#include +#include #include +#include -int mc_pthread_mutex_init(pthread_mutex_t *mutex, - const pthread_mutexattr_t *mutexattr); +void mc_load_intercepted_pthread_functions(); int pthread_mutex_init(pthread_mutex_t *mutex, - const pthread_mutexattr_t *mutexattr) { - return mc_pthread_mutex_init(mutex, mutexattr); -} + const pthread_mutexattr_t *mutexattr); // TODO: Other wrappers here \ No newline at end of file diff --git a/docs/design/include/mcmini/spy/intercept/wrappers.h b/docs/design/include/mcmini/spy/intercept/wrappers.h index 330da3f0..7454977f 100644 --- a/docs/design/include/mcmini/spy/intercept/wrappers.h +++ b/docs/design/include/mcmini/spy/intercept/wrappers.h @@ -1,67 +1,15 @@ -// wrappers.h - -#ifndef WRAPPERS_H -#define WRAPPERS_H +#pragma once #include -#include "mcmini/shared_transition.h" - - -// struct MCTransition { -// // Include necessary information about the transition -// // (This is a placeholder; you may extend it as needed) -// std::shared_ptr data; - -// MCTransition(/* Add relevant parameters */) { -// // Initialize the transition data based on the parameters -// // (This is a placeholder; you may extend it as needed) -// } - -// // Implement additional functions if needed -// }; - -MC_EXTERN int mc_pthread_mutex_init(pthread_mutex_t *, - const pthread_mutexattr_t *); - - -struct MCMutexShadow { - pthread_mutex_t *systemIdentity; - enum State { undefined, unlocked, locked, destroyed } state; - explicit MCMutexShadow(pthread_mutex_t *systemIdentity) - : systemIdentity(systemIdentity), state(undefined) - {} -}; - -struct MCMutex : public MCVisibleObject { -private: - - MCMutexShadow mutexShadow; - -public: - - inline explicit MCMutex(MCMutexShadow shadow) - : MCVisibleObject(), mutexShadow(shadow) - {} - inline MCMutex(const MCMutex &mutex) - : MCVisibleObject(mutex.getObjectId()), - mutexShadow(mutex.mutexShadow) - {} - - std::shared_ptr copy() override; - MCSystemID getSystemId() override; - bool operator==(const MCMutex &) const; - bool operator!=(const MCMutex &) const; +#include "mcmini/entry.h" +#include "mcmini/real_world/mailbox/runner_mailbox.h" - bool canAcquire(tid_t) const; - bool isLocked() const; - bool isUnlocked() const; - bool isDestroyed() const; +void thread_await_scheduler(); +void thread_await_scheduler_for_thread_start_transition(); +void thread_awake_scheduler_for_thread_finish_transition(); +volatile runner_mailbox *thread_get_mailbox(); - void lock(tid_t); - void unlock(); - void init(); - void deinit(); -}; +int mc_pthread_mutex_init(pthread_mutex_t *mutex, + const pthread_mutexattr_t *mutexattr); -#endif // WRAPPERS_H diff --git a/docs/design/src/libmcmini/entry.c b/docs/design/src/libmcmini/entry.c index 39abff07..8c21fd70 100644 --- a/docs/design/src/libmcmini/entry.c +++ b/docs/design/src/libmcmini/entry.c @@ -10,15 +10,13 @@ #include #include -#include "mcmini/real_world/mailbox/runner_mailbox.h" -#include "mcmini/entry.h" -#include "mcmini/common/shm_config.h" +#include "mcmini/mcmini.h" volatile void *shm_start = NULL; MCMINI_THREAD_LOCAL tid_t tid_self = TID_INVALID; tid_t -mc_created_new_thread() +mc_register_this_thread() { static tid_t tid_next = 0; tid_self = tid_next++; @@ -73,21 +71,6 @@ mc_deallocate_shared_memory_region() mc_exit(EXIT_FAILURE); } } - - // TODO: Unlinking likely shouldn't happen in child - // processes where `libmcmini.so` is loaded; instead, - // that should be left to the McMini process to handle. - // rc = shm_unlink(shm_file_name); - // if (rc == -1) { - // if (errno == EACCES) { - // fprintf(stderr, - // "Shared memory region '%s' not owned by this process\n", - // shm_file_name); - // } else { - // perror("shm_unlink"); - // } - // mc_exit(EXIT_FAILURE); - // } } void @@ -101,36 +84,10 @@ mc_exit(int status) _Exit(status); } -void -thread_await_scheduler() -{ - assert(tid_self != TID_INVALID); - volatile runner_mailbox *thread_mailbox = ((volatile runner_mailbox*)(shm_start)) + tid_self; - mc_wake_scheduler(thread_mailbox); - mc_wait_for_scheduler(thread_mailbox); -} - -void -thread_await_scheduler_for_thread_start_transition() -{ - assert(tid_self != TID_INVALID); - volatile runner_mailbox *thread_mailbox = ((volatile runner_mailbox*)(shm_start)) + tid_self; - mc_wait_for_scheduler(thread_mailbox); -} - -void -thread_awake_scheduler_for_thread_finish_transition() -{ - assert(tid_self != TID_INVALID); - volatile runner_mailbox *thread_mailbox = ((volatile runner_mailbox*)(shm_start)) + tid_self; - mc_wake_scheduler(thread_mailbox); -} - -__attribute__((constructor)) void my_ctor() { - mc_created_new_thread(); +__attribute__((constructor)) void libmcmini_main() { + mc_register_this_thread(); + mc_load_intercepted_pthread_functions(); atexit(&mc_deallocate_shared_memory_region); shm_start = mc_allocate_shared_memory_region(); - printf("YO!\n"); thread_await_scheduler_for_thread_start_transition(); - printf("YO2!\n"); } \ No newline at end of file diff --git a/docs/design/src/libmcmini/interception.c b/docs/design/src/libmcmini/interception.c new file mode 100644 index 00000000..d5afbcd3 --- /dev/null +++ b/docs/design/src/libmcmini/interception.c @@ -0,0 +1,46 @@ +#define _GNU_SOURCE +#include "mcmini/spy/intercept/interception.h" + +#include + +#include "mcmini/spy/intercept/wrappers.h" + +typeof(&pthread_create) pthread_create_ptr; +typeof(&pthread_join) pthread_join_ptr; +typeof(&pthread_mutex_init) pthread_mutex_init_ptr; +typeof(&pthread_mutex_lock) pthread_mutex_lock_ptr; +typeof(&pthread_mutex_unlock) pthread_mutex_unlock_ptr; +typeof(&sem_wait) sem_wait_ptr; +typeof(&sem_post) sem_post_ptr; +typeof(&sem_init) sem_init_ptr; +typeof(&sem_destroy) sem_destroy_ptr; +typeof(&pthread_cond_init) pthread_cond_init_ptr; +typeof(&pthread_cond_wait) pthread_cond_wait_ptr; +typeof(&pthread_cond_signal) pthread_cond_signal_ptr; +typeof(&pthread_cond_broadcast) pthread_cond_broadcast_ptr; +typeof(&sleep) sleep_ptr; +__attribute__((__noreturn__)) typeof(&exit) exit_ptr; +__attribute__((__noreturn__)) typeof(&abort) abort_ptr; + +void mc_load_intercepted_pthread_functions() { + pthread_create_ptr = dlsym(RTLD_NEXT, "pthread_create"); + pthread_join_ptr = dlsym(RTLD_NEXT, "pthread_join"); + pthread_mutex_init_ptr = dlsym(RTLD_NEXT, "pthread_mutex_init"); + pthread_mutex_lock_ptr = dlsym(RTLD_NEXT, "pthread_mutex_lock"); + pthread_mutex_unlock_ptr = dlsym(RTLD_NEXT, "pthread_mutex_unlock"); + sem_wait_ptr = dlsym(RTLD_NEXT, "sem_wait"); + sem_post_ptr = dlsym(RTLD_NEXT, "sem_post"); + sem_init_ptr = dlsym(RTLD_NEXT, "sem_init"); + pthread_cond_init_ptr = dlsym(RTLD_NEXT, "pthread_cond_init"); + pthread_cond_wait_ptr = dlsym(RTLD_NEXT, "pthread_cond_wait"); + pthread_cond_signal_ptr = dlsym(RTLD_NEXT, "pthread_cond_signal"); + pthread_cond_broadcast_ptr = dlsym(RTLD_NEXT, "pthread_cond_broadcast"); + sleep_ptr = dlsym(RTLD_NEXT, "sleep"); + exit_ptr = dlsym(RTLD_NEXT, "exit"); + abort_ptr = dlsym(RTLD_NEXT, "abort"); +} + +int pthread_mutex_init(pthread_mutex_t *mutex, + const pthread_mutexattr_t *mutexattr) { + return mc_pthread_mutex_init(mutex, mutexattr); +} \ No newline at end of file diff --git a/docs/design/src/libmcmini/wrappers.c b/docs/design/src/libmcmini/wrappers.c index b9b77dbe..5ae8bd48 100644 --- a/docs/design/src/libmcmini/wrappers.c +++ b/docs/design/src/libmcmini/wrappers.c @@ -1,12 +1,45 @@ #include -#include "mcmini/spy/intercept/interception.h" +#include +#include +#include "mcmini/mcmini.h" + +volatile runner_mailbox *thread_get_mailbox() { + return ((volatile runner_mailbox*)(shm_start)) + tid_self; +} + +void +thread_await_scheduler() +{ + assert(tid_self != TID_INVALID); + volatile runner_mailbox *thread_mailbox = thread_get_mailbox(); + mc_wake_scheduler(thread_mailbox); + mc_wait_for_scheduler(thread_mailbox); +} + +void +thread_await_scheduler_for_thread_start_transition() +{ + assert(tid_self != TID_INVALID); + volatile runner_mailbox *thread_mailbox = thread_get_mailbox(); + mc_wait_for_scheduler(thread_mailbox); +} + +void +thread_awake_scheduler_for_thread_finish_transition() +{ + assert(tid_self != TID_INVALID); + volatile runner_mailbox *thread_mailbox = thread_get_mailbox(); + mc_wake_scheduler(thread_mailbox); +} int mc_pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr) { - printf("Hello from mc_pthread_mutext_init!"); // TODO: write into the shm region enough information // to determine what just happened on the model side + volatile runner_mailbox *mb = thread_get_mailbox(); + memcpy((void*)mb->cnts, &mutex, sizeof(pthread_mutex_t)); + // The coordinator first assumes data is written as follows: // transition id followed by payload. // @@ -15,5 +48,7 @@ int mc_pthread_mutex_init(pthread_mutex_t *mutex, // (new transition can be added at runtime) // For now, it suffices to assign a fixed value and just assume it // corresponds on the model side + + thread_await_scheduler(); return 0; } \ No newline at end of file diff --git a/docs/design/src/mcmini/mcmini.cpp b/docs/design/src/mcmini/mcmini.cpp index d72deaad..484d0d9b 100644 --- a/docs/design/src/mcmini/mcmini.cpp +++ b/docs/design/src/mcmini/mcmini.cpp @@ -43,8 +43,8 @@ void do_model_checking( pending_transitions initial_first_steps; transition_registry tr; - state::objid_t main_thread_id = - state_of_program_at_main.add_object(model::objects::thread::make()); + state::runner_id_t main_thread_id = + state_of_program_at_main.add_runner(model::objects::thread::make()); initial_first_steps.displace_transition_for( 0, make_unique(main_thread_id)); diff --git a/docs/design/src/mcmini/shared_transition.c b/docs/design/src/mcmini/shared_transition.c deleted file mode 100644 index 2198d0c3..00000000 --- a/docs/design/src/mcmini/shared_transition.c +++ /dev/null @@ -1,34 +0,0 @@ -#include "shared_transition.h" -#include "shared_sem.h" - -typedef uint64_t tid_t; -#define MC_THREAD_LOCAL thread_local -extern MC_THREAD_LOCAL tid_t tid_self; -#define TID_INVALID (-1ul) // ULONG_MAX -MC_THREAD_LOCAL tid_t tid_self = TID_INVALID; - -void -thread_await_scheduler() -{ - MC_ASSERT(tid_self != TID_INVALID); - shared_sem_ref cv = &(*trace_sleep_list)[tid_self]; - mc_shared_sem_wake_scheduler(cv); - mc_shared_sem_wait_for_scheduler(cv); -} - - -void -thread_await_scheduler_for_thread_start_transition() -{ - MC_ASSERT(tid_self != TID_INVALID); - mc_shared_sem_ref cv = &(*trace_sleep_list)[tid_self]; - mc_shared_sem_wait_for_scheduler(cv); -} - -void -thread_awake_scheduler_for_thread_finish_transition() -{ - MC_ASSERT(tid_self != TID_INVALID); - mc_shared_sem_ref cv = &(*trace_sleep_list)[tid_self]; - mc_shared_sem_wake_scheduler(cv); -} From 1b94b6c6bfa452b6159bdd612e484712549b684d Mon Sep 17 00:00:00 2001 From: Maxwell Pirtle Date: Wed, 24 Apr 2024 10:14:27 -0400 Subject: [PATCH 016/161] Add memcpy_v for volatile memory copy This commit adds a memcpy function for volatile memory. This ensures that the compile properly analyzes the volatile memory copy that the standard `memcpy` does not enforce. --- docs/design/CMakeLists.txt | 1 + docs/design/include/mcmini/{ => lib}/entry.h | 0 .../mcmini/{ => lib}/shared_transition.h | 17 +++++++------- docs/design/include/mcmini/mcmini.h | 4 +++- docs/design/include/mcmini/mem.h | 6 +++++ .../include/mcmini/spy/intercept/wrappers.h | 2 +- docs/design/src/common/mem.c | 23 +++++++++++++++++++ docs/design/src/examples/hello-world.c | 8 +++---- docs/design/src/libmcmini/wrappers.c | 5 ++-- 9 files changed, 48 insertions(+), 18 deletions(-) rename docs/design/include/mcmini/{ => lib}/entry.h (100%) rename docs/design/include/mcmini/{ => lib}/shared_transition.h (63%) create mode 100644 docs/design/include/mcmini/mem.h create mode 100644 docs/design/src/common/mem.c diff --git a/docs/design/CMakeLists.txt b/docs/design/CMakeLists.txt index cdb69b2a..d4612932 100644 --- a/docs/design/CMakeLists.txt +++ b/docs/design/CMakeLists.txt @@ -35,6 +35,7 @@ set(MCMINI_CPP_SRC src/mcmini/real_world/shm.cpp ) set(LIBMCMINI_C_SRC + src/common/mem.c src/common/runner_mailbox.c src/common/shm_config.c src/libmcmini/entry.c diff --git a/docs/design/include/mcmini/entry.h b/docs/design/include/mcmini/lib/entry.h similarity index 100% rename from docs/design/include/mcmini/entry.h rename to docs/design/include/mcmini/lib/entry.h diff --git a/docs/design/include/mcmini/shared_transition.h b/docs/design/include/mcmini/lib/shared_transition.h similarity index 63% rename from docs/design/include/mcmini/shared_transition.h rename to docs/design/include/mcmini/lib/shared_transition.h index 0b69cc03..7bb8792f 100644 --- a/docs/design/include/mcmini/shared_transition.h +++ b/docs/design/include/mcmini/lib/shared_transition.h @@ -3,16 +3,15 @@ #include typedef uint64_t tid_t; - typedef enum { - mutex_init, - mutex_lock, - mutex_unlock, - sem_init, - sem_wait, - sem_post, - thread_create, - thread_join + MUTEX_INIT_TYPE, + MUTEX_LOCK_TYPE, + MUTEX_UNLOCK_TYPE + // sem_init, + // sem_wait, + // sem_post, + // thread_create, + // thread_join //... } transition_type; diff --git a/docs/design/include/mcmini/mcmini.h b/docs/design/include/mcmini/mcmini.h index 86955912..4cb50aff 100644 --- a/docs/design/include/mcmini/mcmini.h +++ b/docs/design/include/mcmini/mcmini.h @@ -1,7 +1,9 @@ #pragma once -#include "mcmini/entry.h" +#include "mcmini/mem.h" #include "mcmini/plugins.h" +#include "mcmini/lib/entry.h" +#include "mcmini/lib/shared_transition.h" #include "mcmini/common/shm_config.h" #include "mcmini/real_world/mailbox/runner_mailbox.h" #include "mcmini/spy/intercept/interception.h" diff --git a/docs/design/include/mcmini/mem.h b/docs/design/include/mcmini/mem.h new file mode 100644 index 00000000..7431e66f --- /dev/null +++ b/docs/design/include/mcmini/mem.h @@ -0,0 +1,6 @@ +#pragma once +#include + +// memcpy implementation but with volatile memory +volatile void *memcpy_v(volatile void *restrict dst, + const volatile void *restrict src, size_t n); \ No newline at end of file diff --git a/docs/design/include/mcmini/spy/intercept/wrappers.h b/docs/design/include/mcmini/spy/intercept/wrappers.h index 7454977f..e7f0d238 100644 --- a/docs/design/include/mcmini/spy/intercept/wrappers.h +++ b/docs/design/include/mcmini/spy/intercept/wrappers.h @@ -2,7 +2,7 @@ #include -#include "mcmini/entry.h" +#include "mcmini/lib/entry.h" #include "mcmini/real_world/mailbox/runner_mailbox.h" void thread_await_scheduler(); diff --git a/docs/design/src/common/mem.c b/docs/design/src/common/mem.c new file mode 100644 index 00000000..52f10e8a --- /dev/null +++ b/docs/design/src/common/mem.c @@ -0,0 +1,23 @@ +#include "mcmini/mem.h" + +volatile void *memcpy_v(volatile void *restrict dst, + const volatile void *restrict src, size_t n) { + // From cppreference on the use of restrict pointers in the C language: + // + // | Restricted pointers can be assigned to unrestricted pointers freely, the + // | optimization opportunities remain in place as long as the compiler is + // | able to analyze the code: + // | + // | void f(int n, float * restrict r, float * restrict s) + // | { + // | float *p = r, *q = s; // OK + // | while (n-- > 0) + // | *p++ = *q++; // almost certainly optimized just like *r++ = *s++ + // | } + // + // See https://en.cppreference.com/w/c/language/restrict for details. + const volatile unsigned char *srcc = src; + volatile unsigned char *dstc = dst; + while ((n--) > 0) dstc[n] = srcc[n]; + return dst; +} \ No newline at end of file diff --git a/docs/design/src/examples/hello-world.c b/docs/design/src/examples/hello-world.c index 4c4c98f7..3a8c80fe 100644 --- a/docs/design/src/examples/hello-world.c +++ b/docs/design/src/examples/hello-world.c @@ -7,9 +7,9 @@ int main(int argc, char* argv[]) { pthread_mutex_t mut; pthread_mutex_init(&mut, NULL); - pthread_mutex_lock(&mut); - printf("Hello world!\n"); - pthread_mutex_unlock(&mut); - pthread_mutex_destroy(&mut); + // pthread_mutex_lock(&mut); + // printf("Hello world!\n"); + // pthread_mutex_unlock(&mut); + // pthread_mutex_destroy(&mut); return 0; } diff --git a/docs/design/src/libmcmini/wrappers.c b/docs/design/src/libmcmini/wrappers.c index 5ae8bd48..a61a3a52 100644 --- a/docs/design/src/libmcmini/wrappers.c +++ b/docs/design/src/libmcmini/wrappers.c @@ -36,9 +36,9 @@ int mc_pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr) { // TODO: write into the shm region enough information // to determine what just happened on the model side - volatile runner_mailbox *mb = thread_get_mailbox(); - memcpy((void*)mb->cnts, &mutex, sizeof(pthread_mutex_t)); + uint32_t t = MUTEX_INIT_TYPE; + memcpy_v(mb->cnts, &t, sizeof(t)); // The coordinator first assumes data is written as follows: // transition id followed by payload. @@ -48,7 +48,6 @@ int mc_pthread_mutex_init(pthread_mutex_t *mutex, // (new transition can be added at runtime) // For now, it suffices to assign a fixed value and just assume it // corresponds on the model side - thread_await_scheduler(); return 0; } \ No newline at end of file From 6c90a88a95478592398da66ab70d9f518a82d226 Mon Sep 17 00:00:00 2001 From: Maxwell Pirtle Date: Wed, 24 Apr 2024 12:41:15 -0400 Subject: [PATCH 017/161] Record the presence of runners in state_sequence for elements This commit introduces changes to how the elements in the state_sequence are informed of runners. Previously, elements were not informed of changes to the number of runners and thus were not properly synchronizes with the changes forwarded to the `state_sequence`. This commit addresses that issue. Additionally, the use of the `volatile_mem_stream` has been removed. It turns out to be needlessly complicated and does not provide much over what a simply dereference from a raw pointer doesn't itself already achieve (and is probably much slower anyway). --- .../coordinator/model_to_system_map.hpp | 24 +++------ .../include/mcmini/misc/append-only.hpp | 1 + docs/design/include/mcmini/model/state.hpp | 2 +- .../mcmini/model/state/detached_state.hpp | 2 + .../mcmini/model/state/state_sequence.hpp | 20 ++++++-- .../mcmini/model/transition_registry.hpp | 4 +- .../include/mcmini/real_world/process.hpp | 7 ++- .../process/fork_process_source.hpp | 4 -- .../process/local_linux_process.hpp | 3 +- .../mcmini/real_world/remote_address.hpp | 1 + .../src/mcmini/coordinator/coordinator.cpp | 47 ++++++++---------- docs/design/src/mcmini/mcmini.cpp | 6 +-- .../src/mcmini/model/detached_state.cpp | 7 ++- .../src/mcmini/model/state_sequence.cpp | 49 ++++++++++++++++--- .../mcmini/real_world/fork_process_source.cpp | 4 +- .../mcmini/real_world/local_linux_process.cpp | 16 +----- 16 files changed, 106 insertions(+), 91 deletions(-) diff --git a/docs/design/include/mcmini/coordinator/model_to_system_map.hpp b/docs/design/include/mcmini/coordinator/model_to_system_map.hpp index 230e6756..a744446c 100644 --- a/docs/design/include/mcmini/coordinator/model_to_system_map.hpp +++ b/docs/design/include/mcmini/coordinator/model_to_system_map.hpp @@ -3,6 +3,7 @@ #include "mcmini/forwards.hpp" #include "mcmini/misc/optional.hpp" #include "mcmini/model/state.hpp" +#include "mcmini/real_world/remote_address.hpp" /** * @brief A mapping between the remote addresses pointing to the C/C++ structs @@ -38,14 +39,15 @@ struct model_to_system_map final { * * TODO: See the TODOs below */ - void *get_remote_process_handle_for_object(model::state::objid_t id) const; + real_world::remote_address get_remote_process_handle_for_object( + model::state::objid_t id) const; /** * @brief Retrieve the object that corresponds to the given remote address, or - * the empty optional if no such address exists + * `model::invalid_objid` if the address is not known to this mapping. */ - optional get_object_for_remote_process_handle( - void *) const; + model::state::objid_t get_object_for_remote_process_handle( + real_world::remote_address) const; /** * @brief Record the presence of a new visible object that is @@ -58,20 +60,8 @@ struct model_to_system_map final { * invocations_. In the future we could support the ability to _remap_ * process handles dynamically during each new re-execution scheduled by * the coordinator to handle aliasing etc. - * - * TODO: The handle could be _any_ value that is used in the - * multi-threaded program. For now, we restrict it to addresses for the - * mutex case etc. - * - * TODO: This should probably have a `result` as a return type. If the - * handle is already mapped, how we should deal with this situation is a - * bit unclear. */ - model::state::objid_t record_new_object_association( - void *remote_process_visible_object_handle, - std::unique_ptr initial_state); - model::state::objid_t observe_remote_process_handle( - void *remote_process_visible_object_handle, + real_world::remote_address remote_process_visible_object_handle, std::unique_ptr fallback_initial_state); }; diff --git a/docs/design/include/mcmini/misc/append-only.hpp b/docs/design/include/mcmini/misc/append-only.hpp index c5ffb68c..325f3066 100644 --- a/docs/design/include/mcmini/misc/append-only.hpp +++ b/docs/design/include/mcmini/misc/append-only.hpp @@ -16,6 +16,7 @@ struct append_only { using const_iterator = typename std::vector::const_iterator; append_only() = default; + append_only(size_type count) : contents(count) {} append_only(std::vector &&contents) : contents(std::move(contents)) {} append_only(append_only &&) = default; append_only(const append_only &) = default; diff --git a/docs/design/include/mcmini/model/state.hpp b/docs/design/include/mcmini/model/state.hpp index 9c128d7b..66ecb111 100644 --- a/docs/design/include/mcmini/model/state.hpp +++ b/docs/design/include/mcmini/model/state.hpp @@ -145,7 +145,7 @@ class mutable_state : public state { } }; -constexpr static auto invalid_obj_id = +constexpr static auto invalid_objid = std::numeric_limits::max(); } // namespace model \ No newline at end of file diff --git a/docs/design/include/mcmini/model/state/detached_state.hpp b/docs/design/include/mcmini/model/state/detached_state.hpp index eca7d83c..a7598995 100644 --- a/docs/design/include/mcmini/model/state/detached_state.hpp +++ b/docs/design/include/mcmini/model/state/detached_state.hpp @@ -23,6 +23,8 @@ class detached_state : public model::mutable_state { public: detached_state() = default; + detached_state(std::vector &&); + detached_state(append_only &&); detached_state(const detached_state &) = default; detached_state(detached_state &&) = default; detached_state &operator=(const detached_state &) = default; diff --git a/docs/design/include/mcmini/model/state/state_sequence.hpp b/docs/design/include/mcmini/model/state/state_sequence.hpp index 66fa09b1..bc064971 100644 --- a/docs/design/include/mcmini/model/state/state_sequence.hpp +++ b/docs/design/include/mcmini/model/state/state_sequence.hpp @@ -20,6 +20,10 @@ namespace model { * is, given any pair of states `s_i` and `s_j` in the sequence (i <= j), there * exists a sequence of transitions `t_i, ..., t_j` such that `s_j = * t_j(t_{j-1}(...(t_i(s_i))...))` + * + * A `state_sequence` is itself a `model::mutable_state` that is conceptually + * represented by the final state in the sequence. A state sequence is never + * empty, */ class state_sequence : public detached_state { private: @@ -34,12 +38,12 @@ class state_sequence : public detached_state { // appropriate object states replaced for the _last element_ of // `states_in_sequence`. When new objects are added to `visible_objects`, a // corresponding object state is added to the _last_ element in the sequence. - append_only visible_objects; append_only states_in_sequence; - // INVARIANT: Runner ids are assigned sequentially. A runner with id `id` is - // mapped to the object id at index `id - 1`. - append_only runner_to_obj_map; + /// @brief Retrieves the state which represents that sequence as a whole. + element &get_representative_state() const { + return *this->states_in_sequence.back(); + } public: state_sequence(); @@ -53,8 +57,13 @@ class state_sequence : public detached_state { state_sequence &operator=(const state_sequence &&) = delete; state_sequence &operator=(const state_sequence &) = delete; + size_t count() const override; + size_t runner_count() const override; + size_t get_num_states_in_sequence() const; objid_t add_object( std::unique_ptr initial_state) override; + runner_id_t add_runner( + std::unique_ptr initial_state) override; void add_state_for_obj( objid_t id, std::unique_ptr new_state) override; std::unique_ptr mutable_clone() const override; @@ -74,7 +83,8 @@ class state_sequence : public detached_state { */ transition::status follow(const transition &t); - size_t state_count() const; + const state &front() const; + const state &back() const; const state &state_at(size_t i) const; /** diff --git a/docs/design/include/mcmini/model/transition_registry.hpp b/docs/design/include/mcmini/model/transition_registry.hpp index a61109a9..2987934a 100644 --- a/docs/design/include/mcmini/model/transition_registry.hpp +++ b/docs/design/include/mcmini/model/transition_registry.hpp @@ -6,7 +6,7 @@ #include "mcmini/coordinator/coordinator.hpp" #include "mcmini/model/transition.hpp" -#include "mcmini/real_world/mailbox/runner_mailbox_stream.hpp" +#include "mcmini/real_world/mailbox/runner_mailbox.h" namespace model { @@ -28,7 +28,7 @@ class transition_registry final { using runtime_type_id = uint32_t; using rttid = runtime_type_id; using transition_discovery_callback = std::unique_ptr (*)( - const real_world::runner_mailbox_stream&, model_to_system_map&); + const volatile runner_mailbox&, model_to_system_map&); /** * @brief Marks the specified transition subclass as possible to encounter at diff --git a/docs/design/include/mcmini/real_world/process.hpp b/docs/design/include/mcmini/real_world/process.hpp index bdd2dbd6..9664ef74 100644 --- a/docs/design/include/mcmini/real_world/process.hpp +++ b/docs/design/include/mcmini/real_world/process.hpp @@ -3,7 +3,7 @@ #include #include -#include "mcmini/real_world/mailbox/runner_mailbox_stream.hpp" +#include "mcmini/real_world/mailbox/runner_mailbox.h" namespace real_world { @@ -37,7 +37,7 @@ struct process { }; /** - * @brief Schedule the runner with id `id` for execution. + * @brief Schedule the given runner for execution. * * This method signals the proxy process to resume execution of the runner * with id `mcmini_runner_id`. The method blocks until the runner reaches @@ -65,8 +65,7 @@ struct process { * threads, etc.) the idea of "adding" a new slot for a runner dynamically * might be needed. */ - virtual runner_mailbox_stream &execute_runner( - runner_id_t mcmini_runner_id) = 0; + virtual volatile runner_mailbox *execute_runner(runner_id_t) = 0; virtual ~process() = default; }; diff --git a/docs/design/include/mcmini/real_world/process/fork_process_source.hpp b/docs/design/include/mcmini/real_world/process/fork_process_source.hpp index f3f1f3f3..07b7036b 100644 --- a/docs/design/include/mcmini/real_world/process/fork_process_source.hpp +++ b/docs/design/include/mcmini/real_world/process/fork_process_source.hpp @@ -26,10 +26,6 @@ class fork_process_source : public process_source { // Alternatively, have McMini conditionally // compile a std::filesystem::path e.g. static std::unique_ptr rw_region; - static volatile_mem_streambuf - runner_mailbox_bufs[MAX_TOTAL_THREADS_IN_PROGRAM]; - static runner_mailbox_stream - *runner_mailbox_streams[MAX_TOTAL_THREADS_IN_PROGRAM]; static void initialize_shared_memory(); void setup_ld_preload(); diff --git a/docs/design/include/mcmini/real_world/process/local_linux_process.hpp b/docs/design/include/mcmini/real_world/process/local_linux_process.hpp index 71fd65e9..5a09b522 100644 --- a/docs/design/include/mcmini/real_world/process/local_linux_process.hpp +++ b/docs/design/include/mcmini/real_world/process/local_linux_process.hpp @@ -38,7 +38,6 @@ class local_linux_process : public process { public: local_linux_process(pid_t pid, shared_memory_region &shm_slice); virtual ~local_linux_process(); - - runner_mailbox_stream &execute_runner(runner_id_t mcmini_runner_id) override; + volatile runner_mailbox *execute_runner(runner_id_t) override; }; } // namespace real_world diff --git a/docs/design/include/mcmini/real_world/remote_address.hpp b/docs/design/include/mcmini/real_world/remote_address.hpp index a4a609c6..701139ad 100644 --- a/docs/design/include/mcmini/real_world/remote_address.hpp +++ b/docs/design/include/mcmini/real_world/remote_address.hpp @@ -24,6 +24,7 @@ struct remote_address final { public: T* get() const { return remote_addr; } + remote_address() : remote_addr(nullptr) {} remote_address(T* p) : remote_addr(p) {} bool operator==(const remote_address& o) const { diff --git a/docs/design/src/mcmini/coordinator/coordinator.cpp b/docs/design/src/mcmini/coordinator/coordinator.cpp index 4e076d52..0679e706 100644 --- a/docs/design/src/mcmini/coordinator/coordinator.cpp +++ b/docs/design/src/mcmini/coordinator/coordinator.cpp @@ -20,10 +20,9 @@ void coordinator::execute_runner(process::runner_id_t runner_id) { "Failed to execute runner with id \"" + std::to_string(runner_id) + "\": the process is not alive"); } - model::transition_registry::runtime_type_id rttid; - runner_mailbox_stream &mb = + volatile runner_mailbox *mb = this->current_process_handle->execute_runner(runner_id); - mb.read(&rttid); + model::transition_registry::runtime_type_id rttid = mb->cnts[0]; model::transition_registry::transition_discovery_callback callback_function = runtime_transition_mapping.get_callback_for(rttid); @@ -38,7 +37,7 @@ void coordinator::execute_runner(process::runner_id_t runner_id) { "libmcmini.so with this message."); } model_to_system_map remote_address_mapping = model_to_system_map(*this); - auto pending_operation = callback_function(mb, remote_address_mapping); + auto pending_operation = callback_function(*mb, remote_address_mapping); if (!pending_operation) { throw real_world::process::execution_exception( "Failed to translate the data written into the mailbox of runner " + @@ -48,36 +47,30 @@ void coordinator::execute_runner(process::runner_id_t runner_id) { runner_id, std::move(pending_operation)); } -void *model_to_system_map::get_remote_process_handle_for_object( +remote_address model_to_system_map::get_remote_process_handle_for_object( model::state::objid_t id) const { - // TODO: Implement this - return nullptr; + // TODO: Implement his + return remote_address{}; } -optional -model_to_system_map::get_object_for_remote_process_handle(void *handle) const { +model::state::objid_t model_to_system_map::get_object_for_remote_process_handle( + remote_address handle) const { if (_coordinator.system_address_mapping.count(handle) > 0) { - return optional( - _coordinator.system_address_mapping[handle]); + return _coordinator.system_address_mapping[handle]; } - return optional(); -} - -model::state::objid_t model_to_system_map::record_new_object_association( - void *remote_process_visible_object_handle, - std::unique_ptr initial_state) { - // TODO: Create a new object through the coordinator and then map handle - // `remote_process_visible_object_handle` to the newly-created object. - return 0; + return model::invalid_objid; } model::state::objid_t model_to_system_map::observe_remote_process_handle( - void *remote_process_visible_object_handle, + remote_address remote_process_visible_object_handle, std::unique_ptr fallback_initial_state) { - return this - ->get_object_for_remote_process_handle( - remote_process_visible_object_handle) - .value_or(this->record_new_object_association( - remote_process_visible_object_handle, - std::move(fallback_initial_state))); + model::state::objid_t existing_obj = + this->get_object_for_remote_process_handle( + remote_process_visible_object_handle); + if (existing_obj != model::invalid_objid) { + return existing_obj; + } + + // TODO: Inform the coordinator of the new object + return model::invalid_objid; } \ No newline at end of file diff --git a/docs/design/src/mcmini/mcmini.cpp b/docs/design/src/mcmini/mcmini.cpp index 484d0d9b..dd861f18 100644 --- a/docs/design/src/mcmini/mcmini.cpp +++ b/docs/design/src/mcmini/mcmini.cpp @@ -28,7 +28,7 @@ void display_usage() { } std::unique_ptr test_callback( - const real_world::runner_mailbox_stream& rms, model_to_system_map& msm) { + const volatile runner_mailbox& rms, model_to_system_map& msm) { return extensions::make_unique(0); } @@ -52,8 +52,8 @@ void do_model_checking( std::move(initial_first_steps)); // For "vanilla" model checking where we start at the beginning of the - // program, a fork_process_source suffices (fork() + exec() brings us to the - // beginning) + // program, a `fork_process_source suffices` (fork() + exec() brings us to the + // beginning). auto process_source = make_unique("hello-world"); tr.register_transition(&test_callback); diff --git a/docs/design/src/mcmini/model/detached_state.cpp b/docs/design/src/mcmini/model/detached_state.cpp index 8b053511..9981311d 100644 --- a/docs/design/src/mcmini/model/detached_state.cpp +++ b/docs/design/src/mcmini/model/detached_state.cpp @@ -5,6 +5,11 @@ using namespace model; +detached_state::detached_state(std::vector &&objs) + : visible_objects(std::move(objs)) {} +detached_state::detached_state(append_only &&objs) + : visible_objects(std::move(objs)) {} + bool detached_state::contains_object_with_id(state::objid_t id) const { return id < this->visible_objects.size(); } @@ -15,7 +20,7 @@ bool detached_state::contains_runner_with_id(state::runner_id_t id) const { state::objid_t detached_state::get_objid_for_runner(runner_id_t id) const { return this->contains_runner_with_id(id) ? runner_to_obj_map.at(id) - : model::invalid_obj_id; + : model::invalid_objid; } const visible_object_state *detached_state::get_state_of_object( diff --git a/docs/design/src/mcmini/model/state_sequence.cpp b/docs/design/src/mcmini/model/state_sequence.cpp index f8c7f371..1c1d5bff 100644 --- a/docs/design/src/mcmini/model/state_sequence.cpp +++ b/docs/design/src/mcmini/model/state_sequence.cpp @@ -2,6 +2,7 @@ #include #include +#include #include "mcmini/misc/append-only.hpp" #include "mcmini/misc/asserts.hpp" @@ -38,6 +39,7 @@ class state_sequence::element : public state { public: element() = default; + void record_new_runner() { max_visible_runner_id++; } void point_to_state_for(objid_t id, const visible_object_state *new_state) { this->visible_object_states[id] = new_state; } @@ -58,18 +60,26 @@ state_sequence::state_sequence() { this->push_state_snapshot(); } state_sequence::state_sequence(const state &s) { this->push_state_snapshot(); + + // Copy objects const size_t num_objs = s.count(); - for (objid_t i = 0; i < num_objs; i++) + for (objid_t i = 0; i < num_objs; i++) { add_object(s.get_state_of_object(i)->clone()); + } + + // Copy runner information. Here, the state of the runner has already been + // captured + this->runner_to_obj_map = append_only(s.runner_count()); + std::iota(this->runner_to_obj_map.begin(), this->runner_to_obj_map.end(), 0); } state_sequence::state_sequence(std::vector &&initial_objects) - : visible_objects(std::move(initial_objects)) { + : detached_state(std::move(initial_objects)) { this->push_state_snapshot(); } state_sequence::state_sequence(append_only &&ao) - : visible_objects(std::move(ao)) { + : detached_state(std::move(ao)) { this->push_state_snapshot(); } @@ -82,16 +92,23 @@ state::objid_t state_sequence::add_object( // INVARIANT: The current element needs to update at index `id` to reflect // this new object, as this element effectively represents this state objid_t id = visible_objects.size(); - this->states_in_sequence.back()->point_to_state_for(id, initial_state.get()); + this->get_representative_state().point_to_state_for(id, initial_state.get()); visible_objects.push_back(std::move(initial_state)); return id; } +state::runner_id_t state_sequence::add_runner( + std::unique_ptr initial_state) { + state::objid_t id = detached_state::add_runner(std::move(initial_state)); + this->get_representative_state().record_new_runner(); + return id; +} + void state_sequence::add_state_for_obj( objid_t id, std::unique_ptr new_state) { // INVARIANT: The current element needs to update at index `id` to reflect // this new state, as this element effectively represents this state - this->states_in_sequence.back()->point_to_state_for(id, new_state.get()); + this->get_representative_state().point_to_state_for(id, new_state.get()); this->visible_objects.at(id).push_state(std::move(new_state)); } @@ -109,17 +126,33 @@ state_sequence state_sequence::consume_into_subsequence(size_t index) && { return ss; } +size_t state_sequence::count() const { + return this->get_representative_state().count(); +} + +size_t state_sequence::runner_count() const { + return this->get_representative_state().runner_count(); +} + const state &state_sequence::state_at(size_t i) const { return *this->states_in_sequence.at(i); } -size_t state_sequence::state_count() const { +const state &state_sequence::front() const { + return *this->states_in_sequence.at(0); +} + +const state &state_sequence::back() const { + return *this->states_in_sequence.back(); +} + +size_t state_sequence::get_num_states_in_sequence() const { return this->states_in_sequence.size(); } state_sequence::~state_sequence() { // The elements have been dynamically allocated - for (const auto *element : this->states_in_sequence) delete element; + for (const element *element : this->states_in_sequence) delete element; } //////// state_sequence::element /////// @@ -135,7 +168,7 @@ state_sequence::element::element(const state_sequence &owner) : owner(owner) { state::objid_t state_sequence::element::get_objid_for_runner( runner_id_t id) const { return this->contains_runner_with_id(id) ? owner.runner_to_obj_map.at(id) - : model::invalid_obj_id; + : model::invalid_objid; } bool state_sequence::element::contains_object_with_id(state::objid_t id) const { diff --git a/docs/design/src/mcmini/real_world/fork_process_source.cpp b/docs/design/src/mcmini/real_world/fork_process_source.cpp index d26ae5b1..956d8b4d 100644 --- a/docs/design/src/mcmini/real_world/fork_process_source.cpp +++ b/docs/design/src/mcmini/real_world/fork_process_source.cpp @@ -30,9 +30,7 @@ void fork_process_source::initialize_shared_memory() { // TODO: This should be a configurable parameter perhaps... const int max_total_threads = MAX_TOTAL_THREADS_IN_PROGRAM; - for (int i = 0; i < max_total_threads; i++) { - mc_runner_mailbox_init(mbp + i); - } + for (int i = 0; i < max_total_threads; i++) mc_runner_mailbox_init(mbp + i); } fork_process_source::fork_process_source(std::string target_program) diff --git a/docs/design/src/mcmini/real_world/local_linux_process.cpp b/docs/design/src/mcmini/real_world/local_linux_process.cpp index 2c8017be..81294300 100644 --- a/docs/design/src/mcmini/real_world/local_linux_process.cpp +++ b/docs/design/src/mcmini/real_world/local_linux_process.cpp @@ -41,21 +41,9 @@ local_linux_process::~local_linux_process() { } } -runner_mailbox_stream &local_linux_process::execute_runner(runner_id_t id) { +volatile runner_mailbox *local_linux_process::execute_runner(runner_id_t id) { volatile runner_mailbox *rmb = shm_slice.as_stream_of(id); - volatile_mem_streambuf runner_mem_stream{&rmb->cnts, sizeof(&rmb->cnts)}; - mc_wake_thread(rmb); mc_wait_for_thread(rmb); - - static volatile_mem_streambuf runner_mailbox_bufs[50]; - static runner_mailbox_stream *runner_mailbox_streams[50]; - - if (runner_mailbox_streams[id]) { - delete runner_mailbox_streams[id]; - } - runner_mailbox_bufs[id] = std::move(runner_mem_stream); - runner_mailbox_streams[id] = - new runner_mailbox_stream{&runner_mailbox_bufs[id]}; - return *runner_mailbox_streams[id]; + return rmb; } \ No newline at end of file From 04beb31ea0dc90f787a972cf91657b7f374d92fb Mon Sep 17 00:00:00 2001 From: Maxwell Pirtle Date: Thu, 25 Apr 2024 16:02:00 -0400 Subject: [PATCH 018/161] Add explicit `type` field to runner_mailbox --- docs/design/include/mcmini/mem.h | 1 + .../real_world/mailbox/runner_mailbox.h | 1 + .../mcmini/spy/intercept/interception.h | 2 ++ .../include/mcmini/spy/intercept/wrappers.h | 2 ++ docs/design/src/common/mem.c | 6 +++++ docs/design/src/libmcmini/interception.c | 8 ++++++ docs/design/src/libmcmini/wrappers.c | 26 ++++++++++--------- .../src/mcmini/coordinator/coordinator.cpp | 2 +- 8 files changed, 35 insertions(+), 13 deletions(-) diff --git a/docs/design/include/mcmini/mem.h b/docs/design/include/mcmini/mem.h index 7431e66f..fa02a28e 100644 --- a/docs/design/include/mcmini/mem.h +++ b/docs/design/include/mcmini/mem.h @@ -2,5 +2,6 @@ #include // memcpy implementation but with volatile memory +volatile void *memset_v(volatile void *restrict dst, int ch, size_t n); volatile void *memcpy_v(volatile void *restrict dst, const volatile void *restrict src, size_t n); \ No newline at end of file diff --git a/docs/design/include/mcmini/real_world/mailbox/runner_mailbox.h b/docs/design/include/mcmini/real_world/mailbox/runner_mailbox.h index 876ca0d9..1c4c8d74 100644 --- a/docs/design/include/mcmini/real_world/mailbox/runner_mailbox.h +++ b/docs/design/include/mcmini/real_world/mailbox/runner_mailbox.h @@ -10,6 +10,7 @@ extern "C" { typedef struct { sem_t model_side_sem; sem_t child_side_sem; + uint32_t type; uint8_t cnts[64]; // TODO: How much space should each thread have to write // payloads? } runner_mailbox, *runner_mailbox_ref; diff --git a/docs/design/include/mcmini/spy/intercept/interception.h b/docs/design/include/mcmini/spy/intercept/interception.h index c508ca83..249ada34 100644 --- a/docs/design/include/mcmini/spy/intercept/interception.h +++ b/docs/design/include/mcmini/spy/intercept/interception.h @@ -10,5 +10,7 @@ void mc_load_intercepted_pthread_functions(); int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr); +int pthread_mutex_lock(pthread_mutex_t *mutex); +int pthread_mutex_unlock(pthread_mutex_t *mutex); // TODO: Other wrappers here \ No newline at end of file diff --git a/docs/design/include/mcmini/spy/intercept/wrappers.h b/docs/design/include/mcmini/spy/intercept/wrappers.h index e7f0d238..97c8a75e 100644 --- a/docs/design/include/mcmini/spy/intercept/wrappers.h +++ b/docs/design/include/mcmini/spy/intercept/wrappers.h @@ -12,4 +12,6 @@ volatile runner_mailbox *thread_get_mailbox(); int mc_pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr); +int mc_pthread_mutex_lock(pthread_mutex_t *mutex); +int mc_pthread_mutex_unlock(pthread_mutex_t *mutex); diff --git a/docs/design/src/common/mem.c b/docs/design/src/common/mem.c index 52f10e8a..c4435c17 100644 --- a/docs/design/src/common/mem.c +++ b/docs/design/src/common/mem.c @@ -1,5 +1,11 @@ #include "mcmini/mem.h" +volatile void *memset_v(volatile void *restrict dst, int ch, size_t n) { + volatile unsigned char *dstc = dst; + while ((n--) > 0) dstc[n] = ch; + return dst; +} + volatile void *memcpy_v(volatile void *restrict dst, const volatile void *restrict src, size_t n) { // From cppreference on the use of restrict pointers in the C language: diff --git a/docs/design/src/libmcmini/interception.c b/docs/design/src/libmcmini/interception.c index d5afbcd3..af0c1ef9 100644 --- a/docs/design/src/libmcmini/interception.c +++ b/docs/design/src/libmcmini/interception.c @@ -43,4 +43,12 @@ void mc_load_intercepted_pthread_functions() { int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr) { return mc_pthread_mutex_init(mutex, mutexattr); +} + +int pthread_mutex_lock(pthread_mutex_t *mutex) { + return mc_pthread_mutex_lock(mutex); +} + +int pthread_mutex_unlock(pthread_mutex_t *mutex) { + return mc_pthread_mutex_unlock(mutex); } \ No newline at end of file diff --git a/docs/design/src/libmcmini/wrappers.c b/docs/design/src/libmcmini/wrappers.c index a61a3a52..bebf8b93 100644 --- a/docs/design/src/libmcmini/wrappers.c +++ b/docs/design/src/libmcmini/wrappers.c @@ -34,20 +34,22 @@ thread_awake_scheduler_for_thread_finish_transition() int mc_pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr) { - // TODO: write into the shm region enough information - // to determine what just happened on the model side volatile runner_mailbox *mb = thread_get_mailbox(); - uint32_t t = MUTEX_INIT_TYPE; - memcpy_v(mb->cnts, &t, sizeof(t)); + mb->type = MUTEX_INIT_TYPE; + thread_await_scheduler(); + return 0; +} - // The coordinator first assumes data is written as follows: - // transition id followed by payload. - // - // TODO: There's no system in place to synchronize transition ids - // with the registration on the model side. This is a dynamic process - // (new transition can be added at runtime) - // For now, it suffices to assign a fixed value and just assume it - // corresponds on the model side +int mc_pthread_mutex_lock(pthread_mutex_t *mutex) { + volatile runner_mailbox *mb = thread_get_mailbox(); + mb->type = MUTEX_LOCK_TYPE; + thread_await_scheduler(); + return 0; +} + +int mc_pthread_mutex_unlock(pthread_mutex_t *mutex) { + volatile runner_mailbox *mb = thread_get_mailbox(); + mb->type = MUTEX_UNLOCK_TYPE; thread_await_scheduler(); return 0; } \ No newline at end of file diff --git a/docs/design/src/mcmini/coordinator/coordinator.cpp b/docs/design/src/mcmini/coordinator/coordinator.cpp index 0679e706..7235d2ee 100644 --- a/docs/design/src/mcmini/coordinator/coordinator.cpp +++ b/docs/design/src/mcmini/coordinator/coordinator.cpp @@ -22,7 +22,7 @@ void coordinator::execute_runner(process::runner_id_t runner_id) { } volatile runner_mailbox *mb = this->current_process_handle->execute_runner(runner_id); - model::transition_registry::runtime_type_id rttid = mb->cnts[0]; + model::transition_registry::runtime_type_id rttid = mb->type; model::transition_registry::transition_discovery_callback callback_function = runtime_transition_mapping.get_callback_for(rttid); From 2805ac0ec2d2f8a7c483227d34ea98d2f9365ccf Mon Sep 17 00:00:00 2001 From: Maxwell Pirtle Date: Thu, 25 Apr 2024 19:47:30 -0400 Subject: [PATCH 019/161] Wrap main in try-catch for unhandled exceptions This commit wraps `main` in a try-catch to ensure that all exceptions that make it all the way to the top level do not result in a call to `std::terminate()`. This ensures that proper destruction of static global variables occurs even when an exception remains unhandled throughout the entire call stack (save for main). --- .../coordinator/model_to_system_map.hpp | 15 +++---- docs/design/include/mcmini/model/program.hpp | 7 ++++ docs/design/include/mcmini/model/state.hpp | 3 ++ .../include/mcmini/model/transition.hpp | 13 +++++-- .../src/mcmini/coordinator/coordinator.cpp | 39 +++++++++++++++---- docs/design/src/mcmini/mcmini.cpp | 17 +++++++- .../src/mcmini/model/state_sequence.cpp | 2 +- docs/design/src/mcmini/real_world/shm.cpp | 2 - 8 files changed, 73 insertions(+), 25 deletions(-) diff --git a/docs/design/include/mcmini/coordinator/model_to_system_map.hpp b/docs/design/include/mcmini/coordinator/model_to_system_map.hpp index a744446c..a80c3117 100644 --- a/docs/design/include/mcmini/coordinator/model_to_system_map.hpp +++ b/docs/design/include/mcmini/coordinator/model_to_system_map.hpp @@ -34,14 +34,6 @@ struct model_to_system_map final { /* Prevent external construction */ model_to_system_map() = delete; - /** - * @brief Retrieve the remote address of the object with id `id`. - * - * TODO: See the TODOs below - */ - real_world::remote_address get_remote_process_handle_for_object( - model::state::objid_t id) const; - /** * @brief Retrieve the object that corresponds to the given remote address, or * `model::invalid_objid` if the address is not known to this mapping. @@ -62,6 +54,9 @@ struct model_to_system_map final { * the coordinator to handle aliasing etc. */ model::state::objid_t observe_remote_process_handle( - real_world::remote_address remote_process_visible_object_handle, - std::unique_ptr fallback_initial_state); + real_world::remote_address, + std::unique_ptr); + model::state::objid_t observe_remote_process_runner( + real_world::remote_address, + std::unique_ptr); }; diff --git a/docs/design/include/mcmini/model/program.hpp b/docs/design/include/mcmini/model/program.hpp index e4b6bdbf..e229e160 100644 --- a/docs/design/include/mcmini/model/program.hpp +++ b/docs/design/include/mcmini/model/program.hpp @@ -48,6 +48,7 @@ class program { state_sequence state_seq; transition_sequence trace; pending_transitions next_steps; + friend model_to_system_map; public: using runner_id_t = uint32_t; @@ -72,6 +73,12 @@ class program { void model_executing_runner(runner_id_t p, std::unique_ptr new_transition) { + if (p != new_transition->get_executor()) { + throw std::invalid_argument( + "Attempted to assign a transition to runner " + std::to_string(p) + + " with a different runner (" + + std::to_string(new_transition->get_executor()) + ")"); + } const transition *next_s_p = next_steps.get_transition_for_runner(p); if (next_s_p) { this->state_seq.follow(*next_s_p); diff --git a/docs/design/include/mcmini/model/state.hpp b/docs/design/include/mcmini/model/state.hpp index 66ecb111..1c79d16f 100644 --- a/docs/design/include/mcmini/model/state.hpp +++ b/docs/design/include/mcmini/model/state.hpp @@ -97,6 +97,9 @@ class mutable_state : public state { /** * @brief Begin tracking a new visible object, but consider it as the state of * a new runner. + * + * @return the id of the runner that was just added. This is NOT the same as + * the runner's object id. */ virtual runner_id_t add_runner( std::unique_ptr initial_state) = 0; diff --git a/docs/design/include/mcmini/model/transition.hpp b/docs/design/include/mcmini/model/transition.hpp index a6332d8e..ec047d92 100644 --- a/docs/design/include/mcmini/model/transition.hpp +++ b/docs/design/include/mcmini/model/transition.hpp @@ -65,11 +65,12 @@ class transition { public: using runner_id_t = uint32_t; + transition(runner_id_t executor) : executor(executor) {} + /** - * The thread/runner which actually executes this transition. + * @brief Returns the runner which executes this transition. */ - const runner_id_t executor; - transition(runner_id_t executor) : executor(executor) {} + runner_id_t get_executor() const { return executor; } /** * @brief Attempts to produce a state _s'_ from state _s_ through the @@ -136,6 +137,12 @@ class transition { virtual std::string to_string() const = 0; virtual ~transition() = default; + + protected: + /** + * The thread/runner which actually executes this transition. + */ + const runner_id_t executor; }; } // namespace model \ No newline at end of file diff --git a/docs/design/src/mcmini/coordinator/coordinator.cpp b/docs/design/src/mcmini/coordinator/coordinator.cpp index 7235d2ee..3fcd00d4 100644 --- a/docs/design/src/mcmini/coordinator/coordinator.cpp +++ b/docs/design/src/mcmini/coordinator/coordinator.cpp @@ -47,12 +47,6 @@ void coordinator::execute_runner(process::runner_id_t runner_id) { runner_id, std::move(pending_operation)); } -remote_address model_to_system_map::get_remote_process_handle_for_object( - model::state::objid_t id) const { - // TODO: Implement his - return remote_address{}; -} - model::state::objid_t model_to_system_map::get_object_for_remote_process_handle( remote_address handle) const { if (_coordinator.system_address_mapping.count(handle) > 0) { @@ -69,8 +63,37 @@ model::state::objid_t model_to_system_map::observe_remote_process_handle( remote_process_visible_object_handle); if (existing_obj != model::invalid_objid) { return existing_obj; + } else { + model::state::objid_t new_objid = + _coordinator.current_program_model.state_seq.add_object( + std::move(fallback_initial_state)); + + _coordinator.system_address_mapping.insert( + {remote_process_visible_object_handle, new_objid}); + + return new_objid; } +} - // TODO: Inform the coordinator of the new object - return model::invalid_objid; +model::state::objid_t model_to_system_map::observe_remote_process_runner( + real_world::remote_address remote_process_visible_object_handle, + std::unique_ptr fallback_initial_state) { + model::state::objid_t existing_obj = + this->get_object_for_remote_process_handle( + remote_process_visible_object_handle); + if (existing_obj != model::invalid_objid) { + return existing_obj; + } else { + model::state::runner_id_t new_runner_id = + _coordinator.current_program_model.state_seq.add_runner( + std::move(fallback_initial_state)); + model::state::objid_t new_objid = + _coordinator.current_program_model.get_state_sequence() + .get_objid_for_runner(new_runner_id); + + _coordinator.system_address_mapping.insert( + {remote_process_visible_object_handle, new_objid}); + + return new_objid; + } } \ No newline at end of file diff --git a/docs/design/src/mcmini/mcmini.cpp b/docs/design/src/mcmini/mcmini.cpp index dd861f18..131e675f 100644 --- a/docs/design/src/mcmini/mcmini.cpp +++ b/docs/design/src/mcmini/mcmini.cpp @@ -113,4 +113,19 @@ void do_model_checking_from_dmtcp_ckpt_file(std::string file_name) { std::cerr << "Model checking completed!" << std::endl; } -int main(int argc, char** argv) { do_model_checking(); } +int main_cpp(int argc, const char** argv) { + do_model_checking(); + return EXIT_SUCCESS; +} + +int main(int argc, const char** argv) { + try { + return main_cpp(argc, argv); + } catch (const std::exception& e) { + std::cerr << "ERROR: " << e.what() << std::endl; + return EXIT_FAILURE; + } catch (...) { + std::cerr << "ERROR: Unknown error occurred" << std::endl; + return EXIT_FAILURE; + } +} diff --git a/docs/design/src/mcmini/model/state_sequence.cpp b/docs/design/src/mcmini/model/state_sequence.cpp index 1c1d5bff..9ac07b2b 100644 --- a/docs/design/src/mcmini/model/state_sequence.cpp +++ b/docs/design/src/mcmini/model/state_sequence.cpp @@ -99,7 +99,7 @@ state::objid_t state_sequence::add_object( state::runner_id_t state_sequence::add_runner( std::unique_ptr initial_state) { - state::objid_t id = detached_state::add_runner(std::move(initial_state)); + state::runner_id_t id = detached_state::add_runner(std::move(initial_state)); this->get_representative_state().record_new_runner(); return id; } diff --git a/docs/design/src/mcmini/real_world/shm.cpp b/docs/design/src/mcmini/real_world/shm.cpp index 5dbb942e..deb71793 100644 --- a/docs/design/src/mcmini/real_world/shm.cpp +++ b/docs/design/src/mcmini/real_world/shm.cpp @@ -51,7 +51,6 @@ shared_memory_region::~shared_memory_region() { int rc = munmap(const_cast(shm_mmap_region), size()); if (rc == -1) { std::perror("munmap"); - std::exit(EXIT_FAILURE); } rc = shm_unlink(shm_file_name.c_str()); if (rc == -1) { @@ -63,6 +62,5 @@ shared_memory_region::~shared_memory_region() { } else { std::perror("shm_unlink"); } - std::exit(EXIT_FAILURE); } } From b4e1fcb21f2151c7d08cb4fc83b10775f6e4c926 Mon Sep 17 00:00:00 2001 From: Maxwell Pirtle Date: Mon, 29 Apr 2024 21:57:03 -0400 Subject: [PATCH 020/161] Add signal_tracker for process forking --- docs/design/CMakeLists.txt | 1 + docs/design/include/mcmini/defines.h | 9 +-- docs/design/include/mcmini/lib/entry.h | 2 +- docs/design/include/mcmini/mcmini.h | 1 + docs/design/include/mcmini/mcmini.hpp | 1 + .../process/fork_process_source.hpp | 23 +++++- .../real_world/process/template_process.h | 24 ++++++ .../include/mcmini/real_world/process/wait.h | 20 +++++ docs/design/include/mcmini/real_world/shm.hpp | 17 ++-- docs/design/include/mcmini/signal.hpp | 25 ++++++ .../src/examples/volatile_mem_stream.cpp | 4 +- docs/design/src/libmcmini/entry.c | 55 +++++++++++-- docs/design/src/libmcmini/wrappers.c | 6 +- .../src/mcmini/coordinator/coordinator.cpp | 4 - docs/design/src/mcmini/mcmini.cpp | 1 + .../mcmini/real_world/fork_process_source.cpp | 80 ++++++++++++++++--- .../mcmini/real_world/local_linux_process.cpp | 10 ++- docs/design/src/mcmini/signal.cpp | 58 ++++++++++++++ 18 files changed, 297 insertions(+), 44 deletions(-) create mode 100644 docs/design/include/mcmini/real_world/process/template_process.h create mode 100644 docs/design/include/mcmini/real_world/process/wait.h create mode 100644 docs/design/include/mcmini/signal.hpp create mode 100644 docs/design/src/mcmini/signal.cpp diff --git a/docs/design/CMakeLists.txt b/docs/design/CMakeLists.txt index d4612932..7b2a6cc9 100644 --- a/docs/design/CMakeLists.txt +++ b/docs/design/CMakeLists.txt @@ -24,6 +24,7 @@ set(MCMINI_C_SRC ) set(MCMINI_CPP_SRC src/mcmini/mcmini.cpp + src/mcmini/signal.cpp src/mcmini/visible_object.cpp src/mcmini/coordinator/coordinator.cpp src/mcmini/model/detached_state.cpp diff --git a/docs/design/include/mcmini/defines.h b/docs/design/include/mcmini/defines.h index 1884c734..57298758 100644 --- a/docs/design/include/mcmini/defines.h +++ b/docs/design/include/mcmini/defines.h @@ -8,14 +8,14 @@ #define MCMINI_PRIVATE __attribute__((visibility(hidden))) #define MCMINI_THREAD_LOCAL _Thread_local +#define MAX_TOTAL_TRANSITIONS_IN_PROGRAM (1500u) +#define MAX_TOTAL_STATES_IN_STATE_STACK (MAX_TOTAL_TRANSITIONS_IN_PROGRAM + 1u) #define MAX_TOTAL_THREADS_IN_PROGRAM (20u) #define MAX_TOTAL_VISIBLE_OBJECTS_IN_PROGRAM (10000u) -#define MAX_SHARED_MEMORY_ALLOCATION (4096u) #define MAX_TOTAL_STATE_OBJECTS_IN_PROGRAM \ (MAX_TOTAL_THREADS_IN_PROGRAM + MAX_TOTAL_VISIBLE_OBJECTS_IN_PROGRAM) -#define MAX_TOTAL_TRANSITIONS_IN_PROGRAM (1500u) -#define MAX_TOTAL_STATES_IN_STATE_STACK (MAX_TOTAL_TRANSITIONS_IN_PROGRAM + 1u) +#define THREAD_SHM_OFFSET (128) typedef uint64_t tid_t; typedef uint64_t mutid_t; @@ -23,13 +23,10 @@ typedef uint64_t trid_t; #define TID_MAIN_THREAD (0ul) #define TID_INVALID (-1ul) // ULONG_MAX #define TID_PTHREAD_CREATE_FAILED (-2ul) // ULONG_MAX - 1 -#define MUTID_INVALID (-1ul) // ULONG_MAX #define FORK_IS_CHILD_PID(pid) ((pid) == 0) #define FORK_IS_PARENT_PID(pid) (!(FORK_IS_CHILD_PID(pid))) -#define MC_CONSTRUCTOR __attribute__((constructor)) - #define PTHREAD_SUCCESS (0) #define SEM_FLAG_SHARED (1) diff --git a/docs/design/include/mcmini/lib/entry.h b/docs/design/include/mcmini/lib/entry.h index 04c6bf9a..bdb3bc2b 100644 --- a/docs/design/include/mcmini/lib/entry.h +++ b/docs/design/include/mcmini/lib/entry.h @@ -2,7 +2,7 @@ #include "mcmini/defines.h" -extern volatile void *shm_start; +extern volatile void *global_shm_start; extern MCMINI_THREAD_LOCAL tid_t tid_self; void mc_exit(int); diff --git a/docs/design/include/mcmini/mcmini.h b/docs/design/include/mcmini/mcmini.h index 4cb50aff..2fef11de 100644 --- a/docs/design/include/mcmini/mcmini.h +++ b/docs/design/include/mcmini/mcmini.h @@ -6,6 +6,7 @@ #include "mcmini/lib/shared_transition.h" #include "mcmini/common/shm_config.h" #include "mcmini/real_world/mailbox/runner_mailbox.h" +#include "mcmini/real_world/process/template_process.h" #include "mcmini/spy/intercept/interception.h" #include "mcmini/spy/intercept/wrappers.h" diff --git a/docs/design/include/mcmini/mcmini.hpp b/docs/design/include/mcmini/mcmini.hpp index d30f861b..4406f4e1 100644 --- a/docs/design/include/mcmini/mcmini.hpp +++ b/docs/design/include/mcmini/mcmini.hpp @@ -8,3 +8,4 @@ #include "mcmini/model_checking/algorithm.hpp" #include "mcmini/real_world/process.hpp" #include "mcmini/real_world/process_source.hpp" +#include "mcmini/signal.hpp" \ No newline at end of file diff --git a/docs/design/include/mcmini/real_world/process/fork_process_source.hpp b/docs/design/include/mcmini/real_world/process/fork_process_source.hpp index 07b7036b..24a57779 100644 --- a/docs/design/include/mcmini/real_world/process/fork_process_source.hpp +++ b/docs/design/include/mcmini/real_world/process/fork_process_source.hpp @@ -15,21 +15,36 @@ namespace real_world { * located at `target` with `libmcmini.so` preloaded. * * A `fork_process_source` is responsible for creating new processes by forking - * this process and repeatedly `exec()`-ing into a new one. + * a template process, `exec()`-ing into a new one, and then repeatedly forking + * the new process to create new process sources. The processes that this + * process source vends are duplicates of the template process */ class fork_process_source : public process_source { private: // The name of the program which we should exec() into with libmcmini.so // preloaded. std::string target_program; // NOTE: Favor std::filesystem::path if C++17 - // is eventually supported - // Alternatively, have McMini conditionally - // compile a std::filesystem::path e.g. + // is eventually supported + // Alternatively, have McMini conditionally + // compile a std::filesystem::path e.g. + + /// @brief The process id of the template process whose libmcmini performs a + /// `sigwait()` loop ad infinitum. + /// + /// This value refers to the process id of the process that is repeatedly + /// asked to invoke the `fork(2)` system call. + pid_t template_pid = no_template; + constexpr static pid_t no_template = -1; + static std::unique_ptr rw_region; static void initialize_shared_memory(); void setup_ld_preload(); void reset_binary_semaphores_for_new_process(); + void make_new_template_process(); + void template_process_sig_handler(); + + bool has_template_process_alive() const { return template_pid != -1; } public: fork_process_source(std::string target_program); diff --git a/docs/design/include/mcmini/real_world/process/template_process.h b/docs/design/include/mcmini/real_world/process/template_process.h new file mode 100644 index 00000000..ff0201f0 --- /dev/null +++ b/docs/design/include/mcmini/real_world/process/template_process.h @@ -0,0 +1,24 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#define TEMPLATE_FORK_FAILED ((cpid)-2) // fork(2) failed in the template + +struct template_process_t { + // The current process id of the child process currently under control of this + // template process. + pid_t cpid; + + // A semaphore that the McMini process waits on and that + // the template process signals after writing the newly spawned pid to shared + // memory. + sem_t mcmini_process_sem; +}; + +#ifdef __cplusplus +} +#endif // extern "C" \ No newline at end of file diff --git a/docs/design/include/mcmini/real_world/process/wait.h b/docs/design/include/mcmini/real_world/process/wait.h new file mode 100644 index 00000000..ab0dcdbf --- /dev/null +++ b/docs/design/include/mcmini/real_world/process/wait.h @@ -0,0 +1,20 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +// +struct wait_status { + pid_t cpid; +}; + +struct wait_status mc_wait(); +struct wait_status mc_waitpid(pid_t, int); + +#ifdef __cplusplus +} +#endif // extern "C" \ No newline at end of file diff --git a/docs/design/include/mcmini/real_world/shm.hpp b/docs/design/include/mcmini/real_world/shm.hpp index af62038b..49b33085 100644 --- a/docs/design/include/mcmini/real_world/shm.hpp +++ b/docs/design/include/mcmini/real_world/shm.hpp @@ -23,13 +23,20 @@ struct shared_memory_region { volatile void* get() const { return this->shm_mmap_region; } volatile void* contents() const { return get(); } - + volatile void* off(off64_t offset) const { + return static_cast( + static_cast(shm_mmap_region) + offset); + } + template + volatile T* as() const { + return static_cast(shm_mmap_region); + } template - volatile T* as_stream_of(off64_t off = 0) const { - return static_cast(shm_mmap_region) + off; + volatile T* as_array_of(off64_t off_out = 0, off64_t off_in = 0) const { + return static_cast(off(off_in)) + off_out; } - volatile char* byte_stream(off64_t off = 0) const { - return as_stream_of(off); + volatile char* byte_array(off64_t off = 0) const { + return as_array_of(off); } size_t size() const { return this->region_size; } diff --git a/docs/design/include/mcmini/signal.hpp b/docs/design/include/mcmini/signal.hpp new file mode 100644 index 00000000..7cea2537 --- /dev/null +++ b/docs/design/include/mcmini/signal.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include + +struct signal_tracker { + private: + static signal_tracker _instance; + constexpr signal_tracker() = default; + constexpr static size_t MAX_SIGNAL_TYPES = NSIG - 1; +#ifndef ATOMIC_INT_LOCK_FREE +#error "Signal tracking relies on `std::atomic` to be lock-free." +#endif + std::atomic_uint32_t flags[MAX_SIGNAL_TYPES] = {}; + + public: + static signal_tracker &instance(); + void set_signal(int sig); + bool has_signal(int sig) const; + bool try_consume_signal(int sig); +}; + +extern "C" void signal_tracker_sig_handler(int sig); +extern "C" void install_process_wide_signal_handlers(); \ No newline at end of file diff --git a/docs/design/src/examples/volatile_mem_stream.cpp b/docs/design/src/examples/volatile_mem_stream.cpp index 2e5ce9a0..c2b7c454 100644 --- a/docs/design/src/examples/volatile_mem_stream.cpp +++ b/docs/design/src/examples/volatile_mem_stream.cpp @@ -11,10 +11,10 @@ int main() { real_world::shared_memory_region smr{"hello", 100}; - volatile int *smr_bytes = smr.as_stream_of(); + volatile int *smr_bytes = smr.as_array_of(); // std::memset((void *)smr.get(), 0x22, smr.size());q - volatile_mem_streambuf vms{smr.byte_stream(20), 30}; + volatile_mem_streambuf vms{smr.byte_array(20), 30}; uint32_t my_val = UINT32_MAX; diff --git a/docs/design/src/libmcmini/entry.c b/docs/design/src/libmcmini/entry.c index 8c21fd70..f840e646 100644 --- a/docs/design/src/libmcmini/entry.c +++ b/docs/design/src/libmcmini/entry.c @@ -1,3 +1,5 @@ +#define _POSIX_C_SOURCE +#define _GNU_SOURCE #include #include #include @@ -9,10 +11,12 @@ #include #include #include +#include +#include #include "mcmini/mcmini.h" -volatile void *shm_start = NULL; +volatile void *global_shm_start = NULL; MCMINI_THREAD_LOCAL tid_t tid_self = TID_INVALID; tid_t @@ -23,7 +27,7 @@ mc_register_this_thread() return tid_self; } -void * +void mc_allocate_shared_memory_region() { char dpor[100]; @@ -46,9 +50,9 @@ mc_allocate_shared_memory_region() mc_exit(EXIT_FAILURE); } - void *shm_start = + void *gshms = mmap(NULL, shm_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if (shm_start == MAP_FAILED) { + if (gshms == MAP_FAILED) { perror("mmap"); mc_exit(EXIT_FAILURE); } @@ -56,7 +60,7 @@ mc_allocate_shared_memory_region() /* The parent process will handle shm_unlink() */ fsync(fd); close(fd); - return shm_start; + global_shm_start = gshms; } void @@ -64,8 +68,8 @@ mc_deallocate_shared_memory_region() { char shm_file_name[100]; mc_get_shm_handle_name(shm_file_name, sizeof(shm_file_name)); - if (shm_start) { - int rc = munmap((void*)shm_start, shm_size); + if (global_shm_start) { + int rc = munmap((void*)global_shm_start, shm_size); if (rc == -1) { perror("munmap"); mc_exit(EXIT_FAILURE); @@ -84,10 +88,45 @@ mc_exit(int status) _Exit(status); } +void mc_template_process_loop_forever() { + volatile struct template_process_t *tpt = global_shm_start; + sigset_t sigurs1_set; + sigemptyset(&sigurs1_set); + sigaddset(&sigurs1_set, SIGUSR1); + + while (1) { + int sig; + sigwait(&sigurs1_set, &sig); + pid_t cpid = fork(); + if (cpid == -1) { + // `fork()` failed + tpt->cpid = TEMPLATE_FORK_FAILED; + } + else if (cpid == 0) { + // Child case: Simply return and escape into the child process. + return; + } + // `libmcmini.so` acting as a template process. + sem_post((sem_t*)&tpt->mcmini_process_sem); + } +} + +void mc_prevent_addr_randomization() { + if (personality(ADDR_NO_RANDOMIZE) == -1) { + perror("personality"); + mc_exit(EXIT_FAILURE); + } +} + __attribute__((constructor)) void libmcmini_main() { + mc_prevent_addr_randomization(); mc_register_this_thread(); mc_load_intercepted_pthread_functions(); + mc_allocate_shared_memory_region(); atexit(&mc_deallocate_shared_memory_region); - shm_start = mc_allocate_shared_memory_region(); + + if (getenv("libmcmini-freeze") != NULL) { + mc_template_process_loop_forever(); + } thread_await_scheduler_for_thread_start_transition(); } \ No newline at end of file diff --git a/docs/design/src/libmcmini/wrappers.c b/docs/design/src/libmcmini/wrappers.c index bebf8b93..96a79efb 100644 --- a/docs/design/src/libmcmini/wrappers.c +++ b/docs/design/src/libmcmini/wrappers.c @@ -4,7 +4,7 @@ #include "mcmini/mcmini.h" volatile runner_mailbox *thread_get_mailbox() { - return ((volatile runner_mailbox*)(shm_start)) + tid_self; + return ((volatile runner_mailbox*)(global_shm_start + THREAD_SHM_OFFSET)) + tid_self; } void @@ -36,6 +36,8 @@ int mc_pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr) { volatile runner_mailbox *mb = thread_get_mailbox(); mb->type = MUTEX_INIT_TYPE; + printf("YO\n"); + fsync(STDOUT_FILENO); thread_await_scheduler(); return 0; } @@ -43,6 +45,8 @@ int mc_pthread_mutex_init(pthread_mutex_t *mutex, int mc_pthread_mutex_lock(pthread_mutex_t *mutex) { volatile runner_mailbox *mb = thread_get_mailbox(); mb->type = MUTEX_LOCK_TYPE; + printf("YYA"); + fsync(STDOUT_FILENO); thread_await_scheduler(); return 0; } diff --git a/docs/design/src/mcmini/coordinator/coordinator.cpp b/docs/design/src/mcmini/coordinator/coordinator.cpp index 3fcd00d4..041728b4 100644 --- a/docs/design/src/mcmini/coordinator/coordinator.cpp +++ b/docs/design/src/mcmini/coordinator/coordinator.cpp @@ -67,10 +67,8 @@ model::state::objid_t model_to_system_map::observe_remote_process_handle( model::state::objid_t new_objid = _coordinator.current_program_model.state_seq.add_object( std::move(fallback_initial_state)); - _coordinator.system_address_mapping.insert( {remote_process_visible_object_handle, new_objid}); - return new_objid; } } @@ -90,10 +88,8 @@ model::state::objid_t model_to_system_map::observe_remote_process_runner( model::state::objid_t new_objid = _coordinator.current_program_model.get_state_sequence() .get_objid_for_runner(new_runner_id); - _coordinator.system_address_mapping.insert( {remote_process_visible_object_handle, new_objid}); - return new_objid; } } \ No newline at end of file diff --git a/docs/design/src/mcmini/mcmini.cpp b/docs/design/src/mcmini/mcmini.cpp index 131e675f..a2a78f37 100644 --- a/docs/design/src/mcmini/mcmini.cpp +++ b/docs/design/src/mcmini/mcmini.cpp @@ -114,6 +114,7 @@ void do_model_checking_from_dmtcp_ckpt_file(std::string file_name) { } int main_cpp(int argc, const char** argv) { + install_process_wide_signal_handlers(); do_model_checking(); return EXIT_SUCCESS; } diff --git a/docs/design/src/mcmini/real_world/fork_process_source.cpp b/docs/design/src/mcmini/real_world/fork_process_source.cpp index 956d8b4d..75436e2e 100644 --- a/docs/design/src/mcmini/real_world/fork_process_source.cpp +++ b/docs/design/src/mcmini/real_world/fork_process_source.cpp @@ -2,9 +2,11 @@ #include #include +#include #include #include +#include #include #include #include @@ -14,6 +16,8 @@ #include "mcmini/misc/extensions/unique_ptr.hpp" #include "mcmini/real_world/mailbox/runner_mailbox.h" #include "mcmini/real_world/process/local_linux_process.hpp" +#include "mcmini/real_world/process/template_process.h" +#include "mcmini/signal.hpp" using namespace real_world; using namespace extensions; @@ -23,10 +27,9 @@ std::unique_ptr fork_process_source::rw_region = nullptr; void fork_process_source::initialize_shared_memory() { const std::string shm_file_name = "/mcmini-" + std::string(getenv("USER")) + "-" + std::to_string((long)getpid()); - rw_region = make_unique(shm_file_name, shm_size); - volatile runner_mailbox* mbp = rw_region->as_stream_of(); + volatile runner_mailbox* mbp = rw_region->as_array_of(); // TODO: This should be a configurable parameter perhaps... const int max_total_threads = MAX_TOTAL_THREADS_IN_PROGRAM; @@ -42,10 +45,48 @@ fork_process_source::fork_process_source(std::string target_program) std::unique_ptr fork_process_source::make_new_process() { setup_ld_preload(); reset_binary_semaphores_for_new_process(); + if (!has_template_process_alive()) make_new_template_process(); + + if (signal_tracker::instance().try_consume_signal(SIGCHLD)) { + this->template_pid = fork_process_source::no_template; + + // The template process exited unexpectedly: we can no longer create a new + // process from it. `waitpid()` ensures that the child's resources are + // properly reacquired. + if (waitpid(this->template_pid, nullptr, 0) == -1) { + throw process_source::process_creation_exception( + "Failed to create a cleanup zombied child process (waitpid " + "returned -1): " + + std::string(strerror(errno))); + } + throw process_source::process_creation_exception( + "Failed to create a new process (template process died)"); + } + + const volatile template_process_t* tstruct = + rw_region->as(); + int rc = sem_wait((sem_t*)&tstruct->mcmini_process_sem); + + if (rc != 0 && errno == EINTR) { + throw process_source::process_creation_exception( + "The template process ( " + std::to_string(template_pid) + + ") was not able to complete"); + } + + return extensions::make_unique(tstruct->cpid, + *rw_region); +} + +void fork_process_source::make_new_template_process() { + // Reset first. If an exception is raised in subsequent steps, we don't want + // to erroneously think that there is a template process when indeed there + // isn't one. + this->template_pid = fork_process_source::no_template; int pipefd[2]; if (pipe(pipefd) == -1) { - throw std::runtime_error("Failed to open pipe(2)"); + throw std::runtime_error("Failed to open pipe(2): " + + std::string(strerror(errno))); } errno = 0; @@ -56,28 +97,38 @@ std::unique_ptr fork_process_source::make_new_process() { "Failed to create a new process (fork(2) failed): " + std::string(strerror(errno))); } else if (child_pid == 0) { + // ****************** // Child process case - + // ****************** close(pipefd[0]); fcntl(pipefd[1], F_SETFD, FD_CLOEXEC); - // TODO: Add additional arguments here if needed - // const_cast<> is needed to call the C-functions here. A new/delete + // `const_cast<>` is needed to call the C-functions here. A new/delete // or malloc/free _could be_ needed, we'd need to check the man page. As // long as the char * is not actually modified, this is OK and the best way // to interface with the C library routines - char* args[] = {const_cast(this->target_program.c_str()), NULL}; - std::cerr << "About to exec with libmcmini.so loaded! Attempting to run " - << this->target_program.c_str() << std::endl; + char* args[] = {const_cast(this->target_program.c_str()), + NULL /*TODO: Add additional arguments here if needed */}; + setenv("libmcmini-freeze", "1", 1); + personality(ADDR_NO_RANDOMIZE); execvp(this->target_program.c_str(), args); + unsetenv("libmcmini-freeze"); - // IF EXECVP fails + // If `execvp()` fails, we signal the error to the parent process by writing + // into the pipe. int err = errno; write(pipefd[1], &err, sizeof(err)); close(pipefd[1]); exit(EXIT_FAILURE); + // ****************** + // Child process case + // ****************** + } else { + // ******************* // Parent process case + // ******************* + close(pipefd[1]); // Close write end int err = 0; @@ -94,8 +145,12 @@ std::unique_ptr fork_process_source::make_new_process() { std::string(strerror(err))); } close(pipefd[0]); + + // ******************* + // Parent process case + // ******************* + this->template_pid = child_pid; } - return extensions::make_unique(child_pid, *rw_region); } void fork_process_source::setup_ld_preload() { @@ -115,7 +170,8 @@ void fork_process_source::reset_binary_semaphores_for_new_process() { // // INVARIANT: Only one `local_linux_process` is in existence at any given // time. - volatile runner_mailbox* mbp = rw_region->as_stream_of(); + volatile runner_mailbox* mbp = + rw_region->as_array_of(0, THREAD_SHM_OFFSET); const int max_total_threads = MAX_TOTAL_THREADS_IN_PROGRAM; for (int i = 0; i < max_total_threads; i++) { mc_runner_mailbox_destroy(mbp + i); diff --git a/docs/design/src/mcmini/real_world/local_linux_process.cpp b/docs/design/src/mcmini/real_world/local_linux_process.cpp index 81294300..c08db694 100644 --- a/docs/design/src/mcmini/real_world/local_linux_process.cpp +++ b/docs/design/src/mcmini/real_world/local_linux_process.cpp @@ -42,7 +42,15 @@ local_linux_process::~local_linux_process() { } volatile runner_mailbox *local_linux_process::execute_runner(runner_id_t id) { - volatile runner_mailbox *rmb = shm_slice.as_stream_of(id); + volatile runner_mailbox *rmb = + shm_slice.as_array_of(id, THREAD_SHM_OFFSET); + + // TODO: As a sanity check, a `waitpid()` to check if the process is still + // alive is probably warranted. This would prevent a deadlock in _most_ cases. + // Of course, if the process terminates in between the check and the + // sem_wait() call, we'd still have deadlock. A happy medium is to call + // `sem_timedwait()` with a sufficiently long wait value (perhaps 1 second) + // and poll for existence if we haven't heard from the child in a long time. mc_wake_thread(rmb); mc_wait_for_thread(rmb); return rmb; diff --git a/docs/design/src/mcmini/signal.cpp b/docs/design/src/mcmini/signal.cpp new file mode 100644 index 00000000..a1690c3d --- /dev/null +++ b/docs/design/src/mcmini/signal.cpp @@ -0,0 +1,58 @@ +#include "mcmini/signal.hpp" + +void signal_tracker_sig_handler(int sig, siginfo_t *, void *) { + signal_tracker::instance().set_signal(sig); +} + +void install_process_wide_signal_handlers() { + // From + // "https://wiki.sei.cmu.edu/confluence/display/cplusplus/MSC54-CPP.+A+signal+handler+must+be+a+plain+old+function" + // + // """ + // The common subset of the C and C++ languages consists of all + // declarations, definitions, and expressions that may appear in a + // well-formed C++ program and also in a conforming C program. A POF + // (“plain old function”) is a function that uses only features from this + // common subset, and that does not directly or indirectly use any + // function that is not a POF, except that it may use plain lock-free + // atomic operations. A plain lock-free atomic operation is an invocation + // of a function f from Clause 29, such that f is not a member function, + // and either f is the function atomic_is_lock_free, or for every atomic + // argument A passed to f, atomic_is_lock_free(A) yields true. All signal + // handlers shall have C linkage. The behavior of any function other than + // a POF used as a signal handler in a C++ program is + // implementation-defined. + // """ + struct sigaction action; + action.sa_flags = SA_SIGINFO; + action.sa_sigaction = &signal_tracker_sig_handler; + sigemptyset(&action.sa_mask); + sigaction(SIGCHLD, &action, NULL); + sigaction(SIGUSR1, &action, NULL); + sigaction(SIGUSR2, &action, NULL); +} + +signal_tracker &signal_tracker::instance() { + static signal_tracker _instance; + return _instance; +} + +void signal_tracker::set_signal(int sig) { + flags[sig].fetch_add(1, std::memory_order_relaxed); +} + +bool signal_tracker::has_signal(int sig) const { + return flags[sig].load(std::memory_order_relaxed) > 0; +} + +bool signal_tracker::try_consume_signal(int sig) { + uint32_t current = flags[sig].load(std::memory_order_relaxed); + while (current > 0) { + if (flags[sig].compare_exchange_weak(current, current - 1, + std::memory_order_release, + std::memory_order_relaxed)) { + return true; + } + } + return false; // No signal to consume +} \ No newline at end of file From 977b0a82d8e5ebf0a88d170fd1b0d25dee5f7cea Mon Sep 17 00:00:00 2001 From: Maxwell Pirtle Date: Sat, 11 May 2024 11:57:36 -0400 Subject: [PATCH 021/161] Implement state regeneration in coordinator This commit introduces all of the steps necessary in state regeneration on the model side. Some work remains to accomplish the equivalent on the real-world side --- docs/design/CMakeLists.txt | 2 + .../coordinator/model_to_system_map.hpp | 5 +- docs/design/include/mcmini/mem.h | 13 ++- .../include/mcmini/misc/append-only.hpp | 6 ++ .../include/mcmini/model/objects/mutex.hpp | 2 +- .../include/mcmini/model/objects/thread.hpp | 9 +- .../mcmini/model/pending_transitions.hpp | 7 ++ docs/design/include/mcmini/model/program.hpp | 54 ++++++------ .../mcmini/model/state/state_sequence.hpp | 10 +-- .../mcmini/model/transition_registry.hpp | 2 +- .../mcmini/model/transitions/process/exit.hpp | 23 ++++++ .../transitions/thread/thread_create.hpp | 30 +++++++ .../model/transitions/thread/thread_start.hpp | 7 +- .../model/transitions/transition_sequence.hpp | 18 ++-- .../real_world/process/template_process.h | 6 +- docs/design/src/common/mem.c | 6 +- docs/design/src/examples/hello-world.c | 4 +- docs/design/src/libmcmini/entry.c | 16 ++-- docs/design/src/libmcmini/wrappers.c | 13 ++- .../src/mcmini/coordinator/coordinator.cpp | 41 +++++++--- docs/design/src/mcmini/mcmini.cpp | 52 +++++++++--- docs/design/src/mcmini/model/program.cpp | 82 ++++++++++++++++++- .../src/mcmini/model/state_sequence.cpp | 12 ++- .../src/mcmini/model/transition_sequence.cpp | 14 ++++ .../algorithms/classic_dpor.cpp | 1 + .../mcmini/real_world/fork_process_source.cpp | 43 +++++++--- 26 files changed, 360 insertions(+), 118 deletions(-) create mode 100644 docs/design/include/mcmini/model/transitions/process/exit.hpp create mode 100644 docs/design/include/mcmini/model/transitions/thread/thread_create.hpp create mode 100644 docs/design/src/mcmini/model/transition_sequence.cpp diff --git a/docs/design/CMakeLists.txt b/docs/design/CMakeLists.txt index 7b2a6cc9..d5582910 100644 --- a/docs/design/CMakeLists.txt +++ b/docs/design/CMakeLists.txt @@ -19,6 +19,7 @@ set(MCMINI_CMAKE_MODULE_DIR "${CMAKE_SOURCE_DIR}/cmake") # Project source files set(MCMINI_C_SRC + src/common/mem.c src/common/runner_mailbox.c src/common/shm_config.c ) @@ -30,6 +31,7 @@ set(MCMINI_CPP_SRC src/mcmini/model/detached_state.cpp src/mcmini/model/program.cpp src/mcmini/model/state_sequence.cpp + src/mcmini/model/transition_sequence.cpp src/mcmini/model_checking/algorithms/classic_dpor.cpp src/mcmini/real_world/fork_process_source.cpp src/mcmini/real_world/local_linux_process.cpp diff --git a/docs/design/include/mcmini/coordinator/model_to_system_map.hpp b/docs/design/include/mcmini/coordinator/model_to_system_map.hpp index a80c3117..dfcae2d0 100644 --- a/docs/design/include/mcmini/coordinator/model_to_system_map.hpp +++ b/docs/design/include/mcmini/coordinator/model_to_system_map.hpp @@ -43,7 +43,7 @@ struct model_to_system_map final { /** * @brief Record the presence of a new visible object that is - * represented with the system id `system_handle`. + * represented with the system id. * * @param remote_process_visible_object_handle the address containing * the data for the new visible object across process handles of the @@ -58,5 +58,6 @@ struct model_to_system_map final { std::unique_ptr); model::state::objid_t observe_remote_process_runner( real_world::remote_address, - std::unique_ptr); + std::unique_ptr, + std::unique_ptr); }; diff --git a/docs/design/include/mcmini/mem.h b/docs/design/include/mcmini/mem.h index fa02a28e..0d2154ca 100644 --- a/docs/design/include/mcmini/mem.h +++ b/docs/design/include/mcmini/mem.h @@ -1,7 +1,14 @@ #pragma once #include +#ifdef __cplusplus +extern "C" { +#endif + // memcpy implementation but with volatile memory -volatile void *memset_v(volatile void *restrict dst, int ch, size_t n); -volatile void *memcpy_v(volatile void *restrict dst, - const volatile void *restrict src, size_t n); \ No newline at end of file +volatile void *memset_v(volatile void *, int ch, size_t n); +volatile void *memcpy_v(volatile void *, const volatile void *, size_t n); + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/docs/design/include/mcmini/misc/append-only.hpp b/docs/design/include/mcmini/misc/append-only.hpp index 325f3066..533ca28a 100644 --- a/docs/design/include/mcmini/misc/append-only.hpp +++ b/docs/design/include/mcmini/misc/append-only.hpp @@ -36,6 +36,12 @@ struct append_only { const_reference at(size_t i) const { return contents.at(i); } iterator begin() { return this->contents.begin(); } iterator end() { return this->contents.end(); } + iterator erase(iterator first, iterator last) { + return this->contents.erase(first, last); + } + iterator erase(const_iterator first, const_iterator last) { + return this->contents.erase(first, last); + } const_iterator begin() const { return this->contents.cbegin(); } const_iterator end() const { return this->contents.cend(); } const_iterator cbegin() const { return this->contents.cbegin(); } diff --git a/docs/design/include/mcmini/model/objects/mutex.hpp b/docs/design/include/mcmini/model/objects/mutex.hpp index ea9246ba..56260ba2 100644 --- a/docs/design/include/mcmini/model/objects/mutex.hpp +++ b/docs/design/include/mcmini/model/objects/mutex.hpp @@ -19,7 +19,7 @@ struct mutex : public model::visible_object_state { ~mutex() = default; mutex(const mutex &) = default; mutex(state_type state) : current_state(state) {} - static std::unique_ptr make(state_type state) { + static std::unique_ptr make(state_type state = uninitialized) { return extensions::make_unique(state); } diff --git a/docs/design/include/mcmini/model/objects/thread.hpp b/docs/design/include/mcmini/model/objects/thread.hpp index 74801f39..cfaced62 100644 --- a/docs/design/include/mcmini/model/objects/thread.hpp +++ b/docs/design/include/mcmini/model/objects/thread.hpp @@ -11,20 +11,19 @@ namespace objects { struct thread : public model::visible_object_state { public: /* The four possible states for a mutex */ - enum state_type { embryo, running, exited, killed }; + enum state { embryo, running, exited, killed }; private: - state_type current_state = state_type::embryo; + state current_state = state::embryo; public: thread() = default; ~thread() = default; thread(const thread &) = default; - thread(state_type state) : current_state(state) {} - static std::unique_ptr make(state_type state) { + thread(state state) : current_state(state) {} + static std::unique_ptr make(state state = embryo) { return extensions::make_unique(state); } - static std::unique_ptr make() { return thread::make(embryo); } // ---- State Observation --- // bool operator==(const thread &other) const { diff --git a/docs/design/include/mcmini/model/pending_transitions.hpp b/docs/design/include/mcmini/model/pending_transitions.hpp index 38db92ea..f31263d9 100644 --- a/docs/design/include/mcmini/model/pending_transitions.hpp +++ b/docs/design/include/mcmini/model/pending_transitions.hpp @@ -32,6 +32,7 @@ struct pending_transitions final { return _contents.cbegin(); } auto cend() -> decltype(_contents.cend()) const { return _contents.cend(); } + size_t size() const { return this->_contents.size(); } /** * @brief Returns the transition mapped to id `id`, or `nullptr` if no such * runner has been mapped to an id. @@ -47,6 +48,12 @@ struct pending_transitions final { std::unique_ptr displace_transition_for( runner_id_t id, std::unique_ptr new_transition) { + if (id != new_transition->get_executor()) { + throw std::runtime_error( + "Attempting to insert a transition executed by a different runner (" + + std::to_string(id) + + " != " + std::to_string(new_transition->get_executor()) + ")"); + } auto old_transition = std::move(_contents[id]); _contents[id] = std::move(new_transition); return old_transition; diff --git a/docs/design/include/mcmini/model/program.hpp b/docs/design/include/mcmini/model/program.hpp index e229e160..11949c15 100644 --- a/docs/design/include/mcmini/model/program.hpp +++ b/docs/design/include/mcmini/model/program.hpp @@ -48,7 +48,6 @@ class program { state_sequence state_seq; transition_sequence trace; pending_transitions next_steps; - friend model_to_system_map; public: using runner_id_t = uint32_t; @@ -61,34 +60,33 @@ class program { const state_sequence &get_state_sequence() const { return this->state_seq; } const transition_sequence &get_trace() const { return this->trace; } - std::unordered_set get_enabled_runners() const { - std::unordered_set enabled_runners; - for (const auto &runner_and_t : this->next_steps) { - if (runner_and_t.second->is_enabled_in(state_seq)) { - enabled_runners.insert(runner_and_t.first); - } - } - return enabled_runners; - } + size_t get_num_runners() const { return next_steps.size(); } + std::unordered_set get_enabled_runners() const; - void model_executing_runner(runner_id_t p, - std::unique_ptr new_transition) { - if (p != new_transition->get_executor()) { - throw std::invalid_argument( - "Attempted to assign a transition to runner " + std::to_string(p) + - " with a different runner (" + - std::to_string(new_transition->get_executor()) + ")"); - } - const transition *next_s_p = next_steps.get_transition_for_runner(p); - if (next_s_p) { - this->state_seq.follow(*next_s_p); - this->next_steps.displace_transition_for(p, std::move(new_transition)); - } else { - throw std::runtime_error( - "Attempted to execute a runner whose transition was not currently " - "enabled"); - } - } + state::objid_t discover_object(std::unique_ptr); + state::runner_id_t discover_runner(std::unique_ptr, + std::unique_ptr); + + /// @brief Model the execution of runner `p` and replace its next operation + /// with `next_pending_operation`. + /// + /// @param p the id of the runner whose next transition should be simulated. + /// @param next_pending_operation the next transition this is pending after + /// `p` executes; that is, this is the transition that `p` will run in the + /// state modeled _after_ `next_s_p` is executed. + /// @throws an runtime exception is raised if the transition replacing + /// `next_s_p` is not executed by `p` or if `p` is not currently known to the + /// model. + void model_execution_of(runner_id_t p, + std::unique_ptr next_pending_operation); + + /// @brief Restore the model as if it were `n` steps into execution. + /// + /// @param n the number of transitions to consider executed to result in the + /// program model after the method has executed. Formally, if `t_0, t_1, ..., + /// t_(n-1), ..., t_k` is contained in the current trace, then the state after + /// this method is called will be `s_(n+1)` or `s_0` is `n = 0`. + void restore_model_at_depth(uint32_t n); }; // diff --git a/docs/design/include/mcmini/model/state/state_sequence.hpp b/docs/design/include/mcmini/model/state/state_sequence.hpp index bc064971..167539f4 100644 --- a/docs/design/include/mcmini/model/state/state_sequence.hpp +++ b/docs/design/include/mcmini/model/state/state_sequence.hpp @@ -96,13 +96,11 @@ class state_sequence : public detached_state { * longer valid after the sequence is consumed. All other views into the * sequence remain valid. * - * @param index the last index that should be contained in the returned - * subsequence. - * @return the resulting subsequence. The subsequence is identical to this - * sequence up to index `index`. Any objects which didn't exist prior to state - * `s_index` will not exist in the resulting sequence + * @param num_states the number of states that should be left in the sequence + * after consumption. If `num_states` is large than the number of states in + * this sequence, the method has no effect. */ - state_sequence consume_into_subsequence(size_t index) &&; + void consume_into_subsequence(size_t num_states); }; } // namespace model diff --git a/docs/design/include/mcmini/model/transition_registry.hpp b/docs/design/include/mcmini/model/transition_registry.hpp index 2987934a..b14b9c51 100644 --- a/docs/design/include/mcmini/model/transition_registry.hpp +++ b/docs/design/include/mcmini/model/transition_registry.hpp @@ -28,7 +28,7 @@ class transition_registry final { using runtime_type_id = uint32_t; using rttid = runtime_type_id; using transition_discovery_callback = std::unique_ptr (*)( - const volatile runner_mailbox&, model_to_system_map&); + state::runner_id_t, const volatile runner_mailbox&, model_to_system_map&); /** * @brief Marks the specified transition subclass as possible to encounter at diff --git a/docs/design/include/mcmini/model/transitions/process/exit.hpp b/docs/design/include/mcmini/model/transitions/process/exit.hpp new file mode 100644 index 00000000..bc5b0c03 --- /dev/null +++ b/docs/design/include/mcmini/model/transitions/process/exit.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include "mcmini/model/transition.hpp" + +namespace model { +namespace transitions { + +struct process_exit : public model::transition { + public: + process_exit(state::runner_id_t executor) : transition(executor) {} + ~process_exit() = default; + + status modify(model::mutable_state& s) const override { + // We ensure that exiting is never enabled. This ensures that it will never + // be explored by any model checking algorithm + return status::disabled; + } + + std::string to_string() const override { return "exit(2) (syscall)"; } +}; + +} // namespace transitions +} // namespace model \ No newline at end of file diff --git a/docs/design/include/mcmini/model/transitions/thread/thread_create.hpp b/docs/design/include/mcmini/model/transitions/thread/thread_create.hpp new file mode 100644 index 00000000..4fe227dd --- /dev/null +++ b/docs/design/include/mcmini/model/transitions/thread/thread_create.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include "mcmini/model/objects/thread.hpp" +#include "mcmini/model/transition.hpp" + +namespace model { +namespace transitions { + +struct thread_create : public model::transition { + private: + state::runner_id_t target; + + public: + thread_create(state::runner_id_t executor, state::runner_id_t target) + : target(target), transition(executor) {} + ~thread_create() = default; + + status modify(model::mutable_state& s) const override { + using namespace model::objects; + s.add_state_for_runner(target, thread::make(thread::running)); + return status::exists; + } + + std::string to_string() const override { + return "pthread_join(thread: " + std::to_string(target) + ")"; + } +}; + +} // namespace transitions +} // namespace model \ No newline at end of file diff --git a/docs/design/include/mcmini/model/transitions/thread/thread_start.hpp b/docs/design/include/mcmini/model/transitions/thread/thread_start.hpp index 105cdcd5..bee2526d 100644 --- a/docs/design/include/mcmini/model/transitions/thread/thread_start.hpp +++ b/docs/design/include/mcmini/model/transitions/thread/thread_start.hpp @@ -12,13 +12,10 @@ struct thread_start : public model::transition { ~thread_start() = default; status modify(model::mutable_state& s) const override { + // No modification necessary: we simply move into the next state using namespace model::objects; auto* thread_state = s.get_state_of_runner(executor); - if (!thread_state->is_embryo()) { - return status::disabled; - } - s.add_state_for_runner(executor, thread::make(thread::running)); - return status::exists; + return thread_state->is_embryo() ? status::disabled : status::exists; } std::string to_string() const override { return "starts"; } diff --git a/docs/design/include/mcmini/model/transitions/transition_sequence.hpp b/docs/design/include/mcmini/model/transitions/transition_sequence.hpp index d7b07513..b18acf3d 100644 --- a/docs/design/include/mcmini/model/transitions/transition_sequence.hpp +++ b/docs/design/include/mcmini/model/transitions/transition_sequence.hpp @@ -25,12 +25,20 @@ class transition_sequence final { public: transition_sequence() = default; - transition_sequence consume_into_subsequence(uint32_t index) &&; - bool empty() const; - size_t count() const; - const transition* at(size_t i); - void push(std::unique_ptr); + auto begin() -> decltype(contents.begin()) { return contents.begin(); } + auto end() -> decltype(contents.end()) { return contents.end(); } + auto begin() const -> decltype(contents.begin()) { return contents.begin(); } + auto end() const -> decltype(contents.end()) { return contents.end(); } + + bool empty() const { return contents.empty(); } + size_t count() const { return contents.size(); } + const transition* at(size_t i) const { return contents.at(i).get(); } + std::unique_ptr extract_at(size_t i); + void push(std::unique_ptr t) { + contents.push_back(std::move(t)); + } + void consume_into_subsequence(uint32_t depth); }; } // namespace model \ No newline at end of file diff --git a/docs/design/include/mcmini/real_world/process/template_process.h b/docs/design/include/mcmini/real_world/process/template_process.h index ff0201f0..a86ff599 100644 --- a/docs/design/include/mcmini/real_world/process/template_process.h +++ b/docs/design/include/mcmini/real_world/process/template_process.h @@ -6,7 +6,7 @@ extern "C" { #include -#define TEMPLATE_FORK_FAILED ((cpid)-2) // fork(2) failed in the template +#define TEMPLATE_FORK_FAILED ((cpid)-2) // fork(2) failed in the template struct template_process_t { // The current process id of the child process currently under control of this @@ -17,6 +17,10 @@ struct template_process_t { // the template process signals after writing the newly spawned pid to shared // memory. sem_t mcmini_process_sem; + + // A semphore that `libmcmini.so` waits on and that the McMini process signals + // when it wants to spawn a new process. + sem_t libmcmini_sem; }; #ifdef __cplusplus diff --git a/docs/design/src/common/mem.c b/docs/design/src/common/mem.c index c4435c17..104e5ae9 100644 --- a/docs/design/src/common/mem.c +++ b/docs/design/src/common/mem.c @@ -1,13 +1,13 @@ #include "mcmini/mem.h" -volatile void *memset_v(volatile void *restrict dst, int ch, size_t n) { +volatile void *memset_v(volatile void *dst, int ch, size_t n) { volatile unsigned char *dstc = dst; while ((n--) > 0) dstc[n] = ch; return dst; } -volatile void *memcpy_v(volatile void *restrict dst, - const volatile void *restrict src, size_t n) { +volatile void *memcpy_v(volatile void *dst, + const volatile void *src, size_t n) { // From cppreference on the use of restrict pointers in the C language: // // | Restricted pointers can be assigned to unrestricted pointers freely, the diff --git a/docs/design/src/examples/hello-world.c b/docs/design/src/examples/hello-world.c index 3a8c80fe..a63e1fe5 100644 --- a/docs/design/src/examples/hello-world.c +++ b/docs/design/src/examples/hello-world.c @@ -7,9 +7,9 @@ int main(int argc, char* argv[]) { pthread_mutex_t mut; pthread_mutex_init(&mut, NULL); - // pthread_mutex_lock(&mut); + pthread_mutex_lock(&mut); // printf("Hello world!\n"); - // pthread_mutex_unlock(&mut); + pthread_mutex_unlock(&mut); // pthread_mutex_destroy(&mut); return 0; } diff --git a/docs/design/src/libmcmini/entry.c b/docs/design/src/libmcmini/entry.c index f840e646..bfa6f802 100644 --- a/docs/design/src/libmcmini/entry.c +++ b/docs/design/src/libmcmini/entry.c @@ -90,23 +90,25 @@ mc_exit(int status) void mc_template_process_loop_forever() { volatile struct template_process_t *tpt = global_shm_start; - sigset_t sigurs1_set; - sigemptyset(&sigurs1_set); - sigaddset(&sigurs1_set, SIGUSR1); - while (1) { - int sig; - sigwait(&sigurs1_set, &sig); + sem_wait((sem_t*)&tpt->libmcmini_sem); pid_t cpid = fork(); if (cpid == -1) { // `fork()` failed + write(STDOUT_FILENO,"ENTERED2!", 10); + fsync(0); tpt->cpid = TEMPLATE_FORK_FAILED; } else if (cpid == 0) { // Child case: Simply return and escape into the child process. + write(STDOUT_FILENO,"ENTERED3!", 10); + fsync(0); return; } // `libmcmini.so` acting as a template process. + tpt->cpid = cpid; + write(STDOUT_FILENO,"ENTERED4!", 10); + fsync(0); sem_post((sem_t*)&tpt->mcmini_process_sem); } } @@ -125,7 +127,7 @@ __attribute__((constructor)) void libmcmini_main() { mc_allocate_shared_memory_region(); atexit(&mc_deallocate_shared_memory_region); - if (getenv("libmcmini-freeze") != NULL) { + if (getenv("libmcmini-template-loop") != NULL) { mc_template_process_loop_forever(); } thread_await_scheduler_for_thread_start_transition(); diff --git a/docs/design/src/libmcmini/wrappers.c b/docs/design/src/libmcmini/wrappers.c index 96a79efb..875dc685 100644 --- a/docs/design/src/libmcmini/wrappers.c +++ b/docs/design/src/libmcmini/wrappers.c @@ -20,24 +20,21 @@ void thread_await_scheduler_for_thread_start_transition() { assert(tid_self != TID_INVALID); - volatile runner_mailbox *thread_mailbox = thread_get_mailbox(); - mc_wait_for_scheduler(thread_mailbox); + mc_wait_for_scheduler(thread_get_mailbox()); } void thread_awake_scheduler_for_thread_finish_transition() { assert(tid_self != TID_INVALID); - volatile runner_mailbox *thread_mailbox = thread_get_mailbox(); - mc_wake_scheduler(thread_mailbox); + mc_wake_scheduler(thread_get_mailbox()); } int mc_pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr) { volatile runner_mailbox *mb = thread_get_mailbox(); mb->type = MUTEX_INIT_TYPE; - printf("YO\n"); - fsync(STDOUT_FILENO); + memcpy_v(mb->cnts, &mutex, sizeof(mutex)); thread_await_scheduler(); return 0; } @@ -45,8 +42,7 @@ int mc_pthread_mutex_init(pthread_mutex_t *mutex, int mc_pthread_mutex_lock(pthread_mutex_t *mutex) { volatile runner_mailbox *mb = thread_get_mailbox(); mb->type = MUTEX_LOCK_TYPE; - printf("YYA"); - fsync(STDOUT_FILENO); + memcpy_v(mb->cnts, &mutex, sizeof(mutex)); thread_await_scheduler(); return 0; } @@ -54,6 +50,7 @@ int mc_pthread_mutex_lock(pthread_mutex_t *mutex) { int mc_pthread_mutex_unlock(pthread_mutex_t *mutex) { volatile runner_mailbox *mb = thread_get_mailbox(); mb->type = MUTEX_UNLOCK_TYPE; + memcpy_v(mb->cnts, &mutex, sizeof(mutex)); thread_await_scheduler(); return 0; } \ No newline at end of file diff --git a/docs/design/src/mcmini/coordinator/coordinator.cpp b/docs/design/src/mcmini/coordinator/coordinator.cpp index 041728b4..b0df8804 100644 --- a/docs/design/src/mcmini/coordinator/coordinator.cpp +++ b/docs/design/src/mcmini/coordinator/coordinator.cpp @@ -1,5 +1,7 @@ #include "mcmini/coordinator/coordinator.hpp" +#include + #include "mcmini/real_world/process.hpp" using namespace real_world; @@ -37,14 +39,29 @@ void coordinator::execute_runner(process::runner_id_t runner_id) { "libmcmini.so with this message."); } model_to_system_map remote_address_mapping = model_to_system_map(*this); - auto pending_operation = callback_function(*mb, remote_address_mapping); + auto pending_operation = + callback_function(runner_id, *mb, remote_address_mapping); if (!pending_operation) { throw real_world::process::execution_exception( "Failed to translate the data written into the mailbox of runner " + std::to_string(runner_id)); } - this->current_program_model.model_executing_runner( - runner_id, std::move(pending_operation)); + this->current_program_model.model_execution_of(runner_id, + std::move(pending_operation)); +} + +void coordinator::return_to_depth(uint32_t n) { + this->current_program_model.restore_model_at_depth(n); + this->current_process_handle = this->process_source->force_new_process(); + + // Now regenerate the process from scratch. The new process handle has a state + // represented in the model as the state at depth `n = 0` in the state + // sequence; hence to ensure the model and the real world correspond, we must + // re-execute the threads in the order specified in the transition sequence. + assert(this->current_program_model.get_trace().count() == n); + for (const auto &t : this->current_program_model.get_trace()) { + this->current_process_handle->execute_runner(t->get_executor()); + } } model::state::objid_t model_to_system_map::get_object_for_remote_process_handle( @@ -65,7 +82,7 @@ model::state::objid_t model_to_system_map::observe_remote_process_handle( return existing_obj; } else { model::state::objid_t new_objid = - _coordinator.current_program_model.state_seq.add_object( + _coordinator.current_program_model.discover_object( std::move(fallback_initial_state)); _coordinator.system_address_mapping.insert( {remote_process_visible_object_handle, new_objid}); @@ -75,7 +92,8 @@ model::state::objid_t model_to_system_map::observe_remote_process_handle( model::state::objid_t model_to_system_map::observe_remote_process_runner( real_world::remote_address remote_process_visible_object_handle, - std::unique_ptr fallback_initial_state) { + std::unique_ptr fallback_initial_state, + std::unique_ptr fallback_initial_transition) { model::state::objid_t existing_obj = this->get_object_for_remote_process_handle( remote_process_visible_object_handle); @@ -83,11 +101,14 @@ model::state::objid_t model_to_system_map::observe_remote_process_runner( return existing_obj; } else { model::state::runner_id_t new_runner_id = - _coordinator.current_program_model.state_seq.add_runner( - std::move(fallback_initial_state)); - model::state::objid_t new_objid = - _coordinator.current_program_model.get_state_sequence() - .get_objid_for_runner(new_runner_id); + _coordinator.current_program_model.discover_runner( + std::move(fallback_initial_state), + std::move(fallback_initial_transition)); + + model::state::objid_t new_objid = _coordinator.get_current_program_model() + .get_state_sequence() + .get_objid_for_runner(new_runner_id); + _coordinator.system_address_mapping.insert( {remote_process_visible_object_handle, new_objid}); return new_objid; diff --git a/docs/design/src/mcmini/mcmini.cpp b/docs/design/src/mcmini/mcmini.cpp index a2a78f37..c2254363 100644 --- a/docs/design/src/mcmini/mcmini.cpp +++ b/docs/design/src/mcmini/mcmini.cpp @@ -1,11 +1,14 @@ #include "mcmini/mcmini.hpp" #include "mcmini/coordinator/coordinator.hpp" +#include "mcmini/mem.h" #include "mcmini/misc/ddt.hpp" #include "mcmini/misc/extensions/unique_ptr.hpp" #include "mcmini/misc/volatile_mem_streambuf.hpp" #include "mcmini/model/state/detached_state.hpp" #include "mcmini/model/transitions/mutex/mutex_init.hpp" +#include "mcmini/model/transitions/mutex/mutex_lock.hpp" +#include "mcmini/model/transitions/mutex/mutex_unlock.hpp" #include "mcmini/model/transitions/thread/thread_start.hpp" #include "mcmini/model_checking/algorithms/classic_dpor.hpp" #include "mcmini/real_world/process/fork_process_source.hpp" @@ -22,29 +25,56 @@ #include #include +using namespace extensions; +using namespace model; +using namespace real_world; + void display_usage() { std::cout << "mcmini [options] " << std::endl; std::exit(EXIT_FAILURE); } -std::unique_ptr test_callback( - const volatile runner_mailbox& rms, model_to_system_map& msm) { - return extensions::make_unique(0); +std::unique_ptr mutex_init_callback( + state::runner_id_t p, const volatile runner_mailbox& rmb, + model_to_system_map& m) { + pthread_mutex_t* remote_mut; + memcpy_v(&remote_mut, (volatile void*)rmb.cnts, sizeof(pthread_mutex_t*)); + + // how do we get the runner??? + state::objid_t mut = + m.observe_remote_process_handle(remote_mut, objects::mutex::make()); + return make_unique(p, mut); +} + +std::unique_ptr mutex_lock_callback( + state::runner_id_t p, const volatile runner_mailbox& rmb, + model_to_system_map& m) { + pthread_mutex_t* remote_mut; + memcpy_v(&remote_mut, (volatile void*)rmb.cnts, sizeof(pthread_mutex_t*)); + state::objid_t mut = + m.observe_remote_process_handle(remote_mut, objects::mutex::make()); + return make_unique(p, mut); +} + +std::unique_ptr mutex_unlock_callback( + state::runner_id_t p, const volatile runner_mailbox& rmb, + model_to_system_map& m) { + pthread_mutex_t* remote_mut; + memcpy_v(&remote_mut, (volatile void*)rmb.cnts, sizeof(pthread_mutex_t*)); + state::objid_t mut = + m.observe_remote_process_handle(remote_mut, objects::mutex::make()); + return make_unique(p, mut); } void do_model_checking( /* Pass arguments here or rearrange to configure the checker at runtime, e.g. to pick an algorithm, set a max depth, etc. */) { - using namespace extensions; - using namespace model; - using namespace real_world; - state_sequence state_of_program_at_main; pending_transitions initial_first_steps; transition_registry tr; - state::runner_id_t main_thread_id = - state_of_program_at_main.add_runner(model::objects::thread::make()); + state::runner_id_t main_thread_id = state_of_program_at_main.add_runner( + objects::thread::make(objects::thread::state::running)); initial_first_steps.displace_transition_for( 0, make_unique(main_thread_id)); @@ -56,7 +86,9 @@ void do_model_checking( // beginning). auto process_source = make_unique("hello-world"); - tr.register_transition(&test_callback); + tr.register_transition(&mutex_init_callback); + tr.register_transition(&mutex_lock_callback); + tr.register_transition(&mutex_unlock_callback); coordinator coordinator(std::move(model_for_program_starting_at_main), std::move(tr), std::move(process_source)); diff --git a/docs/design/src/mcmini/model/program.cpp b/docs/design/src/mcmini/model/program.cpp index c4a8a848..db92545b 100644 --- a/docs/design/src/mcmini/model/program.cpp +++ b/docs/design/src/mcmini/model/program.cpp @@ -4,4 +4,84 @@ using namespace model; program::program(const state &initial_state, pending_transitions &&initial_first_steps) - : next_steps(std::move(initial_first_steps)), state_seq(initial_state) {} \ No newline at end of file + : next_steps(std::move(initial_first_steps)), state_seq(initial_state) {} + +std::unordered_set program::get_enabled_runners() const { + std::unordered_set enabled_runners; + for (const auto &runner_and_t : this->next_steps) { + if (runner_and_t.second->is_enabled_in(state_seq)) { + enabled_runners.insert(runner_and_t.first); + } + } + return enabled_runners; +} + +void program::restore_model_at_depth(uint32_t n) { + /* + * Fill in the set of next transitions by + * following the trace from the top to `n` since the trace implicitly holds + * what each thread *was* doing next at each point in time. + * + * We can simply keep track of the _smallest_ index in the trace *greater than + * `n`* for each thread. This must be the most recent transition + * that that was pending for the thread at depth `n` + * + * For threads that didn't run after depth _depth_, the latest transition in + * `next_steps` is already correct and we don't need to do anything. + * + * @note It's possible that some of these threads will be + * in the embryo state at depth `n`; those threads should consequently be + * executing the `thread_start()` transition, but this will be in the trace + */ + std::unordered_map runner_to_index_from_top; + for (int32_t i = (trace.count() - 1); i >= (int32_t)(n); i--) + runner_to_index_from_top[trace.at(i)->get_executor()] = i; + + for (const std::pair &e : runner_to_index_from_top) + next_steps.displace_transition_for(e.first, trace.extract_at(e.second)); + + state_seq.consume_into_subsequence(n); + trace.consume_into_subsequence(n); +} + +void program::model_execution_of(runner_id_t p, + std::unique_ptr new_transition) { + if (p != new_transition->get_executor()) { + throw std::runtime_error( + "The next incoming transition replacing `next_s_p` in the model must " + "be run by the same runner (" + + std::to_string(p) + + " != " + std::to_string(new_transition->get_executor()) + ")"); + } + + const transition *next_s_p = next_steps.get_transition_for_runner(p); + if (!next_s_p) { + // This is the first time we've seen `p`; treat `new_transition` as the + // first `next_s_p`. + throw std::runtime_error( + "Attempting to execute a runner whose next transition is unknown to " + "the model. If this runner was dynamically created in the model (e.g. " + "in the callback routine for handling `pthread_create()`), ensure that " + "the "); + } + + transition::status status = this->state_seq.follow(*next_s_p); + if (status == transition::status::disabled) { + throw std::runtime_error( + "Attempted to model the execution of a disabled transition."); + } + trace.push(next_steps.displace_transition_for(p, std::move(new_transition))); +} + +state::objid_t program::discover_object( + std::unique_ptr initial_state) { + return this->state_seq.add_object(std::move(initial_state)); +} + +state::runner_id_t program::discover_runner( + std::unique_ptr initial_state, + std::unique_ptr initial_transition) { + state::runner_id_t id = this->state_seq.add_runner(std::move(initial_state)); + this->next_steps.displace_transition_for(id, std::move(initial_transition)); + return id; +} \ No newline at end of file diff --git a/docs/design/src/mcmini/model/state_sequence.cpp b/docs/design/src/mcmini/model/state_sequence.cpp index 9ac07b2b..b29f5849 100644 --- a/docs/design/src/mcmini/model/state_sequence.cpp +++ b/docs/design/src/mcmini/model/state_sequence.cpp @@ -117,13 +117,11 @@ std::unique_ptr state_sequence::mutable_clone() const { this->visible_objects.cbegin(), this->visible_objects.cend()); } -state_sequence state_sequence::consume_into_subsequence(size_t index) && { - auto elements = append_only( - this->states_in_sequence.begin(), - this->states_in_sequence.begin() + index + 1); - auto ss = state_sequence(std::move(this->visible_objects)); - ss.states_in_sequence = std::move(elements); - return ss; +void state_sequence::consume_into_subsequence(size_t num_states) { + if (num_states <= this->states_in_sequence.size()) + this->states_in_sequence.erase( + this->states_in_sequence.begin() + num_states, + this->states_in_sequence.end()); } size_t state_sequence::count() const { diff --git a/docs/design/src/mcmini/model/transition_sequence.cpp b/docs/design/src/mcmini/model/transition_sequence.cpp new file mode 100644 index 00000000..ce0a707b --- /dev/null +++ b/docs/design/src/mcmini/model/transition_sequence.cpp @@ -0,0 +1,14 @@ +#include "mcmini/model/transitions/transition_sequence.hpp" + +using namespace model; + +void transition_sequence::consume_into_subsequence(uint32_t depth) { + // For depths greater than the size of the sequence + if (depth <= contents.size()) { + contents.erase(contents.begin() + depth, contents.end()); + } +} + +std::unique_ptr transition_sequence::extract_at(size_t i) { + return std::move(this->contents.at(i)); +} \ No newline at end of file diff --git a/docs/design/src/mcmini/model_checking/algorithms/classic_dpor.cpp b/docs/design/src/mcmini/model_checking/algorithms/classic_dpor.cpp index 3f29b23a..7b0cb5fd 100644 --- a/docs/design/src/mcmini/model_checking/algorithms/classic_dpor.cpp +++ b/docs/design/src/mcmini/model_checking/algorithms/classic_dpor.cpp @@ -42,6 +42,7 @@ void classic_dpor::verify_using(coordinator &coordinator, try { coordinator.execute_runner(0); coordinator.execute_runner(0); + coordinator.return_to_depth(0); } catch (real_world::process::execution_exception &e) { std::cerr << "Error: " << e.what() << std::endl; } diff --git a/docs/design/src/mcmini/real_world/fork_process_source.cpp b/docs/design/src/mcmini/real_world/fork_process_source.cpp index 75436e2e..3702d0af 100644 --- a/docs/design/src/mcmini/real_world/fork_process_source.cpp +++ b/docs/design/src/mcmini/real_world/fork_process_source.cpp @@ -43,19 +43,19 @@ fork_process_source::fork_process_source(std::string target_program) } std::unique_ptr fork_process_source::make_new_process() { + // 1. Set up phase (LD_PRELOAD, binary sempahores, template process creation) setup_ld_preload(); reset_binary_semaphores_for_new_process(); if (!has_template_process_alive()) make_new_template_process(); + // 2. Check if the current template process has previously exited; if so, it + // would have delivered a `SIGCHLD` to this process. By default this signal is + // ignored, but McMini explicitly captures it (see `signal_tracker`). if (signal_tracker::instance().try_consume_signal(SIGCHLD)) { this->template_pid = fork_process_source::no_template; - - // The template process exited unexpectedly: we can no longer create a new - // process from it. `waitpid()` ensures that the child's resources are - // properly reacquired. if (waitpid(this->template_pid, nullptr, 0) == -1) { throw process_source::process_creation_exception( - "Failed to create a cleanup zombied child process (waitpid " + "Failed to create a cleanup zombied child process (waitpid(2) " "returned -1): " + std::string(strerror(errno))); } @@ -63,14 +63,24 @@ std::unique_ptr fork_process_source::make_new_process() { "Failed to create a new process (template process died)"); } + // 3. If the current template process is alive, tell it to spawn a new + // process and then wait for it to successfully call `fork(2)` to tell us + // about its new child. const volatile template_process_t* tstruct = rw_region->as(); - int rc = sem_wait((sem_t*)&tstruct->mcmini_process_sem); - if (rc != 0 && errno == EINTR) { + if (sem_post((sem_t*)&tstruct->libmcmini_sem) != 0) { + throw process_source::process_creation_exception( + "The template process (" + std::to_string(template_pid) + + ") was not synchronized with correctly: " + + std::string(strerror(errno))); + } + + if (sem_wait((sem_t*)&tstruct->mcmini_process_sem) != 0) { throw process_source::process_creation_exception( - "The template process ( " + std::to_string(template_pid) + - ") was not able to complete"); + "The template process (" + std::to_string(template_pid) + + ") was not synchronized with correctly: " + + std::string(strerror(errno))); } return extensions::make_unique(tstruct->cpid, @@ -83,6 +93,13 @@ void fork_process_source::make_new_template_process() { // isn't one. this->template_pid = fork_process_source::no_template; + { + const volatile template_process_t* tstruct = + rw_region->as(); + sem_init((sem_t*)&tstruct->mcmini_process_sem, SEM_FLAG_SHARED, 0); + sem_init((sem_t*)&tstruct->libmcmini_sem, SEM_FLAG_SHARED, 0); + } + int pipefd[2]; if (pipe(pipefd) == -1) { throw std::runtime_error("Failed to open pipe(2): " + @@ -109,10 +126,10 @@ void fork_process_source::make_new_template_process() { // to interface with the C library routines char* args[] = {const_cast(this->target_program.c_str()), NULL /*TODO: Add additional arguments here if needed */}; - setenv("libmcmini-freeze", "1", 1); + setenv("libmcmini-template-loop", "1", 1); personality(ADDR_NO_RANDOMIZE); execvp(this->target_program.c_str(), args); - unsetenv("libmcmini-freeze"); + unsetenv("libmcmini-template-loop"); // If `execvp()` fails, we signal the error to the parent process by writing // into the pipe. @@ -136,12 +153,12 @@ void fork_process_source::make_new_template_process() { // waitpid() ensures that the child's resources are properly reacquired. if (waitpid(child_pid, nullptr, 0) == -1) { throw process_source::process_creation_exception( - "Failed to create a cleanup zombied child process (waitpid " + "Failed to create a cleanup zombied child process (waitpid(2) " "returned -1): " + std::string(strerror(errno))); } throw process_source::process_creation_exception( - "Failed to create a new process (execvp failed): " + + "Failed to create a new process (execvp(2) failed): " + std::string(strerror(err))); } close(pipefd[0]); From dd81d9a467063d8d053fe0351a91570924747474 Mon Sep 17 00:00:00 2001 From: Maxwell Pirtle Date: Sat, 11 May 2024 13:01:35 -0400 Subject: [PATCH 022/161] Handle main thread exiting the program As in the stable version of McMini, we handle the _main_ thread returning from the `main` function as an `atexit()` handler which block indefinitely. This ensures that McMini still has control over the process as a model checker --- docs/design/include/mcmini/defines.h | 11 +++++++++ .../include/mcmini/lib/shared_transition.h | 7 +++++- .../mcmini/spy/intercept/interception.h | 9 ++++++- .../include/mcmini/spy/intercept/wrappers.h | 8 +++++++ docs/design/src/libmcmini/entry.c | 11 ++++----- docs/design/src/libmcmini/interception.c | 16 +++++++++++++ docs/design/src/libmcmini/wrappers.c | 24 +++++++++++++++++++ .../mcmini/real_world/fork_process_source.cpp | 1 - docs/design/src/mcmini/real_world/shm.cpp | 10 ++++---- 9 files changed, 83 insertions(+), 14 deletions(-) diff --git a/docs/design/include/mcmini/defines.h b/docs/design/include/mcmini/defines.h index 57298758..84c7f0a2 100644 --- a/docs/design/include/mcmini/defines.h +++ b/docs/design/include/mcmini/defines.h @@ -6,7 +6,18 @@ #define MCMINI_LIBRARY_ENTRY_POINT #define MCMINI_EXPORT __attribute__((visibility(default))) #define MCMINI_PRIVATE __attribute__((visibility(hidden))) + +#ifdef __cplusplus +#define MCMINI_THREAD_LOCAL thread_local +#else #define MCMINI_THREAD_LOCAL _Thread_local +#endif + +#ifdef __cplusplus +#define MCMINI_NO_RETURN [[noreturn]] +#else +#define MCMINI_NO_RETURN __attribute__((__noreturn__)) +#endif #define MAX_TOTAL_TRANSITIONS_IN_PROGRAM (1500u) #define MAX_TOTAL_STATES_IN_STATE_STACK (MAX_TOTAL_TRANSITIONS_IN_PROGRAM + 1u) diff --git a/docs/design/include/mcmini/lib/shared_transition.h b/docs/design/include/mcmini/lib/shared_transition.h index 7bb8792f..d523e21d 100644 --- a/docs/design/include/mcmini/lib/shared_transition.h +++ b/docs/design/include/mcmini/lib/shared_transition.h @@ -6,7 +6,12 @@ typedef uint64_t tid_t; typedef enum { MUTEX_INIT_TYPE, MUTEX_LOCK_TYPE, - MUTEX_UNLOCK_TYPE + MUTEX_UNLOCK_TYPE, + + THREAD_CREATE_TYPE, + THREAD_JOIN_TYPE, + THREAD_EXIT_TYPE, + PROCESS_EXIT_TYPE // sem_init, // sem_wait, // sem_post, diff --git a/docs/design/include/mcmini/spy/intercept/interception.h b/docs/design/include/mcmini/spy/intercept/interception.h index 249ada34..b8dc2864 100644 --- a/docs/design/include/mcmini/spy/intercept/interception.h +++ b/docs/design/include/mcmini/spy/intercept/interception.h @@ -1,6 +1,8 @@ #pragma once #define _GNU_SOURCE + +#include "mcmini/defines.h" #include #include #include @@ -13,4 +15,9 @@ int pthread_mutex_init(pthread_mutex_t *mutex, int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex); -// TODO: Other wrappers here \ No newline at end of file + +void exit(int); +MCMINI_NO_RETURN void libc_exit(int); + +void abort(void); +MCMINI_NO_RETURN void libc_abort(void); \ No newline at end of file diff --git a/docs/design/include/mcmini/spy/intercept/wrappers.h b/docs/design/include/mcmini/spy/intercept/wrappers.h index 97c8a75e..4eca8b69 100644 --- a/docs/design/include/mcmini/spy/intercept/wrappers.h +++ b/docs/design/include/mcmini/spy/intercept/wrappers.h @@ -15,3 +15,11 @@ int mc_pthread_mutex_init(pthread_mutex_t *mutex, int mc_pthread_mutex_lock(pthread_mutex_t *mutex); int mc_pthread_mutex_unlock(pthread_mutex_t *mutex); +/* + An `atexit()` handler is installed in libmcmini.so with this function. + This ensures that if the main thread exits the model checker still maintains + control. +*/ +void mc_exit_main_thread(void); +MCMINI_NO_RETURN void mc_transparent_abort(); +MCMINI_NO_RETURN void mc_transparent_exit(int status); diff --git a/docs/design/src/libmcmini/entry.c b/docs/design/src/libmcmini/entry.c index bfa6f802..355e1c06 100644 --- a/docs/design/src/libmcmini/entry.c +++ b/docs/design/src/libmcmini/entry.c @@ -88,6 +88,10 @@ mc_exit(int status) _Exit(status); } +void mc_prepare_for_model_checking() { + atexit(&mc_exit_main_thread); +} + void mc_template_process_loop_forever() { volatile struct template_process_t *tpt = global_shm_start; while (1) { @@ -95,20 +99,15 @@ void mc_template_process_loop_forever() { pid_t cpid = fork(); if (cpid == -1) { // `fork()` failed - write(STDOUT_FILENO,"ENTERED2!", 10); - fsync(0); tpt->cpid = TEMPLATE_FORK_FAILED; } else if (cpid == 0) { // Child case: Simply return and escape into the child process. - write(STDOUT_FILENO,"ENTERED3!", 10); - fsync(0); + mc_prepare_for_model_checking(); return; } // `libmcmini.so` acting as a template process. tpt->cpid = cpid; - write(STDOUT_FILENO,"ENTERED4!", 10); - fsync(0); sem_post((sem_t*)&tpt->mcmini_process_sem); } } diff --git a/docs/design/src/libmcmini/interception.c b/docs/design/src/libmcmini/interception.c index af0c1ef9..0b8c037e 100644 --- a/docs/design/src/libmcmini/interception.c +++ b/docs/design/src/libmcmini/interception.c @@ -51,4 +51,20 @@ int pthread_mutex_lock(pthread_mutex_t *mutex) { int pthread_mutex_unlock(pthread_mutex_t *mutex) { return mc_pthread_mutex_unlock(mutex); +} + +void exit(int status) { + mc_transparent_exit(status); +} + +void abort(void) { + mc_transparent_abort(); +} + +MCMINI_NO_RETURN void libc_abort(void) { + (*abort_ptr)(); +} + +MCMINI_NO_RETURN void libc_exit(int status) { + (*exit_ptr)(status); } \ No newline at end of file diff --git a/docs/design/src/libmcmini/wrappers.c b/docs/design/src/libmcmini/wrappers.c index 875dc685..09b817dc 100644 --- a/docs/design/src/libmcmini/wrappers.c +++ b/docs/design/src/libmcmini/wrappers.c @@ -53,4 +53,28 @@ int mc_pthread_mutex_unlock(pthread_mutex_t *mutex) { memcpy_v(mb->cnts, &mutex, sizeof(mutex)); thread_await_scheduler(); return 0; +} + +void +mc_exit_main_thread(void) +{ + thread_get_mailbox()->type = THREAD_EXIT_TYPE; + thread_await_scheduler(); +} + +MCMINI_NO_RETURN void +mc_transparent_exit(int status) +{ + volatile runner_mailbox *mb = thread_get_mailbox(); + mb->type = PROCESS_EXIT_TYPE; + memcpy_v(mb->cnts, &status, sizeof(status)); + thread_await_scheduler(); + libc_exit(status); +} + +MCMINI_NO_RETURN void +mc_transparent_abort(void) +{ + thread_await_scheduler(); + libc_abort(); } \ No newline at end of file diff --git a/docs/design/src/mcmini/real_world/fork_process_source.cpp b/docs/design/src/mcmini/real_world/fork_process_source.cpp index 3702d0af..7cadcf66 100644 --- a/docs/design/src/mcmini/real_world/fork_process_source.cpp +++ b/docs/design/src/mcmini/real_world/fork_process_source.cpp @@ -145,7 +145,6 @@ void fork_process_source::make_new_template_process() { // ******************* // Parent process case // ******************* - close(pipefd[1]); // Close write end int err = 0; diff --git a/docs/design/src/mcmini/real_world/shm.cpp b/docs/design/src/mcmini/real_world/shm.cpp index deb71793..5cd0664b 100644 --- a/docs/design/src/mcmini/real_world/shm.cpp +++ b/docs/design/src/mcmini/real_world/shm.cpp @@ -48,11 +48,7 @@ shared_memory_region::~shared_memory_region() { // only concerned with the actual address value and will not attempt to access // the _contents_ of `shm_mmap_region` without the volatile qualification // (such an access is undefined behavior) - int rc = munmap(const_cast(shm_mmap_region), size()); - if (rc == -1) { - std::perror("munmap"); - } - rc = shm_unlink(shm_file_name.c_str()); + int rc = shm_unlink(shm_file_name.c_str()); if (rc == -1) { if (errno == EACCES) { std::fprintf(stderr, @@ -63,4 +59,8 @@ shared_memory_region::~shared_memory_region() { std::perror("shm_unlink"); } } + rc = munmap(const_cast(shm_mmap_region), size()); + if (rc == -1) { + std::perror("munmap"); + } } From ee142c74b1952dc7c3b0294cabc4fdcfee8eadf6 Mon Sep 17 00:00:00 2001 From: Maxwell Pirtle Date: Sat, 11 May 2024 13:03:33 -0400 Subject: [PATCH 023/161] Replace `exit()` with `quick_exit()` after forking C++ destroys global static objects upon the program exiting normally, i.e. with `exit(2)`. `fork_process_source` produces new processes by `fork(2)`-ing the McMini process and attempting to `execvp(2)` into some target process to create a template. This is all fine when the execvp() succeeds; however, on failure, we need be careful. Since we forked the C++ McMini process, if we simply call `exit(2)` in the child process, this will invoke the destructors for the C++ global static objects _which reside in the address space of the child_. This is unexpected though: the child in-between should not perform any clean up (that's the job of the parent) and is only designed to immediately exec() or else report an error via its pipe. The solution is to use `quick_exit()` in this temporary child which does not perform the normal clean up --- .../src/mcmini/real_world/fork_process_source.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/design/src/mcmini/real_world/fork_process_source.cpp b/docs/design/src/mcmini/real_world/fork_process_source.cpp index 7cadcf66..422c49e4 100644 --- a/docs/design/src/mcmini/real_world/fork_process_source.cpp +++ b/docs/design/src/mcmini/real_world/fork_process_source.cpp @@ -136,7 +136,18 @@ void fork_process_source::make_new_template_process() { int err = errno; write(pipefd[1], &err, sizeof(err)); close(pipefd[1]); - exit(EXIT_FAILURE); + + // @note: We invoke `quick_exit()` here to ensure that C++ static + // objects are NOT destroyed. `std::exit()` will invoke the destructors + // of such static objects. This is only intended to happen exactly once + // however; bad things likely would happen to a program which called the + // destructor on an object that already cleaned up its resources. + // + // We must remember that this child is in a completely separate process with + // a completely separate address space, but the shared resources that the + // McMini process holds onto will also (inadvertantly) be shared with the + // child. To get C++ to play nicely, this is how we do it. + std::quick_exit(EXIT_FAILURE); // ****************** // Child process case // ****************** From 580c7c13addbb5900ca91f64e6544f00eab657b7 Mon Sep 17 00:00:00 2001 From: Maxwell Pirtle Date: Sat, 11 May 2024 15:17:23 -0400 Subject: [PATCH 024/161] Ignore SIG_IGN to SIGCHLD in libmcmini This commit adds a `sigaction(2)` call to ignore the SIGCHLD signal sent to the template process. This means that the zombies of the children will not be left around. Handling segfaults and other issues will be a little more difficult though, as the children of the template are NOT children of the McMini process that contains the model --- .../process/fork_process_source.hpp | 1 + docs/design/src/libmcmini/entry.c | 87 ++++++++++--------- docs/design/src/libmcmini/interception.c | 16 +--- docs/design/src/libmcmini/wrappers.c | 30 +++---- .../algorithms/classic_dpor.cpp | 8 ++ .../mcmini/real_world/fork_process_source.cpp | 28 +++++- .../mcmini/real_world/local_linux_process.cpp | 15 +--- 7 files changed, 102 insertions(+), 83 deletions(-) diff --git a/docs/design/include/mcmini/real_world/process/fork_process_source.hpp b/docs/design/include/mcmini/real_world/process/fork_process_source.hpp index 24a57779..738871b6 100644 --- a/docs/design/include/mcmini/real_world/process/fork_process_source.hpp +++ b/docs/design/include/mcmini/real_world/process/fork_process_source.hpp @@ -48,6 +48,7 @@ class fork_process_source : public process_source { public: fork_process_source(std::string target_program); + ~fork_process_source(); std::unique_ptr make_new_process() override; }; diff --git a/docs/design/src/libmcmini/entry.c b/docs/design/src/libmcmini/entry.c index 355e1c06..ccef3fbf 100644 --- a/docs/design/src/libmcmini/entry.c +++ b/docs/design/src/libmcmini/entry.c @@ -1,43 +1,38 @@ #define _POSIX_C_SOURCE #define _GNU_SOURCE +#include +#include +#include #include +#include #include #include #include -#include #include -#include -#include -#include -#include -#include -#include #include +#include +#include +#include #include "mcmini/mcmini.h" volatile void *global_shm_start = NULL; MCMINI_THREAD_LOCAL tid_t tid_self = TID_INVALID; -tid_t -mc_register_this_thread() -{ +tid_t mc_register_this_thread(void) { static tid_t tid_next = 0; tid_self = tid_next++; return tid_self; } -void -mc_allocate_shared_memory_region() -{ +void mc_allocate_shared_memory_region(void) { char dpor[100]; mc_get_shm_handle_name(dpor, sizeof(dpor)); int fd = shm_open(dpor, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); if (fd == -1) { if (errno == EACCES) { - fprintf(stderr, - "Shared memory region '%s' not owned by this process\n", + fprintf(stderr, "Shared memory region '%s' not owned by this process\n", dpor); } else { perror("shm_open"); @@ -50,8 +45,7 @@ mc_allocate_shared_memory_region() mc_exit(EXIT_FAILURE); } - void *gshms = - mmap(NULL, shm_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + void *gshms = mmap(NULL, shm_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (gshms == MAP_FAILED) { perror("mmap"); mc_exit(EXIT_FAILURE); @@ -63,23 +57,19 @@ mc_allocate_shared_memory_region() global_shm_start = gshms; } -void -mc_deallocate_shared_memory_region() -{ +void mc_deallocate_shared_memory_region() { char shm_file_name[100]; mc_get_shm_handle_name(shm_file_name, sizeof(shm_file_name)); if (global_shm_start) { - int rc = munmap((void*)global_shm_start, shm_size); - if (rc == -1) { - perror("munmap"); - mc_exit(EXIT_FAILURE); - } + int rc = munmap((void *)global_shm_start, shm_size); + if (rc == -1) { + perror("munmap"); + mc_exit(EXIT_FAILURE); + } } } -void -mc_exit(int status) -{ +void mc_exit(int status) { // The exit() function is intercepted. Calling exit() directly // results in a deadlock since the thread calling it will block // forever (McMini does not let a process exit() during model @@ -88,39 +78,58 @@ mc_exit(int status) _Exit(status); } -void mc_prepare_for_model_checking() { - atexit(&mc_exit_main_thread); -} - -void mc_template_process_loop_forever() { +void mc_template_process_loop_forever(void) { volatile struct template_process_t *tpt = global_shm_start; while (1) { - sem_wait((sem_t*)&tpt->libmcmini_sem); + sem_wait((sem_t *)&tpt->libmcmini_sem); pid_t cpid = fork(); if (cpid == -1) { // `fork()` failed tpt->cpid = TEMPLATE_FORK_FAILED; - } - else if (cpid == 0) { + } else if (cpid == 0) { // Child case: Simply return and escape into the child process. - mc_prepare_for_model_checking(); + + // This is important to handle the case when the + // main thread hits return 0; in that case, we + // keep the process alive to allow the model checker to + // continue working + // + // NOTE: `atexit()`-handlers can be invoked when a dynamic + // library is unloaded. When we integrate DMTCP, we may need + // to consider this. + atexit(&mc_exit_main_thread); return; } // `libmcmini.so` acting as a template process. tpt->cpid = cpid; - sem_post((sem_t*)&tpt->mcmini_process_sem); + sem_post((sem_t *)&tpt->mcmini_process_sem); } } -void mc_prevent_addr_randomization() { +void mc_prevent_addr_randomization(void) { if (personality(ADDR_NO_RANDOMIZE) == -1) { perror("personality"); mc_exit(EXIT_FAILURE); } } +void mc_install_sig_handlers(void) { + // Ignore SIGCHLD signals delivered to the template. + // This prevents zombie processes from being left + // around without the `waitpid()` + // + // TODO: Determine how to handle the cases with a child + // that exits unexpectedly, e.g. from a SEGFAULT. In + // this case, we must handle SIGCHLD + struct sigaction action; + action.sa_handler = SIG_IGN; + sigemptyset(&action.sa_mask); + sigaction(SIGCHLD, &action, NULL); +} + __attribute__((constructor)) void libmcmini_main() { mc_prevent_addr_randomization(); + mc_install_sig_handlers(); mc_register_this_thread(); mc_load_intercepted_pthread_functions(); mc_allocate_shared_memory_region(); diff --git a/docs/design/src/libmcmini/interception.c b/docs/design/src/libmcmini/interception.c index 0b8c037e..81776541 100644 --- a/docs/design/src/libmcmini/interception.c +++ b/docs/design/src/libmcmini/interception.c @@ -53,18 +53,10 @@ int pthread_mutex_unlock(pthread_mutex_t *mutex) { return mc_pthread_mutex_unlock(mutex); } -void exit(int status) { - mc_transparent_exit(status); -} +void exit(int status) { mc_transparent_exit(status); } -void abort(void) { - mc_transparent_abort(); -} +void abort(void) { mc_transparent_abort(); } -MCMINI_NO_RETURN void libc_abort(void) { - (*abort_ptr)(); -} +MCMINI_NO_RETURN void libc_abort(void) { (*abort_ptr)(); } -MCMINI_NO_RETURN void libc_exit(int status) { - (*exit_ptr)(status); -} \ No newline at end of file +MCMINI_NO_RETURN void libc_exit(int status) { (*exit_ptr)(status); } \ No newline at end of file diff --git a/docs/design/src/libmcmini/wrappers.c b/docs/design/src/libmcmini/wrappers.c index 09b817dc..0ba0797e 100644 --- a/docs/design/src/libmcmini/wrappers.c +++ b/docs/design/src/libmcmini/wrappers.c @@ -1,31 +1,27 @@ +#include #include #include -#include + #include "mcmini/mcmini.h" volatile runner_mailbox *thread_get_mailbox() { - return ((volatile runner_mailbox*)(global_shm_start + THREAD_SHM_OFFSET)) + tid_self; + return ((volatile runner_mailbox *)(global_shm_start + THREAD_SHM_OFFSET)) + + tid_self; } -void -thread_await_scheduler() -{ +void thread_await_scheduler() { assert(tid_self != TID_INVALID); volatile runner_mailbox *thread_mailbox = thread_get_mailbox(); mc_wake_scheduler(thread_mailbox); mc_wait_for_scheduler(thread_mailbox); } -void -thread_await_scheduler_for_thread_start_transition() -{ +void thread_await_scheduler_for_thread_start_transition() { assert(tid_self != TID_INVALID); mc_wait_for_scheduler(thread_get_mailbox()); } -void -thread_awake_scheduler_for_thread_finish_transition() -{ +void thread_awake_scheduler_for_thread_finish_transition() { assert(tid_self != TID_INVALID); mc_wake_scheduler(thread_get_mailbox()); } @@ -55,16 +51,12 @@ int mc_pthread_mutex_unlock(pthread_mutex_t *mutex) { return 0; } -void -mc_exit_main_thread(void) -{ +void mc_exit_main_thread(void) { thread_get_mailbox()->type = THREAD_EXIT_TYPE; thread_await_scheduler(); } -MCMINI_NO_RETURN void -mc_transparent_exit(int status) -{ +MCMINI_NO_RETURN void mc_transparent_exit(int status) { volatile runner_mailbox *mb = thread_get_mailbox(); mb->type = PROCESS_EXIT_TYPE; memcpy_v(mb->cnts, &status, sizeof(status)); @@ -72,9 +64,7 @@ mc_transparent_exit(int status) libc_exit(status); } -MCMINI_NO_RETURN void -mc_transparent_abort(void) -{ +MCMINI_NO_RETURN void mc_transparent_abort(void) { thread_await_scheduler(); libc_abort(); } \ No newline at end of file diff --git a/docs/design/src/mcmini/model_checking/algorithms/classic_dpor.cpp b/docs/design/src/mcmini/model_checking/algorithms/classic_dpor.cpp index 7b0cb5fd..db5fe99d 100644 --- a/docs/design/src/mcmini/model_checking/algorithms/classic_dpor.cpp +++ b/docs/design/src/mcmini/model_checking/algorithms/classic_dpor.cpp @@ -43,6 +43,14 @@ void classic_dpor::verify_using(coordinator &coordinator, coordinator.execute_runner(0); coordinator.execute_runner(0); coordinator.return_to_depth(0); + + coordinator.execute_runner(0); + coordinator.execute_runner(0); + coordinator.return_to_depth(0); + + coordinator.execute_runner(0); + coordinator.execute_runner(0); + coordinator.return_to_depth(0); } catch (real_world::process::execution_exception &e) { std::cerr << "Error: " << e.what() << std::endl; } diff --git a/docs/design/src/mcmini/real_world/fork_process_source.cpp b/docs/design/src/mcmini/real_world/fork_process_source.cpp index 422c49e4..0b521ea6 100644 --- a/docs/design/src/mcmini/real_world/fork_process_source.cpp +++ b/docs/design/src/mcmini/real_world/fork_process_source.cpp @@ -52,13 +52,14 @@ std::unique_ptr fork_process_source::make_new_process() { // would have delivered a `SIGCHLD` to this process. By default this signal is // ignored, but McMini explicitly captures it (see `signal_tracker`). if (signal_tracker::instance().try_consume_signal(SIGCHLD)) { - this->template_pid = fork_process_source::no_template; if (waitpid(this->template_pid, nullptr, 0) == -1) { + this->template_pid = fork_process_source::no_template; throw process_source::process_creation_exception( "Failed to create a cleanup zombied child process (waitpid(2) " "returned -1): " + std::string(strerror(errno))); } + this->template_pid = fork_process_source::no_template; throw process_source::process_creation_exception( "Failed to create a new process (template process died)"); } @@ -204,4 +205,29 @@ void fork_process_source::reset_binary_semaphores_for_new_process() { mc_runner_mailbox_destroy(mbp + i); mc_runner_mailbox_init(mbp + i); } +} + +fork_process_source::~fork_process_source() { + if (template_pid <= 0) { + return; + } + if (kill(template_pid, SIGUSR1) == -1) { + std::cerr << "Error sending SIGUSR1 to process " << template_pid << ": " + << strerror(errno) << std::endl; + } + + int status; + if (waitpid(template_pid, &status, 0) == -1) { + std::cerr << "Error waiting for process (fork) " << template_pid << ": " + << strerror(errno) << std::endl; + } else if (!WIFEXITED(status)) { + // TODO: Log + + // std::cerr << "Process " << template_pid << " did not exit normally." + // << std::endl; + // if (WIFSIGNALED(status)) { + // std::cerr << "Process " << template_pid << " was terminated by signal " + // << WTERMSIG(status) << std::endl; + // } + } } \ No newline at end of file diff --git a/docs/design/src/mcmini/real_world/local_linux_process.cpp b/docs/design/src/mcmini/real_world/local_linux_process.cpp index c08db694..785380c6 100644 --- a/docs/design/src/mcmini/real_world/local_linux_process.cpp +++ b/docs/design/src/mcmini/real_world/local_linux_process.cpp @@ -28,17 +28,10 @@ local_linux_process::~local_linux_process() { << strerror(errno) << std::endl; } - int status; - if (waitpid(pid, &status, 0) == -1) { - std::cerr << "Error waiting for process " << pid << ": " << strerror(errno) - << std::endl; - } else if (!WIFEXITED(status)) { - std::cerr << "Process " << pid << " did not exit normally." << std::endl; - if (WIFSIGNALED(status)) { - std::cerr << "Process " << pid << " was terminated by signal " - << WTERMSIG(status) << std::endl; - } - } + // NOTE: The process `pid` is NOT a child of this process: it + // is a child of the template process (it is a grandchild of this + // process); hence, `waitpid()` is not an appropriate call and should occur + // instead in the `libmcmini.so` template process } volatile runner_mailbox *local_linux_process::execute_runner(runner_id_t id) { From b37de21930da545eace8fc9096d9d19bcf4747ae Mon Sep 17 00:00:00 2001 From: Maxwell Pirtle Date: Sun, 12 May 2024 15:54:38 -0400 Subject: [PATCH 025/161] Add multi-threaded `exit(2)` call Apparently exit(2) is not safe to call from multiple threads. To avoid this, we simply wrap a `pthread_mutex_t` around the call in a call to `exit_mt()` --- docs/design/CMakeLists.txt | 2 ++ docs/design/include/mcmini/common/exit.h | 11 +++++++++++ docs/design/src/common/exit.c | 11 +++++++++++ 3 files changed, 24 insertions(+) create mode 100644 docs/design/include/mcmini/common/exit.h create mode 100644 docs/design/src/common/exit.c diff --git a/docs/design/CMakeLists.txt b/docs/design/CMakeLists.txt index d5582910..c24bbd53 100644 --- a/docs/design/CMakeLists.txt +++ b/docs/design/CMakeLists.txt @@ -19,6 +19,7 @@ set(MCMINI_CMAKE_MODULE_DIR "${CMAKE_SOURCE_DIR}/cmake") # Project source files set(MCMINI_C_SRC + src/common/exit.c src/common/mem.c src/common/runner_mailbox.c src/common/shm_config.c @@ -38,6 +39,7 @@ set(MCMINI_CPP_SRC src/mcmini/real_world/shm.cpp ) set(LIBMCMINI_C_SRC + src/common/exit.c src/common/mem.c src/common/runner_mailbox.c src/common/shm_config.c diff --git a/docs/design/include/mcmini/common/exit.h b/docs/design/include/mcmini/common/exit.h new file mode 100644 index 00000000..79a73fe7 --- /dev/null +++ b/docs/design/include/mcmini/common/exit.h @@ -0,0 +1,11 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +void exit_mt(int status); + +#ifdef __cplusplus +} +#endif // extern "C" \ No newline at end of file diff --git a/docs/design/src/common/exit.c b/docs/design/src/common/exit.c new file mode 100644 index 00000000..1164183f --- /dev/null +++ b/docs/design/src/common/exit.c @@ -0,0 +1,11 @@ +#include "mcmini/common/exit.h" + +#include +#include + +void exit_mt(int status) { + static pthread_mutex_t exit_mut = PTHREAD_MUTEX_INITIALIZER; + pthread_mutex_lock(&exit_mut); + exit(status); + pthread_mutex_unlock(&exit_mut); +} \ No newline at end of file From 35991526ca687becfc263e736dd141d7934b0e87 Mon Sep 17 00:00:00 2001 From: Maxwell Pirtle Date: Sun, 12 May 2024 15:59:03 -0400 Subject: [PATCH 026/161] Add the ability to raise an exception is a signal is captured --- docs/design/include/mcmini/signal.hpp | 7 ++++ .../algorithms/classic_dpor.cpp | 19 +++++----- docs/design/src/mcmini/signal.cpp | 35 +++++++++++++++++++ 3 files changed, 50 insertions(+), 11 deletions(-) diff --git a/docs/design/include/mcmini/signal.hpp b/docs/design/include/mcmini/signal.hpp index 7cea2537..5223defd 100644 --- a/docs/design/include/mcmini/signal.hpp +++ b/docs/design/include/mcmini/signal.hpp @@ -2,7 +2,12 @@ #include #include +#include #include +#include + +using signo_t = int; +extern const std::unordered_map sig_to_str; struct signal_tracker { private: @@ -15,7 +20,9 @@ struct signal_tracker { std::atomic_uint32_t flags[MAX_SIGNAL_TYPES] = {}; public: + struct interrupted_error; static signal_tracker &instance(); + static void throw_if_received(int sig); void set_signal(int sig); bool has_signal(int sig) const; bool try_consume_signal(int sig); diff --git a/docs/design/src/mcmini/model_checking/algorithms/classic_dpor.cpp b/docs/design/src/mcmini/model_checking/algorithms/classic_dpor.cpp index db5fe99d..ea37da25 100644 --- a/docs/design/src/mcmini/model_checking/algorithms/classic_dpor.cpp +++ b/docs/design/src/mcmini/model_checking/algorithms/classic_dpor.cpp @@ -7,6 +7,7 @@ #include #include "mcmini/model/program.hpp" +#include "mcmini/signal.hpp" using namespace model_checking; using namespace model; @@ -40,17 +41,13 @@ void classic_dpor::verify_using(coordinator &coordinator, // Execution may fail... we should raise an exception in these cases try { - coordinator.execute_runner(0); - coordinator.execute_runner(0); - coordinator.return_to_depth(0); - - coordinator.execute_runner(0); - coordinator.execute_runner(0); - coordinator.return_to_depth(0); - - coordinator.execute_runner(0); - coordinator.execute_runner(0); - coordinator.return_to_depth(0); + for (int i = 0; i < 5; i++) { + std::cout << "TEST" << i << std::endl; + coordinator.execute_runner(0); + coordinator.execute_runner(0); + coordinator.return_to_depth(0); + signal_tracker::throw_if_received(SIGINT); + } } catch (real_world::process::execution_exception &e) { std::cerr << "Error: " << e.what() << std::endl; } diff --git a/docs/design/src/mcmini/signal.cpp b/docs/design/src/mcmini/signal.cpp index a1690c3d..e967f761 100644 --- a/docs/design/src/mcmini/signal.cpp +++ b/docs/design/src/mcmini/signal.cpp @@ -1,5 +1,12 @@ #include "mcmini/signal.hpp" +const std::unordered_map sig_to_str = { + {SIGINT, "SIGINT"}, + {SIGCHLD, "SIGCHLD"}, + {SIGKILL, "SIGKILL"}, + {SIGUSR1, "SIGUSR1"}, + {SIGUSR2, "SIGUSR2"}}; + void signal_tracker_sig_handler(int sig, siginfo_t *, void *) { signal_tracker::instance().set_signal(sig); } @@ -28,6 +35,7 @@ void install_process_wide_signal_handlers() { action.sa_sigaction = &signal_tracker_sig_handler; sigemptyset(&action.sa_mask); sigaction(SIGCHLD, &action, NULL); + sigaction(SIGINT, &action, NULL); sigaction(SIGUSR1, &action, NULL); sigaction(SIGUSR2, &action, NULL); } @@ -55,4 +63,31 @@ bool signal_tracker::try_consume_signal(int sig) { } } return false; // No signal to consume +} + +//*** Interrupted Error ***// + +struct signal_tracker::interrupted_error : public std::exception { + private: + std::string msg; + signo_t sig; + + public: + explicit interrupted_error(signo_t sig) : sig(sig) { + msg = "Interrupted with signal " + std::to_string(sig); + if (sig_to_str.count(sig) != 0) { + msg += " ("; + msg += sig_to_str.find(sig)->second; + msg += ")"; + } else { + msg += " (unknown signal name)"; + } + } + const char *what() const noexcept override { return msg.c_str(); } +}; + +void signal_tracker::throw_if_received(int sig) { + if (instance().try_consume_signal(sig)) { + throw interrupted_error(sig); + } } \ No newline at end of file From 643607b59d42dbfc07f2b3176e10083e01cceea9 Mon Sep 17 00:00:00 2001 From: Maxwell Pirtle Date: Sun, 12 May 2024 16:02:14 -0400 Subject: [PATCH 027/161] Fix subtle issue with `kill(2)` delay to grandchild process This commit resolves a subtle issue with the way the architecture changes to McMini affected the plumbing details in `libmcmini.so`. When `libmcmini.so` is in the "template" mode, it is signaled by the `McMini` process to create a new child process. When McMini no longer needs the child process, it will now send a signal to the child process the template created previously by calling `fork(2)`. However, signal delivery is _asynchronous_, which means that exiting from the clean up in the destructor of `local_linux_process` does _not_ necessarily mean that the process itself has died, even though the destructor calls `kill(2)`. To resolve the issue, we effectively clump together the action of "forking the template to produce a new child process" with "waiting for the previous child process to terminate." This is accomplished with a call to `wait(2)` right before the `sem_wait(2)` call in `mc_template_process_loop_forever()`. This has the advantage that the C++ code in the McMini process is already designed to treat the creation of a new child as asynchronous. --- docs/design/include/mcmini/forwards.hpp | 11 +++++--- .../process/fork_process_source.hpp | 15 ++++++++++- docs/design/src/libmcmini/entry.c | 17 +++++-------- .../src/mcmini/coordinator/coordinator.cpp | 2 ++ .../mcmini/real_world/fork_process_source.cpp | 25 +++++++++++-------- .../mcmini/real_world/local_linux_process.cpp | 4 ++- 6 files changed, 47 insertions(+), 27 deletions(-) diff --git a/docs/design/include/mcmini/forwards.hpp b/docs/design/include/mcmini/forwards.hpp index b4448241..0e1d22d7 100644 --- a/docs/design/include/mcmini/forwards.hpp +++ b/docs/design/include/mcmini/forwards.hpp @@ -1,5 +1,8 @@ #pragma once +class coordinator; +class model_to_system_map; + namespace model { class state; class mutable_state; @@ -8,7 +11,7 @@ class state_sequence; class transition; } // namespace model -class coordinator; -class model_to_system_map; - -// TODO: Place all forward declarations here +namespace real_world { +class local_linux_process; +class fork_process_source; +} // namespace real_world \ No newline at end of file diff --git a/docs/design/include/mcmini/real_world/process/fork_process_source.hpp b/docs/design/include/mcmini/real_world/process/fork_process_source.hpp index 738871b6..5e2c644b 100644 --- a/docs/design/include/mcmini/real_world/process/fork_process_source.hpp +++ b/docs/design/include/mcmini/real_world/process/fork_process_source.hpp @@ -1,8 +1,10 @@ #pragma once +#include #include #include "mcmini/defines.h" +#include "mcmini/forwards.hpp" #include "mcmini/misc/volatile_mem_streambuf.hpp" #include "mcmini/real_world/process_source.hpp" #include "mcmini/real_world/shm.hpp" @@ -36,15 +38,26 @@ class fork_process_source : public process_source { pid_t template_pid = no_template; constexpr static pid_t no_template = -1; + /// @brief The number of processes that have been created by any process + /// sources + /// + /// @invariant The number of processes that are actively in-flight is + /// always be <= 1. + /// + /// @note the value is atomic in the event that `fork_process_source` is used + /// by multiple threads. The destructors of `local_linux_process` may + /// concurrently access this value along with the `make_new_process()` method. + static std::atomic_uint32_t num_children_in_flight; static std::unique_ptr rw_region; + static void initialize_shared_memory(); void setup_ld_preload(); void reset_binary_semaphores_for_new_process(); void make_new_template_process(); void template_process_sig_handler(); - bool has_template_process_alive() const { return template_pid != -1; } + friend local_linux_process; public: fork_process_source(std::string target_program); diff --git a/docs/design/src/libmcmini/entry.c b/docs/design/src/libmcmini/entry.c index ccef3fbf..a2d18a2d 100644 --- a/docs/design/src/libmcmini/entry.c +++ b/docs/design/src/libmcmini/entry.c @@ -13,6 +13,8 @@ #include #include #include +#include +#include #include "mcmini/mcmini.h" @@ -81,6 +83,9 @@ void mc_exit(int status) { void mc_template_process_loop_forever(void) { volatile struct template_process_t *tpt = global_shm_start; while (1) { + // Before creating any more children, ensure that the previous one has + // definitely stopped execution + wait(NULL); sem_wait((sem_t *)&tpt->libmcmini_sem); pid_t cpid = fork(); if (cpid == -1) { @@ -114,17 +119,7 @@ void mc_prevent_addr_randomization(void) { } void mc_install_sig_handlers(void) { - // Ignore SIGCHLD signals delivered to the template. - // This prevents zombie processes from being left - // around without the `waitpid()` - // - // TODO: Determine how to handle the cases with a child - // that exits unexpectedly, e.g. from a SEGFAULT. In - // this case, we must handle SIGCHLD - struct sigaction action; - action.sa_handler = SIG_IGN; - sigemptyset(&action.sa_mask); - sigaction(SIGCHLD, &action, NULL); + // TODO: Install signal handlers here for crashes in the child } __attribute__((constructor)) void libmcmini_main() { diff --git a/docs/design/src/mcmini/coordinator/coordinator.cpp b/docs/design/src/mcmini/coordinator/coordinator.cpp index b0df8804..c9ca9169 100644 --- a/docs/design/src/mcmini/coordinator/coordinator.cpp +++ b/docs/design/src/mcmini/coordinator/coordinator.cpp @@ -13,6 +13,7 @@ coordinator::coordinator( : current_program_model(std::move(initial_state)), runtime_transition_mapping(std::move(runtime_transition_mapping)), process_source(std::move(process_source)) { + this->current_process_handle = nullptr; this->current_process_handle = this->process_source->force_new_process(); } @@ -52,6 +53,7 @@ void coordinator::execute_runner(process::runner_id_t runner_id) { void coordinator::return_to_depth(uint32_t n) { this->current_program_model.restore_model_at_depth(n); + this->current_process_handle = nullptr; this->current_process_handle = this->process_source->force_new_process(); // Now regenerate the process from scratch. The new process handle has a state diff --git a/docs/design/src/mcmini/real_world/fork_process_source.cpp b/docs/design/src/mcmini/real_world/fork_process_source.cpp index 0b521ea6..5669b552 100644 --- a/docs/design/src/mcmini/real_world/fork_process_source.cpp +++ b/docs/design/src/mcmini/real_world/fork_process_source.cpp @@ -22,9 +22,12 @@ using namespace real_world; using namespace extensions; +std::atomic_uint32_t fork_process_source::num_children_in_flight; std::unique_ptr fork_process_source::rw_region = nullptr; void fork_process_source::initialize_shared_memory() { + fork_process_source::num_children_in_flight.store(0, + std::memory_order_relaxed); const std::string shm_file_name = "/mcmini-" + std::string(getenv("USER")) + "-" + std::to_string((long)getpid()); rw_region = make_unique(shm_file_name, shm_size); @@ -43,10 +46,19 @@ fork_process_source::fork_process_source(std::string target_program) } std::unique_ptr fork_process_source::make_new_process() { + // Assert: only a single child should be in-flight at any point + if (fork_process_source::num_children_in_flight.load( + std::memory_order_relaxed) >= 1) { + throw process_creation_exception( + "At most one active child process can be in flight at any given time."); + } + // 1. Set up phase (LD_PRELOAD, binary sempahores, template process creation) setup_ld_preload(); reset_binary_semaphores_for_new_process(); - if (!has_template_process_alive()) make_new_template_process(); + if (!has_template_process_alive()) { + make_new_template_process(); + } // 2. Check if the current template process has previously exited; if so, it // would have delivered a `SIGCHLD` to this process. By default this signal is @@ -84,6 +96,8 @@ std::unique_ptr fork_process_source::make_new_process() { std::string(strerror(errno))); } + fork_process_source::num_children_in_flight.fetch_add( + 1, std::memory_order_relaxed); return extensions::make_unique(tstruct->cpid, *rw_region); } @@ -220,14 +234,5 @@ fork_process_source::~fork_process_source() { if (waitpid(template_pid, &status, 0) == -1) { std::cerr << "Error waiting for process (fork) " << template_pid << ": " << strerror(errno) << std::endl; - } else if (!WIFEXITED(status)) { - // TODO: Log - - // std::cerr << "Process " << template_pid << " did not exit normally." - // << std::endl; - // if (WIFSIGNALED(status)) { - // std::cerr << "Process " << template_pid << " was terminated by signal " - // << WTERMSIG(status) << std::endl; - // } } } \ No newline at end of file diff --git a/docs/design/src/mcmini/real_world/local_linux_process.cpp b/docs/design/src/mcmini/real_world/local_linux_process.cpp index 785380c6..0179b04d 100644 --- a/docs/design/src/mcmini/real_world/local_linux_process.cpp +++ b/docs/design/src/mcmini/real_world/local_linux_process.cpp @@ -11,6 +11,7 @@ #include "mcmini/defines.h" #include "mcmini/misc/extensions/unique_ptr.hpp" #include "mcmini/real_world/mailbox/runner_mailbox.h" +#include "mcmini/real_world/process/fork_process_source.hpp" using namespace real_world; using namespace extensions; @@ -27,11 +28,12 @@ local_linux_process::~local_linux_process() { std::cerr << "Error sending SIGUSR1 to process " << pid << ": " << strerror(errno) << std::endl; } - // NOTE: The process `pid` is NOT a child of this process: it // is a child of the template process (it is a grandchild of this // process); hence, `waitpid()` is not an appropriate call and should occur // instead in the `libmcmini.so` template process + fork_process_source::num_children_in_flight.fetch_sub( + 1, std::memory_order_relaxed); } volatile runner_mailbox *local_linux_process::execute_runner(runner_id_t id) { From 2dbb0b53f346a2bc8e88daaf79126f820872a3bb Mon Sep 17 00:00:00 2001 From: Maxwell Pirtle Date: Sun, 12 May 2024 16:17:11 -0400 Subject: [PATCH 028/161] Handle `fork(2)` failiing in the template on the model side --- .../include/mcmini/real_world/process/template_process.h | 7 ++++++- docs/design/src/libmcmini/entry.c | 1 + docs/design/src/mcmini/real_world/fork_process_source.cpp | 7 +++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/docs/design/include/mcmini/real_world/process/template_process.h b/docs/design/include/mcmini/real_world/process/template_process.h index a86ff599..02eaa078 100644 --- a/docs/design/include/mcmini/real_world/process/template_process.h +++ b/docs/design/include/mcmini/real_world/process/template_process.h @@ -6,9 +6,14 @@ extern "C" { #include -#define TEMPLATE_FORK_FAILED ((cpid)-2) // fork(2) failed in the template +#define TEMPLATE_FORK_FAILED (-2) // fork(2) failed in the template struct template_process_t { + + // Unused in most cases, but set in the event of an error after + // calling a C function in the template process + int err; + // The current process id of the child process currently under control of this // template process. pid_t cpid; diff --git a/docs/design/src/libmcmini/entry.c b/docs/design/src/libmcmini/entry.c index a2d18a2d..bf208810 100644 --- a/docs/design/src/libmcmini/entry.c +++ b/docs/design/src/libmcmini/entry.c @@ -90,6 +90,7 @@ void mc_template_process_loop_forever(void) { pid_t cpid = fork(); if (cpid == -1) { // `fork()` failed + tpt->err = errno; tpt->cpid = TEMPLATE_FORK_FAILED; } else if (cpid == 0) { // Child case: Simply return and escape into the child process. diff --git a/docs/design/src/mcmini/real_world/fork_process_source.cpp b/docs/design/src/mcmini/real_world/fork_process_source.cpp index 5669b552..02c98672 100644 --- a/docs/design/src/mcmini/real_world/fork_process_source.cpp +++ b/docs/design/src/mcmini/real_world/fork_process_source.cpp @@ -96,6 +96,13 @@ std::unique_ptr fork_process_source::make_new_process() { std::string(strerror(errno))); } + if (tstruct->cpid == TEMPLATE_FORK_FAILED) { + throw process_source::process_creation_exception( + "The `fork(2)` call in the template process failed unexpectedly " + "(errno " + + std::to_string(tstruct->err) + "): " + strerror(tstruct->err)); + } + fork_process_source::num_children_in_flight.fetch_add( 1, std::memory_order_relaxed); return extensions::make_unique(tstruct->cpid, From 0e1cf7187a505bbd48e4eddc4a61b2694fb17098 Mon Sep 17 00:00:00 2001 From: Maxwell Pirtle Date: Sun, 12 May 2024 16:19:06 -0400 Subject: [PATCH 029/161] Remove current `lib` directory The `lib` directory contains old code while the `libmcmini` directory contains the new code. We will change the name in a subsequent commit --- docs/design/src/lib/entry.c | 150 ------------------------------- docs/design/src/lib/shared_sem.c | 35 -------- docs/design/src/lib/wrappers.c | 41 --------- 3 files changed, 226 deletions(-) delete mode 100644 docs/design/src/lib/entry.c delete mode 100644 docs/design/src/lib/shared_sem.c delete mode 100644 docs/design/src/lib/wrappers.c diff --git a/docs/design/src/lib/entry.c b/docs/design/src/lib/entry.c deleted file mode 100644 index c2c1e7eb..00000000 --- a/docs/design/src/lib/entry.c +++ /dev/null @@ -1,150 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "mcmini/shared_transition.h" -#include "mcmini/entry.h" - -#define MAX_SHARED_MEMORY_ALLOCATION (4096u) - -/* information to be searlized*/ -void *shmStart = NULL; -struct SharedTransition* shmTransitionTypeInfo = NULL; -void *shmTransitionData = NULL; -const size_t shmAllocationSize = sizeof(*trace_list) + (sizeof(*shmTransitionTypeInfo)+MAX_SHARED_MEMORY_ALLOCATION) * MAX_TOTAL_THREADS_IN_PROGRAM; - - -void -get_shm_handle_name(char *dst, size_t sz) -{ - snprintf(dst, sz, "/mcmini-%s-%lu", getenv("USER"), (long)getpid()); - dst[sz - 1] = '\0'; -} - -void * -allocate_shared_memory_region() -{ - // If the region exists, then this returns a fd for the existing - // region. Otherwise, it creates a new shared memory region. - char dpor[100]; - get_shm_handle_name(dpor, sizeof(dpor)); - - // This creates a file in /dev/shm/ - int fd = shm_open(dpor, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); - if (fd == -1) { - if (errno == EACCES) { - fprintf(stderr, - "Shared memory region '%s' not owned by this process\n", - dpor); - } else { - perror("shm_open"); - } - mc_exit(EXIT_FAILURE); - } - int rc = ftruncate(fd, shmAllocationSize); - if (rc == -1) { - perror("ftruncate"); - mc_exit(EXIT_FAILURE); - } - // We want stack at same address for each process. Otherwise, a - // pointer - // to an address in the stack data structure will not work - // TODO: Would the following suffice instead? - // - // static char large_region[1_000_000]; - // void *shm_map_addr = ; - // - // Or do we even need the same location anymore? - // - void *stack_address = (void *)0x4444000; - void *shmStart = - mmap(stack_address, shmAllocationSize, PROT_READ | PROT_WRITE, - MAP_SHARED | MAP_FIXED, fd, 0); - if (shmStart == MAP_FAILED) { - perror("mmap"); - mc_exit(EXIT_FAILURE); - } - // shm_unlink(dpor); // Don't unlink while child processes need to - // open this. - fsync(fd); - close(fd); - return shmStart; -} - -void -deallocate_shared_memory_region() -{ - // TODO: Deallocation - char shm_file_name[100]; - mc_get_shm_handle_name(shm_file_name, sizeof(shm_file_name)); - int rc = munmap(shmStart, shmAllocationSize); - if (rc == -1) { - perror("munmap"); - mc_exit(EXIT_FAILURE); - } - - rc = shm_unlink(shm_file_name); - if (rc == -1) { - if (errno == EACCES) { - fprintf(stderr, - "Shared memory region '%s' not owned by this process\n", - shm_file_name); - } else { - perror("shm_unlink"); - } - mc_exit(EXIT_FAILURE); - } -} - -void -initialize_shared_memory_globals() -{ - // TODO: We can do this just as in mcmini_private.hpp - void *shm = allocate_shared_memory_region(); - void *threadQueueStart = shm; - void *transitionTypeInfoStart = (char *)threadQueueStart + sizeof(*trace_list); - void *transitionDataStart = (char *)transitionTypeInfoStart + sizeof(*shmTransitionTypeInfo); - shmStart = shm; - trace_list = (shared_sem_ref)threadQueueStart; - shmTransitionTypeInfo = (struct SharedTransition *)transitionTypeInfoStart; - shmTransitionData = transitionDataStart; -} - -void intialize_trace_list() -{ - for (int i = 0; i < MAX_TOTAL_THREADS_IN_PROGRAM; i++) { - shared_sem_destroy(&(*trace_list)[i]); - shared_sem_init(&(*trace_list)[i]); - } -} - - -void -mc_exit(int status) -{ - // The exit() function is intercepted. Calling exit() directly - // results in a deadlock since the thread calling it will block - // forever (McMini does not let a process exit() during model - // checking). Keep this in mind before switching this call to - // a different exit function - _Exit(status); -} - -__attribute__((constructor)) void my_ctor() { - // Do something here with the constructor (e.g. dlsym preparation) - //load_intercepted_symbol_addresses(); a function wrappers.c - - /* Open shm file to discover sem_t region + read/write loc for McMini to read from etc*/ - initialize_shared_memory_globals(); - - //Discover the sem_t region and read/write loc - intialize_trace_list(); - -} \ No newline at end of file diff --git a/docs/design/src/lib/shared_sem.c b/docs/design/src/lib/shared_sem.c deleted file mode 100644 index 015c3d17..00000000 --- a/docs/design/src/lib/shared_sem.c +++ /dev/null @@ -1,35 +0,0 @@ -#include "mcmini/shared_sem.h" -#include "mcmini/wrappers.h" - -void shared_sem_init(shared_sem_ref ref) { - if (!ref) return; - __real_sem_init(&ref->runner_sem, 1, 0); - __real_sem_init(&ref->pthread_sem, 1, 0); -} - -void shared_sem_destroy(shared_sem_ref ref) { - if (!ref) return; - __real_sem_destroy(&ref->runner_sem); - __real_sem_destroy(&ref->pthread_sem); -} - -void shared_sem_post_for_thread(shared_sem_ref ref) { - if (!ref) return; - __real_sem_post(&ref->pthread_sem); -} - -void shared_sem_wait_for_thread(shared_sem_ref ref) { - if (!ref) return; - __real_sem_wait(&ref->pthread_sem); -} - -void shared_sem_post_for_runner(shared_sem_ref ref) { - if (!ref) return; - __real_sem_post(&ref->runner_sem); -} - -void shared_sem_wait_for_runner(shared_sem_ref ref) { - if (!ref) return; - __real_sem_wait(&ref->runner_sem); -} - diff --git a/docs/design/src/lib/wrappers.c b/docs/design/src/lib/wrappers.c deleted file mode 100644 index a45c411f..00000000 --- a/docs/design/src/lib/wrappers.c +++ /dev/null @@ -1,41 +0,0 @@ -#include -#include "mcmini/spy/intercept/interception.h" -#include "mcmini/shared_sem.h" -#include "mcmini/shared_transition.h" -#include "wrappers.h" -#include "mcmini/entry.h" -#include - -typedef uint64_t tid_t; -#define MC_THREAD_LOCAL thread_local -extern MC_THREAD_LOCAL tid_t tid_self; -#define TID_INVALID (-1ul) // ULONG_MAX -MC_THREAD_LOCAL tid_t tid_self = TID_INVALID; - - -int mc_pthread_mutex_init(pthread_mutex_t *mutex, - const pthread_mutexattr_t *mutexattr) { - printf("Hello from mc_pthread_mutext_init!"); - // TODO: write into the shm region enough information - // to determine what just happened on the model side - - // The coordinator first assumes data is written as follows: - // transition id followed by payload. - // - // TODO: There's no system in place to synchronize transition ids - // with the registration on the model side. This is a dynamic process - // (new transition can be added at runtime) - // For now, it suffices to assign a fixed value and just assume it - // corresponds on the model side - shared_sem_ref cv = &(*trace_list)[tid_self]; - struct MCMutexShadow newlyCreatedMutex; - initialize_MCMutexShadow(&newlyCreatedMutex, mutex); - auto newTypeInfo = SharedTransition(-1ul , MutexInit); - auto newShmData = newlyCreatedMutex; - extern struct SharedTransition* shmTransitionTypeInfo; -extern void *shmTransitionData; -memcpy(shmTransitionTypeInfo, newTypeInfo, sizeof(SharedTransition)); - -thread_await_scheduler(); -return __real_pthread_mutex_init(mutex, mutexattr); - } \ No newline at end of file From 9766a29025415871db36939539c2b56130517a5c Mon Sep 17 00:00:00 2001 From: Maxwell Pirtle Date: Sun, 12 May 2024 16:20:29 -0400 Subject: [PATCH 030/161] Move `libmcmini` to `lib` --- docs/design/CMakeLists.txt | 6 +++--- docs/design/src/{libmcmini => lib}/entry.c | 0 docs/design/src/{libmcmini => lib}/interception.c | 0 docs/design/src/{libmcmini => lib}/wrappers.c | 0 4 files changed, 3 insertions(+), 3 deletions(-) rename docs/design/src/{libmcmini => lib}/entry.c (100%) rename docs/design/src/{libmcmini => lib}/interception.c (100%) rename docs/design/src/{libmcmini => lib}/wrappers.c (100%) diff --git a/docs/design/CMakeLists.txt b/docs/design/CMakeLists.txt index c24bbd53..cd01881d 100644 --- a/docs/design/CMakeLists.txt +++ b/docs/design/CMakeLists.txt @@ -43,9 +43,9 @@ set(LIBMCMINI_C_SRC src/common/mem.c src/common/runner_mailbox.c src/common/shm_config.c - src/libmcmini/entry.c - src/libmcmini/interception.c - src/libmcmini/wrappers.c + src/lib/entry.c + src/lib/interception.c + src/lib/wrappers.c ) # -Wall -> be strict with warnings diff --git a/docs/design/src/libmcmini/entry.c b/docs/design/src/lib/entry.c similarity index 100% rename from docs/design/src/libmcmini/entry.c rename to docs/design/src/lib/entry.c diff --git a/docs/design/src/libmcmini/interception.c b/docs/design/src/lib/interception.c similarity index 100% rename from docs/design/src/libmcmini/interception.c rename to docs/design/src/lib/interception.c diff --git a/docs/design/src/libmcmini/wrappers.c b/docs/design/src/lib/wrappers.c similarity index 100% rename from docs/design/src/libmcmini/wrappers.c rename to docs/design/src/lib/wrappers.c From c45c77b52a045270a407ec01675e036e76f78c37 Mon Sep 17 00:00:00 2001 From: Maxwell Pirtle Date: Mon, 13 May 2024 12:09:25 -0400 Subject: [PATCH 031/161] Begin porting DPOR code from main branch This commit begins introducing the DPOR code from the main branch into the extended version. Things are a bit nicer, albeit still complex --- docs/design/CMakeLists.txt | 3 +- docs/design/include/mcmini/misc/ddt.hpp | 28 +- docs/design/include/mcmini/model/defines.hpp | 4 + .../mcmini/model/pending_transitions.hpp | 3 +- docs/design/include/mcmini/model/program.hpp | 9 +- docs/design/include/mcmini/model/state.hpp | 3 +- .../include/mcmini/model/transition.hpp | 3 +- .../model/transitions/transition_sequence.hpp | 2 + .../algorithms/classic_dpor.hpp | 35 ++ .../algorithms/classic_dpor/clock_vector.hpp | 115 +++++ .../algorithms/classic_dpor/runner_item.hpp | 30 ++ .../algorithms/classic_dpor/stack_item.hpp | 205 ++++++++ .../include/mcmini/real_world/process.hpp | 3 +- .../algorithms/classic_dpor.cpp | 446 +++++++++++------- .../algorithms/clock_vector.cpp | 15 + 15 files changed, 731 insertions(+), 173 deletions(-) create mode 100644 docs/design/include/mcmini/model/defines.hpp create mode 100644 docs/design/include/mcmini/model_checking/algorithms/classic_dpor/clock_vector.hpp create mode 100644 docs/design/include/mcmini/model_checking/algorithms/classic_dpor/runner_item.hpp create mode 100644 docs/design/include/mcmini/model_checking/algorithms/classic_dpor/stack_item.hpp create mode 100644 docs/design/src/mcmini/model_checking/algorithms/clock_vector.cpp diff --git a/docs/design/CMakeLists.txt b/docs/design/CMakeLists.txt index cd01881d..f3c3b627 100644 --- a/docs/design/CMakeLists.txt +++ b/docs/design/CMakeLists.txt @@ -34,6 +34,7 @@ set(MCMINI_CPP_SRC src/mcmini/model/state_sequence.cpp src/mcmini/model/transition_sequence.cpp src/mcmini/model_checking/algorithms/classic_dpor.cpp + src/mcmini/model_checking/algorithms/clock_vector.cpp src/mcmini/real_world/fork_process_source.cpp src/mcmini/real_world/local_linux_process.cpp src/mcmini/real_world/shm.cpp @@ -74,7 +75,7 @@ target_link_libraries(libmcmini PUBLIC "${LIBMCMINI_EXTRA_LINK_FLAGS}") -add_executable(mcmini "${MCMINI_CPP_SRC}""${MCMINI_C_SRC}") +add_executable(mcmini "${MCMINI_CPP_SRC}" "${MCMINI_C_SRC}") target_include_directories(mcmini PUBLIC "${MCMINI_INCLUDE_DIR}") # Compile examples diff --git a/docs/design/include/mcmini/misc/ddt.hpp b/docs/design/include/mcmini/misc/ddt.hpp index 151456ca..bae2f498 100644 --- a/docs/design/include/mcmini/misc/ddt.hpp +++ b/docs/design/include/mcmini/misc/ddt.hpp @@ -1,9 +1,11 @@ #pragma once +#include #include #include #include #include +#include #include "mcmini/misc/optional.hpp" @@ -99,18 +101,30 @@ struct double_dispatch_member_function_table call(InterfaceType* t1, InterfaceType* t2, - Args... args) { + ReturnType call_or(ReturnType fallback, InterfaceType* t1, InterfaceType* t2, + Args... args) const { auto t1_type = std::type_index(typeid(*t1)); auto t2_type = std::type_index(typeid(*t2)); if (_internal_table.count(t1_type) > 0) { - if (_internal_table[t1_type].count(t2_type) > 0) { - auto& pair = _internal_table[t1_type][t2_type]; - return optional( - pair.first(t1, t2, pair.second, std::forward(args)...)); + if (_internal_table.at(t1_type).count(t2_type) > 0) { + const auto& pair = _internal_table.at(t1_type).at(t2_type); + return pair.first(t1, t2, pair.second, std::forward(args)...); + } + } + return fallback; + } + + ReturnType call(InterfaceType* t1, InterfaceType* t2, Args... args) const { + auto t1_type = std::type_index(typeid(*t1)); + auto t2_type = std::type_index(typeid(*t2)); + if (_internal_table.count(t1_type) > 0) { + if (_internal_table.at(t1_type).count(t2_type) > 0) { + const auto& pair = _internal_table.at(t1_type).at(t2_type); + return pair.first(t1, t2, pair.second, std::forward(args)...); } } - return optional(); + throw std::runtime_error( + "Attempted to invoke a method but missing runtime entry"); } }; diff --git a/docs/design/include/mcmini/model/defines.hpp b/docs/design/include/mcmini/model/defines.hpp new file mode 100644 index 00000000..1993a641 --- /dev/null +++ b/docs/design/include/mcmini/model/defines.hpp @@ -0,0 +1,4 @@ +#pragma once +#include + +using runner_id_t = uint32_t; \ No newline at end of file diff --git a/docs/design/include/mcmini/model/pending_transitions.hpp b/docs/design/include/mcmini/model/pending_transitions.hpp index f31263d9..adac5c14 100644 --- a/docs/design/include/mcmini/model/pending_transitions.hpp +++ b/docs/design/include/mcmini/model/pending_transitions.hpp @@ -3,6 +3,7 @@ #include #include +#include "mcmini/model/defines.hpp" #include "mcmini/model/transition.hpp" namespace model { @@ -18,7 +19,7 @@ namespace model { */ struct pending_transitions final { private: - using runner_id_t = uint32_t; + using runner_id_t = ::runner_id_t; std::unordered_map> _contents; public: diff --git a/docs/design/include/mcmini/model/program.hpp b/docs/design/include/mcmini/model/program.hpp index 11949c15..6b81164f 100644 --- a/docs/design/include/mcmini/model/program.hpp +++ b/docs/design/include/mcmini/model/program.hpp @@ -2,6 +2,7 @@ #include +#include "mcmini/model/defines.hpp" #include "mcmini/model/pending_transitions.hpp" #include "mcmini/model/state/state_sequence.hpp" #include "mcmini/model/transitions/transition_sequence.hpp" @@ -50,7 +51,7 @@ class program { pending_transitions next_steps; public: - using runner_id_t = uint32_t; + using runner_id_t = ::runner_id_t; program(const state &initial_state, pending_transitions &&initial_first_steps); @@ -59,6 +60,12 @@ class program { const state_sequence &get_state_sequence() const { return this->state_seq; } const transition_sequence &get_trace() const { return this->trace; } + const pending_transitions &get_pending_transitions() const { + return this->next_steps; + } + const transition *get_pending_transition_for(runner_id_t rid) const { + return next_steps.get_transition_for_runner(rid); + } size_t get_num_runners() const { return next_steps.size(); } std::unordered_set get_enabled_runners() const; diff --git a/docs/design/include/mcmini/model/state.hpp b/docs/design/include/mcmini/model/state.hpp index 1c79d16f..308e7bf5 100644 --- a/docs/design/include/mcmini/model/state.hpp +++ b/docs/design/include/mcmini/model/state.hpp @@ -6,6 +6,7 @@ #include "mcmini/forwards.hpp" #include "mcmini/misc/asserts.hpp" +#include "mcmini/model/defines.hpp" #include "mcmini/model/visible_object.hpp" namespace model { @@ -16,7 +17,7 @@ namespace model { class state { public: using objid_t = uint32_t; - using runner_id_t = uint32_t; + using runner_id_t = ::runner_id_t; virtual ~state() = default; virtual size_t count() const = 0; diff --git a/docs/design/include/mcmini/model/transition.hpp b/docs/design/include/mcmini/model/transition.hpp index ec047d92..195a94fa 100644 --- a/docs/design/include/mcmini/model/transition.hpp +++ b/docs/design/include/mcmini/model/transition.hpp @@ -3,6 +3,7 @@ #include #include "mcmini/forwards.hpp" +#include "mcmini/model/defines.hpp" #include "mcmini/model/state.hpp" namespace model { @@ -63,7 +64,7 @@ namespace model { */ class transition { public: - using runner_id_t = uint32_t; + using runner_id_t = ::runner_id_t; transition(runner_id_t executor) : executor(executor) {} diff --git a/docs/design/include/mcmini/model/transitions/transition_sequence.hpp b/docs/design/include/mcmini/model/transitions/transition_sequence.hpp index b18acf3d..5e9a9186 100644 --- a/docs/design/include/mcmini/model/transitions/transition_sequence.hpp +++ b/docs/design/include/mcmini/model/transitions/transition_sequence.hpp @@ -24,6 +24,7 @@ class transition_sequence final { std::vector> contents; public: + using index = size_t; transition_sequence() = default; auto begin() -> decltype(contents.begin()) { return contents.begin(); } @@ -34,6 +35,7 @@ class transition_sequence final { bool empty() const { return contents.empty(); } size_t count() const { return contents.size(); } const transition* at(size_t i) const { return contents.at(i).get(); } + const transition* back() const { return contents.back().get(); } std::unique_ptr extract_at(size_t i); void push(std::unique_ptr t) { contents.push_back(std::move(t)); diff --git a/docs/design/include/mcmini/model_checking/algorithms/classic_dpor.hpp b/docs/design/include/mcmini/model_checking/algorithms/classic_dpor.hpp index fd1f0254..5fad8138 100644 --- a/docs/design/include/mcmini/model_checking/algorithms/classic_dpor.hpp +++ b/docs/design/include/mcmini/model_checking/algorithms/classic_dpor.hpp @@ -1,6 +1,9 @@ #pragma once +#include "mcmini/misc/ddt.hpp" #include "mcmini/model_checking/algorithm.hpp" +#include "mcmini/model_checking/algorithms/classic_dpor/runner_item.hpp" +#include "mcmini/model_checking/algorithms/classic_dpor/stack_item.hpp" namespace model_checking { @@ -16,6 +19,38 @@ class classic_dpor final : public algorithm { callbacks no_callbacks; this->verify_using(coordinator, no_callbacks); } + + private: + double_dispatch_member_function_table + dependency_relation; + + bool are_dependent(const model::transition &t1, + const model::transition &t2) const { + return t1.get_executor() == t2.get_executor() || + this->dependency_relation.call_or(true, &t1, &t2); + } + + bool are_independent(const model::transition &t1, + const model::transition &t2) const { + return !are_dependent(t1, t2); + } + + // Do not call these methods directly. They are implementation details of + // the DPOR algorithm and are called at specific points in time! + + clock_vector accumulate_max_clock_vector_against( + const model::transition &, const std::vector &stack) const; + + void grow_stack_after_running(const coordinator &, + std::unordered_map &, + std::vector &); + + void dynamically_update_backtrack_sets( + const coordinator &, std::vector &); + + bool dynamically_update_backtrack_sets_at_index( + const model::transition &S_i, const model::transition &nextSP, + stack_item &preSi, int i, int p); }; } // namespace model_checking \ No newline at end of file diff --git a/docs/design/include/mcmini/model_checking/algorithms/classic_dpor/clock_vector.hpp b/docs/design/include/mcmini/model_checking/algorithms/classic_dpor/clock_vector.hpp new file mode 100644 index 00000000..9c5ed2b0 --- /dev/null +++ b/docs/design/include/mcmini/model_checking/algorithms/classic_dpor/clock_vector.hpp @@ -0,0 +1,115 @@ +#pragma once + +#include +#include +#include + +#include "mcmini/model/defines.hpp" +#include "mcmini/model/transitions/transition_sequence.hpp" + +namespace model_checking { +/** + * @brief A multi-dimensional vector used to keep track of "time" between + * dependencies in a model checking trace + * + * A clock vector is simply a vector of integer components. They are used in + * message-passing systems to determine if an event in a distributed system + * "causally-happens-before" another. But clock vectors are more generally + * applicable: they can be used to determine a "happens-before" partial order + * even in the absence of explicit "send" and "receive" messages. McMini, for + * example, uses clock vectors to determine the "happens-before" relation among + * transitions in a transition sequence + * + * A `clock_vector` implicitly maps a thread id it does not contain to a default + * value of `0`. Thus, a `clock_vector` is "lazy" in the sense that new threads + * are "automatically" mapped without needing to be explicitly added the clock + * vector when the thread is created + */ +struct clock_vector final { + private: + std::unordered_map contents; + + public: + using index = model::transition_sequence::index; + static constexpr index invalid = -1; + + clock_vector() = default; + clock_vector(const clock_vector &) = default; + clock_vector(clock_vector &&) = default; + clock_vector &operator=(const clock_vector &) = default; + clock_vector &operator=(clock_vector &&) = default; + + /** + * @brief The number of components in this clock vector + * + * The `clock_vector` is "lazy" in the sense that + * new components must be explicitly added to the + * clock vector: the clock vector is not of an + * initial, fixed size. Thus two `clock_vector`s may + * have different sizes + * + * @return the number of elements in the clock vector + */ + uint32_t size() const { return this->contents.size(); } + + index &operator[](runner_id_t tid) { + // NOTE: The `operator[]` overload of + // unordered_map will create a new key-value + // pair if `tid` does not exist and will use + // a _default_ value for the value (0 in this case) + // which is actually what we want here + return this->contents[tid]; + } + + /** + * @brief Retrieves the value for the thread if mapped by this clock vector + * + * @param tid the id of the thread to check for a mapping + * @return the value for the thread if mapped by this clock + * vector or `0` if the clock vector does not have an explicit mapping for the + * thread + */ + index value_for(runner_id_t tid) const { + const auto iter = this->contents.find(tid); + if (iter != this->contents.end()) return iter->second; + return 0; + } + + bool has_explicit_mapping_for(runner_id_t rid) const { + return this->contents.count(rid) != 0; + } + + bool has_implicit_mapping_for(runner_id_t rid) const { + return this->contents.count(rid) == 0; + } + + /** + * @brief Computes a clock vector whose components + * are larger than the components of both of + * the given clock vectors + * + * The maximum of two clock vectors is definied to be the clock vector whose + * components are the maxmimum of the corresponding components of the + * arguments. Since the `clock_vector` class is "lazy", the two clock vectors + * given as arguments may not be of the same size. The resultant clock vector + * has components as follows: + * + * 1. For each thread that each clock vector maps, the resulting clock vector + * maps that thread to the maxmimum of the values mapped to the thread by each + * clock vector + * + * 2. For each thread that only a single clock vector maps, the resulting + * clock vector maps that thread to the value mapped by the lone clock vector + * + * The scheme is equivalent to assuming that an unmapped thread by any one + * clock vector is implicitly mapped to zero + * + * @param cv1 the first clock vector + * @param cv2 the second clock vector + * @return clock_vector a clock vector whose components are at least as large + * as the corresponding components of each clock vector and whose size is + * large enough to contain the union of components of each clock vector + */ + static clock_vector max(const clock_vector &cv1, const clock_vector &cv2); +}; +} // namespace model_checking diff --git a/docs/design/include/mcmini/model_checking/algorithms/classic_dpor/runner_item.hpp b/docs/design/include/mcmini/model_checking/algorithms/classic_dpor/runner_item.hpp new file mode 100644 index 00000000..f4bbbed9 --- /dev/null +++ b/docs/design/include/mcmini/model_checking/algorithms/classic_dpor/runner_item.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include + +#include +#include + +#include "mcmini/model_checking/algorithms/classic_dpor/clock_vector.hpp" + +namespace model_checking { +/** + * @brief A simple C-like struct that McMini associates + * with each thread created in the target process + */ +struct runner_item final { + private: + uint32_t executionDepth = 0u; + clock_vector cv; + + public: + uint32_t get_execution_depth() const; + void increment_execution_depth() { executionDepth++; } + void decrement_execution_depth_if_possible() { + executionDepth = std::min(0u, executionDepth - 1); + } + const clock_vector& get_clock_vector() const { return cv; } + void set_clock_vector(const clock_vector& new_cv) { cv = new_cv; } +}; + +} // namespace model_checking \ No newline at end of file diff --git a/docs/design/include/mcmini/model_checking/algorithms/classic_dpor/stack_item.hpp b/docs/design/include/mcmini/model_checking/algorithms/classic_dpor/stack_item.hpp new file mode 100644 index 00000000..9b3ee370 --- /dev/null +++ b/docs/design/include/mcmini/model_checking/algorithms/classic_dpor/stack_item.hpp @@ -0,0 +1,205 @@ +#pragma once + +#include +#include +#include + +#include "mcmini/model_checking/algorithms/classic_dpor/clock_vector.hpp" + +namespace model_checking { + +/** + * @brief Associates information relevant to DPOR about a state it has searched + * + * The `stack_item` is a light-weight representation of a state explored by + * McMini. McMini is a stateless model checker, meaning that is does not + * "remember" or cache the previous states of the visible objects it manages. + * However, DPOR requires that some information be persisted during the + * state-space search in order to function correctly. + * + * Each `stack_item` persists the state's backtracking set, done set, and sleep + * set. Each state explored by McMini has exactly one representative + * `stack_item`. McMini will adjust the state's trio of sets as it continues to + * explore the state space and execute more transitions + */ +struct stack_item final { + private: + /** + * @brief A collection of threads that are scheduled to be run from this state + * to continune to state-space search + * + * @invariant a thread is contained in exactly one of either the backtracking + * set, the done set, or the sleep set + */ + std::unordered_set backtrack_set; + + /** + * @brief A collection of threads that have executed from this state so far + * + * @invariant a thread is contained in exactly one of either the backtracking + * set, the done set, or the sleep set + */ + std::unordered_set done_set; + + /** + * @brief A collection of threads that do + * not need to be executed from this state + * + * The technical definition of a sleep set is + * a collection of transitions. However, we can + * exploit a clever observation + * + * @invariant a thread is contained in + * exactly one of either the backtracking set, + * the done set, or the sleep set + * + * @invariant for any given thread, at most + * one transition can be contained in the sleep + * set. If such a transition exists, it is the + * _next_ transition in this state that will be + * run by that thread + */ + std::unordered_set sleep_set; + + /** + * @brief A cache of threads that are enabled in this state + * + * DPOR needs to know which threads are enabled in an + * arbitrary state in the current DFS search. It is important + * that the state(s) of the visible object(s) McMini + * manages reflect what they were at a particular moment in + * time during the state-space search since a transition's + * status for being enabled can change between state changes + * (consider e.g. a mutex lock becoming disabled after a different + * thread acquires the mutex before this thread does). + * + * Since DPOR performs stateless model checking, McMini can only + * represent a *single* state at any given time. Thus, we can either + * + * A. Re-generate the past states of objects by reseting the current + * state and re-playing the transitions in the transition stack + * leading up to that state; or + * + * B. Compute which threads were enabled when the state was + * encountered and store them for later use + * + * The former is very expensive and would complicate the implementation even + * further. Thus, we opt for the latter choice. + */ + std::unordered_set enabled_runners; + + /** + * @brief The clock vector associated with the + * transition _resulting_ in this state + */ + const clock_vector cv; + + /// @brief The transition which DPOR decided to schedule from this state. + /// + /// @note the item does not own this transition; it is instead owned by the + /// trace of the model which the DPOR algorithm manipulates. It is up to the + const model::transition *out_transition = nullptr; + + public: + stack_item() : stack_item(clock_vector()) {} + stack_item(clock_vector cv) : cv(std::move(cv)) {} + stack_item(clock_vector cv, std::unordered_set enabled_runners) + : stack_item(std::move(cv), nullptr, std::move(enabled_runners)) {} + stack_item(clock_vector cv, const model::transition *out_transition, + std::unordered_set enabled_runners) + : cv(std::move(cv)), + out_transition(out_transition), + enabled_runners(std::move(enabled_runners)) {} + + bool backtrack_set_empty() const { return backtrack_set.empty(); } + bool backtrack_set_contains(runner_id_t id) const { + return backtrack_set.count(id) != 0; + } + bool sleep_set_contains(runner_id_t rid) const { + return sleep_set.count(rid) != 0; + } + bool done_set_contains(runner_id_t rid) const { + return done_set.count(rid) != 0; + } + bool has_enabled_runners() const { return !this->enabled_runners.empty(); } + + /** + * @brief Places the given thread into the backtrack set. + * + * If the thread is already contained in the done set, the thread will + * not be re-added to the backtracking set and this method acts as a no-op. + * Otherwise the given thread will be added to the backtracking set associated + * with this state and will be scheduled to execute from this state. + * + * @param rid the thread to add to the backtracking set + * + * @note McMini will ensure that the given thread is running a transition + * that's enabled. This, however, is not enforced by this method; rather, + * threads added to the backtrack set using this method are simply cached for + * later use by McMini and DPOR + */ + void insert_into_backtrack_set_unless_completed(runner_id_t rid) { + if (!done_set_contains(rid)) backtrack_set.insert(rid); + } + void insert_into_sleep_set(runner_id_t rid) { sleep_set.insert(rid); } + + /** + * @brief Moves a thread into the done set + * + * When a thread is moved into the done set, the thread will not be scheduled + * to execute from this state in future depth-first state-space searches that + * include this state. + * + * @param rid the thread to move to the done set + */ + void mark_searched(runner_id_t rid) { + this->done_set.insert(rid); + this->backtrack_set.erase(rid); + } + + /** + * @brief Caches the threads in the given set + * for later use by DPOR when determining enabled + * threads in this state + * + * @param runners a collection of runners that are enabled in this state + * + * @note McMini will ensure that threads marked enabled in this state are + * indeed enabled. This, however, is not enforced by this method; rather, + * threads marked as enabled are simply cached for later use by McMini and + * DPOR + */ + void mark_enabled_in_state(const std::unordered_set &runners) { + for (const runner_id_t rid : runners) this->enabled_runners.insert(rid); + } + + // NOTE: We arbitrarily always pick the smallest thread to provide a + // determinism + runner_id_t backtrack_set_pop() { + if (backtrack_set_empty()) + throw std::runtime_error("There are no more threads to backtrack on"); + + runner_id_t backtrack_thread = *this->backtrack_set.begin(); + for (const runner_id_t rid : this->backtrack_set) + backtrack_thread = std::min(rid, backtrack_thread); + + this->mark_searched(backtrack_thread); + return backtrack_thread; + } + + clock_vector get_clock_vector() const { return this->cv; } + const std::unordered_set &get_enabled_runners() const { + return this->enabled_runners; + } + const std::unordered_set &get_sleep_set() const { + return this->sleep_set; + } + const model::transition *get_out_transition() const { + return this->out_transition; + } + void set_out_transition(const model::transition *out) { + this->out_transition = out; + } +}; + +} // namespace model_checking diff --git a/docs/design/include/mcmini/real_world/process.hpp b/docs/design/include/mcmini/real_world/process.hpp index 9664ef74..79f74a5c 100644 --- a/docs/design/include/mcmini/real_world/process.hpp +++ b/docs/design/include/mcmini/real_world/process.hpp @@ -3,6 +3,7 @@ #include #include +#include "mcmini/model/defines.hpp" #include "mcmini/real_world/mailbox/runner_mailbox.h" namespace real_world { @@ -27,7 +28,7 @@ namespace real_world { */ struct process { public: - using runner_id_t = uint32_t; + using runner_id_t = ::runner_id_t; public: struct execution_exception : public std::runtime_error { diff --git a/docs/design/src/mcmini/model_checking/algorithms/classic_dpor.cpp b/docs/design/src/mcmini/model_checking/algorithms/classic_dpor.cpp index ea37da25..2c0b6c7b 100644 --- a/docs/design/src/mcmini/model_checking/algorithms/classic_dpor.cpp +++ b/docs/design/src/mcmini/model_checking/algorithms/classic_dpor.cpp @@ -1,190 +1,316 @@ #include "mcmini/model_checking/algorithms/classic_dpor.hpp" -#include - +#include #include #include +#include #include +#include "mcmini/defines.h" #include "mcmini/model/program.hpp" #include "mcmini/signal.hpp" -using namespace model_checking; using namespace model; - -struct transition_sequence_entry { - std::unordered_set backtrack_set; - std::unordered_set sleep_set; -}; +using namespace model_checking; void classic_dpor::verify_using(coordinator &coordinator, const callbacks &callbacks) { - std::cout << "DPOR is running here! Yay!!" << std::endl; + // The code below is an implementation of the model-checking algorithm of + // Flanagan and Godefroid from 2015. + + // 1. Data structure set up + std::unordered_map per_runner_clocks; + + /// @invariant: The number of items in the DPOR-specific stack is the same + /// size as the number of transitions in the current trace plus one. + /// + /// The initial entry into the stack represents the information DPOR tracks + /// for state `s_0`. + std::vector dpor_stack; + dpor_stack.emplace_back( + clock_vector(), + coordinator.get_current_program_model().get_enabled_runners()); + + while (!dpor_stack.empty()) { + // 2. Exploration phase + while (dpor_stack.back().has_enabled_runners()) { + if (dpor_stack.size() >= MAX_TOTAL_TRANSITIONS_IN_PROGRAM) { + throw std::runtime_error( + "*** Execution Limit Reached! ***\n\n" + "McMini ran a trace with" + + std::to_string(dpor_stack.size()) + + " transitions which is\n" + "the most McMini can currently handle in any one trace. Try\n" + "running mcmini with the \"--max-depth-per-thread\" flag\n" + "to limit how far into a trace McMini can go\n"); + } + + { // 2a. Execute the runner in the model and in the real world + // For deterministic results, always choose the smallest runner + const auto &enabled_runners = dpor_stack.back().get_enabled_runners(); + runner_id_t venturer = *enabled_runners.begin(); + for (const runner_id_t rid : enabled_runners) + venturer = std::min(rid, venturer); + coordinator.execute_runner(venturer); + } + + { // 2b. Update DPOR data structures (per-thread data, clock vectors, + // backtrack sets) + this->grow_stack_after_running(coordinator, per_runner_clocks, + dpor_stack); + this->dynamically_update_backtrack_sets(coordinator, dpor_stack); + } + } + + // 3. Backtrack phase + do { + // Locate a spot that contains backtrack threads + if (dpor_stack.back().backtrack_set_empty()) { + dpor_stack.pop_back(); + } else { + break; + } + } while (!dpor_stack.empty()); + } +} + +clock_vector classic_dpor::accumulate_max_clock_vector_against( + const model::transition &t, const std::vector &stack) const { + // The last state in the stack does NOT have an out transition, hence the + // `nullptr` check. Note that `s_i.get_out_transition()` refers to `S_i` + // (case-sensitive) in the paper, viz. the transition between states `s_i` and + // `s_{i+1}`. + clock_vector result; + for (const stack_item &s_i : stack) { + if (s_i.get_out_transition() != nullptr && + this->are_dependent(*s_i.get_out_transition(), t)) { + result = clock_vector::max(result, s_i.get_clock_vector()); + } + } + return result; +} + +void classic_dpor::grow_stack_after_running( + const coordinator &coordinator, + std::unordered_map &per_runner_clocks, + std::vector &stack) { + // In this method, the following invariants are assumed to hold: + // + // 1. `n` := `stack.size()`. + // 2. `t_n` is the `n`th transition executed in the model of `coordinator`. + // This transition *has already executed in the model.* THIS IS VERY + // IMPORTANT as the DPOR information tracking below relies on this heavily. + // + // After this method is executed, the stack will have size `n + 1`. Each entry + // will correspond to the information DPOR cares about for each state in the + // `coordinator`'s state sequence. + assert(coordinator.get_depth_into_program() == stack.size() + 1); + const model::transition *t_n = + coordinator.get_current_program_model().get_trace().back(); + + // NOTE: `cv` corresponds to line 14.3 of figure 4 in the DPOR paper. + clock_vector cv = accumulate_max_clock_vector_against(*t_n, stack); + + // NOTE: The assignment corresponds to line 14.4 of figure 4. Here `S'` + // represents the transition sequence _after_ `t_n` has executed. Since the + // `coordinator` already contains this transition (recall the invariants in + // the comment at the start of this function), `S' == + // `coordinator.get_current_program_model().get_trace()` and thus `|S'| == + // corodinator.get_depth_into_program(). + cv[t_n->get_executor()] = coordinator.get_depth_into_program(); - std::stack dpor_specific_items; + // NOTE: This line corresponds to line 14.5 of figure 4. Here, C' is + // conceptually captured through the DPOR stack and the per-thread DPOR data. + // The former contains the per-state clock vectors while the latter the + // per-thread clock vectors (among other data). + stack_item &s_n = stack.back(); + per_runner_clocks[t_n->get_executor()].set_clock_vector(cv); + stack.emplace_back( + cv, t_n, coordinator.get_current_program_model().get_enabled_runners()); + stack_item &s_n_plus_1 = stack.back(); - // Keep track of stuff here... + // INVARIANT: For each thread `p`, if such a thread is contained + // in the sleep set of `s_n`, then `next(s_n, p)` MUST be the transition + // that would be contained in that sleep set. + for (const runner_id_t &rid : s_n.get_sleep_set()) { + const model::transition *rid_next = + coordinator.get_current_program_model().get_pending_transition_for(rid); + if (this->are_independent(*rid_next, *t_n)) + s_n_plus_1.insert_into_sleep_set(rid); + } + + // `t_n` is inserted into the sleep set AFTER execution. This is how sleep + // sets work (see papers etc.) + s_n.insert_into_sleep_set(t_n->get_executor()); + s_n.mark_searched(t_n->get_executor()); +} - // auto enabled_runners = - // coordinator.get_current_program_model().get_enabled_runners(); +void classic_dpor::dynamically_update_backtrack_sets( + const coordinator &coordinator, std::vector &stack) { + /* + * Updating the backtrack sets is accomplished as follows + * (under the given assumptions) + * + * ASSUMPTIONS + * + * 1. The state reflects last(S) for the transition stack + * + * 2. The thread that ran last is at the top of the transition + * stack (this should always be true) + * + * 3. The next transition for the thread that ran the most + * recent transition in the transition stack (the transition at the + * top of the stack) has been properly updated to reflect what that + * thread will do next + * + * WLOG, assume there are `n` transitions in the transition stack + * and `k` threads that are known to exist at the time of updating + * the backtrack sets. Note this implies that there are `n+1` items + * in the state stack (since there is always the initial state + 1 + * for every subsequent transition thereafter) + * + * Let + * S_i = ith backtracking state item + * T_i = ith transition + * N_p = the next transition for thread p (next(s, p)) + * + * ALGORITHM: + * + * 1. First, get a reference to the transition at the top + * of the transition stack (i.e. the most recent transition) + * as well as the thread that ran that transition. WLOG suppose that + * thread has a thread id `i`. + * + * This transition will be used to test against the transitions + * queued as running "next" for all of the **other** threads + * that exist + * + * 2. Test whether a backtrack point is needed at state + * S_n for the other threads by comparing N_p, for all p != i. + * + * 3. Get a reference to N_i and traverse the transition stack + * to determine if a backtrack point is needed anywhere for + * thread `i` + */ + const size_t num_threads = + coordinator.get_current_program_model().get_num_runners(); - // TODO: We could attach a `model_checking::oracle` here which, given - // a set of threads and a program trace, perhaps some other information, - // decides which thread to run. Something like this might be interesting here. + std::unordered_set thread_ids; + thread_ids.reserve(num_threads); + for (tid_t i = 0; i < num_threads; i++) thread_ids.insert(i); - // Pick an enabled thread etc. + const ssize_t tStackTop = (ssize_t)(stack.size()) - 1; + const runner_id_t last_runner_to_execute = + coordinator.get_current_program_model() + .get_trace() + .back() + ->get_executor(); + thread_ids.erase(last_runner_to_execute); - // Based on the items in `dpor_specific_items`, do something interesting. + // O(# threads) + { + const model::transition &S_n = + *coordinator.get_current_program_model().get_trace().back(); + + for (runner_id_t rid = 0; rid < num_threads; rid++) { + const model::transition &nextSP = *coordinator.get_current_program_model() + .get_pending_transitions() + .get_transition_for_runner(rid); + dynamically_update_backtrack_sets_at_index(S_n, nextSP, stack.back(), + tStackTop, rid); + } + } - // For now, we simply tell the coordinator to run one thread for a few steps, - // backtrack once, and then exit + // O(transition stack size) + { + const model::transition &next_s_p_for_latest_runner = + *coordinator.get_current_program_model() + .get_pending_transitions() + .get_transition_for_runner(last_runner_to_execute); - // Execution may fail... we should raise an exception in these cases - try { - for (int i = 0; i < 5; i++) { - std::cout << "TEST" << i << std::endl; - coordinator.execute_runner(0); - coordinator.execute_runner(0); - coordinator.return_to_depth(0); - signal_tracker::throw_if_received(SIGINT); + // It only remains to add backtrack points at the necessary + // points for thread `last_runner_to_execute`. We start at one step elow the + // top since we know that transition to not be co-enabled (since it was, by + // assumption, run by `last_runner_to_execute`) + for (int i = tStackTop - 1; i >= 0; i--) { + const model::transition &S_i = + *coordinator.get_current_program_model().get_trace().at(i); + const bool shouldStop = dynamically_update_backtrack_sets_at_index( + S_i, next_s_p_for_latest_runner, stack.at(i), i, + last_runner_to_execute); + /* + * Stop when we find the _first_ such i; this + * will be the maxmimum `i` since we're searching + * backwards + */ + if (shouldStop) break; } - } catch (real_world::process::execution_exception &e) { - std::cerr << "Error: " << e.what() << std::endl; } } -// void classic_dpor::dynamicallyUpdateBacktrackSets() { -// /* -// * Updating the backtrack sets is accomplished as follows -// * (under the given assumptions) -// * -// * ASSUMPTIONS -// * -// * 1. The state reflects last(S) for the transition stack -// * -// * 2. The thread that ran last is at the top of the transition -// * stack (this should always be true) -// * -// * 3. The next transition for the thread that ran the most -// * recent transition in the transition stack (the transition at the -// * top of the stack) has been properly updated to reflect what that -// * thread will do next -// * -// * WLOG, assume there are `n` transitions in the transition stack -// * and `k` threads that are known to exist at the time of updating -// * the backtrack sets. Note this implies that there are `n+1` items -// * in the state stack (since there is always the initial state + 1 -// * for every subsequent transition thereafter) -// * -// * Let -// * S_i = ith backtracking state item -// * T_i = ith transition -// * N_p = the next transition for thread p (next(s, p)) -// * -// * ALGORITHM: -// * -// * 1. First, get a reference to the transition at the top -// * of the transition stack (i.e. the most recent transition) -// * as well as the thread that ran that transition. WLOG suppose that -// * thread has a thread id `i`. -// * -// * This transition will be used to test against the transitions -// * queued as running "next" for all of the **other** threads -// * that exist -// * -// * 2. Test whether a backtrack point is needed at state -// * S_n for the other threads by comparing N_p, for all p != i. -// * -// * 3. Get a reference to N_i and traverse the transition stack -// * to determine if a backtrack point is needed anywhere for -// * thread `i` -// */ -// const uint64_t num_threads = this->getNumProgramThreads(); - -// std::unordered_set thread_ids; -// for (tid_t i = 0; i < num_threads; i++) thread_ids.insert(i); - -// // 3. Determine the i -// const MCTransition &tStackTop = this->getTransitionStackTop(); -// const tid_t mostRecentThreadId = tStackTop.getThreadId(); -// const MCTransition &nextTransitionForMostRecentThread = -// this->getNextTransitionForThread(mostRecentThreadId); -// thread_ids.erase(mostRecentThreadId); - -// // O(# threads) -// { -// const MCTransition &S_n = this->getTransitionStackTop(); -// MCStackItem &s_n = this->getStateItemAtIndex(this->transitionStackTop); -// const std::unordered_set enabledThreadsAt_s_n = -// s_n.getEnabledThreadsInState(); - -// for (tid_t tid : thread_ids) { -// const MCTransition &nextSP = this->getNextTransitionForThread(tid); -// this->dynamicallyUpdateBacktrackSetsHelper( -// S_n, s_n, nextSP, this->transitionStackTop, (int)tid); -// } -// } +// bool MCStack::happensBefore(int i, int j) const { +// MC_ASSERT(i >= 0 && j >= 0); +// const tid_t tid = getThreadRunningTransitionAtIndex(i); +// const MCClockVector cv = clockVectorForTransitionAtIndex(j); +// return i <= cv.valueForThread(tid).value_or(0); +// } -// // O(transition stack size) - -// // It only remains to add backtrack points at the necessary -// // points for thread `mostRecentThreadId`. We start at one step -// // below the top since we know that transition to not be co-enabled -// // (since it was, by assumption, run by `mostRecentThreadId` -// for (int i = this->transitionStackTop - 1; i >= 0; i--) { -// const MCTransition &S_i = this->getTransitionAtIndex(i); -// MCStackItem &preSi = this->getStateItemAtIndex(i); -// const bool shouldStop = dynamicallyUpdateBacktrackSetsHelper( -// S_i, preSi, nextTransitionForMostRecentThread, i, -// (int)mostRecentThreadId); -// /* -// * Stop when we find the first such i; this -// * will be the maxmimum `i` since we're searching -// * backwards -// */ -// if (shouldStop) break; -// } +// bool MCStack::happensBeforeThread(int i, tid_t p) const { +// const tid_t tid = getThreadRunningTransitionAtIndex(i); +// const MCClockVector cv = getThreadDataForThread(p).getClockVector(); +// return i <= cv.valueForThread(tid).value_or(0); // } -// bool MCStack::dynamicallyUpdateBacktrackSetsHelper(const MCTransition &S_i, -// MCStackItem &preSi, -// const MCTransition -// &nextSP, int i, int p) { -// const unordered_set enabledThreadsAtPreSi = -// preSi.getEnabledThreadsInState(); -// const bool shouldProcess = MCTransition::dependentTransitions(S_i, nextSP) -// && -// MCTransition::coenabledTransitions(S_i, nextSP) -// && !this->happensBeforeThread(i, p); - -// // if there exists i such that ... -// if (shouldProcess) { -// std::unordered_set E; - -// for (tid_t q : enabledThreadsAtPreSi) { -// const bool inE = q == p || this->threadsRaceAfterDepth(i, q, p); -// const bool isInSleepSet = preSi.threadIsInSleepSet(q); - -// // If E != empty set -// if (inE && !isInSleepSet) E.insert(q); -// } - -// if (E.empty()) { -// // E is the empty set -> add every enabled thread at pre(S, i) -// for (tid_t q : enabledThreadsAtPreSi) -// if (!preSi.threadIsInSleepSet(q)) -// preSi.addBacktrackingThreadIfUnsearched(q); -// } else { -// for (tid_t q : E) { -// // If there is a thread in preSi that we -// // are already backtracking AND which is contained -// // in the set E, chose that thread to backtrack -// // on. This is equivalent to not having to do -// // anything -// if (preSi.isBacktrackingOnThread(q)) return shouldProcess; -// } -// preSi.addBacktrackingThreadIfUnsearched(*E.begin()); -// } +// bool MCStack::threadsRaceAfterDepth(int depth, tid_t q, tid_t p) const { +// // We want to search the entire transition stack in this case +// const auto transitionStackHeight = this->getTransitionStackSize(); +// for (int j = depth + 1; j < transitionStackHeight; j++) { +// if (q == this->getThreadRunningTransitionAtIndex(j) && +// this->happensBeforeThread(j, p)) +// return true; // } -// return shouldProcess; +// return false; // } + +bool classic_dpor::dynamically_update_backtrack_sets_at_index( + const model::transition &S_i, const model::transition &nextSP, + stack_item &preSi, int i, int p) { + // TODO: add in co-enabled conditions + const bool has_reversible_race = + this->are_dependent(nextSP, S_i); //&& !this->happensBeforeThread(i, p); + + // If there exists i such that ... + if (has_reversible_race) { + std::unordered_set E; + + const std::unordered_set &enabled_at_preSi = + preSi.get_enabled_runners(); + + for (tid_t q : enabled_at_preSi) { + const bool inE = q == p; //|| this->threadsRaceAfterDepth(i, q, p); + + // If E != empty set + if (inE && !preSi.sleep_set_contains(q)) E.insert(q); + } + + if (E.empty()) { + // E is the empty set -> add every enabled thread at pre(S, i) + for (tid_t q : enabled_at_preSi) + if (!preSi.sleep_set_contains(q)) + preSi.insert_into_backtrack_set_unless_completed(q); + } else { + for (tid_t q : E) { + // If there is a thread in preSi that we + // are already backtracking AND which is contained + // in the set E, chose that thread to backtrack + // on. This is equivalent to not having to do + // anything + if (preSi.backtrack_set_contains(q)) return true; + } + + // Otherwise select an arbitrary thread to backtrack upon. + preSi.insert_into_backtrack_set_unless_completed(*E.begin()); + } + } + return has_reversible_race; +} diff --git a/docs/design/src/mcmini/model_checking/algorithms/clock_vector.cpp b/docs/design/src/mcmini/model_checking/algorithms/clock_vector.cpp new file mode 100644 index 00000000..2e2d5244 --- /dev/null +++ b/docs/design/src/mcmini/model_checking/algorithms/clock_vector.cpp @@ -0,0 +1,15 @@ +#include "mcmini/model_checking/algorithms/classic_dpor/clock_vector.hpp" + +using namespace model_checking; + +using namespace std; + +clock_vector clock_vector::max(const clock_vector &cv1, + const clock_vector &cv2) { + clock_vector maxCV; + for (const auto &e : cv2.contents) + maxCV[e.first] = std::max(cv1.value_for(e.first), e.second); + for (const auto &e : cv1.contents) + maxCV[e.first] = std::max(cv2.value_for(e.first), e.second); + return maxCV; +} From f3b4d1c880233160a7662792d32e3bbae41493af Mon Sep 17 00:00:00 2001 From: Maxwell Pirtle Date: Tue, 14 May 2024 10:13:55 -0400 Subject: [PATCH 032/161] Add remaining DPOR logic --- .../algorithms/classic_dpor.hpp | 21 ++-- .../src/mcmini/model/state_sequence.cpp | 12 +- .../algorithms/classic_dpor.cpp | 111 ++++++++++-------- 3 files changed, 79 insertions(+), 65 deletions(-) diff --git a/docs/design/include/mcmini/model_checking/algorithms/classic_dpor.hpp b/docs/design/include/mcmini/model_checking/algorithms/classic_dpor.hpp index 5fad8138..17d5007e 100644 --- a/docs/design/include/mcmini/model_checking/algorithms/classic_dpor.hpp +++ b/docs/design/include/mcmini/model_checking/algorithms/classic_dpor.hpp @@ -38,19 +38,22 @@ class classic_dpor final : public algorithm { // Do not call these methods directly. They are implementation details of // the DPOR algorithm and are called at specific points in time! - clock_vector accumulate_max_clock_vector_against( - const model::transition &, const std::vector &stack) const; + struct dpor_context; - void grow_stack_after_running(const coordinator &, - std::unordered_map &, - std::vector &); + bool happens_before(const dpor_context &, int i, int j) const; + bool happens_before_thread(const dpor_context &, int i, runner_id_t p) const; + bool threads_race_after(const dpor_context &context, int i, runner_id_t q, + runner_id_t p) const; - void dynamically_update_backtrack_sets( - const coordinator &, std::vector &); + clock_vector accumulate_max_clock_vector_against(const model::transition &, + const dpor_context &) const; + + void grow_stack_after_running(const coordinator &, dpor_context &); + void dynamically_update_backtrack_sets(const coordinator &, dpor_context &); bool dynamically_update_backtrack_sets_at_index( - const model::transition &S_i, const model::transition &nextSP, - stack_item &preSi, int i, int p); + const dpor_context &, const model::transition &S_i, + const model::transition &nextSP, stack_item &preSi, int i, int p); }; } // namespace model_checking \ No newline at end of file diff --git a/docs/design/src/mcmini/model/state_sequence.cpp b/docs/design/src/mcmini/model/state_sequence.cpp index b29f5849..118a02bc 100644 --- a/docs/design/src/mcmini/model/state_sequence.cpp +++ b/docs/design/src/mcmini/model/state_sequence.cpp @@ -112,11 +112,6 @@ void state_sequence::add_state_for_obj( this->visible_objects.at(id).push_state(std::move(new_state)); } -std::unique_ptr state_sequence::mutable_clone() const { - return state::from_visible_objects( - this->visible_objects.cbegin(), this->visible_objects.cend()); -} - void state_sequence::consume_into_subsequence(size_t num_states) { if (num_states <= this->states_in_sequence.size()) this->states_in_sequence.erase( @@ -250,6 +245,13 @@ class state_sequence::diff_state : public mutable_state { std::unique_ptr mutable_clone() const override; }; +std::unique_ptr state_sequence::mutable_clone() const { + return extensions::make_unique(*this); + // return extensions::make_unique(this); + // return state::from_visible_objects( + // this->visible_objects.cbegin(), this->visible_objects.cend()); +} + state::objid_t state_sequence::diff_state::get_objid_for_runner( runner_id_t id) const { if (this->new_runners.count(id) > 0) { diff --git a/docs/design/src/mcmini/model_checking/algorithms/classic_dpor.cpp b/docs/design/src/mcmini/model_checking/algorithms/classic_dpor.cpp index 2c0b6c7b..5bd83804 100644 --- a/docs/design/src/mcmini/model_checking/algorithms/classic_dpor.cpp +++ b/docs/design/src/mcmini/model_checking/algorithms/classic_dpor.cpp @@ -13,20 +13,29 @@ using namespace model; using namespace model_checking; +struct classic_dpor::dpor_context { + std::vector stack; + std::unordered_map per_runner_clocks; + + const transition *get_transition(int i) const { + return stack.at(i).get_out_transition(); + } +}; + void classic_dpor::verify_using(coordinator &coordinator, const callbacks &callbacks) { // The code below is an implementation of the model-checking algorithm of // Flanagan and Godefroid from 2015. // 1. Data structure set up - std::unordered_map per_runner_clocks; /// @invariant: The number of items in the DPOR-specific stack is the same /// size as the number of transitions in the current trace plus one. /// /// The initial entry into the stack represents the information DPOR tracks /// for state `s_0`. - std::vector dpor_stack; + dpor_context context; + auto &dpor_stack = context.stack; dpor_stack.emplace_back( clock_vector(), coordinator.get_current_program_model().get_enabled_runners()); @@ -56,9 +65,8 @@ void classic_dpor::verify_using(coordinator &coordinator, { // 2b. Update DPOR data structures (per-thread data, clock vectors, // backtrack sets) - this->grow_stack_after_running(coordinator, per_runner_clocks, - dpor_stack); - this->dynamically_update_backtrack_sets(coordinator, dpor_stack); + this->grow_stack_after_running(coordinator, context); + this->dynamically_update_backtrack_sets(coordinator, context); } } @@ -75,13 +83,13 @@ void classic_dpor::verify_using(coordinator &coordinator, } clock_vector classic_dpor::accumulate_max_clock_vector_against( - const model::transition &t, const std::vector &stack) const { + const model::transition &t, const dpor_context &context) const { // The last state in the stack does NOT have an out transition, hence the // `nullptr` check. Note that `s_i.get_out_transition()` refers to `S_i` // (case-sensitive) in the paper, viz. the transition between states `s_i` and // `s_{i+1}`. clock_vector result; - for (const stack_item &s_i : stack) { + for (const stack_item &s_i : context.stack) { if (s_i.get_out_transition() != nullptr && this->are_dependent(*s_i.get_out_transition(), t)) { result = clock_vector::max(result, s_i.get_clock_vector()); @@ -90,10 +98,8 @@ clock_vector classic_dpor::accumulate_max_clock_vector_against( return result; } -void classic_dpor::grow_stack_after_running( - const coordinator &coordinator, - std::unordered_map &per_runner_clocks, - std::vector &stack) { +void classic_dpor::grow_stack_after_running(const coordinator &coordinator, + dpor_context &context) { // In this method, the following invariants are assumed to hold: // // 1. `n` := `stack.size()`. @@ -104,12 +110,12 @@ void classic_dpor::grow_stack_after_running( // After this method is executed, the stack will have size `n + 1`. Each entry // will correspond to the information DPOR cares about for each state in the // `coordinator`'s state sequence. - assert(coordinator.get_depth_into_program() == stack.size() + 1); + assert(coordinator.get_depth_into_program() == context.stack.size()); const model::transition *t_n = coordinator.get_current_program_model().get_trace().back(); // NOTE: `cv` corresponds to line 14.3 of figure 4 in the DPOR paper. - clock_vector cv = accumulate_max_clock_vector_against(*t_n, stack); + clock_vector cv = accumulate_max_clock_vector_against(*t_n, context); // NOTE: The assignment corresponds to line 14.4 of figure 4. Here `S'` // represents the transition sequence _after_ `t_n` has executed. Since the @@ -123,11 +129,11 @@ void classic_dpor::grow_stack_after_running( // conceptually captured through the DPOR stack and the per-thread DPOR data. // The former contains the per-state clock vectors while the latter the // per-thread clock vectors (among other data). - stack_item &s_n = stack.back(); - per_runner_clocks[t_n->get_executor()].set_clock_vector(cv); - stack.emplace_back( + context.per_runner_clocks[t_n->get_executor()].set_clock_vector(cv); + context.stack.emplace_back( cv, t_n, coordinator.get_current_program_model().get_enabled_runners()); - stack_item &s_n_plus_1 = stack.back(); + stack_item &s_n = context.stack.at(context.stack.size() - 2); + stack_item &s_n_plus_1 = context.stack.back(); // INVARIANT: For each thread `p`, if such a thread is contained // in the sleep set of `s_n`, then `next(s_n, p)` MUST be the transition @@ -141,12 +147,13 @@ void classic_dpor::grow_stack_after_running( // `t_n` is inserted into the sleep set AFTER execution. This is how sleep // sets work (see papers etc.) + s_n.set_out_transition(t_n); s_n.insert_into_sleep_set(t_n->get_executor()); s_n.mark_searched(t_n->get_executor()); } void classic_dpor::dynamically_update_backtrack_sets( - const coordinator &coordinator, std::vector &stack) { + const coordinator &coordinator, dpor_context &context) { /* * Updating the backtrack sets is accomplished as follows * (under the given assumptions) @@ -199,7 +206,7 @@ void classic_dpor::dynamically_update_backtrack_sets( thread_ids.reserve(num_threads); for (tid_t i = 0; i < num_threads; i++) thread_ids.insert(i); - const ssize_t tStackTop = (ssize_t)(stack.size()) - 1; + const ssize_t tStackTop = (ssize_t)(context.stack.size()) - 1; const runner_id_t last_runner_to_execute = coordinator.get_current_program_model() .get_trace() @@ -216,8 +223,8 @@ void classic_dpor::dynamically_update_backtrack_sets( const model::transition &nextSP = *coordinator.get_current_program_model() .get_pending_transitions() .get_transition_for_runner(rid); - dynamically_update_backtrack_sets_at_index(S_n, nextSP, stack.back(), - tStackTop, rid); + dynamically_update_backtrack_sets_at_index( + context, S_n, nextSP, context.stack.back(), tStackTop, rid); } } @@ -236,7 +243,7 @@ void classic_dpor::dynamically_update_backtrack_sets( const model::transition &S_i = *coordinator.get_current_program_model().get_trace().at(i); const bool shouldStop = dynamically_update_backtrack_sets_at_index( - S_i, next_s_p_for_latest_runner, stack.at(i), i, + context, S_i, next_s_p_for_latest_runner, context.stack.at(i), i, last_runner_to_execute); /* * Stop when we find the _first_ such i; this @@ -248,36 +255,38 @@ void classic_dpor::dynamically_update_backtrack_sets( } } -// bool MCStack::happensBefore(int i, int j) const { -// MC_ASSERT(i >= 0 && j >= 0); -// const tid_t tid = getThreadRunningTransitionAtIndex(i); -// const MCClockVector cv = clockVectorForTransitionAtIndex(j); -// return i <= cv.valueForThread(tid).value_or(0); -// } - -// bool MCStack::happensBeforeThread(int i, tid_t p) const { -// const tid_t tid = getThreadRunningTransitionAtIndex(i); -// const MCClockVector cv = getThreadDataForThread(p).getClockVector(); -// return i <= cv.valueForThread(tid).value_or(0); -// } - -// bool MCStack::threadsRaceAfterDepth(int depth, tid_t q, tid_t p) const { -// // We want to search the entire transition stack in this case -// const auto transitionStackHeight = this->getTransitionStackSize(); -// for (int j = depth + 1; j < transitionStackHeight; j++) { -// if (q == this->getThreadRunningTransitionAtIndex(j) && -// this->happensBeforeThread(j, p)) -// return true; -// } -// return false; -// } +bool classic_dpor::happens_before(const dpor_context &context, int i, + int j) const { + const runner_id_t rid = + context.stack.at(i).get_out_transition()->get_executor(); + const clock_vector &cv = context.stack.at(i).get_clock_vector(); + return i <= cv.value_for(rid); +} + +bool classic_dpor::happens_before_thread(const dpor_context &context, int i, + runner_id_t p) const { + const runner_id_t rid = context.get_transition(i)->get_executor(); + const clock_vector &cv = context.per_runner_clocks.at(p).get_clock_vector(); + return i <= cv.value_for(rid); +} + +bool classic_dpor::threads_race_after(const dpor_context &context, int i, + runner_id_t q, runner_id_t p) const { + const size_t transitionStackHeight = context.stack.size(); + for (size_t j = (size_t)i + 1; j < transitionStackHeight; j++) { + if (q == context.get_transition(j)->get_executor() && + this->happens_before_thread(context, j, p)) + return true; + } + return false; +} bool classic_dpor::dynamically_update_backtrack_sets_at_index( - const model::transition &S_i, const model::transition &nextSP, - stack_item &preSi, int i, int p) { + const dpor_context &context, const model::transition &S_i, + const model::transition &nextSP, stack_item &preSi, int i, int p) { // TODO: add in co-enabled conditions - const bool has_reversible_race = - this->are_dependent(nextSP, S_i); //&& !this->happensBeforeThread(i, p); + const bool has_reversible_race = this->are_dependent(nextSP, S_i) && + !this->happens_before_thread(context, i, p); // If there exists i such that ... if (has_reversible_race) { @@ -286,8 +295,8 @@ bool classic_dpor::dynamically_update_backtrack_sets_at_index( const std::unordered_set &enabled_at_preSi = preSi.get_enabled_runners(); - for (tid_t q : enabled_at_preSi) { - const bool inE = q == p; //|| this->threadsRaceAfterDepth(i, q, p); + for (runner_id_t q : enabled_at_preSi) { + const bool inE = q == p || this->threads_race_after(context, i, q, p); // If E != empty set if (inE && !preSi.sleep_set_contains(q)) E.insert(q); From 541091d6e57af69b6aff554a8a260e8c072c1f68 Mon Sep 17 00:00:00 2001 From: Maxwell Pirtle Date: Tue, 14 May 2024 19:49:17 -0400 Subject: [PATCH 033/161] Extract `diff_state` as its own class This commit introduces `model::diff_state` as a standalone entity independent of the `state_sequence` implementation. There is a surprising benefit to doing this: The implementation of `transition` is now both more efficient AND more insightful, the former because we avoid a copy of the provided state altogether (`diff_state` is cheap in the base case) AND we return the state that now also encodes the DIFFERENCE it made on the provided state. This could prove interesting later --- docs/design/CMakeLists.txt | 1 + .../include/mcmini/model/state/diff_state.hpp | 62 +++++++ .../mcmini/model/state/state_sequence.hpp | 2 +- .../include/mcmini/model/transition.hpp | 75 ++++---- .../include/mcmini/model/visible_object.hpp | 2 +- docs/design/src/mcmini/model/diff_state.cpp | 80 +++++++++ .../src/mcmini/model/state_sequence.cpp | 164 +----------------- 7 files changed, 196 insertions(+), 190 deletions(-) create mode 100644 docs/design/include/mcmini/model/state/diff_state.hpp create mode 100644 docs/design/src/mcmini/model/diff_state.cpp diff --git a/docs/design/CMakeLists.txt b/docs/design/CMakeLists.txt index f3c3b627..ebbfbdb5 100644 --- a/docs/design/CMakeLists.txt +++ b/docs/design/CMakeLists.txt @@ -30,6 +30,7 @@ set(MCMINI_CPP_SRC src/mcmini/visible_object.cpp src/mcmini/coordinator/coordinator.cpp src/mcmini/model/detached_state.cpp + src/mcmini/model/diff_state.cpp src/mcmini/model/program.cpp src/mcmini/model/state_sequence.cpp src/mcmini/model/transition_sequence.cpp diff --git a/docs/design/include/mcmini/model/state/diff_state.hpp b/docs/design/include/mcmini/model/state/diff_state.hpp new file mode 100644 index 00000000..a6a40abf --- /dev/null +++ b/docs/design/include/mcmini/model/state/diff_state.hpp @@ -0,0 +1,62 @@ +#pragma once + +#include "mcmini/model/state.hpp" +#include "mcmini/model/visible_object.hpp" + +namespace model { + +/// @brief A state which defines its own state as a change in state from some +/// base state `base_state` +class diff_state : public mutable_state { + private: + const state &base_state; + + public: + std::unordered_map new_object_states; + std::unordered_map new_runners; + + diff_state(const state &s) : base_state(s) {} + diff_state(const diff_state &ds) + : base_state(ds.base_state), new_object_states(ds.new_object_states) {} + diff_state(detached_state &&) = delete; + diff_state &operator=(const diff_state &) = delete; + detached_state &operator=(detached_state &&) = delete; + + const state &get_base() const { return this->base_state; } + bool differs_from_base() const { + return !this->new_object_states.empty() || !this->new_runners.empty(); + } + + /* `mutable_state` overrrides */ + size_t count() const override { + size_t count = this->new_object_states.size() + base_state.count(); + for (const auto p : new_object_states) { + // Each item in `new_object_states` that is also in `base_state` defines + // states for _previously existing_ objects. These objects are accounted + // for in `count` (double-counted), hence the `--` + if (base_state.contains_object_with_id(p.first)) count--; + } + return count; + } + size_t runner_count() const override { + return base_state.runner_count() + new_runners.size(); + } + objid_t get_objid_for_runner(runner_id_t id) const override; + bool contains_object_with_id(state::objid_t id) const override; + bool contains_runner_with_id(runner_id_t id) const override; + const visible_object_state *get_state_of_object(objid_t id) const override; + const visible_object_state *get_state_of_runner( + runner_id_t id) const override; + objid_t add_object( + std::unique_ptr initial_state) override; + runner_id_t add_runner( + std::unique_ptr initial_state) override; + void add_state_for_obj( + objid_t id, std::unique_ptr new_state) override; + void add_state_for_runner( + runner_id_t id, std::unique_ptr new_state) override; + std::unique_ptr consume_obj(objid_t id) && + override; + std::unique_ptr mutable_clone() const override; +}; +} // namespace model diff --git a/docs/design/include/mcmini/model/state/state_sequence.hpp b/docs/design/include/mcmini/model/state/state_sequence.hpp index 167539f4..41da5111 100644 --- a/docs/design/include/mcmini/model/state/state_sequence.hpp +++ b/docs/design/include/mcmini/model/state/state_sequence.hpp @@ -6,6 +6,7 @@ #include "mcmini/forwards.hpp" #include "mcmini/misc/append-only.hpp" #include "mcmini/model/state/detached_state.hpp" +#include "mcmini/model/state/diff_state.hpp" #include "mcmini/model/transition.hpp" namespace model { @@ -27,7 +28,6 @@ namespace model { */ class state_sequence : public detached_state { private: - class diff_state; class element; /// @brief Inserts an instance of `element` in the `states_in_sequence` diff --git a/docs/design/include/mcmini/model/transition.hpp b/docs/design/include/mcmini/model/transition.hpp index 195a94fa..568a3de8 100644 --- a/docs/design/include/mcmini/model/transition.hpp +++ b/docs/design/include/mcmini/model/transition.hpp @@ -5,6 +5,7 @@ #include "mcmini/forwards.hpp" #include "mcmini/model/defines.hpp" #include "mcmini/model/state.hpp" +#include "mcmini/model/state/diff_state.hpp" namespace model { @@ -67,11 +68,20 @@ class transition { using runner_id_t = ::runner_id_t; transition(runner_id_t executor) : executor(executor) {} + runner_id_t get_executor() const { return executor; } /** - * @brief Returns the runner which executes this transition. + * @brief A result of a modification to a state. + * + * There are two possible outcomes attempting to apply a transition + * function: + * + * 1. Either the transition _exists_ at this state and is thus defined at + * the given state. + * 2. Or else the transition is _not_ defined as this state and the + * transition is disabled. */ - runner_id_t get_executor() const { return executor; } + enum class status { exists, disabled }; /** * @brief Attempts to produce a state _s'_ from state _s_ through the @@ -84,35 +94,38 @@ class transition { * * @param s the state to pass as an argument to the transition. * @returns the resulting state _s'_ that would be produced if this transition - * were applied to state _s_ if such a transition is defined at _s_, and - * otherwise `nullptr` + * were applied to state _s_ if such a transition is defined at _s_, along + * with the enabledness of the given transition. If the transition is not + * defined, a `diff_state` with base `s` is returned, but no other + * modifications are made to the base. + * @note a pair is NOT redundant here: it's possible for a transition to be + * enabled at state `s` but have no effect on it. Hence, we _cannot_ simply + * rely on the fact that the `diff_state` has changed w.r.t its base to + * determine enabledness; instead, we must return both the `diff_state` AND + * the enabled status. + * @note construction of a new `diff_state` in the `else` branch ensures that + * partial modifications to `s` are not transferred out of the call to + * the method into the resulting `diff_state`. Some parts of the transition + * may have modified the state, and only then would the transition have + * determined it is no longer enabled. This would be discouraged, but it's + * possible, so we opt for the defensive stance. + * @note the `diff_state` that is returned retains the reference to the state + * `s` supplied to this function. In other words, the state with respect to + * which the diff is defined is `s`. Keep this in mind if you plan to store + * the `diff_state` or continue using it after calling this function. */ - std::unique_ptr apply_to(const state& s) const { - std::unique_ptr s_prime = s.mutable_clone(); - return modify(*s_prime) == status::exists - ? std::unique_ptr(std::move(s_prime)) - : std::unique_ptr(); + std::pair apply_to(const state& s) const { + diff_state s_prime{s}; + return modify(s_prime) == status::exists + ? std::make_pair(s_prime, status::exists) + : std::make_pair(diff_state{s}, status::disabled); + } + constexpr bool is_enabled_in(const state& s) const { + return apply_to(s).second == status::exists; + } + constexpr bool is_disabled_in(const state& s) const { + return !is_enabled_in(s); } - - /** - * @brief Whether the transition is defined in the given state. - * - * @param s the state to determine if this transition is enabled. - */ - bool is_enabled_in(const state& s) const { return apply_to(s) != nullptr; } - - /** - * @brief A result of a modification to a state. - * - * There are two possible outcomes attempting to apply a transition - * function: - * - * 1. Either the transition _exists_ at this state and is thus defined at - * the given state. - * 2. Or else the transition is _not_ defined as this state and the - * transition is disabled. - */ - enum class status { exists, disabled }; /** * @brief Fire the transition as if it were run from state _state_. @@ -140,9 +153,7 @@ class transition { virtual ~transition() = default; protected: - /** - * The thread/runner which actually executes this transition. - */ + /// @brief The thread/runner which actually executes this transition. const runner_id_t executor; }; diff --git a/docs/design/include/mcmini/model/visible_object.hpp b/docs/design/include/mcmini/model/visible_object.hpp index 5156349a..543240ca 100644 --- a/docs/design/include/mcmini/model/visible_object.hpp +++ b/docs/design/include/mcmini/model/visible_object.hpp @@ -87,7 +87,7 @@ class visible_object final { /// @brief Extracts the current state from this object. /// @return a pointer to the current state of this object, or `nullptr` is the /// object contains no states. - std::unique_ptr consume_into_current_state() && { + std::unique_ptr consume_into_current_state() { if (history.empty()) { return nullptr; } diff --git a/docs/design/src/mcmini/model/diff_state.cpp b/docs/design/src/mcmini/model/diff_state.cpp new file mode 100644 index 00000000..7abee790 --- /dev/null +++ b/docs/design/src/mcmini/model/diff_state.cpp @@ -0,0 +1,80 @@ +#include "mcmini/model/state/diff_state.hpp" + +using namespace model; + +state::objid_t diff_state::get_objid_for_runner(runner_id_t id) const { + if (this->new_runners.count(id) > 0) { + return this->new_runners.at(id); + } else { + return this->base_state.get_objid_for_runner(id); + } +} + +bool diff_state::contains_object_with_id(objid_t id) const { + return this->new_object_states.count(id) > 0 || + this->base_state.contains_object_with_id(id); +} + +bool diff_state::contains_runner_with_id(objid_t id) const { + return this->new_runners.count(id) > 0 || + this->base_state.contains_runner_with_id(id); +} + +const visible_object_state *diff_state::get_state_of_object(objid_t id) const { + if (this->new_object_states.count(id) > 0) { + return this->new_object_states.at(id).get_current_state(); + } else { + return this->base_state.get_state_of_object(id); + } +} + +const visible_object_state *diff_state::get_state_of_runner( + runner_id_t id) const { + if (this->new_runners.count(id) > 0) { + return this->get_state_of_object(this->new_runners.at(id)); + } else { + return this->base_state.get_state_of_runner(id); + } +} + +state::objid_t diff_state::add_object( + std::unique_ptr initial_state) { + // The next id that would be assigned is one more than + // the largest id available. The last id of the base it `size() - 1` and + // we are `new_object_state.size()` elements in + state::objid_t next_id = base_state.count() + new_object_states.size(); + new_object_states[next_id] = visible_object(std::move(initial_state)); + return next_id; +} + +state::runner_id_t diff_state::add_runner( + std::unique_ptr initial_state) { + objid_t objid = this->add_object(std::move(initial_state)); + + // The next runner id would be the current size. + state::objid_t next_runner_id = runner_count(); + this->new_runners.insert({next_runner_id, objid}); + return next_runner_id; +} + +void diff_state::add_state_for_obj( + objid_t id, std::unique_ptr new_state) { + // Here we seek to insert all new states into the local cache instead of + // forwarding them onto the underlying base state. + visible_object &vobj = new_object_states[id]; + vobj.push_state(std::move(new_state)); +} + +void diff_state::add_state_for_runner( + runner_id_t id, std::unique_ptr new_state) { + this->add_state_for_obj(this->get_objid_for_runner(id), std::move(new_state)); +} + +std::unique_ptr diff_state::consume_obj( + objid_t id) && { + throw std::runtime_error("Consumption is not permitted on diff states"); +} + +std::unique_ptr diff_state::mutable_clone() const { + return extensions::make_unique(this->base_state); +} diff --git a/docs/design/src/mcmini/model/state_sequence.cpp b/docs/design/src/mcmini/model/state_sequence.cpp index 118a02bc..ca238703 100644 --- a/docs/design/src/mcmini/model/state_sequence.cpp +++ b/docs/design/src/mcmini/model/state_sequence.cpp @@ -195,168 +195,20 @@ std::unique_ptr state_sequence::element::mutable_clone() const { return state; } -//////// diff_state /////// - -class state_sequence::diff_state : public mutable_state { - private: - const state_sequence &base_state; - - public: - std::unordered_map new_object_states; - std::unordered_map new_runners; - - diff_state(const state_sequence &s) : base_state(s) {} - diff_state(const diff_state &ds) - : base_state(ds.base_state), new_object_states(ds.new_object_states) {} - diff_state(detached_state &&) = delete; - diff_state &operator=(const diff_state &) = delete; - detached_state &operator=(detached_state &&) = delete; - - /* `state` overrrides */ - virtual size_t count() const override { - size_t count = this->new_object_states.size() + base_state.count(); - for (const auto p : new_object_states) { - // Each item in `new_object_states` that is also in `base_state` defines - // states for _previously existing_ objects. These objects are accounted - // for in `count` (double-counted), hence the `--` - if (base_state.contains_object_with_id(p.first)) count--; - } - return count; - } - size_t runner_count() const override { - return base_state.runner_count() + new_runners.size(); - } - objid_t get_objid_for_runner(runner_id_t id) const override; - bool contains_object_with_id(state::objid_t id) const override; - bool contains_runner_with_id(runner_id_t id) const override; - const visible_object_state *get_state_of_object(objid_t id) const override; - const visible_object_state *get_state_of_runner( - runner_id_t id) const override; - objid_t add_object( - std::unique_ptr initial_state) override; - runner_id_t add_runner( - std::unique_ptr initial_state) override; - void add_state_for_obj( - objid_t id, std::unique_ptr new_state) override; - void add_state_for_runner( - runner_id_t id, std::unique_ptr new_state) override; - std::unique_ptr consume_obj(objid_t id) && - override; - std::unique_ptr mutable_clone() const override; -}; - std::unique_ptr state_sequence::mutable_clone() const { - return extensions::make_unique(*this); - // return extensions::make_unique(this); - // return state::from_visible_objects( - // this->visible_objects.cbegin(), this->visible_objects.cend()); -} - -state::objid_t state_sequence::diff_state::get_objid_for_runner( - runner_id_t id) const { - if (this->new_runners.count(id) > 0) { - return this->new_runners.at(id); - } else { - return this->base_state.get_objid_for_runner(id); + auto detached_ss = extensions::make_unique(*this); + for (const auto &robjid : runner_to_obj_map) { + detached_ss->add_runner(get_state_of_object(robjid)->clone()); } -} - -bool state_sequence::diff_state::contains_object_with_id(objid_t id) const { - return this->new_object_states.count(id) > 0 || - this->base_state.contains_object_with_id(id); -} - -bool state_sequence::diff_state::contains_runner_with_id(objid_t id) const { - return this->new_runners.count(id) > 0 || - this->base_state.contains_runner_with_id(id); -} - -const visible_object_state *state_sequence::diff_state::get_state_of_object( - objid_t id) const { - if (this->new_object_states.count(id) > 0) { - return this->new_object_states.at(id).get_current_state(); - } else { - return this->base_state.get_state_of_object(id); - } -} - -const visible_object_state *state_sequence::diff_state::get_state_of_runner( - runner_id_t id) const { - if (this->new_runners.count(id) > 0) { - return this->get_state_of_object(this->new_runners.at(id)); - } else { - return this->base_state.get_state_of_runner(id); - } -} - -state::objid_t state_sequence::diff_state::add_object( - std::unique_ptr initial_state) { - // The next id that would be assigned is one more than - // the largest id available. The last id of the base it `size() - 1` and - // we are `new_object_state.size()` elements in - state::objid_t next_id = - base_state.visible_objects.size() + new_object_states.size(); - new_object_states[next_id] = visible_object(std::move(initial_state)); - return next_id; -} - -state::runner_id_t state_sequence::diff_state::add_runner( - std::unique_ptr initial_state) { - objid_t objid = this->add_object(std::move(initial_state)); - - // The next runner id would be the current size. - state::objid_t next_runner_id = runner_count(); - this->new_runners.insert({next_runner_id, objid}); - return next_runner_id; -} - -void state_sequence::diff_state::add_state_for_obj( - objid_t id, std::unique_ptr new_state) { - // Here we seek to insert all new states into the local cache instead of - // forwarding them onto the underlying base state. - visible_object &vobj = new_object_states[id]; - vobj.push_state(std::move(new_state)); -} - -void state_sequence::diff_state::add_state_for_runner( - runner_id_t id, std::unique_ptr new_state) { - this->add_state_for_obj(this->get_objid_for_runner(id), std::move(new_state)); -} - -std::unique_ptr -state_sequence::diff_state::consume_obj(objid_t id) && { - throw std::runtime_error("Consumption is not permitted on diff states"); -} - -std::unique_ptr state_sequence::diff_state::mutable_clone() - const { - return extensions::make_unique(this->base_state); + return detached_ss; } transition::status state_sequence::follow(const transition &t) { - // Supply the transition a `diff_state` intentionally. We have control over - // the `apply_to` function: it simply returns a clone of the state it was - // provided. A `diff_state` copies only the reference to its underlying - // "backing" state, and only new objects will be - diff_state ds(*this); - auto maybe_diff = t.apply_to(ds); - - if (maybe_diff != nullptr) { - // Apply the diff to the sequence itself and create a new element which - // refers to the latest states of all the objects. The element is a - // placeholder for the state of the sequence as it looks after the - // application of transition `t`. Later if the sequenced is queried for - // the state of the object. - // - // Assumes that the `mutable_clone()` of diff_state is also a - // `diff_state` and that `apply_to()` returns the mutated clone. Since these - // are implementation details under our control, this assumption holds - const diff_state &ds_after_t = static_cast(*maybe_diff); - - for (auto new_state : ds_after_t.new_object_states) + auto result = t.apply_to(*this); + if (result.second == model::transition::status::exists) { + for (auto &new_state : result.first.new_object_states) this->visible_objects[new_state.first].push_state( - std::move(new_state.second).consume_into_current_state()); - + new_state.second.consume_into_current_state()); this->push_state_snapshot(); return transition::status::exists; } From 66689b8078eb53460db50ff5b00d22da2e6b4cc7 Mon Sep 17 00:00:00 2001 From: Maxwell Pirtle Date: Tue, 14 May 2024 20:33:27 -0400 Subject: [PATCH 034/161] Demonstrate callbacks with new DPOR version --- .../include/mcmini/lib/shared_transition.h | 1 - .../mcmini/model/pending_transitions.hpp | 4 +- .../mcmini/model/transition_registry.hpp | 10 ++--- .../mcmini/model_checking/algorithm.hpp | 16 ++++---- docs/design/src/lib/wrappers.c | 6 +++ docs/design/src/mcmini/mcmini.cpp | 41 ++++++++++++++++--- .../algorithms/classic_dpor.cpp | 5 +++ 7 files changed, 62 insertions(+), 21 deletions(-) diff --git a/docs/design/include/mcmini/lib/shared_transition.h b/docs/design/include/mcmini/lib/shared_transition.h index d523e21d..8d6f8f72 100644 --- a/docs/design/include/mcmini/lib/shared_transition.h +++ b/docs/design/include/mcmini/lib/shared_transition.h @@ -7,7 +7,6 @@ typedef enum { MUTEX_INIT_TYPE, MUTEX_LOCK_TYPE, MUTEX_UNLOCK_TYPE, - THREAD_CREATE_TYPE, THREAD_JOIN_TYPE, THREAD_EXIT_TYPE, diff --git a/docs/design/include/mcmini/model/pending_transitions.hpp b/docs/design/include/mcmini/model/pending_transitions.hpp index adac5c14..a0ccd479 100644 --- a/docs/design/include/mcmini/model/pending_transitions.hpp +++ b/docs/design/include/mcmini/model/pending_transitions.hpp @@ -1,7 +1,7 @@ #pragma once +#include #include -#include #include "mcmini/model/defines.hpp" #include "mcmini/model/transition.hpp" @@ -20,7 +20,7 @@ namespace model { struct pending_transitions final { private: using runner_id_t = ::runner_id_t; - std::unordered_map> _contents; + std::map> _contents; public: auto begin() -> decltype(_contents.begin()) { return _contents.begin(); } diff --git a/docs/design/include/mcmini/model/transition_registry.hpp b/docs/design/include/mcmini/model/transition_registry.hpp index b14b9c51..459d8b25 100644 --- a/docs/design/include/mcmini/model/transition_registry.hpp +++ b/docs/design/include/mcmini/model/transition_registry.hpp @@ -38,7 +38,8 @@ class transition_registry final { * associate with the returned id * @returns a positive integer which conceptually represents the transition. */ - runtime_type_id register_transition(transition_discovery_callback callback) { + void register_transition(rttid rttid, + transition_discovery_callback callback) { // TODO: Mapping between types and the serialization // function pointers. For plugins loaded by McMini, each will have the // chance to register the transitions it defines. Here the RTTI needs to @@ -46,8 +47,7 @@ class transition_registry final { // here. See the `ld` man page and specifically the two linker flags // `--dynamic-list-cpp-typeinfo` and `-E` for details. `-E` is definitely // sufficient it seems in my small testing - runtime_callbacks.push_back(callback); - return runtime_callbacks.size() - 1; + runtime_callbacks.insert({rttid, callback}); } /** @@ -73,12 +73,12 @@ class transition_registry final { * assigned id `rttid`, or `nullptr` if no such `rttid` has been registered. */ transition_discovery_callback get_callback_for(runtime_type_id rttid) { - if (this->runtime_callbacks.size() <= rttid) return nullptr; + if (this->runtime_callbacks.count(rttid) == 0) return nullptr; return this->runtime_callbacks.at(rttid); } private: - std::vector runtime_callbacks; + std::unordered_map runtime_callbacks; }; } // namespace model \ No newline at end of file diff --git a/docs/design/include/mcmini/model_checking/algorithm.hpp b/docs/design/include/mcmini/model_checking/algorithm.hpp index 86126ff8..5cbcd15b 100644 --- a/docs/design/include/mcmini/model_checking/algorithm.hpp +++ b/docs/design/include/mcmini/model_checking/algorithm.hpp @@ -1,5 +1,7 @@ #pragma once +#include + #include "mcmini/coordinator/coordinator.hpp" #include "mcmini/model/program.hpp" @@ -11,16 +13,14 @@ namespace model_checking { */ class algorithm { public: - // TODO: Eventually we may want to pass more information to the callbacks - // (e.g. the algorithm itself or the coordinator) to provide detailed printing - // information. - // TODO: struct callbacks { public: - virtual void encountered_unknown_error_in(const model::program &) {} - virtual void encountered_deadlock_in(const model::program &) {} - virtual void encountered_crash_in(const model::program &) {} - virtual void encountered_data_race_in(const model::program &) {} + callbacks() = default; + std::function crash; + std::function deadlock; + std::function data_race; + std::function unknown_error; + std::function trace_completed; }; /** diff --git a/docs/design/src/lib/wrappers.c b/docs/design/src/lib/wrappers.c index 0ba0797e..9184b296 100644 --- a/docs/design/src/lib/wrappers.c +++ b/docs/design/src/lib/wrappers.c @@ -52,6 +52,12 @@ int mc_pthread_mutex_unlock(pthread_mutex_t *mutex) { } void mc_exit_main_thread(void) { + // IMPORTANT: This is NOT a typo! + // 1. In the first case, McMini models the thread as alive + // but about to exit + // 2. In the second case, McMini models the thread as having exited. + thread_get_mailbox()->type = THREAD_EXIT_TYPE; + thread_await_scheduler(); thread_get_mailbox()->type = THREAD_EXIT_TYPE; thread_await_scheduler(); } diff --git a/docs/design/src/mcmini/mcmini.cpp b/docs/design/src/mcmini/mcmini.cpp index c2254363..714449e3 100644 --- a/docs/design/src/mcmini/mcmini.cpp +++ b/docs/design/src/mcmini/mcmini.cpp @@ -1,6 +1,7 @@ #include "mcmini/mcmini.hpp" #include "mcmini/coordinator/coordinator.hpp" +#include "mcmini/lib/shared_transition.h" #include "mcmini/mem.h" #include "mcmini/misc/ddt.hpp" #include "mcmini/misc/extensions/unique_ptr.hpp" @@ -9,6 +10,7 @@ #include "mcmini/model/transitions/mutex/mutex_init.hpp" #include "mcmini/model/transitions/mutex/mutex_lock.hpp" #include "mcmini/model/transitions/mutex/mutex_unlock.hpp" +#include "mcmini/model/transitions/thread/thread_exit.hpp" #include "mcmini/model/transitions/thread/thread_start.hpp" #include "mcmini/model_checking/algorithms/classic_dpor.hpp" #include "mcmini/real_world/process/fork_process_source.hpp" @@ -23,6 +25,7 @@ #include #include +#include #include using namespace extensions; @@ -34,6 +37,24 @@ void display_usage() { std::exit(EXIT_FAILURE); } +void finished_trace_classic_dpor(const coordinator& c) { + static uint32_t trace_id = 0; + + std::stringstream ss; + const auto& program_model = c.get_current_program_model(); + ss << "TRACE " << trace_id << "\n"; + for (const auto& t : program_model.get_trace()) { + ss << "thread " << t->get_executor() << ": " << t->to_string() << "\n"; + } + ss << "\nNEXT THREAD OPERATIONS\n"; + for (const auto& tpair : program_model.get_pending_transitions()) { + ss << "thread " << tpair.first << ": " << tpair.second->to_string() << "\n"; + } + std::cout << ss.str(); + std::cout.flush(); + trace_id++; +} + std::unique_ptr mutex_init_callback( state::runner_id_t p, const volatile runner_mailbox& rmb, model_to_system_map& m) { @@ -63,7 +84,13 @@ std::unique_ptr mutex_unlock_callback( memcpy_v(&remote_mut, (volatile void*)rmb.cnts, sizeof(pthread_mutex_t*)); state::objid_t mut = m.observe_remote_process_handle(remote_mut, objects::mutex::make()); - return make_unique(p, mut); + return make_unique(p, mut); +} + +std::unique_ptr thread_exit_callback( + state::runner_id_t p, const volatile runner_mailbox& rmb, + model_to_system_map& m) { + return make_unique(p); } void do_model_checking( @@ -86,9 +113,10 @@ void do_model_checking( // beginning). auto process_source = make_unique("hello-world"); - tr.register_transition(&mutex_init_callback); - tr.register_transition(&mutex_lock_callback); - tr.register_transition(&mutex_unlock_callback); + tr.register_transition(MUTEX_INIT_TYPE, &mutex_init_callback); + tr.register_transition(MUTEX_LOCK_TYPE, &mutex_lock_callback); + tr.register_transition(MUTEX_UNLOCK_TYPE, &mutex_unlock_callback); + tr.register_transition(THREAD_EXIT_TYPE, &thread_exit_callback); coordinator coordinator(std::move(model_for_program_starting_at_main), std::move(tr), std::move(process_source)); @@ -96,7 +124,10 @@ void do_model_checking( std::unique_ptr classic_dpor_checker = make_unique(); - classic_dpor_checker->verify_using(coordinator); + model_checking::algorithm::callbacks c; + c.trace_completed = &finished_trace_classic_dpor; + + classic_dpor_checker->verify_using(coordinator, c); std::cout << "Model checking completed!" << std::endl; } diff --git a/docs/design/src/mcmini/model_checking/algorithms/classic_dpor.cpp b/docs/design/src/mcmini/model_checking/algorithms/classic_dpor.cpp index 5bd83804..affde124 100644 --- a/docs/design/src/mcmini/model_checking/algorithms/classic_dpor.cpp +++ b/docs/design/src/mcmini/model_checking/algorithms/classic_dpor.cpp @@ -70,6 +70,11 @@ void classic_dpor::verify_using(coordinator &coordinator, } } + // TODO: Check for deadlock + // TODO: Check for the program crashing + + callbacks.trace_completed(coordinator); + // 3. Backtrack phase do { // Locate a spot that contains backtrack threads From de73142452bb1f194b5b7e8b2a64fdbb599b6f47 Mon Sep 17 00:00:00 2001 From: Maxwell Pirtle Date: Wed, 15 May 2024 09:15:57 -0400 Subject: [PATCH 035/161] Add `mcmini_shm_file` to organize shared memory --- .../design/include/mcmini/common/shm_config.h | 7 ++++++ .../include/mcmini/lib/shared_transition.h | 11 +++++--- .../real_world/process/template_process.h | 25 +++++++++++-------- docs/design/src/lib/entry.c | 3 ++- docs/design/src/lib/wrappers.c | 3 +-- .../mcmini/real_world/fork_process_source.cpp | 5 ++-- .../mcmini/real_world/local_linux_process.cpp | 4 +-- 7 files changed, 37 insertions(+), 21 deletions(-) diff --git a/docs/design/include/mcmini/common/shm_config.h b/docs/design/include/mcmini/common/shm_config.h index ee96f64c..a831b9d8 100644 --- a/docs/design/include/mcmini/common/shm_config.h +++ b/docs/design/include/mcmini/common/shm_config.h @@ -8,10 +8,17 @@ extern "C" { #include #include "mcmini/defines.h" +#include "mcmini/real_world/mailbox/runner_mailbox.h" +#include "mcmini/real_world/process/template_process.h" const extern size_t shm_size; void mc_get_shm_handle_name(char *dst, size_t sz); +struct mcmini_shm_file { + struct template_process_t tpt; + runner_mailbox mailboxes[MAX_TOTAL_THREADS_IN_PROGRAM]; +}; + #ifdef __cplusplus } #endif // extern "C" \ No newline at end of file diff --git a/docs/design/include/mcmini/lib/shared_transition.h b/docs/design/include/mcmini/lib/shared_transition.h index 8d6f8f72..b3e31ba0 100644 --- a/docs/design/include/mcmini/lib/shared_transition.h +++ b/docs/design/include/mcmini/lib/shared_transition.h @@ -1,7 +1,10 @@ -#ifndef INCLUDE_SHARED_TRANSITION_H -#define INCLUDE_SHARED_TRANSITION_H +#pragma once #include +#ifdef __cplusplus +extern "C" { +#endif + typedef uint64_t tid_t; typedef enum { MUTEX_INIT_TYPE, @@ -24,4 +27,6 @@ struct shared_transition { transition_type type; }; -#endif \ No newline at end of file +#ifdef __cplusplus +} +#endif // extern "C" diff --git a/docs/design/include/mcmini/real_world/process/template_process.h b/docs/design/include/mcmini/real_world/process/template_process.h index 02eaa078..f378b70e 100644 --- a/docs/design/include/mcmini/real_world/process/template_process.h +++ b/docs/design/include/mcmini/real_world/process/template_process.h @@ -5,26 +5,31 @@ extern "C" { #endif #include +#include #define TEMPLATE_FORK_FAILED (-2) // fork(2) failed in the template struct template_process_t { - - // Unused in most cases, but set in the event of an error after - // calling a C function in the template process + /// Unused in most cases, but set in the event of an error after + /// calling a C function in the template process (e.g. after `fork(2)` fails). int err; - // The current process id of the child process currently under control of this - // template process. + /// The process id of the latest `fork()` of the template process, or + /// `TEMPLATE_FORK_FAILED` in the event that `fork(2)` in the template process + /// failed. pid_t cpid; - // A semaphore that the McMini process waits on and that - // the template process signals after writing the newly spawned pid to shared - // memory. + /// If a `siginfo_t` was passed to the template process from its current + /// child, the template will populate this value. + siginfo_t sa_cinfo; + + /// A semaphore that the McMini process waits on and that + /// the template process signals after writing the newly spawned pid to shared + /// memory. sem_t mcmini_process_sem; - // A semphore that `libmcmini.so` waits on and that the McMini process signals - // when it wants to spawn a new process. + /// A semphore that `libmcmini.so` waits on and that the McMini process + /// signals when it wants to spawn a new process. sem_t libmcmini_sem; }; diff --git a/docs/design/src/lib/entry.c b/docs/design/src/lib/entry.c index bf208810..c78d1030 100644 --- a/docs/design/src/lib/entry.c +++ b/docs/design/src/lib/entry.c @@ -81,7 +81,8 @@ void mc_exit(int status) { } void mc_template_process_loop_forever(void) { - volatile struct template_process_t *tpt = global_shm_start; + volatile struct mcmini_shm_file *shm_file = global_shm_start; + volatile struct template_process_t *tpt = &shm_file->tpt; while (1) { // Before creating any more children, ensure that the previous one has // definitely stopped execution diff --git a/docs/design/src/lib/wrappers.c b/docs/design/src/lib/wrappers.c index 9184b296..15cc587f 100644 --- a/docs/design/src/lib/wrappers.c +++ b/docs/design/src/lib/wrappers.c @@ -5,8 +5,7 @@ #include "mcmini/mcmini.h" volatile runner_mailbox *thread_get_mailbox() { - return ((volatile runner_mailbox *)(global_shm_start + THREAD_SHM_OFFSET)) + - tid_self; + return &((volatile struct mcmini_shm_file *)(global_shm_start))->mailboxes[tid_self]; } void thread_await_scheduler() { diff --git a/docs/design/src/mcmini/real_world/fork_process_source.cpp b/docs/design/src/mcmini/real_world/fork_process_source.cpp index 02c98672..1653febe 100644 --- a/docs/design/src/mcmini/real_world/fork_process_source.cpp +++ b/docs/design/src/mcmini/real_world/fork_process_source.cpp @@ -80,7 +80,7 @@ std::unique_ptr fork_process_source::make_new_process() { // process and then wait for it to successfully call `fork(2)` to tell us // about its new child. const volatile template_process_t* tstruct = - rw_region->as(); + &(rw_region->as()->tpt); if (sem_post((sem_t*)&tstruct->libmcmini_sem) != 0) { throw process_source::process_creation_exception( @@ -219,8 +219,7 @@ void fork_process_source::reset_binary_semaphores_for_new_process() { // // INVARIANT: Only one `local_linux_process` is in existence at any given // time. - volatile runner_mailbox* mbp = - rw_region->as_array_of(0, THREAD_SHM_OFFSET); + volatile runner_mailbox* mbp = (rw_region->as()->mailboxes); const int max_total_threads = MAX_TOTAL_THREADS_IN_PROGRAM; for (int i = 0; i < max_total_threads; i++) { mc_runner_mailbox_destroy(mbp + i); diff --git a/docs/design/src/mcmini/real_world/local_linux_process.cpp b/docs/design/src/mcmini/real_world/local_linux_process.cpp index 0179b04d..5ccb334b 100644 --- a/docs/design/src/mcmini/real_world/local_linux_process.cpp +++ b/docs/design/src/mcmini/real_world/local_linux_process.cpp @@ -8,7 +8,7 @@ #include #include -#include "mcmini/defines.h" +#include "mcmini/common/shm_config.h" #include "mcmini/misc/extensions/unique_ptr.hpp" #include "mcmini/real_world/mailbox/runner_mailbox.h" #include "mcmini/real_world/process/fork_process_source.hpp" @@ -38,7 +38,7 @@ local_linux_process::~local_linux_process() { volatile runner_mailbox *local_linux_process::execute_runner(runner_id_t id) { volatile runner_mailbox *rmb = - shm_slice.as_array_of(id, THREAD_SHM_OFFSET); + &(shm_slice.as_array_of()->mailboxes[id]); // TODO: As a sanity check, a `waitpid()` to check if the process is still // alive is probably warranted. This would prevent a deadlock in _most_ cases. From 01e05087276ac68e22f03d8362ab5d0c7bb34924 Mon Sep 17 00:00:00 2001 From: Maxwell Pirtle Date: Wed, 15 May 2024 09:47:14 -0400 Subject: [PATCH 036/161] Add prctl(2) call in the McMini and child processes We use prctl(2) to deliver a `SIGTERM` to the template process and to the child process in the event that either the `McMini` process or the parent process exit unexpectedly. We should note that this could potentially be risky. If the template process fails (which would only occur if `libmcmini.so` has a bug most likely), the child will also die, but McMini will not be notified of this. The solution would be to check for the reception of a signal when sleeping on the semaphores and checking whether an exceptional condition occured --- .../mcmini/coordinator/coordinator.hpp | 20 ++++++++++ .../design/include/mcmini/model/exception.hpp | 10 +++++ docs/design/src/lib/entry.c | 38 +++++++++++++------ .../src/mcmini/coordinator/coordinator.cpp | 6 +-- .../mcmini/real_world/fork_process_source.cpp | 21 +++++++--- 5 files changed, 75 insertions(+), 20 deletions(-) create mode 100644 docs/design/include/mcmini/model/exception.hpp diff --git a/docs/design/include/mcmini/coordinator/coordinator.hpp b/docs/design/include/mcmini/coordinator/coordinator.hpp index 3ed6dca2..1d09d602 100644 --- a/docs/design/include/mcmini/coordinator/coordinator.hpp +++ b/docs/design/include/mcmini/coordinator/coordinator.hpp @@ -170,6 +170,26 @@ class coordinator { std::unordered_map, model::state::objid_t> system_address_mapping; + void assign_new_process_handle() { + // NOTE: This is INTENTIONALLY split into two separate statements, i.e. + // assiging `nullptr` and THEN calling `force_new_process()`. We want to + // destroy the old process first before we create a new one. This is + // necessary because the processes generated by the process source may share + // resources, so we want to be sure that any shared resources are + // relinquished before creating a new process. + // + // The trouble is that C++ will evaluate the right-hand expression before + // the assignment, which means that there will be TWO processes at the same + // time for a brief moment in between the right-hand evaluation, the + // assignment, and the destruction of the previous process handle. This has + // the potential to introduce a race condition which is bad. By splitting + // the statement into two, we ensure that C++ first destroys the current + // handle and THEN creates the new one, but not concurrently (see C++ + // evaluation ordering on cppreference for more details). + this->current_process_handle = nullptr; + this->current_process_handle = this->process_source->force_new_process(); + } + // Allow modifications through the `model_to_system_map` (impl detail) friend model_to_system_map; }; \ No newline at end of file diff --git a/docs/design/include/mcmini/model/exception.hpp b/docs/design/include/mcmini/model/exception.hpp new file mode 100644 index 00000000..8ba716a3 --- /dev/null +++ b/docs/design/include/mcmini/model/exception.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include +#include + +namespace model { + +/// @brief An error that is raised when a program encounters undefined behavior. +struct undefined_behavior : public std::exception {}; +} // namespace model \ No newline at end of file diff --git a/docs/design/src/lib/entry.c b/docs/design/src/lib/entry.c index c78d1030..d6160814 100644 --- a/docs/design/src/lib/entry.c +++ b/docs/design/src/lib/entry.c @@ -15,6 +15,7 @@ #include #include #include +#include #include "mcmini/mcmini.h" @@ -80,6 +81,29 @@ void mc_exit(int status) { _Exit(status); } +void mc_prepare_new_child_process_spawned(pid_t ppid_before_fork) { + // This is important to handle the case when the + // main thread hits return 0; in that case, we + // keep the process alive to allow the model checker to + // continue working + // + // NOTE: `atexit()`-handlers can be invoked when a dynamic + // library is unloaded. When we integrate DMTCP, we may need + // to consider this. + atexit(&mc_exit_main_thread); + + // IMPORTANT: If the THREAD in the template process ever exits, this will prove + // problematic as it is when the THREAD which called `fork()` exits that the + // signal will be delivered to this process. + if (prctl(PR_SET_PDEATHSIG, SIGTERM) == -1) { perror("prctl"); abort(); } + + // HANDLE RACE CONDITION! It's possible that the parent exited BEFORE the call + // to `prctl()` above. To test if this is the case, we check whether the current + // parent process id differs from the process id of the parent which fork()-ed this + // process. If they don't match, we know that the parent exited before prctl() + if (getppid() != ppid_before_fork) exit(EXIT_FAILURE); +} + void mc_template_process_loop_forever(void) { volatile struct mcmini_shm_file *shm_file = global_shm_start; volatile struct template_process_t *tpt = &shm_file->tpt; @@ -88,23 +112,15 @@ void mc_template_process_loop_forever(void) { // definitely stopped execution wait(NULL); sem_wait((sem_t *)&tpt->libmcmini_sem); - pid_t cpid = fork(); + const pid_t ppid_before_fork = getpid(); + const pid_t cpid = fork(); if (cpid == -1) { // `fork()` failed tpt->err = errno; tpt->cpid = TEMPLATE_FORK_FAILED; } else if (cpid == 0) { // Child case: Simply return and escape into the child process. - - // This is important to handle the case when the - // main thread hits return 0; in that case, we - // keep the process alive to allow the model checker to - // continue working - // - // NOTE: `atexit()`-handlers can be invoked when a dynamic - // library is unloaded. When we integrate DMTCP, we may need - // to consider this. - atexit(&mc_exit_main_thread); + mc_prepare_new_child_process_spawned(ppid_before_fork); return; } // `libmcmini.so` acting as a template process. diff --git a/docs/design/src/mcmini/coordinator/coordinator.cpp b/docs/design/src/mcmini/coordinator/coordinator.cpp index c9ca9169..41ced8d8 100644 --- a/docs/design/src/mcmini/coordinator/coordinator.cpp +++ b/docs/design/src/mcmini/coordinator/coordinator.cpp @@ -13,8 +13,7 @@ coordinator::coordinator( : current_program_model(std::move(initial_state)), runtime_transition_mapping(std::move(runtime_transition_mapping)), process_source(std::move(process_source)) { - this->current_process_handle = nullptr; - this->current_process_handle = this->process_source->force_new_process(); + this->assign_new_process_handle(); } void coordinator::execute_runner(process::runner_id_t runner_id) { @@ -52,9 +51,8 @@ void coordinator::execute_runner(process::runner_id_t runner_id) { } void coordinator::return_to_depth(uint32_t n) { + this->assign_new_process_handle(); this->current_program_model.restore_model_at_depth(n); - this->current_process_handle = nullptr; - this->current_process_handle = this->process_source->force_new_process(); // Now regenerate the process from scratch. The new process handle has a state // represented in the model as the state at depth `n = 0` in the state diff --git a/docs/design/src/mcmini/real_world/fork_process_source.cpp b/docs/design/src/mcmini/real_world/fork_process_source.cpp index 1653febe..9d031283 100644 --- a/docs/design/src/mcmini/real_world/fork_process_source.cpp +++ b/docs/design/src/mcmini/real_world/fork_process_source.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -149,7 +150,15 @@ void fork_process_source::make_new_template_process() { char* args[] = {const_cast(this->target_program.c_str()), NULL /*TODO: Add additional arguments here if needed */}; setenv("libmcmini-template-loop", "1", 1); + + // Ensures that addresses in the template process remain "stable" personality(ADDR_NO_RANDOMIZE); + + // Ensures that the template process is sent a SIGTERM if THIS THREAD ever + // exits. Since McMini is currently single-threaded, this is equivalent to + // saying if McMini ever exits. Note that this `prctl(2)` persists across + // `execvp(2)`. + prctl(PR_SET_PDEATHSIG, SIGTERM); execvp(this->target_program.c_str(), args); unsetenv("libmcmini-template-loop"); @@ -161,19 +170,21 @@ void fork_process_source::make_new_template_process() { // @note: We invoke `quick_exit()` here to ensure that C++ static // objects are NOT destroyed. `std::exit()` will invoke the destructors - // of such static objects. This is only intended to happen exactly once - // however; bad things likely would happen to a program which called the - // destructor on an object that already cleaned up its resources. + // of such static objects, among other cleanup. This is only intended to + // happen exactly once however; bad things likely would happen to a program + // which called the destructor on an object that already cleaned up its + // resources. // // We must remember that this child is in a completely separate process with // a completely separate address space, but the shared resources that the // McMini process holds onto will also (inadvertantly) be shared with the - // child. To get C++ to play nicely, this is how we do it. + // child. We want the resources to be destroyed in the MCMINI process, NOT + // this (failed) child fork(). To get C++ to play nicely, this is how we do + // it. std::quick_exit(EXIT_FAILURE); // ****************** // Child process case // ****************** - } else { // ******************* // Parent process case From a24e6dd92a08349f654529cbb2864cab440d0789 Mon Sep 17 00:00:00 2001 From: Maxwell Pirtle Date: Wed, 15 May 2024 18:26:07 -0400 Subject: [PATCH 037/161] Add `mc_pthread_create` logic --- docs/design/include/mcmini/defines.h | 2 +- docs/design/include/mcmini/lib/entry.h | 1 + .../mcmini/spy/intercept/interception.h | 10 ++- .../include/mcmini/spy/intercept/wrappers.h | 2 + docs/design/src/lib/entry.c | 12 ++- docs/design/src/lib/interception.c | 24 +++++- docs/design/src/lib/wrappers.c | 84 ++++++++++++++++++- 7 files changed, 125 insertions(+), 10 deletions(-) diff --git a/docs/design/include/mcmini/defines.h b/docs/design/include/mcmini/defines.h index 84c7f0a2..57a0b45b 100644 --- a/docs/design/include/mcmini/defines.h +++ b/docs/design/include/mcmini/defines.h @@ -28,8 +28,8 @@ #define THREAD_SHM_OFFSET (128) +typedef uint32_t runner_id_t; typedef uint64_t tid_t; -typedef uint64_t mutid_t; typedef uint64_t trid_t; #define TID_MAIN_THREAD (0ul) #define TID_INVALID (-1ul) // ULONG_MAX diff --git a/docs/design/include/mcmini/lib/entry.h b/docs/design/include/mcmini/lib/entry.h index bdb3bc2b..f7cbb68d 100644 --- a/docs/design/include/mcmini/lib/entry.h +++ b/docs/design/include/mcmini/lib/entry.h @@ -6,6 +6,7 @@ extern volatile void *global_shm_start; extern MCMINI_THREAD_LOCAL tid_t tid_self; void mc_exit(int); +tid_t mc_register_this_thread(void); diff --git a/docs/design/include/mcmini/spy/intercept/interception.h b/docs/design/include/mcmini/spy/intercept/interception.h index b8dc2864..e2010f2c 100644 --- a/docs/design/include/mcmini/spy/intercept/interception.h +++ b/docs/design/include/mcmini/spy/intercept/interception.h @@ -15,9 +15,17 @@ int pthread_mutex_init(pthread_mutex_t *mutex, int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex); +int pthread_create(pthread_t *thread, const pthread_attr_t *attr, + void *(*routine)(void *), void *arg); +int libpthread_pthread_create(pthread_t *thread, const pthread_attr_t *attr, + void *(*routine)(void *), void *arg); + +int libpthread_sem_init(sem_t*, int, int); +int libpthread_sem_post(sem_t*); +int libpthread_sem_wait(sem_t*); void exit(int); MCMINI_NO_RETURN void libc_exit(int); void abort(void); -MCMINI_NO_RETURN void libc_abort(void); \ No newline at end of file +MCMINI_NO_RETURN void libc_abort(void); diff --git a/docs/design/include/mcmini/spy/intercept/wrappers.h b/docs/design/include/mcmini/spy/intercept/wrappers.h index 4eca8b69..6bcd6648 100644 --- a/docs/design/include/mcmini/spy/intercept/wrappers.h +++ b/docs/design/include/mcmini/spy/intercept/wrappers.h @@ -14,6 +14,8 @@ int mc_pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr); int mc_pthread_mutex_lock(pthread_mutex_t *mutex); int mc_pthread_mutex_unlock(pthread_mutex_t *mutex); +int mc_pthread_create(pthread_t *thread, const pthread_attr_t *attr, + void *(*routine)(void *), void *arg); /* An `atexit()` handler is installed in libmcmini.so with this function. diff --git a/docs/design/src/lib/entry.c b/docs/design/src/lib/entry.c index d6160814..82d16345 100644 --- a/docs/design/src/lib/entry.c +++ b/docs/design/src/lib/entry.c @@ -23,6 +23,14 @@ volatile void *global_shm_start = NULL; MCMINI_THREAD_LOCAL tid_t tid_self = TID_INVALID; tid_t mc_register_this_thread(void) { + // NOTE: It is an internal error for more than one thread + // to be executing this function. If the model checker maintains + // control over each thread, it will only enable a single + // thread to execute this function at once. Anything else + // is undefined behavior + // + // NOTE: If `McMini` introduces parallelism into the + // model-checking process, this would have to be adjusted. static tid_t tid_next = 0; tid_self = tid_next++; return tid_self; @@ -81,7 +89,7 @@ void mc_exit(int status) { _Exit(status); } -void mc_prepare_new_child_process_spawned(pid_t ppid_before_fork) { +void mc_prepare_new_child_process(pid_t ppid_before_fork) { // This is important to handle the case when the // main thread hits return 0; in that case, we // keep the process alive to allow the model checker to @@ -120,7 +128,7 @@ void mc_template_process_loop_forever(void) { tpt->cpid = TEMPLATE_FORK_FAILED; } else if (cpid == 0) { // Child case: Simply return and escape into the child process. - mc_prepare_new_child_process_spawned(ppid_before_fork); + mc_prepare_new_child_process(ppid_before_fork); return; } // `libmcmini.so` acting as a template process. diff --git a/docs/design/src/lib/interception.c b/docs/design/src/lib/interception.c index 81776541..6c18d6d1 100644 --- a/docs/design/src/lib/interception.c +++ b/docs/design/src/lib/interception.c @@ -53,10 +53,30 @@ int pthread_mutex_unlock(pthread_mutex_t *mutex) { return mc_pthread_mutex_unlock(mutex); } -void exit(int status) { mc_transparent_exit(status); } +int pthread_create(pthread_t *thread, const pthread_attr_t *attr, + void *(*routine)(void *), void *arg) { + return mc_pthread_create(thread, attr, routine, arg); + } + +void exit(int status) { mc_transparent_exit(status); } void abort(void) { mc_transparent_abort(); } + +// Forwarding methods to the underlying libraries MCMINI_NO_RETURN void libc_abort(void) { (*abort_ptr)(); } +MCMINI_NO_RETURN void libc_exit(int status) { (*exit_ptr)(status); } -MCMINI_NO_RETURN void libc_exit(int status) { (*exit_ptr)(status); } \ No newline at end of file +int libpthread_pthread_create(pthread_t *thread, const pthread_attr_t *attr, + void *(*routine)(void *), void *arg) { + return (*pthread_create_ptr)(thread, attr, routine, arg); +} +int libpthread_sem_init(sem_t* sem, int pshared, int value) { + return (*sem_init_ptr)(sem, pshared, value); +} +int libpthread_sem_post(sem_t*sem) { + return (*sem_post_ptr)(sem); +} +int libpthread_sem_wait(sem_t*sem) { + return (*sem_wait_ptr)(sem); +} \ No newline at end of file diff --git a/docs/design/src/lib/wrappers.c b/docs/design/src/lib/wrappers.c index 15cc587f..691aca33 100644 --- a/docs/design/src/lib/wrappers.c +++ b/docs/design/src/lib/wrappers.c @@ -1,6 +1,7 @@ #include #include #include +#include #include "mcmini/mcmini.h" @@ -25,6 +26,12 @@ void thread_awake_scheduler_for_thread_finish_transition() { mc_wake_scheduler(thread_get_mailbox()); } +void thread_block_indefinitely() { + while (1) { + pause(); + } +} + int mc_pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr) { volatile runner_mailbox *mb = thread_get_mailbox(); @@ -50,15 +57,31 @@ int mc_pthread_mutex_unlock(pthread_mutex_t *mutex) { return 0; } +void mc_exit_thread(void) { + thread_get_mailbox()->type = THREAD_EXIT_TYPE; + thread_await_scheduler(); + thread_awake_scheduler_for_thread_finish_transition(); +} + void mc_exit_main_thread(void) { + if (tid_self != TID_MAIN_THREAD) libc_abort(); // IMPORTANT: This is NOT a typo! - // 1. In the first case, McMini models the thread as alive - // but about to exit - // 2. In the second case, McMini models the thread as having exited. + // 1. `thread_await_scheduler()` is called when the + // main thread is known to be _alive_ to the model + // 2. `thread_awake_scheduler_for_thread_finish_transition()` + // is called to simulate the thread having "exited" + // 3. `thread_block_indefinitely()` ensures that the + // main thread never actually exits + // + // However, unlike a normal thread exiting, we want to ensure that + // the process doesn't terminate; hence, we prevent the main thread + // from ever escaping this function. thread_get_mailbox()->type = THREAD_EXIT_TYPE; thread_await_scheduler(); + thread_get_mailbox()->type = THREAD_EXIT_TYPE; - thread_await_scheduler(); + thread_awake_scheduler_for_thread_finish_transition(); + thread_block_indefinitely(); } MCMINI_NO_RETURN void mc_transparent_exit(int status) { @@ -72,4 +95,57 @@ MCMINI_NO_RETURN void mc_transparent_exit(int status) { MCMINI_NO_RETURN void mc_transparent_abort(void) { thread_await_scheduler(); libc_abort(); +} + + +struct mc_thread_routine_arg { + void *arg; + thread_routine routine; + sem_t mc_pthread_create_binary_sem; +}; + +void * +mc_thread_routine_wrapper(void *arg) +{ + mc_register_this_thread(); + + struct mc_thread_routine_arg * unwrapped_arg = arg; + libpthread_sem_post(&unwrapped_arg->mc_pthread_create_binary_sem); + + // Simulates THREAD_START for this thread NOTE: + // Don't write into shared memory here! The + // scheduler already knows how to handle the case of thread creation + thread_await_scheduler_for_thread_start_transition(); + void *return_value = unwrapped_arg->routine(unwrapped_arg->arg); + + mc_exit_thread(); + return return_value; +} + + +int +mc_pthread_create(pthread_t *thread, const pthread_attr_t *attr, + void *(*routine)(void *), void *arg) +{ + // TODO: add support for thread attributes + struct mc_thread_routine_arg libmcmini_controlled_thread_arg; + libmcmini_controlled_thread_arg.arg = arg; + libmcmini_controlled_thread_arg.routine = routine; + libpthread_sem_init(&libmcmini_controlled_thread_arg.mc_pthread_create_binary_sem, 0, 0); + + errno = 0; + const int return_value = libpthread_pthread_create( + thread, attr, &mc_thread_routine_wrapper, &libmcmini_controlled_thread_arg); + const int pthread_errno = errno; + + // IMPORTANT: We need to ensure that the thread that is + // created has been assigned an; otherwise, there is a race condition + // in which two thread creates in the child might + // not be scheduled to run until *two* steps of the scheduler + libpthread_sem_wait(&libmcmini_controlled_thread_arg.mc_pthread_create_binary_sem); + + memcpy_v(thread_get_mailbox()->cnts, &pthread_errno, sizeof(int)); + thread_get_mailbox()->type = THREAD_CREATE_TYPE; + thread_await_scheduler(); + return return_value; } \ No newline at end of file From 3f230b9219c4e321fefb0414f265c7b1c89d7fe9 Mon Sep 17 00:00:00 2001 From: Maxwell Pirtle Date: Sun, 19 May 2024 16:23:29 -0400 Subject: [PATCH 038/161] Refine DPOR logic minus backtracking --- .../mcmini/coordinator/coordinator.hpp | 13 +-- .../coordinator/model_to_system_map.hpp | 7 +- docs/design/include/mcmini/defines.h | 10 +- docs/design/include/mcmini/lib/entry.h | 4 +- .../include/mcmini/lib/shared_transition.h | 4 +- docs/design/include/mcmini/model/defines.hpp | 2 +- docs/design/include/mcmini/model/program.hpp | 27 +++++- .../include/mcmini/model/transition.hpp | 3 + .../model/transitions/mutex/mutex_init.hpp | 2 +- .../model/transitions/mutex/mutex_lock.hpp | 2 +- .../model/transitions/mutex/mutex_unlock.hpp | 2 +- .../transitions/thread/thread_create.hpp | 2 +- .../model/transitions/thread/thread_exit.hpp | 2 +- .../mcmini/model/visible_object_state.hpp | 3 - .../algorithms/classic_dpor.hpp | 5 +- .../algorithms/classic_dpor/runner_item.hpp | 1 + .../algorithms/classic_dpor/stack_item.hpp | 9 +- docs/design/src/lib/entry.c | 6 +- docs/design/src/lib/wrappers.c | 18 ++-- .../src/mcmini/coordinator/coordinator.cpp | 11 +-- docs/design/src/mcmini/mcmini.cpp | 16 ++++ .../src/mcmini/model/detached_state.cpp | 3 + docs/design/src/mcmini/model/diff_state.cpp | 5 +- docs/design/src/mcmini/model/program.cpp | 16 +++- .../src/mcmini/model/state_sequence.cpp | 6 +- .../algorithms/classic_dpor.cpp | 95 ++++++++++--------- 26 files changed, 172 insertions(+), 102 deletions(-) diff --git a/docs/design/include/mcmini/coordinator/coordinator.hpp b/docs/design/include/mcmini/coordinator/coordinator.hpp index 1d09d602..1d075242 100644 --- a/docs/design/include/mcmini/coordinator/coordinator.hpp +++ b/docs/design/include/mcmini/coordinator/coordinator.hpp @@ -124,8 +124,8 @@ class coordinator { * number of steps into execution. * * The coordinator can be scheduled to restore the model and the external - * world to correspond to how it looked in the past. This is useful for model - * checkers that want to. + * world to correspond to how it looked in the past. This is used by model + * checkers to restore previously modeled states. * * The method has no effect if `n == get_depth_into_program()`. * @@ -182,10 +182,11 @@ class coordinator { // the assignment, which means that there will be TWO processes at the same // time for a brief moment in between the right-hand evaluation, the // assignment, and the destruction of the previous process handle. This has - // the potential to introduce a race condition which is bad. By splitting - // the statement into two, we ensure that C++ first destroys the current - // handle and THEN creates the new one, but not concurrently (see C++ - // evaluation ordering on cppreference for more details). + // the potential to introduce a race condition between these shared + // resources. By splitting the statement into two, we ensure that C++ first + // calls the destructor for the current process handle and THEN creates the + // new one, but not concurrently (see C++ evaluation ordering on + // cppreference for more details). this->current_process_handle = nullptr; this->current_process_handle = this->process_source->force_new_process(); } diff --git a/docs/design/include/mcmini/coordinator/model_to_system_map.hpp b/docs/design/include/mcmini/coordinator/model_to_system_map.hpp index dfcae2d0..83d7a8b5 100644 --- a/docs/design/include/mcmini/coordinator/model_to_system_map.hpp +++ b/docs/design/include/mcmini/coordinator/model_to_system_map.hpp @@ -53,11 +53,14 @@ struct model_to_system_map final { * process handles dynamically during each new re-execution scheduled by * the coordinator to handle aliasing etc. */ + using runner_generation_function = + std::function( + model::state::runner_id_t)>; model::state::objid_t observe_remote_process_handle( real_world::remote_address, std::unique_ptr); - model::state::objid_t observe_remote_process_runner( + model::state::runner_id_t observe_remote_process_runner( real_world::remote_address, std::unique_ptr, - std::unique_ptr); + runner_generation_function f); }; diff --git a/docs/design/include/mcmini/defines.h b/docs/design/include/mcmini/defines.h index 57a0b45b..6af2e8a8 100644 --- a/docs/design/include/mcmini/defines.h +++ b/docs/design/include/mcmini/defines.h @@ -28,12 +28,12 @@ #define THREAD_SHM_OFFSET (128) -typedef uint32_t runner_id_t; -typedef uint64_t tid_t; +typedef uint16_t runner_id_t; typedef uint64_t trid_t; -#define TID_MAIN_THREAD (0ul) -#define TID_INVALID (-1ul) // ULONG_MAX -#define TID_PTHREAD_CREATE_FAILED (-2ul) // ULONG_MAX - 1 +#define RUNNER_ID_MAX UINT16_MAX +#define TID_MAIN_THREAD ((runner_id_t)0) +#define TID_INVALID ((runner_id_t)-1) +#define TID_PTHREAD_CREATE_FAILED ((runner_id_t)-2) #define FORK_IS_CHILD_PID(pid) ((pid) == 0) #define FORK_IS_PARENT_PID(pid) (!(FORK_IS_CHILD_PID(pid))) diff --git a/docs/design/include/mcmini/lib/entry.h b/docs/design/include/mcmini/lib/entry.h index f7cbb68d..a9fc9521 100644 --- a/docs/design/include/mcmini/lib/entry.h +++ b/docs/design/include/mcmini/lib/entry.h @@ -3,10 +3,10 @@ #include "mcmini/defines.h" extern volatile void *global_shm_start; -extern MCMINI_THREAD_LOCAL tid_t tid_self; +extern MCMINI_THREAD_LOCAL runner_id_t tid_self; void mc_exit(int); -tid_t mc_register_this_thread(void); +runner_id_t mc_register_this_thread(void); diff --git a/docs/design/include/mcmini/lib/shared_transition.h b/docs/design/include/mcmini/lib/shared_transition.h index b3e31ba0..529ae35d 100644 --- a/docs/design/include/mcmini/lib/shared_transition.h +++ b/docs/design/include/mcmini/lib/shared_transition.h @@ -4,8 +4,8 @@ #ifdef __cplusplus extern "C" { #endif +#include "mcmini/defines.h" -typedef uint64_t tid_t; typedef enum { MUTEX_INIT_TYPE, MUTEX_LOCK_TYPE, @@ -23,7 +23,7 @@ typedef enum { } transition_type; struct shared_transition { - tid_t executor; + runner_id_t executor; transition_type type; }; diff --git a/docs/design/include/mcmini/model/defines.hpp b/docs/design/include/mcmini/model/defines.hpp index 1993a641..05b5e609 100644 --- a/docs/design/include/mcmini/model/defines.hpp +++ b/docs/design/include/mcmini/model/defines.hpp @@ -1,4 +1,4 @@ #pragma once #include -using runner_id_t = uint32_t; \ No newline at end of file +using runner_id_t = uint16_t; \ No newline at end of file diff --git a/docs/design/include/mcmini/model/program.hpp b/docs/design/include/mcmini/model/program.hpp index 6b81164f..ef4258e7 100644 --- a/docs/design/include/mcmini/model/program.hpp +++ b/docs/design/include/mcmini/model/program.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include "mcmini/model/defines.hpp" @@ -58,6 +59,8 @@ class program { program(program &&) = default; program(const program &) = delete; + std::unordered_set get_enabled_runners() const; + size_t get_num_runners() const { return next_steps.size(); } const state_sequence &get_state_sequence() const { return this->state_seq; } const transition_sequence &get_trace() const { return this->trace; } const pending_transitions &get_pending_transitions() const { @@ -67,12 +70,23 @@ class program { return next_steps.get_transition_for_runner(rid); } - size_t get_num_runners() const { return next_steps.size(); } - std::unordered_set get_enabled_runners() const; + using runner_generation_function = + std::function( + model::state::runner_id_t)>; + + /// @brief Introduce a new object into the model with initial state `s` + /// @param s the initial state of the new object to add to the model + /// @return the id assigned to the object in the model + state::objid_t discover_object(std::unique_ptr s); - state::objid_t discover_object(std::unique_ptr); - state::runner_id_t discover_runner(std::unique_ptr, - std::unique_ptr); + /// @brief Introduce a new object into the model with initial state `s` + /// @param s the initial state of the new object to add to the model + /// @param f a function which, when passed the id that will be vended to the + /// runner, produces the first transition that runner is executing in the + /// model (i.e. the very first pending operation). + /// @return the id assigned to the runner. + state::runner_id_t discover_runner(std::unique_ptr s, + runner_generation_function f); /// @brief Model the execution of runner `p` and replace its next operation /// with `next_pending_operation`. @@ -94,6 +108,9 @@ class program { /// t_(n-1), ..., t_k` is contained in the current trace, then the state after /// this method is called will be `s_(n+1)` or `s_0` is `n = 0`. void restore_model_at_depth(uint32_t n); + + // MARK: Program State + bool is_in_deadlock() const; }; // diff --git a/docs/design/include/mcmini/model/transition.hpp b/docs/design/include/mcmini/model/transition.hpp index 568a3de8..54a27c14 100644 --- a/docs/design/include/mcmini/model/transition.hpp +++ b/docs/design/include/mcmini/model/transition.hpp @@ -150,6 +150,9 @@ class transition { // coordinator::context::model_to_system_map&) const = 0;` virtual std::string to_string() const = 0; + std::string debug_string() const { + return "thread " + std::to_string(this->executor) + ": " + to_string(); + } virtual ~transition() = default; protected: diff --git a/docs/design/include/mcmini/model/transitions/mutex/mutex_init.hpp b/docs/design/include/mcmini/model/transitions/mutex/mutex_init.hpp index 3cd5e0b0..5a389043 100644 --- a/docs/design/include/mcmini/model/transitions/mutex/mutex_init.hpp +++ b/docs/design/include/mcmini/model/transitions/mutex/mutex_init.hpp @@ -22,7 +22,7 @@ struct mutex_init : public model::transition { } std::string to_string() const override { - return "mutex_init(mutex:" + std::to_string(mutex_id) + ")"; + return "pthread_mutex_init(mutex:" + std::to_string(mutex_id) + ")"; } }; } // namespace transitions diff --git a/docs/design/include/mcmini/model/transitions/mutex/mutex_lock.hpp b/docs/design/include/mcmini/model/transitions/mutex/mutex_lock.hpp index e9ca4380..ecfabfa7 100644 --- a/docs/design/include/mcmini/model/transitions/mutex/mutex_lock.hpp +++ b/docs/design/include/mcmini/model/transitions/mutex/mutex_lock.hpp @@ -28,7 +28,7 @@ struct mutex_lock : public model::transition { } std::string to_string() const override { - return "mutex_lock(mutex:" + std::to_string(mutex_id) + ")"; + return "pthread_mutex_lock(mutex:" + std::to_string(mutex_id) + ")"; } }; } // namespace transitions diff --git a/docs/design/include/mcmini/model/transitions/mutex/mutex_unlock.hpp b/docs/design/include/mcmini/model/transitions/mutex/mutex_unlock.hpp index e59806c0..180c4734 100644 --- a/docs/design/include/mcmini/model/transitions/mutex/mutex_unlock.hpp +++ b/docs/design/include/mcmini/model/transitions/mutex/mutex_unlock.hpp @@ -23,7 +23,7 @@ struct mutex_unlock : public model::transition { } std::string to_string() const override { - return "mutex_unlock(mutex:" + std::to_string(mutex_id) + ")"; + return "pthread_mutex_unlock(mutex:" + std::to_string(mutex_id) + ")"; } }; } // namespace transitions diff --git a/docs/design/include/mcmini/model/transitions/thread/thread_create.hpp b/docs/design/include/mcmini/model/transitions/thread/thread_create.hpp index 4fe227dd..21e2f973 100644 --- a/docs/design/include/mcmini/model/transitions/thread/thread_create.hpp +++ b/docs/design/include/mcmini/model/transitions/thread/thread_create.hpp @@ -22,7 +22,7 @@ struct thread_create : public model::transition { } std::string to_string() const override { - return "pthread_join(thread: " + std::to_string(target) + ")"; + return "pthread_create(thread: " + std::to_string(target) + ")"; } }; diff --git a/docs/design/include/mcmini/model/transitions/thread/thread_exit.hpp b/docs/design/include/mcmini/model/transitions/thread/thread_exit.hpp index 229a85f8..7360947c 100644 --- a/docs/design/include/mcmini/model/transitions/thread/thread_exit.hpp +++ b/docs/design/include/mcmini/model/transitions/thread/thread_exit.hpp @@ -14,7 +14,7 @@ struct thread_exit : public model::transition { status modify(model::mutable_state& s) const override { using namespace model::objects; auto* thread_state = s.get_state_of_runner(executor); - if (!thread_state->is_running()) { + if (!thread_state->is_running() || executor == TID_MAIN_THREAD) { return status::disabled; } s.add_state_for_runner(executor, thread::make(thread::exited)); diff --git a/docs/design/include/mcmini/model/visible_object_state.hpp b/docs/design/include/mcmini/model/visible_object_state.hpp index 092e44f2..188eb0b1 100644 --- a/docs/design/include/mcmini/model/visible_object_state.hpp +++ b/docs/design/include/mcmini/model/visible_object_state.hpp @@ -6,9 +6,6 @@ namespace model { -/** - * @brief A capture of the state of a particular visible object. - */ class visible_object_state { public: virtual ~visible_object_state() = default; diff --git a/docs/design/include/mcmini/model_checking/algorithms/classic_dpor.hpp b/docs/design/include/mcmini/model_checking/algorithms/classic_dpor.hpp index 17d5007e..95506c7b 100644 --- a/docs/design/include/mcmini/model_checking/algorithms/classic_dpor.hpp +++ b/docs/design/include/mcmini/model_checking/algorithms/classic_dpor.hpp @@ -48,8 +48,9 @@ class classic_dpor final : public algorithm { clock_vector accumulate_max_clock_vector_against(const model::transition &, const dpor_context &) const; - void grow_stack_after_running(const coordinator &, dpor_context &); - void dynamically_update_backtrack_sets(const coordinator &, dpor_context &); + void continue_dpor_by_expanding_trace_with(runner_id_t p, dpor_context &); + void grow_stack_after_running(dpor_context &); + void dynamically_update_backtrack_sets(dpor_context &); bool dynamically_update_backtrack_sets_at_index( const dpor_context &, const model::transition &S_i, diff --git a/docs/design/include/mcmini/model_checking/algorithms/classic_dpor/runner_item.hpp b/docs/design/include/mcmini/model_checking/algorithms/classic_dpor/runner_item.hpp index f4bbbed9..bfa6b37f 100644 --- a/docs/design/include/mcmini/model_checking/algorithms/classic_dpor/runner_item.hpp +++ b/docs/design/include/mcmini/model_checking/algorithms/classic_dpor/runner_item.hpp @@ -18,6 +18,7 @@ struct runner_item final { clock_vector cv; public: + runner_item() = default; uint32_t get_execution_depth() const; void increment_execution_depth() { executionDepth++; } void decrement_execution_depth_if_possible() { diff --git a/docs/design/include/mcmini/model_checking/algorithms/classic_dpor/stack_item.hpp b/docs/design/include/mcmini/model_checking/algorithms/classic_dpor/stack_item.hpp index 9b3ee370..ce341bb9 100644 --- a/docs/design/include/mcmini/model_checking/algorithms/classic_dpor/stack_item.hpp +++ b/docs/design/include/mcmini/model_checking/algorithms/classic_dpor/stack_item.hpp @@ -1,9 +1,9 @@ #pragma once - #include #include #include +#include "mcmini/defines.h" #include "mcmini/model_checking/algorithms/classic_dpor/clock_vector.hpp" namespace model_checking { @@ -188,6 +188,13 @@ struct stack_item final { } clock_vector get_clock_vector() const { return this->cv; } + runner_id_t get_first_enabled_runner() const { + runner_id_t r = RUNNER_ID_MAX; + for (runner_id_t p : this->enabled_runners) { + r = std::min(r, p); + } + return r; + } const std::unordered_set &get_enabled_runners() const { return this->enabled_runners; } diff --git a/docs/design/src/lib/entry.c b/docs/design/src/lib/entry.c index 82d16345..19bf6244 100644 --- a/docs/design/src/lib/entry.c +++ b/docs/design/src/lib/entry.c @@ -20,9 +20,9 @@ #include "mcmini/mcmini.h" volatile void *global_shm_start = NULL; -MCMINI_THREAD_LOCAL tid_t tid_self = TID_INVALID; +MCMINI_THREAD_LOCAL runner_id_t tid_self = TID_INVALID; -tid_t mc_register_this_thread(void) { +runner_id_t mc_register_this_thread(void) { // NOTE: It is an internal error for more than one thread // to be executing this function. If the model checker maintains // control over each thread, it will only enable a single @@ -31,7 +31,7 @@ tid_t mc_register_this_thread(void) { // // NOTE: If `McMini` introduces parallelism into the // model-checking process, this would have to be adjusted. - static tid_t tid_next = 0; + static runner_id_t tid_next = 0; tid_self = tid_next++; return tid_self; } diff --git a/docs/design/src/lib/wrappers.c b/docs/design/src/lib/wrappers.c index 691aca33..0fac9664 100644 --- a/docs/design/src/lib/wrappers.c +++ b/docs/design/src/lib/wrappers.c @@ -113,11 +113,13 @@ mc_thread_routine_wrapper(void *arg) libpthread_sem_post(&unwrapped_arg->mc_pthread_create_binary_sem); // Simulates THREAD_START for this thread NOTE: - // Don't write into shared memory here! The - // scheduler already knows how to handle the case of thread creation + // We don't need to write into shared memory here. The + // scheduler already knows how to handle the case + // of thread creation thread_await_scheduler_for_thread_start_transition(); void *return_value = unwrapped_arg->routine(unwrapped_arg->arg); + free(arg); mc_exit_thread(); return return_value; } @@ -128,21 +130,21 @@ mc_pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*routine)(void *), void *arg) { // TODO: add support for thread attributes - struct mc_thread_routine_arg libmcmini_controlled_thread_arg; - libmcmini_controlled_thread_arg.arg = arg; - libmcmini_controlled_thread_arg.routine = routine; - libpthread_sem_init(&libmcmini_controlled_thread_arg.mc_pthread_create_binary_sem, 0, 0); + struct mc_thread_routine_arg *libmcmini_controlled_thread_arg = malloc(sizeof(struct mc_thread_routine_arg)); + libmcmini_controlled_thread_arg->arg = arg; + libmcmini_controlled_thread_arg->routine = routine; + libpthread_sem_init(&libmcmini_controlled_thread_arg->mc_pthread_create_binary_sem, 0, 0); errno = 0; const int return_value = libpthread_pthread_create( - thread, attr, &mc_thread_routine_wrapper, &libmcmini_controlled_thread_arg); + thread, attr, &mc_thread_routine_wrapper, libmcmini_controlled_thread_arg); const int pthread_errno = errno; // IMPORTANT: We need to ensure that the thread that is // created has been assigned an; otherwise, there is a race condition // in which two thread creates in the child might // not be scheduled to run until *two* steps of the scheduler - libpthread_sem_wait(&libmcmini_controlled_thread_arg.mc_pthread_create_binary_sem); + libpthread_sem_wait(&libmcmini_controlled_thread_arg->mc_pthread_create_binary_sem); memcpy_v(thread_get_mailbox()->cnts, &pthread_errno, sizeof(int)); thread_get_mailbox()->type = THREAD_CREATE_TYPE; diff --git a/docs/design/src/mcmini/coordinator/coordinator.cpp b/docs/design/src/mcmini/coordinator/coordinator.cpp index 41ced8d8..5562eb44 100644 --- a/docs/design/src/mcmini/coordinator/coordinator.cpp +++ b/docs/design/src/mcmini/coordinator/coordinator.cpp @@ -90,10 +90,10 @@ model::state::objid_t model_to_system_map::observe_remote_process_handle( } } -model::state::objid_t model_to_system_map::observe_remote_process_runner( +model::state::runner_id_t model_to_system_map::observe_remote_process_runner( real_world::remote_address remote_process_visible_object_handle, std::unique_ptr fallback_initial_state, - std::unique_ptr fallback_initial_transition) { + runner_generation_function f) { model::state::objid_t existing_obj = this->get_object_for_remote_process_handle( remote_process_visible_object_handle); @@ -102,15 +102,12 @@ model::state::objid_t model_to_system_map::observe_remote_process_runner( } else { model::state::runner_id_t new_runner_id = _coordinator.current_program_model.discover_runner( - std::move(fallback_initial_state), - std::move(fallback_initial_transition)); - + std::move(fallback_initial_state), std::move(f)); model::state::objid_t new_objid = _coordinator.get_current_program_model() .get_state_sequence() .get_objid_for_runner(new_runner_id); - _coordinator.system_address_mapping.insert( {remote_process_visible_object_handle, new_objid}); - return new_objid; + return new_runner_id; } } \ No newline at end of file diff --git a/docs/design/src/mcmini/mcmini.cpp b/docs/design/src/mcmini/mcmini.cpp index 714449e3..5e676d09 100644 --- a/docs/design/src/mcmini/mcmini.cpp +++ b/docs/design/src/mcmini/mcmini.cpp @@ -10,6 +10,7 @@ #include "mcmini/model/transitions/mutex/mutex_init.hpp" #include "mcmini/model/transitions/mutex/mutex_lock.hpp" #include "mcmini/model/transitions/mutex/mutex_unlock.hpp" +#include "mcmini/model/transitions/thread/thread_create.hpp" #include "mcmini/model/transitions/thread/thread_exit.hpp" #include "mcmini/model/transitions/thread/thread_start.hpp" #include "mcmini/model_checking/algorithms/classic_dpor.hpp" @@ -87,6 +88,20 @@ std::unique_ptr mutex_unlock_callback( return make_unique(p, mut); } +std::unique_ptr thread_create_callback( + state::runner_id_t p, const volatile runner_mailbox& rmb, + model_to_system_map& m) { + pthread_t new_thread; + memcpy_v(&new_thread, static_cast(&rmb.cnts), + sizeof(pthread_t)); + runner_id_t new_thread_id = m.observe_remote_process_runner( + (void*)new_thread, objects::thread::make(objects::thread::state::running), + [](runner_id_t id) { + return make_unique(id); + }); + return make_unique(p, new_thread_id); +} + std::unique_ptr thread_exit_callback( state::runner_id_t p, const volatile runner_mailbox& rmb, model_to_system_map& m) { @@ -116,6 +131,7 @@ void do_model_checking( tr.register_transition(MUTEX_INIT_TYPE, &mutex_init_callback); tr.register_transition(MUTEX_LOCK_TYPE, &mutex_lock_callback); tr.register_transition(MUTEX_UNLOCK_TYPE, &mutex_unlock_callback); + tr.register_transition(THREAD_CREATE_TYPE, &thread_create_callback); tr.register_transition(THREAD_EXIT_TYPE, &thread_exit_callback); coordinator coordinator(std::move(model_for_program_starting_at_main), diff --git a/docs/design/src/mcmini/model/detached_state.cpp b/docs/design/src/mcmini/model/detached_state.cpp index 9981311d..a9d11d9a 100644 --- a/docs/design/src/mcmini/model/detached_state.cpp +++ b/docs/design/src/mcmini/model/detached_state.cpp @@ -50,6 +50,9 @@ state::runner_id_t detached_state::add_runner( void detached_state::add_state_for_obj( objid_t id, std::unique_ptr new_state) { + if (id == invalid_objid) { + throw std::runtime_error("Attempted to insert an invalid object id"); + } // INVARIANT: The current element needs to update at index `id` to reflect // this new state, as this element effectively represents this state this->visible_objects.at(id).push_state(std::move(new_state)); diff --git a/docs/design/src/mcmini/model/diff_state.cpp b/docs/design/src/mcmini/model/diff_state.cpp index 7abee790..19f3ab4b 100644 --- a/docs/design/src/mcmini/model/diff_state.cpp +++ b/docs/design/src/mcmini/model/diff_state.cpp @@ -15,7 +15,7 @@ bool diff_state::contains_object_with_id(objid_t id) const { this->base_state.contains_object_with_id(id); } -bool diff_state::contains_runner_with_id(objid_t id) const { +bool diff_state::contains_runner_with_id(runner_id_t id) const { return this->new_runners.count(id) > 0 || this->base_state.contains_runner_with_id(id); } @@ -59,6 +59,9 @@ state::runner_id_t diff_state::add_runner( void diff_state::add_state_for_obj( objid_t id, std::unique_ptr new_state) { + if (id == invalid_objid) { + throw std::runtime_error("Attempted to insert an invalid object id"); + } // Here we seek to insert all new states into the local cache instead of // forwarding them onto the underlying base state. visible_object &vobj = new_object_states[id]; diff --git a/docs/design/src/mcmini/model/program.cpp b/docs/design/src/mcmini/model/program.cpp index db92545b..c5791a69 100644 --- a/docs/design/src/mcmini/model/program.cpp +++ b/docs/design/src/mcmini/model/program.cpp @@ -1,5 +1,7 @@ #include "mcmini/model/program.hpp" +#include + using namespace model; program::program(const state &initial_state, @@ -68,7 +70,8 @@ void program::model_execution_of(runner_id_t p, transition::status status = this->state_seq.follow(*next_s_p); if (status == transition::status::disabled) { throw std::runtime_error( - "Attempted to model the execution of a disabled transition."); + "Attempted to model the execution of a disabled transition(" + + next_s_p->debug_string() + ")"); } trace.push(next_steps.displace_transition_for(p, std::move(new_transition))); } @@ -80,8 +83,15 @@ state::objid_t program::discover_object( state::runner_id_t program::discover_runner( std::unique_ptr initial_state, - std::unique_ptr initial_transition) { + runner_generation_function f) { state::runner_id_t id = this->state_seq.add_runner(std::move(initial_state)); - this->next_steps.displace_transition_for(id, std::move(initial_transition)); + this->next_steps.displace_transition_for(id, f(id)); return id; +} + +bool program::is_in_deadlock() const { + for (const auto &t : this->get_pending_transitions()) { + if (t.second->is_enabled_in(this->state_seq)) return false; + } + return true; } \ No newline at end of file diff --git a/docs/design/src/mcmini/model/state_sequence.cpp b/docs/design/src/mcmini/model/state_sequence.cpp index ca238703..71531cff 100644 --- a/docs/design/src/mcmini/model/state_sequence.cpp +++ b/docs/design/src/mcmini/model/state_sequence.cpp @@ -106,6 +106,9 @@ state::runner_id_t state_sequence::add_runner( void state_sequence::add_state_for_obj( objid_t id, std::unique_ptr new_state) { + if (id == invalid_objid) { + throw std::runtime_error("Attempted to insert an invalid object id"); + } // INVARIANT: The current element needs to update at index `id` to reflect // this new state, as this element effectively represents this state this->get_representative_state().point_to_state_for(id, new_state.get()); @@ -168,7 +171,8 @@ bool state_sequence::element::contains_object_with_id(state::objid_t id) const { return id < this->visible_object_states.size(); } -bool state_sequence::element::contains_runner_with_id(state::objid_t id) const { +bool state_sequence::element::contains_runner_with_id( + state::runner_id_t id) const { return id < max_visible_runner_id; } diff --git a/docs/design/src/mcmini/model_checking/algorithms/classic_dpor.cpp b/docs/design/src/mcmini/model_checking/algorithms/classic_dpor.cpp index affde124..99e8d87f 100644 --- a/docs/design/src/mcmini/model_checking/algorithms/classic_dpor.cpp +++ b/docs/design/src/mcmini/model_checking/algorithms/classic_dpor.cpp @@ -14,14 +14,33 @@ using namespace model; using namespace model_checking; struct classic_dpor::dpor_context { + ::coordinator &coordinator; std::vector stack; - std::unordered_map per_runner_clocks; + std::array per_runner_clocks; + + dpor_context(::coordinator &c) : coordinator(c) {} const transition *get_transition(int i) const { return stack.at(i).get_out_transition(); } }; +clock_vector classic_dpor::accumulate_max_clock_vector_against( + const model::transition &t, const dpor_context &context) const { + // The last state in the stack does NOT have an out transition, hence the + // `nullptr` check. Note that `s_i.get_out_transition()` refers to `S_i` + // (case-sensitive) in the paper, viz. the transition between states `s_i` and + // `s_{i+1}`. + clock_vector result; + for (const stack_item &s_i : context.stack) { + if (s_i.get_out_transition() != nullptr && + this->are_dependent(*s_i.get_out_transition(), t)) { + result = clock_vector::max(result, s_i.get_clock_vector()); + } + } + return result; +} + void classic_dpor::verify_using(coordinator &coordinator, const callbacks &callbacks) { // The code below is an implementation of the model-checking algorithm of @@ -34,7 +53,7 @@ void classic_dpor::verify_using(coordinator &coordinator, /// /// The initial entry into the stack represents the information DPOR tracks /// for state `s_0`. - dpor_context context; + dpor_context context(coordinator); auto &dpor_stack = context.stack; dpor_stack.emplace_back( clock_vector(), @@ -54,20 +73,12 @@ void classic_dpor::verify_using(coordinator &coordinator, "to limit how far into a trace McMini can go\n"); } - { // 2a. Execute the runner in the model and in the real world - // For deterministic results, always choose the smallest runner - const auto &enabled_runners = dpor_stack.back().get_enabled_runners(); - runner_id_t venturer = *enabled_runners.begin(); - for (const runner_id_t rid : enabled_runners) - venturer = std::min(rid, venturer); - coordinator.execute_runner(venturer); - } - - { // 2b. Update DPOR data structures (per-thread data, clock vectors, - // backtrack sets) - this->grow_stack_after_running(coordinator, context); - this->dynamically_update_backtrack_sets(coordinator, context); - } + // Execute the runner in the model and in the real world + // For deterministic results, always choose the "first" enabled runner. + // A runner precedes another runner in being enabled iff it has a smaller + // id. + this->continue_dpor_by_expanding_trace_with( + dpor_stack.back().get_first_enabled_runner(), context); } // TODO: Check for deadlock @@ -81,30 +92,26 @@ void classic_dpor::verify_using(coordinator &coordinator, if (dpor_stack.back().backtrack_set_empty()) { dpor_stack.pop_back(); } else { - break; + // Select one thread to backtrack upon. + + // At this point, the model checker's data structures are valid for + // `dpor_stack.size()` states; however, the model and the associated + // process(es) that the model represent do not yet correspond after + // backtracking. + coordinator.return_to_depth(dpor_stack.size() - 1); } } while (!dpor_stack.empty()); } } -clock_vector classic_dpor::accumulate_max_clock_vector_against( - const model::transition &t, const dpor_context &context) const { - // The last state in the stack does NOT have an out transition, hence the - // `nullptr` check. Note that `s_i.get_out_transition()` refers to `S_i` - // (case-sensitive) in the paper, viz. the transition between states `s_i` and - // `s_{i+1}`. - clock_vector result; - for (const stack_item &s_i : context.stack) { - if (s_i.get_out_transition() != nullptr && - this->are_dependent(*s_i.get_out_transition(), t)) { - result = clock_vector::max(result, s_i.get_clock_vector()); - } - } - return result; +void classic_dpor::continue_dpor_by_expanding_trace_with( + runner_id_t p, dpor_context &context) { + context.coordinator.execute_runner(p); + this->grow_stack_after_running(context); + this->dynamically_update_backtrack_sets(context); } -void classic_dpor::grow_stack_after_running(const coordinator &coordinator, - dpor_context &context) { +void classic_dpor::grow_stack_after_running(dpor_context &context) { // In this method, the following invariants are assumed to hold: // // 1. `n` := `stack.size()`. @@ -115,6 +122,7 @@ void classic_dpor::grow_stack_after_running(const coordinator &coordinator, // After this method is executed, the stack will have size `n + 1`. Each entry // will correspond to the information DPOR cares about for each state in the // `coordinator`'s state sequence. + const coordinator &coordinator = context.coordinator; assert(coordinator.get_depth_into_program() == context.stack.size()); const model::transition *t_n = coordinator.get_current_program_model().get_trace().back(); @@ -157,8 +165,7 @@ void classic_dpor::grow_stack_after_running(const coordinator &coordinator, s_n.mark_searched(t_n->get_executor()); } -void classic_dpor::dynamically_update_backtrack_sets( - const coordinator &coordinator, dpor_context &context) { +void classic_dpor::dynamically_update_backtrack_sets(dpor_context &context) { /* * Updating the backtrack sets is accomplished as follows * (under the given assumptions) @@ -204,12 +211,13 @@ void classic_dpor::dynamically_update_backtrack_sets( * to determine if a backtrack point is needed anywhere for * thread `i` */ + const coordinator &coordinator = context.coordinator; const size_t num_threads = coordinator.get_current_program_model().get_num_runners(); - std::unordered_set thread_ids; + std::unordered_set thread_ids; thread_ids.reserve(num_threads); - for (tid_t i = 0; i < num_threads; i++) thread_ids.insert(i); + for (runner_id_t i = 0; i < num_threads; i++) thread_ids.insert(i); const ssize_t tStackTop = (ssize_t)(context.stack.size()) - 1; const runner_id_t last_runner_to_execute = @@ -271,7 +279,7 @@ bool classic_dpor::happens_before(const dpor_context &context, int i, bool classic_dpor::happens_before_thread(const dpor_context &context, int i, runner_id_t p) const { const runner_id_t rid = context.get_transition(i)->get_executor(); - const clock_vector &cv = context.per_runner_clocks.at(p).get_clock_vector(); + const clock_vector &cv = context.per_runner_clocks[p].get_clock_vector(); return i <= cv.value_for(rid); } @@ -295,12 +303,9 @@ bool classic_dpor::dynamically_update_backtrack_sets_at_index( // If there exists i such that ... if (has_reversible_race) { - std::unordered_set E; - - const std::unordered_set &enabled_at_preSi = - preSi.get_enabled_runners(); + std::unordered_set E; - for (runner_id_t q : enabled_at_preSi) { + for (runner_id_t q : preSi.get_enabled_runners()) { const bool inE = q == p || this->threads_race_after(context, i, q, p); // If E != empty set @@ -309,11 +314,11 @@ bool classic_dpor::dynamically_update_backtrack_sets_at_index( if (E.empty()) { // E is the empty set -> add every enabled thread at pre(S, i) - for (tid_t q : enabled_at_preSi) + for (runner_id_t q : preSi.get_enabled_runners()) if (!preSi.sleep_set_contains(q)) preSi.insert_into_backtrack_set_unless_completed(q); } else { - for (tid_t q : E) { + for (runner_id_t q : E) { // If there is a thread in preSi that we // are already backtracking AND which is contained // in the set E, chose that thread to backtrack From 1219ff4fbb1e072dc3c06ba5d44636f6e77900ee Mon Sep 17 00:00:00 2001 From: Maxwell Pirtle Date: Sun, 19 May 2024 16:50:45 -0400 Subject: [PATCH 039/161] Add backtracking logic to DPOR --- .../algorithms/classic_dpor/stack_item.hpp | 12 +++++- docs/design/src/examples/CMakeLists.txt | 2 - docs/design/src/examples/hello-world.c | 11 +++-- .../algorithms/classic_dpor.cpp | 42 ++++++++++--------- 4 files changed, 40 insertions(+), 27 deletions(-) diff --git a/docs/design/include/mcmini/model_checking/algorithms/classic_dpor/stack_item.hpp b/docs/design/include/mcmini/model_checking/algorithms/classic_dpor/stack_item.hpp index ce341bb9..b7d696ae 100644 --- a/docs/design/include/mcmini/model_checking/algorithms/classic_dpor/stack_item.hpp +++ b/docs/design/include/mcmini/model_checking/algorithms/classic_dpor/stack_item.hpp @@ -173,12 +173,20 @@ struct stack_item final { for (const runner_id_t rid : runners) this->enabled_runners.insert(rid); } - // NOTE: We arbitrarily always pick the smallest thread to provide a - // determinism runner_id_t backtrack_set_pop() { + if (backtrack_set_empty()) + throw std::runtime_error("There are no more threads to backtrack on"); + runner_id_t backtrack_thread = *this->backtrack_set.begin(); + this->mark_searched(backtrack_thread); + return backtrack_thread; + } + + runner_id_t backtrack_set_pop_first() { if (backtrack_set_empty()) throw std::runtime_error("There are no more threads to backtrack on"); + // NOTE: We arbitrarily always pick the smallest thread to provide a + // determinism runner_id_t backtrack_thread = *this->backtrack_set.begin(); for (const runner_id_t rid : this->backtrack_set) backtrack_thread = std::min(rid, backtrack_thread); diff --git a/docs/design/src/examples/CMakeLists.txt b/docs/design/src/examples/CMakeLists.txt index 6ca3e08d..5c86ebe2 100644 --- a/docs/design/src/examples/CMakeLists.txt +++ b/docs/design/src/examples/CMakeLists.txt @@ -1,4 +1,2 @@ add_executable(hello-world hello-world.c) -add_executable(vms volatile_mem_stream.cpp ../../src/mcmini/real_world/shm.cpp) -target_include_directories(vms PUBLIC ../../include) target_link_libraries(hello-world PUBLIC -pthread) diff --git a/docs/design/src/examples/hello-world.c b/docs/design/src/examples/hello-world.c index a63e1fe5..858d28a9 100644 --- a/docs/design/src/examples/hello-world.c +++ b/docs/design/src/examples/hello-world.c @@ -3,13 +3,18 @@ #include #include +pthread_mutex_t mut; + +void *test(void* unused) { + pthread_mutex_lock(&mut); +} + int main(int argc, char* argv[]) { - pthread_mutex_t mut; + pthread_t child; pthread_mutex_init(&mut, NULL); + pthread_create(&child, NULL, &test, NULL); pthread_mutex_lock(&mut); - // printf("Hello world!\n"); pthread_mutex_unlock(&mut); - // pthread_mutex_destroy(&mut); return 0; } diff --git a/docs/design/src/mcmini/model_checking/algorithms/classic_dpor.cpp b/docs/design/src/mcmini/model_checking/algorithms/classic_dpor.cpp index 99e8d87f..a3cc9ff9 100644 --- a/docs/design/src/mcmini/model_checking/algorithms/classic_dpor.cpp +++ b/docs/design/src/mcmini/model_checking/algorithms/classic_dpor.cpp @@ -72,35 +72,37 @@ void classic_dpor::verify_using(coordinator &coordinator, "running mcmini with the \"--max-depth-per-thread\" flag\n" "to limit how far into a trace McMini can go\n"); } - - // Execute the runner in the model and in the real world - // For deterministic results, always choose the "first" enabled runner. - // A runner precedes another runner in being enabled iff it has a smaller - // id. + // NOTE: For deterministic results, always choose the "first" enabled + // runner. A runner precedes another runner in being enabled iff it has a + // smaller id. this->continue_dpor_by_expanding_trace_with( dpor_stack.back().get_first_enabled_runner(), context); } - // TODO: Check for deadlock // TODO: Check for the program crashing callbacks.trace_completed(coordinator); + // if (coordinator.get_current_program_model().is_in_deadlock()) { + // callbacks.deadlock(coordinator); + // } // 3. Backtrack phase - do { - // Locate a spot that contains backtrack threads - if (dpor_stack.back().backtrack_set_empty()) { - dpor_stack.pop_back(); - } else { - // Select one thread to backtrack upon. - - // At this point, the model checker's data structures are valid for - // `dpor_stack.size()` states; however, the model and the associated - // process(es) that the model represent do not yet correspond after - // backtracking. - coordinator.return_to_depth(dpor_stack.size() - 1); - } - } while (!dpor_stack.empty()); + while (!dpor_stack.empty() && dpor_stack.back().backtrack_set_empty()) + dpor_stack.pop_back(); + + if (!dpor_stack.empty()) { + // At this point, the model checker's data structures are valid for + // `dpor_stack.size()` states; however, the model and the associated + // process(es) that the model represent do not yet correspond after + // backtracking. + coordinator.return_to_depth(dpor_stack.size() - 1); + + // The first step of the NEXT exploration phase begins with following + // one of the backtrack threads. Select one thread to backtrack upon and + // follow it before continuing onto the exploration phase. + this->continue_dpor_by_expanding_trace_with( + dpor_stack.back().backtrack_set_pop_first(), context); + } } } From 3b47069a66712329aff9411cb00649931b9a179f Mon Sep 17 00:00:00 2001 From: Maxwell Pirtle Date: Mon, 20 May 2024 16:17:30 -0400 Subject: [PATCH 040/161] Reduce the use of unique_ptr inside the code base --- .../mcmini/model/pending_transitions.hpp | 12 +++++----- docs/design/include/mcmini/model/program.hpp | 9 ++++--- .../model/transitions/transition_sequence.hpp | 13 +++++----- .../include/mcmini/model/visible_object.hpp | 24 +++++++++---------- docs/design/src/mcmini/mcmini.cpp | 4 +--- .../src/mcmini/model/detached_state.cpp | 2 +- docs/design/src/mcmini/model/program.cpp | 10 ++++---- .../src/mcmini/model/transition_sequence.cpp | 9 ++++++- docs/design/src/mcmini/visible_object.cpp | 5 ++++ 9 files changed, 46 insertions(+), 42 deletions(-) diff --git a/docs/design/include/mcmini/model/pending_transitions.hpp b/docs/design/include/mcmini/model/pending_transitions.hpp index a0ccd479..a9f9c704 100644 --- a/docs/design/include/mcmini/model/pending_transitions.hpp +++ b/docs/design/include/mcmini/model/pending_transitions.hpp @@ -20,7 +20,7 @@ namespace model { struct pending_transitions final { private: using runner_id_t = ::runner_id_t; - std::map> _contents; + std::map _contents; public: auto begin() -> decltype(_contents.begin()) { return _contents.begin(); } @@ -42,22 +42,22 @@ struct pending_transitions final { */ const transition *get_transition_for_runner(runner_id_t id) const { if (_contents.count(id) > 0) { - return _contents.at(id).get(); + return _contents.at(id); } return nullptr; } std::unique_ptr displace_transition_for( - runner_id_t id, std::unique_ptr new_transition) { + runner_id_t id, const transition *new_transition) { if (id != new_transition->get_executor()) { throw std::runtime_error( "Attempting to insert a transition executed by a different runner (" + std::to_string(id) + " != " + std::to_string(new_transition->get_executor()) + ")"); } - auto old_transition = std::move(_contents[id]); - _contents[id] = std::move(new_transition); - return old_transition; + const transition *old_transition = _contents[id]; + _contents[id] = new_transition; + return make_unique(old_transition); } }; diff --git a/docs/design/include/mcmini/model/program.hpp b/docs/design/include/mcmini/model/program.hpp index ef4258e7..3d1cae1b 100644 --- a/docs/design/include/mcmini/model/program.hpp +++ b/docs/design/include/mcmini/model/program.hpp @@ -92,14 +92,13 @@ class program { /// with `next_pending_operation`. /// /// @param p the id of the runner whose next transition should be simulated. - /// @param next_pending_operation the next transition this is pending after - /// `p` executes; that is, this is the transition that `p` will run in the - /// state modeled _after_ `next_s_p` is executed. + /// @param npo (short for `next_pending_operation`) the next transition this + /// is pending after `p` executes; that is, this is the transition that `p` + /// will run in the state modeled _after_ `next_s_p` is executed. /// @throws an runtime exception is raised if the transition replacing /// `next_s_p` is not executed by `p` or if `p` is not currently known to the /// model. - void model_execution_of(runner_id_t p, - std::unique_ptr next_pending_operation); + void model_execution_of(runner_id_t p, const transition *npo); /// @brief Restore the model as if it were `n` steps into execution. /// diff --git a/docs/design/include/mcmini/model/transitions/transition_sequence.hpp b/docs/design/include/mcmini/model/transitions/transition_sequence.hpp index 5e9a9186..97da6e5a 100644 --- a/docs/design/include/mcmini/model/transitions/transition_sequence.hpp +++ b/docs/design/include/mcmini/model/transitions/transition_sequence.hpp @@ -21,11 +21,12 @@ namespace model { */ class transition_sequence final { private: - std::vector> contents; + std::vector contents; public: using index = size_t; transition_sequence() = default; + ~transition_sequence(); auto begin() -> decltype(contents.begin()) { return contents.begin(); } auto end() -> decltype(contents.end()) { return contents.end(); } @@ -34,13 +35,11 @@ class transition_sequence final { bool empty() const { return contents.empty(); } size_t count() const { return contents.size(); } - const transition* at(size_t i) const { return contents.at(i).get(); } - const transition* back() const { return contents.back().get(); } - std::unique_ptr extract_at(size_t i); - void push(std::unique_ptr t) { - contents.push_back(std::move(t)); - } + const transition* at(size_t i) const { return contents.at(i); } + const transition* back() const { return contents.back(); } + void push(const transition* t) { contents.push_back(t); } void consume_into_subsequence(uint32_t depth); + std::unique_ptr extract_at(size_t i); }; } // namespace model \ No newline at end of file diff --git a/docs/design/include/mcmini/model/visible_object.hpp b/docs/design/include/mcmini/model/visible_object.hpp index 543240ca..fd8aa401 100644 --- a/docs/design/include/mcmini/model/visible_object.hpp +++ b/docs/design/include/mcmini/model/visible_object.hpp @@ -26,13 +26,12 @@ namespace model { */ class visible_object final { private: - std::vector> history; + std::vector history; /** - *@brief Construct a visible object with the given history _history_. + * @brief Construct a visible object with the given history _history_. */ - visible_object( - std::vector> history) + visible_object(std::vector history) : history(std::move(history)) {} visible_object(const visible_object &other, size_t num_states) { @@ -44,7 +43,7 @@ class visible_object final { visible_object(visible_object &&) = default; visible_object &operator=(visible_object &&) = default; visible_object(std::unique_ptr &&initial_state) { - push_state(std::move(initial_state)); + push_state(initial_state.release()); } visible_object(const visible_object &other) { *this = other.slice(other.get_num_states()); @@ -52,16 +51,17 @@ class visible_object final { visible_object &operator=(const visible_object &other) { return *this = *other.clone(); } + ~visible_object(); public: size_t get_num_states() const { return history.size(); } const visible_object_state *state_at(size_t i) const { - return this->history.at(i).get(); + return this->history.at(i); } const visible_object_state *get_current_state() const { - return this->history.back().get(); + return this->history.back(); } - void push_state(std::unique_ptr s) { + void push_state(const visible_object_state *s) { history.push_back(std::move(s)); } /** @@ -74,12 +74,10 @@ class visible_object final { * the first `num_states` states. */ visible_object slice(size_t num_states) const { - auto sliced_states = - std::vector>(); + std::vector sliced_states; sliced_states.reserve(num_states); for (int j = 0; j < num_states; j++) { - sliced_states.push_back( - extensions::to_const_unique_ptr(history.at(j)->clone())); + sliced_states.push_back(history.at(j)->clone().release()); } return visible_object(std::move(sliced_states)); } @@ -91,7 +89,7 @@ class visible_object final { if (history.empty()) { return nullptr; } - return std::move(history.back()); + return std::unique_ptr(history.back()); } std::unique_ptr clone() const { diff --git a/docs/design/src/mcmini/mcmini.cpp b/docs/design/src/mcmini/mcmini.cpp index 5e676d09..5d4858e9 100644 --- a/docs/design/src/mcmini/mcmini.cpp +++ b/docs/design/src/mcmini/mcmini.cpp @@ -61,8 +61,6 @@ std::unique_ptr mutex_init_callback( model_to_system_map& m) { pthread_mutex_t* remote_mut; memcpy_v(&remote_mut, (volatile void*)rmb.cnts, sizeof(pthread_mutex_t*)); - - // how do we get the runner??? state::objid_t mut = m.observe_remote_process_handle(remote_mut, objects::mutex::make()); return make_unique(p, mut); @@ -118,7 +116,7 @@ void do_model_checking( state::runner_id_t main_thread_id = state_of_program_at_main.add_runner( objects::thread::make(objects::thread::state::running)); initial_first_steps.displace_transition_for( - 0, make_unique(main_thread_id)); + 0, new transitions::thread_start(main_thread_id)); program model_for_program_starting_at_main(state_of_program_at_main, std::move(initial_first_steps)); diff --git a/docs/design/src/mcmini/model/detached_state.cpp b/docs/design/src/mcmini/model/detached_state.cpp index a9d11d9a..25619bfc 100644 --- a/docs/design/src/mcmini/model/detached_state.cpp +++ b/docs/design/src/mcmini/model/detached_state.cpp @@ -55,7 +55,7 @@ void detached_state::add_state_for_obj( } // INVARIANT: The current element needs to update at index `id` to reflect // this new state, as this element effectively represents this state - this->visible_objects.at(id).push_state(std::move(new_state)); + this->visible_objects.at(id).push_state(new_state.release()); } void detached_state::add_state_for_runner( diff --git a/docs/design/src/mcmini/model/program.cpp b/docs/design/src/mcmini/model/program.cpp index c5791a69..0bbe81f0 100644 --- a/docs/design/src/mcmini/model/program.cpp +++ b/docs/design/src/mcmini/model/program.cpp @@ -46,14 +46,12 @@ void program::restore_model_at_depth(uint32_t n) { trace.consume_into_subsequence(n); } -void program::model_execution_of(runner_id_t p, - std::unique_ptr new_transition) { - if (p != new_transition->get_executor()) { +void program::model_execution_of(runner_id_t p, const transition *npo) { + if (p != npo->get_executor()) { throw std::runtime_error( "The next incoming transition replacing `next_s_p` in the model must " "be run by the same runner (" + - std::to_string(p) + - " != " + std::to_string(new_transition->get_executor()) + ")"); + std::to_string(p) + " != " + std::to_string(npo->get_executor()) + ")"); } const transition *next_s_p = next_steps.get_transition_for_runner(p); @@ -73,7 +71,7 @@ void program::model_execution_of(runner_id_t p, "Attempted to model the execution of a disabled transition(" + next_s_p->debug_string() + ")"); } - trace.push(next_steps.displace_transition_for(p, std::move(new_transition))); + trace.push(next_steps.displace_transition_for(p, npo)); } state::objid_t program::discover_object( diff --git a/docs/design/src/mcmini/model/transition_sequence.cpp b/docs/design/src/mcmini/model/transition_sequence.cpp index ce0a707b..f7626e88 100644 --- a/docs/design/src/mcmini/model/transition_sequence.cpp +++ b/docs/design/src/mcmini/model/transition_sequence.cpp @@ -10,5 +10,12 @@ void transition_sequence::consume_into_subsequence(uint32_t depth) { } std::unique_ptr transition_sequence::extract_at(size_t i) { - return std::move(this->contents.at(i)); + auto result = std::unique_ptr(this->contents.at(i)); + this->contents.at(i) = nullptr; + return result; +} + +transition_sequence::~transition_sequence() { + for (const transition* t : contents) + if (t) delete t; } \ No newline at end of file diff --git a/docs/design/src/mcmini/visible_object.cpp b/docs/design/src/mcmini/visible_object.cpp index 21bfbd89..2a4fc8df 100644 --- a/docs/design/src/mcmini/visible_object.cpp +++ b/docs/design/src/mcmini/visible_object.cpp @@ -1,3 +1,8 @@ #include "mcmini/model/visible_object.hpp" using namespace model; + +visible_object::~visible_object() { + for (const visible_object_state* s : history) + if (s) delete s; +} \ No newline at end of file From f1a6fe813f74b00fbc99342f383f04217d1b2d8a Mon Sep 17 00:00:00 2001 From: Maxwell Pirtle Date: Thu, 23 May 2024 11:38:21 -0400 Subject: [PATCH 041/161] Reduce the use of `std::unique_ptr` in function signatures --- docs/design/CMakeLists.txt | 16 +- .../coordinator/model_to_system_map.hpp | 41 ++--- .../include/mcmini/misc/extensions/memory.hpp | 17 +++ .../mcmini/misc/volatile_mem_streambuf.hpp | 90 +++++------ .../design/include/mcmini/model/exception.hpp | 4 +- .../include/mcmini/model/objects/mutex.hpp | 4 - .../include/mcmini/model/objects/thread.hpp | 4 - .../mcmini/model/pending_transitions.hpp | 20 +-- docs/design/include/mcmini/model/program.hpp | 7 +- docs/design/include/mcmini/model/state.hpp | 45 +----- .../mcmini/model/state/detached_state.hpp | 17 +-- .../include/mcmini/model/state/diff_state.hpp | 17 +-- .../mcmini/model/state/state_sequence.hpp | 29 ++-- .../mcmini/model/transition_registry.hpp | 5 +- .../model/transitions/mutex/mutex_init.hpp | 4 +- .../model/transitions/mutex/mutex_lock.hpp | 4 +- .../model/transitions/mutex/mutex_unlock.hpp | 8 +- .../transitions/thread/thread_create.hpp | 4 +- .../model/transitions/thread/thread_exit.hpp | 2 +- .../model/transitions/thread/thread_join.hpp | 2 +- .../model/transitions/transition_sequence.hpp | 2 +- .../include/mcmini/model/visible_object.hpp | 15 +- .../mcmini/model_checking/algorithm.hpp | 4 + .../algorithms/classic_dpor.hpp | 10 +- .../algorithms/classic_dpor/stack_item.hpp | 24 +-- .../include/mcmini/real_world/process.hpp | 1 + .../mcmini/real_world/process_source.hpp | 2 + .../src/mcmini/coordinator/coordinator.cpp | 70 ++++----- docs/design/src/mcmini/mcmini.cpp | 103 +++++++------ .../src/mcmini/model/detached_state.cpp | 27 ++-- docs/design/src/mcmini/model/diff_state.cpp | 28 ++-- docs/design/src/mcmini/model/program.cpp | 23 ++- .../src/mcmini/model/state_sequence.cpp | 142 ++++++++++++------ .../src/mcmini/model/transition_sequence.cpp | 9 +- .../algorithms/classic_dpor.cpp | 36 +++-- 35 files changed, 453 insertions(+), 383 deletions(-) create mode 100644 docs/design/include/mcmini/misc/extensions/memory.hpp diff --git a/docs/design/CMakeLists.txt b/docs/design/CMakeLists.txt index ebbfbdb5..ea1b7a97 100644 --- a/docs/design/CMakeLists.txt +++ b/docs/design/CMakeLists.txt @@ -50,7 +50,10 @@ set(LIBMCMINI_C_SRC src/lib/wrappers.c ) -# -Wall -> be strict with warnings +set(MCMINI_EXTRA_COMPILER_FLAGS -Wall -Werror) +set(MCMINI_EXTRA_COMPILER_DEFINITIONS "") +set(MCMINI_EXTRA_LINK_FLAGS "") + set(LIBMCMINI_EXTRA_COMPILER_FLAGS -Wall -Werror) set(LIBMCMINI_EXTRA_COMPILER_DEFINITIONS MC_SHARED_LIBRARY=1) @@ -62,7 +65,6 @@ set(LIBMCMINI_EXTRA_LINK_FLAGS -lrt -pthread -lm -ldl) # libmcmini.so -> the dylib which is loaded add_library(libmcmini SHARED "${LIBMCMINI_C_SRC}") -add_library(McMini::Dylib ALIAS libmcmini) set_target_properties(libmcmini PROPERTIES OUTPUT_NAME "mcmini") target_include_directories(libmcmini PUBLIC "${MCMINI_INCLUDE_DIR}") @@ -78,6 +80,14 @@ target_link_libraries(libmcmini add_executable(mcmini "${MCMINI_CPP_SRC}" "${MCMINI_C_SRC}") target_include_directories(mcmini PUBLIC "${MCMINI_INCLUDE_DIR}") +target_compile_definitions(mcmini + PUBLIC + "${MCMINI_EXTRA_COMPILER_DEFINITIONS}") +target_compile_options(mcmini + PRIVATE + "${MCMINI_EXTRA_COMPILER_FLAGS}") +target_link_libraries(mcmini + PUBLIC + "${MCMINI_EXTRA_LINK_FLAGS}") -# Compile examples add_subdirectory(src/examples) diff --git a/docs/design/include/mcmini/coordinator/model_to_system_map.hpp b/docs/design/include/mcmini/coordinator/model_to_system_map.hpp index 83d7a8b5..e9cedd32 100644 --- a/docs/design/include/mcmini/coordinator/model_to_system_map.hpp +++ b/docs/design/include/mcmini/coordinator/model_to_system_map.hpp @@ -1,5 +1,6 @@ #pragma once +#include "mcmini/coordinator/coordinator.hpp" #include "mcmini/forwards.hpp" #include "mcmini/misc/optional.hpp" #include "mcmini/model/state.hpp" @@ -18,6 +19,13 @@ * `pthread_mutex_lock()`). McMini therefore needs to maintain a * correspondence between these addresses and the identifiers in McMini's * model used to represent those objects to the model checker. + * + * @important: Handles are assumed to remain valid _across process source + * invocations_. In the future, we could support the ability to _remap_ + * process handles dynamically during each new re-execution scheduled by + * the coordinator to handle aliasing etc. by using the trace as a total + * ordering on object-creation events. Until we run into this issue, we leave it + * for future development. */ struct model_to_system_map final { private: @@ -31,15 +39,19 @@ struct model_to_system_map final { friend coordinator; public: - /* Prevent external construction */ model_to_system_map() = delete; /** * @brief Retrieve the object that corresponds to the given remote address, or - * `model::invalid_objid` if the address is not known to this mapping. + * `model::invalid_objid` if the address is not contained in this mapping. */ - model::state::objid_t get_object_for_remote_process_handle( - real_world::remote_address) const; + model::state::objid_t get_model_of(real_world::remote_address) const; + bool contains(real_world::remote_address addr) const { + return get_model_of(addr) != model::invalid_objid; + } + + using runner_generation_function = + std::function; /** * @brief Record the presence of a new visible object that is @@ -47,20 +59,11 @@ struct model_to_system_map final { * * @param remote_process_visible_object_handle the address containing * the data for the new visible object across process handles of the - * - * TODO: Handles are assumed to remain valid _across process source - * invocations_. In the future we could support the ability to _remap_ - * process handles dynamically during each new re-execution scheduled by - * the coordinator to handle aliasing etc. + * `real_world::process_source` in the coordinator */ - using runner_generation_function = - std::function( - model::state::runner_id_t)>; - model::state::objid_t observe_remote_process_handle( - real_world::remote_address, - std::unique_ptr); - model::state::runner_id_t observe_remote_process_runner( - real_world::remote_address, - std::unique_ptr, - runner_generation_function f); + model::state::objid_t observe_object(real_world::remote_address, + const model::visible_object_state *); + model::state::runner_id_t observe_runner(real_world::remote_address, + const model::visible_object_state *, + runner_generation_function f); }; diff --git a/docs/design/include/mcmini/misc/extensions/memory.hpp b/docs/design/include/mcmini/misc/extensions/memory.hpp new file mode 100644 index 00000000..7974ac2d --- /dev/null +++ b/docs/design/include/mcmini/misc/extensions/memory.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include + +namespace extensions { + +template +constexpr void destroy_at(T *p) { + if (p) p->~T(); +} + +template +constexpr void destroy(ForwardIt first, ForwardIt last) { + for (; first != last; ++first) destroy_at(std::addressof(*first)); +} + +} // namespace extensions \ No newline at end of file diff --git a/docs/design/include/mcmini/misc/volatile_mem_streambuf.hpp b/docs/design/include/mcmini/misc/volatile_mem_streambuf.hpp index 62aa8a89..a377d81e 100644 --- a/docs/design/include/mcmini/misc/volatile_mem_streambuf.hpp +++ b/docs/design/include/mcmini/misc/volatile_mem_streambuf.hpp @@ -1,52 +1,52 @@ -#pragma once +// #pragma once -#include -#include +// #include +// #include -#include "mcmini/real_world/shm.hpp" +// #include "mcmini/real_world/shm.hpp" -struct volatile_mem_streambuf : public std::streambuf { - private: - char *volatile_cache; - size_t rw_region_size; - volatile char *rw_region; +// struct volatile_mem_streambuf : public std::streambuf { +// private: +// char *volatile_cache; +// size_t rw_region_size; +// volatile char *rw_region; - public: - volatile_mem_streambuf() = default; - volatile_mem_streambuf( - const real_world::shared_memory_region &read_write_region) - : volatile_mem_streambuf(read_write_region.get(), - read_write_region.size()) { - reset(); - } - volatile_mem_streambuf(volatile void *rw_start, size_t rw_size) - : rw_region(static_cast(rw_start)), - rw_region_size(rw_size), - volatile_cache(new char[rw_size]) { - reset(); - } +// public: +// volatile_mem_streambuf() = default; +// volatile_mem_streambuf( +// const real_world::shared_memory_region &read_write_region) +// : volatile_mem_streambuf(read_write_region.get(), +// read_write_region.size()) { +// reset(); +// } +// volatile_mem_streambuf(volatile void *rw_start, size_t rw_size) +// : rw_region(static_cast(rw_start)), +// rw_region_size(rw_size), +// volatile_cache(new char[rw_size]) { +// reset(); +// } - void reset() { - setg(volatile_cache, volatile_cache, volatile_cache + rw_region_size); - setp(volatile_cache, volatile_cache + rw_region_size); - } +// void reset() { +// setg(volatile_cache, volatile_cache, volatile_cache + rw_region_size); +// setp(volatile_cache, volatile_cache + rw_region_size); +// } - ~volatile_mem_streambuf() { delete[] volatile_cache; } +// ~volatile_mem_streambuf() { delete[] volatile_cache; } - protected: - int_type underflow() override { - std::copy(rw_region, rw_region + rw_region_size, volatile_cache); - setg(volatile_cache, volatile_cache, volatile_cache + rw_region_size); - return this->gptr() != this->egptr() ? traits_type::to_int_type(*gptr()) - : traits_type::eof(); - } - int_type overflow(int_type ch = traits_type::eof()) override { - std::copy(volatile_cache, volatile_cache + rw_region_size, rw_region); - setp(volatile_cache, volatile_cache + rw_region_size); - return this->pptr() != this->epptr() ? ch : traits_type::eof(); - } - int sync() override { - std::copy(volatile_cache, volatile_cache + rw_region_size, rw_region); - return std::streambuf::sync(); - } -}; \ No newline at end of file +// protected: +// int_type underflow() override { +// std::copy(rw_region, rw_region + rw_region_size, volatile_cache); +// setg(volatile_cache, volatile_cache, volatile_cache + rw_region_size); +// return this->gptr() != this->egptr() ? traits_type::to_int_type(*gptr()) +// : traits_type::eof(); +// } +// int_type overflow(int_type ch = traits_type::eof()) override { +// std::copy(volatile_cache, volatile_cache + rw_region_size, rw_region); +// setp(volatile_cache, volatile_cache + rw_region_size); +// return this->pptr() != this->epptr() ? ch : traits_type::eof(); +// } +// int sync() override { +// std::copy(volatile_cache, volatile_cache + rw_region_size, rw_region); +// return std::streambuf::sync(); +// } +// }; \ No newline at end of file diff --git a/docs/design/include/mcmini/model/exception.hpp b/docs/design/include/mcmini/model/exception.hpp index 8ba716a3..ea0dd5fe 100644 --- a/docs/design/include/mcmini/model/exception.hpp +++ b/docs/design/include/mcmini/model/exception.hpp @@ -6,5 +6,7 @@ namespace model { /// @brief An error that is raised when a program encounters undefined behavior. -struct undefined_behavior : public std::exception {}; +struct undefined_behavior_exception : public std::runtime_error { + undefined_behavior_exception(const char *what) : std::runtime_error(what) {} +}; } // namespace model \ No newline at end of file diff --git a/docs/design/include/mcmini/model/objects/mutex.hpp b/docs/design/include/mcmini/model/objects/mutex.hpp index 56260ba2..569e2918 100644 --- a/docs/design/include/mcmini/model/objects/mutex.hpp +++ b/docs/design/include/mcmini/model/objects/mutex.hpp @@ -19,10 +19,6 @@ struct mutex : public model::visible_object_state { ~mutex() = default; mutex(const mutex &) = default; mutex(state_type state) : current_state(state) {} - static std::unique_ptr make(state_type state = uninitialized) { - return extensions::make_unique(state); - } - // ---- State Observation --- // bool operator==(const mutex &other) const { return this->current_state == other.current_state; diff --git a/docs/design/include/mcmini/model/objects/thread.hpp b/docs/design/include/mcmini/model/objects/thread.hpp index cfaced62..0d6226c0 100644 --- a/docs/design/include/mcmini/model/objects/thread.hpp +++ b/docs/design/include/mcmini/model/objects/thread.hpp @@ -21,10 +21,6 @@ struct thread : public model::visible_object_state { ~thread() = default; thread(const thread &) = default; thread(state state) : current_state(state) {} - static std::unique_ptr make(state state = embryo) { - return extensions::make_unique(state); - } - // ---- State Observation --- // bool operator==(const thread &other) const { return this->current_state == other.current_state; diff --git a/docs/design/include/mcmini/model/pending_transitions.hpp b/docs/design/include/mcmini/model/pending_transitions.hpp index a9f9c704..24172d09 100644 --- a/docs/design/include/mcmini/model/pending_transitions.hpp +++ b/docs/design/include/mcmini/model/pending_transitions.hpp @@ -3,6 +3,7 @@ #include #include +#include "mcmini/misc/extensions/unique_ptr.hpp" #include "mcmini/model/defines.hpp" #include "mcmini/model/transition.hpp" @@ -23,6 +24,8 @@ struct pending_transitions final { std::map _contents; public: + pending_transitions() = default; + ~pending_transitions() {} auto begin() -> decltype(_contents.begin()) { return _contents.begin(); } auto end() -> decltype(_contents.end()) { return _contents.end(); } auto begin() const -> decltype(_contents.cbegin()) { @@ -47,17 +50,16 @@ struct pending_transitions final { return nullptr; } - std::unique_ptr displace_transition_for( - runner_id_t id, const transition *new_transition) { - if (id != new_transition->get_executor()) { - throw std::runtime_error( - "Attempting to insert a transition executed by a different runner (" + - std::to_string(id) + - " != " + std::to_string(new_transition->get_executor()) + ")"); - } + const transition *release(const transition *new_transition) noexcept { + runner_id_t id = new_transition->get_executor(); const transition *old_transition = _contents[id]; _contents[id] = new_transition; - return make_unique(old_transition); + return old_transition; + } + + std::unique_ptr set_transition( + const transition *new_transition) noexcept { + return std::unique_ptr(release(new_transition)); } }; diff --git a/docs/design/include/mcmini/model/program.hpp b/docs/design/include/mcmini/model/program.hpp index 3d1cae1b..0b9b1dee 100644 --- a/docs/design/include/mcmini/model/program.hpp +++ b/docs/design/include/mcmini/model/program.hpp @@ -71,13 +71,12 @@ class program { } using runner_generation_function = - std::function( - model::state::runner_id_t)>; + std::function; /// @brief Introduce a new object into the model with initial state `s` /// @param s the initial state of the new object to add to the model /// @return the id assigned to the object in the model - state::objid_t discover_object(std::unique_ptr s); + state::objid_t discover_object(const visible_object_state *s); /// @brief Introduce a new object into the model with initial state `s` /// @param s the initial state of the new object to add to the model @@ -85,7 +84,7 @@ class program { /// runner, produces the first transition that runner is executing in the /// model (i.e. the very first pending operation). /// @return the id assigned to the runner. - state::runner_id_t discover_runner(std::unique_ptr s, + state::runner_id_t discover_runner(const visible_object_state *s, runner_generation_function f); /// @brief Model the execution of runner `p` and replace its next operation diff --git a/docs/design/include/mcmini/model/state.hpp b/docs/design/include/mcmini/model/state.hpp index 308e7bf5..b96e030c 100644 --- a/docs/design/include/mcmini/model/state.hpp +++ b/docs/design/include/mcmini/model/state.hpp @@ -10,10 +10,6 @@ #include "mcmini/model/visible_object.hpp" namespace model { -/** - * @brief A particular snapshot in time of a program undergoing verification - * from the perspective of McMini. - */ class state { public: using objid_t = uint32_t; @@ -28,8 +24,6 @@ class state { virtual const visible_object_state *get_state_of_object(objid_t id) const = 0; virtual const visible_object_state *get_state_of_runner( runner_id_t id) const = 0; - virtual std::unique_ptr consume_obj( - objid_t id) && = 0; virtual std::unique_ptr mutable_clone() const = 0; // TODO: Potentially provide an interface here that conforms to C++11's @@ -38,29 +32,6 @@ class state { // is defined elsewhere which should provide a pair of objid_t // and const visible_object_state* associated with that id. - template - static std::unique_ptr from_visible_object_states( - ForwardIter begin, ForwardIter end, Args &&...args) { - auto state = - extensions::make_unique(std::forward(args)...); - for (auto elem = begin; elem != end; elem++) { - state->add_object((*elem)->clone()); - } - return state; - } - - template - static std::unique_ptr from_visible_objects(ForwardIter begin, - ForwardIter end, - Args &&...args) { - auto state = - extensions::make_unique(std::forward(args)...); - for (auto elem = begin; elem != end; elem++) { - state->add_object((*elem).get_current_state()->clone()); - } - return state; - } - template const concrete_visible_object_state *get_state_of_object(objid_t id) const { static_assert(std::is_base_of initial_state) = 0; + virtual objid_t add_object(const visible_object_state *initial_state) = 0; /** * @brief Begin tracking a new visible object, but consider it as the state of @@ -102,8 +72,7 @@ class mutable_state : public state { * @return the id of the runner that was just added. This is NOT the same as * the runner's object id. */ - virtual runner_id_t add_runner( - std::unique_ptr initial_state) = 0; + virtual runner_id_t add_runner(const visible_object_state *initial_state) = 0; /** * @brief Adds the given state _state_ for the object with id _id_. @@ -112,8 +81,8 @@ class mutable_state : public state { * of type `visible_object_state_type`, the behavior of this function is * undefined. */ - virtual void add_state_for_obj( - objid_t id, std::unique_ptr new_state) = 0; + virtual void add_state_for_obj(objid_t id, + const visible_object_state *new_state) = 0; /** * @brief Adds the given state _state_ for the runner with id _id_. @@ -121,8 +90,8 @@ class mutable_state : public state { * This is equivalent to first retrieving the object id of the runner in this * state and then asking for that object's state. */ - virtual void add_state_for_runner( - runner_id_t id, std::unique_ptr new_state) = 0; + virtual void add_state_for_runner(runner_id_t id, + const visible_object_state *new_state) = 0; /** * @brief Creates a copy of the given state. diff --git a/docs/design/include/mcmini/model/state/detached_state.hpp b/docs/design/include/mcmini/model/state/detached_state.hpp index a7598995..84c3c2c1 100644 --- a/docs/design/include/mcmini/model/state/detached_state.hpp +++ b/docs/design/include/mcmini/model/state/detached_state.hpp @@ -18,7 +18,7 @@ class detached_state : public model::mutable_state { append_only visible_objects; // INVARIANT: Runner ids are assigned sequentially. A runner with id `id` is - // mapped to the object id at index `id - 1`. + // mapped to the object id at index `id `. append_only runner_to_obj_map; public: @@ -39,16 +39,11 @@ class detached_state : public model::mutable_state { const visible_object_state *get_state_of_object(objid_t id) const override; const visible_object_state *get_state_of_runner( runner_id_t id) const override; - objid_t add_object( - std::unique_ptr initial_state) override; - runner_id_t add_runner( - std::unique_ptr initial_state) override; - void add_state_for_obj( - objid_t id, std::unique_ptr new_state) override; - void add_state_for_runner( - runner_id_t id, std::unique_ptr new_state) override; - std::unique_ptr consume_obj(objid_t id) && - override; + objid_t add_object(const visible_object_state *) override; + runner_id_t add_runner(const visible_object_state *) override; + void add_state_for_obj(objid_t id, const visible_object_state *) override; + void add_state_for_runner(runner_id_t id, + const visible_object_state *) override; std::unique_ptr mutable_clone() const override; }; diff --git a/docs/design/include/mcmini/model/state/diff_state.hpp b/docs/design/include/mcmini/model/state/diff_state.hpp index a6a40abf..cf8d3ce8 100644 --- a/docs/design/include/mcmini/model/state/diff_state.hpp +++ b/docs/design/include/mcmini/model/state/diff_state.hpp @@ -30,7 +30,7 @@ class diff_state : public mutable_state { /* `mutable_state` overrrides */ size_t count() const override { size_t count = this->new_object_states.size() + base_state.count(); - for (const auto p : new_object_states) { + for (const auto &p : new_object_states) { // Each item in `new_object_states` that is also in `base_state` defines // states for _previously existing_ objects. These objects are accounted // for in `count` (double-counted), hence the `--` @@ -47,16 +47,11 @@ class diff_state : public mutable_state { const visible_object_state *get_state_of_object(objid_t id) const override; const visible_object_state *get_state_of_runner( runner_id_t id) const override; - objid_t add_object( - std::unique_ptr initial_state) override; - runner_id_t add_runner( - std::unique_ptr initial_state) override; - void add_state_for_obj( - objid_t id, std::unique_ptr new_state) override; - void add_state_for_runner( - runner_id_t id, std::unique_ptr new_state) override; - std::unique_ptr consume_obj(objid_t id) && - override; + objid_t add_object(const visible_object_state *) override; + runner_id_t add_runner(const visible_object_state *) override; + void add_state_for_obj(objid_t id, const visible_object_state *) override; + void add_state_for_runner(runner_id_t id, + const visible_object_state *) override; std::unique_ptr mutable_clone() const override; }; } // namespace model diff --git a/docs/design/include/mcmini/model/state/state_sequence.hpp b/docs/design/include/mcmini/model/state/state_sequence.hpp index 41da5111..ec2335ad 100644 --- a/docs/design/include/mcmini/model/state/state_sequence.hpp +++ b/docs/design/include/mcmini/model/state/state_sequence.hpp @@ -26,10 +26,15 @@ namespace model { * represented by the final state in the sequence. A state sequence is never * empty, */ -class state_sequence : public detached_state { +class state_sequence : public mutable_state { private: class element; + // INVARIANT: Runner ids are assigned sequentially. A runner with id `id` is + // mapped to the object id at index `id `. + append_only runner_to_obj_map; + append_only visible_objects; + /// @brief Inserts an instance of `element` in the `states_in_sequence` void push_state_snapshot(); @@ -49,9 +54,7 @@ class state_sequence : public detached_state { state_sequence(); ~state_sequence(); state_sequence(const state &); - // state_sequence(state &&); - state_sequence(state_sequence &) = delete; - state_sequence(state_sequence &&) = default; + state_sequence(state_sequence &&); state_sequence(std::vector &&); state_sequence(append_only &&); state_sequence &operator=(const state_sequence &&) = delete; @@ -60,12 +63,18 @@ class state_sequence : public detached_state { size_t count() const override; size_t runner_count() const override; size_t get_num_states_in_sequence() const; - objid_t add_object( - std::unique_ptr initial_state) override; - runner_id_t add_runner( - std::unique_ptr initial_state) override; - void add_state_for_obj( - objid_t id, std::unique_ptr new_state) override; + + objid_t get_objid_for_runner(runner_id_t id) const override; + bool contains_object_with_id(state::objid_t id) const override; + bool contains_runner_with_id(runner_id_t id) const override; + const visible_object_state *get_state_of_object(objid_t id) const override; + const visible_object_state *get_state_of_runner( + runner_id_t id) const override; + objid_t add_object(const visible_object_state *) override; + runner_id_t add_runner(const visible_object_state *) override; + void add_state_for_obj(objid_t id, const visible_object_state *) override; + void add_state_for_runner(runner_id_t id, + const visible_object_state *) override; std::unique_ptr mutable_clone() const override; /* Applying transitions */ diff --git a/docs/design/include/mcmini/model/transition_registry.hpp b/docs/design/include/mcmini/model/transition_registry.hpp index 459d8b25..7fce549c 100644 --- a/docs/design/include/mcmini/model/transition_registry.hpp +++ b/docs/design/include/mcmini/model/transition_registry.hpp @@ -27,8 +27,9 @@ class transition_registry final { public: using runtime_type_id = uint32_t; using rttid = runtime_type_id; - using transition_discovery_callback = std::unique_ptr (*)( - state::runner_id_t, const volatile runner_mailbox&, model_to_system_map&); + using transition_discovery_callback = + transition *(*)(state::runner_id_t, const volatile runner_mailbox &, + model_to_system_map &); /** * @brief Marks the specified transition subclass as possible to encounter at diff --git a/docs/design/include/mcmini/model/transitions/mutex/mutex_init.hpp b/docs/design/include/mcmini/model/transitions/mutex/mutex_init.hpp index 5a389043..5749abac 100644 --- a/docs/design/include/mcmini/model/transitions/mutex/mutex_init.hpp +++ b/docs/design/include/mcmini/model/transitions/mutex/mutex_init.hpp @@ -12,12 +12,12 @@ struct mutex_init : public model::transition { public: mutex_init(runner_id_t executor, state::objid_t mutex_id) - : mutex_id(mutex_id), transition(executor) {} + : transition(executor), mutex_id(mutex_id) {} ~mutex_init() = default; status modify(model::mutable_state& s) const override { using namespace model::objects; - s.add_state_for_obj(mutex_id, mutex::make(mutex::unlocked)); + s.add_state_for_obj(mutex_id, new mutex(mutex::unlocked)); return status::exists; } diff --git a/docs/design/include/mcmini/model/transitions/mutex/mutex_lock.hpp b/docs/design/include/mcmini/model/transitions/mutex/mutex_lock.hpp index ecfabfa7..ca0452fb 100644 --- a/docs/design/include/mcmini/model/transitions/mutex/mutex_lock.hpp +++ b/docs/design/include/mcmini/model/transitions/mutex/mutex_lock.hpp @@ -12,7 +12,7 @@ struct mutex_lock : public model::transition { public: mutex_lock(runner_id_t executor, state::objid_t mutex_id) - : mutex_id(mutex_id), transition(executor) {} + : transition(executor), mutex_id(mutex_id) {} ~mutex_lock() = default; status modify(model::mutable_state& s) const override { @@ -23,7 +23,7 @@ struct mutex_lock : public model::transition { if (ms->is_locked()) { return status::disabled; } - s.add_state_for_obj(mutex_id, mutex::make(mutex::locked)); + s.add_state_for_obj(mutex_id, new mutex(mutex::locked)); return status::exists; } diff --git a/docs/design/include/mcmini/model/transitions/mutex/mutex_unlock.hpp b/docs/design/include/mcmini/model/transitions/mutex/mutex_unlock.hpp index 180c4734..80d3717e 100644 --- a/docs/design/include/mcmini/model/transitions/mutex/mutex_unlock.hpp +++ b/docs/design/include/mcmini/model/transitions/mutex/mutex_unlock.hpp @@ -12,13 +12,15 @@ struct mutex_unlock : public model::transition { public: mutex_unlock(runner_id_t executor, state::objid_t mutex_id) - : mutex_id(mutex_id), transition(executor) {} + : transition(executor), mutex_id(mutex_id) {} ~mutex_unlock() = default; status modify(model::mutable_state& s) const override { using namespace model::objects; - const mutex* ms = s.get_state_of_object(mutex_id); - s.add_state_for_obj(mutex_id, mutex::make(mutex::unlocked)); + // TODO: If the mutex already unlocked, this would be erroneous program + // behavior. We should distinguish between this and other cases. + // const mutex* ms = s.get_state_of_object(mutex_id); + s.add_state_for_obj(mutex_id, new mutex(mutex::unlocked)); return status::exists; } diff --git a/docs/design/include/mcmini/model/transitions/thread/thread_create.hpp b/docs/design/include/mcmini/model/transitions/thread/thread_create.hpp index 21e2f973..6da32358 100644 --- a/docs/design/include/mcmini/model/transitions/thread/thread_create.hpp +++ b/docs/design/include/mcmini/model/transitions/thread/thread_create.hpp @@ -12,12 +12,12 @@ struct thread_create : public model::transition { public: thread_create(state::runner_id_t executor, state::runner_id_t target) - : target(target), transition(executor) {} + : transition(executor), target(target) {} ~thread_create() = default; status modify(model::mutable_state& s) const override { using namespace model::objects; - s.add_state_for_runner(target, thread::make(thread::running)); + s.add_state_for_runner(target, new thread(thread::running)); return status::exists; } diff --git a/docs/design/include/mcmini/model/transitions/thread/thread_exit.hpp b/docs/design/include/mcmini/model/transitions/thread/thread_exit.hpp index 7360947c..282e6393 100644 --- a/docs/design/include/mcmini/model/transitions/thread/thread_exit.hpp +++ b/docs/design/include/mcmini/model/transitions/thread/thread_exit.hpp @@ -17,7 +17,7 @@ struct thread_exit : public model::transition { if (!thread_state->is_running() || executor == TID_MAIN_THREAD) { return status::disabled; } - s.add_state_for_runner(executor, thread::make(thread::exited)); + s.add_state_for_runner(executor, new thread(thread::exited)); return status::exists; } diff --git a/docs/design/include/mcmini/model/transitions/thread/thread_join.hpp b/docs/design/include/mcmini/model/transitions/thread/thread_join.hpp index 0f16dc9e..c1cc124d 100644 --- a/docs/design/include/mcmini/model/transitions/thread/thread_join.hpp +++ b/docs/design/include/mcmini/model/transitions/thread/thread_join.hpp @@ -12,7 +12,7 @@ struct thread_join : public model::transition { public: thread_join(state::runner_id_t executor, state::runner_id_t target) - : target(target), transition(executor) {} + : transition(executor), target(target) {} ~thread_join() = default; status modify(model::mutable_state& s) const override { diff --git a/docs/design/include/mcmini/model/transitions/transition_sequence.hpp b/docs/design/include/mcmini/model/transitions/transition_sequence.hpp index 97da6e5a..4ec85241 100644 --- a/docs/design/include/mcmini/model/transitions/transition_sequence.hpp +++ b/docs/design/include/mcmini/model/transitions/transition_sequence.hpp @@ -37,9 +37,9 @@ class transition_sequence final { size_t count() const { return contents.size(); } const transition* at(size_t i) const { return contents.at(i); } const transition* back() const { return contents.back(); } + std::unique_ptr extract_at(size_t i); void push(const transition* t) { contents.push_back(t); } void consume_into_subsequence(uint32_t depth); - std::unique_ptr extract_at(size_t i); }; } // namespace model \ No newline at end of file diff --git a/docs/design/include/mcmini/model/visible_object.hpp b/docs/design/include/mcmini/model/visible_object.hpp index fd8aa401..7302be47 100644 --- a/docs/design/include/mcmini/model/visible_object.hpp +++ b/docs/design/include/mcmini/model/visible_object.hpp @@ -42,9 +42,11 @@ class visible_object final { visible_object() = default; visible_object(visible_object &&) = default; visible_object &operator=(visible_object &&) = default; - visible_object(std::unique_ptr &&initial_state) { - push_state(initial_state.release()); + visible_object(const visible_object_state *initial_state) { + push_state(initial_state); } + visible_object(std::unique_ptr initial_state) + : visible_object(initial_state.release()) {} visible_object(const visible_object &other) { *this = other.slice(other.get_num_states()); } @@ -76,7 +78,7 @@ class visible_object final { visible_object slice(size_t num_states) const { std::vector sliced_states; sliced_states.reserve(num_states); - for (int j = 0; j < num_states; j++) { + for (size_t j = 0; j < num_states; j++) { sliced_states.push_back(history.at(j)->clone().release()); } return visible_object(std::move(sliced_states)); @@ -84,12 +86,15 @@ class visible_object final { /// @brief Extracts the current state from this object. /// @return a pointer to the current state of this object, or `nullptr` is the - /// object contains no states. + /// object contains no states. After calling this method, the visible object + /// should be considered destroyed std::unique_ptr consume_into_current_state() { if (history.empty()) { return nullptr; } - return std::unique_ptr(history.back()); + auto result = std::unique_ptr(history.back()); + history.pop_back(); + return result; } std::unique_ptr clone() const { diff --git a/docs/design/include/mcmini/model_checking/algorithm.hpp b/docs/design/include/mcmini/model_checking/algorithm.hpp index 5cbcd15b..43af7772 100644 --- a/docs/design/include/mcmini/model_checking/algorithm.hpp +++ b/docs/design/include/mcmini/model_checking/algorithm.hpp @@ -3,6 +3,7 @@ #include #include "mcmini/coordinator/coordinator.hpp" +#include "mcmini/model/exception.hpp" #include "mcmini/model/program.hpp" namespace model_checking { @@ -21,6 +22,9 @@ class algorithm { std::function data_race; std::function unknown_error; std::function trace_completed; + std::function + undefined_behavior; }; /** diff --git a/docs/design/include/mcmini/model_checking/algorithms/classic_dpor.hpp b/docs/design/include/mcmini/model_checking/algorithms/classic_dpor.hpp index 95506c7b..403e028e 100644 --- a/docs/design/include/mcmini/model_checking/algorithms/classic_dpor.hpp +++ b/docs/design/include/mcmini/model_checking/algorithms/classic_dpor.hpp @@ -40,9 +40,10 @@ class classic_dpor final : public algorithm { struct dpor_context; - bool happens_before(const dpor_context &, int i, int j) const; - bool happens_before_thread(const dpor_context &, int i, runner_id_t p) const; - bool threads_race_after(const dpor_context &context, int i, runner_id_t q, + bool happens_before(const dpor_context &, size_t i, size_t j) const; + bool happens_before_thread(const dpor_context &, size_t i, + runner_id_t p) const; + bool threads_race_after(const dpor_context &context, size_t i, runner_id_t q, runner_id_t p) const; clock_vector accumulate_max_clock_vector_against(const model::transition &, @@ -54,7 +55,8 @@ class classic_dpor final : public algorithm { bool dynamically_update_backtrack_sets_at_index( const dpor_context &, const model::transition &S_i, - const model::transition &nextSP, stack_item &preSi, int i, int p); + const model::transition &nextSP, stack_item &preSi, size_t i, + runner_id_t p); }; } // namespace model_checking \ No newline at end of file diff --git a/docs/design/include/mcmini/model_checking/algorithms/classic_dpor/stack_item.hpp b/docs/design/include/mcmini/model_checking/algorithms/classic_dpor/stack_item.hpp index b7d696ae..6795fa37 100644 --- a/docs/design/include/mcmini/model_checking/algorithms/classic_dpor/stack_item.hpp +++ b/docs/design/include/mcmini/model_checking/algorithms/classic_dpor/stack_item.hpp @@ -24,6 +24,18 @@ namespace model_checking { */ struct stack_item final { private: + /** + * @brief The clock vector associated with the + * transition _resulting_ in this state + */ + const clock_vector cv; + + /// @brief The transition which DPOR decided to schedule from this state. + /// + /// @note the item does not own this transition; it is instead owned by the + /// trace of the model which the DPOR algorithm manipulates. It is up to the + const model::transition *out_transition = nullptr; + /** * @brief A collection of threads that are scheduled to be run from this state * to continune to state-space search @@ -88,18 +100,6 @@ struct stack_item final { */ std::unordered_set enabled_runners; - /** - * @brief The clock vector associated with the - * transition _resulting_ in this state - */ - const clock_vector cv; - - /// @brief The transition which DPOR decided to schedule from this state. - /// - /// @note the item does not own this transition; it is instead owned by the - /// trace of the model which the DPOR algorithm manipulates. It is up to the - const model::transition *out_transition = nullptr; - public: stack_item() : stack_item(clock_vector()) {} stack_item(clock_vector cv) : cv(std::move(cv)) {} diff --git a/docs/design/include/mcmini/real_world/process.hpp b/docs/design/include/mcmini/real_world/process.hpp index 79f74a5c..fd6e21b5 100644 --- a/docs/design/include/mcmini/real_world/process.hpp +++ b/docs/design/include/mcmini/real_world/process.hpp @@ -2,6 +2,7 @@ #include #include +#include #include "mcmini/model/defines.hpp" #include "mcmini/real_world/mailbox/runner_mailbox.h" diff --git a/docs/design/include/mcmini/real_world/process_source.hpp b/docs/design/include/mcmini/real_world/process_source.hpp index 2d405cf8..fc10fb26 100644 --- a/docs/design/include/mcmini/real_world/process_source.hpp +++ b/docs/design/include/mcmini/real_world/process_source.hpp @@ -1,5 +1,7 @@ #pragma once +#include + #include "mcmini/real_world/process.hpp" namespace real_world { diff --git a/docs/design/src/mcmini/coordinator/coordinator.cpp b/docs/design/src/mcmini/coordinator/coordinator.cpp index 5562eb44..491313c1 100644 --- a/docs/design/src/mcmini/coordinator/coordinator.cpp +++ b/docs/design/src/mcmini/coordinator/coordinator.cpp @@ -39,15 +39,14 @@ void coordinator::execute_runner(process::runner_id_t runner_id) { "libmcmini.so with this message."); } model_to_system_map remote_address_mapping = model_to_system_map(*this); - auto pending_operation = + model::transition *pending_operation = callback_function(runner_id, *mb, remote_address_mapping); if (!pending_operation) { throw real_world::process::execution_exception( "Failed to translate the data written into the mailbox of runner " + std::to_string(runner_id)); } - this->current_program_model.model_execution_of(runner_id, - std::move(pending_operation)); + this->current_program_model.model_execution_of(runner_id, pending_operation); } void coordinator::return_to_depth(uint32_t n) { @@ -59,12 +58,12 @@ void coordinator::return_to_depth(uint32_t n) { // sequence; hence to ensure the model and the real world correspond, we must // re-execute the threads in the order specified in the transition sequence. assert(this->current_program_model.get_trace().count() == n); - for (const auto &t : this->current_program_model.get_trace()) { + for (const model::transition *t : this->current_program_model.get_trace()) { this->current_process_handle->execute_runner(t->get_executor()); } } -model::state::objid_t model_to_system_map::get_object_for_remote_process_handle( +model::state::objid_t model_to_system_map::get_model_of( remote_address handle) const { if (_coordinator.system_address_mapping.count(handle) > 0) { return _coordinator.system_address_mapping[handle]; @@ -72,42 +71,35 @@ model::state::objid_t model_to_system_map::get_object_for_remote_process_handle( return model::invalid_objid; } -model::state::objid_t model_to_system_map::observe_remote_process_handle( - remote_address remote_process_visible_object_handle, - std::unique_ptr fallback_initial_state) { - model::state::objid_t existing_obj = - this->get_object_for_remote_process_handle( - remote_process_visible_object_handle); - if (existing_obj != model::invalid_objid) { - return existing_obj; - } else { - model::state::objid_t new_objid = - _coordinator.current_program_model.discover_object( - std::move(fallback_initial_state)); - _coordinator.system_address_mapping.insert( - {remote_process_visible_object_handle, new_objid}); - return new_objid; +model::state::objid_t model_to_system_map::observe_object( + real_world::remote_address rp_vobj_handle, + const model::visible_object_state *vobs) { + if (contains(rp_vobj_handle)) { + throw std::runtime_error( + "Attempting to rebind a remote address to an object that already " + "exists in the model. Did you check that the object doesn't already " + "exist?"); } + model::state::objid_t new_objid = + _coordinator.current_program_model.discover_object(vobs); + _coordinator.system_address_mapping.insert({rp_vobj_handle, new_objid}); + return new_objid; } -model::state::runner_id_t model_to_system_map::observe_remote_process_runner( - real_world::remote_address remote_process_visible_object_handle, - std::unique_ptr fallback_initial_state, - runner_generation_function f) { - model::state::objid_t existing_obj = - this->get_object_for_remote_process_handle( - remote_process_visible_object_handle); - if (existing_obj != model::invalid_objid) { - return existing_obj; - } else { - model::state::runner_id_t new_runner_id = - _coordinator.current_program_model.discover_runner( - std::move(fallback_initial_state), std::move(f)); - model::state::objid_t new_objid = _coordinator.get_current_program_model() - .get_state_sequence() - .get_objid_for_runner(new_runner_id); - _coordinator.system_address_mapping.insert( - {remote_process_visible_object_handle, new_objid}); - return new_runner_id; +model::state::runner_id_t model_to_system_map::observe_runner( + real_world::remote_address rp_vobj_handle, + const model::visible_object_state *vobs, runner_generation_function f) { + if (contains(rp_vobj_handle)) { + throw std::runtime_error( + "Attempting to rebind a remote address to an object that already " + "exists in the model. Did you check that the object doesn't already " + "exist?"); } + model::state::runner_id_t new_runner_id = + _coordinator.current_program_model.discover_runner(vobs, std::move(f)); + model::state::objid_t new_objid = _coordinator.get_current_program_model() + .get_state_sequence() + .get_objid_for_runner(new_runner_id); + _coordinator.system_address_mapping.insert({rp_vobj_handle, new_objid}); + return new_runner_id; } \ No newline at end of file diff --git a/docs/design/src/mcmini/mcmini.cpp b/docs/design/src/mcmini/mcmini.cpp index 5d4858e9..622c5e00 100644 --- a/docs/design/src/mcmini/mcmini.cpp +++ b/docs/design/src/mcmini/mcmini.cpp @@ -31,6 +31,7 @@ using namespace extensions; using namespace model; +using namespace objects; using namespace real_world; void display_usage() { @@ -56,67 +57,80 @@ void finished_trace_classic_dpor(const coordinator& c) { trace_id++; } -std::unique_ptr mutex_init_callback( - state::runner_id_t p, const volatile runner_mailbox& rmb, - model_to_system_map& m) { +model::transition* mutex_init_callback(state::runner_id_t p, + const volatile runner_mailbox& rmb, + model_to_system_map& m) { + // Fetch the remote object pthread_mutex_t* remote_mut; memcpy_v(&remote_mut, (volatile void*)rmb.cnts, sizeof(pthread_mutex_t*)); - state::objid_t mut = - m.observe_remote_process_handle(remote_mut, objects::mutex::make()); - return make_unique(p, mut); + + // Locate the corresponding model of this object + if (!m.contains(remote_mut)) + m.observe_object(remote_mut, new mutex(mutex::state_type::uninitialized)); + + state::objid_t mut = m.get_model_of(remote_mut); + return new transitions::mutex_init(p, mut); } -std::unique_ptr mutex_lock_callback( - state::runner_id_t p, const volatile runner_mailbox& rmb, - model_to_system_map& m) { +model::transition* mutex_lock_callback(state::runner_id_t p, + const volatile runner_mailbox& rmb, + model_to_system_map& m) { pthread_mutex_t* remote_mut; memcpy_v(&remote_mut, (volatile void*)rmb.cnts, sizeof(pthread_mutex_t*)); - state::objid_t mut = - m.observe_remote_process_handle(remote_mut, objects::mutex::make()); - return make_unique(p, mut); + + // TODO: add code from Gene's PR here + if (!m.contains(remote_mut)) + throw undefined_behavior_exception( + "Attempting to lock an uninitialized mutex"); + + state::objid_t mut = m.get_model_of(remote_mut); + return new transitions::mutex_lock(p, mut); } -std::unique_ptr mutex_unlock_callback( - state::runner_id_t p, const volatile runner_mailbox& rmb, - model_to_system_map& m) { +model::transition* mutex_unlock_callback(state::runner_id_t p, + const volatile runner_mailbox& rmb, + model_to_system_map& m) { pthread_mutex_t* remote_mut; memcpy_v(&remote_mut, (volatile void*)rmb.cnts, sizeof(pthread_mutex_t*)); - state::objid_t mut = - m.observe_remote_process_handle(remote_mut, objects::mutex::make()); - return make_unique(p, mut); + + // TODO: add code from Gene's PR here + if (!m.contains(remote_mut)) + throw undefined_behavior_exception( + "Attempting to lock an uninitialized mutex"); + + state::objid_t mut = m.get_model_of(remote_mut); + return new transitions::mutex_unlock(p, mut); } -std::unique_ptr thread_create_callback( - state::runner_id_t p, const volatile runner_mailbox& rmb, - model_to_system_map& m) { +model::transition* thread_create_callback(state::runner_id_t p, + const volatile runner_mailbox& rmb, + model_to_system_map& m) { pthread_t new_thread; memcpy_v(&new_thread, static_cast(&rmb.cnts), sizeof(pthread_t)); - runner_id_t new_thread_id = m.observe_remote_process_runner( - (void*)new_thread, objects::thread::make(objects::thread::state::running), - [](runner_id_t id) { - return make_unique(id); - }); - return make_unique(p, new_thread_id); + runner_id_t new_thread_id = m.observe_runner( + (void*)new_thread, new objects::thread(objects::thread::embryo), + [](runner_id_t id) { return new transitions::thread_start(id); }); + return new transitions::thread_create(p, new_thread_id); } -std::unique_ptr thread_exit_callback( - state::runner_id_t p, const volatile runner_mailbox& rmb, - model_to_system_map& m) { - return make_unique(p); +model::transition* thread_exit_callback(state::runner_id_t p, + const volatile runner_mailbox& rmb, + model_to_system_map& m) { + return new transitions::thread_exit(p); } void do_model_checking( /* Pass arguments here or rearrange to configure the checker at runtime, e.g. to pick an algorithm, set a max depth, etc. */) { - state_sequence state_of_program_at_main; + detached_state state_of_program_at_main; pending_transitions initial_first_steps; transition_registry tr; state::runner_id_t main_thread_id = state_of_program_at_main.add_runner( - objects::thread::make(objects::thread::state::running)); - initial_first_steps.displace_transition_for( - 0, new transitions::thread_start(main_thread_id)); + new objects::thread(objects::thread::state::running)); + initial_first_steps.set_transition( + new transitions::thread_start(main_thread_id)); program model_for_program_starting_at_main(state_of_program_at_main, std::move(initial_first_steps)); @@ -197,13 +211,14 @@ int main_cpp(int argc, const char** argv) { } int main(int argc, const char** argv) { - try { - return main_cpp(argc, argv); - } catch (const std::exception& e) { - std::cerr << "ERROR: " << e.what() << std::endl; - return EXIT_FAILURE; - } catch (...) { - std::cerr << "ERROR: Unknown error occurred" << std::endl; - return EXIT_FAILURE; - } + // try { + + // } catch (const std::exception& e) { + // std::cerr << "ERROR: " << e.what() << std::endl; + // return EXIT_FAILURE; + // } catch (...) { + // std::cerr << "ERROR: Unknown error occurred" << std::endl; + // return EXIT_FAILURE; + // } + return main_cpp(argc, argv); } diff --git a/docs/design/src/mcmini/model/detached_state.cpp b/docs/design/src/mcmini/model/detached_state.cpp index 25619bfc..0ab54413 100644 --- a/docs/design/src/mcmini/model/detached_state.cpp +++ b/docs/design/src/mcmini/model/detached_state.cpp @@ -34,39 +34,34 @@ const visible_object_state *detached_state::get_state_of_runner( } state::objid_t detached_state::add_object( - std::unique_ptr initial_state) { + const visible_object_state *initial_state) { // INVARIANT: The current element needs to update at index `id` to reflect // this new object, as this element effectively represents this state - visible_objects.push_back(std::move(initial_state)); + visible_objects.push_back(initial_state); return visible_objects.size() - 1; } state::runner_id_t detached_state::add_runner( - std::unique_ptr new_state) { - objid_t id = this->add_object(std::move(new_state)); + const visible_object_state *new_state) { + objid_t id = this->add_object(new_state); this->runner_to_obj_map.push_back(id); return this->runner_to_obj_map.size() - 1; } -void detached_state::add_state_for_obj( - objid_t id, std::unique_ptr new_state) { +void detached_state::add_state_for_obj(objid_t id, + const visible_object_state *new_state) { if (id == invalid_objid) { - throw std::runtime_error("Attempted to insert an invalid object id"); + throw std::runtime_error( + "Attempted to insert a state for an invalid object id"); } // INVARIANT: The current element needs to update at index `id` to reflect // this new state, as this element effectively represents this state - this->visible_objects.at(id).push_state(new_state.release()); + this->visible_objects.at(id).push_state(new_state); } void detached_state::add_state_for_runner( - runner_id_t id, std::unique_ptr new_state) { - return this->add_state_for_obj(this->get_objid_for_runner(id), - std::move(new_state)); -} - -std::unique_ptr detached_state::consume_obj( - objid_t id) && { - return std::move(visible_objects.at(id)).consume_into_current_state(); + runner_id_t id, const visible_object_state *new_state) { + return this->add_state_for_obj(this->get_objid_for_runner(id), new_state); } std::unique_ptr detached_state::mutable_clone() const { diff --git a/docs/design/src/mcmini/model/diff_state.cpp b/docs/design/src/mcmini/model/diff_state.cpp index 19f3ab4b..87fdc747 100644 --- a/docs/design/src/mcmini/model/diff_state.cpp +++ b/docs/design/src/mcmini/model/diff_state.cpp @@ -38,18 +38,18 @@ const visible_object_state *diff_state::get_state_of_runner( } state::objid_t diff_state::add_object( - std::unique_ptr initial_state) { + const visible_object_state *initial_state) { // The next id that would be assigned is one more than // the largest id available. The last id of the base it `size() - 1` and // we are `new_object_state.size()` elements in state::objid_t next_id = base_state.count() + new_object_states.size(); - new_object_states[next_id] = visible_object(std::move(initial_state)); + new_object_states[next_id] = initial_state; return next_id; } state::runner_id_t diff_state::add_runner( - std::unique_ptr initial_state) { - objid_t objid = this->add_object(std::move(initial_state)); + const visible_object_state *initial_state) { + objid_t objid = this->add_object(initial_state); // The next runner id would be the current size. state::objid_t next_runner_id = runner_count(); @@ -57,25 +57,21 @@ state::runner_id_t diff_state::add_runner( return next_runner_id; } -void diff_state::add_state_for_obj( - objid_t id, std::unique_ptr new_state) { +void diff_state::add_state_for_obj(objid_t id, + const visible_object_state *new_state) { if (id == invalid_objid) { - throw std::runtime_error("Attempted to insert an invalid object id"); + throw std::runtime_error( + "Attempted to insert a state for an invalid object id"); } // Here we seek to insert all new states into the local cache instead of // forwarding them onto the underlying base state. visible_object &vobj = new_object_states[id]; - vobj.push_state(std::move(new_state)); + vobj.push_state(new_state); } -void diff_state::add_state_for_runner( - runner_id_t id, std::unique_ptr new_state) { - this->add_state_for_obj(this->get_objid_for_runner(id), std::move(new_state)); -} - -std::unique_ptr diff_state::consume_obj( - objid_t id) && { - throw std::runtime_error("Consumption is not permitted on diff states"); +void diff_state::add_state_for_runner(runner_id_t id, + const visible_object_state *new_state) { + this->add_state_for_obj(this->get_objid_for_runner(id), new_state); } std::unique_ptr diff_state::mutable_clone() const { diff --git a/docs/design/src/mcmini/model/program.cpp b/docs/design/src/mcmini/model/program.cpp index 0bbe81f0..b7dd293d 100644 --- a/docs/design/src/mcmini/model/program.cpp +++ b/docs/design/src/mcmini/model/program.cpp @@ -6,7 +6,7 @@ using namespace model; program::program(const state &initial_state, pending_transitions &&initial_first_steps) - : next_steps(std::move(initial_first_steps)), state_seq(initial_state) {} + : state_seq(initial_state), next_steps(std::move(initial_first_steps)) {} std::unordered_set program::get_enabled_runners() const { std::unordered_set enabled_runners; @@ -39,8 +39,8 @@ void program::restore_model_at_depth(uint32_t n) { for (int32_t i = (trace.count() - 1); i >= (int32_t)(n); i--) runner_to_index_from_top[trace.at(i)->get_executor()] = i; - for (const std::pair &e : runner_to_index_from_top) - next_steps.displace_transition_for(e.first, trace.extract_at(e.second)); + for (const std::pair e : runner_to_index_from_top) + next_steps.set_transition(trace.extract_at(e.second).release()); state_seq.consume_into_subsequence(n); trace.consume_into_subsequence(n); @@ -71,25 +71,24 @@ void program::model_execution_of(runner_id_t p, const transition *npo) { "Attempted to model the execution of a disabled transition(" + next_s_p->debug_string() + ")"); } - trace.push(next_steps.displace_transition_for(p, npo)); + trace.push(next_steps.release(npo)); } state::objid_t program::discover_object( - std::unique_ptr initial_state) { - return this->state_seq.add_object(std::move(initial_state)); + const visible_object_state *initial_state) { + return this->state_seq.add_object(initial_state); } state::runner_id_t program::discover_runner( - std::unique_ptr initial_state, - runner_generation_function f) { - state::runner_id_t id = this->state_seq.add_runner(std::move(initial_state)); - this->next_steps.displace_transition_for(id, f(id)); + const visible_object_state *initial_state, runner_generation_function f) { + state::runner_id_t id = this->state_seq.add_runner(initial_state); + this->next_steps.set_transition(f(id)); return id; } bool program::is_in_deadlock() const { - for (const auto &t : this->get_pending_transitions()) { - if (t.second->is_enabled_in(this->state_seq)) return false; + for (const auto &p : this->get_pending_transitions()) { + if (p.second->is_enabled_in(this->state_seq)) return false; } return true; } \ No newline at end of file diff --git a/docs/design/src/mcmini/model/state_sequence.cpp b/docs/design/src/mcmini/model/state_sequence.cpp index 71531cff..bfd20437 100644 --- a/docs/design/src/mcmini/model/state_sequence.cpp +++ b/docs/design/src/mcmini/model/state_sequence.cpp @@ -6,6 +6,7 @@ #include "mcmini/misc/append-only.hpp" #include "mcmini/misc/asserts.hpp" +#include "mcmini/misc/extensions/memory.hpp" #include "mcmini/misc/extensions/unique_ptr.hpp" #include "mcmini/model/state/detached_state.hpp" @@ -21,7 +22,7 @@ using namespace model; */ class state_sequence::element : public state { private: - const state_sequence &owner; + const state_sequence *owner; /// @brief A collection of references to states in the sequence /// _owning_sequence_ to which this element belongs. @@ -34,7 +35,7 @@ class state_sequence::element : public state { // this element is aware of. runner_id_t max_visible_runner_id; - element(const state_sequence &owner); + element(const state_sequence *owner); friend state_sequence; public: @@ -51,8 +52,6 @@ class state_sequence::element : public state { const visible_object_state *get_state_of_object(objid_t id) const override; const visible_object_state *get_state_of_runner( runner_id_t id) const override; - std::unique_ptr consume_obj(objid_t id) && - override; std::unique_ptr mutable_clone() const override; }; @@ -60,66 +59,120 @@ state_sequence::state_sequence() { this->push_state_snapshot(); } state_sequence::state_sequence(const state &s) { this->push_state_snapshot(); - - // Copy objects const size_t num_objs = s.count(); - for (objid_t i = 0; i < num_objs; i++) { - add_object(s.get_state_of_object(i)->clone()); + for (objid_t i = 0; i < (objid_t)num_objs; i++) + add_object(s.get_state_of_object(i)->clone().release()); + + const size_t num_runners = s.runner_count(); + for (runner_id_t p = 0; p < (runner_id_t)num_runners; p++) { + this->get_representative_state().record_new_runner(); + this->runner_to_obj_map.push_back(s.get_objid_for_runner(p)); } +} - // Copy runner information. Here, the state of the runner has already been - // captured - this->runner_to_obj_map = append_only(s.runner_count()); - std::iota(this->runner_to_obj_map.begin(), this->runner_to_obj_map.end(), 0); +state_sequence::state_sequence(state_sequence &&s) { + // For the std::vector<> move constructor: + // """ + // Constructs the container with the contents of + // other using move semantics. Allocator is obtained by move-construction + // from the allocator belonging to other. After the move, other is guaranteed + // to be empty(). + // """ + // See https://en.cppreference.com/w/cpp/container/vector/vector + this->states_in_sequence = std::move(s.states_in_sequence); + this->visible_objects = std::move(s.visible_objects); + this->runner_to_obj_map = std::move(s.runner_to_obj_map); + + // The elements currently point to `s` as their owner. Since we + // are now transferring ownership to _this_ state object, we need to inform + // each element. Note that the states they refer to are still valid however as + // we also acquire the visible objects they refer to viz. `s.visible_objects` + for (element *s : this->states_in_sequence) s->owner = this; } state_sequence::state_sequence(std::vector &&initial_objects) - : detached_state(std::move(initial_objects)) { + : visible_objects(std::move(initial_objects)) { this->push_state_snapshot(); } state_sequence::state_sequence(append_only &&ao) - : detached_state(std::move(ao)) { + : visible_objects(std::move(ao)) { this->push_state_snapshot(); } void state_sequence::push_state_snapshot() { - this->states_in_sequence.push_back(new element(*this)); + this->states_in_sequence.push_back(new element(this)); +} + +state::objid_t state_sequence::get_objid_for_runner(runner_id_t id) const { + return this->get_representative_state().get_objid_for_runner(id); +} + +bool state_sequence::contains_object_with_id(objid_t id) const { + return this->get_representative_state().contains_object_with_id(id); +} + +bool state_sequence::contains_runner_with_id(runner_id_t id) const { + return this->get_representative_state().contains_runner_with_id(id); +} + +const visible_object_state *state_sequence::get_state_of_object( + objid_t id) const { + return this->get_representative_state().get_state_of_object(id); } -state::objid_t state_sequence::add_object( - std::unique_ptr initial_state) { +const visible_object_state *state_sequence::get_state_of_runner( + runner_id_t id) const { + return this->get_representative_state().get_state_of_runner(id); +} + +state::objid_t state_sequence::add_object(const visible_object_state *s) { // INVARIANT: The current element needs to update at index `id` to reflect // this new object, as this element effectively represents this state objid_t id = visible_objects.size(); - this->get_representative_state().point_to_state_for(id, initial_state.get()); - visible_objects.push_back(std::move(initial_state)); + this->get_representative_state().point_to_state_for(id, s); + visible_objects.push_back(s); return id; } -state::runner_id_t state_sequence::add_runner( - std::unique_ptr initial_state) { - state::runner_id_t id = detached_state::add_runner(std::move(initial_state)); +state::runner_id_t state_sequence::add_runner(const visible_object_state *s) { + objid_t id = this->add_object(s); + this->runner_to_obj_map.push_back(id); this->get_representative_state().record_new_runner(); - return id; + return this->runner_to_obj_map.size() - 1; } -void state_sequence::add_state_for_obj( - objid_t id, std::unique_ptr new_state) { +void state_sequence::add_state_for_obj(objid_t id, + const visible_object_state *new_state) { if (id == invalid_objid) { - throw std::runtime_error("Attempted to insert an invalid object id"); + throw std::runtime_error( + "Attempted to insert a state for an invalid object id"); } // INVARIANT: The current element needs to update at index `id` to reflect // this new state, as this element effectively represents this state - this->get_representative_state().point_to_state_for(id, new_state.get()); - this->visible_objects.at(id).push_state(std::move(new_state)); + this->get_representative_state().point_to_state_for(id, new_state); + this->visible_objects.at(id).push_state(new_state); +} + +void state_sequence::add_state_for_runner( + runner_id_t id, const visible_object_state *new_state) { + objid_t objid = this->get_objid_for_runner(id); + if (objid == invalid_objid) { + throw std::runtime_error( + "Attempted to insert a state for a runner that does not exist in " + "this state."); + } + this->add_state_for_obj(objid, new_state); } void state_sequence::consume_into_subsequence(size_t num_states) { - if (num_states <= this->states_in_sequence.size()) + if (num_states <= this->states_in_sequence.size()) { + extensions::destroy(this->states_in_sequence.begin() + num_states, + this->states_in_sequence.end()); this->states_in_sequence.erase( this->states_in_sequence.begin() + num_states, this->states_in_sequence.end()); + } } size_t state_sequence::count() const { @@ -153,17 +206,17 @@ state_sequence::~state_sequence() { //////// state_sequence::element /////// -state_sequence::element::element(const state_sequence &owner) : owner(owner) { - for (objid_t i = 0; i < owner.visible_objects.size(); i++) { +state_sequence::element::element(const state_sequence *owner) : owner(owner) { + for (objid_t i = 0; i < owner->visible_objects.size(); i++) { this->visible_object_states[i] = - owner.visible_objects.at(i).get_current_state(); + owner->visible_objects.at(i).get_current_state(); } - this->max_visible_runner_id = owner.runner_to_obj_map.size(); + this->max_visible_runner_id = owner->runner_to_obj_map.size(); } state::objid_t state_sequence::element::get_objid_for_runner( runner_id_t id) const { - return this->contains_runner_with_id(id) ? owner.runner_to_obj_map.at(id) + return this->contains_runner_with_id(id) ? owner->runner_to_obj_map.at(id) : model::invalid_objid; } @@ -186,25 +239,20 @@ const visible_object_state *state_sequence::element::get_state_of_runner( return this->get_state_of_object(this->get_objid_for_runner(id)); } -std::unique_ptr -state_sequence::element::consume_obj(objid_t id) && { - throw std::runtime_error( - "Consumption is not permitted on elements of a state sequence"); -} - std::unique_ptr state_sequence::element::mutable_clone() const { auto state = extensions::make_unique(); for (objid_t i = 0; i < this->visible_object_states.size(); i++) - state->add_object(this->visible_object_states.at(i)->clone()); + state->add_object(this->visible_object_states.at(i)->clone().release()); return state; } std::unique_ptr state_sequence::mutable_clone() const { - auto detached_ss = extensions::make_unique(*this); - for (const auto &robjid : runner_to_obj_map) { - detached_ss->add_runner(get_state_of_object(robjid)->clone()); - } - return detached_ss; + // auto detached_ss = extensions::make_unique(*this); + // for (const auto &robjid : runner_to_obj_map) { + // detached_ss->add_runner(get_state_of_object(robjid)->clone().release()); + // } + // return detached_ss; + throw std::runtime_error("TODO: Implement state cloning for state_sequence"); } transition::status state_sequence::follow(const transition &t) { @@ -212,7 +260,7 @@ transition::status state_sequence::follow(const transition &t) { if (result.second == model::transition::status::exists) { for (auto &new_state : result.first.new_object_states) this->visible_objects[new_state.first].push_state( - new_state.second.consume_into_current_state()); + new_state.second.consume_into_current_state().release()); this->push_state_snapshot(); return transition::status::exists; } diff --git a/docs/design/src/mcmini/model/transition_sequence.cpp b/docs/design/src/mcmini/model/transition_sequence.cpp index f7626e88..648d8c4e 100644 --- a/docs/design/src/mcmini/model/transition_sequence.cpp +++ b/docs/design/src/mcmini/model/transition_sequence.cpp @@ -1,10 +1,14 @@ #include "mcmini/model/transitions/transition_sequence.hpp" +#include "mcmini/misc/extensions/memory.hpp" + using namespace model; void transition_sequence::consume_into_subsequence(uint32_t depth) { - // For depths greater than the size of the sequence + // For depths greater than the size of the sequence, this method has no + // effect. if (depth <= contents.size()) { + extensions::destroy(contents.begin() + depth, contents.end()); contents.erase(contents.begin() + depth, contents.end()); } } @@ -16,6 +20,5 @@ std::unique_ptr transition_sequence::extract_at(size_t i) { } transition_sequence::~transition_sequence() { - for (const transition* t : contents) - if (t) delete t; + extensions::destroy(contents.begin(), contents.end()); } \ No newline at end of file diff --git a/docs/design/src/mcmini/model_checking/algorithms/classic_dpor.cpp b/docs/design/src/mcmini/model_checking/algorithms/classic_dpor.cpp index a3cc9ff9..6575327a 100644 --- a/docs/design/src/mcmini/model_checking/algorithms/classic_dpor.cpp +++ b/docs/design/src/mcmini/model_checking/algorithms/classic_dpor.cpp @@ -7,6 +7,7 @@ #include #include "mcmini/defines.h" +#include "mcmini/model/exception.hpp" #include "mcmini/model/program.hpp" #include "mcmini/signal.hpp" @@ -75,16 +76,21 @@ void classic_dpor::verify_using(coordinator &coordinator, // NOTE: For deterministic results, always choose the "first" enabled // runner. A runner precedes another runner in being enabled iff it has a // smaller id. - this->continue_dpor_by_expanding_trace_with( - dpor_stack.back().get_first_enabled_runner(), context); + try { + this->continue_dpor_by_expanding_trace_with( + dpor_stack.back().get_first_enabled_runner(), context); + } catch (const model::undefined_behavior_exception &ube) { + callbacks.undefined_behavior(coordinator, ube); + return; + } } // TODO: Check for deadlock // TODO: Check for the program crashing callbacks.trace_completed(coordinator); - // if (coordinator.get_current_program_model().is_in_deadlock()) { - // callbacks.deadlock(coordinator); - // } + if (coordinator.get_current_program_model().is_in_deadlock()) { + callbacks.deadlock(coordinator); + } // 3. Backtrack phase while (!dpor_stack.empty() && dpor_stack.back().backtrack_set_empty()) @@ -100,8 +106,13 @@ void classic_dpor::verify_using(coordinator &coordinator, // The first step of the NEXT exploration phase begins with following // one of the backtrack threads. Select one thread to backtrack upon and // follow it before continuing onto the exploration phase. - this->continue_dpor_by_expanding_trace_with( - dpor_stack.back().backtrack_set_pop_first(), context); + try { + this->continue_dpor_by_expanding_trace_with( + dpor_stack.back().backtrack_set_pop_first(), context); + } catch (const model::undefined_behavior_exception &ube) { + callbacks.undefined_behavior(coordinator, ube); + return; + } } } } @@ -270,22 +281,22 @@ void classic_dpor::dynamically_update_backtrack_sets(dpor_context &context) { } } -bool classic_dpor::happens_before(const dpor_context &context, int i, - int j) const { +bool classic_dpor::happens_before(const dpor_context &context, size_t i, + size_t j) const { const runner_id_t rid = context.stack.at(i).get_out_transition()->get_executor(); const clock_vector &cv = context.stack.at(i).get_clock_vector(); return i <= cv.value_for(rid); } -bool classic_dpor::happens_before_thread(const dpor_context &context, int i, +bool classic_dpor::happens_before_thread(const dpor_context &context, size_t i, runner_id_t p) const { const runner_id_t rid = context.get_transition(i)->get_executor(); const clock_vector &cv = context.per_runner_clocks[p].get_clock_vector(); return i <= cv.value_for(rid); } -bool classic_dpor::threads_race_after(const dpor_context &context, int i, +bool classic_dpor::threads_race_after(const dpor_context &context, size_t i, runner_id_t q, runner_id_t p) const { const size_t transitionStackHeight = context.stack.size(); for (size_t j = (size_t)i + 1; j < transitionStackHeight; j++) { @@ -298,7 +309,8 @@ bool classic_dpor::threads_race_after(const dpor_context &context, int i, bool classic_dpor::dynamically_update_backtrack_sets_at_index( const dpor_context &context, const model::transition &S_i, - const model::transition &nextSP, stack_item &preSi, int i, int p) { + const model::transition &nextSP, stack_item &preSi, size_t i, + runner_id_t p) { // TODO: add in co-enabled conditions const bool has_reversible_race = this->are_dependent(nextSP, S_i) && !this->happens_before_thread(context, i, p); From 99ffc106cd7bb6be00e88b9f4efeda02479e9e17 Mon Sep 17 00:00:00 2001 From: Maxwell Pirtle Date: Thu, 23 May 2024 11:44:18 -0400 Subject: [PATCH 042/161] Remove unusued `volatile_mem_stream` --- .../mcmini/misc/volatile_mem_streambuf.hpp | 52 ------------------- .../process/fork_process_source.hpp | 1 - .../process/local_linux_process.hpp | 1 - .../src/examples/volatile_mem_stream.cpp | 38 -------------- docs/design/src/mcmini/mcmini.cpp | 1 - 5 files changed, 93 deletions(-) delete mode 100644 docs/design/include/mcmini/misc/volatile_mem_streambuf.hpp delete mode 100644 docs/design/src/examples/volatile_mem_stream.cpp diff --git a/docs/design/include/mcmini/misc/volatile_mem_streambuf.hpp b/docs/design/include/mcmini/misc/volatile_mem_streambuf.hpp deleted file mode 100644 index a377d81e..00000000 --- a/docs/design/include/mcmini/misc/volatile_mem_streambuf.hpp +++ /dev/null @@ -1,52 +0,0 @@ -// #pragma once - -// #include -// #include - -// #include "mcmini/real_world/shm.hpp" - -// struct volatile_mem_streambuf : public std::streambuf { -// private: -// char *volatile_cache; -// size_t rw_region_size; -// volatile char *rw_region; - -// public: -// volatile_mem_streambuf() = default; -// volatile_mem_streambuf( -// const real_world::shared_memory_region &read_write_region) -// : volatile_mem_streambuf(read_write_region.get(), -// read_write_region.size()) { -// reset(); -// } -// volatile_mem_streambuf(volatile void *rw_start, size_t rw_size) -// : rw_region(static_cast(rw_start)), -// rw_region_size(rw_size), -// volatile_cache(new char[rw_size]) { -// reset(); -// } - -// void reset() { -// setg(volatile_cache, volatile_cache, volatile_cache + rw_region_size); -// setp(volatile_cache, volatile_cache + rw_region_size); -// } - -// ~volatile_mem_streambuf() { delete[] volatile_cache; } - -// protected: -// int_type underflow() override { -// std::copy(rw_region, rw_region + rw_region_size, volatile_cache); -// setg(volatile_cache, volatile_cache, volatile_cache + rw_region_size); -// return this->gptr() != this->egptr() ? traits_type::to_int_type(*gptr()) -// : traits_type::eof(); -// } -// int_type overflow(int_type ch = traits_type::eof()) override { -// std::copy(volatile_cache, volatile_cache + rw_region_size, rw_region); -// setp(volatile_cache, volatile_cache + rw_region_size); -// return this->pptr() != this->epptr() ? ch : traits_type::eof(); -// } -// int sync() override { -// std::copy(volatile_cache, volatile_cache + rw_region_size, rw_region); -// return std::streambuf::sync(); -// } -// }; \ No newline at end of file diff --git a/docs/design/include/mcmini/real_world/process/fork_process_source.hpp b/docs/design/include/mcmini/real_world/process/fork_process_source.hpp index 5e2c644b..7024d11d 100644 --- a/docs/design/include/mcmini/real_world/process/fork_process_source.hpp +++ b/docs/design/include/mcmini/real_world/process/fork_process_source.hpp @@ -5,7 +5,6 @@ #include "mcmini/defines.h" #include "mcmini/forwards.hpp" -#include "mcmini/misc/volatile_mem_streambuf.hpp" #include "mcmini/real_world/process_source.hpp" #include "mcmini/real_world/shm.hpp" diff --git a/docs/design/include/mcmini/real_world/process/local_linux_process.hpp b/docs/design/include/mcmini/real_world/process/local_linux_process.hpp index 5a09b522..90ba39f2 100644 --- a/docs/design/include/mcmini/real_world/process/local_linux_process.hpp +++ b/docs/design/include/mcmini/real_world/process/local_linux_process.hpp @@ -4,7 +4,6 @@ #include #include -#include "mcmini/misc/volatile_mem_streambuf.hpp" #include "mcmini/real_world/process.hpp" #include "mcmini/real_world/shm.hpp" diff --git a/docs/design/src/examples/volatile_mem_stream.cpp b/docs/design/src/examples/volatile_mem_stream.cpp deleted file mode 100644 index c2b7c454..00000000 --- a/docs/design/src/examples/volatile_mem_stream.cpp +++ /dev/null @@ -1,38 +0,0 @@ - - -#include -#include -#include - -#include "mcmini/misc/volatile_mem_streambuf.hpp" -#include "mcmini/real_world/remote_address.hpp" -#include "mcmini/real_world/shm.hpp" - -int main() { - real_world::shared_memory_region smr{"hello", 100}; - - volatile int *smr_bytes = smr.as_array_of(); - - // std::memset((void *)smr.get(), 0x22, smr.size());q - volatile_mem_streambuf vms{smr.byte_array(20), 30}; - - uint32_t my_val = UINT32_MAX; - - auto *j = std::cout.rdbuf(&vms); - std::cout.write((char *)(&my_val), sizeof(my_val)); - std::cout.write((char *)(&my_val), sizeof(my_val)); - std::cout.flush(); - std::cout.rdbuf(j); - - j = std::cin.rdbuf(&vms); - - std::cin.read((char *)(&my_val), sizeof(my_val)); - - std::cin.rdbuf(j); - - std::cout << "Value?: " << std::hex << my_val << " whoaa" << std::endl; - - system("xxd /dev/shm/hello"); - - return 0; -} diff --git a/docs/design/src/mcmini/mcmini.cpp b/docs/design/src/mcmini/mcmini.cpp index 622c5e00..8944a44d 100644 --- a/docs/design/src/mcmini/mcmini.cpp +++ b/docs/design/src/mcmini/mcmini.cpp @@ -5,7 +5,6 @@ #include "mcmini/mem.h" #include "mcmini/misc/ddt.hpp" #include "mcmini/misc/extensions/unique_ptr.hpp" -#include "mcmini/misc/volatile_mem_streambuf.hpp" #include "mcmini/model/state/detached_state.hpp" #include "mcmini/model/transitions/mutex/mutex_init.hpp" #include "mcmini/model/transitions/mutex/mutex_lock.hpp" From 964b6c3b84a1107097e99ca49162fa4103d5cd9f Mon Sep 17 00:00:00 2001 From: Maxwell Pirtle Date: Thu, 23 May 2024 11:52:47 -0400 Subject: [PATCH 043/161] Add newline pre-commit check --- .pre-commit-config.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f3136ef2..c18781e1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,8 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 + rev: v4.6.0 hooks: - id: trailing-whitespace - - id: check-added-large-files \ No newline at end of file + - id: check-added-large-files + - id: check-case-conflict + - id: end-of-file-fixer From c4f5bf0957b2401afbf6eb4b33ee9fa8f9a7645c Mon Sep 17 00:00:00 2001 From: Maxwell Pirtle Date: Fri, 24 May 2024 10:02:45 -0400 Subject: [PATCH 044/161] Incorportate clang-tidy fixes --- .clang-tidy | 122 +++++++++++++++--- .../coordinator/model_to_system_map.hpp | 2 +- .../include/mcmini/model/transition.hpp | 4 +- .../mcmini/model/transition_registry.hpp | 3 +- .../src/examples/transition-api-demo.cpp | 12 +- .../src/mcmini/coordinator/coordinator.cpp | 29 ++++- docs/design/src/mcmini/mcmini.cpp | 48 ++++--- .../src/mcmini/model/detached_state.cpp | 14 +- docs/design/src/mcmini/model/diff_state.cpp | 23 ++-- docs/design/src/mcmini/model/program.cpp | 18 ++- .../src/mcmini/model/state_sequence.cpp | 22 ++-- .../src/mcmini/model/transition_sequence.cpp | 7 +- .../algorithms/classic_dpor.cpp | 71 +++++----- .../algorithms/clock_vector.cpp | 10 +- .../mcmini/real_world/fork_process_source.cpp | 23 +++- .../mcmini/real_world/local_linux_process.cpp | 10 +- docs/design/src/mcmini/real_world/shm.cpp | 9 +- docs/design/src/mcmini/signal.cpp | 18 ++- docs/design/src/mcmini/visible_object.cpp | 4 +- 19 files changed, 316 insertions(+), 133 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 39037ccb..8e734b30 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,20 +1,104 @@ -Checks: '-*,clang-diagnostic-*,llvm-*,misc-*,-misc-unused-parameters-misc-non-private-member-variables-in-classes,readability-identifier-naming' +--- +Checks: 'clang-diagnostic-*,clang-analyzer-*,-*,clang-diagnostic-*,llvm-*,misc-*,-misc-unused-parameters,-misc-non-private-member-variables-in-classes,readability-identifier-naming' +WarningsAsErrors: '' +HeaderFileExtensions: + - '' + - h + - hh + - hpp + - hxx +ImplementationFileExtensions: + - c + - cc + - cpp + - cxx +HeaderFilterRegex: '' +FormatStyle: none +User: maxwellpirtle CheckOptions: - - key: readability-identifier-naming.ClassCase - value: CamelCase - - key: readability-identifier-naming.EnumCase - value: CamelCase - - key: readability-identifier-naming.FunctionCase - value: camelBack - - key: readability-identifier-naming.MemberCase - value: camelBack - - key: readability-identifier-naming.ParameterCase - value: camelBack - - key: readability-identifier-naming.UnionCase - value: CamelCase - - key: readability-identifier-naming.VariableCase - value: camelBack - - key: readability-identifier-naming.NamespaceCase - value: lower_case - - key: readability-identifier-naming.InlineNamespaceCase - value: lower_case + cert-dcl16-c.NewSuffixes: 'L;LL;LU;LLU' + cert-err33-c.AllowCastToVoid: 'true' + cert-err33-c.CheckedFunctions: '::aligned_alloc;::asctime_s;::at_quick_exit;::atexit;::bsearch;::bsearch_s;::btowc;::c16rtomb;::c32rtomb;::calloc;::clock;::cnd_broadcast;::cnd_init;::cnd_signal;::cnd_timedwait;::cnd_wait;::ctime_s;::fclose;::fflush;::fgetc;::fgetpos;::fgets;::fgetwc;::fopen;::fopen_s;::fprintf;::fprintf_s;::fputc;::fputs;::fputwc;::fputws;::fread;::freopen;::freopen_s;::fscanf;::fscanf_s;::fseek;::fsetpos;::ftell;::fwprintf;::fwprintf_s;::fwrite;::fwscanf;::fwscanf_s;::getc;::getchar;::getenv;::getenv_s;::gets_s;::getwc;::getwchar;::gmtime;::gmtime_s;::localtime;::localtime_s;::malloc;::mbrtoc16;::mbrtoc32;::mbsrtowcs;::mbsrtowcs_s;::mbstowcs;::mbstowcs_s;::memchr;::mktime;::mtx_init;::mtx_lock;::mtx_timedlock;::mtx_trylock;::mtx_unlock;::printf_s;::putc;::putwc;::raise;::realloc;::remove;::rename;::scanf;::scanf_s;::setlocale;::setvbuf;::signal;::snprintf;::snprintf_s;::sprintf;::sprintf_s;::sscanf;::sscanf_s;::strchr;::strerror_s;::strftime;::strpbrk;::strrchr;::strstr;::strtod;::strtof;::strtoimax;::strtok;::strtok_s;::strtol;::strtold;::strtoll;::strtoul;::strtoull;::strtoumax;::strxfrm;::swprintf;::swprintf_s;::swscanf;::swscanf_s;::thrd_create;::thrd_detach;::thrd_join;::thrd_sleep;::time;::timespec_get;::tmpfile;::tmpfile_s;::tmpnam;::tmpnam_s;::tss_create;::tss_get;::tss_set;::ungetc;::ungetwc;::vfprintf;::vfprintf_s;::vfscanf;::vfscanf_s;::vfwprintf;::vfwprintf_s;::vfwscanf;::vfwscanf_s;::vprintf_s;::vscanf;::vscanf_s;::vsnprintf;::vsnprintf_s;::vsprintf;::vsprintf_s;::vsscanf;::vsscanf_s;::vswprintf;::vswprintf_s;::vswscanf;::vswscanf_s;::vwprintf_s;::vwscanf;::vwscanf_s;::wcrtomb;::wcschr;::wcsftime;::wcspbrk;::wcsrchr;::wcsrtombs;::wcsrtombs_s;::wcsstr;::wcstod;::wcstof;::wcstoimax;::wcstok;::wcstok_s;::wcstol;::wcstold;::wcstoll;::wcstombs;::wcstombs_s;::wcstoul;::wcstoull;::wcstoumax;::wcsxfrm;::wctob;::wctrans;::wctype;::wmemchr;::wprintf_s;::wscanf;::wscanf_s;' + cert-oop54-cpp.WarnOnlyIfThisHasSuspiciousField: 'false' + cert-str34-c.DiagnoseSignedUnsignedCharComparisons: 'false' + cppcoreguidelines-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic: 'true' + google-readability-braces-around-statements.ShortStatementLines: '1' + google-readability-function-size.StatementThreshold: '800' + google-readability-namespace-comments.ShortNamespaceLines: '10' + google-readability-namespace-comments.SpacesBeforeComments: '2' + llvm-else-after-return.WarnOnConditionVariables: 'false' + llvm-else-after-return.WarnOnUnfixable: 'false' + llvm-header-guard.HeaderFileExtensions: ';h;hh;hpp;hxx' + llvm-namespace-comment.ShortNamespaceLines: '1' + llvm-namespace-comment.SpacesBeforeComments: '1' + llvm-qualified-auto.AddConstToQualified: 'false' + misc-const-correctness.AnalyzeReferences: 'true' + misc-const-correctness.AnalyzeValues: 'true' + misc-const-correctness.TransformPointersAsValues: 'false' + misc-const-correctness.TransformReferences: 'true' + misc-const-correctness.TransformValues: 'true' + misc-const-correctness.WarnPointersAsValues: 'false' + misc-coroutine-hostile-raii.RAIITypesList: 'std::lock_guard;std::scoped_lock' + misc-coroutine-hostile-raii.SafeAwaitableList: '' + misc-definitions-in-headers.HeaderFileExtensions: ';h;hh;hpp;hxx' + misc-definitions-in-headers.UseHeaderFileExtension: 'true' + misc-header-include-cycle.IgnoredFilesList: '' + misc-include-cleaner.DeduplicateFindings: 'true' + misc-include-cleaner.IgnoreHeaders: '' + misc-throw-by-value-catch-by-reference.CheckThrowTemporaries: 'true' + misc-throw-by-value-catch-by-reference.MaxSize: '1' + misc-throw-by-value-catch-by-reference.WarnOnLargeObjects: 'false' + misc-uniqueptr-reset-release.IncludeStyle: llvm + misc-use-anonymous-namespace.HeaderFileExtensions: ';h;hh;hpp;hxx' + readability-identifier-naming.AggressiveDependentMemberLookup: 'false' + readability-identifier-naming.CheckAnonFieldInParent: 'false' + readability-identifier-naming.ClassCase: lower_case + readability-identifier-naming.ClassHungarianPrefix: Off + readability-identifier-naming.ClassIgnoredRegexp: '' + readability-identifier-naming.ClassPrefix: '' + readability-identifier-naming.ClassSuffix: '' + readability-identifier-naming.EnumCase: lower_case + readability-identifier-naming.EnumHungarianPrefix: Off + readability-identifier-naming.EnumIgnoredRegexp: '' + readability-identifier-naming.EnumPrefix: '' + readability-identifier-naming.EnumSuffix: '' + readability-identifier-naming.FunctionCase: lower_case + readability-identifier-naming.FunctionHungarianPrefix: Off + readability-identifier-naming.FunctionIgnoredRegexp: '' + readability-identifier-naming.FunctionPrefix: '' + readability-identifier-naming.FunctionSuffix: '' + readability-identifier-naming.GetConfigPerFile: 'true' + readability-identifier-naming.IgnoreFailedSplit: 'false' + readability-identifier-naming.IgnoreMainLikeFunctions: 'false' + readability-identifier-naming.InlineNamespaceCase: lower_case + readability-identifier-naming.InlineNamespaceHungarianPrefix: Off + readability-identifier-naming.InlineNamespaceIgnoredRegexp: '' + readability-identifier-naming.InlineNamespacePrefix: '' + readability-identifier-naming.InlineNamespaceSuffix: '' + readability-identifier-naming.MemberCase: lower_case + readability-identifier-naming.MemberHungarianPrefix: Off + readability-identifier-naming.MemberIgnoredRegexp: '' + readability-identifier-naming.MemberPrefix: '' + readability-identifier-naming.MemberSuffix: '' + readability-identifier-naming.NamespaceCase: lower_case + readability-identifier-naming.NamespaceHungarianPrefix: Off + readability-identifier-naming.NamespaceIgnoredRegexp: '' + readability-identifier-naming.NamespacePrefix: '' + readability-identifier-naming.NamespaceSuffix: '' + readability-identifier-naming.ParameterCase: lower_case + readability-identifier-naming.ParameterHungarianPrefix: Off + readability-identifier-naming.ParameterIgnoredRegexp: '' + readability-identifier-naming.ParameterPrefix: '' + readability-identifier-naming.ParameterSuffix: '' + readability-identifier-naming.UnionCase: lower_case + readability-identifier-naming.UnionHungarianPrefix: Off + readability-identifier-naming.UnionIgnoredRegexp: '' + readability-identifier-naming.UnionPrefix: '' + readability-identifier-naming.UnionSuffix: '' + readability-identifier-naming.VariableCase: lower_case + readability-identifier-naming.VariableHungarianPrefix: Off + readability-identifier-naming.VariableIgnoredRegexp: '' + readability-identifier-naming.VariablePrefix: '' + readability-identifier-naming.VariableSuffix: '' +SystemHeaders: false +... diff --git a/docs/design/include/mcmini/coordinator/model_to_system_map.hpp b/docs/design/include/mcmini/coordinator/model_to_system_map.hpp index e9cedd32..de655451 100644 --- a/docs/design/include/mcmini/coordinator/model_to_system_map.hpp +++ b/docs/design/include/mcmini/coordinator/model_to_system_map.hpp @@ -27,7 +27,7 @@ * ordering on object-creation events. Until we run into this issue, we leave it * for future development. */ -struct model_to_system_map final { +class model_to_system_map final { private: coordinator &_coordinator; diff --git a/docs/design/include/mcmini/model/transition.hpp b/docs/design/include/mcmini/model/transition.hpp index 54a27c14..258d6472 100644 --- a/docs/design/include/mcmini/model/transition.hpp +++ b/docs/design/include/mcmini/model/transition.hpp @@ -120,7 +120,7 @@ class transition { ? std::make_pair(s_prime, status::exists) : std::make_pair(diff_state{s}, status::disabled); } - constexpr bool is_enabled_in(const state& s) const { + bool is_enabled_in(const state& s) const { return apply_to(s).second == status::exists; } constexpr bool is_disabled_in(const state& s) const { @@ -160,4 +160,4 @@ class transition { const runner_id_t executor; }; -} // namespace model \ No newline at end of file +} // namespace model diff --git a/docs/design/include/mcmini/model/transition_registry.hpp b/docs/design/include/mcmini/model/transition_registry.hpp index 7fce549c..eeedd437 100644 --- a/docs/design/include/mcmini/model/transition_registry.hpp +++ b/docs/design/include/mcmini/model/transition_registry.hpp @@ -4,7 +4,6 @@ #include #include -#include "mcmini/coordinator/coordinator.hpp" #include "mcmini/model/transition.hpp" #include "mcmini/real_world/mailbox/runner_mailbox.h" @@ -82,4 +81,4 @@ class transition_registry final { std::unordered_map runtime_callbacks; }; -} // namespace model \ No newline at end of file +} // namespace model diff --git a/docs/design/src/examples/transition-api-demo.cpp b/docs/design/src/examples/transition-api-demo.cpp index 67f2bbdd..1d1b7d56 100644 --- a/docs/design/src/examples/transition-api-demo.cpp +++ b/docs/design/src/examples/transition-api-demo.cpp @@ -386,21 +386,21 @@ #include -class Base { +class base { public: - bool operator==(const Base &other) { return false; } + bool operator==(const base &other) { return false; } virtual void func() { std::cout << "Base::func\n"; } }; -class Derived : public Base { +class derived : public base { public: void func() override { std::cout << "Derived::func\n"; } }; int main() { - void (Base::*ptrToFunc)() = &Base::func; - Derived d; + void (base::*const ptr_to_func)() = &base::func; + derived const d; // This will call Derived::func because d is an instance of Derived. - (d.*ptrToFunc)(); + (d.*ptr_to_func)(); } diff --git a/docs/design/src/mcmini/coordinator/coordinator.cpp b/docs/design/src/mcmini/coordinator/coordinator.cpp index 491313c1..38de5943 100644 --- a/docs/design/src/mcmini/coordinator/coordinator.cpp +++ b/docs/design/src/mcmini/coordinator/coordinator.cpp @@ -1,8 +1,22 @@ #include "mcmini/coordinator/coordinator.hpp" #include +#include +#include +#include +#include +#include +#include "mcmini/coordinator/model_to_system_map.hpp" +#include "mcmini/model/program.hpp" +#include "mcmini/model/state.hpp" +#include "mcmini/model/transition.hpp" +#include "mcmini/model/transition_registry.hpp" +#include "mcmini/model/visible_object_state.hpp" +#include "mcmini/real_world/mailbox/runner_mailbox.h" #include "mcmini/real_world/process.hpp" +#include "mcmini/real_world/process_source.hpp" +#include "mcmini/real_world/remote_address.hpp" using namespace real_world; @@ -24,7 +38,7 @@ void coordinator::execute_runner(process::runner_id_t runner_id) { } volatile runner_mailbox *mb = this->current_process_handle->execute_runner(runner_id); - model::transition_registry::runtime_type_id rttid = mb->type; + model::transition_registry::runtime_type_id const rttid = mb->type; model::transition_registry::transition_discovery_callback callback_function = runtime_transition_mapping.get_callback_for(rttid); @@ -80,7 +94,7 @@ model::state::objid_t model_to_system_map::observe_object( "exists in the model. Did you check that the object doesn't already " "exist?"); } - model::state::objid_t new_objid = + model::state::objid_t const new_objid = _coordinator.current_program_model.discover_object(vobs); _coordinator.system_address_mapping.insert({rp_vobj_handle, new_objid}); return new_objid; @@ -95,11 +109,12 @@ model::state::runner_id_t model_to_system_map::observe_runner( "exists in the model. Did you check that the object doesn't already " "exist?"); } - model::state::runner_id_t new_runner_id = + model::state::runner_id_t const new_runner_id = _coordinator.current_program_model.discover_runner(vobs, std::move(f)); - model::state::objid_t new_objid = _coordinator.get_current_program_model() - .get_state_sequence() - .get_objid_for_runner(new_runner_id); + model::state::objid_t const new_objid = + _coordinator.get_current_program_model() + .get_state_sequence() + .get_objid_for_runner(new_runner_id); _coordinator.system_address_mapping.insert({rp_vobj_handle, new_objid}); return new_runner_id; -} \ No newline at end of file +} diff --git a/docs/design/src/mcmini/mcmini.cpp b/docs/design/src/mcmini/mcmini.cpp index 8944a44d..c2cebdf9 100644 --- a/docs/design/src/mcmini/mcmini.cpp +++ b/docs/design/src/mcmini/mcmini.cpp @@ -1,28 +1,36 @@ -#include "mcmini/mcmini.hpp" +#include +#include +#include #include "mcmini/coordinator/coordinator.hpp" +#include "mcmini/coordinator/model_to_system_map.hpp" +#include "mcmini/defines.h" #include "mcmini/lib/shared_transition.h" #include "mcmini/mem.h" -#include "mcmini/misc/ddt.hpp" #include "mcmini/misc/extensions/unique_ptr.hpp" +#include "mcmini/model/exception.hpp" +#include "mcmini/model/objects/mutex.hpp" +#include "mcmini/model/objects/thread.hpp" +#include "mcmini/model/pending_transitions.hpp" +#include "mcmini/model/program.hpp" +#include "mcmini/model/state.hpp" #include "mcmini/model/state/detached_state.hpp" +#include "mcmini/model/transition.hpp" +#include "mcmini/model/transition_registry.hpp" #include "mcmini/model/transitions/mutex/mutex_init.hpp" #include "mcmini/model/transitions/mutex/mutex_lock.hpp" #include "mcmini/model/transitions/mutex/mutex_unlock.hpp" #include "mcmini/model/transitions/thread/thread_create.hpp" #include "mcmini/model/transitions/thread/thread_exit.hpp" #include "mcmini/model/transitions/thread/thread_start.hpp" +#include "mcmini/model_checking/algorithm.hpp" #include "mcmini/model_checking/algorithms/classic_dpor.hpp" +#include "mcmini/real_world/mailbox/runner_mailbox.h" #include "mcmini/real_world/process/fork_process_source.hpp" -#include "mcmini/real_world/shm.hpp" +#include "mcmini/signal.hpp" #define _XOPEN_SOURCE_EXTENDED 1 -#include -#include -#include -#include - #include #include #include @@ -67,7 +75,7 @@ model::transition* mutex_init_callback(state::runner_id_t p, if (!m.contains(remote_mut)) m.observe_object(remote_mut, new mutex(mutex::state_type::uninitialized)); - state::objid_t mut = m.get_model_of(remote_mut); + state::objid_t const mut = m.get_model_of(remote_mut); return new transitions::mutex_init(p, mut); } @@ -82,7 +90,7 @@ model::transition* mutex_lock_callback(state::runner_id_t p, throw undefined_behavior_exception( "Attempting to lock an uninitialized mutex"); - state::objid_t mut = m.get_model_of(remote_mut); + state::objid_t const mut = m.get_model_of(remote_mut); return new transitions::mutex_lock(p, mut); } @@ -97,7 +105,7 @@ model::transition* mutex_unlock_callback(state::runner_id_t p, throw undefined_behavior_exception( "Attempting to lock an uninitialized mutex"); - state::objid_t mut = m.get_model_of(remote_mut); + state::objid_t const mut = m.get_model_of(remote_mut); return new transitions::mutex_unlock(p, mut); } @@ -107,7 +115,7 @@ model::transition* thread_create_callback(state::runner_id_t p, pthread_t new_thread; memcpy_v(&new_thread, static_cast(&rmb.cnts), sizeof(pthread_t)); - runner_id_t new_thread_id = m.observe_runner( + runner_id_t const new_thread_id = m.observe_runner( (void*)new_thread, new objects::thread(objects::thread::embryo), [](runner_id_t id) { return new transitions::thread_start(id); }); return new transitions::thread_create(p, new_thread_id); @@ -126,7 +134,7 @@ void do_model_checking( pending_transitions initial_first_steps; transition_registry tr; - state::runner_id_t main_thread_id = state_of_program_at_main.add_runner( + state::runner_id_t const main_thread_id = state_of_program_at_main.add_runner( new objects::thread(objects::thread::state::running)); initial_first_steps.set_transition( new transitions::thread_start(main_thread_id)); @@ -159,7 +167,7 @@ void do_model_checking( } void do_model_checking_from_dmtcp_ckpt_file(std::string file_name) { - model::detached_state state_of_program_at_main; + model::detached_state const state_of_program_at_main; model::pending_transitions initial_first_steps; // TODO: Create initializer // or else add other methods @@ -167,13 +175,13 @@ void do_model_checking_from_dmtcp_ckpt_file(std::string file_name) { // // single thread "main" that is alive and then running the transition { - // Read that information from the linked list __inside the restarted - // image__ - // while (! not all information read yet) {} - // read(...); + // Read that information from the linked list __inside the restarted + // image__ + // while (! not all information read yet) {} + // read(...); - // auto state_of_some_object_in_the_ckpt_image = new mutex(); - // state_of_program_at_main.add_state_for(); + // auto state_of_some_object_in_the_ckpt_image = new mutex(); + // state_of_program_at_main.add_state_for(); } { diff --git a/docs/design/src/mcmini/model/detached_state.cpp b/docs/design/src/mcmini/model/detached_state.cpp index 0ab54413..5de47e21 100644 --- a/docs/design/src/mcmini/model/detached_state.cpp +++ b/docs/design/src/mcmini/model/detached_state.cpp @@ -1,7 +1,15 @@ #include "mcmini/model/state/detached_state.hpp" -#include "mcmini/misc/asserts.hpp" +#include +#include +#include +#include + +#include "mcmini/misc/append-only.hpp" #include "mcmini/misc/extensions/unique_ptr.hpp" +#include "mcmini/model/state.hpp" +#include "mcmini/model/visible_object.hpp" +#include "mcmini/model/visible_object_state.hpp" using namespace model; @@ -43,7 +51,7 @@ state::objid_t detached_state::add_object( state::runner_id_t detached_state::add_runner( const visible_object_state *new_state) { - objid_t id = this->add_object(new_state); + objid_t const id = this->add_object(new_state); this->runner_to_obj_map.push_back(id); return this->runner_to_obj_map.size() - 1; } @@ -66,4 +74,4 @@ void detached_state::add_state_for_runner( std::unique_ptr detached_state::mutable_clone() const { return extensions::make_unique(*this); -} \ No newline at end of file +} diff --git a/docs/design/src/mcmini/model/diff_state.cpp b/docs/design/src/mcmini/model/diff_state.cpp index 87fdc747..bdf46b3a 100644 --- a/docs/design/src/mcmini/model/diff_state.cpp +++ b/docs/design/src/mcmini/model/diff_state.cpp @@ -1,13 +1,20 @@ #include "mcmini/model/state/diff_state.hpp" +#include +#include + +#include "mcmini/misc/extensions/unique_ptr.hpp" +#include "mcmini/model/state.hpp" +#include "mcmini/model/visible_object.hpp" +#include "mcmini/model/visible_object_state.hpp" + using namespace model; state::objid_t diff_state::get_objid_for_runner(runner_id_t id) const { if (this->new_runners.count(id) > 0) { return this->new_runners.at(id); - } else { - return this->base_state.get_objid_for_runner(id); } + return this->base_state.get_objid_for_runner(id); } bool diff_state::contains_object_with_id(objid_t id) const { @@ -23,18 +30,16 @@ bool diff_state::contains_runner_with_id(runner_id_t id) const { const visible_object_state *diff_state::get_state_of_object(objid_t id) const { if (this->new_object_states.count(id) > 0) { return this->new_object_states.at(id).get_current_state(); - } else { - return this->base_state.get_state_of_object(id); } + return this->base_state.get_state_of_object(id); } const visible_object_state *diff_state::get_state_of_runner( runner_id_t id) const { if (this->new_runners.count(id) > 0) { return this->get_state_of_object(this->new_runners.at(id)); - } else { - return this->base_state.get_state_of_runner(id); } + return this->base_state.get_state_of_runner(id); } state::objid_t diff_state::add_object( @@ -42,17 +47,17 @@ state::objid_t diff_state::add_object( // The next id that would be assigned is one more than // the largest id available. The last id of the base it `size() - 1` and // we are `new_object_state.size()` elements in - state::objid_t next_id = base_state.count() + new_object_states.size(); + state::objid_t const next_id = base_state.count() + new_object_states.size(); new_object_states[next_id] = initial_state; return next_id; } state::runner_id_t diff_state::add_runner( const visible_object_state *initial_state) { - objid_t objid = this->add_object(initial_state); + objid_t const objid = this->add_object(initial_state); // The next runner id would be the current size. - state::objid_t next_runner_id = runner_count(); + state::objid_t const next_runner_id = runner_count(); this->new_runners.insert({next_runner_id, objid}); return next_runner_id; } diff --git a/docs/design/src/mcmini/model/program.cpp b/docs/design/src/mcmini/model/program.cpp index b7dd293d..be7a393e 100644 --- a/docs/design/src/mcmini/model/program.cpp +++ b/docs/design/src/mcmini/model/program.cpp @@ -1,6 +1,16 @@ #include "mcmini/model/program.hpp" -#include +#include +#include +#include +#include +#include +#include + +#include "mcmini/model/pending_transitions.hpp" +#include "mcmini/model/state.hpp" +#include "mcmini/model/transition.hpp" +#include "mcmini/model/visible_object_state.hpp" using namespace model; @@ -65,7 +75,7 @@ void program::model_execution_of(runner_id_t p, const transition *npo) { "the "); } - transition::status status = this->state_seq.follow(*next_s_p); + transition::status const status = this->state_seq.follow(*next_s_p); if (status == transition::status::disabled) { throw std::runtime_error( "Attempted to model the execution of a disabled transition(" + @@ -81,7 +91,7 @@ state::objid_t program::discover_object( state::runner_id_t program::discover_runner( const visible_object_state *initial_state, runner_generation_function f) { - state::runner_id_t id = this->state_seq.add_runner(initial_state); + state::runner_id_t const id = this->state_seq.add_runner(initial_state); this->next_steps.set_transition(f(id)); return id; } @@ -91,4 +101,4 @@ bool program::is_in_deadlock() const { if (p.second->is_enabled_in(this->state_seq)) return false; } return true; -} \ No newline at end of file +} diff --git a/docs/design/src/mcmini/model/state_sequence.cpp b/docs/design/src/mcmini/model/state_sequence.cpp index bfd20437..04d12995 100644 --- a/docs/design/src/mcmini/model/state_sequence.cpp +++ b/docs/design/src/mcmini/model/state_sequence.cpp @@ -1,14 +1,20 @@ #include "mcmini/model/state/state_sequence.hpp" -#include -#include -#include +#include +#include +#include +#include +#include +#include #include "mcmini/misc/append-only.hpp" -#include "mcmini/misc/asserts.hpp" #include "mcmini/misc/extensions/memory.hpp" #include "mcmini/misc/extensions/unique_ptr.hpp" +#include "mcmini/model/state.hpp" #include "mcmini/model/state/detached_state.hpp" +#include "mcmini/model/transition.hpp" +#include "mcmini/model/visible_object.hpp" +#include "mcmini/model/visible_object_state.hpp" using namespace model; @@ -129,14 +135,14 @@ const visible_object_state *state_sequence::get_state_of_runner( state::objid_t state_sequence::add_object(const visible_object_state *s) { // INVARIANT: The current element needs to update at index `id` to reflect // this new object, as this element effectively represents this state - objid_t id = visible_objects.size(); + objid_t const id = visible_objects.size(); this->get_representative_state().point_to_state_for(id, s); visible_objects.push_back(s); return id; } state::runner_id_t state_sequence::add_runner(const visible_object_state *s) { - objid_t id = this->add_object(s); + objid_t const id = this->add_object(s); this->runner_to_obj_map.push_back(id); this->get_representative_state().record_new_runner(); return this->runner_to_obj_map.size() - 1; @@ -156,7 +162,7 @@ void state_sequence::add_state_for_obj(objid_t id, void state_sequence::add_state_for_runner( runner_id_t id, const visible_object_state *new_state) { - objid_t objid = this->get_objid_for_runner(id); + objid_t const objid = this->get_objid_for_runner(id); if (objid == invalid_objid) { throw std::runtime_error( "Attempted to insert a state for a runner that does not exist in " @@ -265,4 +271,4 @@ transition::status state_sequence::follow(const transition &t) { return transition::status::exists; } return transition::status::disabled; -} \ No newline at end of file +} diff --git a/docs/design/src/mcmini/model/transition_sequence.cpp b/docs/design/src/mcmini/model/transition_sequence.cpp index 648d8c4e..aab0428b 100644 --- a/docs/design/src/mcmini/model/transition_sequence.cpp +++ b/docs/design/src/mcmini/model/transition_sequence.cpp @@ -1,6 +1,11 @@ #include "mcmini/model/transitions/transition_sequence.hpp" +#include +#include +#include + #include "mcmini/misc/extensions/memory.hpp" +#include "mcmini/model/transition.hpp" using namespace model; @@ -21,4 +26,4 @@ std::unique_ptr transition_sequence::extract_at(size_t i) { transition_sequence::~transition_sequence() { extensions::destroy(contents.begin(), contents.end()); -} \ No newline at end of file +} diff --git a/docs/design/src/mcmini/model_checking/algorithms/classic_dpor.cpp b/docs/design/src/mcmini/model_checking/algorithms/classic_dpor.cpp index 6575327a..33421a1b 100644 --- a/docs/design/src/mcmini/model_checking/algorithms/classic_dpor.cpp +++ b/docs/design/src/mcmini/model_checking/algorithms/classic_dpor.cpp @@ -1,15 +1,23 @@ #include "mcmini/model_checking/algorithms/classic_dpor.hpp" +#include + +#include #include -#include -#include -#include +#include +#include +#include #include +#include +#include "mcmini/coordinator/coordinator.hpp" #include "mcmini/defines.h" #include "mcmini/model/exception.hpp" #include "mcmini/model/program.hpp" -#include "mcmini/signal.hpp" +#include "mcmini/model/transition.hpp" +#include "mcmini/model_checking/algorithms/classic_dpor/clock_vector.hpp" +#include "mcmini/model_checking/algorithms/classic_dpor/runner_item.hpp" +#include "mcmini/model_checking/algorithms/classic_dpor/stack_item.hpp" using namespace model; using namespace model_checking; @@ -232,7 +240,7 @@ void classic_dpor::dynamically_update_backtrack_sets(dpor_context &context) { thread_ids.reserve(num_threads); for (runner_id_t i = 0; i < num_threads; i++) thread_ids.insert(i); - const ssize_t tStackTop = (ssize_t)(context.stack.size()) - 1; + const ssize_t t_stack_top = (ssize_t)(context.stack.size()) - 1; const runner_id_t last_runner_to_execute = coordinator.get_current_program_model() .get_trace() @@ -242,15 +250,16 @@ void classic_dpor::dynamically_update_backtrack_sets(dpor_context &context) { // O(# threads) { - const model::transition &S_n = + const model::transition &s_n = *coordinator.get_current_program_model().get_trace().back(); for (runner_id_t rid = 0; rid < num_threads; rid++) { - const model::transition &nextSP = *coordinator.get_current_program_model() - .get_pending_transitions() - .get_transition_for_runner(rid); + const model::transition &next_sp = + *coordinator.get_current_program_model() + .get_pending_transitions() + .get_transition_for_runner(rid); dynamically_update_backtrack_sets_at_index( - context, S_n, nextSP, context.stack.back(), tStackTop, rid); + context, s_n, next_sp, context.stack.back(), t_stack_top, rid); } } @@ -265,18 +274,18 @@ void classic_dpor::dynamically_update_backtrack_sets(dpor_context &context) { // points for thread `last_runner_to_execute`. We start at one step elow the // top since we know that transition to not be co-enabled (since it was, by // assumption, run by `last_runner_to_execute`) - for (int i = tStackTop - 1; i >= 0; i--) { - const model::transition &S_i = + for (int i = t_stack_top - 1; i >= 0; i--) { + const model::transition &s_i = *coordinator.get_current_program_model().get_trace().at(i); - const bool shouldStop = dynamically_update_backtrack_sets_at_index( - context, S_i, next_s_p_for_latest_runner, context.stack.at(i), i, + const bool should_stop = dynamically_update_backtrack_sets_at_index( + context, s_i, next_s_p_for_latest_runner, context.stack.at(i), i, last_runner_to_execute); /* * Stop when we find the _first_ such i; this * will be the maxmimum `i` since we're searching * backwards */ - if (shouldStop) break; + if (should_stop) break; } } } @@ -298,8 +307,8 @@ bool classic_dpor::happens_before_thread(const dpor_context &context, size_t i, bool classic_dpor::threads_race_after(const dpor_context &context, size_t i, runner_id_t q, runner_id_t p) const { - const size_t transitionStackHeight = context.stack.size(); - for (size_t j = (size_t)i + 1; j < transitionStackHeight; j++) { + const size_t transition_stack_height = context.stack.size(); + for (size_t j = (size_t)i + 1; j < transition_stack_height; j++) { if (q == context.get_transition(j)->get_executor() && this->happens_before_thread(context, j, p)) return true; @@ -308,41 +317,41 @@ bool classic_dpor::threads_race_after(const dpor_context &context, size_t i, } bool classic_dpor::dynamically_update_backtrack_sets_at_index( - const dpor_context &context, const model::transition &S_i, - const model::transition &nextSP, stack_item &preSi, size_t i, + const dpor_context &context, const model::transition &s_i, + const model::transition &next_sp, stack_item &pre_si, size_t i, runner_id_t p) { // TODO: add in co-enabled conditions - const bool has_reversible_race = this->are_dependent(nextSP, S_i) && + const bool has_reversible_race = this->are_dependent(next_sp, s_i) && !this->happens_before_thread(context, i, p); // If there exists i such that ... if (has_reversible_race) { - std::unordered_set E; + std::unordered_set e; - for (runner_id_t q : preSi.get_enabled_runners()) { - const bool inE = q == p || this->threads_race_after(context, i, q, p); + for (runner_id_t const q : pre_si.get_enabled_runners()) { + const bool in_e = q == p || this->threads_race_after(context, i, q, p); // If E != empty set - if (inE && !preSi.sleep_set_contains(q)) E.insert(q); + if (in_e && !pre_si.sleep_set_contains(q)) e.insert(q); } - if (E.empty()) { + if (e.empty()) { // E is the empty set -> add every enabled thread at pre(S, i) - for (runner_id_t q : preSi.get_enabled_runners()) - if (!preSi.sleep_set_contains(q)) - preSi.insert_into_backtrack_set_unless_completed(q); + for (runner_id_t const q : pre_si.get_enabled_runners()) + if (!pre_si.sleep_set_contains(q)) + pre_si.insert_into_backtrack_set_unless_completed(q); } else { - for (runner_id_t q : E) { + for (runner_id_t const q : e) { // If there is a thread in preSi that we // are already backtracking AND which is contained // in the set E, chose that thread to backtrack // on. This is equivalent to not having to do // anything - if (preSi.backtrack_set_contains(q)) return true; + if (pre_si.backtrack_set_contains(q)) return true; } // Otherwise select an arbitrary thread to backtrack upon. - preSi.insert_into_backtrack_set_unless_completed(*E.begin()); + pre_si.insert_into_backtrack_set_unless_completed(*e.begin()); } } return has_reversible_race; diff --git a/docs/design/src/mcmini/model_checking/algorithms/clock_vector.cpp b/docs/design/src/mcmini/model_checking/algorithms/clock_vector.cpp index 2e2d5244..84a72eb6 100644 --- a/docs/design/src/mcmini/model_checking/algorithms/clock_vector.cpp +++ b/docs/design/src/mcmini/model_checking/algorithms/clock_vector.cpp @@ -1,15 +1,17 @@ #include "mcmini/model_checking/algorithms/classic_dpor/clock_vector.hpp" +#include + using namespace model_checking; using namespace std; clock_vector clock_vector::max(const clock_vector &cv1, const clock_vector &cv2) { - clock_vector maxCV; + clock_vector max_cv; for (const auto &e : cv2.contents) - maxCV[e.first] = std::max(cv1.value_for(e.first), e.second); + max_cv[e.first] = std::max(cv1.value_for(e.first), e.second); for (const auto &e : cv1.contents) - maxCV[e.first] = std::max(cv2.value_for(e.first), e.second); - return maxCV; + max_cv[e.first] = std::max(cv2.value_for(e.first), e.second); + return max_cv; } diff --git a/docs/design/src/mcmini/real_world/fork_process_source.cpp b/docs/design/src/mcmini/real_world/fork_process_source.cpp index 9d031283..1e424e16 100644 --- a/docs/design/src/mcmini/real_world/fork_process_source.cpp +++ b/docs/design/src/mcmini/real_world/fork_process_source.cpp @@ -2,22 +2,38 @@ #include #include +#include +#include +#include +#include #include #include #include +#include #include +#include +#include #include +#include +#include #include #include +#include #include +#include +#include +#include #include "mcmini/common/shm_config.h" #include "mcmini/defines.h" #include "mcmini/misc/extensions/unique_ptr.hpp" #include "mcmini/real_world/mailbox/runner_mailbox.h" +#include "mcmini/real_world/process.hpp" #include "mcmini/real_world/process/local_linux_process.hpp" #include "mcmini/real_world/process/template_process.h" +#include "mcmini/real_world/process_source.hpp" +#include "mcmini/real_world/shm.hpp" #include "mcmini/signal.hpp" using namespace real_world; @@ -130,13 +146,14 @@ void fork_process_source::make_new_template_process() { } errno = 0; - pid_t child_pid = fork(); + pid_t const child_pid = fork(); if (child_pid == -1) { // fork(2) failed throw process_source::process_creation_exception( "Failed to create a new process (fork(2) failed): " + std::string(strerror(errno))); - } else if (child_pid == 0) { + } + if (child_pid == 0) { // ****************** // Child process case // ****************** @@ -252,4 +269,4 @@ fork_process_source::~fork_process_source() { std::cerr << "Error waiting for process (fork) " << template_pid << ": " << strerror(errno) << std::endl; } -} \ No newline at end of file +} diff --git a/docs/design/src/mcmini/real_world/local_linux_process.cpp b/docs/design/src/mcmini/real_world/local_linux_process.cpp index 5ccb334b..20467bab 100644 --- a/docs/design/src/mcmini/real_world/local_linux_process.cpp +++ b/docs/design/src/mcmini/real_world/local_linux_process.cpp @@ -1,20 +1,18 @@ #include "mcmini/real_world/process/local_linux_process.hpp" #include -#include +#include -#include +#include #include #include -#include #include "mcmini/common/shm_config.h" -#include "mcmini/misc/extensions/unique_ptr.hpp" #include "mcmini/real_world/mailbox/runner_mailbox.h" #include "mcmini/real_world/process/fork_process_source.hpp" +#include "mcmini/real_world/shm.hpp" using namespace real_world; -using namespace extensions; local_linux_process::local_linux_process(pid_t pid, shared_memory_region &shm_slice) @@ -49,4 +47,4 @@ volatile runner_mailbox *local_linux_process::execute_runner(runner_id_t id) { mc_wake_thread(rmb); mc_wait_for_thread(rmb); return rmb; -} \ No newline at end of file +} diff --git a/docs/design/src/mcmini/real_world/shm.cpp b/docs/design/src/mcmini/real_world/shm.cpp index 5cd0664b..bd4a2c92 100644 --- a/docs/design/src/mcmini/real_world/shm.cpp +++ b/docs/design/src/mcmini/real_world/shm.cpp @@ -3,10 +3,12 @@ #include #include #include -#include #include +#include +#include #include +#include using namespace real_world; @@ -14,7 +16,8 @@ shared_memory_region::shared_memory_region(const std::string &shm_file_name, size_t region_size) : shm_file_name(shm_file_name), region_size(region_size) { // This creates a file in /dev/shm/ - int fd = shm_open(shm_file_name.c_str(), O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); + int const fd = + shm_open(shm_file_name.c_str(), O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); if (fd == -1) { if (errno == EACCES) { std::fprintf(stderr, @@ -25,7 +28,7 @@ shared_memory_region::shared_memory_region(const std::string &shm_file_name, } std::exit(EXIT_FAILURE); } - int rc = ftruncate(fd, size()); + int const rc = ftruncate(fd, size()); if (rc == -1) { std::perror("ftruncate"); std::exit(EXIT_FAILURE); diff --git a/docs/design/src/mcmini/signal.cpp b/docs/design/src/mcmini/signal.cpp index e967f761..8e577515 100644 --- a/docs/design/src/mcmini/signal.cpp +++ b/docs/design/src/mcmini/signal.cpp @@ -1,5 +1,17 @@ #include "mcmini/signal.hpp" +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + const std::unordered_map sig_to_str = { {SIGINT, "SIGINT"}, {SIGCHLD, "SIGCHLD"}, @@ -41,8 +53,8 @@ void install_process_wide_signal_handlers() { } signal_tracker &signal_tracker::instance() { - static signal_tracker _instance; - return _instance; + static signal_tracker instance; + return instance; } void signal_tracker::set_signal(int sig) { @@ -90,4 +102,4 @@ void signal_tracker::throw_if_received(int sig) { if (instance().try_consume_signal(sig)) { throw interrupted_error(sig); } -} \ No newline at end of file +} diff --git a/docs/design/src/mcmini/visible_object.cpp b/docs/design/src/mcmini/visible_object.cpp index 2a4fc8df..152eb7ed 100644 --- a/docs/design/src/mcmini/visible_object.cpp +++ b/docs/design/src/mcmini/visible_object.cpp @@ -1,8 +1,10 @@ #include "mcmini/model/visible_object.hpp" +#include "mcmini/model/visible_object_state.hpp" + using namespace model; visible_object::~visible_object() { for (const visible_object_state* s : history) if (s) delete s; -} \ No newline at end of file +} From e7099657885e4c078fc39dff564e6a61098fe8ac Mon Sep 17 00:00:00 2001 From: Maxwell Pirtle Date: Fri, 24 May 2024 10:43:19 -0400 Subject: [PATCH 045/161] Remove unsued files in include/ dir --- .../coordinator/model_to_system_map.hpp | 3 +- docs/design/include/mcmini/misc/cli.hpp | 2122 ---------------- docs/design/include/mcmini/misc/ddt.hpp | 4 +- docs/design/include/mcmini/misc/optional.hpp | 2140 ----------------- 4 files changed, 3 insertions(+), 4266 deletions(-) delete mode 100644 docs/design/include/mcmini/misc/cli.hpp delete mode 100644 docs/design/include/mcmini/misc/optional.hpp diff --git a/docs/design/include/mcmini/coordinator/model_to_system_map.hpp b/docs/design/include/mcmini/coordinator/model_to_system_map.hpp index de655451..b9f9d5c5 100644 --- a/docs/design/include/mcmini/coordinator/model_to_system_map.hpp +++ b/docs/design/include/mcmini/coordinator/model_to_system_map.hpp @@ -1,8 +1,9 @@ #pragma once +#include + #include "mcmini/coordinator/coordinator.hpp" #include "mcmini/forwards.hpp" -#include "mcmini/misc/optional.hpp" #include "mcmini/model/state.hpp" #include "mcmini/real_world/remote_address.hpp" diff --git a/docs/design/include/mcmini/misc/cli.hpp b/docs/design/include/mcmini/misc/cli.hpp deleted file mode 100644 index 9c64145c..00000000 --- a/docs/design/include/mcmini/misc/cli.hpp +++ /dev/null @@ -1,2122 +0,0 @@ -/* - -Copyright (c) 2014-2022 Jarryd Beck - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - -*/ - -// vim: ts=2:sw=2:expandtab - -#ifndef CXXOPTS_HPP_INCLUDED -#define CXXOPTS_HPP_INCLUDED - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef CXXOPTS_NO_EXCEPTIONS -#include -#endif - -#if defined(__GNUC__) && !defined(__clang__) -#if (__GNUC__ * 10 + __GNUC_MINOR__) < 49 -#define CXXOPTS_NO_REGEX true -#endif -#endif -#if defined(_MSC_VER) && !defined(__clang__) -#define CXXOPTS_LINKONCE_CONST __declspec(selectany) extern -#define CXXOPTS_LINKONCE __declspec(selectany) extern -#else -#define CXXOPTS_LINKONCE_CONST -#define CXXOPTS_LINKONCE -#endif - -#ifndef CXXOPTS_NO_REGEX -#include -#endif // CXXOPTS_NO_REGEX - -// Nonstandard before C++17, which is coincidentally what we also need for -// -#ifdef __has_include -#if __has_include() -#include -#ifdef __cpp_lib_optional -#define CXXOPTS_HAS_OPTIONAL -#endif -#endif -#endif - -#define CXXOPTS_FALLTHROUGH -#ifdef __has_cpp_attribute -#if __has_cpp_attribute(fallthrough) -#undef CXXOPTS_FALLTHROUGH -#define CXXOPTS_FALLTHROUGH [[fallthrough]] -#endif -#endif - -#if __cplusplus >= 201603L -#define CXXOPTS_NODISCARD [[nodiscard]] -#else -#define CXXOPTS_NODISCARD -#endif - -#ifndef CXXOPTS_VECTOR_DELIMITER -#define CXXOPTS_VECTOR_DELIMITER ',' -#endif - -#define CXXOPTS__VERSION_MAJOR 3 -#define CXXOPTS__VERSION_MINOR 1 -#define CXXOPTS__VERSION_PATCH 1 - -#if (__GNUC__ < 10 || (__GNUC__ == 10 && __GNUC_MINOR__ < 1)) && __GNUC__ >= 6 -#define CXXOPTS_NULL_DEREF_IGNORE -#endif - -#if defined(__GNUC__) -#define DO_PRAGMA(x) _Pragma(#x) -#define CXXOPTS_DIAGNOSTIC_PUSH DO_PRAGMA(GCC diagnostic push) -#define CXXOPTS_DIAGNOSTIC_POP DO_PRAGMA(GCC diagnostic pop) -#define CXXOPTS_IGNORE_WARNING(x) DO_PRAGMA(GCC diagnostic ignored x) -#else -// define other compilers here if needed -#define CXXOPTS_DIAGNOSTIC_PUSH -#define CXXOPTS_DIAGNOSTIC_POP -#define CXXOPTS_IGNORE_WARNING(x) -#endif - -#ifdef CXXOPTS_NO_RTTI -#define CXXOPTS_RTTI_CAST static_cast -#else -#define CXXOPTS_RTTI_CAST dynamic_cast -#endif - -namespace cxxopts { -static constexpr struct { - uint8_t major, minor, patch; -} version = {CXXOPTS__VERSION_MAJOR, CXXOPTS__VERSION_MINOR, - CXXOPTS__VERSION_PATCH}; -} // namespace cxxopts - -// when we ask cxxopts to use Unicode, help strings are processed using ICU, -// which results in the correct lengths being computed for strings when they -// are formatted for the help output -// it is necessary to make sure that can be found by the -// compiler, and that icu-uc is linked in to the binary. - -#ifdef CXXOPTS_USE_UNICODE -#include - -namespace cxxopts { - -using String = icu::UnicodeString; - -inline String toLocalString(std::string s) { - return icu::UnicodeString::fromUTF8(std::move(s)); -} - -// GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we -// want to silence it: warning: base class 'class -// std::enable_shared_from_this' has accessible non-virtual -// destructor -CXXOPTS_DIAGNOSTIC_PUSH -CXXOPTS_IGNORE_WARNING("-Wnon-virtual-dtor") -// This will be ignored under other compilers like LLVM clang. -class UnicodeStringIterator { - public: - using iterator_category = std::forward_iterator_tag; - using value_type = int32_t; - using difference_type = std::ptrdiff_t; - using pointer = value_type*; - using reference = value_type&; - - UnicodeStringIterator(const icu::UnicodeString* string, int32_t pos) - : s(string), i(pos) {} - - value_type operator*() const { return s->char32At(i); } - - bool operator==(const UnicodeStringIterator& rhs) const { - return s == rhs.s && i == rhs.i; - } - - bool operator!=(const UnicodeStringIterator& rhs) const { - return !(*this == rhs); - } - - UnicodeStringIterator& operator++() { - ++i; - return *this; - } - - UnicodeStringIterator operator+(int32_t v) { - return UnicodeStringIterator(s, i + v); - } - - private: - const icu::UnicodeString* s; - int32_t i; -}; -CXXOPTS_DIAGNOSTIC_POP - -inline String& stringAppend(String& s, String a) { - return s.append(std::move(a)); -} - -inline String& stringAppend(String& s, std::size_t n, UChar32 c) { - for (std::size_t i = 0; i != n; ++i) { - s.append(c); - } - - return s; -} - -template -String& stringAppend(String& s, Iterator begin, Iterator end) { - while (begin != end) { - s.append(*begin); - ++begin; - } - - return s; -} - -inline size_t stringLength(const String& s) { - return static_cast(s.length()); -} - -inline std::string toUTF8String(const String& s) { - std::string result; - s.toUTF8String(result); - - return result; -} - -inline bool empty(const String& s) { return s.isEmpty(); } - -} // namespace cxxopts - -namespace std { - -inline cxxopts::UnicodeStringIterator begin(const icu::UnicodeString& s) { - return cxxopts::UnicodeStringIterator(&s, 0); -} - -inline cxxopts::UnicodeStringIterator end(const icu::UnicodeString& s) { - return cxxopts::UnicodeStringIterator(&s, s.length()); -} - -} // namespace std - -// ifdef CXXOPTS_USE_UNICODE -#else - -namespace cxxopts { - -using String = std::string; - -template -T toLocalString(T&& t) { - return std::forward(t); -} - -inline std::size_t stringLength(const String& s) { return s.length(); } - -inline String& stringAppend(String& s, const String& a) { return s.append(a); } - -inline String& stringAppend(String& s, std::size_t n, char c) { - return s.append(n, c); -} - -template -String& stringAppend(String& s, Iterator begin, Iterator end) { - return s.append(begin, end); -} - -template -std::string toUTF8String(T&& t) { - return std::forward(t); -} - -inline bool empty(const std::string& s) { return s.empty(); } - -} // namespace cxxopts - -// ifdef CXXOPTS_USE_UNICODE -#endif - -namespace cxxopts { - -namespace { -CXXOPTS_LINKONCE_CONST std::string LQUOTE("\'"); -CXXOPTS_LINKONCE_CONST std::string RQUOTE("\'"); -} // namespace - -// GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we -// want to silence it: warning: base class 'class -// std::enable_shared_from_this' has accessible non-virtual -// destructor This will be ignored under other compilers like LLVM clang. -CXXOPTS_DIAGNOSTIC_PUSH -CXXOPTS_IGNORE_WARNING("-Wnon-virtual-dtor") - -// some older versions of GCC warn under this warning -CXXOPTS_IGNORE_WARNING("-Weffc++") -class Value : public std::enable_shared_from_this { - public: - virtual ~Value() = default; - - virtual std::shared_ptr clone() const = 0; - - virtual void add(const std::string& text) const = 0; - - virtual void parse(const std::string& text) const = 0; - - virtual void parse() const = 0; - - virtual bool has_default() const = 0; - - virtual bool is_container() const = 0; - - virtual bool has_implicit() const = 0; - - virtual std::string get_default_value() const = 0; - - virtual std::string get_implicit_value() const = 0; - - virtual std::shared_ptr default_value(const std::string& value) = 0; - - virtual std::shared_ptr implicit_value(const std::string& value) = 0; - - virtual std::shared_ptr no_implicit_value() = 0; - - virtual bool is_boolean() const = 0; -}; - -CXXOPTS_DIAGNOSTIC_POP - -namespace exceptions { - -class exception : public std::exception { - public: - explicit exception(std::string message) : m_message(std::move(message)) {} - - CXXOPTS_NODISCARD - const char* what() const noexcept override { return m_message.c_str(); } - - private: - std::string m_message; -}; - -class specification : public exception { - public: - explicit specification(const std::string& message) : exception(message) {} -}; - -class parsing : public exception { - public: - explicit parsing(const std::string& message) : exception(message) {} -}; - -class option_already_exists : public specification { - public: - explicit option_already_exists(const std::string& option) - : specification("Option " + LQUOTE + option + RQUOTE + - " already exists") {} -}; - -class invalid_option_format : public specification { - public: - explicit invalid_option_format(const std::string& format) - : specification("Invalid option format " + LQUOTE + format + RQUOTE) {} -}; - -class invalid_option_syntax : public parsing { - public: - explicit invalid_option_syntax(const std::string& text) - : parsing("Argument " + LQUOTE + text + RQUOTE + - " starts with a - but has incorrect syntax") {} -}; - -class no_such_option : public parsing { - public: - explicit no_such_option(const std::string& option) - : parsing("Option " + LQUOTE + option + RQUOTE + " does not exist") {} -}; - -class missing_argument : public parsing { - public: - explicit missing_argument(const std::string& option) - : parsing("Option " + LQUOTE + option + RQUOTE + - " is missing an argument") {} -}; - -class option_requires_argument : public parsing { - public: - explicit option_requires_argument(const std::string& option) - : parsing("Option " + LQUOTE + option + RQUOTE + - " requires an argument") {} -}; - -class gratuitous_argument_for_option : public parsing { - public: - gratuitous_argument_for_option(const std::string& option, - const std::string& arg) - : parsing("Option " + LQUOTE + option + RQUOTE + - " does not take an argument, but argument " + LQUOTE + arg + - RQUOTE + " given") {} -}; - -class requested_option_not_present : public parsing { - public: - explicit requested_option_not_present(const std::string& option) - : parsing("Option " + LQUOTE + option + RQUOTE + " not present") {} -}; - -class option_has_no_value : public exception { - public: - explicit option_has_no_value(const std::string& option) - : exception(!option.empty() - ? ("Option " + LQUOTE + option + RQUOTE + " has no value") - : "Option has no value") {} -}; - -class incorrect_argument_type : public parsing { - public: - explicit incorrect_argument_type(const std::string& arg) - : parsing("Argument " + LQUOTE + arg + RQUOTE + " failed to parse") {} -}; - -} // namespace exceptions - -template -void throw_or_mimic(const std::string& text) { - static_assert(std::is_base_of::value, - "throw_or_mimic only works on std::exception and " - "deriving classes"); - -#ifndef CXXOPTS_NO_EXCEPTIONS - // If CXXOPTS_NO_EXCEPTIONS is not defined, just throw - throw T{text}; -#else - // Otherwise manually instantiate the exception, print what() to stderr, - // and exit - T exception{text}; - std::cerr << exception.what() << std::endl; - std::exit(EXIT_FAILURE); -#endif -} - -using OptionNames = std::vector; - -namespace values { - -namespace parser_tool { - -struct IntegerDesc { - std::string negative = ""; - std::string base = ""; - std::string value = ""; -}; -struct ArguDesc { - std::string arg_name = ""; - bool grouping = false; - bool set_value = false; - std::string value = ""; -}; - -#ifdef CXXOPTS_NO_REGEX -inline IntegerDesc SplitInteger(const std::string& text) { - if (text.empty()) { - throw_or_mimic(text); - } - IntegerDesc desc; - const char* pdata = text.c_str(); - if (*pdata == '-') { - pdata += 1; - desc.negative = "-"; - } - if (strncmp(pdata, "0x", 2) == 0) { - pdata += 2; - desc.base = "0x"; - } - if (*pdata != '\0') { - desc.value = std::string(pdata); - } else { - throw_or_mimic(text); - } - return desc; -} - -inline bool IsTrueText(const std::string& text) { - const char* pdata = text.c_str(); - if (*pdata == 't' || *pdata == 'T') { - pdata += 1; - if (strncmp(pdata, "rue\0", 4) == 0) { - return true; - } - } else if (strncmp(pdata, "1\0", 2) == 0) { - return true; - } - return false; -} - -inline bool IsFalseText(const std::string& text) { - const char* pdata = text.c_str(); - if (*pdata == 'f' || *pdata == 'F') { - pdata += 1; - if (strncmp(pdata, "alse\0", 5) == 0) { - return true; - } - } else if (strncmp(pdata, "0\0", 2) == 0) { - return true; - } - return false; -} - -inline OptionNames split_option_names(const std::string& text) { - OptionNames split_names; - - std::string::size_type token_start_pos = 0; - auto length = text.length(); - - if (length == 0) { - throw_or_mimic(text); - } - - while (token_start_pos < length) { - const auto& npos = std::string::npos; - auto next_non_space_pos = text.find_first_not_of(' ', token_start_pos); - if (next_non_space_pos == npos) { - throw_or_mimic(text); - } - token_start_pos = next_non_space_pos; - auto next_delimiter_pos = text.find(',', token_start_pos); - if (next_delimiter_pos == token_start_pos) { - throw_or_mimic(text); - } - if (next_delimiter_pos == npos) { - next_delimiter_pos = length; - } - auto token_length = next_delimiter_pos - token_start_pos; - // validate the token itself matches the regex /([:alnum:][-_[:alnum:]]*/ - { - const char* option_name_valid_chars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789" - "_-.?"; - - if (!std::isalnum(text[token_start_pos], std::locale::classic()) || - text.find_first_not_of(option_name_valid_chars, token_start_pos) < - next_delimiter_pos) { - throw_or_mimic(text); - } - } - split_names.emplace_back(text.substr(token_start_pos, token_length)); - token_start_pos = next_delimiter_pos + 1; - } - return split_names; -} - -inline ArguDesc ParseArgument(const char* arg, bool& matched) { - ArguDesc argu_desc; - const char* pdata = arg; - matched = false; - if (strncmp(pdata, "--", 2) == 0) { - pdata += 2; - if (isalnum(*pdata, std::locale::classic())) { - argu_desc.arg_name.push_back(*pdata); - pdata += 1; - while (isalnum(*pdata, std::locale::classic()) || *pdata == '-' || - *pdata == '_') { - argu_desc.arg_name.push_back(*pdata); - pdata += 1; - } - if (argu_desc.arg_name.length() > 1) { - if (*pdata == '=') { - argu_desc.set_value = true; - pdata += 1; - if (*pdata != '\0') { - argu_desc.value = std::string(pdata); - } - matched = true; - } else if (*pdata == '\0') { - matched = true; - } - } - } - } else if (strncmp(pdata, "-", 1) == 0) { - pdata += 1; - argu_desc.grouping = true; - while (isalnum(*pdata, std::locale::classic())) { - argu_desc.arg_name.push_back(*pdata); - pdata += 1; - } - matched = !argu_desc.arg_name.empty() && *pdata == '\0'; - } - return argu_desc; -} - -#else // CXXOPTS_NO_REGEX - -namespace { -CXXOPTS_LINKONCE -const char* const integer_pattern = "(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)"; -CXXOPTS_LINKONCE -const char* const truthy_pattern = "(t|T)(rue)?|1"; -CXXOPTS_LINKONCE -const char* const falsy_pattern = "(f|F)(alse)?|0"; -CXXOPTS_LINKONCE -const char* const option_pattern = - "--([[:alnum:]][-_[:alnum:]\\.]+)(=(.*))?|-([[:alnum:]].*)"; -CXXOPTS_LINKONCE -const char* const option_specifier_pattern = - "([[:alnum:]][-_[:alnum:]\\.]*)(,[ ]*[[:alnum:]][-_[:alnum:]]*)*"; -CXXOPTS_LINKONCE -const char* const option_specifier_separator_pattern = ", *"; - -} // namespace - -inline IntegerDesc SplitInteger(const std::string& text) { - static const std::basic_regex integer_matcher(integer_pattern); - - std::smatch match; - std::regex_match(text, match, integer_matcher); - - if (match.length() == 0) { - throw_or_mimic(text); - } - - IntegerDesc desc; - desc.negative = match[1]; - desc.base = match[2]; - desc.value = match[3]; - - if (match.length(4) > 0) { - desc.base = match[5]; - desc.value = "0"; - return desc; - } - - return desc; -} - -inline bool IsTrueText(const std::string& text) { - static const std::basic_regex truthy_matcher(truthy_pattern); - std::smatch result; - std::regex_match(text, result, truthy_matcher); - return !result.empty(); -} - -inline bool IsFalseText(const std::string& text) { - static const std::basic_regex falsy_matcher(falsy_pattern); - std::smatch result; - std::regex_match(text, result, falsy_matcher); - return !result.empty(); -} - -// Gets the option names specified via a single, comma-separated string, -// and returns the separate, space-discarded, non-empty names -// (without considering which or how many are single-character) -inline OptionNames split_option_names(const std::string& text) { - static const std::basic_regex option_specifier_matcher( - option_specifier_pattern); - if (!std::regex_match(text.c_str(), option_specifier_matcher)) { - throw_or_mimic(text); - } - - OptionNames split_names; - - static const std::basic_regex option_specifier_separator_matcher( - option_specifier_separator_pattern); - constexpr int use_non_matches{-1}; - auto token_iterator = std::sregex_token_iterator( - text.begin(), text.end(), option_specifier_separator_matcher, - use_non_matches); - std::copy(token_iterator, std::sregex_token_iterator(), - std::back_inserter(split_names)); - return split_names; -} - -inline ArguDesc ParseArgument(const char* arg, bool& matched) { - static const std::basic_regex option_matcher(option_pattern); - std::match_results result; - std::regex_match(arg, result, option_matcher); - matched = !result.empty(); - - ArguDesc argu_desc; - if (matched) { - argu_desc.arg_name = result[1].str(); - argu_desc.set_value = result[2].length() > 0; - argu_desc.value = result[3].str(); - if (result[4].length() > 0) { - argu_desc.grouping = true; - argu_desc.arg_name = result[4].str(); - } - } - - return argu_desc; -} - -#endif // CXXOPTS_NO_REGEX -#undef CXXOPTS_NO_REGEX -} // namespace parser_tool - -namespace detail { - -template -struct SignedCheck; - -template -struct SignedCheck { - template - void operator()(bool negative, U u, const std::string& text) { - if (negative) { - if (u > static_cast((std::numeric_limits::min)())) { - throw_or_mimic(text); - } - } else { - if (u > static_cast((std::numeric_limits::max)())) { - throw_or_mimic(text); - } - } - } -}; - -template -struct SignedCheck { - template - void operator()(bool, U, const std::string&) const {} -}; - -template -void check_signed_range(bool negative, U value, const std::string& text) { - SignedCheck::is_signed>()(negative, value, text); -} - -} // namespace detail - -template -void checked_negate(R& r, T&& t, const std::string&, std::true_type) { - // if we got to here, then `t` is a positive number that fits into - // `R`. So to avoid MSVC C4146, we first cast it to `R`. - // See https://github.com/jarro2783/cxxopts/issues/62 for more details. - r = static_cast(-static_cast(t - 1) - 1); -} - -template -void checked_negate(R&, T&&, const std::string& text, std::false_type) { - throw_or_mimic(text); -} - -template -void integer_parser(const std::string& text, T& value) { - parser_tool::IntegerDesc int_desc = parser_tool::SplitInteger(text); - - using US = typename std::make_unsigned::type; - constexpr bool is_signed = std::numeric_limits::is_signed; - - const bool negative = int_desc.negative.length() > 0; - const uint8_t base = int_desc.base.length() > 0 ? 16 : 10; - const std::string& value_match = int_desc.value; - - US result = 0; - - for (char ch : value_match) { - US digit = 0; - - if (ch >= '0' && ch <= '9') { - digit = static_cast(ch - '0'); - } else if (base == 16 && ch >= 'a' && ch <= 'f') { - digit = static_cast(ch - 'a' + 10); - } else if (base == 16 && ch >= 'A' && ch <= 'F') { - digit = static_cast(ch - 'A' + 10); - } else { - throw_or_mimic(text); - } - - const US next = static_cast(result * base + digit); - if (result > next) { - throw_or_mimic(text); - } - - result = next; - } - - detail::check_signed_range(negative, result, text); - - if (negative) { - checked_negate(value, result, text, - std::integral_constant()); - } else { - value = static_cast(result); - } -} - -template -void stringstream_parser(const std::string& text, T& value) { - std::stringstream in(text); - in >> value; - if (!in) { - throw_or_mimic(text); - } -} - -template ::value>::type* = nullptr> -void parse_value(const std::string& text, T& value) { - integer_parser(text, value); -} - -inline void parse_value(const std::string& text, bool& value) { - if (parser_tool::IsTrueText(text)) { - value = true; - return; - } - - if (parser_tool::IsFalseText(text)) { - value = false; - return; - } - - throw_or_mimic(text); -} - -inline void parse_value(const std::string& text, std::string& value) { - value = text; -} - -// The fallback parser. It uses the stringstream parser to parse all types -// that have not been overloaded explicitly. It has to be placed in the -// source code before all other more specialized templates. -template ::value>::type* = nullptr> -void parse_value(const std::string& text, T& value) { - stringstream_parser(text, value); -} - -template -void parse_value(const std::string& text, std::vector& value) { - if (text.empty()) { - T v; - parse_value(text, v); - value.emplace_back(std::move(v)); - return; - } - std::stringstream in(text); - std::string token; - while (!in.eof() && std::getline(in, token, CXXOPTS_VECTOR_DELIMITER)) { - T v; - parse_value(token, v); - value.emplace_back(std::move(v)); - } -} - -template -void add_value(const std::string& text, T& value) { - parse_value(text, value); -} - -template -void add_value(const std::string& text, std::vector& value) { - T v; - add_value(text, v); - value.emplace_back(std::move(v)); -} - -#ifdef CXXOPTS_HAS_OPTIONAL -template -void parse_value(const std::string& text, std::optional& value) { - T result; - parse_value(text, result); - value = std::move(result); -} -#endif - -inline void parse_value(const std::string& text, char& c) { - if (text.length() != 1) { - throw_or_mimic(text); - } - - c = text[0]; -} - -template -struct type_is_container { - static constexpr bool value = false; -}; - -template -struct type_is_container> { - static constexpr bool value = true; -}; - -template -class abstract_value : public Value { - using Self = abstract_value; - - public: - abstract_value() : m_result(std::make_shared()), m_store(m_result.get()) {} - - explicit abstract_value(T* t) : m_store(t) {} - - ~abstract_value() override = default; - - abstract_value& operator=(const abstract_value&) = default; - - abstract_value(const abstract_value& rhs) { - if (rhs.m_result) { - m_result = std::make_shared(); - m_store = m_result.get(); - } else { - m_store = rhs.m_store; - } - - m_default = rhs.m_default; - m_implicit = rhs.m_implicit; - m_default_value = rhs.m_default_value; - m_implicit_value = rhs.m_implicit_value; - } - - void add(const std::string& text) const override { - add_value(text, *m_store); - } - - void parse(const std::string& text) const override { - parse_value(text, *m_store); - } - - bool is_container() const override { return type_is_container::value; } - - void parse() const override { parse_value(m_default_value, *m_store); } - - bool has_default() const override { return m_default; } - - bool has_implicit() const override { return m_implicit; } - - std::shared_ptr default_value(const std::string& value) override { - m_default = true; - m_default_value = value; - return shared_from_this(); - } - - std::shared_ptr implicit_value(const std::string& value) override { - m_implicit = true; - m_implicit_value = value; - return shared_from_this(); - } - - std::shared_ptr no_implicit_value() override { - m_implicit = false; - return shared_from_this(); - } - - std::string get_default_value() const override { return m_default_value; } - - std::string get_implicit_value() const override { return m_implicit_value; } - - bool is_boolean() const override { return std::is_same::value; } - - const T& get() const { - if (m_store == nullptr) { - return *m_result; - } - return *m_store; - } - - protected: - std::shared_ptr m_result{}; - T* m_store{}; - - bool m_default = false; - bool m_implicit = false; - - std::string m_default_value{}; - std::string m_implicit_value{}; -}; - -template -class standard_value : public abstract_value { - public: - using abstract_value::abstract_value; - - CXXOPTS_NODISCARD - std::shared_ptr clone() const override { - return std::make_shared>(*this); - } -}; - -template <> -class standard_value : public abstract_value { - public: - ~standard_value() override = default; - - standard_value() { set_default_and_implicit(); } - - explicit standard_value(bool* b) : abstract_value(b) { - m_implicit = true; - m_implicit_value = "true"; - } - - std::shared_ptr clone() const override { - return std::make_shared>(*this); - } - - private: - void set_default_and_implicit() { - m_default = true; - m_default_value = "false"; - m_implicit = true; - m_implicit_value = "true"; - } -}; - -} // namespace values - -template -std::shared_ptr value() { - return std::make_shared>(); -} - -template -std::shared_ptr value(T& t) { - return std::make_shared>(&t); -} - -class OptionAdder; - -CXXOPTS_NODISCARD -inline const std::string& first_or_empty(const OptionNames& long_names) { - static const std::string empty{""}; - return long_names.empty() ? empty : long_names.front(); -} - -class OptionDetails { - public: - OptionDetails(std::string short_, OptionNames long_, String desc, - std::shared_ptr val) - : m_short(std::move(short_)), - m_long(std::move(long_)), - m_desc(std::move(desc)), - m_value(std::move(val)), - m_count(0) { - m_hash = std::hash{}(first_long_name() + m_short); - } - - OptionDetails(const OptionDetails& rhs) - : m_desc(rhs.m_desc), - m_value(rhs.m_value->clone()), - m_count(rhs.m_count) {} - - OptionDetails(OptionDetails&& rhs) = default; - - CXXOPTS_NODISCARD - const String& description() const { return m_desc; } - - CXXOPTS_NODISCARD - const Value& value() const { return *m_value; } - - CXXOPTS_NODISCARD - std::shared_ptr make_storage() const { return m_value->clone(); } - - CXXOPTS_NODISCARD - const std::string& short_name() const { return m_short; } - - CXXOPTS_NODISCARD - const std::string& first_long_name() const { return first_or_empty(m_long); } - - CXXOPTS_NODISCARD - const std::string& essential_name() const { - return m_long.empty() ? m_short : m_long.front(); - } - - CXXOPTS_NODISCARD - const OptionNames& long_names() const { return m_long; } - - std::size_t hash() const { return m_hash; } - - private: - std::string m_short{}; - OptionNames m_long{}; - String m_desc{}; - std::shared_ptr m_value{}; - int m_count; - - std::size_t m_hash{}; -}; - -struct HelpOptionDetails { - std::string s; - OptionNames l; - String desc; - bool has_default; - std::string default_value; - bool has_implicit; - std::string implicit_value; - std::string arg_help; - bool is_container; - bool is_boolean; -}; - -struct HelpGroupDetails { - std::string name{}; - std::string description{}; - std::vector options{}; -}; - -class OptionValue { - public: - void add(const std::shared_ptr& details, - const std::string& text) { - ensure_value(details); - ++m_count; - m_value->add(text); - m_long_names = &details->long_names(); - } - - void parse(const std::shared_ptr& details, - const std::string& text) { - ensure_value(details); - ++m_count; - m_value->parse(text); - m_long_names = &details->long_names(); - } - - void parse_default(const std::shared_ptr& details) { - ensure_value(details); - m_default = true; - m_long_names = &details->long_names(); - m_value->parse(); - } - - void parse_no_value(const std::shared_ptr& details) { - m_long_names = &details->long_names(); - } - -#if defined(CXXOPTS_NULL_DEREF_IGNORE) - CXXOPTS_DIAGNOSTIC_PUSH - CXXOPTS_IGNORE_WARNING("-Wnull-dereference") -#endif - - CXXOPTS_NODISCARD - std::size_t count() const noexcept { return m_count; } - -#if defined(CXXOPTS_NULL_DEREF_IGNORE) - CXXOPTS_DIAGNOSTIC_POP -#endif - - // TODO: maybe default options should count towards the number of arguments - CXXOPTS_NODISCARD - bool has_default() const noexcept { return m_default; } - - template - const T& as() const { - if (m_value == nullptr) { - throw_or_mimic( - m_long_names == nullptr ? "" : first_or_empty(*m_long_names)); - } - - return CXXOPTS_RTTI_CAST&>(*m_value).get(); - } - - private: - void ensure_value(const std::shared_ptr& details) { - if (m_value == nullptr) { - m_value = details->make_storage(); - } - } - - const OptionNames* m_long_names = nullptr; - // Holding this pointer is safe, since OptionValue's only exist in key-value - // pairs, where the key has the string we point to. - std::shared_ptr m_value{}; - std::size_t m_count = 0; - bool m_default = false; -}; - -class KeyValue { - public: - KeyValue(std::string key_, std::string value_) noexcept - : m_key(std::move(key_)), m_value(std::move(value_)) {} - - CXXOPTS_NODISCARD - const std::string& key() const { return m_key; } - - CXXOPTS_NODISCARD - const std::string& value() const { return m_value; } - - template - T as() const { - T result; - values::parse_value(m_value, result); - return result; - } - - private: - std::string m_key; - std::string m_value; -}; - -using ParsedHashMap = std::unordered_map; -using NameHashMap = std::unordered_map; - -class ParseResult { - public: - class Iterator { - public: - using iterator_category = std::forward_iterator_tag; - using value_type = KeyValue; - using difference_type = void; - using pointer = const KeyValue*; - using reference = const KeyValue&; - - Iterator() = default; - Iterator(const Iterator&) = default; - - // GCC complains about m_iter not being initialised in the member - // initializer list - CXXOPTS_DIAGNOSTIC_PUSH - CXXOPTS_IGNORE_WARNING("-Weffc++") - Iterator(const ParseResult* pr, bool end = false) : m_pr(pr) { - if (end) { - m_sequential = false; - m_iter = m_pr->m_defaults.end(); - } else { - m_sequential = true; - m_iter = m_pr->m_sequential.begin(); - - if (m_iter == m_pr->m_sequential.end()) { - m_sequential = false; - m_iter = m_pr->m_defaults.begin(); - } - } - } - CXXOPTS_DIAGNOSTIC_POP - - Iterator& operator++() { - ++m_iter; - if (m_sequential && m_iter == m_pr->m_sequential.end()) { - m_sequential = false; - m_iter = m_pr->m_defaults.begin(); - return *this; - } - return *this; - } - - Iterator operator++(int) { - Iterator retval = *this; - ++(*this); - return retval; - } - - bool operator==(const Iterator& other) const { - return (m_sequential == other.m_sequential) && (m_iter == other.m_iter); - } - - bool operator!=(const Iterator& other) const { return !(*this == other); } - - const KeyValue& operator*() { return *m_iter; } - - const KeyValue* operator->() { return m_iter.operator->(); } - - private: - const ParseResult* m_pr; - std::vector::const_iterator m_iter; - bool m_sequential = true; - }; - - ParseResult() = default; - ParseResult(const ParseResult&) = default; - - ParseResult(NameHashMap&& keys, ParsedHashMap&& values, - std::vector sequential, - std::vector default_opts, - std::vector&& unmatched_args) - : m_keys(std::move(keys)), - m_values(std::move(values)), - m_sequential(std::move(sequential)), - m_defaults(std::move(default_opts)), - m_unmatched(std::move(unmatched_args)) {} - - ParseResult& operator=(ParseResult&&) = default; - ParseResult& operator=(const ParseResult&) = default; - - Iterator begin() const { return Iterator(this); } - - Iterator end() const { return Iterator(this, true); } - - std::size_t count(const std::string& o) const { - auto iter = m_keys.find(o); - if (iter == m_keys.end()) { - return 0; - } - - auto viter = m_values.find(iter->second); - - if (viter == m_values.end()) { - return 0; - } - - return viter->second.count(); - } - - const OptionValue& operator[](const std::string& option) const { - auto iter = m_keys.find(option); - - if (iter == m_keys.end()) { - throw_or_mimic(option); - } - - auto viter = m_values.find(iter->second); - - if (viter == m_values.end()) { - throw_or_mimic(option); - } - - return viter->second; - } - - const std::vector& arguments() const { return m_sequential; } - - const std::vector& unmatched() const { return m_unmatched; } - - const std::vector& defaults() const { return m_defaults; } - - const std::string arguments_string() const { - std::string result; - for (const auto& kv : m_sequential) { - result += kv.key() + " = " + kv.value() + "\n"; - } - for (const auto& kv : m_defaults) { - result += kv.key() + " = " + kv.value() + " " + "(default)" + "\n"; - } - return result; - } - - private: - NameHashMap m_keys{}; - ParsedHashMap m_values{}; - std::vector m_sequential{}; - std::vector m_defaults{}; - std::vector m_unmatched{}; -}; - -struct Option { - Option(std::string opts, std::string desc, - std::shared_ptr value = ::cxxopts::value(), - std::string arg_help = "") - : opts_(std::move(opts)), - desc_(std::move(desc)), - value_(std::move(value)), - arg_help_(std::move(arg_help)) {} - - std::string opts_; - std::string desc_; - std::shared_ptr value_; - std::string arg_help_; -}; - -using OptionMap = - std::unordered_map>; -using PositionalList = std::vector; -using PositionalListIterator = PositionalList::const_iterator; - -class OptionParser { - public: - OptionParser(const OptionMap& options, const PositionalList& positional, - bool allow_unrecognised) - : m_options(options), - m_positional(positional), - m_allow_unrecognised(allow_unrecognised) {} - - ParseResult parse(int argc, const char* const* argv); - - bool consume_positional(const std::string& a, PositionalListIterator& next); - - void checked_parse_arg(int argc, const char* const* argv, int& current, - const std::shared_ptr& value, - const std::string& name); - - void add_to_option(const std::shared_ptr& value, - const std::string& arg); - - void parse_option(const std::shared_ptr& value, - const std::string& name, const std::string& arg = ""); - - void parse_default(const std::shared_ptr& details); - - void parse_no_value(const std::shared_ptr& details); - - private: - void finalise_aliases(); - - const OptionMap& m_options; - const PositionalList& m_positional; - - std::vector m_sequential{}; - std::vector m_defaults{}; - bool m_allow_unrecognised; - - ParsedHashMap m_parsed{}; - NameHashMap m_keys{}; -}; - -class Options { - public: - explicit Options(std::string program_name, std::string help_string = "") - : m_program(std::move(program_name)), - m_help_string(toLocalString(std::move(help_string))), - m_custom_help("[OPTION...]"), - m_positional_help("positional parameters"), - m_show_positional(false), - m_allow_unrecognised(false), - m_width(76), - m_tab_expansion(false), - m_options(std::make_shared()) {} - - Options& positional_help(std::string help_text) { - m_positional_help = std::move(help_text); - return *this; - } - - Options& custom_help(std::string help_text) { - m_custom_help = std::move(help_text); - return *this; - } - - Options& show_positional_help() { - m_show_positional = true; - return *this; - } - - Options& allow_unrecognised_options() { - m_allow_unrecognised = true; - return *this; - } - - Options& set_width(std::size_t width) { - m_width = width; - return *this; - } - - Options& set_tab_expansion(bool expansion = true) { - m_tab_expansion = expansion; - return *this; - } - - ParseResult parse(int argc, const char* const* argv); - - OptionAdder add_options(std::string group = ""); - - void add_options(const std::string& group, - std::initializer_list