//===-- include/flang/Runtime/memory.h --------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
// Thin wrapper around malloc()/free() to isolate the dependency,
// ease porting, and provide an owning pointer.
#ifndef FORTRAN_RUNTIME_MEMORY_H_
#define FORTRAN_RUNTIME_MEMORY_H_
#include "flang/Common/api-attrs.h"
#include <cassert>
#include <memory>
#include <type_traits>
namespace Fortran::runtime {
class Terminator;
[[nodiscard]] RT_API_ATTRS void *AllocateMemoryOrCrash(
const Terminator &, std::size_t bytes);
template <typename A>
[[nodiscard]] RT_API_ATTRS A &AllocateOrCrash(const Terminator &t) {
return *reinterpret_cast<A *>(AllocateMemoryOrCrash(t, sizeof(A)));
}
RT_API_ATTRS void *ReallocateMemoryOrCrash(
const Terminator &, void *ptr, std::size_t newByteSize);
RT_API_ATTRS void FreeMemory(void *);
template <typename A> RT_API_ATTRS void FreeMemory(A *p) {
FreeMemory(reinterpret_cast<void *>(p));
}
template <typename A> RT_API_ATTRS void FreeMemoryAndNullify(A *&p) {
FreeMemory(p);
p = nullptr;
}
// Very basic implementation mimicking std::unique_ptr.
// It should work for any offload device compiler.
// It uses a fixed memory deleter based on FreeMemory(),
// and does not support array objects with runtime length.
template <typename A> class OwningPtr {
public:
using pointer_type = A *;
OwningPtr() = default;
RT_API_ATTRS explicit OwningPtr(pointer_type p) : ptr_(p) {}
RT_API_ATTRS OwningPtr(const OwningPtr &) = delete;
RT_API_ATTRS OwningPtr &operator=(const OwningPtr &) = delete;
RT_API_ATTRS OwningPtr(OwningPtr &&other) {
ptr_ = other.ptr_;
other.ptr_ = pointer_type{};
}
RT_API_ATTRS OwningPtr &operator=(OwningPtr &&other) {
if (this != &other) {
delete_ptr(ptr_);
ptr_ = other.ptr_;
other.ptr_ = pointer_type{};
}
return *this;
}
constexpr RT_API_ATTRS OwningPtr(std::nullptr_t) : OwningPtr() {}
// Delete the pointer, if owns one.
RT_API_ATTRS ~OwningPtr() {
if (ptr_ != pointer_type{}) {
delete_ptr(ptr_);
ptr_ = pointer_type{};
}
}
// Release the ownership.
RT_API_ATTRS pointer_type release() {
pointer_type p = ptr_;
ptr_ = pointer_type{};
return p;
}
RT_DIAG_PUSH
RT_DIAG_DISABLE_CALL_HOST_FROM_DEVICE_WARN
// Replace the pointer.
RT_API_ATTRS void reset(pointer_type p = pointer_type{}) {
std::swap(ptr_, p);
if (p != pointer_type{}) {
// Delete the owned pointer.
delete_ptr(p);
}
}
// Exchange the pointer with another object.
RT_API_ATTRS void swap(OwningPtr &other) { std::swap(ptr_, other.ptr_); }
RT_DIAG_POP
// Get the stored pointer.
RT_API_ATTRS pointer_type get() const { return ptr_; }
RT_API_ATTRS explicit operator bool() const {
return get() != pointer_type{};
}
RT_API_ATTRS typename std::add_lvalue_reference<A>::type operator*() const {
assert(get() != pointer_type{});
return *get();
}
RT_API_ATTRS pointer_type operator->() const { return get(); }
private:
RT_API_ATTRS void delete_ptr(pointer_type p) { FreeMemory(p); }
pointer_type ptr_{};
};
template <typename X, typename Y>
inline RT_API_ATTRS bool operator!=(
const OwningPtr<X> &x, const OwningPtr<Y> &y) {
return x.get() != y.get();
}
template <typename X>
inline RT_API_ATTRS bool operator!=(const OwningPtr<X> &x, std::nullptr_t) {
return (bool)x;
}
template <typename X>
inline RT_API_ATTRS bool operator!=(std::nullptr_t, const OwningPtr<X> &x) {
return (bool)x;
}
template <typename A> class SizedNew {
public:
explicit RT_API_ATTRS SizedNew(const Terminator &terminator)
: terminator_{terminator} {}
template <typename... X>
[[nodiscard]] RT_API_ATTRS OwningPtr<A> operator()(
std::size_t bytes, X &&...x) {
return OwningPtr<A>{new (AllocateMemoryOrCrash(terminator_, bytes))
A{std::forward<X>(x)...}};
}
private:
const Terminator &terminator_;
};
template <typename A> struct New : public SizedNew<A> {
using SizedNew<A>::SizedNew;
template <typename... X>
[[nodiscard]] RT_API_ATTRS OwningPtr<A> operator()(X &&...x) {
return SizedNew<A>::operator()(sizeof(A), std::forward<X>(x)...);
}
};
template <typename A> struct Allocator {
using value_type = A;
explicit Allocator(const Terminator &t) : terminator{t} {}
template <typename B>
explicit constexpr Allocator(const Allocator<B> &that) noexcept
: terminator{that.terminator} {}
Allocator(const Allocator &) = default;
Allocator(Allocator &&) = default;
[[nodiscard]] constexpr A *allocate(std::size_t n) {
return reinterpret_cast<A *>(
AllocateMemoryOrCrash(terminator, n * sizeof(A)));
}
constexpr void deallocate(A *p, std::size_t) { FreeMemory(p); }
const Terminator &terminator;
};
} // namespace Fortran::runtime
#endif // FORTRAN_RUNTIME_MEMORY_H_