In new gnulib code, I need to allocate memory blocks through malloc() but with
a 16-bytes alignment.

The posix_memalign and memalign functions only help on those platforms where
they exist. It's not possible to emulate posix_memalign or memalign when they
are not present, because when malloc() returned p, we can call free (p)
but not free (p+4) or free (p+8) or similar.

Gnulib has a module 'pagealign_alloc' but it produces a getpagesize() alignment
- not useful (very wasteful) for a 16-bytes alignment.

So here is a module that provides aligned_malloc() and aligned_free().

The alignment is given at compile-time, so that when the desired alignment is
<= the alignment guaranteed by malloc(), there is no overhead. (With an
alignment given at runtime, the aligned_free() function would have to fetch
a back-pointer in all cases, and that means wasting sizeof (void *) bytes
in the case where the desired alignment is small.)


2020-07-21  Bruno Haible  <br...@clisp.org>

        aligned-malloc: Add tests.
        * tests/test-aligned-malloc.c: New file.
        * modules/aligned-malloc-tests: New file.

        aligned-malloc: New module.
        * lib/aligned-malloc.h: New file.
        * m4/malloc-align.m4: New file.
        * modules/aligned-malloc: New file.
        * doc/posix-functions/posix_memalign.texi: Mention the new module.
        * doc/glibc-functions/memalign.texi: Likewise.

>From 8ce76ed581d7ad000c8d72d9fe759929d653f171 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Tue, 21 Jul 2020 10:03:42 +0200
Subject: [PATCH 1/2] aligned-malloc: New module.

* lib/aligned-malloc.h: New file.
* m4/malloc-align.m4: New file.
* modules/aligned-malloc: New file.
* doc/posix-functions/posix_memalign.texi: Mention the new module.
* doc/glibc-functions/memalign.texi: Likewise.
---
 ChangeLog                               |   9 ++
 doc/glibc-functions/memalign.texi       |   3 +
 doc/posix-functions/posix_memalign.texi |   7 +-
 lib/aligned-malloc.h                    | 140 ++++++++++++++++++++++++++++++++
 m4/malloc-align.m4                      | 140 ++++++++++++++++++++++++++++++++
 modules/aligned-malloc                  |  25 ++++++
 6 files changed, 322 insertions(+), 2 deletions(-)
 create mode 100644 lib/aligned-malloc.h
 create mode 100644 m4/malloc-align.m4
 create mode 100644 modules/aligned-malloc

diff --git a/ChangeLog b/ChangeLog
index 654ad7c..768b209 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,14 @@
 2020-07-21  Bruno Haible  <br...@clisp.org>
 
+	aligned-malloc: New module.
+	* lib/aligned-malloc.h: New file.
+	* m4/malloc-align.m4: New file.
+	* modules/aligned-malloc: New file.
+	* doc/posix-functions/posix_memalign.texi: Mention the new module.
+	* doc/glibc-functions/memalign.texi: Likewise.
+
+2020-07-21  Bruno Haible  <br...@clisp.org>
+
 	inttypes: Fix PRI*PTR and SCN*PTR on 64-bit native Windows.
 	* m4/inttypes.m4 (gl_INTTYPES_PRI_SCN): On 64-bit native Windows, make
 	sure PRIPTR_PREFIX is defined to "ll", not "l".
diff --git a/doc/glibc-functions/memalign.texi b/doc/glibc-functions/memalign.texi
index a7e9715..0740f73 100644
--- a/doc/glibc-functions/memalign.texi
+++ b/doc/glibc-functions/memalign.texi
@@ -27,3 +27,6 @@ Portability problems not fixed by Gnulib:
 This function is missing on some platforms:
 Mac OS X 10.5, FreeBSD 6.0, NetBSD 5.0, OpenBSD 3.8, Minix 3.1.8, AIX 5.1, HP-UX 11.00, mingw, MSVC 14.
 @end itemize
