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