/* ----------------------------------------------------------------------- *//** * * @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 * MC = dbal::FunctionContext, ZM = dbal::DoZero, * F = dbal::ThrowBadAlloc */ template inline MutableArrayHandle Allocator::internalAllocateArray( const std::array& 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::max() * - ARR_OVERHEAD_NONULLS(Dimensions)) / inElementSize >= numElements) */ if ((std::numeric_limits::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(allocate(size)); SET_VARSIZE(array, size); array->ndim = Dimensions; array->dataoffset = 0; array->elemtype = TypeTraits::oid; for (std::size_t i = 0; i < Dimensions; ++i) { ARR_DIMS(array)[i] = static_cast(inNumElements[i]); ARR_LBOUND(array)[i] = 1; } return MutableArrayHandle(array); } #define MADLIB_ALLOCATE_ARRAY_DEF(z, n, _ignored) \ template \ inline \ MutableArrayHandle \ Allocator::allocateArray( \ BOOST_PP_ENUM_PARAMS_Z(z, BOOST_PP_INC(n), std::size_t inDim) \ ) const { \ std::array numElements = {{ \ BOOST_PP_ENUM_PARAMS_Z(z, BOOST_PP_INC(n), inDim) \ }}; \ return internalAllocateArray \ (numElements); \ } \ \ template \ inline \ MutableArrayHandle \ Allocator::allocateArray( \ BOOST_PP_ENUM_PARAMS_Z(z, BOOST_PP_INC(n), std::size_t inDim) \ ) const { \ std::array numElements = {{ \ BOOST_PP_ENUM_PARAMS_Z(z, BOOST_PP_INC(n), inDim) \ }}; \ return internalAllocateArray \ (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 inline MutableByteString Allocator::allocateByteString(std::size_t inSize) const { bytea* byteString = static_cast( allocate(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 inline void * Allocator::allocate(size_t inSize) const { return internalAllocate(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 inline void * Allocator::reallocate(void *inPtr, const size_t inSize) const { return internalAllocate(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 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 MAXIMUM_ALIGNOF >= 16, 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 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::max() - 16) return NULL; /* Precondition: inSize <= std::numeric_limits::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 MAXIMUM_ALIGNOF >= 16, 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 inline void * Allocator::internalRePalloc(void *inPtr, size_t inSize) const { #if MAXIMUM_ALIGNOF >= 16 return repalloc(inPtr, inSize); #else if (inSize > std::numeric_limits::max() - 16) { pfree(unaligned(inPtr)); return NULL; } /* Precondition: inSize <= std::numeric_limits::max() - 16 */ const size_t size = inSize + 16; void *raw = repalloc(unaligned(inPtr), size); if (ZM == dbal::DoZero) { std::fill( static_cast(raw), static_cast(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(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( (reinterpret_cast(inPtr) & ~(uintptr_t(15))) + 16); *(reinterpret_cast(aligned) - 1) = inPtr; return aligned; } /** * @internal * @brief Return the address of memory block that corresponds to the given * 16-byte aligned address * * Unless MAXIMUM_ALIGNOF >= 16, 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(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 F == ThrowBadAlloc: 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 F == ReturnNULL: 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 * F == ThrowBadAlloc */ 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(inPtr, inSize) : internalPalloc(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)