/* Memory allocation aligned to system page boundaries.

   Copyright (C) 2005-2007, 2009-2026 Free Software Foundation, Inc.

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program 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 for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */

/* Written by Derek R. Price <derek@ximbiot.com>.  */

#include <config.h>

/* Specification.  */
#include "pagealign_alloc.h"

#include <errno.h>
#include <stdlib.h>

#include <fcntl.h>
#include <unistd.h>
#include <stdint.h>

#if HAVE_SYS_MMAN_H
# include <sys/mman.h>
#endif
#if defined _WIN32 && !defined __CYGWIN__
# include <malloc.h>
# define WIN32_LEAN_AND_MEAN /* avoid including junk */
# include <windows.h>
#endif

#include <error.h>
#include "gl_xmap.h"
#include "gl_hash_map.h"
#include "gettext.h"

#define _(msgid) dgettext (GNULIB_TEXT_DOMAIN, msgid)

#if HAVE_SYS_MMAN_H
/* Define MAP_FAILED for old systems which neglect to.  */
# ifndef MAP_FAILED
#  define MAP_FAILED ((void *)-1)
# endif
#endif


/* Implementation of pagealign_alloc.  */
pagealign_impl_t pagealign_impl;

/* Map:
   For PA_IMPL_MALLOC:
     aligned_ptr -> void *.
     For each memory region, we store the original pointer returned by malloc().
   For PA_IMPL_MMAP:
     aligned_ptr -> size_t.
     For each memory region, we store its size.  */
static gl_map_t page_info_map;


/* Returns the default implementation.  */
static pagealign_impl_t
get_default_impl (void)
{
  /* The default is chosen so as to
     1. (most important) not waste memory, when possible.
     2. when there is no waste of memory, avoid using the page_info_map,
        when possible.
     Regarding the amount of used memory, it was determined through the
     'bench-pagealign_alloc' program.  The results were:

                          b         c         d         e         f

       glibc            176%      101%      175%       ---       ---
       musl libc        198%      101%      197%       ---       ---
       macOS            190%      100%      195%       ---       ---
       FreeBSD          190%      100%      380%       ---       ---
       NetBSD           185%      101%      379%       ---       ---
       OpenBSD          177%      101%      101%       ---       ---
       AIX              177%      101%      176%       ---       ---
       Solaris 11.4     176%      101%      175%       ---       ---
       Cygwin           181%      100%      181%       ---       ---
       native Windows   184%       ---       ---      180%      100%
       Android          182%      101%      181%       ---       ---

     where
       b = PA_IMPL_MALLOC
       c = PA_IMPL_MMAP
       d = PA_IMPL_POSIX_MEMALIGN
       e = PA_IMPL_ALIGNED_MALLOC
       f = PA_IMPL_VIRTUAL_ALLOC
   */
#if defined _WIN32 && !defined __CYGWIN__
  /* Native Windows.  */
  return PA_IMPL_VIRTUAL_ALLOC;
#elif defined __OpenBSD__ && HAVE_POSIX_MEMALIGN
  /* On OpenBSD, we may choose among PA_IMPL_MMAP and PA_IMPL_POSIX_MEMALIGN.
     The latter does not need the page_info_map.  */
  return PA_IMPL_POSIX_MEMALIGN;
#elif HAVE_SYS_MMAN_H
  /* On all other platforms, PA_IMPL_MMAP is the only implementation that does
     not waste memory.  */
  return PA_IMPL_MMAP;
#else
  /* Old platforms without mmap: use PA_IMPL_MALLOC.  */
  return PA_IMPL_MALLOC;
#endif
}


