// // eng-malloc // // The engine has its own private eng::malloc which it uses to allocate // everything. The engine's malloc heap only contains engine data structures. // This helps achieve determinism when playing a replay log. // // About determinism: one of the key rules for maintaining deterministic // behavior is to not ever use operations that execute in arbitrary order. // For example, don't iterate over an unordered map, because there's no // rule about what order the items are produced. They could be produced // in a different order during replay than during the original recording. // Actually, you can occasionally get away with it // // The engine's eng::malloc is a thin wrapper around Doug Lea's Malloc, a good // general-purpose single-threaded malloc. It's probably not the fastest any // more (it was, once), but it's still quite good. It's also fairly easy // to work with. // // In order to get all engine data structures into the eng::malloc heap, you // need to jump through quite a few hoops: // // * When writing a class that gets allocated using operator new, // always derive from eng::opnew. This adds a custom operator new // to your class, which causes your class to be allocated using eng::malloc. // If you write a class that isn't ever supposed to be allocated using // operator new, derive from eng::nevernew instead. // // * When using STL containers, you need to use the eng variant: // eng::map, eng::set, eng::vector, eng::unordered_map, eng::unordered_set, // and eng::deque. These classes derive from eng::opnew, and they also // use eng::malloc for their internal nodes. // // * Use eng::string instead of std::string. Use eng::ostringstream // instead of std::ostringstream. // // * Simple classes like std::pair, std::string_view, std::less, std::hash, and // so forth are not wrapped, because it is not normal to allocate these // classes using operator new. Do not use operator new or delete on // these classes. // // * Instead of std::make_shared, use eng::make_shared. You need this // because std::make_shared doesn't respect your custom operator new. // // * Failing to jump through all these hoops won't break your code in any // obvious way - you'll just have some of your data structures in the malloc // heap instead of the eng::malloc heap. This won't break // determinism unless you iterate over a data structure like an unordered map // but it creates a situation where we can't detect // nondeterminism. // // * Sometimes we deliberately put certain data structures into the malloc // heap, because we know that those particular data structures won't be // identical between record and replay. In that situation, the fact that // we don't detect the nondeterminism is actually a benefit. // // * Be aware that most C++ streams use the system malloc heap, and there's no // way to change that. That's ok, it's fine if some small percentage of our // data goes into the malloc heap. By the way, eng::ostringstream uses // the eng::malloc heap. // #ifndef ENG_MALLOC_HPP #define ENG_MALLOC_HPP #include #include #include namespace eng { #ifdef __linux__ void* malloc(size_t x); void free(void *p); void* realloc(void*, size_t); int memhash(); #else inline void *malloc(size_t x) { return ::malloc(x); } inline void free(void *p) { return ::free(p); } inline void *realloc(void *p, size_t x) { return ::realloc(p, x); } inline int memhash() { return 0; } #endif } // namespace eng // An allocator for lua states that uses eng::malloc and eng::free namespace eng { inline void *l_alloc(void *ud, void *ptr, size_t osize, size_t nsize) { if (nsize == 0) { ::eng::free(ptr); return NULL; } else { return ::eng::realloc(ptr, nsize); } } } // namespace eng // eng_allocator is similar to std::allocator, but allocates // objects using eng::malloc and eng::free. template class eng_allocator { public: using value_type = T; eng_allocator() noexcept {} template eng_allocator(eng_allocator const&) noexcept {} value_type* allocate(std::size_t n) { return static_cast(eng::malloc(n*sizeof(value_type))); } void deallocate(value_type* p, std::size_t) noexcept { eng::free(p); } }; // Another name for eng_allocator is eng::allocator. namespace eng { template using allocator = ::eng_allocator; } // namespace eng // Mandated equality and inequality operators for eng_allocator. template bool operator==(const eng_allocator &, const eng_allocator &) noexcept { return true; } template bool operator!=(const eng_allocator &, const eng_allocator &) noexcept { return false; } // eng::opnew. A class containing operator new and operator delete, // meant to be used as a base class for inheritance. namespace eng { class opnew { public: void *operator new(size_t size) { return ::eng::malloc(size); } void operator delete(void *p, size_t size) { return ::eng::free(p); } void *operator new[](size_t size) { return ::eng::malloc(size); } void operator delete[](void *p, size_t size) { return ::eng::free(p); } }; } // namespace eng // eng::nevernew. A class containing private operator new and // operator delete, making it impossible to 'new' the class. // This means the class must be embedded as a field in some other // class, and it gets allocated when its enclosing object gets // allocated. namespace eng { class nevernew { private: void *operator new(size_t size) { assert(false && "not supposed to 'new' this class"); } void operator delete(void *p, size_t size) { assert(false && "not supposed to 'delete' this class"); } void *operator new[](size_t size) { assert(false && "not supposed to 'new' this class"); } void operator delete[](void *p, size_t size) { assert(false && "not supposed to 'delete' this class"); } }; } // namespace eng // eng::make_shared allocates shared objects using eng::malloc. namespace eng { template inline ::std::shared_ptr make_shared(Args&&... args) { return std::allocate_shared(eng::allocator(), args...); } } // namespace eng // eng::make_unique doesn't do anything different than std::make_unique: they // both use operator new and delete. You must derive from eng::opnew to change // operator new and delete. namespace eng { template inline ::std::unique_ptr make_unique(Args&&... args) { return std::make_unique(args...); } } // namespace eng #endif // ENG_MALLOC_HPP