/*! \file alloc.h * \brief External definitions for memory allocation subsystem. * */ #ifndef ALLOC_H #define ALLOC_H constexpr int POOL_LBUF = 0; constexpr int POOL_SBUF = 1; constexpr int POOL_MBUF = 2; constexpr int POOL_BOOL = 3; constexpr int POOL_DESC = 4; constexpr int POOL_QENTRY = 5; constexpr int POOL_PCACHE = 6; constexpr int NUM_POOLS = 7; #define LBUF_SIZE 32768 // Large (must remain #define for preprocessor conditionals) constexpr int GBUF_SIZE = 1024; // Generic constexpr int MBUF_SIZE = 400; // Medium constexpr int PBUF_SIZE = 128; // Pathname constexpr int SBUF_SIZE = 64; // Small extern LIBMUX_API bool g_paranoid_alloc; // Pool memory footprint budget (#pool-oom). Buffers taken from the system // are never returned to it (they stay on the pool freelist), so the process's // pool footprint only grows on the slow allocation path and g_pool_system_bytes // never shrinks. When g_pool_limit_bytes is non-zero and the running system // total would exceed it, the allocator trips the per-command abort // (alarm_clock.alarmed) so a runaway command unwinds and frees its buffers to // the freelist instead of the process reaching a fatal OutOfMemory. Once // breached the ceiling stays in force until restart or the admin raises the // limit (the warning re-arms only if the limit is raised past the footprint). // 0 = unlimited (legacy behavior). Set by the engine from config. extern LIBMUX_API size_t g_pool_limit_bytes; extern LIBMUX_API size_t g_pool_system_bytes; // total bytes ever taken from system LIBMUX_API void pool_init(int, int); LIBMUX_API UTF8* pool_alloc(int poolnum, const UTF8* tag, const UTF8* file, const int line); LIBMUX_API UTF8* pool_alloc_lbuf(const UTF8* tag, const UTF8* file, const int line); LIBMUX_API void pool_free(int poolnum, UTF8* buf, const UTF8* file, const int line); LIBMUX_API void pool_free_lbuf(UTF8* buf, const UTF8* file, const int line); // alloc_notify_fn — callback for @list buffers output. // Engine sets this to a function that calls notify(). // typedef void (*ALLOC_NOTIFY_FN)(dbref player, const UTF8 *text); extern LIBMUX_API ALLOC_NOTIFY_FN g_alloc_notify_fn; // driver_config_sync_fn — callback so engine @admin of DRIVER_CONFIG knobs // can push mudconf into the driver's g_dc basket without a restart. Driver // registers this after the first GetConfig(); engine calls it from // cf_live_driver_int. nullptr until the driver is up (boot conf is applied // by the initial GetConfig after LoadGame). // typedef void (*DRIVER_CONFIG_SYNC_FN)(void); extern LIBMUX_API DRIVER_CONFIG_SYNC_FN g_driver_config_sync_fn; LIBMUX_API void list_bufstats(dbref); LIBMUX_API void list_buftrace(dbref); LIBMUX_API void pool_reset(void); #define alloc_lbuf(s) pool_alloc_lbuf(T(s), reinterpret_cast(__FILE__), __LINE__) #define free_lbuf(b) pool_free_lbuf(reinterpret_cast(b), reinterpret_cast(__FILE__), __LINE__) #define alloc_mbuf(s) pool_alloc(POOL_MBUF, T(s), reinterpret_cast(__FILE__), __LINE__) #define free_mbuf(b) pool_free(POOL_MBUF, reinterpret_cast(b), reinterpret_cast(__FILE__), __LINE__) #define alloc_sbuf(s) pool_alloc(POOL_SBUF, T(s), reinterpret_cast(__FILE__), __LINE__) #define free_sbuf(b) pool_free(POOL_SBUF, reinterpret_cast(b), reinterpret_cast(__FILE__), __LINE__) #define alloc_bool(s) reinterpret_cast(pool_alloc(POOL_BOOL, T(s), reinterpret_cast(__FILE__), __LINE__)) #define free_bool(b) pool_free(POOL_BOOL, reinterpret_cast(b), reinterpret_cast(__FILE__), __LINE__) #define alloc_qentry(s) reinterpret_cast(pool_alloc(POOL_QENTRY, T(s), reinterpret_cast(__FILE__), __LINE__)) #define free_qentry(b) pool_free(POOL_QENTRY, reinterpret_cast(b), reinterpret_cast(__FILE__), __LINE__) #define alloc_pcache(s) reinterpret_cast(pool_alloc(POOL_PCACHE, T(s), reinterpret_cast(__FILE__), __LINE__)) #define free_pcache(b) pool_free(POOL_PCACHE, reinterpret_cast(b), reinterpret_cast(__FILE__), __LINE__) // src is evaluated exactly once, and BEFORE the bounds test. // // It used to be evaluated only inside the test, which makes the macro // unsafe for any argument with a side effect: once the buffer is full the // argument stops being evaluated at all. A caller writing the natural // // while (p < end) safe_chr(*p++, buff, bufp); // // would then spin forever rather than truncate, because p stops advancing // exactly when the copy stops happening. That is not hypothetical -- it is // #1930, where color_ops' WP_SAFE had this shape and one softcode // expression could hang the whole (single-threaded) server. Twelve loops // there passed *s++ to it. // // No caller here passes a side-effecting argument today; every call site of // safe_chr / safe_sb_chr / safe_mb_chr / safe_bool was checked. This is a // trap with nobody standing on it, and the point of the temporary is that // stepping on it later is harmless. #define safe_copy_chr_ascii(src, buff, bufp, nSizeOfBuffer) \ { \ const UTF8 scca_chr_ = static_cast(src); \ if (static_cast(*bufp - buff) < nSizeOfBuffer) \ { \ **bufp = scca_chr_; \ (*bufp)++; \ } \ } #define safe_str(s,b,p) safe_copy_str_lbuf(s,b,p) #define safe_bool(c,b,p) safe_chr(((c) ? '1' : '0'),b,p) #define safe_sb_str(s,b,p) safe_copy_str(s,b,p,(SBUF_SIZE-1)) #define safe_mb_str(s,b,p) safe_copy_str(s,b,p,(MBUF_SIZE-1)) #define safe_chr_ascii(c,b,p) safe_copy_chr_ascii(static_cast(c),b,p,(LBUF_SIZE-1)) #define safe_sb_chr_ascii(c,b,p) safe_copy_chr_ascii(c,b,p,(SBUF_SIZE-1)) #define safe_mb_chr_ascii(c,b,p) safe_copy_chr_ascii(c,b,p,(MBUF_SIZE-1)) // Slowly transition from safe_chr to safe_chr_ascii and safe_chr_utf8, // safe_sb_chr to safe_sb_chr_ascii,and safe_mb_chr to safe_mb_chr_ascii. // #define safe_sb_chr safe_sb_chr_ascii #define safe_mb_chr safe_mb_chr_ascii #define safe_chr safe_chr_ascii // --------------------------------------------------------------- // RAII wrapper for pool-allocated LBUF buffers. // // Replaces stack-allocated UTF8 buf[LBUF_SIZE] with a heap-backed // pool buffer that is automatically freed when the scope exits. // This removes LBUF_SIZE from the stack frame, making it safe to // increase LBUF_SIZE without risking stack overflow in recursive // evaluation paths. // // Usage: // LBuf tmp(T("my_func")); // UTF8 *bp = tmp; // safe_str(text, tmp, &bp); // *bp = '\0'; // // The LBuf_Src macro captures __FILE__/__LINE__ for pool tracking: // LBuf_Src tmp("my_func"); // // To adopt a buffer returned by atr_get/atr_pget (caller-owned): // LBuf_Adopt buf(atr_pget(thing, A_DESC, &aowner, &aflags)); // class LBuf { UTF8 *m_buf; const UTF8 *m_file; int m_line; struct adopt_tag {}; LBuf(UTF8 *buf, const UTF8 *file, int line, adopt_tag) : m_buf(buf), m_file(file), m_line(line) {} public: LBuf(const UTF8 *tag, const UTF8 *file, int line) : m_buf(pool_alloc_lbuf(tag, file, line)), m_file(file), m_line(line) {} ~LBuf() { if (m_buf) pool_free_lbuf(m_buf, m_file, m_line); } LBuf(const LBuf &) = delete; LBuf &operator=(const LBuf &) = delete; LBuf(LBuf &&other) noexcept : m_buf(other.m_buf), m_file(other.m_file), m_line(other.m_line) { other.m_buf = nullptr; } LBuf &operator=(LBuf &&other) noexcept { if (this != &other) { if (m_buf) pool_free_lbuf(m_buf, m_file, m_line); m_buf = other.m_buf; m_file = other.m_file; m_line = other.m_line; other.m_buf = nullptr; } return *this; } static LBuf adopt(UTF8 *buf, const UTF8 *file, int line) { return LBuf(buf, file, line, adopt_tag{}); } // Relinquish ownership and return the raw buffer; the caller becomes // responsible for freeing it (e.g. a producer that returns an lbuf for the // caller to adopt). After release() the LBuf is empty and frees nothing. UTF8 *release() { UTF8 *b = m_buf; m_buf = nullptr; return b; } // Free the held buffer now and become empty (idempotent). void reset() { if (m_buf) pool_free_lbuf(m_buf, m_file, m_line); m_buf = nullptr; } UTF8 *get() { return m_buf; } const UTF8 *get() const { return m_buf; } operator UTF8 *() { return m_buf; } operator const UTF8 *() const { return m_buf; } UTF8 &operator[](size_t i) { return m_buf[i]; } const UTF8 &operator[](size_t i) const { return m_buf[i]; } }; #define LBuf_Src(tag) LBuf(T(tag), reinterpret_cast(__FILE__), __LINE__) #define LBuf_Adopt(expr) LBuf::adopt((expr), reinterpret_cast(__FILE__), __LINE__) //! \class LBufPtr // Owning, nullable, movable handle for a pool lbuf. // // LBuf always allocates in its constructor and is the right tool for a scoped // scratch buffer. LBufPtr differs in being default-constructible to empty, // reseatable, and able to release() its ownership. That makes it suitable for: // - conditional ownership (own a buffer on some paths, borrow on others), // - holding an lbuf in a struct/queue entry across function boundaries, // - a producer that hands the buffer back to its caller (construct, fill, // release() at the single success return — early/error returns free it). // // Usage: // LBufPtr buf; // empty // buf = LBufPtr_Src("producer"); // allocate // ... fill buf ... // return buf.release(); // caller now owns it // // LBufPtr held = LBufPtr_Adopt(atr_pget(...)); // adopt caller-owned buffer // class LBufPtr { UTF8 *m_buf; const UTF8 *m_file; int m_line; struct adopt_tag {}; LBufPtr(UTF8 *buf, const UTF8 *file, int line, adopt_tag) : m_buf(buf), m_file(file), m_line(line) {} public: LBufPtr() : m_buf(nullptr), m_file(nullptr), m_line(0) {} LBufPtr(const UTF8 *tag, const UTF8 *file, int line) : m_buf(pool_alloc_lbuf(tag, file, line)), m_file(file), m_line(line) {} ~LBufPtr() { if (m_buf) pool_free_lbuf(m_buf, m_file, m_line); } LBufPtr(const LBufPtr &) = delete; LBufPtr &operator=(const LBufPtr &) = delete; LBufPtr(LBufPtr &&other) noexcept : m_buf(other.m_buf), m_file(other.m_file), m_line(other.m_line) { other.m_buf = nullptr; } LBufPtr &operator=(LBufPtr &&other) noexcept { if (this != &other) { if (m_buf) pool_free_lbuf(m_buf, m_file, m_line); m_buf = other.m_buf; m_file = other.m_file; m_line = other.m_line; other.m_buf = nullptr; } return *this; } static LBufPtr adopt(UTF8 *buf, const UTF8 *file, int line) { return LBufPtr(buf, file, line, adopt_tag{}); } // Relinquish ownership; caller becomes responsible for freeing. UTF8 *release() { UTF8 *b = m_buf; m_buf = nullptr; return b; } // Free the held buffer now and become empty (idempotent). void reset() { if (m_buf) pool_free_lbuf(m_buf, m_file, m_line); m_buf = nullptr; } UTF8 *get() { return m_buf; } const UTF8 *get() const { return m_buf; } operator UTF8 *() { return m_buf; } operator const UTF8 *() const { return m_buf; } UTF8 &operator[](size_t i) { return m_buf[i]; } const UTF8 &operator[](size_t i) const { return m_buf[i]; } explicit operator bool() const { return m_buf != nullptr; } }; #define LBufPtr_Src(tag) LBufPtr(T(tag), reinterpret_cast(__FILE__), __LINE__) #define LBufPtr_Adopt(expr) LBufPtr::adopt((expr), reinterpret_cast(__FILE__), __LINE__) #include //! \struct RegBuffer // Shared buffer that packs multiple register values into a single // LBUF_SIZE block. Reference-counted via std::shared_ptr. struct RegBuffer { UTF8 data[LBUF_SIZE]; size_t used; RegBuffer() : used(0) { memset(data, 0, sizeof(data)); } }; //! \struct reg_ref // A register value — a view into a shared RegBuffer. // Allocated with new/delete (not pool-allocated). // The refcount tracks how many pointers reference this reg_ref. // The shared_ptr automatically manages the underlying buffer. struct reg_ref { int refcount; std::shared_ptr buf; size_t reg_len; UTF8 *reg_ptr; reg_ref() : refcount(0), reg_len(0), reg_ptr(nullptr) {} }; #endif //!ALLOC_H