GSoC 2017
@STEllAR-GROUP/hpx
Project:
Re-implementing hpx::util::unwrapped and unifying the API of hpx::wait and hpx::when
by Denis Blank
Supervised by: Hartmut Kaiser
Overview
Abstract
In HPX we express dependencies between future results through the when_*, wait_* and dataflow functions, which are mostly used in combination with the unwrapped function that allows to work with plain values directly, rather than futures of values. My project added the ability to those functions to also accept arbitrary containers, tuple-like objects and move only types as well as the capability to deal with futures nested inside such types. For my GSoC project, I re-implemented and extended the existing solution of unwrapped into an independent implementation, which provide those functionalities for synchronous and asynchronous usage. This features will make it much easier to express dependencies between futures in HPX in the future while access its result in a convenient way.
Overview
The following report describes the progress that I’ve made during my GSoC 2017 project: “Re-implementing hpx::util::unwrapped and unifying the API of hpx::wait and hpx::when”.
The main task of this project was to provide two utility APIs for traversing and mapping objects in arbitrary variadic packs. In order to solve this task, I implemented a synchronous and an asynchronous version of the traversal API. Support for mapping across types is supported synchronously.
Both utility APIs are used to remove code duplication as well as making the APIs which unwrap or wait for futures more uniform. Additionally, the APIs introduce support for move only types as well as better support for objects which are deeply nested inside homogeneous containers (std::vector, std::list)
and tuple like containers (std::tuple, hpx::util::tuple, std::array).
Below you can see, for which APIs the traversal and mapping APIs replace the internal implementation:
  
Through this contribution, many issues in the HPX issue tracker were approached or closed, for instance: #1126 #1132, #1400, #1404 and #2456.
A detailed description of my original project description is available here.
May
Community Bonding
During the community bonding phase, I approached some light defects in order to get used to the codebase as well as getting to know the HPX community.
- Documentation improvements:
    
e890af420cAdd documentation to invoke_fused and invoke9085f6b499Add documentation to hpx::util::unwrapped and hpx::util::unwrapped2. * Also add some internal comments for improved guidance
 - CMake improvements:
    
7952bcb596Generatecmake_variables.qbkandcmake_toolchains.qbkoutside of the source tree * Prevents built system caused working tree pollution0bb983f1d8Fix cmake soft errors due to failing creation of links on windows * Prevents errors when generating the documentation on windows since cmake doesn’t provide thecreate_symlinkthere. * We use cmd under windows to create links instead
 - Doxygen improvements:
    
b993029f51Extract all documentation entities in order to autolink functions correctly * Closes #26158f71f2a4e6Hide thedetailnamespace in doxygen per default
 
June
Phase I - June
At the beginning of the first phase, I focused on fixing the requirements of my project.
Due to this I discovered the need of a spread or explode helper function which could invoke a callable object with the content of a given tuple directly, while passing non tuple like types through. A detailed description of my research can be found here.
In the remaining time of the first phase, I implemented the synchronous mapping and traversal API map_pack and traverse_pack, which was submitted as part of pull request #2704.
A minimalistic example of a typical use case of this API might be the following, where we map all values to floats:
1
2
3
4
5
hpx::util::tuple<float, std::vector<float>,
                 hpx::util::tuple<float, float>> res =
map_pack([](int i) {
    return float(i);
}, 1, std::vector<int>{2, 3, 4}, hpx::util::make_tuple(5, 6));
The API fully supports all requirements mentioned above like arbitrarily nested containers
and move only types. Thus it’s a real improvement over the previous internal code
of the unwrapped function.
Since the API isn’t fixed on mapping hpx::lcos::future objects we can test it, with more generic unit tests. Also, we are able to test the mapping API and the future unwrap functionality independently from each other.
Additionally, the capability to test for compiler features directly with header only parts of HPX was added to CMake.
This is used to provide a generic feature test, that uses the traverse_pack API, which tests whether the currently used compiler is capable of full expression SFINAE support.
- #2704 Implement the synchronous mapping and traversal API:
    
