/*
 * Copyright (c) 2021, 2022 SAP SE. All rights reserved.
 * Copyright (c) 2021, 2025, 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 "jvm_io.h"
#include "memory/allocation.hpp"
#include "nmt/nmtPreInit.hpp"
#include "runtime/os.hpp"
#include "utilities/debug.hpp"
#include "utilities/ostream.hpp"
#include "unittest.hpp"

// This tests the NMTPreInitAllocationTable hash table used to store C-heap allocations before NMT initialization ran.

static size_t small_random_nonzero_size() {
  // We keep the sizes random but not too random; the more regular the sizes, the
  // more regular the malloc return pointers and the better we see how our hash
  // function copes in the NMT preinit lu table.
  switch (os::random() % 4) {
  case 0: return 0x10;
  case 1: return 0x42;
  case 2: return 0x20;
  case 3: return 0x80;
  }
  return 1;
}

//#define VERBOSE

static void print_and_check_table(NMTPreInitAllocationTable& table, int expected_num_entries) {
  char tmp[256];
  stringStream ss(tmp, sizeof(tmp));
  char expected[256];
  jio_snprintf(expected, sizeof(expected), "entries: %d", expected_num_entries);
  table.print_state(&ss);
  EXPECT_EQ(::strncmp(tmp, expected, strlen(expected)), 0);
#ifdef VERBOSE
  tty->print_raw(tmp);
  tty->cr();
#endif
  DEBUG_ONLY(table.verify();)
}

TEST_VM(NMTPreInit, stress_test_map) {
  NMTPreInitAllocationTable table;
  const int num_allocs = 32 * K; // about factor 100 more than normally expected
  NMTPreInitAllocation** allocations = NEW_C_HEAP_ARRAY(NMTPreInitAllocation*, num_allocs, mtTest);

  // Fill table with allocations
  for (int i = 0; i < num_allocs; i++) {
    NMTPreInitAllocation* a = NMTPreInitAllocation::do_alloc(small_random_nonzero_size());
    table.add(a);
    allocations[i] = a;
  }

  print_and_check_table(table, num_allocs);

  // look them all up
  for (int i = 0; i < num_allocs; i++) {
    const NMTPreInitAllocation* a = table.find(allocations[i]->payload);
    ASSERT_EQ(a, allocations[i]);
  }

  // Randomly realloc
  for (int j = 0; j < num_allocs/2; j++) {
    int pos = os::random() % num_allocs;
    NMTPreInitAllocation* a1 = allocations[pos];
    NMTPreInitAllocation* a2 = table.find_and_remove(a1->payload);
    ASSERT_EQ(a1, a2);
    NMTPreInitAllocation* a3 = NMTPreInitAllocation::do_reallocate(a2, small_random_nonzero_size());
    table.add(a3);
    allocations[pos] = a3;
  }

  print_and_check_table(table, num_allocs);

  // look them all up
  for (int i = 0; i < num_allocs; i++) {
    const NMTPreInitAllocation* a = table.find(allocations[i]->payload);
    ASSERT_EQ(a, allocations[i]);
  }

  // free all
  for (int i = 0; i < num_allocs; i++) {
    NMTPreInitAllocation* a = table.find_and_remove(allocations[i]->payload);
    ASSERT_EQ(a, allocations[i]);
    NMTPreInitAllocation::do_free(a);
    allocations[i] = nullptr;
  }

  print_and_check_table(table, 0);

  FREE_C_HEAP_ARRAY(allocations);
}

#ifdef ASSERT
// Test that we will assert if the lookup table is seriously over-booked.
TEST_VM_ASSERT_MSG(NMTPreInit, assert_on_lu_table_overflow, ".*NMT preinit lookup table degenerated.*") {
  NMTPreInitAllocationTable table;
  const int num_allocs = 400 * 1000; // anything above ~250K entries should trigger the assert (note: normal number of entries is ~500)
  for (int i = 0; i < num_allocs; i++) {
    NMTPreInitAllocation* a = NMTPreInitAllocation::do_alloc(1);
    table.add(a);
  }
#ifdef VERBOSE
  table.print_state(tty);
  tty->cr();
#endif
  table.verify();
}
#endif // ASSERT
