Lots more work on eng::malloc
This commit is contained in:
@@ -65,7 +65,7 @@ LUA_OBJ_FILES=\
|
|||||||
CORE_OBJ_FILES=\
|
CORE_OBJ_FILES=\
|
||||||
obj/invocation.o\
|
obj/invocation.o\
|
||||||
obj/spookyv2.o\
|
obj/spookyv2.o\
|
||||||
obj/two-mallocs.o\
|
obj/eng-malloc.o\
|
||||||
obj/debugcollector.o\
|
obj/debugcollector.o\
|
||||||
obj/drivenengine.o\
|
obj/drivenengine.o\
|
||||||
obj/dummycert.o\
|
obj/dummycert.o\
|
||||||
|
|||||||
@@ -263,7 +263,6 @@ public:
|
|||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
mallopt(M_MMAP_MAX, 0); // Keep malloc in the 'brk' area.
|
|
||||||
disable_randomization(argc, argv);
|
disable_randomization(argc, argv);
|
||||||
allocate_buffers();
|
allocate_buffers();
|
||||||
enable_tty_raw();
|
enable_tty_raw();
|
||||||
|
|||||||
122
luprex/core/cpp/eng-malloc.cpp
Normal file
122
luprex/core/cpp/eng-malloc.cpp
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
|
||||||
|
// We only use a custom allocator on linux (for now)
|
||||||
|
// The allocator we chose is 'dlmalloc' (doug lea's malloc).
|
||||||
|
// It's a good allocator for single-threaded programs.
|
||||||
|
// It needs to be configured by setting a bunch of defines.
|
||||||
|
//
|
||||||
|
#ifdef __linux__
|
||||||
|
|
||||||
|
// We need this to define dlmalloc, not malloc.
|
||||||
|
#define USE_DL_PREFIX 1
|
||||||
|
|
||||||
|
// We don't need mspaces.
|
||||||
|
#define ONLY_MSPACES 0
|
||||||
|
#define MSPACES 0
|
||||||
|
|
||||||
|
// We don't need mallinfo
|
||||||
|
#define NO_MALLINFO 1
|
||||||
|
|
||||||
|
// We don't need dlmalloc_inspect_all
|
||||||
|
#define MALLOC_INSPECT_ALL 0
|
||||||
|
|
||||||
|
// Disable locking. The entire engine is single-threaded.
|
||||||
|
#define USE_LOCKS 0
|
||||||
|
|
||||||
|
// This allocator can use sbrk to obtain RAM. We're going to
|
||||||
|
// emulate sbrk, in order to put it in a different memory region
|
||||||
|
// than the system malloc, which also uses sbrk.
|
||||||
|
#define HAVE_MORECORE 1
|
||||||
|
#define MORECORE emulated_sbrk
|
||||||
|
#define MORECORE_CANNOT_TRIM 1
|
||||||
|
|
||||||
|
// We won't let the memory allocator use mmap. This is because
|
||||||
|
// opening the replay log may be implemented in stdio using mmap.
|
||||||
|
// So using mmap might trigger different results under replay.
|
||||||
|
#define HAVE_MMAP 0
|
||||||
|
|
||||||
|
// Fix a warning in dlmalloc.
|
||||||
|
#define MAX_RELEASE_CHECK_RATE INT_MAX
|
||||||
|
|
||||||
|
// Don't generate random seeds, or time-based seeds.
|
||||||
|
#define USE_DEV_RANDOM 0
|
||||||
|
#define LACKS_TIME_H 1
|
||||||
|
|
||||||
|
// Don't export any dlmalloc functions.
|
||||||
|
#define DLMALLOC_EXPORT static inline
|
||||||
|
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cassert>
|
||||||
|
#include <climits>
|
||||||
|
|
||||||
|
static char *emulated_sbrk_base;
|
||||||
|
static intptr_t emulated_sbrk_used;
|
||||||
|
static intptr_t emulated_sbrk_limit;
|
||||||
|
|
||||||
|
// We assume that the increments are already aligned to the system
|
||||||
|
// page size, because dlmalloc does that for us.
|
||||||
|
//
|
||||||
|
// Our emulated sbrk cannot trim the memory size. It can only grow.
|
||||||
|
//
|
||||||
|
static void *emulated_sbrk(intptr_t increment) {
|
||||||
|
if (emulated_sbrk_base == 0) {
|
||||||
|
emulated_sbrk_limit = intptr_t(64) * 1024 * 1024 * 1024;
|
||||||
|
void *map = mmap(nullptr, emulated_sbrk_limit, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||||
|
assert(map != MAP_FAILED);
|
||||||
|
assert(map != nullptr);
|
||||||
|
emulated_sbrk_base = (char *)map;
|
||||||
|
emulated_sbrk_used = 0;
|
||||||
|
}
|
||||||
|
int64_t old_used = emulated_sbrk_used;
|
||||||
|
int64_t new_used = emulated_sbrk_used + increment;
|
||||||
|
if (new_used > emulated_sbrk_limit) {
|
||||||
|
return (void *)(-1);
|
||||||
|
}
|
||||||
|
assert(new_used >= old_used);
|
||||||
|
emulated_sbrk_used = new_used;
|
||||||
|
if (new_used > old_used) {
|
||||||
|
int status = mprotect(emulated_sbrk_base + old_used, new_used - old_used, PROT_READ | PROT_WRITE);
|
||||||
|
assert(status == 0);
|
||||||
|
}
|
||||||
|
return emulated_sbrk_base + old_used;
|
||||||
|
}
|
||||||
|
|
||||||
|
#include "dlmalloc/dlmalloc.c"
|
||||||
|
|
||||||
|
namespace eng {
|
||||||
|
static uint32_t hash;
|
||||||
|
void *malloc(size_t size) {
|
||||||
|
void *result = dlmalloc(size);
|
||||||
|
hash += uint32_t(uintptr_t(result)) + 0x83748374;
|
||||||
|
hash += hash << 10;
|
||||||
|
hash ^= hash >> 6;
|
||||||
|
hash += uint32_t(size) + 0x85893489;
|
||||||
|
hash += hash << 10;
|
||||||
|
hash ^= hash >> 6;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
void *realloc(void *p, size_t size) {
|
||||||
|
void *result = dlrealloc(p, size);
|
||||||
|
hash += uint32_t(uintptr_t(p)) + 0x74741912;
|
||||||
|
hash += hash << 10;
|
||||||
|
hash ^= hash >> 6;
|
||||||
|
hash += uint32_t(uintptr_t(result)) + 0x68843823;
|
||||||
|
hash += hash << 10;
|
||||||
|
hash ^= hash >> 6;
|
||||||
|
hash += uint32_t(size) + 0x81444120;
|
||||||
|
hash += hash << 10;
|
||||||
|
hash ^= hash >> 6;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
void free(void *p) {
|
||||||
|
hash += uint32_t(uintptr_t(p)) + 0x87823448;
|
||||||
|
dlfree(p);
|
||||||
|
}
|
||||||
|
int memhash() {
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
} // namespace eng
|
||||||
|
|
||||||
|
#endif // ifdef __linux__
|
||||||
|
|
||||||
|
|
||||||
152
luprex/core/cpp/eng-malloc.hpp
Normal file
152
luprex/core/cpp/eng-malloc.hpp
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
//
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
// 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 using STL classes, you need to use the 'eng' variant of those classes:
|
||||||
|
//
|
||||||
|
// - eng::string instead of std::string (include "wrap-string.hpp")
|
||||||
|
// - eng::vector instead of std::vector (include "wrap-vector.hpp")
|
||||||
|
// - eng::map instead of std::map (include "wrap-map.hpp")
|
||||||
|
// - etc.
|
||||||
|
//
|
||||||
|
// These eng classes allocate memory using eng::malloc instead of malloc.
|
||||||
|
//
|
||||||
|
// * All your classes must derive from eng::opnew. This adds a custom operator
|
||||||
|
// new and operator delete to your class, which allocate using eng::malloc.
|
||||||
|
//
|
||||||
|
// * Use eng::make_shared and eng::make_unique instead of std::make_shared and
|
||||||
|
// std::make_unique.
|
||||||
|
//
|
||||||
|
// * Simple classes like std::pair, std::string_view, std::less, std::hash, and
|
||||||
|
// so forth don't allocate memory. Those classes are not wrapped. There is
|
||||||
|
// no eng::pair, eng::less, etc.
|
||||||
|
//
|
||||||
|
// * Be aware that most C++ streams use the system malloc heap, and there's no
|
||||||
|
// way to change that. Fortunately, eng::ostringstream uses the dlmalloc
|
||||||
|
// heap.
|
||||||
|
//
|
||||||
|
// * 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 can break determinism of
|
||||||
|
// replay.
|
||||||
|
//
|
||||||
|
#ifndef ENG_MALLOC_HPP
|
||||||
|
#define ENG_MALLOC_HPP
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
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(x); }
|
||||||
|
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 T>
|
||||||
|
class eng_allocator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using value_type = T;
|
||||||
|
eng_allocator() noexcept {}
|
||||||
|
template <class U> eng_allocator(eng_allocator<U> const&) noexcept {}
|
||||||
|
|
||||||
|
value_type* allocate(std::size_t n)
|
||||||
|
{
|
||||||
|
return static_cast<value_type*>(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<class T>
|
||||||
|
using allocator = ::eng_allocator<T>;
|
||||||
|
} // namespace eng
|
||||||
|
|
||||||
|
// Mandated equality and inequality operators for eng_allocator.
|
||||||
|
template <class T, class U>
|
||||||
|
bool operator==(const eng_allocator<T> &, const eng_allocator<U> &) noexcept
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
template <class T, class U>
|
||||||
|
bool operator!=(const eng_allocator<T> &, const eng_allocator<U> &) 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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace eng
|
||||||
|
|
||||||
|
// eng::make_shared allocates shared objects using eng::malloc.
|
||||||
|
namespace eng {
|
||||||
|
template<class T, class... Args>
|
||||||
|
inline ::std::shared_ptr<T> make_shared(Args&&... args) {
|
||||||
|
static eng::allocator<T> alloc;
|
||||||
|
return std::allocate_shared<T>(alloc, args...);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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<class T, class... Args>
|
||||||
|
inline ::std::unique_ptr<T> make_unique(Args&&... args) {
|
||||||
|
return std::make_unique<T>(args...);
|
||||||
|
}
|
||||||
|
} // namespace eng
|
||||||
|
|
||||||
|
#endif // ENG_MALLOC_HPP
|
||||||
|
|
||||||
@@ -95,7 +95,7 @@ public:
|
|||||||
virtual void event_update() {
|
virtual void event_update() {
|
||||||
double clock = get_clock();
|
double clock = get_clock();
|
||||||
if (clock > last_clock_ + 0.5) {
|
if (clock > last_clock_ + 0.5) {
|
||||||
int ms = dlmalloc_hash();
|
int ms = eng::memhash();
|
||||||
stdostream() << std::fixed << std::setprecision(2) << clock << " " << std::hex << ms << " ";
|
stdostream() << std::fixed << std::setprecision(2) << clock << " " << std::hex << ms << " ";
|
||||||
count_++;
|
count_++;
|
||||||
last_clock_ = clock;
|
last_clock_ = clock;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
class Client : public eng::heap {
|
class Client : public eng::opnew {
|
||||||
public:
|
public:
|
||||||
int64_t actor_id_;
|
int64_t actor_id_;
|
||||||
SharedChannel channel_;
|
SharedChannel channel_;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#include "two-mallocs.hpp"
|
#include "eng-malloc.hpp"
|
||||||
#include "luastack.hpp"
|
#include "luastack.hpp"
|
||||||
#include "wrap-string.hpp"
|
#include "wrap-string.hpp"
|
||||||
#include "wrap-vector.hpp"
|
#include "wrap-vector.hpp"
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
LuaConsole::LuaConsole() {
|
LuaConsole::LuaConsole() {
|
||||||
lua_state_ = LuaStack::newstate(lalloc_dlmalloc);
|
lua_state_ = LuaStack::newstate(eng::l_alloc);
|
||||||
clear_raw_input();
|
clear_raw_input();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
|
|
||||||
LuaSnap::LuaSnap() {
|
LuaSnap::LuaSnap() {
|
||||||
state_ = LuaStack::newstate(lalloc_dlmalloc);
|
state_ = LuaStack::newstate(eng::l_alloc);
|
||||||
LuaStack LS(state_);
|
LuaStack LS(state_);
|
||||||
|
|
||||||
// Create the persist table and the unpersist table.
|
// Create the persist table and the unpersist table.
|
||||||
|
|||||||
@@ -34,11 +34,21 @@ static int panicf(lua_State *L) {
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// An allocator for lua states that uses system malloc and system free.
|
||||||
|
static void *l_alloc(void *ud, void *ptr, size_t osize, size_t nsize) {
|
||||||
|
if (nsize == 0) {
|
||||||
|
free(ptr);
|
||||||
|
return NULL;
|
||||||
|
} else {
|
||||||
|
return realloc(ptr, nsize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
lua_State *LuaStack::newstate (lua_Alloc allocf) {
|
lua_State *LuaStack::newstate (lua_Alloc allocf) {
|
||||||
lua_State *L = lua_newstate(allocf, NULL);
|
if (allocf == nullptr) allocf = l_alloc;
|
||||||
if (L) lua_atpanic(L, &panicf);
|
lua_State *L = lua_newstate(allocf, NULL);
|
||||||
return L;
|
if (L) lua_atpanic(L, &panicf);
|
||||||
|
return L;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LuaStack::ckboolean(LuaSlot s) const {
|
bool LuaStack::ckboolean(LuaSlot s) const {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
|
||||||
struct PrintBufferCore : public eng::heap {
|
struct PrintBufferCore : public eng::opnew {
|
||||||
// The most recent lines printed.
|
// The most recent lines printed.
|
||||||
eng::deque<eng::string> lines_;
|
eng::deque<eng::string> lines_;
|
||||||
|
|
||||||
|
|||||||
@@ -89,7 +89,7 @@
|
|||||||
|
|
||||||
struct PrintBufferCore;
|
struct PrintBufferCore;
|
||||||
|
|
||||||
class PrintBuffer : public eng::heap {
|
class PrintBuffer : public eng::opnew {
|
||||||
private:
|
private:
|
||||||
PrintBufferCore *core_;
|
PrintBufferCore *core_;
|
||||||
|
|
||||||
@@ -138,7 +138,7 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
class PrintChanneler : public eng::heap {
|
class PrintChanneler : public eng::opnew {
|
||||||
private:
|
private:
|
||||||
int64_t line_;
|
int64_t line_;
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -498,7 +498,7 @@ void SourceDB::deserialize_source(util::LuaSourceVec *sv, StreamBuffer *sb) {
|
|||||||
|
|
||||||
// This function should not touch the dlmalloc heap.
|
// This function should not touch the dlmalloc heap.
|
||||||
void SourceDB::register_lua_builtins() {
|
void SourceDB::register_lua_builtins() {
|
||||||
lua_State *L = LuaStack::newstate(lalloc_malloc);
|
lua_State *L = LuaStack::newstate(nullptr);
|
||||||
luaL_openlibs(L);
|
luaL_openlibs(L);
|
||||||
LuaVar globals,classtab,func;
|
LuaVar globals,classtab,func;
|
||||||
LuaStack LS(L, globals, classtab, func);
|
LuaStack LS(L, globals, classtab, func);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#include "wrap-string.hpp"
|
#include "wrap-string.hpp"
|
||||||
|
|
||||||
#include "two-mallocs.hpp"
|
#include "eng-malloc.hpp"
|
||||||
#include "streambuffer.hpp"
|
#include "streambuffer.hpp"
|
||||||
#include "spookyv2.hpp"
|
#include "spookyv2.hpp"
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ StreamBuffer::StreamBuffer() {
|
|||||||
|
|
||||||
StreamBuffer::StreamBuffer(int64_t size, bool fixed) {
|
StreamBuffer::StreamBuffer(int64_t size, bool fixed) {
|
||||||
assert(size >= 0);
|
assert(size >= 0);
|
||||||
init(fixed, true, (char*)dlmalloc(size), size);
|
init(fixed, true, (char*)eng::malloc(size), size);
|
||||||
}
|
}
|
||||||
|
|
||||||
StreamBuffer::StreamBuffer(const char *s, int64_t size) {
|
StreamBuffer::StreamBuffer(const char *s, int64_t size) {
|
||||||
@@ -40,7 +40,7 @@ StreamBuffer::StreamBuffer(const eng::string &src) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
StreamBuffer::~StreamBuffer() {
|
StreamBuffer::~StreamBuffer() {
|
||||||
if (owned_ && (buf_lo_ != 0)) dlfree(buf_lo_);
|
if (owned_ && (buf_lo_ != 0)) eng::free(buf_lo_);
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t StreamBuffer::total_reads() const {
|
int64_t StreamBuffer::total_reads() const {
|
||||||
@@ -87,9 +87,9 @@ void StreamBuffer::make_space_slow(int64_t bytes) {
|
|||||||
} else if (existing_size >= desired_size) {
|
} else if (existing_size >= desired_size) {
|
||||||
if (data_size > 0) memcpy(buf_lo_, read_cursor_, data_size);
|
if (data_size > 0) memcpy(buf_lo_, read_cursor_, data_size);
|
||||||
} else {
|
} else {
|
||||||
char *nbuf = (char *)dlmalloc(desired_size);
|
char *nbuf = (char *)eng::malloc(desired_size);
|
||||||
if (data_size > 0) memcpy(nbuf, read_cursor_, data_size);
|
if (data_size > 0) memcpy(nbuf, read_cursor_, data_size);
|
||||||
if (buf_lo_ != nullptr) dlfree(buf_lo_);
|
if (buf_lo_ != nullptr) eng::free(buf_lo_);
|
||||||
buf_lo_ = nbuf;
|
buf_lo_ = nbuf;
|
||||||
buf_hi_ = nbuf + desired_size;
|
buf_hi_ = nbuf + desired_size;
|
||||||
}
|
}
|
||||||
@@ -116,7 +116,7 @@ char *StreamBuffer::get_overwrite(int64_t size, int64_t write_count_after) {
|
|||||||
void StreamBuffer::clear() {
|
void StreamBuffer::clear() {
|
||||||
assert(owned_);
|
assert(owned_);
|
||||||
if (!fixed_size_) {
|
if (!fixed_size_) {
|
||||||
if (buf_lo_ != nullptr) dlfree(buf_lo_);
|
if (buf_lo_ != nullptr) eng::free(buf_lo_);
|
||||||
buf_lo_ = 0;
|
buf_lo_ = 0;
|
||||||
buf_hi_ = 0;
|
buf_hi_ = 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
|
|
||||||
// We only use a custom allocator on linux (for now)
|
|
||||||
// The allocator we chose is 'dlmalloc' (doug lea's malloc).
|
|
||||||
// It's a good allocator for single-threaded programs.
|
|
||||||
// It needs to be configured by setting a bunch of defines.
|
|
||||||
//
|
|
||||||
#ifdef __linux__
|
|
||||||
|
|
||||||
// We need this to define dlmalloc, not malloc.
|
|
||||||
#define USE_DL_PREFIX 1
|
|
||||||
|
|
||||||
// We don't need mspaces.
|
|
||||||
#define ONLY_MSPACES 0
|
|
||||||
#define MSPACES 0
|
|
||||||
|
|
||||||
// We don't need mallinfo
|
|
||||||
#define NO_MALLINFO 1
|
|
||||||
|
|
||||||
// We don't need dlmalloc_inspect_all
|
|
||||||
#define MALLOC_INSPECT_ALL 0
|
|
||||||
|
|
||||||
// We don't need dlmalloc_stats.
|
|
||||||
#define NO_MALLOC_STATS 1
|
|
||||||
|
|
||||||
// Disable locking. The entire engine is single-threaded.
|
|
||||||
#define USE_LOCKS 0
|
|
||||||
|
|
||||||
// For now, we'll let the allocator use mmap to get memory.
|
|
||||||
// It's not clear if this is going to be deterministic.
|
|
||||||
#define HAVE_MMAP 1
|
|
||||||
|
|
||||||
// One way to force determinism would be to emulate sbrk
|
|
||||||
// using mmap and mremap, always putting the heap at a fixed
|
|
||||||
// address. For now, we're not doing that, we're just
|
|
||||||
// using mmap and relying on that being deterministic (we hope).
|
|
||||||
#define HAVE_MORECORE 0
|
|
||||||
#define MORECORE emulate_sbrk
|
|
||||||
|
|
||||||
// The properties of mmap under linux
|
|
||||||
#define MMAP_CLEARS 1
|
|
||||||
#define HAVE_MREMAP 1
|
|
||||||
|
|
||||||
// Don't generate random seeds, or time-based seeds.
|
|
||||||
#define USE_DEV_RANDOM 0
|
|
||||||
#define LACKS_TIME_H 1
|
|
||||||
|
|
||||||
#include "dlmalloc/dlmalloc.c"
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
int dlmalloc_hash() {
|
|
||||||
void *blocks[15];
|
|
||||||
int hash = 0;
|
|
||||||
for (int i = 0; i < 15; i++) {
|
|
||||||
void *blk = dlmalloc(1 << i);
|
|
||||||
blocks[i] = blk;
|
|
||||||
hash = (hash * 17) + (int)(intptr_t)(blk);
|
|
||||||
}
|
|
||||||
for (int i = 0; i < 15; i++) {
|
|
||||||
dlfree(blocks[i]);
|
|
||||||
}
|
|
||||||
return (hash & 0x7FFFFFFF) | (0x40000000);
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,157 +0,0 @@
|
|||||||
//
|
|
||||||
// Two Mallocs:
|
|
||||||
//
|
|
||||||
// The purpose of this file is to put all engine data structures into
|
|
||||||
// a private heap that isn't used by any other library. This helps
|
|
||||||
// achieve determinism when playing a replay log.
|
|
||||||
//
|
|
||||||
// For the engine's heap, we chose Doug Lea's Malloc (dlmalloc). It's
|
|
||||||
// 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
|
|
||||||
// may be possible to replace dlmalloc with another malloc, but
|
|
||||||
// dlmalloc is pretty easy to work with.
|
|
||||||
//
|
|
||||||
// In order to get all engine data structures into the dlmalloc heap,
|
|
||||||
// you need to jump through quite a few hoops:
|
|
||||||
//
|
|
||||||
// * When using STL classes, you need to use the 'eng' variant of those
|
|
||||||
// classes:
|
|
||||||
//
|
|
||||||
// use 'eng::string' instead of std::string (include "wrap-string.hpp")
|
|
||||||
// use 'eng::vector' instead of std::vector (include "wrap-vector.hpp")
|
|
||||||
// use 'eng::map' instead of std::map (include "wrap-map.hpp")
|
|
||||||
// etc.
|
|
||||||
//
|
|
||||||
// These eng classes allocate memory from dlmalloc instead of malloc.
|
|
||||||
//
|
|
||||||
// * All your classes must derive from eng::heap. This adds a custom
|
|
||||||
// operator new and operator delete to your class.
|
|
||||||
//
|
|
||||||
// * Use eng::make_shared and eng::make_unique instead of std::make_shared
|
|
||||||
// and std::make_unique.
|
|
||||||
//
|
|
||||||
// * Simple classes like std::pair, std::string_view, std::less,
|
|
||||||
// std::hash, and so forth don't allocate memory. Those classes
|
|
||||||
// are not wrapped. There is no eng::pair, eng:less, etc.
|
|
||||||
//
|
|
||||||
// * Be aware that most C++ streams use the malloc heap, and there's no
|
|
||||||
// way to change that. Fortunately, eng::ostringstream uses the
|
|
||||||
// dlmalloc heap.
|
|
||||||
//
|
|
||||||
// * 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 dlmalloc heap. This can break
|
|
||||||
// determinism of replay.
|
|
||||||
//
|
|
||||||
#ifndef TWO_MALLOCS_HPP
|
|
||||||
#define TWO_MALLOCS_HPP
|
|
||||||
|
|
||||||
#include <cstddef>
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
// dlmalloc is only used on linux.
|
|
||||||
extern "C" {
|
|
||||||
#ifdef __linux__
|
|
||||||
void* dlmalloc(size_t x);
|
|
||||||
void dlfree(void *p);
|
|
||||||
void* dlrealloc(void*, size_t);
|
|
||||||
#else
|
|
||||||
void* dlmalloc(size_t x) { return malloc(x); }
|
|
||||||
void dlfree(void *p) { free(p); }
|
|
||||||
void* dlrealloc(void *p, size_t x) { return realloc(p,x); }
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the current state of the dlmalloc allocator as a 30-bit hash.
|
|
||||||
extern int dlmalloc_hash();
|
|
||||||
|
|
||||||
// An allocator for lua states that uses dlmalloc and dlfree.
|
|
||||||
inline void *lalloc_dlmalloc (void *ud, void *ptr, size_t osize, size_t nsize) {
|
|
||||||
if (nsize == 0) {
|
|
||||||
dlfree(ptr);
|
|
||||||
return NULL;
|
|
||||||
} else {
|
|
||||||
return dlrealloc(ptr, nsize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// An allocator for lua states that uses malloc and free.
|
|
||||||
inline void *lalloc_malloc (void *ud, void *ptr, size_t osize, size_t nsize) {
|
|
||||||
if (nsize == 0) {
|
|
||||||
free(ptr);
|
|
||||||
return NULL;
|
|
||||||
} else {
|
|
||||||
return realloc(ptr, nsize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// eng:allocator: a class meant to be used as an STL Allocator.
|
|
||||||
// Causes objects to be allocated using dlmalloc and dlfree.
|
|
||||||
template <class T>
|
|
||||||
class dlmalloc_allocator
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
using value_type = T;
|
|
||||||
dlmalloc_allocator() noexcept {}
|
|
||||||
template <class U> dlmalloc_allocator(dlmalloc_allocator<U> const&) noexcept {}
|
|
||||||
|
|
||||||
value_type* allocate(std::size_t n)
|
|
||||||
{
|
|
||||||
return static_cast<value_type*>(dlmalloc(n*sizeof(value_type)));
|
|
||||||
}
|
|
||||||
|
|
||||||
void deallocate(value_type* p, std::size_t) noexcept
|
|
||||||
{
|
|
||||||
dlfree(p);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template <class T, class U>
|
|
||||||
bool operator==(const dlmalloc_allocator<T> &, const dlmalloc_allocator<U> &) noexcept
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
template <class T, class U>
|
|
||||||
bool operator!=(const dlmalloc_allocator<T> &, const dlmalloc_allocator<U> &) noexcept
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Another name for dlmalloc_allocator
|
|
||||||
namespace eng {
|
|
||||||
template <class T>
|
|
||||||
using allocator = dlmalloc_allocator<T>;
|
|
||||||
}
|
|
||||||
|
|
||||||
// eng::heap a class meant to be used as a base class.
|
|
||||||
// Causes 'new' and 'delete' for this class to use dlmalloc and dlfree.
|
|
||||||
namespace eng {
|
|
||||||
class heap {
|
|
||||||
public:
|
|
||||||
void *operator new(size_t size)
|
|
||||||
{
|
|
||||||
return dlmalloc(size);
|
|
||||||
}
|
|
||||||
|
|
||||||
void operator delete(void *p, size_t size)
|
|
||||||
{
|
|
||||||
return dlfree(p);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
// eng::make_shared allocates shared objects in the dlmalloc heap.
|
|
||||||
namespace eng {
|
|
||||||
template<class T, class... Args>
|
|
||||||
inline std::shared_ptr<T> make_shared(Args&&... args) {
|
|
||||||
static eng::allocator<T> alloc;
|
|
||||||
return std::allocate_shared<T>(alloc, args...);
|
|
||||||
}
|
|
||||||
template<class T, class... Args>
|
|
||||||
inline std::unique_ptr<T> make_unique(Args&&... args) {
|
|
||||||
return std::make_unique<T>(args...);
|
|
||||||
}
|
|
||||||
} // namespace eng
|
|
||||||
|
|
||||||
#endif // TWO_MALLOCS_HPP
|
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ with open(f"wrap-{dash}.hpp", "w") as f:
|
|||||||
print(f"#ifndef WRAP_{ubase}_HPP", file=f)
|
print(f"#ifndef WRAP_{ubase}_HPP", file=f)
|
||||||
print(f"#define WRAP_{ubase}_HPP", file=f)
|
print(f"#define WRAP_{ubase}_HPP", file=f)
|
||||||
print("", file=f)
|
print("", file=f)
|
||||||
print('#include "two-mallocs.hpp"', file=f)
|
print('#include "eng-malloc.hpp"', file=f)
|
||||||
print(f"#include <{base}>", file=f)
|
print(f"#include <{base}>", file=f)
|
||||||
print("", file=f)
|
print("", file=f)
|
||||||
print("namespace eng {", file=f)
|
print("namespace eng {", file=f)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#ifndef WRAP_DEQUE_HPP
|
#ifndef WRAP_DEQUE_HPP
|
||||||
#define WRAP_DEQUE_HPP
|
#define WRAP_DEQUE_HPP
|
||||||
|
|
||||||
#include "two-mallocs.hpp"
|
#include "eng-malloc.hpp"
|
||||||
#include <deque>
|
#include <deque>
|
||||||
|
|
||||||
namespace eng {
|
namespace eng {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#ifndef WRAP_MAP_HPP
|
#ifndef WRAP_MAP_HPP
|
||||||
#define WRAP_MAP_HPP
|
#define WRAP_MAP_HPP
|
||||||
|
|
||||||
#include "two-mallocs.hpp"
|
#include "eng-malloc.hpp"
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
namespace eng {
|
namespace eng {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#ifndef WRAP_SET_HPP
|
#ifndef WRAP_SET_HPP
|
||||||
#define WRAP_SET_HPP
|
#define WRAP_SET_HPP
|
||||||
|
|
||||||
#include "two-mallocs.hpp"
|
#include "eng-malloc.hpp"
|
||||||
#include <set>
|
#include <set>
|
||||||
|
|
||||||
namespace eng {
|
namespace eng {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#ifndef WRAP_SSTREAM_HPP
|
#ifndef WRAP_SSTREAM_HPP
|
||||||
#define WRAP_SSTREAM_HPP
|
#define WRAP_SSTREAM_HPP
|
||||||
|
|
||||||
#include "two-mallocs.hpp"
|
#include "eng-malloc.hpp"
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
namespace eng {
|
namespace eng {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#ifndef WRAP_STRING_HPP
|
#ifndef WRAP_STRING_HPP
|
||||||
#define WRAP_STRING_HPP
|
#define WRAP_STRING_HPP
|
||||||
|
|
||||||
#include "two-mallocs.hpp"
|
#include "eng-malloc.hpp"
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace eng {
|
namespace eng {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#ifndef WRAP_UNORDERED_MAP_HPP
|
#ifndef WRAP_UNORDERED_MAP_HPP
|
||||||
#define WRAP_UNORDERED_MAP_HPP
|
#define WRAP_UNORDERED_MAP_HPP
|
||||||
|
|
||||||
#include "two-mallocs.hpp"
|
#include "eng-malloc.hpp"
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
namespace eng {
|
namespace eng {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#ifndef WRAP_UNORDERED_SET_HPP
|
#ifndef WRAP_UNORDERED_SET_HPP
|
||||||
#define WRAP_UNORDERED_SET_HPP
|
#define WRAP_UNORDERED_SET_HPP
|
||||||
|
|
||||||
#include "two-mallocs.hpp"
|
#include "eng-malloc.hpp"
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
|
||||||
namespace eng {
|
namespace eng {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#ifndef WRAP_VECTOR_HPP
|
#ifndef WRAP_VECTOR_HPP
|
||||||
#define WRAP_VECTOR_HPP
|
#define WRAP_VECTOR_HPP
|
||||||
|
|
||||||
#include "two-mallocs.hpp"
|
#include "eng-malloc.hpp"
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace eng {
|
namespace eng {
|
||||||
|
|||||||
@@ -4286,10 +4286,9 @@ static int sys_trim(mstate m, size_t pad) {
|
|||||||
if (HAVE_MMAP &&
|
if (HAVE_MMAP &&
|
||||||
sp->size >= extra &&
|
sp->size >= extra &&
|
||||||
!has_segment_link(m, sp)) { /* can't shrink if pinned */
|
!has_segment_link(m, sp)) { /* can't shrink if pinned */
|
||||||
size_t newsize = sp->size - extra;
|
|
||||||
/* Prefer mremap, fall back to munmap */
|
/* Prefer mremap, fall back to munmap */
|
||||||
if ((CALL_MREMAP(sp->base, sp->size, newsize, 0) != MFAIL) ||
|
if ((CALL_MREMAP(sp->base, sp->size, (sp->size - extra), 0) != MFAIL) ||
|
||||||
(CALL_MUNMAP(sp->base + newsize, extra) == 0)) {
|
(CALL_MUNMAP(sp->base + (sp->size - extra), extra) == 0)) {
|
||||||
released = extra;
|
released = extra;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user