+
+The Gnulib module @code{aligned-malloc} provides functions for
+allocating and freeing blocks of suitably aligned memory.
diff --git a/doc/posix-functions/posix_memalign.texi b/doc/posix-functions/posix_memalign.texi
index 53abc86..084841b 100644
--- a/doc/posix-functions/posix_memalign.texi
+++ b/doc/posix-functions/posix_memalign.texi
@@ -17,5 +17,8 @@ This function is missing on some platforms:
 Mac OS X 10.5, FreeBSD 6.0, NetBSD 3.0, OpenBSD 3.8, Minix 3.1.8, AIX 5.1, HP-UX 11, IRIX 6.5, Solaris 10, Cygwin 1.5.x, mingw, MSVC 14, Android 4.1.
 @end itemize
 
-The Gnulib module @code{pagealign_alloc} provides a similar API
-that returns memory aligned on a system page boundary.
+The Gnulib module @code{aligned-malloc} provides functions for
+allocating and freeing blocks of suitably aligned memory.
+
+The Gnulib module @code{pagealign_alloc} provides a similar API for
+allocating and freeing blocks of memory aligned on a system page boundary.
diff --git a/lib/aligned-malloc.h b/lib/aligned-malloc.h
new file mode 100644
index 0000000..86382fd
--- /dev/null
+++ b/lib/aligned-malloc.h
@@ -0,0 +1,140 @@
+/* Allocate memory with indefinite extent and specified alignment.
+
+   Copyright (C) 2020 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 Bruno Haible <br...@clisp.org>, 2020.  */
+
+/* Before including this file, you need to define the following macro:
+
+   ALIGNMENT      A constant expression that evaluates to the desired alignment
+                  (a power of 2).
+
+   And you also need to #include <stdint.h> and <stdlib.h>.  */
+
+/* aligned_malloc allocates a block of memory of SIZE bytes, aligned on a
+   boundary of ALIGNMENT bytes.
+   The block can be freed through aligned_free(), NOT through free().
+   Upon failure, it returns NULL.  */
+
+/* This module exists instead of a posix_memalign() or memalign() emulation,
+   because we can't reasonably emulate posix_memalign() or memalign():
+   If malloc() returned p, only free (p) is allowed, not free (p + 1),
+   free (p + 2), free (p + 4), free (p + 8), or similar.
+
+   We can use posix_memalign().  On older systems, we can alternatively use
+   memalign() instead.  In the Solaris documentation of memalign() it is not
+   specified how a memory block returned by memalign() can be freed, but
+   it actually can be freed with free().  */
+
+#if ((ALIGNMENT) <= MALLOC_ALIGNMENT) || HAVE_POSIX_MEMALIGN || HAVE_MEMALIGN
+
+# if (ALIGNMENT) <= MALLOC_ALIGNMENT
+/* Simply use malloc.  */
+
+#  ifdef aligned_malloc
+   /* The caller wants an inline function, not a macro.  */
+static inline void *
+aligned_malloc (size_t size)
+{
+  return malloc (size);
+}
+#  else
+#   define aligned_malloc malloc
+#  endif
+
+# elif HAVE_POSIX_MEMALIGN
+/* Use posix_memalign.
+   This is OK since ALIGNMENT > MALLOC_ALIGNMENT >= sizeof (void *).  */
+
+static inline void *
+aligned_malloc (size_t size)
+{
+  void *p;
+  int ret = posix_memalign (&p, (ALIGNMENT), size);
+  if (ret == 0)
+    return p;
+  else
+    return NULL;
+}
+
+# elif HAVE_MEMALIGN                    /* HP-UX, IRIX, Solaris <= 10 */
+/* Use memalign.  */
+
+static inline void *
+aligned_malloc (size_t size)
+{
+  return memalign ((ALIGNMENT), size);
+}
+
+# endif
+
+# ifdef aligned_free
+   /* The caller wants an inline function, not a macro.  */
+static inline void
+aligned_free (void *q)
+{
+  free (q);
+}
+# else
+#  define aligned_free free
+# endif
+
+#else
+/* Use malloc and waste a bit of memory.  */
+
+static inline void *
+aligned_malloc (size_t size)
+{
+  size += (ALIGNMENT);
+  if (size >= (ALIGNMENT)) /* no overflow? */
+    {
+      void *p = malloc (size);
+      if (p != NULL)
+        {
+          /* Go to the next multiple of ALIGNMENT.  */
+          void *q =
+            (void *) (((uintptr_t) p + (ALIGNMENT)) & -(intptr_t)(ALIGNMENT));
+          /* Now q - p <= ALIGNMENT and
+             q - p >= MALLOC_ALIGNMENT >= sizeof (void *).
+             This is enough to store a back pointer to p.  */
+          ((void **) q)[-1] = p;
+          return q;
+        }
+    }
+  return NULL;
+}
+
+static inline void
+aligned_free (void *q)
+{
+  if (q != NULL)
+    {
+      if ((uintptr_t) q & ((ALIGNMENT) - 1))
+        /* Argument not aligned as expected.  */
+        abort ();
+      else
+        {
+          void *p = ((void **) q)[-1];
+          if (!((uintptr_t) p <= (uintptr_t) q
+                && (uintptr_t) q - (uintptr_t) p >= MALLOC_ALIGNMENT
+                && (uintptr_t) q - (uintptr_t) p <= (ALIGNMENT)))
+            abort ();
+          free (p);
+        }
+    }
+}
+
+#endif
diff --git a/m4/malloc-align.m4 b/m4/malloc-align.m4
new file mode 100644
index 0000000..7ee8343
--- /dev/null
+++ b/m4/malloc-align.m4
@@ -0,0 +1,140 @@
+# malloc-align.m4 serial 1
+dnl Copyright (C) 2020 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+dnl Defines a C macro MALLOC_ALIGNMENT, whose value is a numeric constant,
+dnl a power of 2, with the property that
+dnl   (uintptr_t) malloc (N)
+dnl is always guaranteed to be a multiple of MALLOC_ALIGNMENT.
+
+AC_DEFUN([gl_MALLOC_ALIGNMENT],
+[
+  AC_REQUIRE([AC_CANONICAL_HOST])
+  AC_CACHE_CHECK([for the malloc() alignment],
+    [gl_cv_malloc_alignment],
+    [gl_cv_malloc_alignment=
+     AC_RUN_IFELSE(
+       [AC_LANG_PROGRAM(
+          [[#include <stdio.h>
+            #include <stdlib.h>
+            #if defined _WIN32 && !defined __CYGWIN__
+            # include <inttypes.h>
+            /* uintptr_t is equivalent to 'unsigned long long' if _WIN64,
+               or to 'unsigned long' otherwise.  */
+            #else
+            # undef uintptr_t
+            # define uintptr_t unsigned long
+            #endif
+          ]],
+          [[FILE *fp = fopen ("conftest.out", "w");
+            if (fp == NULL)
+              return 1;
+            {
+              uintptr_t bits = 0;
+              bits |= (uintptr_t) malloc (1);
+              bits |= (uintptr_t) malloc (1);
+              bits |= (uintptr_t) malloc (1);
+              bits |= (uintptr_t) malloc (2);
+              bits |= (uintptr_t) malloc (2);
+              bits |= (uintptr_t) malloc (2);
+              bits |= (uintptr_t) malloc (3);
+              bits |= (uintptr_t) malloc (3);
+              bits |= (uintptr_t) malloc (3);
+              bits |= (uintptr_t) malloc (5);
+              bits |= (uintptr_t) malloc (8);
+              bits |= (uintptr_t) malloc (8);
+              bits |= (uintptr_t) malloc (13);
+              bits |= (uintptr_t) malloc (13);
+              bits |= (uintptr_t) malloc (19);
+              bits |= (uintptr_t) malloc (19);
+              bits |= (uintptr_t) malloc (28);
+              bits |= (uintptr_t) malloc (28);
+              bits |= (uintptr_t) malloc (37);
+              bits |= (uintptr_t) malloc (37);
+              bits |= (uintptr_t) malloc (73);
+              bits |= (uintptr_t) malloc (73);
+              bits |= (uintptr_t) malloc (117);
+              bits |= (uintptr_t) malloc (117);
+              bits |= (uintptr_t) malloc (351);
+              bits |= (uintptr_t) malloc (351);
+              bits |= (uintptr_t) malloc (914);
+              bits |= (uintptr_t) malloc (914);
+              bits |= (uintptr_t) malloc (1712);
+              bits |= (uintptr_t) malloc (1712);
+              bits |= (uintptr_t) malloc (4021);
+              bits |= (uintptr_t) malloc (4021);
+              bits |= (uintptr_t) malloc (7641);
+              bits |= (uintptr_t) malloc (7641);
+              bits |= (uintptr_t) malloc (17027);
+              bits |= (uintptr_t) malloc (17027);
+              bits |= (uintptr_t) malloc (81231);
+              bits |= (uintptr_t) malloc (81231);
+              fprintf (fp, "%u\n", (unsigned int) (((bits ^ (bits - 1)) + 1) >> 1));
+            }
+            if (fclose (fp) != 0)
+              return 2;
+            return 0;
+          ]])
+       ],
+       [gl_cv_malloc_alignment=`cat conftest.out`],
+       [gl_cv_malloc_alignment="unknown"],
+       [dnl When cross-compiling, guess a value. Note that it's OK to return
+        dnl a smaller value (e.g. 4 instead of 8 or 16).
+        gl_cv_malloc_alignment="unknown"
+        case "$host_os" in
+          linux* | mingw*)
+            dnl On Linux:
+            dnl - It's 8 on most 32-bit platforms, except 16 on x86_64-x32 and
+            dnl   (with newer versions of glibc) on i386 and powerpc.  8 is a
+            dnl   safe guess.
+            dnl - It's 16 on all 64-bit platforms.
+            dnl On Windows: It's 8 on 32-bit Windows, 16 on 64-bit Windows.
+            for nn in 4 8 16 32; do
+              AC_COMPILE_IFELSE(
+                [AC_LANG_PROGRAM([[
+                   #define MALLOC_ALIGN (2 * sizeof (void *))
+                   int test [MALLOC_ALIGN <= $nn ? 1 : -1];
+                   ]])
+                ],
+                [gl_cv_malloc_alignment="guessing $nn"
+                 break
+                ],
+                [:])
+            done
+            ;;
+          *)
+            dnl If we don't know, assume the worst.
+            dnl This minimum is e.g. reached on NetBSD/i386 and NetBSD/sparc.
+            for nn in 4 8 16; do
+              AC_COMPILE_IFELSE(
+                [AC_LANG_PROGRAM([[
+                   #define MALLOC_ALIGN (sizeof (void *))
+                   int test [MALLOC_ALIGN <= $nn ? 1 : -1];
+                   ]])
+                ],
+                [gl_cv_malloc_alignment="guessing $nn"
+                 break
+                ],
+                [:])
+            done
+            ;;
+        esac
+       ])
+    ])
+  case "$gl_cv_malloc_alignment" in
+    "unknown")
+      dnl Assume the worst.
+      value=4
+    ;;
+    "guessing "*)
+      value=`echo "$gl_cv_malloc_alignment" | sed -e 's/guessing //'`
+      ;;
+    *)
+      value="$gl_cv_malloc_alignment"
+      ;;
+  esac
+  AC_DEFINE_UNQUOTED([MALLOC_ALIGNMENT], [$value],
+    [Define to the guaranteed alignment of malloc() return values.])
+])
diff --git a/modules/aligned-malloc b/modules/aligned-malloc
new file mode 100644
index 0000000..29483d8
--- /dev/null
+++ b/modules/aligned-malloc
@@ -0,0 +1,25 @@
+Description:
+Allocate memory with indefinite extent and specified alignment.
+
+Files:
+lib/aligned-malloc.h
+m4/malloc-align.m4
+
+Depends-on:
+stdint
+
+configure.ac:
+gl_MALLOC_ALIGNMENT
+AC_REQUIRE([AC_C_INLINE])
+AC_CHECK_FUNCS([posix_memalign memalign])
+
+Makefile.am:
+
+Include:
+"aligned-malloc.h"
+
+License:
+LGPLv2+
+
+Maintainer:
+all
-- 
2.7.4

