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);
+}