| /* reducer_impl.cpp -*-C++-*- |
| * |
| ************************************************************************* |
| * |
| * @copyright |
| * Copyright (C) 2009-2013, Intel Corporation |
| * All rights reserved. |
| * |
| * @copyright |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in |
| * the documentation and/or other materials provided with the |
| * distribution. |
| * * Neither the name of Intel Corporation nor the names of its |
| * contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| * |
| * @copyright |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
| * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
| * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS |
| * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
| * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY |
| * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| * POSSIBILITY OF SUCH DAMAGE. |
| * |
| * Patents Pending, Intel Corporation. |
| **************************************************************************/ |
| |
| /** |
| * Support for reducers |
| */ |
| |
| // ICL: Don't complain about conversion from pointer to same-sized integral type |
| // in hashfun. That's why we're using size_t |
| #ifdef _WIN32 |
| # pragma warning(disable: 1684) |
| #endif |
| |
| #include "reducer_impl.h" |
| #include "scheduler.h" |
| #include "bug.h" |
| #include "os.h" |
| #include "global_state.h" |
| #include "frame_malloc.h" |
| |
| #include "cilk/hyperobject_base.h" |
| #include "cilktools/cilkscreen.h" |
| #include "internal/abi.h" |
| |
| #if REDPAR_DEBUG > 0 |
| #include <stdio.h> |
| #include <stdlib.h> |
| #endif |
| |
| |
| #define DBG if(0) // if(1) enables some internal checks |
| |
| // Check that w is the currently executing worker. This method is a |
| // no-op unless the debug level is set high enough. |
| static inline void verify_current_wkr(__cilkrts_worker *w) |
| { |
| #if REDPAR_DEBUG >= 5 |
| __cilkrts_worker* tmp = __cilkrts_get_tls_worker(); |
| if (w != tmp) { |
| fprintf(stderr, "W=%d, actual=%d... missing a refresh....\n", |
| w->self, |
| tmp->self); |
| } |
| CILK_ASSERT(w == tmp); // __cilkrts_get_tls_worker()); |
| #endif |
| } |
| |
| // Suppress clang warning that the expression result is unused |
| #if defined(__clang__) && (! defined(__INTEL_COMPILER)) |
| # pragma clang diagnostic push |
| # pragma clang diagnostic ignored "-Wunused-value" |
| #endif // __clang__ |
| |
| /// Helper class to disable and re-enable Cilkscreen |
| struct DisableCilkscreen |
| { |
| DisableCilkscreen () { __cilkscreen_disable_checking(); } |
| ~DisableCilkscreen () { __cilkscreen_enable_checking(); } |
| }; |
| |
| /// Helper class to enable and re-disable Cilkscreen |
| struct EnableCilkscreen |
| { |
| EnableCilkscreen () { __cilkscreen_enable_checking(); } |
| ~EnableCilkscreen () { __cilkscreen_disable_checking(); } |
| }; |
| |
| #if defined(__clang__) && (! defined(__INTEL_COMPILER)) |
| # pragma clang diagnostic pop |
| #endif // __clang__ |
| |
| /** |
| * @brief Element for a hyperobject |
| */ |
| struct elem { |
| void *key; ///< Shared key for this hyperobject |
| __cilkrts_hyperobject_base *hb; ///< Base of the hyperobject. |
| void *view; ///< Strand-private view of this hyperobject |
| /// Destroy and deallocate the view object for this element and set view to |
| /// null. |
| void destroy(); |
| |
| /// Returns true if this element contains a leftmost view. |
| bool is_leftmost() const; |
| }; |
| |
| /** Bucket containing at most NMAX elements */ |
| struct bucket { |
| /// Size of the array of elements for this bucket |
| size_t nmax; |
| |
| /** |
| * We use the ``struct hack'' to allocate an array of variable |
| * dimension at the end of the struct. However, we allocate a |
| * total of NMAX+1 elements instead of NMAX. The last one always |
| * has key == 0, which we use as a termination criterion |
| */ |
| elem el[1]; |
| }; |
| |
| /** |
| * Class that implements the map for reducers so we can find the |
| * view for a strand. |
| */ |
| struct cilkred_map { |
| /** Handy pointer to the global state */ |
| global_state_t *g; |
| |
| /** Number of elements in table */ |
| size_t nelem; |
| |
| /** Number of buckets */ |
| size_t nbuckets; |
| |
| /** Array of pointers to buckets */ |
| bucket **buckets; |
| |
| /** Set true if merging (for debugging purposes) */ |
| bool merging; |
| |
| /** Set true for leftmost reducer map */ |
| bool is_leftmost; |
| |
| /** @brief Return element mapped to 'key' or null if not found. */ |
| elem *lookup(void *key); |
| |
| /** |
| * @brief Insert key/value element into hash map without rehashing. |
| * Does not check for duplicate key. |
| */ |
| elem *insert_no_rehash(__cilkrts_worker *w, |
| void *key, |
| __cilkrts_hyperobject_base *hb, |
| void *value); |
| |
| /** |
| * @brief Insert key/value element into hash map, rehashing if necessary. |
| * Does not check for duplicate key. |
| */ |
| inline elem *rehash_and_insert(__cilkrts_worker *w, |
| void *key, |
| __cilkrts_hyperobject_base *hb, |
| void *value); |
| |
| /** @brief Grow bucket by one element, reallocating bucket if necessary */ |
| static elem *grow(__cilkrts_worker *w, bucket **bp); |
| |
| /** @brief Rehash a worker's reducer map */ |
| void rehash(__cilkrts_worker *); |
| |
| /** |
| * @brief Returns true if a rehash is needed due to the number of elements that |
| * have been inserted. |
| */ |
| inline bool need_rehash_p() const; |
| |
| /** @brief Allocate and initialize the buckets */ |
| void make_buckets(__cilkrts_worker *w, size_t nbuckets); |
| |
| /** |
| * Specify behavior when the same key is present in both maps passed |
| * into merge(). |
| */ |
| enum merge_kind |
| { |
| MERGE_UNORDERED, ///< Assertion fails |
| MERGE_INTO_LEFT, ///< Merges the argument from the right into the left |
| MERGE_INTO_RIGHT ///< Merges the argument from the left into the right |
| }; |
| |
| /** |
| * @brief Merge another reducer map into this one, destroying the other map in |
| * the process. |
| */ |
| __cilkrts_worker* merge(__cilkrts_worker *current_wkr, |
| cilkred_map *other_map, |
| enum merge_kind kind); |
| |
| /** @brief check consistency of a reducer map */ |
| void check(bool allow_null_view); |
| |
| /** @brief Test whether the cilkred_map is empty */ |
| bool is_empty() { return nelem == 0; } |
| }; |
| |
| static inline struct cilkred_map* install_new_reducer_map(__cilkrts_worker *w) { |
| cilkred_map *h; |
| h = __cilkrts_make_reducer_map(w); |
| w->reducer_map = h; |
| return h; |
| } |
| |
| static size_t sizeof_bucket(size_t nmax) |
| { |
| bucket *b = 0; |
| return (sizeof(*b) + nmax * sizeof(b->el[0])); |
| } |
| |
| static bucket *alloc_bucket(__cilkrts_worker *w, size_t nmax) |
| { |
| bucket *b = (bucket *) |
| __cilkrts_frame_malloc(w, sizeof_bucket(nmax)); |
| b->nmax = nmax; |
| return b; |
| } |
| |
| static void free_bucket(__cilkrts_worker *w, bucket **bp) |
| { |
| bucket *b = *bp; |
| if (b) { |
| __cilkrts_frame_free(w, b, sizeof_bucket(b->nmax)); |
| *bp = 0; |
| } |
| } |
| |
| /* round up nmax to fill a memory allocator block completely */ |
| static size_t roundup(size_t nmax) |
| { |
| size_t sz = sizeof_bucket(nmax); |
| |
| /* round up size to a full malloc block */ |
| sz = __cilkrts_frame_malloc_roundup(sz); |
| |
| /* invert sizeof_bucket() */ |
| nmax = ((sz - sizeof(bucket)) / sizeof(elem)); |
| |
| return nmax; |
| } |
| |
| static bool is_power_of_2(size_t n) |
| { |
| return (n & (n - 1)) == 0; |
| } |
| |
| void cilkred_map::make_buckets(__cilkrts_worker *w, |
| size_t new_nbuckets) |
| { |
| nbuckets = new_nbuckets; |
| |
| CILK_ASSERT(is_power_of_2(nbuckets)); |
| #if defined __GNUC__ && defined __ICC |
| /* bug workaround -- suppress calls to _intel_fast_memset */ |
| bucket *volatile*new_buckets = (bucket *volatile*) |
| #else |
| bucket **new_buckets = (bucket **) |
| #endif |
| __cilkrts_frame_malloc(w, nbuckets * sizeof(*(buckets))); |
| |
| #if REDPAR_DEBUG >= 1 |
| fprintf(stderr, "W=%d, desc=make_buckets, new_buckets=%p, new_nbuckets=%zd\n", |
| w->self, new_buckets, new_nbuckets); |
| #endif |
| |
| for (size_t i = 0; i < new_nbuckets; ++i) |
| new_buckets[i] = 0; |
| #if defined __GNUC__ && defined __ICC |
| buckets = (bucket **)new_buckets; |
| #else |
| buckets = new_buckets; |
| #endif |
| nelem = 0; |
| } |
| |
| static void free_buckets(__cilkrts_worker *w, |
| bucket **buckets, |
| size_t nbuckets) |
| { |
| size_t i; |
| |
| #if REDPAR_DEBUG >= 1 |
| verify_current_wkr(w); |
| fprintf(stderr, "W=%d, desc=free_buckets, buckets=%p, size=%zd\n", |
| w->self, buckets, |
| nbuckets * sizeof(*buckets)); |
| #endif |
| |
| for (i = 0; i < nbuckets; ++i) |
| free_bucket(w, buckets + i); |
| |
| __cilkrts_frame_free(w, buckets, nbuckets * sizeof(*buckets)); |
| } |
| |
| static size_t minsz(size_t nelem) |
| { |
| return 1U + nelem + nelem / 8U; |
| } |
| |
| static size_t nextsz(size_t nelem) |
| { |
| return 2 * nelem; |
| } |
| |
| bool cilkred_map::need_rehash_p() const |
| { |
| return minsz(nelem) > nbuckets; |
| } |
| |
| static inline size_t hashfun(const cilkred_map *h, void *key) |
| { |
| size_t k = (size_t) key; |
| |
| k ^= k >> 21; |
| k ^= k >> 8; |
| k ^= k >> 3; |
| |
| return k & (h->nbuckets - 1); |
| } |
| |
| // Given a __cilkrts_hyperobject_base, return the key to that hyperobject in |
| // the reducer map. |
| static inline void* get_hyperobject_key(__cilkrts_hyperobject_base *hb) |
| { |
| // The current implementation uses the address of the lefmost view as the |
| // key. |
| return reinterpret_cast<char*>(hb) + hb->__view_offset; |
| } |
| |
| // Given a hyperobject key, return a pointer to the leftmost object. In the |
| // current implementation, the address of the leftmost object IS the key, so |
| // this function is an effective noop. |
| static inline void* get_leftmost_view(void *key) |
| { |
| return key; |
| } |
| |
| /* debugging support: check consistency of a reducer map */ |
| void cilkred_map::check(bool allow_null_view) |
| { |
| size_t count = 0; |
| |
| CILK_ASSERT(buckets); |
| for (size_t i = 0; i < nbuckets; ++i) { |
| bucket *b = buckets[i]; |
| if (b) |
| for (elem *el = b->el; el->key; ++el) { |
| CILK_ASSERT(allow_null_view || el->view); |
| ++count; |
| } |
| } |
| CILK_ASSERT(nelem == count); |
| /*global_reducer_map::check();*/ |
| } |
| |
| /* grow bucket by one element, reallocating bucket if necessary */ |
| elem *cilkred_map::grow(__cilkrts_worker *w, |
| bucket **bp) |
| { |
| size_t i, nmax, nnmax; |
| bucket *b, *nb; |
| |
| b = *bp; |
| if (b) { |
| nmax = b->nmax; |
| /* find empty element if any */ |
| for (i = 0; i < nmax; ++i) |
| if (b->el[i].key == 0) |
| return &(b->el[i]); |
| /* do not use the last one even if empty */ |
| } else { |
| nmax = 0; |
| } |
| |
| verify_current_wkr(w); |
| /* allocate a new bucket */ |
| nnmax = roundup(2 * nmax); |
| nb = alloc_bucket(w, nnmax); |
| |
| |
| /* copy old bucket into new */ |
| for (i = 0; i < nmax; ++i) |
| nb->el[i] = b->el[i]; |
| |
| free_bucket(w, bp); *bp = nb; |
| |
| /* zero out extra elements */ |
| for (; i < nnmax; ++i) |
| nb->el[i].key = 0; |
| |
| /* zero out the last one */ |
| nb->el[i].key = 0; |
| |
| return &(nb->el[nmax]); |
| } |
| |
| elem *cilkred_map::insert_no_rehash(__cilkrts_worker *w, |
| void *key, |
| __cilkrts_hyperobject_base *hb, |
| void *view) |
| { |
| |
| #if REDPAR_DEBUG >= 2 |
| fprintf(stderr, "[W=%d, desc=insert_no_rehash, this_map=%p]\n", |
| w->self, this); |
| verify_current_wkr(w); |
| #endif |
| |
| CILK_ASSERT((w == 0 && g == 0) || w->g == g); |
| CILK_ASSERT(key != 0); |
| CILK_ASSERT(view != 0); |
| |
| elem *el = grow(w, &(buckets[hashfun(this, key)])); |
| |
| #if REDPAR_DEBUG >= 3 |
| fprintf(stderr, "[W=%d, this=%p, inserting key=%p, view=%p, el = %p]\n", |
| w->self, this, key, view, el); |
| #endif |
| |
| el->key = key; |
| el->hb = hb; |
| el->view = view; |
| ++nelem; |
| |
| return el; |
| } |
| |
| void cilkred_map::rehash(__cilkrts_worker *w) |
| { |
| #if REDPAR_DEBUG >= 1 |
| fprintf(stderr, "[W=%d, desc=rehash, this_map=%p, g=%p, w->g=%p]\n", |
| w->self, this, g, w->g); |
| verify_current_wkr(w); |
| #endif |
| CILK_ASSERT((w == 0 && g == 0) || w->g == g); |
| |
| size_t onbuckets = nbuckets; |
| size_t onelem = nelem; |
| bucket **obuckets = buckets; |
| size_t i; |
| bucket *b; |
| |
| make_buckets(w, nextsz(nbuckets)); |
| |
| for (i = 0; i < onbuckets; ++i) { |
| b = obuckets[i]; |
| if (b) { |
| elem *oel; |
| for (oel = b->el; oel->key; ++oel) |
| insert_no_rehash(w, oel->key, oel->hb, oel->view); |
| } |
| } |
| |
| CILK_ASSERT(nelem == onelem); |
| |
| free_buckets(w, obuckets, onbuckets); |
| } |
| |
| elem *cilkred_map::rehash_and_insert(__cilkrts_worker *w, |
| void *key, |
| __cilkrts_hyperobject_base *hb, |
| void *view) |
| { |
| |
| #if REDPAR_DEBUG >= 1 |
| fprintf(stderr, "W=%d, this_map =%p, inserting key=%p, view=%p\n", |
| w->self, this, key, view); |
| verify_current_wkr(w); |
| #endif |
| |
| if (need_rehash_p()) |
| rehash(w); |
| |
| return insert_no_rehash(w, key, hb, view); |
| } |
| |
| |
| elem *cilkred_map::lookup(void *key) |
| { |
| bucket *b = buckets[hashfun(this, key)]; |
| |
| if (b) { |
| elem *el; |
| for (el = b->el; el->key; ++el) { |
| if (el->key == key) { |
| CILK_ASSERT(el->view); |
| return el; |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| void elem::destroy() |
| { |
| if (! is_leftmost()) { |
| |
| // Call destroy_fn and deallocate_fn on the view, but not if it's the |
| // leftmost view. |
| cilk_c_monoid *monoid = &(hb->__c_monoid); |
| cilk_c_reducer_destroy_fn_t destroy_fn = monoid->destroy_fn; |
| cilk_c_reducer_deallocate_fn_t deallocate_fn = monoid->deallocate_fn; |
| |
| destroy_fn((void*)hb, view); |
| deallocate_fn((void*)hb, view); |
| } |
| |
| view = 0; |
| } |
| |
| inline |
| bool elem::is_leftmost() const |
| { |
| // implementation uses the address of the leftmost view as the key, so if |
| // key == view, then this element refers to the leftmost view. |
| return key == view; |
| } |
| |
| /* remove the reducer from the current reducer map. If the reducer |
| exists in maps other than the current one, the behavior is |
| undefined. */ |
| extern "C" |
| CILK_EXPORT void __CILKRTS_STRAND_STALE( |
| __cilkrts_hyper_destroy(__cilkrts_hyperobject_base *hb)) |
| { |
| // Disable Cilkscreen for the duration of this call. The destructor for |
| // this class will re-enable Cilkscreen when the method returns. This |
| // will prevent Cilkscreen from reporting apparent races in reducers |
| DisableCilkscreen x; |
| |
| __cilkrts_worker* w = __cilkrts_get_tls_worker(); |
| if (! w) { |
| // If no worker, then Cilk is not running and there is no reducer |
| // map. Do nothing. The reducer's destructor will take care of |
| // destroying the leftmost view. |
| return; |
| } |
| |
| const char *UNSYNCED_REDUCER_MSG = |
| "Destroying a reducer while it is visible to unsynced child tasks, or\n" |
| "calling CILK_C_UNREGISTER_REDUCER() on an unregistered reducer.\n" |
| "Did you forget a _Cilk_sync or CILK_C_REGISTER_REDUCER()?"; |
| |
| cilkred_map* h = w->reducer_map; |
| if (NULL == h) |
| cilkos_error(UNSYNCED_REDUCER_MSG); // Does not return |
| |
| if (h->merging) { |
| verify_current_wkr(w); |
| __cilkrts_bug("User error: hyperobject used by another hyperobject"); |
| } |
| |
| void* key = get_hyperobject_key(hb); |
| elem *el = h->lookup(key); |
| |
| // Verify that the reducer is being destroyed from the leftmost strand for |
| // which the reducer is defined. |
| if (! (el && el->is_leftmost())) |
| cilkos_error(UNSYNCED_REDUCER_MSG); |
| |
| #if REDPAR_DEBUG >= 3 |
| fprintf(stderr, "[W=%d, key=%p, lookup in map %p, found el=%p, about to destroy]\n", |
| w->self, key, h, el); |
| #endif |
| |
| // Remove the element from the hash bucket. Do not bother shrinking |
| // the bucket. Note that the destroy() function does not actually |
| // call the destructor for the leftmost view. |
| el->destroy(); |
| do { |
| el[0] = el[1]; |
| ++el; |
| } while (el->key); |
| --h->nelem; |
| |
| #if REDPAR_DEBUG >= 2 |
| fprintf(stderr, "[W=%d, desc=hyper_destroy_finish, key=%p, w->reducer_map=%p]\n", |
| w->self, key, w->reducer_map); |
| #endif |
| } |
| |
| extern "C" |
| CILK_EXPORT |
| void __cilkrts_hyper_create(__cilkrts_hyperobject_base *hb) |
| { |
| // This function registers the specified hyperobject in the current |
| // reducer map and registers the initial value of the hyperobject as the |
| // leftmost view of the reducer. |
| __cilkrts_worker *w = __cilkrts_get_tls_worker(); |
| if (! w) { |
| // If there is no worker, then there is nothing to do: The iniitial |
| // value will automatically be used as the left-most view when we |
| // enter Cilk. |
| return; |
| } |
| |
| // Disable Cilkscreen for the duration of this call. The destructor for |
| // this class will re-enable Cilkscreen when the method returns. This |
| // will prevent Cilkscreen from reporting apparent races in reducers |
| DisableCilkscreen x; |
| |
| void* key = get_hyperobject_key(hb); |
| void* view = get_leftmost_view(key); |
| cilkred_map *h = w->reducer_map; |
| |
| if (__builtin_expect(!h, 0)) { |
| h = install_new_reducer_map(w); |
| #if REDPAR_DEBUG >= 2 |
| fprintf(stderr, "[W=%d, hb=%p, hyper_create, isntalled new map %p, view=%p]\n", |
| w->self, hb, h, view); |
| #endif |
| } |
| |
| /* Must not exist. */ |
| CILK_ASSERT(h->lookup(key) == NULL); |
| |
| #if REDPAR_DEBUG >= 3 |
| verify_current_wkr(w); |
| fprintf(stderr, "[W=%d, hb=%p, lookup in map %p of view %p, should be null]\n", |
| w->self, hb, h, view); |
| fprintf(stderr, "W=%d, h=%p, inserting key %p, view%p\n", |
| w->self, |
| h, |
| &(hb->__c_monoid), |
| view); |
| #endif |
| |
| if (h->merging) |
| __cilkrts_bug("User error: hyperobject used by another hyperobject"); |
| |
| CILK_ASSERT(w->reducer_map == h); |
| // The address of the leftmost value is the same as the key for lookup. |
| (void) h->rehash_and_insert(w, view, hb, view); |
| } |
| |
| extern "C" |
| CILK_EXPORT void* __CILKRTS_STRAND_PURE( |
| __cilkrts_hyper_lookup(__cilkrts_hyperobject_base *hb)) |
| { |
| __cilkrts_worker* w = __cilkrts_get_tls_worker_fast(); |
| void* key = get_hyperobject_key(hb); |
| if (! w) |
| return get_leftmost_view(key); |
| |
| // Disable Cilkscreen for the duration of this call. This will |
| // prevent Cilkscreen from reporting apparent races in reducers |
| DisableCilkscreen dguard; |
| |
| if (__builtin_expect(w->g->force_reduce, 0)) |
| __cilkrts_promote_own_deque(w); |
| cilkred_map* h = w->reducer_map; |
| |
| if (__builtin_expect(!h, 0)) { |
| h = install_new_reducer_map(w); |
| } |
| |
| if (h->merging) |
| __cilkrts_bug("User error: hyperobject used by another hyperobject"); |
| elem* el = h->lookup(key); |
| if (! el) { |
| /* lookup failed; insert a new default element */ |
| void *rep; |
| |
| { |
| /* re-enable cilkscreen while calling the constructor */ |
| EnableCilkscreen eguard; |
| if (h->is_leftmost) |
| { |
| // This special case is called only if the reducer was not |
| // registered using __cilkrts_hyper_create, e.g., if this is a |
| // C reducer in global scope or if there is no bound worker. |
| rep = get_leftmost_view(key); |
| } |
| else |
| { |
| rep = hb->__c_monoid.allocate_fn((void*)hb, |
| hb->__view_size); |
| // TBD: Handle exception on identity function |
| hb->__c_monoid.identity_fn((void*)hb, rep); |
| } |
| } |
| |
| #if REDPAR_DEBUG >= 3 |
| fprintf(stderr, "W=%d, h=%p, inserting key %p, view%p\n", |
| w->self, |
| h, |
| &(hb->__c_monoid), |
| rep); |
| CILK_ASSERT(w->reducer_map == h); |
| #endif |
| el = h->rehash_and_insert(w, key, hb, rep); |
| } |
| |
| return el->view; |
| } |
| |
| extern "C" CILK_EXPORT |
| void* __cilkrts_hyperobject_alloc(void* ignore, std::size_t bytes) |
| { |
| return std::malloc(bytes); |
| } |
| |
| extern "C" CILK_EXPORT |
| void __cilkrts_hyperobject_dealloc(void* ignore, void* view) |
| { |
| std::free(view); |
| } |
| |
| /* No-op destroy function */ |
| extern "C" CILK_EXPORT |
| void __cilkrts_hyperobject_noop_destroy(void* ignore, void* ignore2) |
| { |
| } |
| |
| cilkred_map *__cilkrts_make_reducer_map(__cilkrts_worker *w) |
| { |
| CILK_ASSERT(w); |
| |
| cilkred_map *h; |
| size_t nbuckets = 1; /* default value */ |
| |
| h = (cilkred_map *)__cilkrts_frame_malloc(w, sizeof(*h)); |
| #if REDPAR_DEBUG >= 1 |
| fprintf(stderr, "[W=%d, desc=make_reducer_frame_malloc_reducer_map, h=%p]\n", |
| w->self, h); |
| #endif |
| |
| h->g = w ? w->g : 0; |
| h->make_buckets(w, nbuckets); |
| h->merging = false; |
| h->is_leftmost = false; |
| |
| return h; |
| } |
| |
| /* Destroy a reducer map. The map must have been allocated |
| from the worker's global context and should have been |
| allocated from the same worker. */ |
| void __cilkrts_destroy_reducer_map(__cilkrts_worker *w, cilkred_map *h) |
| { |
| CILK_ASSERT((w == 0 && h->g == 0) || w->g == h->g); |
| verify_current_wkr(w); |
| |
| /* the reducer map is allowed to contain el->view == NULL here (and |
| only here). We set el->view == NULL only when we know that the |
| map will be destroyed immediately afterwards. */ |
| DBG h->check(/*allow_null_view=*/true); |
| |
| bucket *b; |
| size_t i; |
| |
| for (i = 0; i < h->nbuckets; ++i) { |
| b = h->buckets[i]; |
| if (b) { |
| elem *el; |
| for (el = b->el; el->key; ++el) { |
| if (el->view) |
| el->destroy(); |
| } |
| } |
| } |
| |
| free_buckets(w, h->buckets, h->nbuckets); |
| |
| #if REDPAR_DEBUG >= 1 |
| fprintf(stderr, "W=%d, destroy_red_map, freeing map h=%p, size=%zd\n", |
| w->self, h, sizeof(*h)); |
| #endif |
| |
| __cilkrts_frame_free(w, h, sizeof(*h)); |
| } |
| |
| /* Set the specified reducer map as the leftmost map if is_leftmost is true, |
| otherwise, set it to not be the leftmost map. */ |
| void __cilkrts_set_leftmost_reducer_map(cilkred_map *h, int is_leftmost) |
| { |
| h->is_leftmost = is_leftmost; |
| } |
| |
| |
| __cilkrts_worker* cilkred_map::merge(__cilkrts_worker *w, |
| cilkred_map *other_map, |
| enum merge_kind kind) |
| { |
| // Disable Cilkscreen while the we merge the maps. The destructor for |
| // the guard class will re-enable Cilkscreen when it goes out of scope. |
| // This will prevent Cilkscreen from reporting apparent races in between |
| // the reduce function and the reducer operations. The Cilk runtime |
| // guarantees that a pair of reducer maps will only be merged when no |
| // other strand will access them. |
| DisableCilkscreen guard; |
| |
| #if REDPAR_DEBUG >= 2 |
| fprintf(stderr, "[W=%d, desc=merge, this_map=%p, other_map=%p]\n", |
| w->self, |
| this, other_map); |
| #endif |
| // Remember the current stack frame. |
| __cilkrts_stack_frame *current_sf = w->current_stack_frame; |
| merging = true; |
| other_map->merging = true; |
| |
| // Merging to the leftmost view is a special case because every leftmost |
| // element must be initialized before the merge. |
| CILK_ASSERT(!other_map->is_leftmost /* || kind == MERGE_UNORDERED */); |
| bool merge_to_leftmost = (this->is_leftmost |
| /* && !other_map->is_leftmost */); |
| |
| DBG check(/*allow_null_view=*/false); |
| DBG other_map->check(/*allow_null_view=*/false); |
| |
| for (size_t i = 0; i < other_map->nbuckets; ++i) { |
| bucket *b = other_map->buckets[i]; |
| if (b) { |
| for (elem *other_el = b->el; other_el->key; ++other_el) { |
| /* Steal the value from the other map, which will be |
| destroyed at the end of this operation. */ |
| void *other_view = other_el->view; |
| CILK_ASSERT(other_view); |
| |
| void *key = other_el->key; |
| __cilkrts_hyperobject_base *hb = other_el->hb; |
| elem *this_el = lookup(key); |
| |
| if (this_el == 0 && merge_to_leftmost) { |
| /* Initialize leftmost view before merging. */ |
| void* leftmost = get_leftmost_view(key); |
| // leftmost == other_view can be true if the initial view |
| // was created in other than the leftmost strand of the |
| // spawn tree, but then made visible to subsequent strands |
| // (E.g., the reducer was allocated on the heap and the |
| // pointer was returned to the caller.) In such cases, |
| // parallel semantics says that syncing with earlier |
| // strands will always result in 'this_el' being null, |
| // thus propagating the initial view up the spawn tree |
| // until it reaches the leftmost strand. When synching |
| // with the leftmost strand, leftmost == other_view will be |
| // true and we must avoid reducing the initial view with |
| // itself. |
| if (leftmost != other_view) |
| this_el = rehash_and_insert(w, key, hb, leftmost); |
| } |
| |
| if (this_el == 0) { |
| /* move object from other map into this one */ |
| rehash_and_insert(w, key, hb, other_view); |
| other_el->view = 0; |
| continue; /* No element-level merge necessary */ |
| } |
| |
| /* The same key is present in both maps with values |
| A and B. Three choices: fail, A OP B, B OP A. */ |
| switch (kind) |
| { |
| case MERGE_UNORDERED: |
| __cilkrts_bug("TLS Reducer race"); |
| break; |
| case MERGE_INTO_RIGHT: |
| /* Swap elements in order to preserve object |
| identity */ |
| other_el->view = this_el->view; |
| this_el->view = other_view; |
| /* FALL THROUGH */ |
| case MERGE_INTO_LEFT: { |
| /* Stealing should be disabled during reduce |
| (even if force-reduce is enabled). */ |
| |
| #if DISABLE_PARALLEL_REDUCERS |
| __cilkrts_stack_frame * volatile *saved_protected_tail; |
| saved_protected_tail = __cilkrts_disallow_stealing(w, NULL); |
| #endif |
| |
| { |
| CILK_ASSERT(current_sf->worker == w); |
| CILK_ASSERT(w->current_stack_frame == current_sf); |
| |
| /* TBD: if reduce throws an exception we need to stop it |
| here. */ |
| hb->__c_monoid.reduce_fn((void*)hb, |
| this_el->view, |
| other_el->view); |
| w = current_sf->worker; |
| |
| #if REDPAR_DEBUG >= 2 |
| verify_current_wkr(w); |
| CILK_ASSERT(w->current_stack_frame == current_sf); |
| #endif |
| } |
| |
| #if DISABLE_PARALLEL_REDUCERS |
| /* Restore stealing */ |
| __cilkrts_restore_stealing(w, saved_protected_tail); |
| #endif |
| |
| } break; |
| } |
| } |
| } |
| } |
| this->is_leftmost = this->is_leftmost || other_map->is_leftmost; |
| merging = false; |
| other_map->merging = false; |
| verify_current_wkr(w); |
| __cilkrts_destroy_reducer_map(w, other_map); |
| return w; |
| } |
| |
| |
| /** |
| * Print routine for debugging the merging of reducer maps. |
| * A no-op unless REDPAR_DEBUG set high enough. |
| */ |
| static inline |
| void debug_map_merge(__cilkrts_worker *w, |
| cilkred_map *left_map, |
| cilkred_map *right_map, |
| __cilkrts_worker **final_wkr) |
| { |
| #if REDPAR_DEBUG >= 2 |
| fprintf(stderr, "[W=%d, desc=finish_merge, left_map=%p, right_map=%p, w->reducer_map=%p, right_ans=%p, final_wkr=%d]\n", |
| w->self, left_map, right_map, w->reducer_map, right_map, (*final_wkr)->self); |
| #endif |
| } |
| |
| |
| /** |
| * merge RIGHT into LEFT; |
| * return whichever map allows for faster merge, and destroy the other one. |
| * |
| * *w_ptr should be the currently executing worker. |
| * *w_ptr may change during execution if the reduction is parallel. |
| */ |
| cilkred_map* |
| merge_reducer_maps(__cilkrts_worker **w_ptr, |
| cilkred_map *left_map, |
| cilkred_map *right_map) |
| { |
| __cilkrts_worker *w = *w_ptr; |
| if (!left_map) { |
| debug_map_merge(w, left_map, right_map, w_ptr); |
| return right_map; |
| } |
| |
| if (!right_map) { |
| debug_map_merge(w, left_map, right_map, w_ptr); |
| return left_map; |
| } |
| |
| /* Special case, if left_map is leftmost, then always merge into it. |
| For C reducers this forces lazy creation of the leftmost views. */ |
| if (left_map->is_leftmost || left_map->nelem > right_map->nelem) { |
| *w_ptr = left_map->merge(w, right_map, cilkred_map::MERGE_INTO_LEFT); |
| debug_map_merge(*w_ptr, left_map, right_map, w_ptr); |
| return left_map; |
| } else { |
| *w_ptr = right_map->merge(w, left_map, cilkred_map::MERGE_INTO_RIGHT); |
| debug_map_merge(*w_ptr, left_map, right_map, w_ptr); |
| return right_map; |
| } |
| } |
| |
| /** |
| * Merges RIGHT into LEFT, and then repeatedly calls |
| * merge_reducer_maps_helper() until (*w_ptr)->reducer_map is NULL. |
| * |
| * *w_ptr may change as reductions execute. |
| */ |
| cilkred_map* |
| repeated_merge_reducer_maps(__cilkrts_worker **w_ptr, |
| cilkred_map *left_map, |
| cilkred_map *right_map) |
| { |
| // Note: if right_map == NULL but w->reducer_map != NULL, then |
| // this loop will reduce w->reducer_map into left_map. |
| do { |
| left_map = merge_reducer_maps(w_ptr, left_map, right_map); |
| verify_current_wkr(*w_ptr); |
| |
| // Pull any newly created reducer map and loop around again. |
| right_map = (*w_ptr)->reducer_map; |
| (*w_ptr)->reducer_map = NULL; |
| } while (right_map); |
| return left_map; |
| } |
| |
| /* End reducer_impl.cpp */ |