// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "src/heap/allocation-observer.h"

#include "src/heap/heap.h"
#include "src/heap/spaces.h"

namespace v8 {
namespace internal {

void AllocationCounter::AddAllocationObserver(AllocationObserver* observer) {
#if DEBUG
  auto it = std::find_if(observers_.begin(), observers_.end(),
                         [observer](const AllocationObserverCounter& aoc) {
                           return aoc.observer_ == observer;
                         });
  DCHECK_EQ(observers_.end(), it);
#endif

  if (step_in_progress_) {
    pending_added_.push_back(AllocationObserverCounter(observer, 0, 0));
    return;
  }

  intptr_t step_size = observer->GetNextStepSize();
  size_t observer_next_counter = current_counter_ + step_size;

  observers_.push_back(AllocationObserverCounter(observer, current_counter_,
                                                 observer_next_counter));

  if (observers_.size() == 1) {
    DCHECK_EQ(current_counter_, next_counter_);
    next_counter_ = observer_next_counter;
  } else {
    size_t missing_bytes = next_counter_ - current_counter_;
    next_counter_ = current_counter_ +
                    std::min(static_cast<intptr_t>(missing_bytes), step_size);
  }
}

void AllocationCounter::RemoveAllocationObserver(AllocationObserver* observer) {
  auto it = std::find_if(observers_.begin(), observers_.end(),
                         [observer](const AllocationObserverCounter& aoc) {
                           return aoc.observer_ == observer;
                         });
  DCHECK_NE(observers_.end(), it);

  if (step_in_progress_) {
    DCHECK_EQ(pending_removed_.count(observer), 0);
    pending_removed_.insert(observer);
    return;
  }

  observers_.erase(it);

  if (observers_.size() == 0) {
    current_counter_ = next_counter_ = 0;
  } else {
    size_t step_size = 0;

    for (AllocationObserverCounter& observer : observers_) {
      size_t left_in_step = observer.next_counter_ - current_counter_;
      DCHECK_GT(left_in_step, 0);
      step_size = step_size ? std::min(step_size, left_in_step) : left_in_step;
    }

    next_counter_ = current_counter_ + step_size;
  }
}

void AllocationCounter::AdvanceAllocationObservers(size_t allocated) {
  if (!IsActive()) {
    return;
  }

  DCHECK(!step_in_progress_);
  DCHECK_LT(allocated, next_counter_ - current_counter_);
  current_counter_ += allocated;
}

void AllocationCounter::InvokeAllocationObservers(Address soon_object,
                                                  size_t object_size,
                                                  size_t aligned_object_size) {
  if (!IsActive()) {
    return;
  }

  DCHECK(!step_in_progress_);
  DCHECK_GE(aligned_object_size, next_counter_ - current_counter_);
  DCHECK(soon_object);
  bool step_run = false;
  step_in_progress_ = true;
  size_t step_size = 0;

  DCHECK(pending_added_.empty());
  DCHECK(pending_removed_.empty());

  for (AllocationObserverCounter& aoc : observers_) {
    if (aoc.next_counter_ - current_counter_ <= aligned_object_size) {
      {
        DisallowGarbageCollection no_gc;
        aoc.observer_->Step(
            static_cast<int>(current_counter_ - aoc.prev_counter_), soon_object,
            object_size);
      }
      size_t observer_step_size = aoc.observer_->GetNextStepSize();

      aoc.prev_counter_ = current_counter_;
      aoc.next_counter_ =
          current_counter_ + aligned_object_size + observer_step_size;
      step_run = true;
    }

    size_t left_in_step = aoc.next_counter_ - current_counter_;
    step_size = step_size ? std::min(step_size, left_in_step) : left_in_step;
  }

  CHECK(step_run);

  // Now process newly added allocation observers.
  for (AllocationObserverCounter& aoc : pending_added_) {
    size_t observer_step_size = aoc.observer_->GetNextStepSize();
    aoc.prev_counter_ = current_counter_;
    aoc.next_counter_ =
        current_counter_ + aligned_object_size + observer_step_size;

    DCHECK_NE(step_size, 0);
    step_size = std::min(step_size, aligned_object_size + observer_step_size);

    observers_.push_back(aoc);
  }

  pending_added_.clear();

  if (!pending_removed_.empty()) {
    observers_.erase(
        std::remove_if(observers_.begin(), observers_.end(),
                       [this](const AllocationObserverCounter& aoc) {
                         return pending_removed_.count(aoc.observer_) != 0;
                       }));
    pending_removed_.clear();

    // Some observers were removed, recalculate step size.
    step_size = 0;
    for (AllocationObserverCounter& aoc : observers_) {
      size_t left_in_step = aoc.next_counter_ - current_counter_;
      step_size = step_size ? std::min(step_size, left_in_step) : left_in_step;
    }

    if (observers_.empty()) {
      next_counter_ = current_counter_ = 0;
      step_in_progress_ = false;
      return;
    }
  }

  next_counter_ = current_counter_ + step_size;
  step_in_progress_ = false;
}

PauseAllocationObserversScope::PauseAllocationObserversScope(Heap* heap)
    : heap_(heap) {
  DCHECK_EQ(heap->gc_state(), Heap::NOT_IN_GC);

  for (SpaceIterator it(heap_); it.HasNext();) {
    it.Next()->PauseAllocationObservers();
  }
}

PauseAllocationObserversScope::~PauseAllocationObserversScope() {
  for (SpaceIterator it(heap_); it.HasNext();) {
    it.Next()->ResumeAllocationObservers();
  }
}

}  // namespace internal
}  // namespace v8
