/*
 * Copyright (c) 2023, Red Hat, Inc. All rights reserved.
 * Copyright (c) 2023, Oracle and/or its affiliates. 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 "precompiled.hpp"
#include "logging/log.hpp"
#include "memory/resourceArea.hpp"
#include "runtime/os.hpp"
#include "utilities/align.hpp"
#include "utilities/globalDefinitions.hpp"
#include "utilities/macros.hpp"
#include "utilities/resourceHash.hpp"

#define LOG_PLEASE
#include "testutils.hpp"
#include "unittest.hpp"

// On AIX, these tests make no sense as long as JDK-8315321 remains unfixed since the attach
// addresses are not predictable.
#ifndef AIX

// Must be the same as in os::attempt_reserve_memory_between()
struct ARMB_constants {
  static constexpr uintptr_t absolute_max = NOT_LP64(G * 3) LP64_ONLY(G * 128 * 1024);
  static constexpr unsigned max_attempts = 32;
  static constexpr unsigned min_random_value_range = 16;
  static constexpr unsigned total_shuffle_threshold = 1024;
};

// Testing os::attempt_reserve_memory_between()

static void release_if_needed(char* p, size_t s) {
  if (p != nullptr) {
    os::release_memory(p, s);
  }
}

#define ERRINFO "addr: " << ((void*)addr) << " min: " << ((void*)min) << " max: " << ((void*)max) \
                 << " bytes: " << bytes << " alignment: " << alignment << " randomized: " << randomized

static char* call_attempt_reserve_memory_between(char* min, char* max, size_t bytes, size_t alignment, bool randomized) {
  char* const  addr = os::attempt_reserve_memory_between(min, max, bytes, alignment, randomized);
  if (addr != nullptr) {
    EXPECT_TRUE(is_aligned(addr, alignment)) << ERRINFO;
    EXPECT_TRUE(is_aligned(addr, os::vm_allocation_granularity())) << ERRINFO;
    EXPECT_LE(addr, max - bytes) << ERRINFO;
    EXPECT_LE(addr, (char*)ARMB_constants::absolute_max - bytes) << ERRINFO;
    EXPECT_GE(addr, min) << ERRINFO;
    EXPECT_GE(addr, (char*)os::vm_min_address()) << ERRINFO;
  }
  return addr;
}

class Expect {
  const bool _expect_success;
  const bool _expect_failure;
  const char* const _expected_result; // if _expect_success
public:
  Expect(bool expect_success, bool expect_failure, char* expected_result)
    : _expect_success(expect_success), _expect_failure(expect_failure), _expected_result(expected_result)
  {
    assert(!expect_success || !expect_failure, "make up your mind");
  }
  bool check_reality(char* result) const {
    if (_expect_failure) {
      return result == nullptr;
    }
    if (_expect_success) {
      return (_expected_result == nullptr) ? result != nullptr : result == _expected_result;
    }
    return true;
  }
  static Expect failure()           { return Expect(false, true, nullptr); }
  static Expect success_any()       { return Expect(true, false, nullptr); }
  static Expect success(char* addr) { return Expect(true, false, addr); }
  static Expect dontcare()          { return Expect(false, false, nullptr); }
};

static void test_attempt_reserve_memory_between(char* min, char* max, size_t bytes, size_t alignment, bool randomized,
                                                Expect expectation, int line = -1) {
  char* const addr = call_attempt_reserve_memory_between(min, max, bytes, alignment, randomized);
  EXPECT_TRUE(expectation.check_reality(addr)) << ERRINFO << " L" << line;
  release_if_needed(addr, bytes);
}
#undef ERRINFO

// Helper for attempt_reserve_memory_between tests to
// reserve an area with a hole in the middle
struct SpaceWithHole {
  char* _base;
  const size_t _len;
  const size_t _hole_offset;
  const size_t _hole_size;


  static constexpr size_t _p1_offset = 0;
  const size_t _p1_size;
  const size_t _p2_offset;
  const size_t _p2_size;

  char* _p1;
  char* _p2;

  size_t p1size() const { return hole_offset(); }
  size_t p2size() const { return _len - hole_size() - hole_offset(); }

public:

  char* base() const          { return _base; }
  char* end() const           { return _base + _len; }
  char* hole() const          { return _base + hole_offset(); }
  char* hole_end() const      { return hole() + hole_size(); }

  size_t hole_size() const    { return _hole_size; }
  size_t hole_offset() const  { return _hole_offset; }

  SpaceWithHole(size_t total_size, size_t hole_offset, size_t hole_size) :
    _base(nullptr), _len(total_size), _hole_offset(hole_offset), _hole_size(hole_size),
    _p1_size(hole_offset), _p2_offset(hole_offset + hole_size), _p2_size(total_size - hole_offset - hole_size),
    _p1(nullptr), _p2(nullptr)
  {
    assert(_p1_size > 0 && _p2_size > 0, "Cannot have holes at the border");
  }

  bool reserve() {
    // We cannot create a hole by punching, since NMT cannot cope with releases
    // crossing reservation boundaries. Therefore we first reserve the total,
    // release it again, reserve the parts.
    for (int i = 56; _base == nullptr && i > 32; i--) {
      // We reserve at weird outlier addresses, in order to minimize the chance of concurrent mmaps grabbing
      // the hole.
      const uintptr_t candidate = nth_bit(i);
      if ((candidate + _len) <= ARMB_constants::absolute_max) {
        _base = os::attempt_reserve_memory_at((char*)candidate, _len);
      }
    }
    if (_base == nullptr) {
      return false;
    }
    // Release total mapping, remap the individual non-holy parts
    os::release_memory(_base, _len);
    _p1 = os::attempt_reserve_memory_at(_base + _p1_offset, _p1_size);
    _p2 = os::attempt_reserve_memory_at(_base + _p2_offset, _p2_size);
    if (_p1 == nullptr || _p2 == nullptr) {
      return false;
    }
    LOG_HERE("SpaceWithHole: [" PTR_FORMAT " ... [" PTR_FORMAT " ... " PTR_FORMAT ") ... " PTR_FORMAT ")",
             p2i(base()), p2i(hole()), p2i(hole_end()), p2i(end()));
    return true;
  }

  ~SpaceWithHole() {
    release_if_needed(_p1, _p1_size);
    release_if_needed(_p2, _p2_size);
  }
};

// Test that, when reserving in a range randomly, we get random results
static void test_attempt_reserve_memory_between_random_distribution(unsigned num_possible_attach_points) {

  const size_t ag = os::vm_allocation_granularity();

  // Create a space that is mostly a hole bordered by two small stripes of reserved memory, with
  // as many attach points as we need.
  SpaceWithHole space((2 + num_possible_attach_points) * ag, ag, num_possible_attach_points * ag);
  if (!space.reserve()) {
    tty->print_cr("Failed to reserve holed space, skipping.");
    return;
  }

  const size_t bytes = ag;
  const size_t alignment = ag;

  // Below this threshold the API should never return memory since the randomness is too weak.
  const bool expect_failure = (num_possible_attach_points < ARMB_constants::min_random_value_range);

  // Below this threshold we expect values to be completely random, otherwise they randomized but still ordered.
  const bool total_shuffled = (num_possible_attach_points < ARMB_constants::total_shuffle_threshold);

  // Allocate n times within that hole (with subsequent deletions) and remember unique addresses returned.
  constexpr unsigned num_tries_per_attach_point = 100;
  ResourceMark rm;
  ResourceHashtable<char*, unsigned> ht;
  const unsigned num_tries = expect_failure ? 3 : (num_possible_attach_points * num_tries_per_attach_point);
  unsigned num_uniq = 0; // Number of uniq addresses returned

  // In "total shuffle" mode, all possible attach points are randomized; outside that mode, the API
  // attempts to limit fragmentation by favouring the ends of the ranges.
  const unsigned expected_variance =
    total_shuffled ? num_possible_attach_points : (num_possible_attach_points / ARMB_constants::max_attempts);

  // Its not easy to find a good threshold for automated tests to test randomness
  // that rules out intermittent errors. We apply a generous fudge factor.
  constexpr double fudge_factor = 0.25f;
  const unsigned expected_variance_with_fudge = MAX2(2u, (unsigned)((double)expected_variance * fudge_factor));

#define ERRINFO " num_possible_attach_points: " << num_possible_attach_points << " total_shuffle? " << total_shuffled \
                << " expected variance: " << expected_variance << " with fudge: " << expected_variance_with_fudge \
                << " alignment: " << alignment << " bytes: " << bytes;

  for (unsigned i = 0; i < num_tries &&
       num_uniq < expected_variance_with_fudge; // Stop early if we confirmed enough variance.
       i ++) {
    char* p = call_attempt_reserve_memory_between(space.base(), space.end(), bytes, alignment, true);
    if (p != nullptr) {
      ASSERT_GE(p, space.hole()) << ERRINFO;
      ASSERT_LE(p + bytes, space.hole_end()) << ERRINFO;
      release_if_needed(p, bytes);
      bool created = false;
      unsigned* num = ht.put_if_absent(p, 0, &created);
      (*num) ++;
      num_uniq = (unsigned)ht.number_of_entries();
    }
  }

  ASSERT_LE(num_uniq, num_possible_attach_points) << num_uniq << ERRINFO;

  if (!expect_failure) {
    ASSERT_GE(num_uniq, expected_variance_with_fudge) << ERRINFO;
  }
#undef ERRINFO
}

#define RANDOMIZED_RANGE_TEST(num) \
  TEST_VM(os, attempt_reserve_memory_between_random_distribution_ ## num ## _attach_points) { \
    test_attempt_reserve_memory_between_random_distribution(num); \
}

RANDOMIZED_RANGE_TEST(2)
RANDOMIZED_RANGE_TEST(15)
RANDOMIZED_RANGE_TEST(16)
RANDOMIZED_RANGE_TEST(712)
RANDOMIZED_RANGE_TEST(12000)

// Test that, given a smallish range - not many attach points - with a hole, we attach within that hole.
TEST_VM(os, attempt_reserve_memory_randomization_threshold) {

  constexpr int threshold = ARMB_constants::min_random_value_range;
  const size_t ps = os::vm_page_size();
  const size_t ag = os::vm_allocation_granularity();

  SpaceWithHole space(ag * (threshold + 2), ag, ag * threshold);
  if (!space.reserve()) {
    tty->print_cr("Failed to reserve holed space, skipping.");
    return;
  }

  // Test with a range that only allows for (threshold - 1) reservations
  test_attempt_reserve_memory_between(space.hole(), space.hole_end() - ag, ps, ag, true, Expect::failure());

  // Test with a range just above the threshold. Should succeed.
  test_attempt_reserve_memory_between(space.hole(), space.hole_end(), ps, ag, true, Expect::success_any());
}

// Test all possible combos
TEST_VM(os, attempt_reserve_memory_between_combos) {
  const size_t large_end = NOT_LP64(G) LP64_ONLY(64 * G);
  for (size_t range_size = os::vm_allocation_granularity(); range_size <= large_end; range_size *= 2) {
    for (size_t start_offset = 0; start_offset <= large_end; start_offset += (large_end / 2)) {
      char* const min = (char*)(uintptr_t)start_offset;
      char* const max = min + range_size;
      for (size_t bytes = os::vm_page_size(); bytes < large_end; bytes *= 2) {
        for (size_t alignment = os::vm_allocation_granularity(); alignment < large_end; alignment *= 2) {
          test_attempt_reserve_memory_between(min, max, bytes, alignment, true, Expect::dontcare(), __LINE__);
          test_attempt_reserve_memory_between(min, max, bytes, alignment, false, Expect::dontcare(), __LINE__);
        }
      }
    }
  }
}

TEST_VM(os, attempt_reserve_memory_randomization_cornercases) {
  const size_t ps = os::vm_page_size();
  const size_t ag = os::vm_allocation_granularity();
  constexpr size_t quarter_address_space = NOT_LP64(nth_bit(30)) LP64_ONLY(nth_bit(62));

  // Zero-sized range
  test_attempt_reserve_memory_between(nullptr, nullptr, ps, ag, false, Expect::failure());
  test_attempt_reserve_memory_between((char*)(3 * G), (char*)(3 * G), ps, ag, false, Expect::dontcare(), __LINE__);
  test_attempt_reserve_memory_between((char*)SIZE_MAX, (char*)SIZE_MAX, ps, ag, false, Expect::failure(), __LINE__);

  test_attempt_reserve_memory_between(nullptr, nullptr, ps, ag, true, Expect::failure());
  test_attempt_reserve_memory_between((char*)(3 * G), (char*)(3 * G), ps, ag, true, Expect::dontcare(), __LINE__);
  test_attempt_reserve_memory_between((char*)(3 * G), (char*)(3 * G), ps, ag, true, Expect::dontcare(), __LINE__);
  test_attempt_reserve_memory_between((char*)SIZE_MAX, (char*)SIZE_MAX, ps, ag, true, Expect::failure(), __LINE__);

  // Full size
  // Note: paradoxically, success is not guaranteed here, since a significant portion of the attach points
  // could be located in un-allocatable territory.
  test_attempt_reserve_memory_between(nullptr, (char*)SIZE_MAX, ps, quarter_address_space / 8, false, Expect::dontcare(), __LINE__);
  test_attempt_reserve_memory_between(nullptr, (char*)SIZE_MAX, ps, quarter_address_space / 8, true, Expect::dontcare(), __LINE__);

  // Very small range at start
  test_attempt_reserve_memory_between(nullptr, (char*)ag, ps, ag, false, Expect::dontcare(), __LINE__);
  test_attempt_reserve_memory_between(nullptr, (char*)ag, ps, ag, true, Expect::dontcare(), __LINE__);

  // Very small range at end
  test_attempt_reserve_memory_between((char*)(SIZE_MAX - (ag * 2)), (char*)(SIZE_MAX), ps, ag, false, Expect::dontcare(), __LINE__);
  test_attempt_reserve_memory_between((char*)(SIZE_MAX - (ag * 2)), (char*)(SIZE_MAX), ps, ag, true, Expect::dontcare(), __LINE__);

  // At start, high alignment, check if we run into neg. overflow problems
  test_attempt_reserve_memory_between(nullptr, (char*)G, ps, G, false, Expect::dontcare(), __LINE__);
  test_attempt_reserve_memory_between(nullptr, (char*)G, ps, G, true, Expect::dontcare(), __LINE__);

  // At start, very high alignment, check if we run into neg. overflow problems
  test_attempt_reserve_memory_between((char*)quarter_address_space, (char*)SIZE_MAX, ps, quarter_address_space, false, Expect::dontcare(), __LINE__);
  test_attempt_reserve_memory_between((char*)quarter_address_space, (char*)SIZE_MAX, ps, quarter_address_space, true, Expect::dontcare(), __LINE__);
}

// Test that, regardless where the hole is in the [min, max) range, if we probe nonrandomly, we will fill that hole
// as long as the range size is smaller than the number of probe attempts
TEST_VM(os, attempt_reserve_memory_between_small_range_fill_hole) {
  const size_t ps = os::vm_page_size();
  const size_t ag = os::vm_allocation_granularity();
  constexpr int num = ARMB_constants::max_attempts;
  for (int i = 0; i < num; i ++) {
    SpaceWithHole space(ag * (num + 2), ag * (i + 1), ag);
    if (!space.reserve()) {
      tty->print_cr("Failed to reserve holed space, skipping.");
    } else {
      test_attempt_reserve_memory_between(space.base() + ag, space.end() - ag, space.hole_size(), space.hole_size(), false, Expect::success(space.hole()), __LINE__);
    }
  }
}

#endif // AIX