>From fd8403522f26c5dca64fcc29bcf4c6b4c5b08335 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Tue, 21 Jul 2020 10:04:36 +0200
Subject: [PATCH 2/2] aligned-malloc: Add tests.

* tests/test-aligned-malloc.c: New file.
* modules/aligned-malloc-tests: New file.
---
 ChangeLog                    |   4 ++
 modules/aligned-malloc-tests |  11 +++++
 tests/test-aligned-malloc.c  | 102 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 117 insertions(+)
 create mode 100644 modules/aligned-malloc-tests
 create mode 100644 tests/test-aligned-malloc.c

diff --git a/ChangeLog b/ChangeLog
index 768b209..8da483c 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,9 @@
 2020-07-21  Bruno Haible  <br...@clisp.org>
 
+	aligned-malloc: Add tests.
+	* tests/test-aligned-malloc.c: New file.
+	* modules/aligned-malloc-tests: New file.
+
 	aligned-malloc: New module.
 	* lib/aligned-malloc.h: New file.
 	* m4/malloc-align.m4: New file.
diff --git a/modules/aligned-malloc-tests b/modules/aligned-malloc-tests
new file mode 100644
index 0000000..4d60549
--- /dev/null
+++ b/modules/aligned-malloc-tests
@@ -0,0 +1,11 @@
+Files:
+tests/test-aligned-malloc.c
+tests/macros.h
+
+Depends-on:
+
+configure.ac:
+
+Makefile.am:
+TESTS += test-aligned-malloc
+check_PROGRAMS += test-aligned-malloc
diff --git a/tests/test-aligned-malloc.c b/tests/test-aligned-malloc.c
new file mode 100644
index 0000000..6368746
--- /dev/null
+++ b/tests/test-aligned-malloc.c
@@ -0,0 +1,102 @@
+/* Test of allocating memory with given alignment.
+
+   Copyright (C) 2020 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 Bruno Haible <br...@clisp.org>, 2020.  */
+
+#include <config.h>
+
+#include <stdint.h>
+#include <stdlib.h>
+
+#define ALIGNMENT 4
+#define aligned_malloc aligned4_malloc
+#define aligned_free aligned4_free
+#include "aligned-malloc.h"
+#undef aligned_free
+#undef aligned_malloc
+#undef ALIGNMENT
+
+#define ALIGNMENT 8
+#define aligned_malloc aligned8_malloc
+#define aligned_free aligned8_free
+#include "aligned-malloc.h"
+#undef aligned_free
+#undef aligned_malloc
+#undef ALIGNMENT
+
+#define ALIGNMENT 16
+#define aligned_malloc aligned16_malloc
+#define aligned_free aligned16_free
+#include "aligned-malloc.h"
+#undef aligned_free
+#undef aligned_malloc
+#undef ALIGNMENT
+
+#define ALIGNMENT 32
+#define aligned_malloc aligned32_malloc
+#define aligned_free aligned32_free
+#include "aligned-malloc.h"
+#undef aligned_free
+#undef aligned_malloc
+#undef ALIGNMENT
+
+#include <string.h>
+
+#include "macros.h"
+
+int
+main (int argc, char *argv[])
+{
+  static size_t sizes[] =
+    { 13, 8, 17, 450, 320, 1, 99, 4, 15, 16, 2, 76, 37, 127, 2406, 641 };
+  void *aligned4_blocks[SIZEOF (sizes)];
+  void *aligned8_blocks[SIZEOF (sizes)];
+  void *aligned16_blocks[SIZEOF (sizes)];
+  void *aligned32_blocks[SIZEOF (sizes)];
+  size_t i;
+
+  for (i = 0; i < SIZEOF (sizes); i++)
+    {
+      size_t size = sizes[i];
+
+      aligned4_blocks[i] = aligned4_malloc (size);
+      ASSERT (((uintptr_t) aligned4_blocks[i] % 4) == 0);
+      memset (aligned4_blocks[i], 'w', size);
+
+      aligned8_blocks[i] = aligned8_malloc (size);
+      ASSERT (((uintptr_t) aligned8_blocks[i] % 8) == 0);
+      memset (aligned8_blocks[i], 'x', size);
+
+      aligned16_blocks[i] = aligned16_malloc (size);
+      ASSERT (((uintptr_t) aligned16_blocks[i] % 16) == 0);
+      memset (aligned16_blocks[i], 'y', size);
+
+      aligned32_blocks[i] = aligned32_malloc (size);
+      ASSERT (((uintptr_t) aligned32_blocks[i] % 32) == 0);
+      memset (aligned32_blocks[i], 'z', size);
+    }
+
+  for (i = 0; i < SIZEOF (sizes); i++)
+    {
+      aligned4_free (aligned4_blocks[i]);
+      aligned8_free (aligned8_blocks[i]);
+      aligned16_free (aligned16_blocks[i]);
+      aligned32_free (aligned32_blocks[i]);
+    }
+
+  return 0;
+}
-- 
2.7.4

Reply via email to