2de39aa622Add support of std::array to hpx::util::tuple_size and tuple_elementaed3ff37d6Add an is_tuple_like trait for sequenceable type detectione2fe00b6e2Remove slashes before CMAKE_FILES_DIRECTORY variables * As stated by https://cmake.org/Wiki/CMake_Useful_Variables CMAKE_FILES_DIRECTORY contains a leading slash already, and should be used as following with the current binary directory:${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}. Otherwise this will lead to paths with double slashes.bcf25f41bdFix custom include and link directories in config tests880cfb77f6Append -D to COMPILE_DEFINITIONS passed to add_hpx_config_test7586970e85Make it possible to run feature tests on header only parts of HPX * add_hpx_in_framework_config_test makes HPX and its dependencies available to the corresponding feature test.4788bdb6dbAdd a synchronous mapping API * Can be used to traverse or map elements contained in any nested container or tuple-like type. * Meant for replacing the internal API of hpx::util::unwrapped and some functions which are used to wait on futures.7108fff97dAdd a feature test for full expression sfinae support * Tests whether the compiler provides full expression SFINAE supports so all parts of HPX can be compiled. * Currently MSVC is the only compiler that doesn’t implement it.
 
July
Phase II - July
Based on the mapping API which was implemented in phase one, unwrapped was reimplemented. Pull request #2741 contributed the rewritten implementation of unwrapped as well as the whole conversion of the HPX codebase.
Due to the requirement, that the new unwrapped should pass none future arguments through, it was considered to split the deferred and the immediate unwrapped into two separated interfaces, since the implementation selection of unwrapped would be broken otherwise:
unwrap: Unwraps a variadic pack of futures directlyunwrapping: Creates a callable object that unwraps the futures
Also, multiple versions of unwrap and unwrapping are provided to unwrap
until a particular future depth of a given pack:
unwrap_nandunwrapping_n: Unwraps futures recursively until depthn.unwrap_allandunwrapping_all: Unwraps all futures recursively which occur inside the pack.
A simple usage case of unwrap looks like the one below, where we map futures to their real values:
1
2
3
4
5
6
7
// Single arguments
int i1 = hpx:util::unwrap(hpx::lcos::make_ready_future(0));
// Multiple arguments
hpx::tuple<int, int> i2 =
  hpx:util::unwrap(hpx::lcos::make_ready_future(1),
                   hpx::lcos::make_ready_future(2));
Currently, the old unwrapped function forwards its input to unwrap and unwrapping,
so we are able to test the behavior of the new implementation.
The next step was to replace all occurrences of unwrapped through
unwrap and unwrapping accordingly while officially deprecating the unwrapped API.
Overall this step took me longer than expected, mainly because the exact behavior
of unwrapped was unknown and thus it had to be derived from many unit tests and examples.
Due to the previously named reasons and because a future
- #2741 Deprecate unwrapped and implement unwrap and unwrapping:
    
6bd34c5f51Move the unwrapped tests from lcos to util5c208ae687Correctly return the exit code when the unwrapped unit test faileddcfb50363aFix tuple_cat when using l-value references * Add basic unit tests to ensure the correctness of tuple_cat.db4f99e61bFix tuple_cat when using r-value references (and move only types) * Add a unit tests for this.a0b96c0c4dFix the mapping of containers holding move only types2ef735bff6Fix the mapping of arrray like heterogeneous containers444b431f89Fix the mapping of l-value references (hpx::lcos::shared_future) * Add some unit tests to ensure the behaviour7c23554788Fix the mapping of containers when mapping to references341025a131Allow map_pack to apply 1:n mappingsac831b6375Propagate empty mappings back to the caller * Required for returning empty mappings from vector<future> 0c6265e1d8Implement the rewritten version of unwrapped (called unwrap and unwrapping) * unwrapped will be deprecated, until it’s removed unwrapped will use unwrap as underlying implementation. * The re-implementation and deprecation was required due to unresolvable issues in the old implementation, which mainly arised from the requirement to route non future values through.0a50f9c486Remove the old implementation of unwrapped * Add a proxy which emulates its behaviour using the new unwrap API * The proxy will be flagged as deprecated once the usage of unwrapped was replaced inside all unit tests and examples.93c55adab0Remove unsupported tests of unwrap * Disable deprecation warnings inside the legacy unwrapped tests1ef7985cb4Map futureto zero arguments when unwrapping c093f475acImplement unit tests for unwrap and unwrapping * Partially re-uses the previous tests of tests/unit/util/unwrapped.cpp35bd75b730Remove using namespace in the pack_traversal unit test * Correct the naming of some functionsc708feafe5Rename the internal mapper invoke to invoke_mapper * Prevents wrong function selection caused through ADL * Also namespace a call to make_tuple1ba82eea86Add constexpr and noexcept to the pack_traversal APIb6545ba263Useunwrapandunwrappingin the core librarye4465fc968Useunwrapandunwrappingin the examples347be2f5f5Useunwrapandunwrappingin the tests6a32d0cee5Replace the usage of boost::atomic in the unwrap test * Ref #27825982cf22a1Deprecate hpx::util::unwrapped * Use hpx::util::unwrap and hpx::util::unwrapping instead, that clearify which underlying implementation should be used (the immediate or the deferred one). The automatic implementation selection was broken since unwrapped allowed to pass non future arguments through. * Closes #1400 * Closes #1404 * Closes #2456 * Ref #1126 * Ref #1132
 
