Attempt to claim the lock at index. Return true on lock taken. lane_mask is a bitmap of the threads in the warp that would hold the single lock on success, e.g. the result of rpc::get_lane_mask() The lock is held when the n-th bit of the lock bitfield is set.
| 169 | /// single lock on success, e.g. the result of rpc::get_lane_mask() |
| 170 | /// The lock is held when the n-th bit of the lock bitfield is set. |
| 171 | RPC_ATTRS bool try_lock(uint64_t lane_mask, uint32_t index) { |
| 172 | // On amdgpu, test and set to the nth lock bit and a sync_lane would suffice |
| 173 | // On volta, need to handle differences between the threads running and |
| 174 | // the threads that were detected in the previous call to get_lane_mask() |
| 175 | // |
| 176 | // All threads in lane_mask try to claim the lock. At most one can succeed. |
| 177 | // There may be threads active which are not in lane mask which must not |
| 178 | // succeed in taking the lock, as otherwise it will leak. This is handled |
| 179 | // by making threads which are not in lane_mask or with 0, a no-op. |
| 180 | uint32_t id = rpc::get_lane_id(); |
| 181 | bool id_in_lane_mask = lane_mask & (1ul << id); |
| 182 | |
| 183 | // All threads in the warp call fetch_or. Possibly at the same time. |
| 184 | bool before = set_nth(lock, index, id_in_lane_mask); |
| 185 | uint64_t packed = rpc::ballot(lane_mask, before); |
| 186 | |
| 187 | // If every bit set in lane_mask is also set in packed, every single thread |
| 188 | // in the warp failed to get the lock. Ballot returns unset for threads not |
| 189 | // in the lane mask. |
| 190 | // |
| 191 | // Cases, per thread: |
| 192 | // mask==0 -> unspecified before, discarded by ballot -> 0 |
| 193 | // mask==1 and before==0 (success), set zero by ballot -> 0 |
| 194 | // mask==1 and before==1 (failure), set one by ballot -> 1 |
| 195 | // |
| 196 | // mask != packed implies at least one of the threads got the lock |
| 197 | // atomic semantics of fetch_or mean at most one of the threads for the lock |
| 198 | |
| 199 | // If holding the lock then the caller can load values knowing said loads |
| 200 | // won't move past the lock. No such guarantee is needed if the lock acquire |
| 201 | // failed. This conditional branch is expected to fold in the caller after |
| 202 | // inlining the current function. |
| 203 | bool holding_lock = lane_mask != packed; |
| 204 | if (holding_lock) |
| 205 | __scoped_atomic_thread_fence(__ATOMIC_ACQUIRE, __MEMORY_SCOPE_DEVICE); |
| 206 | return holding_lock; |
| 207 | } |
| 208 | |
| 209 | /// Unlock the lock at index. We need a lane sync to keep this function |
| 210 | /// convergent, otherwise the compiler will sink the store and deadlock. |
no test coverage detected