Files
test/intern/cycles/util/guarded_allocator.h
Brecht Van Lommel 73fe848e07 Fix: Cycles log levels conflict with macros on some platforms
In particular DEBUG, but prefix all of them to be sure.

Pull Request: https://projects.blender.org/blender/blender/pulls/141749
2025-07-10 19:44:14 +02:00

162 lines
4.0 KiB
C++

/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include <cstddef>
#include <cstdlib>
#include <new>
#ifdef WITH_BLENDER_GUARDEDALLOC
# include "../../guardedalloc/MEM_guardedalloc.h"
#endif
CCL_NAMESPACE_BEGIN
/* Internal use only. */
void util_guarded_mem_alloc(const size_t n);
void util_guarded_mem_free(const size_t n);
/* Guarded allocator for the use with STL. */
template<typename T> class GuardedAllocator {
public:
using size_type = size_t;
using difference_type = ptrdiff_t;
using pointer = T *;
using const_pointer = const T *;
using reference = T &;
using const_reference = const T &;
using value_type = T;
GuardedAllocator() = default;
GuardedAllocator(const GuardedAllocator & /*unused*/) = default;
T *allocate(const size_t n, const void *hint = nullptr)
{
(void)hint;
size_t size = n * sizeof(T);
util_guarded_mem_alloc(size);
if (n == 0) {
return nullptr;
}
T *mem;
#ifdef WITH_BLENDER_GUARDEDALLOC
/* C++ standard requires allocation functions to allocate memory suitably
* aligned for any standard type. This is 16 bytes for 64 bit platform as
* far as i concerned. We might over-align on 32bit here, but that should
* be all safe actually.
*/
mem = (T *)MEM_mallocN_aligned(size, 16, "Cycles Alloc");
#else
mem = (T *)malloc(size);
#endif
if (mem == nullptr) {
throw std::bad_alloc();
}
return mem;
}
void deallocate(T *p, const size_t n)
{
util_guarded_mem_free(n * sizeof(T));
if (p != nullptr) {
#ifdef WITH_BLENDER_GUARDEDALLOC
MEM_freeN(const_cast<void *>(static_cast<const void *>(p)));
#else
free(p);
#endif
}
}
T *address(T &x) const
{
return &x;
}
const T *address(const T &x) const
{
return &x;
}
GuardedAllocator<T> &operator=(const GuardedAllocator & /*unused*/) = default;
size_t max_size() const
{
return size_t(-1);
}
template<class U> struct rebind {
using other = GuardedAllocator<U>;
};
template<class U> GuardedAllocator(const GuardedAllocator<U> & /*unused*/) {}
template<class U> GuardedAllocator &operator=(const GuardedAllocator<U> & /*unused*/)
{
return *this;
}
bool operator==(const GuardedAllocator & /*other*/) const
{
return true;
}
bool operator!=(const GuardedAllocator &other) const
{
return !operator==(other);
}
#ifdef _MSC_VER
/* Welcome to the black magic here.
*
* The issue is that MSVC C++ allocates container proxy on any
* vector initialization, including static vectors which don't
* have any data yet. This leads to several issues:
*
* - Static objects initialization fiasco (global_stats from
* util_stats.h might not be initialized yet).
* - If main() function changes allocator type (for example,
* this might happen with `blender --debug-memory`) nobody
* will know how to convert already allocated memory to a new
* guarded allocator.
*
* Here we work this around by making it so container proxy does
* not use guarded allocation. A bit fragile, unfortunately.
*/
template<> struct rebind<std::_Container_proxy> {
typedef std::allocator<std::_Container_proxy> other;
};
operator std::allocator<std::_Container_proxy>() const
{
return std::allocator<std::_Container_proxy>();
}
#endif
};
/* Get memory usage and peak from the guarded STL allocator. */
size_t util_guarded_get_mem_used();
size_t util_guarded_get_mem_peak();
/* Call given function and keep track if it runs out of memory.
*
* If it does run out f memory, stop execution and set progress
* to do a global cancel.
*
* It's not fully robust, but good enough to catch obvious issues
* when running out of memory.
*/
#define MEM_GUARDED_CALL(progress, func, ...) \
do { \
try { \
(func)(__VA_ARGS__); \
} \
catch (std::bad_alloc &) { \
LOG_ERROR << "Out of memory"; \
fflush(stderr); \
(progress)->set_error("Out of memory"); \
} \
} while (false)
CCL_NAMESPACE_END