Home | History | Annotate | Line # | Download | only in std
      1  1.1  mrg // <barrier> -*- C++ -*-
      2  1.1  mrg 
      3  1.1  mrg // Copyright (C) 2020-2022 Free Software Foundation, Inc.
      4  1.1  mrg //
      5  1.1  mrg // This file is part of the GNU ISO C++ Library.  This library is free
      6  1.1  mrg // software; you can redistribute it and/or modify it under the
      7  1.1  mrg // terms of the GNU General Public License as published by the
      8  1.1  mrg // Free Software Foundation; either version 3, or (at your option)
      9  1.1  mrg // any later version.
     10  1.1  mrg 
     11  1.1  mrg // This library is distributed in the hope that it will be useful,
     12  1.1  mrg // but WITHOUT ANY WARRANTY; without even the implied warranty of
     13  1.1  mrg // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     14  1.1  mrg // GNU General Public License for more details.
     15  1.1  mrg 
     16  1.1  mrg // Under Section 7 of GPL version 3, you are granted additional
     17  1.1  mrg // permissions described in the GCC Runtime Library Exception, version
     18  1.1  mrg // 3.1, as published by the Free Software Foundation.
     19  1.1  mrg 
     20  1.1  mrg // You should have received a copy of the GNU General Public License and
     21  1.1  mrg // a copy of the GCC Runtime Library Exception along with this program;
     22  1.1  mrg // see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
     23  1.1  mrg // <http://www.gnu.org/licenses/>.
     24  1.1  mrg 
     25  1.1  mrg // This implementation is based on libcxx/include/barrier
     26  1.1  mrg //===-- barrier.h --------------------------------------------------===//
     27  1.1  mrg //
     28  1.1  mrg // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
     29  1.1  mrg // See https://llvm.org/LICENSE.txt for license information.
     30  1.1  mrg // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
     31  1.1  mrg //
     32  1.1  mrg //===---------------------------------------------------------------===//
     33  1.1  mrg 
     34  1.1  mrg /** @file include/barrier
     35  1.1  mrg  *  This is a Standard C++ Library header.
     36  1.1  mrg  */
     37  1.1  mrg 
     38  1.1  mrg #ifndef _GLIBCXX_BARRIER
     39  1.1  mrg #define _GLIBCXX_BARRIER 1
     40  1.1  mrg 
     41  1.1  mrg #pragma GCC system_header
     42  1.1  mrg 
     43  1.1  mrg #if __cplusplus > 201703L
     44  1.1  mrg #include <bits/atomic_base.h>
     45  1.1  mrg #if __cpp_lib_atomic_wait && __cpp_aligned_new
     46  1.1  mrg #include <bits/std_thread.h>
     47  1.1  mrg #include <bits/unique_ptr.h>
     48  1.1  mrg 
     49  1.1  mrg #include <array>
     50  1.1  mrg 
     51  1.1  mrg #define __cpp_lib_barrier 201907L
     52  1.1  mrg 
     53  1.1  mrg namespace std _GLIBCXX_VISIBILITY(default)
     54  1.1  mrg {
     55  1.1  mrg _GLIBCXX_BEGIN_NAMESPACE_VERSION
     56  1.1  mrg 
     57  1.1  mrg   struct __empty_completion
     58  1.1  mrg   {
     59  1.1  mrg     _GLIBCXX_ALWAYS_INLINE void
     60  1.1  mrg     operator()() noexcept
     61  1.1  mrg     { }
     62  1.1  mrg   };
     63  1.1  mrg 
     64  1.1  mrg /*
     65  1.1  mrg 
     66  1.1  mrg The default implementation of __tree_barrier is a classic tree barrier.
     67  1.1  mrg 
     68  1.1  mrg It looks different from literature pseudocode for two main reasons:
     69  1.1  mrg  1. Threads that call into std::barrier functions do not provide indices,
     70  1.1  mrg     so a numbering step is added before the actual barrier algorithm,
     71  1.1  mrg     appearing as an N+1 round to the N rounds of the tree barrier.
     72  1.1  mrg  2. A great deal of attention has been paid to avoid cache line thrashing
     73  1.1  mrg     by flattening the tree structure into cache-line sized arrays, that
     74  1.1  mrg     are indexed in an efficient way.
     75  1.1  mrg 
     76  1.1  mrg */
     77  1.1  mrg 
     78  1.1  mrg   enum class __barrier_phase_t : unsigned char { };
     79  1.1  mrg 
     80  1.1  mrg   template<typename _CompletionF>
     81  1.1  mrg     class __tree_barrier
     82  1.1  mrg     {
     83  1.1  mrg       using __atomic_phase_ref_t = std::__atomic_ref<__barrier_phase_t>;
     84  1.1  mrg       using __atomic_phase_const_ref_t = std::__atomic_ref<const __barrier_phase_t>;
     85  1.1  mrg       static constexpr auto __phase_alignment =
     86  1.1  mrg 		      __atomic_phase_ref_t::required_alignment;
     87  1.1  mrg 
     88  1.1  mrg       using __tickets_t = std::array<__barrier_phase_t, 64>;
     89  1.1  mrg       struct alignas(64) /* naturally-align the heap state */ __state_t
     90  1.1  mrg       {
     91  1.1  mrg 	alignas(__phase_alignment) __tickets_t __tickets;
     92  1.1  mrg       };
     93  1.1  mrg 
     94  1.1  mrg       ptrdiff_t _M_expected;
     95  1.1  mrg       unique_ptr<__state_t[]> _M_state;
     96  1.1  mrg       __atomic_base<ptrdiff_t> _M_expected_adjustment;
     97  1.1  mrg       _CompletionF _M_completion;
     98  1.1  mrg 
     99  1.1  mrg       alignas(__phase_alignment) __barrier_phase_t  _M_phase;
    100  1.1  mrg 
    101  1.1  mrg       bool
    102  1.1  mrg       _M_arrive(__barrier_phase_t __old_phase, size_t __current)
    103  1.1  mrg       {
    104  1.1  mrg 	const auto __old_phase_val = static_cast<unsigned char>(__old_phase);
    105  1.1  mrg 	const auto __half_step =
    106  1.1  mrg 			   static_cast<__barrier_phase_t>(__old_phase_val + 1);
    107  1.1  mrg 	const auto __full_step =
    108  1.1  mrg 			   static_cast<__barrier_phase_t>(__old_phase_val + 2);
    109  1.1  mrg 
    110  1.1  mrg 	size_t __current_expected = _M_expected;
    111  1.1  mrg 	__current %= ((_M_expected + 1) >> 1);
    112  1.1  mrg 
    113  1.1  mrg 	for (int __round = 0; ; ++__round)
    114  1.1  mrg 	  {
    115  1.1  mrg 	    if (__current_expected <= 1)
    116  1.1  mrg 		return true;
    117  1.1  mrg 	    size_t const __end_node = ((__current_expected + 1) >> 1),
    118  1.1  mrg 			 __last_node = __end_node - 1;
    119  1.1  mrg 	    for ( ; ; ++__current)
    120  1.1  mrg 	      {
    121  1.1  mrg 		if (__current == __end_node)
    122  1.1  mrg 		  __current = 0;
    123  1.1  mrg 		auto __expect = __old_phase;
    124  1.1  mrg 		__atomic_phase_ref_t __phase(_M_state[__current]
    125  1.1  mrg 						.__tickets[__round]);
    126  1.1  mrg 		if (__current == __last_node && (__current_expected & 1))
    127  1.1  mrg 		  {
    128  1.1  mrg 		    if (__phase.compare_exchange_strong(__expect, __full_step,
    129  1.1  mrg 						        memory_order_acq_rel))
    130  1.1  mrg 		      break;     // I'm 1 in 1, go to next __round
    131  1.1  mrg 		  }
    132  1.1  mrg 		else if (__phase.compare_exchange_strong(__expect, __half_step,
    133  1.1  mrg 						         memory_order_acq_rel))
    134  1.1  mrg 		  {
    135  1.1  mrg 		    return false; // I'm 1 in 2, done with arrival
    136  1.1  mrg 		  }
    137  1.1  mrg 		else if (__expect == __half_step)
    138  1.1  mrg 		  {
    139  1.1  mrg 		    if (__phase.compare_exchange_strong(__expect, __full_step,
    140  1.1  mrg 						        memory_order_acq_rel))
    141  1.1  mrg 		      break;    // I'm 2 in 2, go to next __round
    142  1.1  mrg 		  }
    143  1.1  mrg 	      }
    144  1.1  mrg 	    __current_expected = __last_node + 1;
    145  1.1  mrg 	    __current >>= 1;
    146  1.1  mrg 	  }
    147  1.1  mrg       }
    148  1.1  mrg 
    149  1.1  mrg     public:
    150  1.1  mrg       using arrival_token = __barrier_phase_t;
    151  1.1  mrg 
    152  1.1  mrg       static constexpr ptrdiff_t
    153  1.1  mrg       max() noexcept
    154  1.1  mrg       { return __PTRDIFF_MAX__; }
    155  1.1  mrg 
    156  1.1  mrg       __tree_barrier(ptrdiff_t __expected, _CompletionF __completion)
    157  1.1  mrg 	  : _M_expected(__expected), _M_expected_adjustment(0),
    158  1.1  mrg 	    _M_completion(move(__completion)),
    159  1.1  mrg 	    _M_phase(static_cast<__barrier_phase_t>(0))
    160  1.1  mrg       {
    161  1.1  mrg 	size_t const __count = (_M_expected + 1) >> 1;
    162  1.1  mrg 
    163  1.1  mrg 	_M_state = std::make_unique<__state_t[]>(__count);
    164  1.1  mrg       }
    165  1.1  mrg 
    166  1.1  mrg       [[nodiscard]] arrival_token
    167  1.1  mrg       arrive(ptrdiff_t __update)
    168  1.1  mrg       {
    169  1.1  mrg 	std::hash<std::thread::id> __hasher;
    170  1.1  mrg 	size_t __current = __hasher(std::this_thread::get_id());
    171  1.1  mrg 	__atomic_phase_ref_t __phase(_M_phase);
    172  1.1  mrg 	const auto __old_phase = __phase.load(memory_order_relaxed);
    173  1.1  mrg 	const auto __cur = static_cast<unsigned char>(__old_phase);
    174  1.1  mrg 	for(; __update; --__update)
    175  1.1  mrg 	  {
    176  1.1  mrg 	    if(_M_arrive(__old_phase, __current))
    177  1.1  mrg 	      {
    178  1.1  mrg 		_M_completion();
    179  1.1  mrg 		_M_expected += _M_expected_adjustment.load(memory_order_relaxed);
    180  1.1  mrg 		_M_expected_adjustment.store(0, memory_order_relaxed);
    181  1.1  mrg 		auto __new_phase = static_cast<__barrier_phase_t>(__cur + 2);
    182  1.1  mrg 		__phase.store(__new_phase, memory_order_release);
    183  1.1  mrg 		__phase.notify_all();
    184  1.1  mrg 	      }
    185  1.1  mrg 	  }
    186  1.1  mrg 	return __old_phase;
    187  1.1  mrg       }
    188  1.1  mrg 
    189  1.1  mrg       void
    190  1.1  mrg       wait(arrival_token&& __old_phase) const
    191  1.1  mrg       {
    192  1.1  mrg 	__atomic_phase_const_ref_t __phase(_M_phase);
    193  1.1  mrg 	auto const __test_fn = [=]
    194  1.1  mrg 	  {
    195  1.1  mrg 	    return __phase.load(memory_order_acquire) != __old_phase;
    196  1.1  mrg 	  };
    197  1.1  mrg 	std::__atomic_wait_address(&_M_phase, __test_fn);
    198  1.1  mrg       }
    199  1.1  mrg 
    200  1.1  mrg       void
    201  1.1  mrg       arrive_and_drop()
    202  1.1  mrg       {
    203  1.1  mrg 	_M_expected_adjustment.fetch_sub(1, memory_order_relaxed);
    204  1.1  mrg 	(void)arrive(1);
    205  1.1  mrg       }
    206  1.1  mrg     };
    207  1.1  mrg 
    208  1.1  mrg   template<typename _CompletionF = __empty_completion>
    209  1.1  mrg     class barrier
    210  1.1  mrg     {
    211  1.1  mrg       // Note, we may introduce a "central" barrier algorithm at some point
    212  1.1  mrg       // for more space constrained targets
    213  1.1  mrg       using __algorithm_t = __tree_barrier<_CompletionF>;
    214  1.1  mrg       __algorithm_t _M_b;
    215  1.1  mrg 
    216  1.1  mrg     public:
    217  1.1  mrg       class arrival_token final
    218  1.1  mrg       {
    219  1.1  mrg       public:
    220  1.1  mrg 	arrival_token(arrival_token&&) = default;
    221  1.1  mrg 	arrival_token& operator=(arrival_token&&) = default;
    222  1.1  mrg 	~arrival_token() = default;
    223  1.1  mrg 
    224  1.1  mrg       private:
    225  1.1  mrg 	friend class barrier;
    226  1.1  mrg 	using __token = typename __algorithm_t::arrival_token;
    227  1.1  mrg 	explicit arrival_token(__token __tok) noexcept : _M_tok(__tok) { }
    228  1.1  mrg 	__token _M_tok;
    229  1.1  mrg       };
    230  1.1  mrg 
    231  1.1  mrg       static constexpr ptrdiff_t
    232  1.1  mrg       max() noexcept
    233  1.1  mrg       { return __algorithm_t::max(); }
    234  1.1  mrg 
    235  1.1  mrg       explicit
    236  1.1  mrg       barrier(ptrdiff_t __count, _CompletionF __completion = _CompletionF())
    237  1.1  mrg       : _M_b(__count, std::move(__completion))
    238  1.1  mrg       { }
    239  1.1  mrg 
    240  1.1  mrg       barrier(barrier const&) = delete;
    241  1.1  mrg       barrier& operator=(barrier const&) = delete;
    242  1.1  mrg 
    243  1.1  mrg       [[nodiscard]] arrival_token
    244  1.1  mrg       arrive(ptrdiff_t __update = 1)
    245  1.1  mrg       { return arrival_token{_M_b.arrive(__update)}; }
    246  1.1  mrg 
    247  1.1  mrg       void
    248  1.1  mrg       wait(arrival_token&& __phase) const
    249  1.1  mrg       { _M_b.wait(std::move(__phase._M_tok)); }
    250  1.1  mrg 
    251  1.1  mrg       void
    252  1.1  mrg       arrive_and_wait()
    253  1.1  mrg       { wait(arrive()); }
    254  1.1  mrg 
    255  1.1  mrg       void
    256  1.1  mrg       arrive_and_drop()
    257  1.1  mrg       { _M_b.arrive_and_drop(); }
    258  1.1  mrg     };
    259  1.1  mrg 
    260  1.1  mrg _GLIBCXX_END_NAMESPACE_VERSION
    261  1.1  mrg } // namespace
    262  1.1  mrg #endif // __cpp_lib_atomic_wait && __cpp_aligned_new
    263  1.1  mrg #endif // __cplusplus > 201703L
    264  1.1  mrg #endif // _GLIBCXX_BARRIER
    265