// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "gpu/command_buffer/service/sync_point_manager.h"

#include <limits.h>
#include <stddef.h>
#include <stdint.h>

#include "base/functional/bind.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/metrics/histogram_macros.h"
#include "base/synchronization/lock.h"
#include "base/trace_event/trace_event.h"
#include "gpu/config/gpu_finch_features.h"
#include "third_party/perfetto/include/perfetto/tracing/track_event_args.h"

namespace gpu {
namespace {
uint64_t GenerateCallbackId() {
  static std::atomic_uint64_t next_callback_id{1u};
  return next_callback_id.fetch_add(1, std::memory_order_relaxed);
}
}  // namespace

SyncPointOrderData::OrderFence::OrderFence(
    uint64_t order,
    uint64_t release,
    scoped_refptr<SyncPointClientState> state,
    uint64_t callback_id)
    : order_num(order),
      fence_release(release),
      client_state(std::move(state)),
      callback_id(callback_id) {}

SyncPointOrderData::OrderFence::OrderFence(const OrderFence& other) = default;

SyncPointOrderData::OrderFence::~OrderFence() = default;

SyncPointOrderData::SyncPointOrderData(SyncPointManager* sync_point_manager,
                                       SequenceId sequence_id)
    : sync_point_manager_(sync_point_manager), sequence_id_(sequence_id) {
  // Creation could happen outside of GPU thread.
  DETACH_FROM_THREAD(processing_thread_checker_);
}

SyncPointOrderData::~SyncPointOrderData() {
  DCHECK(destroyed_);
}

void SyncPointOrderData::Destroy() {
  // Because of circular references between the SyncPointOrderData and
  // SyncPointClientState, we must remove the references on destroy. Releasing
  // the fence syncs in the order fence queue would be redundant at this point
  // because they are assumed to be released on the destruction of the
  // SyncPointClientState.
  {
    base::AutoLock lock(lock_);
    DCHECK(!destroyed_);
    destroyed_ = true;
    while (!order_fence_queue_.empty()) {
      order_fence_queue_.pop();
    }
  }
  // Now what we've marked ourselves as destroyed_, we can finally remove
  // ourselves from the manager's map.
  sync_point_manager_->RemoveSyncPointOrderData(base::WrapRefCounted(this));
}

uint64_t SyncPointOrderData::GenerateUnprocessedOrderNumber() {
  base::AutoLock auto_lock(lock_);
  DCHECK(!destroyed_);
  last_unprocessed_order_num_ = sync_point_manager_->GenerateOrderNumber();
  unprocessed_order_nums_.push(last_unprocessed_order_num_);
  return last_unprocessed_order_num_;
}

void SyncPointOrderData::BeginProcessingOrderNumber(uint64_t order_num) {
  DCHECK(processing_thread_checker_.CalledOnValidThread());
  DCHECK_GE(order_num, current_order_num_);
  // Use thread-safe accessors here because |processed_order_num_| and
  // |unprocessed_order_num_| are protected by a lock.
  DCHECK_GT(order_num, processed_order_num());
  DCHECK_LE(order_num, unprocessed_order_num());
  current_order_num_ = order_num;
  paused_ = false;
}

void SyncPointOrderData::PauseProcessingOrderNumber(uint64_t order_num) {
  DCHECK(processing_thread_checker_.CalledOnValidThread());
  DCHECK_EQ(current_order_num_, order_num);
  DCHECK(!paused_);
  paused_ = true;
}

void SyncPointOrderData::FinishProcessingOrderNumber(uint64_t order_num) {
  DCHECK(processing_thread_checker_.CalledOnValidThread());
  DCHECK_EQ(current_order_num_, order_num);
  DCHECK(!paused_);

  // Catch invalid waits which were waiting on fence syncs that do not exist.
  // When we end processing an order number, we should release any fence syncs
  // which were suppose to be released during this order number.
  // Release without the lock to avoid possible deadlocks.
  std::vector<OrderFence> ensure_releases;
  {
    base::AutoLock auto_lock(lock_);
    DCHECK_GT(order_num, processed_order_num_);
    processed_order_num_ = order_num;

    DCHECK(!unprocessed_order_nums_.empty());
    DCHECK_EQ(order_num, unprocessed_order_nums_.front());
    unprocessed_order_nums_.pop();

    if (!sync_point_manager_->graph_validation_enabled()) {
      uint64_t next_order_num = 0;
      if (!unprocessed_order_nums_.empty()) {
        next_order_num = unprocessed_order_nums_.front();
      }

      while (!order_fence_queue_.empty()) {
        const OrderFence& order_fence = order_fence_queue_.top();
        // It's possible for the fence's order number to equal next order
        // number. This happens when the wait was enqueued with an order number
        // greater than the last unprocessed order number. So don't release the
        // fence yet.
        if (!next_order_num || order_fence.order_num < next_order_num) {
          ensure_releases.push_back(order_fence);
          order_fence_queue_.pop();
          continue;
        }
        break;
      }
    }
  }

  for (OrderFence& order_fence : ensure_releases) {
    order_fence.client_state->EnsureWaitReleased(order_fence.fence_release,
                                                 order_fence.callback_id);
  }
}

uint64_t SyncPointOrderData::ValidateReleaseOrderNumber(
    scoped_refptr<SyncPointClientState> client_state,
    uint64_t wait_order_num,
    uint64_t fence_release) {
  client_state->fence_sync_lock_.AssertAcquired();  // Can't statically check.
  base::AutoLock lock(lock_);

  if (destroyed_)
    return 0;

  if (sync_point_manager_->graph_validation_enabled()) {
    return GenerateCallbackId();
  }

  // We should have unprocessed order numbers which could potentially release
  // this fence.
  if (unprocessed_order_nums_.empty()) {
    return 0;
  }

  // We should have an unprocessed order number lower than the wait order
  // number for the wait to be valid. It's not possible for wait order number to
  // equal next unprocessed order number, but we handle that defensively.
  if (wait_order_num <= unprocessed_order_nums_.front()) {
    return 0;
  }

  // So far it could be valid, but add an order fence guard to be sure it
  // gets released eventually.
  uint64_t expected_order_num =
      std::min(unprocessed_order_nums_.back(), wait_order_num);
  uint64_t callback_id = GenerateCallbackId();
  order_fence_queue_.emplace(expected_order_num, fence_release,
                             std::move(client_state), callback_id);
  return callback_id;
}

SyncPointClientState::ReleaseCallback::ReleaseCallback(
    uint64_t release,
    base::OnceClosure callback,
    uint64_t callback_id)
    : release_count(release),
      callback_closure(std::move(callback)),
      callback_id(callback_id) {}

SyncPointClientState::ReleaseCallback::ReleaseCallback(
    ReleaseCallback&& other) = default;

SyncPointClientState::ReleaseCallback::~ReleaseCallback() = default;

SyncPointClientState::SyncPointClientState(
    SyncPointManager* sync_point_manager,
    scoped_refptr<SyncPointOrderData> order_data,
    CommandBufferNamespace namespace_id,
    CommandBufferId command_buffer_id)
    : sync_point_manager_(sync_point_manager),
      order_data_(std::move(order_data)),
      namespace_id_(namespace_id),
      command_buffer_id_(command_buffer_id) {}

SyncPointClientState::~SyncPointClientState() {
  DCHECK_EQ(UINT64_MAX, fence_sync_release_);
}

void SyncPointClientState::Destroy() {
  sync_point_manager_->DestroySyncPointClientState(base::WrapRefCounted(this));
}

std::vector<base::OnceClosure>
SyncPointClientState::DestroyAndReturnCallbacks() {
  base::AutoLock lock(fence_sync_lock_);
  DCHECK(!destroyed_.IsSet());
  destroyed_.Set();
  std::vector<base::OnceClosure> callbacks;
  callbacks.reserve(release_callback_queue_.size());
  while (!release_callback_queue_.empty()) {
    callbacks.push_back(
        std::move(const_cast<ReleaseCallback&>(release_callback_queue_.top())
                      .callback_closure));
    release_callback_queue_.pop();
  }
  fence_sync_release_ = UINT64_MAX;
  return callbacks;
}

bool SyncPointClientState::IsFenceSyncReleased(uint64_t release) {
  base::AutoLock lock(fence_sync_lock_);
  return release <= fence_sync_release_;
}

bool SyncPointClientState::WaitForRelease(uint64_t release,
                                          uint64_t wait_order_num,
                                          base::OnceClosure callback) {
  // Lock must be held the whole time while we validate otherwise it could be
  // released while we are checking.
  base::AutoLock auto_lock(fence_sync_lock_);

  // Already released, do not run the callback.
  if (release <= fence_sync_release_)
    return false;

  uint64_t callback_id =
      order_data_->ValidateReleaseOrderNumber(this, wait_order_num, release);
  if (callback_id) {
    // Add the callback which will be called upon release.
    TRACE_EVENT("gpu,toplevel.flow", "SyncToken::Wait",
                perfetto::Flow::ProcessScoped(callback_id, "SyncToken"));
    release_callback_queue_.emplace(release, std::move(callback), callback_id);
    return true;
  }

  DLOG(ERROR)
      << "Client waiting on non-existent sync token or sequence destroyed";
  return false;
}

void SyncPointClientState::EnsureFenceSyncReleased(uint64_t release,
                                                   ReleaseCause cause) {
  // Call callbacks without the lock to avoid possible deadlocks.
  std::vector<ReleaseCallback> callback_list;
  {
    base::AutoLock auto_lock(fence_sync_lock_);

    // Check that in the ReleaseCause::kExplicitClientRelease case, the
    // release count must be larger than previously-seen release count from the
    // client.
    //
    // For the ReleaseCause::kTaskCompletionRelease case, we relax the check a
    // little bit to allow the release count to be "no less than"
    // previously-seen release count from the client. That is because currently
    // for some clients consecutive tasks may specify the same task release
    // number, if no new fence sync is inserted.
    //
    // Please also note that if forceful release has happened to resolve invalid
    // waits, the current `fence_sync_release_` may be larger than `release`.
    if ((cause == ReleaseCause::kExplicitClientRelease &&
         release <= client_fence_sync_release_) ||
        (cause == ReleaseCause::kTaskCompletionRelease &&
         release < client_fence_sync_release_)) {
      static constexpr char error_message[] =
          "Client attempted to release a fence sync that has been released.";
      if (!sync_point_manager_->suppress_fatal_log_for_testing()) {
        LOG(DFATAL) << error_message;
      } else {
        LOG(ERROR) << error_message;
      }
    }

    if (cause == ReleaseCause::kExplicitClientRelease ||
        cause == ReleaseCause::kTaskCompletionRelease) {
      client_fence_sync_release_ = release;
    }

    if (release <= fence_sync_release_) {
      DCHECK(release_callback_queue_.empty() ||
             release_callback_queue_.top().release_count > release);
      return;
    }
    fence_sync_release_ = release;

    while (!release_callback_queue_.empty() &&
           release_callback_queue_.top().release_count <= release) {
      ReleaseCallback& release_callback =
          const_cast<ReleaseCallback&>(release_callback_queue_.top());
      callback_list.emplace_back(std::move(release_callback));
      release_callback_queue_.pop();
    }
  }

  for (ReleaseCallback& callback : callback_list) {
    TRACE_EVENT("gpu,toplevel.flow", "SyncToken::Release",
                perfetto::TerminatingFlow::ProcessScoped(callback.callback_id,
                                                         "SyncToken"));
    std::move(callback.callback_closure).Run();
  }
}

void SyncPointClientState::EnsureWaitReleased(uint64_t release,
                                              uint64_t callback_id) {
  // This method should not be called if graph-based validation is enabled.
  DCHECK(!sync_point_manager_->graph_validation_enabled());

  // Call callbacks without the lock to avoid possible deadlocks.
  base::OnceClosure callback;

  {
    base::AutoLock auto_lock(fence_sync_lock_);
    if (release <= fence_sync_release_)
      return;

    std::vector<ReleaseCallback> popped_callbacks;
    popped_callbacks.reserve(release_callback_queue_.size());

    while (!release_callback_queue_.empty() &&
           release_callback_queue_.top().release_count <= release) {
      ReleaseCallback& top_item =
          const_cast<ReleaseCallback&>(release_callback_queue_.top());
      if (top_item.release_count == release &&
          top_item.callback_id == callback_id) {
        // Call the callback, and discard this item from the callback queue.
        callback = std::move(top_item.callback_closure);
      } else {
        // Store the item to be placed back into the callback queue later.
        popped_callbacks.emplace_back(std::move(top_item));
      }
      release_callback_queue_.pop();
    }

    // Add back in popped items.
    for (ReleaseCallback& popped_callback : popped_callbacks) {
      release_callback_queue_.emplace(std::move(popped_callback));
    }
  }

  if (callback) {
    // This effectively releases the wait without releasing the fence.
    DLOG(ERROR) << "Client did not release sync token as expected";
    TRACE_EVENT(
        "gpu,toplevel.flow", "SyncToken::ForceEndWait",
        perfetto::TerminatingFlow::ProcessScoped(callback_id, "SyncToken"));
    std::move(callback).Run();
  }
}

SyncPointManager::SyncPointManager()
    : graph_validation_enabled_(features::IsSyncPointGraphValidationEnabled()) {
}

SyncPointManager::~SyncPointManager() {
  DCHECK(order_data_map_.empty());
  for (const ClientStateMap& client_state_map : client_state_maps_)
    DCHECK(client_state_map.empty());
}

scoped_refptr<SyncPointOrderData> SyncPointManager::CreateSyncPointOrderData() {
  base::AutoLock auto_lock(lock_);
  SequenceId sequence_id = sequence_id_generator_.GenerateNextId();
  scoped_refptr<SyncPointOrderData> order_data =
      new SyncPointOrderData(this, sequence_id);
  DCHECK(!order_data_map_.count(sequence_id));
  order_data_map_.insert(std::make_pair(sequence_id, order_data));
  return order_data;
}

void SyncPointManager::RemoveSyncPointOrderData(
    scoped_refptr<SyncPointOrderData> order_data) {
  base::AutoLock auto_lock(lock_);
  // destroyed_ is the flag used to check the validity of SyncPointOrderData. It
  // must be destroyed before removing it from order_data_map_.
  DCHECK(TS_UNCHECKED_READ(order_data->destroyed_));
  const int num_erased = order_data_map_.erase(order_data->sequence_id());
  DCHECK_EQ(num_erased, 1);
}

scoped_refptr<SyncPointClientState>
SyncPointManager::CreateSyncPointClientState(
    CommandBufferNamespace namespace_id,
    CommandBufferId command_buffer_id,
    SequenceId sequence_id) {
  base::AutoLock auto_lock(lock_);

  scoped_refptr<SyncPointOrderData> order_data =
      GetSyncPointOrderData(sequence_id);
  scoped_refptr<SyncPointClientState> client_state = new SyncPointClientState(
      this, order_data, namespace_id, command_buffer_id);

  DCHECK_GE(namespace_id, 0);
  DCHECK_LT(static_cast<size_t>(namespace_id), std::size(client_state_maps_));
  const auto [_, inserted] = client_state_maps_[namespace_id].insert(
      std::make_pair(command_buffer_id, client_state));
  CHECK(inserted);

  return client_state;
}

void SyncPointManager::DestroySyncPointClientState(
    scoped_refptr<SyncPointClientState> client_state) {
  std::vector<base::OnceClosure> callbacks;
  // First, destroy the client state (which marks it as destroyed). Its pending
  // callbacks will be executed after we remove the client state from our map.
  {
    base::AutoLock lock(lock_);
    callbacks = client_state->DestroyAndReturnCallbacks();
    const CommandBufferNamespace namespace_id = client_state->namespace_id();
    const CommandBufferId command_buffer_id = client_state->command_buffer_id();
    DCHECK_GE(namespace_id, 0);
    DCHECK_LT(static_cast<size_t>(namespace_id), std::size(client_state_maps_));
    DCHECK(client_state_maps_[namespace_id].count(command_buffer_id));
    client_state_maps_[namespace_id].erase(command_buffer_id);
  }
  // At this point, if SyncPointClientState::Wait is called, it will (correctly)
  // return false because client_state is removed from our map. It is safe to
  // call the callbacks.
  for (auto& closure : callbacks) {
    std::move(closure).Run();
  }
}

void SyncPointManager::EnsureFenceSyncReleased(const SyncToken& release,
                                               ReleaseCause cause) {
  scoped_refptr<SyncPointClientState> client_state;
  {
    base::AutoLock lock(lock_);

    if (base::ShouldRecordSubsampledMetric(0.01)) {
      if (graph_validation_enabled_) {
        UMA_HISTOGRAM_ENUMERATION("GPU.FenceSyncRelease.GraphValidation.Cause",
                                  cause);
      } else {
        UMA_HISTOGRAM_ENUMERATION("GPU.FenceSyncRelease.OrderValidation.Cause",
                                  cause);
      }
    }

    if (cause == ReleaseCause::kForceRelease) {
      if (graph_validation_enabled_) {
        UMA_HISTOGRAM_ENUMERATION(
            "GPU.FenceSyncRelease.GraphValidation.ForceReleaseNamespace",
            release.namespace_id(),
            CommandBufferNamespace::NUM_COMMAND_BUFFER_NAMESPACES);
      } else {
        NOTREACHED() << "ReleaseCause::kForceRelease is only used in "
                        "graph-based validation.";
      }
    }

    client_state = GetSyncPointClientState(release.namespace_id(),
                                           release.command_buffer_id());
  }
  if (client_state) {
    // This must be called without holding `lock_`, because it may call release
    // callbacks which are not supposed to be called under `lock_`.
    client_state->EnsureFenceSyncReleased(release.release_count(), cause);
  }
}

bool SyncPointManager::IsSyncTokenReleased(const SyncToken& sync_token) {
  base::AutoLock auto_lock(lock_);
  scoped_refptr<SyncPointClientState> release_state = GetSyncPointClientState(
      sync_token.namespace_id(), sync_token.command_buffer_id());
  // release_state can't be destroyed while we hold lock_.
  if (release_state)
    return release_state->IsFenceSyncReleased(sync_token.release_count());
  return true;
}

SequenceId SyncPointManager::GetSyncTokenReleaseSequenceId(
    const SyncToken& sync_token) {
  base::AutoLock auto_lock(lock_);
  return GetSyncTokenReleaseSequenceIdInternal(sync_token);
}

SequenceId SyncPointManager::GetSyncTokenReleaseSequenceIdInternal(
    const SyncToken& sync_token) {
  scoped_refptr<SyncPointClientState> client_state = GetSyncPointClientState(
      sync_token.namespace_id(), sync_token.command_buffer_id());
  if (client_state) {
    return client_state->sequence_id();
  }
  return SequenceId();
}

uint64_t SyncPointManager::GetProcessedOrderNum() const {
  base::AutoLock auto_lock(lock_);
  uint64_t processed_order_num = 0;
  for (const auto& kv : order_data_map_) {
    processed_order_num =
        std::max(processed_order_num, kv.second->processed_order_num());
  }
  return processed_order_num;
}

uint64_t SyncPointManager::GetUnprocessedOrderNum() const {
  base::AutoLock auto_lock(lock_);
  uint64_t unprocessed_order_num = 0;
  for (const auto& kv : order_data_map_) {
    unprocessed_order_num =
        std::max(unprocessed_order_num, kv.second->unprocessed_order_num());
  }
  return unprocessed_order_num;
}

bool SyncPointManager::Wait(const SyncToken& sync_token,
                            SequenceId sequence_id,
                            uint64_t wait_order_num,
                            base::OnceClosure callback) {
  base::AutoLock auto_lock(lock_);
  if (sequence_id == GetSyncTokenReleaseSequenceIdInternal(sync_token)) {
    return false;
  }

  scoped_refptr<SyncPointClientState> release_state = GetSyncPointClientState(
      sync_token.namespace_id(), sync_token.command_buffer_id());
  if (release_state &&
      release_state->WaitForRelease(sync_token.release_count(), wait_order_num,
                                    std::move(callback))) {
    return true;
  }
  // Do not run callback if wait is invalid.
  return false;
}

uint64_t SyncPointManager::GenerateOrderNumber() {
  return next_order_num_.fetch_add(1, std::memory_order_relaxed);
}

scoped_refptr<SyncPointClientState> SyncPointManager::GetSyncPointClientState(
    CommandBufferNamespace namespace_id,
    CommandBufferId command_buffer_id) {
  if (namespace_id >= 0) {
    DCHECK_LT(static_cast<size_t>(namespace_id), std::size(client_state_maps_));
    ClientStateMap& client_state_map = client_state_maps_[namespace_id];
    auto it = client_state_map.find(command_buffer_id);
    if (it != client_state_map.end())
      return it->second;
  }
  return nullptr;
}

scoped_refptr<SyncPointOrderData> SyncPointManager::GetSyncPointOrderData(
    SequenceId sequence_id) {
  auto it = order_data_map_.find(sequence_id);
  if (it != order_data_map_.end())
    return it->second;
  return nullptr;
}

}  // namespace gpu