void *
pagealign_alloc (size_t size)
{
  pagealign_impl_t impl = pagealign_impl;
  if (impl == PA_IMPL_DEFAULT)
    impl = get_default_impl ();

  void *ret;
  switch (impl)
    {
    case PA_IMPL_MALLOC:
      {
        size_t pagesize = getpagesize ();
        void *unaligned_ptr = malloc (size + pagesize - 1);
        if (unaligned_ptr == NULL)
          {
            /* Set errno.  We don't know whether malloc already set errno: some
               implementations of malloc do, some don't.  */
            errno = ENOMEM;
            return NULL;
          }
        ret = (char *) unaligned_ptr
              + ((- (uintptr_t) unaligned_ptr) & (pagesize - 1));
        if (page_info_map == NULL)
          page_info_map =
            gl_map_create_empty (GL_HASH_MAP, NULL, NULL, NULL, NULL);
        gl_map_put (page_info_map, ret, unaligned_ptr);
      }
      break;

    case PA_IMPL_MMAP:
      #if HAVE_SYS_MMAN_H
      ret = mmap (NULL, size, PROT_READ | PROT_WRITE,
                  MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
      if (ret == MAP_FAILED)
        return NULL;
      if (page_info_map == NULL)
        page_info_map =
          gl_map_create_empty (GL_HASH_MAP, NULL, NULL, NULL, NULL);
      gl_map_put (page_info_map, ret, (void *) (uintptr_t) size);
      break;
      #else
      errno = ENOSYS;
      return NULL;
      #endif

    case PA_IMPL_POSIX_MEMALIGN:
      #if HAVE_POSIX_MEMALIGN
      {
        int status = posix_memalign (&ret, getpagesize (), size);
        if (status)
          {
            errno = status;
            return NULL;
          }
      }
      break;
      #else
      errno = ENOSYS;
      return NULL;
      #endif

    case PA_IMPL_ALIGNED_MALLOC:
      #if defined _WIN32 && !defined __CYGWIN__
      /* Documentation:
         <https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/aligned-malloc>  */
      return _aligned_malloc (size, getpagesize ());
      #else
      errno = ENOSYS;
      return NULL;
      #endif

    case PA_IMPL_VIRTUAL_ALLOC:
      #if defined _WIN32 && !defined __CYGWIN__
      /* Documentation:
         <https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc>  */
      ret = VirtualAlloc (NULL, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
      if (ret == NULL)
        {
          errno = ENOMEM;
          return NULL;
        }
      break;
      #else
      errno = ENOSYS;
      return NULL;
      #endif

    default:
      abort ();
    }

  return ret;
}

void *
pagealign_xalloc (size_t size)
{
  void *ret = pagealign_alloc (size);
  if (ret == NULL)
    xalloc_die ();
  return ret;
}


void
pagealign_free (void *aligned_ptr)
{
  pagealign_impl_t impl = pagealign_impl;
  if (impl == PA_IMPL_DEFAULT)
    impl = get_default_impl ();

  switch (impl)
    {
    case PA_IMPL_MALLOC:
      {
        const void *value;
        if (page_info_map == NULL
            || !gl_map_getremove (page_info_map, aligned_ptr, &value))
          abort ();
        void *unaligned_ptr = (void *) value;
        free (unaligned_ptr);
      }
      break;

    case PA_IMPL_MMAP:
      #if HAVE_SYS_MMAN_H
      {
        const void *value;
        if (page_info_map == NULL
            || !gl_map_getremove (page_info_map, aligned_ptr, &value))
          abort ();
        if (munmap (aligned_ptr, (size_t) (uintptr_t) value) < 0)
          error (EXIT_FAILURE, errno, "Failed to unmap memory");
      }
      break;
      #else
      abort ();
      #endif

    case PA_IMPL_POSIX_MEMALIGN:
      #if HAVE_POSIX_MEMALIGN
      free (aligned_ptr);
      break;
      #else
      abort ();
      #endif

    case PA_IMPL_ALIGNED_MALLOC:
      #if defined _WIN32 && !defined __CYGWIN__
      /* Documentation:
         <https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/aligned-free>  */
      _aligned_free (aligned_ptr);
      break;
      #else
      abort ();
      #endif

    case PA_IMPL_VIRTUAL_ALLOC:
      #if defined _WIN32 && !defined __CYGWIN__
      /* Documentation:
         <https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualfree>  */
      if (!VirtualFree (aligned_ptr, 0, MEM_RELEASE))
        error (EXIT_FAILURE, 0, "Failed to free memory");
      break;
      #else
      abort ();
      #endif

    default:
      abort ();
    }
}
