| /* Copyright (c) 2026 The Khronos Group Inc. |
| * Copyright (c) 2026 Valve Corporation |
| * Copyright (c) 2026 LunarG, Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "sync_access_map.h" |
| |
| namespace syncval { |
| |
| void AccessMap::Assign(const AccessMap &other) { |
| auto temp_copy(other.impl_map_); |
| impl_map_.swap(temp_copy); |
| } |
| |
| AccessMap::iterator AccessMap::LowerBound(ResourceAddress range_begin) { |
| auto it = impl_map_.lower_bound(AccessRange(range_begin, range_begin)); |
| return it; |
| } |
| |
| AccessMap::const_iterator AccessMap::LowerBound(ResourceAddress range_begin) const { |
| auto it = impl_map_.lower_bound(AccessRange(range_begin, range_begin)); |
| return it; |
| } |
| |
| AccessMap::iterator AccessMap::Erase(const iterator &pos) { |
| assert(pos != end()); |
| return impl_map_.erase(pos); |
| } |
| |
| void AccessMap::Erase(iterator first, iterator last) { |
| auto current = first; |
| while (current != last) { |
| assert(current != end()); |
| current = impl_map_.erase(current); |
| } |
| } |
| |
| AccessMap::iterator AccessMap::Insert(const_iterator hint, const AccessRange &range, const AccessState &access_state) { |
| assert(range.non_empty()); |
| bool hint_open; |
| const_iterator impl_next = hint; |
| if (impl_map_.empty()) { |
| hint_open = true; |
| } else if (impl_next == impl_map_.cbegin()) { |
| hint_open = range.strictly_less(impl_next->first); |
| } else if (impl_next == impl_map_.cend()) { |
| auto impl_prev = impl_next; |
| --impl_prev; |
| hint_open = range.strictly_greater(impl_prev->first); |
| } else { |
| auto impl_prev = impl_next; |
| --impl_prev; |
| hint_open = range.strictly_greater(impl_prev->first) && range.strictly_less(impl_next->first); |
| } |
| |
| if (!hint_open) { |
| // Hint was unhelpful, fall back to the non-hinted version |
| auto plain_insert = Insert(range, access_state); |
| return plain_insert.first; |
| } |
| |
| auto impl_insert = impl_map_.insert(impl_next, {range, access_state}); |
| return iterator(impl_insert); |
| } |
| |
| std::pair<AccessMap::iterator, bool> AccessMap::Insert(const AccessRange &range, const AccessState &access_state) { |
| assert(range.non_empty()); |
| |
| // Look for range conflicts (and an insertion point, which makes the lower_bound *not* wasted work) |
| // we don't have to check upper if just check that lower doesn't intersect (which it would if lower != upper) |
| auto lower = LowerBound(range.begin); |
| if (lower == end() || !lower->first.intersects(range)) { |
| // range is not even partially overlapped, and lower is strictly > than key |
| return {impl_map_.emplace_hint(lower, range, access_state), true}; |
| } |
| // We don't replace |
| return {lower, false}; |
| } |
| |
| AccessMap::iterator AccessMap::InfillGap(const_iterator range_lower_bound, const AccessRange &range, |
| const AccessState &access_state) { |
| assert(LowerBound(range.begin) == range_lower_bound); |
| assert(range_lower_bound == end() || range.strictly_less(range_lower_bound->first)); |
| return impl_map_.insert(range_lower_bound, {range, access_state}); |
| } |
| |
| void AccessMap::InfillGaps(const AccessRange &range, const AccessState &access_state) { |
| AccessMapLocator pos(*this, range.begin); |
| while (range.includes(pos.index)) { |
| if (!pos.inside_lower_bound_range) { |
| if (pos.lower_bound == end() || range.end <= pos.lower_bound->first.begin) { |
| const AccessRange gap_range(pos.index, range.end); |
| impl_map_.insert(pos.lower_bound, {gap_range, access_state}); |
| return; // reached range.end |
| } else { |
| const AccessRange gap_range(pos.index, pos.lower_bound->first.begin); |
| impl_map_.insert(pos.lower_bound, {gap_range, access_state}); |
| pos.Seek(pos.lower_bound->first.end); |
| } |
| } else { |
| pos.Seek(pos.lower_bound->first.end); |
| } |
| } |
| } |
| |
| AccessMap::iterator AccessMap::Split(const iterator split_it, const index_type &index) { |
| const auto range = split_it->first; |
| |
| if (!range.includes(index)) { |
| return split_it; // If we don't have a valid split point, just return the iterator |
| } |
| |
| AccessRange lower_range(range.begin, index); |
| |
| if (lower_range.empty()) { |
| // This is a noop, we're keeping the upper half which is the same as split_it |
| return split_it; |
| } |
| |
| // Save the contents and erase |
| auto value = split_it->second; |
| auto next_it = impl_map_.erase(split_it); |
| |
| AccessRange upper_range(index, range.end); |
| assert(!upper_range.empty()); // Upper range cannot be empty |
| |
| // Copy value to the upper range |
| // NOTE: we insert from upper to lower because that's what emplace_hint can do in constant time |
| assert(impl_map_.find(upper_range) == impl_map_.end()); |
| next_it = impl_map_.emplace_hint(next_it, std::make_pair(upper_range, value)); |
| |
| // Move value to the lower range (we can move since the upper range already got a copy of value) |
| assert(impl_map_.find(lower_range) == impl_map_.end()); |
| next_it = impl_map_.emplace_hint(next_it, std::make_pair(lower_range, std::move(value))); |
| |
| // Iterator to the beginning of the lower range |
| return next_it; |
| } |
| |
| AccessMap::iterator Split(AccessMap::iterator in, AccessMap &map, const AccessRange &range) { |
| assert(in != map.end()); // Not designed for use with invalid iterators... |
| const AccessRange in_range = in->first; |
| const AccessRange split_range = in_range & range; |
| |
| if (split_range.empty()) { |
| return map.end(); |
| } |
| auto pos = in; |
| if (split_range.begin != in_range.begin) { |
| pos = map.Split(pos, split_range.begin); |
| ++pos; |
| } |
| if (split_range.end != in_range.end) { |
| pos = map.Split(pos, split_range.end); |
| } |
| return pos; |
| } |
| |
| void Consolidate(AccessMap &map) { |
| using It = AccessMap::iterator; |
| |
| It current = map.begin(); |
| const It map_end = map.end(); |
| |
| // To be included in a merge range there must be no gap in the AccessRange space, and the mapped_type values must match |
| auto can_merge = [](const It &last, const It &cur) { |
| return cur->first.begin == last->first.end && cur->second == last->second; |
| }; |
| |
| while (current != map_end) { |
| // Establish a trival merge range at the current location, advancing current. Merge range is inclusive of merge_last |
| const It merge_first = current; |
| It merge_last = current; |
| ++current; |
| |
| // Expand the merge range as much as possible |
| while (current != map_end && can_merge(merge_last, current)) { |
| merge_last = current; |
| ++current; |
| } |
| |
| // Current isn't in the active merge range. If there is a non-trivial merge range, we resolve it here. |
| if (merge_first != merge_last) { |
| // IFF there is more than one range in (merge_first, merge_last) <- again noting the *inclusive* last |
| // Create a new Val spanning (first, last), substitute it for the multiple entries. |
| |
| const AccessRange merged_range(merge_first->first.begin, merge_last->first.end); |
| AccessState access = merge_last->second; |
| |
| // Note that current points to merge_last + 1, and is valid even if at map_end for these operations |
| map.Erase(merge_first, current); |
| |
| map.Insert(current, merged_range, std::move(access)); |
| } |
| } |
| } |
| |
| template <typename TAccessMap> |
| TAccessMapLocator<TAccessMap>::TAccessMapLocator(TAccessMap &map, index_type index) : map_(&map), index(index) { |
| lower_bound = LowerBoundForIndex(index); |
| inside_lower_bound_range = InsideLowerBoundRange(); |
| } |
| |
| template <typename TAccessMap> |
| TAccessMapLocator<TAccessMap>::TAccessMapLocator(TAccessMap &map, index_type index, const iterator &index_lower_bound) |
| : map_(&map), index(index), lower_bound(index_lower_bound) { |
| assert(LowerBoundForIndex(index) == index_lower_bound); |
| inside_lower_bound_range = InsideLowerBoundRange(); |
| } |
| |
| template <typename TAccessMap> |
| void TAccessMapLocator<TAccessMap>::Seek(index_type seek_to) { |
| if (TrySeekLocal(seek_to)) { |
| return; |
| } |
| index = seek_to; |
| lower_bound = LowerBoundForIndex(seek_to); // Expensive part |
| inside_lower_bound_range = InsideLowerBoundRange(); |
| } |
| |
| template <typename TAccessMap> |
| bool TAccessMapLocator<TAccessMap>::TrySeekLocal(index_type seek_to) { |
| auto is_lower_than = [this](AccessMap::index_type index, const auto &it) { return it == map_->end() || index < it->first.end; }; |
| |
| // Already here |
| if (index == seek_to) { |
| return true; |
| } |
| // The optimization is only for forward movement |
| if (index < seek_to) { |
| // Check if the current range is still a valid lower bound |
| if (is_lower_than(seek_to, lower_bound)) { |
| assert(lower_bound == LowerBoundForIndex(seek_to)); |
| index = seek_to; |
| inside_lower_bound_range = InsideLowerBoundRange(); |
| return true; |
| } |
| // Check if the next range is a valid lower bound |
| auto next_it = lower_bound; |
| ++next_it; |
| if (is_lower_than(seek_to, next_it)) { |
| assert(next_it == LowerBoundForIndex(seek_to)); |
| index = seek_to; |
| lower_bound = next_it; |
| inside_lower_bound_range = InsideLowerBoundRange(); |
| return true; |
| } |
| } |
| return false; // Need to re-search lower bound |
| } |
| |
| template <typename TAccessMap> |
| AccessMap::index_type TAccessMapLocator<TAccessMap>::DistanceToEdge() const { |
| if (lower_bound == map_->end()) { |
| return 0; |
| } |
| const index_type edge = inside_lower_bound_range ? lower_bound->first.end : lower_bound->first.begin; |
| return edge - index; |
| } |
| |
| // Explicit instantiation of const and non-const locators |
| template class TAccessMapLocator<AccessMap>; |
| template class TAccessMapLocator<const AccessMap>; |
| |
| void ParallelIterator::OnCurrentRangeModified(const iterator &new_lower_bound) { |
| // Only map A can be modified, map B is constant |
| pos_A = AccessMapLocator(map_A_, range.begin, new_lower_bound); |
| range.end = range.begin + ComputeDelta(); |
| } |
| |
| void ParallelIterator::SeekAfterModification(index_type index) { |
| // Destination map locator must be reinitialized after modification. |
| // Seek() (potentially more efficient) can only be used when there is no modification. |
| pos_A = AccessMapLocator(map_A_, index); |
| |
| pos_B.Seek(index); |
| range = AccessRange(index, index + ComputeDelta()); |
| } |
| |
| void ParallelIterator::NextRange() { |
| const index_type start = range.end; |
| const index_type delta = range.distance(); |
| assert(delta != 0); // Trying to increment past end |
| |
| pos_A.Seek(pos_A.index + delta); |
| pos_B.Seek(pos_B.index + delta); |
| |
| range = AccessRange(start, start + ComputeDelta()); |
| assert(pos_A.index == start); |
| assert(pos_B.index == start); |
| } |
| |
| ParallelIterator::index_type ParallelIterator::ComputeDelta() { |
| const index_type delta_A = pos_A.DistanceToEdge(); |
| const index_type delta_B = pos_B.DistanceToEdge(); |
| |
| // If either A or B are at end, there distance is *0*, so shouldn't be considered in the "distance to edge" |
| if (delta_A == 0) { // lower A is at end |
| return delta_B; |
| } else if (delta_B == 0) { // lower B is at end |
| return delta_A; |
| } else { |
| // Use the nearest edge, s.t. over this range A and B are both constant |
| return std::min(delta_A, delta_B); |
| } |
| } |
| |
| } // namespace syncval |