/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef ART_RUNTIME_GC_SPACE_MEMORY_TOOL_MALLOC_SPACE_INL_H_
#define ART_RUNTIME_GC_SPACE_MEMORY_TOOL_MALLOC_SPACE_INL_H_

#include "memory_tool_malloc_space.h"

#include "base/memory_tool.h"
#include "memory_tool_settings.h"
#include "mirror/object-inl.h"

namespace art {
namespace gc {
namespace space {

namespace memory_tool_details {

template <size_t kMemoryToolRedZoneBytes, bool kUseObjSizeForUsable>
inline mirror::Object* AdjustForMemoryTool(void* obj_with_rdz,
                                           size_t num_bytes,
                                           size_t bytes_allocated,
                                           size_t usable_size,
                                           size_t bytes_tl_bulk_allocated,
                                           size_t* bytes_allocated_out,
                                           size_t* usable_size_out,
                                           size_t* bytes_tl_bulk_allocated_out) {
  if (bytes_allocated_out != nullptr) {
    *bytes_allocated_out = bytes_allocated;
  }
  if (bytes_tl_bulk_allocated_out != nullptr) {
    *bytes_tl_bulk_allocated_out = bytes_tl_bulk_allocated;
  }

  // This cuts over-provision and is a trade-off between testing the over-provisioning code paths
  // vs checking overflows in the regular paths.
  if (usable_size_out != nullptr) {
    if (kUseObjSizeForUsable) {
      *usable_size_out = num_bytes;
    } else {
      *usable_size_out = usable_size - 2 * kMemoryToolRedZoneBytes;
    }
  }

  // Left redzone.
  MEMORY_TOOL_MAKE_NOACCESS(obj_with_rdz, kMemoryToolRedZoneBytes);

  // Make requested memory readable.
  // (If the allocator assumes memory is zeroed out, we might get UNDEFINED warnings, so make
  //  everything DEFINED initially.)
  mirror::Object* result = reinterpret_cast<mirror::Object*>(
      reinterpret_cast<uint8_t*>(obj_with_rdz) + kMemoryToolRedZoneBytes);
  MEMORY_TOOL_MAKE_DEFINED(result, num_bytes);

  // Right redzone. Assumes that if bytes_allocated > usable_size, then the difference is
  // management data at the upper end, and for simplicity we will not protect that.
  // At the moment, this fits RosAlloc (no management data in a slot, usable_size == alloc_size)
  // and DlMalloc (allocation_size = (usable_size == num_bytes) + 4, 4 is management)
  MEMORY_TOOL_MAKE_NOACCESS(reinterpret_cast<uint8_t*>(result) + num_bytes,
                    usable_size - (num_bytes + kMemoryToolRedZoneBytes));

  return result;
}

inline size_t GetObjSizeNoThreadSafety(mirror::Object* obj) NO_THREAD_SAFETY_ANALYSIS {
  return obj->SizeOf<kVerifyNone>();
}

}  // namespace memory_tool_details

template <typename S,
          size_t kMemoryToolRedZoneBytes,
          bool kAdjustForRedzoneInAllocSize,
          bool kUseObjSizeForUsable>
mirror::Object*
MemoryToolMallocSpace<S,
                      kMemoryToolRedZoneBytes,
                      kAdjustForRedzoneInAllocSize,
                      kUseObjSizeForUsable>::AllocWithGrowth(
    Thread* self,
    size_t num_bytes,
    size_t* bytes_allocated_out,
    size_t* usable_size_out,
    size_t* bytes_tl_bulk_allocated_out) {
  size_t bytes_allocated;
  size_t usable_size;
  size_t bytes_tl_bulk_allocated;
  void* obj_with_rdz = S::AllocWithGrowth(self,
                                          num_bytes + 2 * kMemoryToolRedZoneBytes,
                                          &bytes_allocated,
                                          &usable_size,
                                          &bytes_tl_bulk_allocated);
  if (obj_with_rdz == nullptr) {
    return nullptr;
  }

  return memory_tool_details::AdjustForMemoryTool<kMemoryToolRedZoneBytes, kUseObjSizeForUsable>(
      obj_with_rdz,
      num_bytes,
      bytes_allocated,
      usable_size,
      bytes_tl_bulk_allocated,
      bytes_allocated_out,
      usable_size_out,
      bytes_tl_bulk_allocated_out);
}

template <typename S,
          size_t kMemoryToolRedZoneBytes,
          bool kAdjustForRedzoneInAllocSize,
          bool kUseObjSizeForUsable>
mirror::Object* MemoryToolMallocSpace<S,
                                      kMemoryToolRedZoneBytes,
                                      kAdjustForRedzoneInAllocSize,
                                      kUseObjSizeForUsable>::Alloc(
    Thread* self,
    size_t num_bytes,
    size_t* bytes_allocated_out,
    size_t* usable_size_out,
    size_t* bytes_tl_bulk_allocated_out) {
  size_t bytes_allocated;
  size_t usable_size;
  size_t bytes_tl_bulk_allocated;
  void* obj_with_rdz = S::Alloc(self,
                                num_bytes + 2 * kMemoryToolRedZoneBytes,
                                &bytes_allocated,
                                &usable_size,
                                &bytes_tl_bulk_allocated);
  if (obj_with_rdz == nullptr) {
    return nullptr;
  }

  return memory_tool_details::AdjustForMemoryTool<kMemoryToolRedZoneBytes, kUseObjSizeForUsable>(
      obj_with_rdz,
      num_bytes,
      bytes_allocated,
      usable_size,
      bytes_tl_bulk_allocated,
      bytes_allocated_out,
      usable_size_out,
      bytes_tl_bulk_allocated_out);
}

template <typename S,
          size_t kMemoryToolRedZoneBytes,
          bool kAdjustForRedzoneInAllocSize,
          bool kUseObjSizeForUsable>
mirror::Object* MemoryToolMallocSpace<S,
                                      kMemoryToolRedZoneBytes,
                                      kAdjustForRedzoneInAllocSize,
                                      kUseObjSizeForUsable>::AllocThreadUnsafe(
    Thread* self,
    size_t num_bytes,
    size_t* bytes_allocated_out,
    size_t* usable_size_out,
    size_t* bytes_tl_bulk_allocated_out) {
  size_t bytes_allocated;
  size_t usable_size;
  size_t bytes_tl_bulk_allocated;
  void* obj_with_rdz = S::AllocThreadUnsafe(self,
                                            num_bytes + 2 * kMemoryToolRedZoneBytes,
                                            &bytes_allocated,
                                            &usable_size,
                                            &bytes_tl_bulk_allocated);
  if (obj_with_rdz == nullptr) {
    return nullptr;
  }

  return memory_tool_details::AdjustForMemoryTool<kMemoryToolRedZoneBytes, kUseObjSizeForUsable>(
      obj_with_rdz,
      num_bytes,
      bytes_allocated,
      usable_size,
      bytes_tl_bulk_allocated,
      bytes_allocated_out,
      usable_size_out,
      bytes_tl_bulk_allocated_out);
}

template <typename S,
          size_t kMemoryToolRedZoneBytes,
          bool kAdjustForRedzoneInAllocSize,
          bool kUseObjSizeForUsable>
size_t MemoryToolMallocSpace<S,
                             kMemoryToolRedZoneBytes,
                             kAdjustForRedzoneInAllocSize,
                             kUseObjSizeForUsable>::AllocationSize(
    mirror::Object* obj, size_t* usable_size) {
  size_t result = S::AllocationSize(
      reinterpret_cast<mirror::Object*>(
          reinterpret_cast<uint8_t*>(obj)
              - (kAdjustForRedzoneInAllocSize ? kMemoryToolRedZoneBytes : 0)),
      usable_size);
  if (usable_size != nullptr) {
    if (kUseObjSizeForUsable) {
      *usable_size = memory_tool_details::GetObjSizeNoThreadSafety(obj);
    } else {
      *usable_size = *usable_size - 2 * kMemoryToolRedZoneBytes;
    }
  }
  return result;
}

template <typename S,
          size_t kMemoryToolRedZoneBytes,
          bool kAdjustForRedzoneInAllocSize,
          bool kUseObjSizeForUsable>
size_t MemoryToolMallocSpace<S,
                             kMemoryToolRedZoneBytes,
                             kAdjustForRedzoneInAllocSize,
                             kUseObjSizeForUsable>::Free(Thread* self, mirror::Object* ptr) {
  void* obj_after_rdz = reinterpret_cast<void*>(ptr);
  uint8_t* obj_with_rdz = reinterpret_cast<uint8_t*>(obj_after_rdz) - kMemoryToolRedZoneBytes;

  // Make redzones undefined.
  size_t usable_size;
  size_t allocation_size = AllocationSize(ptr, &usable_size);

  // Unprotect the allocation.
  // Use the obj-size-for-usable flag to determine whether usable_size is the more important one,
  // e.g., whether there's data in the allocation_size (and usable_size can't be trusted).
  if (kUseObjSizeForUsable) {
    MEMORY_TOOL_MAKE_UNDEFINED(obj_with_rdz, allocation_size);
  } else {
    MEMORY_TOOL_MAKE_UNDEFINED(obj_with_rdz, usable_size + 2 * kMemoryToolRedZoneBytes);
  }

  return S::Free(self, reinterpret_cast<mirror::Object*>(obj_with_rdz));
}

template <typename S,
          size_t kMemoryToolRedZoneBytes,
          bool kAdjustForRedzoneInAllocSize,
          bool kUseObjSizeForUsable>
size_t MemoryToolMallocSpace<S,
                             kMemoryToolRedZoneBytes,
                             kAdjustForRedzoneInAllocSize,
                             kUseObjSizeForUsable>::FreeList(
                                 Thread* self, size_t num_ptrs, mirror::Object** ptrs) {
  size_t freed = 0;
  // Sort the pointers to free non class objects first. See b/131542326 for why this is necessary to
  // avoid crashes.
  std::sort(ptrs, ptrs + num_ptrs, [](mirror::Object* a, mirror::Object* b)
      REQUIRES_SHARED(Locks::mutator_lock_) {
    return a->IsClass() < b->IsClass();
  });
  for (size_t i = 0; i < num_ptrs; i++) {
    freed += Free(self, ptrs[i]);
    ptrs[i] = nullptr;
  }
  return freed;
}

template <typename S,
          size_t kMemoryToolRedZoneBytes,
          bool kAdjustForRedzoneInAllocSize,
          bool kUseObjSizeForUsable>
template <typename... Params>
MemoryToolMallocSpace<S,
                      kMemoryToolRedZoneBytes,
                      kAdjustForRedzoneInAllocSize,
                      kUseObjSizeForUsable>::MemoryToolMallocSpace(
                          MemMap&& mem_map, size_t initial_size, Params... params)
                          : S(std::move(mem_map), initial_size, params...) {
  // Don't want to change the memory tool states of the mem map here as the allocator is already
  // initialized at this point and that may interfere with what the allocator does internally. Note
  // that the tail beyond the initial size is mprotected.
}

template <typename S,
          size_t kMemoryToolRedZoneBytes,
          bool kAdjustForRedzoneInAllocSize,
          bool kUseObjSizeForUsable>
size_t MemoryToolMallocSpace<S,
                             kMemoryToolRedZoneBytes,
                             kAdjustForRedzoneInAllocSize,
                             kUseObjSizeForUsable>::MaxBytesBulkAllocatedFor(size_t num_bytes) {
  return S::MaxBytesBulkAllocatedFor(num_bytes + 2 * kMemoryToolRedZoneBytes);
}

}  // namespace space
}  // namespace gc
}  // namespace art

#endif  // ART_RUNTIME_GC_SPACE_MEMORY_TOOL_MALLOC_SPACE_INL_H_
