Add AlignedAlloc<N> template.

Helper template to perform aligned memory allocation and
releases. Because std::aligned_alloc() is not available on
Windows or on MacOS.

Bug: None
Change-Id: Ie742a76873ff5a8918af59aa59765930dc0ce235
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/13620
Reviewed-by: Sylvain Defresne <sdefresne@chromium.org>
Commit-Queue: David Turner <digit@google.com>
diff --git a/build/gen.py b/build/gen.py
index 5fd5783..8b833d0 100755
--- a/build/gen.py
+++ b/build/gen.py
@@ -816,6 +816,7 @@
         'src/gn/visual_studio_writer_unittest.cc',
         'src/gn/xcode_object_unittest.cc',
         'src/gn/xml_element_writer_unittest.cc',
+        'src/util/aligned_alloc_unittest.cc',
         'src/util/test/gn_test.cc',
       ], 'libs': []},
   }
diff --git a/src/util/aligned_alloc.h b/src/util/aligned_alloc.h
new file mode 100644
index 0000000..f5cf757
--- /dev/null
+++ b/src/util/aligned_alloc.h
@@ -0,0 +1,109 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TOOLS_UTIL_ALIGNED_ALLOC_H_
+#define TOOLS_UTIL_ALIGNED_ALLOC_H_
+
+#ifdef __APPLE__
+#include <Availability.h>
+#endif
+
+#include <cstdlib>
+
+#define IMPL_ALIGNED_ALLOC_CXX17 1
+#define IMPL_ALIGNED_ALLOC_WIN32 2
+#define IMPL_ALIGNED_ALLOC_MALLOC 3
+
+#ifndef IMPL_ALIGNED_ALLOC
+#ifdef _WIN32
+#define IMPL_ALIGNED_ALLOC IMPL_ALIGNED_ALLOC_WIN32
+#elif defined(__APPLE__) && defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && \
+    __MAC_OS_X_VERSION_MIN_REQUIRED < 101500
+// Note that aligned_alloc() is only available at runtime starting from
+// OSX 10.15, so use malloc() when compiling binaries that must run on older
+// releases.
+#define IMPL_ALIGNED_ALLOC IMPL_ALIGNED_ALLOC_MALLOC
+#else
+#define IMPL_ALIGNED_ALLOC IMPL_ALIGNED_ALLOC_CXX17
+#endif
+#endif
+
+#if IMPL_ALIGNED_ALLOC == IMPL_ALIGNED_ALLOC_WIN32
+#include <malloc.h>  // for _aligned_malloc() and _aligned_free()
+#endif
+
+#if IMPL_ALIGNED_ALLOC == IMPL_ALIGNED_ALLOC_MALLOC
+#include "base/logging.h"  // For DCHECK()
+#endif
+
+// AlignedAlloc<N> provides Alloc() and Free() methods that can be
+// used to allocate and release blocks of heap memory aligned with
+// N bytes.
+//
+// The implementation uses std::aligned_alloc() when it is available,
+// or uses fallbacks otherwise. On Win32, _aligned_malloc() and
+// _aligned_free() are used, while for older MacOS releases, ::malloc() is
+// used directly with a small trick.
+template <size_t ALIGNMENT>
+struct AlignedAlloc {
+  static void* Alloc(size_t size) {
+    static_assert((ALIGNMENT & (ALIGNMENT - 1)) == 0,
+                  "ALIGNMENT must be a power of 2");
+#if IMPL_ALIGNED_ALLOC == IMPL_ALIGNED_ALLOC_WIN32
+    return _aligned_malloc(size, ALIGNMENT);
+#elif IMPL_ALIGNED_ALLOC == IMPL_ALIGNED_ALLOC_MALLOC
+    if (ALIGNMENT <= sizeof(void*)) {
+      return ::malloc(size);
+    } else if (size == 0) {
+      return nullptr;
+    } else {
+      // Allocation size must be a multiple of ALIGNMENT
+      DCHECK((size % ALIGNMENT) == 0);
+
+      // Allocate block and store its address just before just before the
+      // result's address, as in:
+      //    ________________________________________
+      //   |    |          |                        |
+      //   |    | real_ptr |                        |
+      //   |____|__________|________________________|
+      //
+      //   ^               ^
+      //   real_ptr         result
+      //
+      // Note that malloc() guarantees that results are aligned on sizeof(void*)
+      // if the allocation size if larger than sizeof(void*). Hence, only
+      // |ALIGNMENT - sizeof(void*)| extra bytes are required.
+      void* real_block = ::malloc(size + ALIGNMENT - sizeof(void*));
+      auto addr = reinterpret_cast<uintptr_t>(real_block) + sizeof(void*);
+      uintptr_t padding = (ALIGNMENT - addr) % ALIGNMENT;
+      addr += padding;
+      reinterpret_cast<void**>(addr - sizeof(void*))[0] = real_block;
+      return reinterpret_cast<void*>(addr);
+    }
+#else  // IMPL_ALIGNED_ALLOC_CXX17
+    return std::aligned_alloc(ALIGNMENT, size);
+#endif
+  }
+
+  static void Free(void* block) {
+#if IMPL_ALIGNED_ALLOC == IMPL_ALIGNED_ALLOC_WIN32
+    _aligned_free(block);
+#elif IMPL_ALIGNED_ALLOC == IMPL_ALIGNED_ALLOC_MALLOC
+    if (ALIGNMENT <= sizeof(void*)) {
+      ::free(block);
+    } else if (block) {
+      if (ALIGNMENT > sizeof(void*)) {
+        // Read address of real block just before the aligned block.
+        block = *(reinterpret_cast<void**>(block) - 1);
+      }
+      ::free(block);
+    }
+#else
+    // Allocation came from std::aligned_alloc()
+    return std::free(block);
+#endif
+  }
+};
+
+#endif  // TOOLS_UTIL_ALIGNED_ALLOC_H_
diff --git a/src/util/aligned_alloc_unittest.cc b/src/util/aligned_alloc_unittest.cc
new file mode 100644
index 0000000..d41a775
--- /dev/null
+++ b/src/util/aligned_alloc_unittest.cc
@@ -0,0 +1,23 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "util/aligned_alloc.h"
+#include "util/test/test.h"
+
+using AlignedAllocPtrSize = AlignedAlloc<sizeof(void*)>;
+using AlignedAlloc32 = AlignedAlloc<32>;
+
+TEST(AlignedAllocTest, PtrSized) {
+  void* ptr = AlignedAllocPtrSize::Alloc(2 * sizeof(void*));
+  ASSERT_TRUE(ptr);
+  ASSERT_EQ(reinterpret_cast<uintptr_t>(ptr) % sizeof(void*), 0u);
+  AlignedAllocPtrSize::Free(ptr);
+}
+
+TEST(AlignedAllocTest, Align32) {
+  void* ptr = AlignedAlloc32::Alloc(64);
+  ASSERT_TRUE(ptr);
+  ASSERT_EQ(reinterpret_cast<uintptr_t>(ptr) % 32u, 0u);
+  AlignedAlloc32::Free(ptr);
+}