blob: 4c442070ea4007eb7e921c5eb33628c5f4bebb01 [file] [log] [blame]
/* ----------------------------------------------------------------------- *//**
*
* @file Allocator_impl.hpp
*
*//* ----------------------------------------------------------------------- */
#ifndef MADLIB_POSTGRES_ALLOCATOR_IMPL_HPP
#define MADLIB_POSTGRES_ALLOCATOR_IMPL_HPP
namespace madlib {
namespace dbconnector {
namespace postgres {
/**
* @brief Construct an empty postgres array of the given size.
*
* This calls allocate() to allocate a block of memory and then initializes
* PostgreSQL meta information.
*
* @note
* There is a template-overloaded version with defaults
* <tt>MC = dbal::FunctionContext</tt>, <tt>ZM = dbal::DoZero</tt>,
* <tt>F = dbal::ThrowBadAlloc</tt>
*/
template <typename T, std::size_t Dimensions, dbal::MemoryContext MC,
dbal::ZeroMemory ZM, dbal::OnMemoryAllocationFailure F>
inline
MutableArrayHandle<T>
Allocator::internalAllocateArray(
const std::array<std::size_t, Dimensions>& inNumElements) const {
std::size_t numElements = Dimensions ? 1 : 0;
for (std::size_t i = 0; i < Dimensions; ++i)
numElements *= inNumElements[i];
/*
* Check that the size will not exceed addressable memory. Therefore, the
* following precondition has to hold:
* ((std::numeric_limits<std::size_t>::max()
* - ARR_OVERHEAD_NONULLS(Dimensions)) / inElementSize >= numElements)
*/
if ((std::numeric_limits<std::size_t>::max()
- ARR_OVERHEAD_NONULLS(Dimensions)) / sizeof(T) < numElements)
throw std::bad_alloc();
std::size_t size = sizeof(T) * numElements
+ ARR_OVERHEAD_NONULLS(Dimensions);
ArrayType *array;
// Note: Except for the allocate call, the following statements do not call
// into the PostgreSQL backend. We are only using macros here.
// PostgreSQL requires that all memory is overwritten with zeros. So
// we ingore ZM here
array = static_cast<ArrayType*>(allocate<MC, dbal::DoZero, F>(size));
SET_VARSIZE(array, size);
array->ndim = Dimensions;
array->dataoffset = 0;
array->elemtype = TypeTraits<T>::oid;
for (std::size_t i = 0; i < Dimensions; ++i) {
ARR_DIMS(array)[i] = static_cast<int>(inNumElements[i]);
ARR_LBOUND(array)[i] = 1;
}
return MutableArrayHandle<T>(array);
}
#define MADLIB_ALLOCATE_ARRAY_DEF(z, n, _ignored) \
template <typename T> \
inline \
MutableArrayHandle<T> \
Allocator::allocateArray( \
BOOST_PP_ENUM_PARAMS_Z(z, BOOST_PP_INC(n), std::size_t inDim) \
) const { \
std::array<std::size_t, BOOST_PP_INC(n)> numElements = {{ \
BOOST_PP_ENUM_PARAMS_Z(z, BOOST_PP_INC(n), inDim) \
}}; \
return internalAllocateArray<T, BOOST_PP_INC(n), \
dbal::FunctionContext, dbal::DoZero, dbal::ThrowBadAlloc> \
(numElements); \
} \
\
template <typename T, dbal::MemoryContext MC, \
dbal::ZeroMemory ZM, dbal::OnMemoryAllocationFailure F> \
inline \
MutableArrayHandle<T> \
Allocator::allocateArray( \
BOOST_PP_ENUM_PARAMS_Z(z, BOOST_PP_INC(n), std::size_t inDim) \
) const { \
std::array<std::size_t, BOOST_PP_INC(n)> numElements = {{ \
BOOST_PP_ENUM_PARAMS_Z(z, BOOST_PP_INC(n), inDim) \
}}; \
return internalAllocateArray<T, BOOST_PP_INC(n), MC, ZM, F> \
(numElements); \
}
BOOST_PP_REPEAT(MADLIB_MAX_ARRAY_DIMS, MADLIB_ALLOCATE_ARRAY_DEF,
0 /* ignored */)
#undef MADLIB_ALLOCATE_ARRAY_DEF
/**
* @brief Construct a byte string of the given size
*/
template <dbal::MemoryContext MC, dbal::ZeroMemory ZM,
dbal::OnMemoryAllocationFailure F>
inline
MutableByteString
Allocator::allocateByteString(std::size_t inSize) const {
bytea* byteString = static_cast<bytea*>(
allocate<MC, dbal::DoZero, F>(ByteString::kEffectiveHeaderSize + inSize)
);
SET_VARSIZE(byteString, ByteString::kEffectiveHeaderSize + inSize);
return byteString;
}
/**
* @brief Allocate a block of memory
*
* @return The address of a 16-byte aligned block of memory large enough to hold
* \c inSize bytes. On all supported platforms, 16-byte alignment is enough
* for any arbitrary operation.
*/
template <dbal::MemoryContext MC, dbal::ZeroMemory ZM,
dbal::OnMemoryAllocationFailure F>
inline
void *
Allocator::allocate(size_t inSize) const {
return internalAllocate<MC, ZM, F, NewAllocation>(NULL, inSize);
}
/**
* @brief Change the size of a block of memory previously allocated with
* Allocator allocation functions
*
* There is no guerantee that the returned pointer is the same as \c inPtr.
*
* @param inPtr Pointer to a memory block previously allocated with allocate or
* reallocate.
* @param inSize Requested size for the reallocated memory block
*
* @return The address of a 16-byte aligned block of memory large enough to hold
* \c inSize bytes. On all supported platforms, 16-byte alignment is enough
* for any arbitrary operation.
*/
template <dbal::MemoryContext MC, dbal::ZeroMemory ZM,
dbal::OnMemoryAllocationFailure F>
inline
void *
Allocator::reallocate(void *inPtr, const size_t inSize) const {
return internalAllocate<MC, ZM, F, Reallocation>(inPtr, inSize);
}
/**
* @brief Free a block of memory previously allocated with
* Allocator allocation functions
*
* @internal
* This function uses the PostgreSQL pfree() macro. This calls
* MemoryContextFreeImpl, which again calls, by default, AllocSetFree() from
* utils/mmgr/aset.c.
*
* We must not throw errors, so we are essentially ignoring all errors.
* This function is also called by operator delete(),
* which must not throw *any* exceptions.
*
* @param inPtr Pointer to a memory block previously allocated with allocate,
* reallocate or allocateArray. If a null pointer is passed as argument, no
* action occurs. (std::free has the same behavior.)
*
* @see See also the notes for PGAllocator::allocate(const size_t) and
* PGAllocator::allocate(const size_t, const std::nothrow_t&)
*/
template <dbal::MemoryContext MC>
inline
void
Allocator::free(void *inPtr) const {
if (inPtr == NULL)
return;
/*
* See allocate(const size_t, const std::nothrow_t&) why we disable
* processing of interrupts.
*/
HOLD_INTERRUPTS();
PG_TRY(); {
pfree(unaligned(inPtr));
} PG_CATCH(); {
FlushErrorState();
} PG_END_TRY();
RESUME_INTERRUPTS();
}
/**
* @brief Thin wrapper around \c palloc() that returns a 16-byte-aligned
* pointer.
*
* @internal
* This function uses the PostgreSQL palloc() and palloc0() macros. They
* call MemoryContextAllocImpl() or MemoryContextAllocZeroImpl(),
* respectively, which then call, by default, AllocSetAlloc() from
* utils/mmgr/aset.c.
*
* Unless <tt>MAXIMUM_ALIGNOF >= 16</tt>, we waste 16 additional bytes of
* memory. The call to \c palloc() might throw a PostgreSQL exception. Thus,
* this function should only be used inside a \c PG_TRY() block.
*/
template <dbal::ZeroMemory ZM>
inline
void *
Allocator::internalPalloc(size_t inSize) const {
#if MAXIMUM_ALIGNOF >= 16
return (ZM == dbal::DoZero) ? palloc0(inSize) : palloc(inSize);
#else
if (inSize > std::numeric_limits<size_t>::max() - 16)
return NULL;
/* Precondition: inSize <= std::numeric_limits<size_t>::max() - 16 */
const size_t size = inSize + 16;
void *raw = (ZM == dbal::DoZero) ? palloc0(size) : palloc(size);
return makeAligned(raw);
#endif
}
/**
* @brief Thin wrapper around \c repalloc() that returns a 16-byte-aligned
* pointer.
*
* @tparam ZM Initialize memory block by overwriting with zeros?
*
* @internal
* This function uses the PostgreSQL repalloc() macro. This calls
* MemoryContextReallocImpl, which again calls, by default,
* AllocSetRealloc() from utils/mmgr/aset.c.
*
* Unless <tt>MAXIMUM_ALIGNOF >= 16</tt>, we waste 16 additional bytes of
* memory. The call to \c repalloc() might throw a PostgreSQL exception. Thus,
* this function should only be used inside a \c PG_TRY() block.
*/
template <dbal::ZeroMemory ZM>
inline
void *
Allocator::internalRePalloc(void *inPtr, size_t inSize) const {
#if MAXIMUM_ALIGNOF >= 16
return repalloc(inPtr, inSize);
#else
if (inSize > std::numeric_limits<size_t>::max() - 16) {
pfree(unaligned(inPtr));
return NULL;
}
/* Precondition: inSize <= std::numeric_limits<size_t>::max() - 16 */
const size_t size = inSize + 16;
void *raw = repalloc(unaligned(inPtr), size);
if (ZM == dbal::DoZero) {
std::fill(
static_cast<char*>(raw),
static_cast<char*>(raw) + inSize, 0);
}
return makeAligned(raw);
#endif
}
/**
* @internal
* @brief Return next 16-byte boundary after inPtr and store inPtr in word
* immediately before that
*/
inline
void *
Allocator::makeAligned(void *inPtr) const {
if (inPtr == NULL) return NULL;
/*
* Precondition: reinterprete_cast<size_t>(raw) % sizeof(void**) == 0,
* i.e., the memory returned by palloc is at least aligned on word size
* boundaries. That ensures that the word immediately preceding raw belongs
* to us an can be written to safely.
*/
void *aligned = reinterpret_cast<void*>(
(reinterpret_cast<uintptr_t>(inPtr) & ~(uintptr_t(15))) + 16);
*(reinterpret_cast<void**>(aligned) - 1) = inPtr;
return aligned;
}
/**
* @internal
* @brief Return the address of memory block that corresponds to the given
* 16-byte aligned address
*
* Unless <tt>MAXIMUM_ALIGNOF >= 16</tt>, we free the block of memory pointed to
* by the word immediately in front of the memory pointed to by \c inPtr.
*/
inline
void *
Allocator::unaligned(void *inPtr) const {
#if MAXIMUM_ALIGNOF >= 16
return inPtr;
#else
return (*(reinterpret_cast<void**>(inPtr) - 1));
#endif
}
/**
* @brief Allocate memory in our PostgreSQL memory context. Throws on fail.
*
* @tparam MC Which memory context to allocate in?
* @tparam ZM Initialize memory block by overwriting with zeros?
* @tparam F What to do in case of failure?
* @tparam R Do a reallication or a new allocation?
*
* If <tt>F == ThrowBadAlloc</tt>: In case allocation fails, throw an exception.
* At the boundary of the C++ layer, another PostgreSQL error will be raised
* (i.e., there will be at least two errors on the PostgreSQL error handling
* stack).
*
* If <tt>F == ReturnNULL</tt>: In case allocation fails, do not throw an
* exception. ALso, make sure we do not leave in an error state. Instead, we
* just return NULL.
*
* We will hold back interrupts while in this function because we do not want
* to flush the postgres error state unless it is related to memory allocation.
* (We have to flush the error state because we cannot throw exceptions within
* allocate).
*
* Interrupts/Signals are only processed whenever the CHECK_FOR_INTERRUPTS()
* macro is called (see miscadmin.h). Some PostgreSQL function implicitly call
* this macro (a notable example being ereport -- the rationale here is that
* the user should be able to abort queries that produce lots of output).
* For the actual processing, see ProcessInterrupts() in tcop/postgres.c.
* All aborting is done through the ereport mechanism.
*
* By default, PostgreSQL's memory allocation happens in AllocSetAlloc from
* utils/mmgr/aset.c.
*
* @see PGInterface for information on necessary precautions when writing
* PostgreSQL plug-in code in C++.
*
* @exception std::bad_alloc if the allocation fails and
* <tt>F == ThrowBadAlloc</tt>
*/
template <
dbal::MemoryContext MC,
dbal::ZeroMemory ZM,
dbal::OnMemoryAllocationFailure F,
Allocator::ReallocateMemory R>
inline
void *
Allocator::internalAllocate(void *inPtr, const size_t inSize) const {
// Avoid warning that inPtr is not used if R == NewAllocation
(void) inPtr;
void *ptr;
bool errorOccurred = false;
if (F == dbal::ReturnNULL) {
/*
* HOLD_INTERRUPTS() and RESUME_INTERRUPTS() only change the value of a
* global variable but have no other side effects. In particular, they
* do not call CHECK_INTERRUPTS(). Hence, we are save to use these
* macros outside of a PG_TRY() block.
*/
HOLD_INTERRUPTS();
}
PG_TRY(); {
/*
* We used to respect the request for MC == dbal::AggregateContext here,
* but current PostgreSQL/Greenplum versions do not take any advantage
* of transition states allocated in the aggregate context anyways.
* They still copy the transition state, whenever the address of the
* returned state is different from the address of the input state.
*
* Any other use of the aggregate context (say, auxiliary data/caches
* for transition states) is not currently supported by the C++ AL.
*
* See also: MADLIB-606 (issue that triggered this code change).
*/
ptr = R ? internalRePalloc<ZM>(inPtr, inSize)
: internalPalloc<ZM>(inSize);
} PG_CATCH(); {
if (F == dbal::ReturnNULL) {
/*
* This cannot be due to an interrupt, so it's reasonably safe
* to assume that the PG exception was a pure memory-allocation
* issue. We ignore the error and flush the error state.
* Flushing is necessary for leaving the error state (e.g., the memory
* context is restored).
*/
FlushErrorState();
ptr = NULL;
} else {
/*
* PostgreSQL error messages can be stacked. So, it doesn't hurt to add
* our own message. After unwinding the C++ stack, the PostgreSQL
* exception will be re-thrown into the PostgreSQL C code.
*
* Throwing C++ exceptions inside a PG_CATCH block is not problematic
* per se, but it is good practise to keep the exception mechanisms clearly
* separated.
*/
errorOccurred = true;
}
} PG_END_TRY();
if (F == dbal::ReturnNULL) {
RESUME_INTERRUPTS();
}
if (errorOccurred || !ptr)
// We do not want to interleave PG exceptions and C++ exceptions.
throw std::bad_alloc();
return ptr;
}
/**
* @brief Get the default allocator
*/
inline
Allocator&
defaultAllocator() {
static Allocator sDefaultAllocator;
return sDefaultAllocator;
}
} // namespace postgres
} // namespace dbconnector
} // namespace madlib
#endif // defined(MADLIB_POSTGRES_ALLOCATOR_IMPL_HPP)