August
Phase III - August
For the third phase of my GSoC project, I implemented the asynchronous traversal API, which makes it possible to traverse futures inside a variadic pack while being able to suspend the current execution context.
We need to provide a special visitor object for this API:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
struct my_async_visitor
{
    /// The synchronous overload is called for each object,
    /// it may return false to suspend the current control.
    /// In that case the overload below is called.
    template <typename T>
    bool operator()(async_traverse_visit_tag, T&& element)
    {
        return true;
    }
    /// The asynchronous overload this is called when the
    /// synchronous overload returned false.
    /// In addition to the current visited element the overload is
    /// called with a contnuation callable object which resumes the
    /// traversal when it's called later.
    /// The continuation next may be stored and called later or
    /// dropped completely to abort the traversal early.
    template <typename T, typename N>
    void operator()(async_traverse_detach_tag, T&& element, N&& next)
    {
    }
    /// The overload is called when the traversal was finished.
    /// As argument the whole pack is passed over which we
    /// traversed asynchrnously.
    template <typename T>
    void operator()(async_traverse_complete_tag, T&& pack)
    {
    }
};
Then we are able to traverse an arbitrary pack of futureswhile being able to suspend the current execution context at any time to resume it later.
1
2
3
4
hpx::future<void> f1;
hpx::future<int> f2;
util::traverse_pack_async(my_async_visitor{}, f1, 2, f2);
This implementation is equal to a stackless coroutine, however we may re-implement this API with the synchronous one as soon as co_await is introduced and available in C++20.
Finally, I replaced the internal API of wait_all and dataflow with the synchronous and  asynchronous traversal API, so both interfaces are able to deal with the same set or arguments (unification).
The capabilities of both functions were extended to the one of unwrapped to deal with:
- nested types
 - move only types
 - arbitrary homogeneous and tuple-like containers
 - passing non future types through
 
As positive side effect duplicated code in wait_all and dataflow was removed.
- Fixes for the unwrapped transition:
    
320f515335Fix a parsing error with Visual Studio 2015 which occurred in unwrap * Ref #2741c442ec06e9Fix a potential unconditial moves in hpx::util::tuple_cat * Thanks to K-ballo for pointing this out * We use hpx::util::get now for retrieving a specific element at index i instead of the internal tuple_element API. * Ref #27414ea728d97aFix a unit test failure on GCC in tuple_cat * Add a test which targets the same issue on other toolchainsf9ec761db1Remove constexpr from a void function
 - Improve the continous integration testing:
    
af4fdf501aAdd Visual Studio 2015 to the tested toolchains in Appveyor * Closes #2844
 - #2829 Implement an API for asynchronous pack traversal:
 
Conclusion
Conclusion
For me personally, Google Summer of Code was a great experience because I learned a lot about Open-Source, collaboration, and development (C++ and high-performance computing in particular).
Also I really liked, that I had the chance to contribute to a major Open-Source project, while receiving professional advice.
The HPX community and maintainers (STEllAR-GROUP) were really helpful due to the whole program and provided me great support.
Special thanks go to my mentor Hartmut Kaiser who always was supportive.
I would definitely recommend others to apply for a Google Summer of Code stipend at @STEllAR-GROUP/hpx.