/*
 * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved.
 * Copyright (c) 2017, 2021 SAP SE. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 *
 */

#include "logging/log.hpp"
#include "memory/metaspace/metachunk.hpp"
#include "memory/metaspace/metaspaceCommon.hpp"
#include "memory/metaspace/metaspaceSettings.hpp"
#include "memory/metaspace/virtualSpaceNode.hpp"
#include "runtime/mutexLocker.hpp"
#include "runtime/os.hpp"
#include "utilities/align.hpp"
#include "utilities/copy.hpp"
#include "utilities/debug.hpp"
#include "utilities/macros.hpp"
#include "utilities/ostream.hpp"

namespace metaspace {

// Return a single char presentation of the state ('f', 'u', 'd')
char Metachunk::get_state_char() const {
  switch (_state) {
  case State::Free:   return 'f';
  case State::InUse:  return 'u';
  case State::Dead:   return 'd';
  }
  return '?';
}

#ifdef ASSERT
void Metachunk::assert_have_expand_lock() {
  assert_lock_strong(Metaspace_lock);
}
#endif

// Commit uncommitted section of the chunk.
// Fails if we hit a commit limit.
bool Metachunk::commit_up_to(size_t new_committed_words) {
  // Please note:
  //
  // VirtualSpaceNode::ensure_range_is_committed(), when called over a range containing both committed and uncommitted parts,
  // will replace the whole range with a new mapping, thus erasing the existing content in the committed parts. Therefore
  // we must make sure never to call VirtualSpaceNode::ensure_range_is_committed() over a range containing live data.
  //
  // Luckily, this cannot happen by design. We have two cases:
  //
  // 1) chunks equal or larger than a commit granule.
  //    In this case, due to chunk geometry, the chunk should cover whole commit granules (in other words, a chunk equal or larger than
  //    a commit granule will never share a granule with a neighbor). That means whatever we commit or uncommit here does not affect
  //    neighboring chunks. We only have to take care not to re-commit used parts of ourself. We do this by moving the committed_words
  //    limit in multiple of commit granules.
  //
  // 2) chunks smaller than a commit granule.
  //    In this case, a chunk shares a single commit granule with its neighbors. But this never can be a problem:
  //    - Either the commit granule is already committed (and maybe the neighbors contain live data). In that case calling
  //      ensure_range_is_committed() will do nothing.
  //    - Or the commit granule is not committed, but in this case, the neighbors are uncommitted too and cannot contain live data.

#ifdef ASSERT
  if (word_size() >= Settings::commit_granule_words()) {
    // case (1)
    assert(is_aligned(base(), Settings::commit_granule_bytes()) &&
           is_aligned(end(), Settings::commit_granule_bytes()),
           "Chunks larger than a commit granule must cover whole granules.");
    assert(is_aligned(_committed_words, Settings::commit_granule_words()),
           "The commit boundary must be aligned to commit granule size");
    assert(_used_words <= _committed_words, "Sanity");
  } else {
    // case (2)
    assert(_committed_words == 0 || _committed_words == word_size(), "Sanity");
  }
#endif

  // We should hold the expand lock at this point.
  assert_lock_strong(Metaspace_lock);

  const size_t commit_from = _committed_words;
  const size_t commit_to =   MIN2(align_up(new_committed_words, Settings::commit_granule_words()), word_size());
  assert(commit_from >= used_words(), "Sanity");
  assert(commit_to <= word_size(), "Sanity");
  if (commit_to > commit_from) {
    log_debug(metaspace)("Chunk " METACHUNK_FORMAT ": attempting to move commit line to "
                         "%zu words.", METACHUNK_FORMAT_ARGS(this), commit_to);
    if (!_vsnode->ensure_range_is_committed(base() + commit_from, commit_to - commit_from)) {
      DEBUG_ONLY(verify();)
      return false;
    }
  }

  // Remember how far we have committed.
  _committed_words = commit_to;
  DEBUG_ONLY(verify();)
  return true;
}

// Ensure that chunk is committed up to at least new_committed_words words.
// Fails if we hit a commit limit.
bool Metachunk::ensure_committed(size_t new_committed_words) {
  bool rc = true;
  if (new_committed_words > committed_words()) {
    MutexLocker cl(Metaspace_lock, Mutex::_no_safepoint_check_flag);
    rc = commit_up_to(new_committed_words);
  }
  return rc;
}

bool Metachunk::ensure_committed_locked(size_t new_committed_words) {
  // the .._locked() variant should be called if we own the lock already.
  assert_lock_strong(Metaspace_lock);
  bool rc = true;
  if (new_committed_words > committed_words()) {
    rc = commit_up_to(new_committed_words);
  }
  return rc;
}

// Uncommit chunk area. The area must be a common multiple of the
// commit granule size (in other words, we cannot uncommit chunks smaller than
// a commit granule size).
void Metachunk::uncommit() {
  MutexLocker cl(Metaspace_lock, Mutex::_no_safepoint_check_flag);
  uncommit_locked();
}

void Metachunk::uncommit_locked() {
  // Only uncommit chunks which are free, have no used words set (extra precaution) and are equal or larger in size than a single commit granule.
  assert_lock_strong(Metaspace_lock);
  assert(_state == State::Free && _used_words == 0 && word_size() >= Settings::commit_granule_words(),
         "Only free chunks equal or larger than commit granule size can be uncommitted "
         "(chunk " METACHUNK_FULL_FORMAT ").", METACHUNK_FULL_FORMAT_ARGS(this));
  if (word_size() >= Settings::commit_granule_words()) {
    _vsnode->uncommit_range(base(), word_size());
    _committed_words = 0;
  }
}
void Metachunk::set_committed_words(size_t v) {
  // Set committed words. Since we know that we only commit whole commit granules, we can round up v here.
  v = MIN2(align_up(v, Settings::commit_granule_words()), word_size());
 _committed_words = v;
}

// Allocate word_size words from this chunk (word_size must be aligned to
//  allocation_alignment_words).
//
// Caller must make sure the chunk is both large enough and committed far enough
// to hold the allocation. Will always work.
//
MetaWord* Metachunk::allocate(size_t request_word_size) {
  // Caller must have made sure this works
  assert(free_words() >= request_word_size, "Chunk too small.");
  assert(free_below_committed_words() >= request_word_size, "Chunk not committed.");
  MetaWord* const p = top();
  _used_words += request_word_size;
  SOMETIMES(verify();)
  return p;
}

#ifdef ASSERT

// Zap this structure.
void Metachunk::zap_header(uint8_t c) {
  memset(this, c, sizeof(Metachunk));
}

// Verifies linking with neighbors in virtual space.
// Can only be done under expand lock protection.
void Metachunk::verify_neighborhood() const {
  assert_lock_strong(Metaspace_lock);
  assert(!is_dead(), "Do not call on dead chunks.");
  if (is_root_chunk()) {
    // Root chunks are all alone in the world.
    assert(next_in_vs() == nullptr || prev_in_vs() == nullptr, "Root chunks should have no neighbors");
  } else {
    // Non-root chunks have neighbors, at least one, possibly two.
    assert(next_in_vs() != nullptr || prev_in_vs() != nullptr,
           "A non-root chunk should have neighbors (chunk @" PTR_FORMAT
           ", base " PTR_FORMAT ", level " CHKLVL_FORMAT ".",
           p2i(this), p2i(base()), level());
    if (prev_in_vs() != nullptr) {
      assert(prev_in_vs()->end() == base(),
             "Chunk " METACHUNK_FULL_FORMAT ": should be adjacent to predecessor: " METACHUNK_FULL_FORMAT ".",
             METACHUNK_FULL_FORMAT_ARGS(this), METACHUNK_FULL_FORMAT_ARGS(prev_in_vs()));
      assert(prev_in_vs()->next_in_vs() == this,
             "Chunk " METACHUNK_FULL_FORMAT ": broken link to left neighbor: " METACHUNK_FULL_FORMAT " (" PTR_FORMAT ").",
             METACHUNK_FULL_FORMAT_ARGS(this), METACHUNK_FULL_FORMAT_ARGS(prev_in_vs()), p2i(prev_in_vs()->next_in_vs()));
    }
    if (next_in_vs() != nullptr) {
      assert(end() == next_in_vs()->base(),
             "Chunk " METACHUNK_FULL_FORMAT ": should be adjacent to successor: " METACHUNK_FULL_FORMAT ".",
             METACHUNK_FULL_FORMAT_ARGS(this), METACHUNK_FULL_FORMAT_ARGS(next_in_vs()));
      assert(next_in_vs()->prev_in_vs() == this,
             "Chunk " METACHUNK_FULL_FORMAT ": broken link to right neighbor: " METACHUNK_FULL_FORMAT " (" PTR_FORMAT ").",
             METACHUNK_FULL_FORMAT_ARGS(this), METACHUNK_FULL_FORMAT_ARGS(next_in_vs()), p2i(next_in_vs()->prev_in_vs()));
    }

    // One of the neighbors must be the buddy. It can be whole or splintered.

    // The chunk following us or preceding us may be our buddy or a splintered part of it.
    Metachunk* buddy = is_leader() ? next_in_vs() : prev_in_vs();
    assert(buddy != nullptr, "Missing neighbor.");
    assert(!buddy->is_dead(), "Invalid buddy state.");

    // This neighbor is either or buddy (same level) or a splinter of our buddy - hence
    // the level can never be smaller (aka the chunk size cannot be larger).
    assert(buddy->level() >= level(), "Wrong level.");

    if (buddy->level() == level()) {
      // If the buddy is of the same size as us, it is unsplintered.
      assert(buddy->is_leader() == !is_leader(),
             "Only one chunk can be leader in a pair");

      // When direct buddies are neighbors, one or both should be in use, otherwise they should
      // have been merged.
      // But since we call this verification function from internal functions where we are about to merge or just did split,
      // do not test this. We have RootChunkArea::verify_area_is_ideally_merged() for testing that.
      if (is_leader()) {
        assert(buddy->base() == end(), "Sanity");
        assert(is_aligned(base(), word_size() * 2 * BytesPerWord), "Sanity");
      } else {
        assert(buddy->end() == base(), "Sanity");
        assert(is_aligned(buddy->base(), word_size() * 2 * BytesPerWord), "Sanity");
      }
    } else {
      // Buddy, but splintered, and this is a part of it.
      if (is_leader()) {
        assert(buddy->base() == end(), "Sanity");
      } else {
        assert(buddy->end() > (base() - word_size()), "Sanity");
      }
    }
  }
}

volatile MetaWord dummy = nullptr;

void Metachunk::verify() const {
  // Note. This should be called under CLD lock protection.

  // We can verify everything except the _prev_in_vs/_next_in_vs pair.
  // This is because neighbor chunks may be added concurrently, so we cannot rely
  //  on the content of _next_in_vs/_prev_in_vs unless we have the expand lock.
  assert(!is_dead(), "Do not call on dead chunks.");
  if (is_free()) {
    assert(used_words() == 0, "free chunks are not used.");
  }

  // Note: only call this on a life Metachunk.
  chunklevel::check_valid_level(level());

  assert(base() != nullptr, "No base ptr");
  assert(committed_words() >= used_words(),
         "mismatch: committed: %zu, used: %zu.",
         committed_words(), used_words());
  assert(word_size() >= committed_words(),
         "mismatch: word_size: %zu, committed: %zu.",
         word_size(), committed_words());

  // Test base pointer
  assert(base() != nullptr, "Base pointer null");
  assert(vsnode() != nullptr, "No space");
  vsnode()->check_pointer(base());

  // Starting address shall be aligned to chunk size.
  const size_t required_alignment = word_size() * sizeof(MetaWord);
  assert_is_aligned(base(), required_alignment);

  // Test accessing the committed area. But not for ASAN. We don't know which portions
  // of the chunk are still poisoned.
#if !INCLUDE_ASAN
  SOMETIMES(
    if (_committed_words > 0) {
      for (const MetaWord* p = _base; p < _base + _committed_words; p += os::vm_page_size()) {
        dummy = *p;
      }
      dummy = *(_base + _committed_words - 1);
    }
  )
#endif // !INCLUDE_ASAN
}
#endif // ASSERT

void Metachunk::print_on(outputStream* st) const {
  // Note: must also work with invalid/random data. (e.g. do not call word_size())
  st->print("Chunk @" PTR_FORMAT ", state %c, base " PTR_FORMAT ", "
            "level " CHKLVL_FORMAT " (%zu words), "
            "used %zu words, committed %zu words.",
            p2i(this), get_state_char(), p2i(base()), level(),
            (chunklevel::is_valid_level(level()) ? chunklevel::word_size_for_level(level()) : SIZE_MAX),
            used_words(), committed_words());
}

} // namespace metaspace

