97bac407创建于 2022年6月30日历史提交
// 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_