/*
* dj64 - 64bit djgpp-compatible tool-chain
* Copyright (C) 2021-2024 @stsp
*
* FDPP - freedos port to modern C++
* Copyright (C) 2018 Stas Sergeev (stsp)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see .
*/
/*
* NOTE: this file is a port of smalloc.c file from fdpp project.
* In fdpp project it is distributed under the terms of GNU GPLv3+
* with an authorship of Stas Sergeev (stsp).
* As an author of the aforementioned smalloc.c, I donate the code
* to dj64dev project, allowing to re-license it under the terms
* of GNU LGPLv3+.
*
* --stsp
*/
/*
* smalloc - small memory allocator for dosemu.
*
* Author: Stas Sergeev
*/
#include
#include
#include
#include
#include
#include
#include "smalloc.h"
#define POOL_USED(p) (p->mn.used || p->mn.next)
#ifndef _min
#define _min(x, y) ((x) < (y) ? (x) : (y))
#endif
#ifndef PAGE_SIZE
#define PAGE_SIZE 4096
#endif
#define _PAGE_MASK (~(PAGE_SIZE-1))
#ifndef PAGE_ALIGN
#define PAGE_ALIGN(addr) (((addr)+PAGE_SIZE-1)&_PAGE_MASK)
#endif
static void smerror_dummy(int prio, const char *fmt, ...) FORMAT(printf, 2, 3);
static void (*smerr)(int prio, const char *fmt, ...)
FORMAT(printf, 2, 3) = smerror_dummy;
static void smerror_dummy(int prio, const char *fmt, ...)
{
}
#define smerror(mp, ...) mp->smerr(3, __VA_ARGS__)
static int do_dump(struct mempool *mp, char *buf, int len)
{
int pos = 0;
struct memnode *mn;
#define DO_PRN(...) do { \
if (pos >= len) \
return -1; \
pos += snprintf(buf + pos, len - pos, __VA_ARGS__); \
} while (0)
DO_PRN("Total size: %zi\n", mp->size);
DO_PRN("Available space: %zi (%zi used)\n", mp->avail,
mp->size - mp->avail);
DO_PRN("Largest free area: %zi\n", smget_largest_free_area(mp));
DO_PRN("Memory pool dump:\n");
for (mn = &mp->mn; mn; mn = mn->next)
DO_PRN("\tarea: %zi bytes, %s\n",
mn->size, mn->used ? "used" : "free");
return 0;
}
void smdump(struct mempool *mp)
{
char buf[16384];
int err = do_dump(mp, buf, sizeof(buf));
if (!err)
mp->smerr(0, "%s", buf);
else
mp->smerr(3, "dump buffer overflow\n");
}
static FORMAT(printf, 3, 4)
void do_smerror(int prio, struct mempool *mp, const char *fmt, ...)
{
char buf[16384];
int err;
size_t pos;
va_list al;
assert(prio != -1);
va_start(al, fmt);
pos = vsnprintf(buf, sizeof(buf), fmt, al);
va_end(al);
err = -1;
if (pos < sizeof(buf))
err = do_dump(mp, buf + pos, sizeof(buf) - pos);
if (!err)
mp->smerr(0, "%s", buf);
else
mp->smerr(3, "dump buffer overflow\n");
}
static int get_oom_pr(struct mempool *mp, size_t size)
{
if (size <= smget_largest_free_area(mp))
return -1;
if (size > mp->size)
return 2;
if (size > mp->avail)
return 1;
return 0;
}
static void sm_uncommit(struct mempool *mp, void *addr, size_t size)
{
/* align address up and align down size */
uintptr_t a = (uintptr_t)addr;
uintptr_t aa = PAGE_ALIGN(a);
size_t aligned_size = (size - (aa - a)) & _PAGE_MASK;
void *aligned_addr = (void *)aa;
mp->avail += size;
assert(mp->avail <= mp->size);
if (!mp->uncommit)
return;
mp->uncommit(aligned_addr, aligned_size);
}
static int __sm_commit(struct mempool *mp, void *addr, unsigned size,
void *e_addr, size_t e_size)
{
if (!mp->commit)
return 1;
if (!mp->commit(addr, size)) {
smerror(mp, "SMALLOC: failed to commit %p %i\n", addr, size);
if (e_size)
sm_uncommit(mp, e_addr, e_size);
return 0;
}
return 1;
}
static int sm_commit(struct mempool *mp, void *addr, size_t size,
void *e_addr, size_t e_size)
{
/* align address down and align up size */
uintptr_t a = (uintptr_t)addr;
uintptr_t aa = a & _PAGE_MASK;
size_t aligned_size = PAGE_ALIGN(size + (a - aa));
void *aligned_addr = (void *)aa;
int ok = __sm_commit(mp, aligned_addr, aligned_size, e_addr, e_size);
if (ok) {
assert(mp->avail >= size);
mp->avail -= size;
}
return ok;
}
static int sm_commit_simple(struct mempool *mp, void *addr, size_t size)
{
return sm_commit(mp, addr, size, NULL, 0);
}
static void mntruncate(struct memnode *pmn, size_t size)
{
int delta = pmn->size - size;
if (delta == 0)
return;
/* delta can be < 0 */
if (pmn->next && !pmn->next->used) {
struct memnode *nmn = pmn->next;
assert(size > 0 && nmn->size + delta >= 0);
nmn->size += delta;
nmn->mem_area -= delta;
pmn->size -= delta;
if (nmn->size == 0) {
pmn->next = nmn->next;
free(nmn);
assert(!pmn->next || pmn->next->used);
}
} else {
struct memnode *new_mn;
assert(size < pmn->size);
new_mn = (struct memnode *)malloc(sizeof(struct memnode));
new_mn->next = pmn->next;
new_mn->size = delta;
new_mn->used = 0;
new_mn->mem_area = pmn->mem_area + size;
pmn->next = new_mn;
pmn->size = size;
}
}
static struct memnode *find_mn(struct mempool *mp, unsigned char *ptr,
struct memnode **prev)
{
struct memnode *pmn, *mn;
if (!POOL_USED(mp)) {
smerror(mp, "SMALLOC: unused pool passed\n");
return NULL;
}
for (pmn = NULL, mn = &mp->mn; mn; pmn = mn, mn = mn->next) {
if (mn->mem_area > ptr)
return NULL;
if (mn->mem_area == ptr) {
if (prev)
*prev = pmn;
return mn;
}
}
return NULL;
}
static struct memnode *find_mn_at(struct mempool *mp, unsigned char *ptr)
{
struct memnode *mn;
for (mn = &mp->mn; mn; mn = mn->next) {
if (mn->mem_area > ptr)
return NULL;
if (mn->mem_area + mn->size > ptr)
return mn;
}
return NULL;
}
static struct memnode *smfind_free_area(struct mempool *mp, size_t size)
{
struct memnode *mn;
for (mn = &mp->mn; mn; mn = mn->next) {
if (!mn->used && mn->size >= size)
return mn;
}
return NULL;
}
static struct memnode *smfind_free_area_topdown(struct mempool *mp,
unsigned char *top, size_t size)
{
struct memnode *mn;
struct memnode *mn1 = NULL;
for (mn = &mp->mn; mn; mn = mn->next) {
if (top && mn->mem_area + size > top)
break;
if (!mn->used && mn->size >= size)
mn1 = mn;
}
return mn1;
}
static struct memnode *sm_alloc_fixed(struct mempool *mp, void *ptr,
unsigned size)
{
struct memnode *mn;
ptrdiff_t delta;
if (!size || !ptr) {
smerror(mp, "SMALLOC: zero-sized allocation attempted\n");
return NULL;
}
if (!(mn = find_mn_at(mp, (unsigned char *)ptr))) {
smerror(mp, "SMALLOC: invalid address %p on alloc_fixed\n", ptr);
return NULL;
}
if (mn->used) {
do_smerror(0, mp, "SMALLOC: address %p already allocated\n", ptr);
return NULL;
}
delta = (uint8_t *)ptr - mn->mem_area;
assert(delta >= 0);
if ((unsigned)(size + delta) > mn->size) {
int pr = get_oom_pr(mp, size);
if (pr < 0)
pr = 0;
do_smerror(pr, mp, "SMALLOC: no space %i at address %p\n", size, ptr);
return NULL;
}
if (delta) {
mntruncate(mn, delta);
mn = mn->next;
assert(!mn->used && mn->size >= size);
}
if (!sm_commit_simple(mp, mn->mem_area, size))
return NULL;
mn->used = 1;
mntruncate(mn, size);
assert(mn->size == size);
memset(mn->mem_area, 0, size);
return mn;
}
static struct memnode *sm_alloc_aligned(struct mempool *mp, size_t align,
unsigned size)
{
struct memnode *mn;
int delta;
uintptr_t iptr;
if (!size) {
smerror(mp, "SMALLOC: zero-sized allocation attempted\n");
return NULL;
}
/* power of 2 align */
assert(__builtin_popcount(align) == 1);
align--;
if (!(mn = smfind_free_area(mp, size + align))) {
do_smerror(get_oom_pr(mp, size), mp,
"SMALLOC: Out Of Memory on alloc, requested=%u\n", size);
return NULL;
}
/* insert small node to align the start */
iptr = (uintptr_t)mn->mem_area;
delta = ((iptr | align) - iptr + 1) & align;
if (delta) {
mntruncate(mn, delta);
mn = mn->next;
assert(!mn->used && mn->size >= size);
}
if (!sm_commit_simple(mp, mn->mem_area, size))
return NULL;
mn->used = 1;
mntruncate(mn, size);
assert(mn->size == size);
memset(mn->mem_area, 0, size);
return mn;
}
static struct memnode *sm_alloc_mn(struct mempool *mp, size_t size)
{
return sm_alloc_aligned(mp, 1, size);
}
static struct memnode *sm_alloc_aligned_topdown(struct mempool *mp,
unsigned char *top, size_t align, unsigned size)
{
struct memnode *mn;
int delta;
uintptr_t iptr;
uintptr_t min_top;
uintptr_t iend;
if (!size) {
smerror(mp, "SMALLOC: zero-sized allocation attempted\n");
return NULL;
}
/* power of 2 align */
assert(__builtin_popcount(align) == 1);
align--;
if (!(mn = smfind_free_area_topdown(mp, top, size + align))) {
do_smerror(get_oom_pr(mp, size), mp,
"SMALLOC: Out Of Memory on alloc, requested=%u\n", size);
return NULL;
}
/* use top part of the found area */
min_top = (uintptr_t)mn->mem_area + mn->size;
if (top)
min_top = _min(min_top, (uintptr_t)top);
iptr = (min_top - size) & ~align;
iend = iptr + size;
delta = (uintptr_t)mn->mem_area + mn->size - iend;
if (delta)
mntruncate(mn, mn->size - delta);
assert(iptr >= (uintptr_t)mn->mem_area);
delta = iptr - (uintptr_t)mn->mem_area;
if (delta) {
mntruncate(mn, delta);
mn = mn->next;
assert(!mn->used && mn->size >= size);
}
if (!sm_commit_simple(mp, mn->mem_area, size))
return NULL;
mn->used = 1;
mntruncate(mn, size);
assert(mn->size == size);
memset(mn->mem_area, 0, size);
return mn;
}
static struct memnode *sm_alloc_topdown(struct mempool *mp, size_t size)
{
return sm_alloc_aligned_topdown(mp, NULL, 1, size);
}
void *smalloc(struct mempool *mp, size_t size)
{
struct memnode *mn = sm_alloc_mn(mp, size);
if (!mn)
return NULL;
return mn->mem_area;
}
void *smalloc_fixed(struct mempool *mp, void *ptr, size_t size)
{
struct memnode *mn = sm_alloc_fixed(mp, ptr, size);
if (!mn)
return NULL;
assert(mn->mem_area == ptr);
return mn->mem_area;
}
void *smalloc_aligned(struct mempool *mp, size_t align, size_t size)
{
struct memnode *mn = sm_alloc_aligned(mp, align, size);
if (!mn)
return NULL;
assert(((uintptr_t)mn->mem_area & (align - 1)) == 0);
return mn->mem_area;
}
void *smalloc_topdown(struct mempool *mp, size_t size)
{
struct memnode *mn = sm_alloc_topdown(mp, size);
if (!mn)
return NULL;
return mn->mem_area;
}
void *smalloc_aligned_topdown(struct mempool *mp, unsigned char *top,
size_t align, size_t size)
{
struct memnode *mn = sm_alloc_aligned_topdown(mp, top, align, size);
if (!mn)
return NULL;
assert(((uintptr_t)mn->mem_area & (align - 1)) == 0);
return mn->mem_area;
}
int smfree(struct mempool *mp, void *ptr)
{
struct memnode *mn, *pmn;
if (!ptr)
return -1;
if (!(mn = find_mn(mp, (unsigned char *)ptr, &pmn))) {
smerror(mp, "SMALLOC: bad pointer passed to smfree()\n");
return -1;
}
if (!mn->used) {
smerror(mp, "SMALLOC: attempt to free the not allocated region (double-free)\n");
return -1;
}
assert(mn->size > 0);
sm_uncommit(mp, mn->mem_area, mn->size);
mn->used = 0;
if (mn->next && !mn->next->used) {
/* merge with next */
assert(mn->next->mem_area >= mn->mem_area);
mntruncate(mn, mn->size + mn->next->size);
}
if (pmn && !pmn->used) {
/* merge with prev */
assert(pmn->mem_area <= mn->mem_area);
mntruncate(pmn, pmn->size + mn->size);
mn = pmn;
}
return 0;
}
/* part of smrealloc() that covers the cases where the
* extra memnode needs to be allocated for realloc */
static struct memnode *sm_realloc_alloc_mn(struct mempool *mp,
struct memnode *pmn, struct memnode *mn,
struct memnode *nmn, unsigned size)
{
struct memnode *new_mn;
if (pmn && !pmn->used && pmn->size + mn->size +
(nmn->used ? 0 : nmn->size) >= size) {
/* move to prev memnode */
size_t psize = _min(size, pmn->size);
if (!sm_commit_simple(mp, pmn->mem_area, psize))
return NULL;
if (size > pmn->size + mn->size) {
if (!sm_commit(mp, nmn->mem_area, size - pmn->size - mn->size,
pmn->mem_area, psize))
return NULL;
}
pmn->used = 1;
memmove(pmn->mem_area, mn->mem_area, mn->size);
memset(pmn->mem_area + mn->size, 0, size - mn->size);
mn->used = 0;
if (size < pmn->size + mn->size) {
size_t overl = size > pmn->size ? size - pmn->size : 0;
sm_uncommit(mp, mn->mem_area + overl, mn->size - overl);
}
if (!nmn->used) // merge with next
mntruncate(mn, mn->size + nmn->size);
mntruncate(pmn, size);
new_mn = pmn;
} else {
/* relocate */
new_mn = sm_alloc_mn(mp, size);
if (!new_mn) {
do_smerror(get_oom_pr(mp, size), mp,
"SMALLOC: Out Of Memory on realloc, requested=%u\n", size);
return NULL;
}
memcpy(new_mn->mem_area, mn->mem_area, mn->size);
smfree(mp, mn->mem_area);
}
return new_mn;
}
void *smrealloc(struct mempool *mp, void *ptr, size_t size)
{
struct memnode *mn, *pmn;
if (!ptr)
return smalloc(mp, size);
if (!(mn = find_mn(mp, (unsigned char *)ptr, &pmn))) {
smerror(mp, "SMALLOC: bad pointer passed to smrealloc()\n");
return NULL;
}
if (!mn->used) {
smerror(mp, "SMALLOC: attempt to realloc the not allocated region\n");
return NULL;
}
if (size == 0) {
smfree(mp, ptr);
return NULL;
}
if (size == mn->size)
return ptr;
if (size < mn->size) {
/* shrink */
sm_uncommit(mp, mn->mem_area + size, mn->size - size);
mntruncate(mn, size);
} else {
/* grow */
struct memnode *nmn = mn->next;
if (nmn && !nmn->used && mn->size + nmn->size >= size) {
/* expand by shrinking next memnode */
if (!sm_commit_simple(mp, nmn->mem_area, size - mn->size))
return NULL;
memset(nmn->mem_area, 0, size - mn->size);
mntruncate(mn, size);
} else {
/* need to allocate new memnode */
mn = sm_realloc_alloc_mn(mp, pmn, mn, nmn, size);
if (!mn)
return NULL;
}
}
assert(mn->size == size);
return mn->mem_area;
}
void *smrealloc_aligned(struct mempool *mp, void *ptr, int align, size_t size)
{
struct memnode *mn, *pmn;
assert(__builtin_popcount(align) == 1);
if (!ptr)
return smalloc_aligned(mp, align, size);
if (!(mn = find_mn(mp, (unsigned char *)ptr, &pmn))) {
smerror(mp, "SMALLOC: bad pointer passed to smrealloc()\n");
return NULL;
}
if (!mn->used) {
smerror(mp, "SMALLOC: attempt to realloc the not allocated region\n");
return NULL;
}
if (size == 0) {
smfree(mp, ptr);
return NULL;
}
if (size == mn->size)
return ptr;
if ((uintptr_t)mn->mem_area & (align - 1)) {
smerror(mp, "SMALLOC: unaligned pointer passed to smrealloc_aligned()\n");
return NULL;
}
if (size < mn->size) {
/* shrink */
sm_uncommit(mp, mn->mem_area + size, mn->size - size);
mntruncate(mn, size);
} else {
/* grow */
struct memnode *nmn = mn->next;
if (nmn && !nmn->used && mn->size + nmn->size >= size) {
/* expand by shrinking next memnode */
if (!sm_commit_simple(mp, nmn->mem_area, size - mn->size))
return NULL;
memset(nmn->mem_area, 0, size - mn->size);
mntruncate(mn, size);
} else {
/* lazy impl */
struct memnode *new_mn = sm_alloc_aligned(mp, align, size);
if (!new_mn)
return NULL;
memcpy(new_mn->mem_area, mn->mem_area, mn->size);
smfree(mp, mn->mem_area);
}
}
assert(mn->size == size);
return mn->mem_area;
}
int sminit(struct mempool *mp, void *start, size_t size)
{
mp->size = size;
mp->mn.size = size;
mp->mn.used = 0;
mp->mn.next = NULL;
mp->mn.mem_area = (unsigned char *)start;
mp->avail = size;
mp->commit = NULL;
mp->uncommit = NULL;
mp->smerr = smerr;
return 0;
}
static int do_sminit_com(struct mempool *mp, void *start, size_t size,
int (*commit)(void *area, size_t size),
int (*uncommit)(void *area, size_t size), int do_uncommit)
{
sminit(mp, start, size);
mp->commit = commit;
mp->uncommit = uncommit;
if (uncommit && do_uncommit)
uncommit(start, size);
return 0;
}
int sminit_com(struct mempool *mp, void *start, size_t size,
int (*commit)(void *area, size_t size),
int (*uncommit)(void *area, size_t size))
{
return do_sminit_com(mp, start, size, commit, uncommit, 1);
}
int sminit_comu(struct mempool *mp, void *start, size_t size,
int (*commit)(void *area, size_t size),
int (*uncommit)(void *area, size_t size))
{
return do_sminit_com(mp, start, size, commit, uncommit, 0);
}
void smfree_all(struct mempool *mp)
{
struct memnode *mn;
while (POOL_USED(mp)) {
mn = &mp->mn;
if (!mn->used)
mn = mn->next;
assert(mn && mn->used);
smfree(mp, mn->mem_area);
}
assert(!mp->mn.next);
}
int smdestroy(struct mempool *mp)
{
unsigned avail = mp->avail;
smfree_all(mp);
assert(mp->mn.size >= avail);
/* return leaked size */
return mp->mn.size - avail;
}
size_t smget_free_space(struct mempool *mp)
{
return mp->avail;
}
size_t smget_free_space_upto(struct mempool *mp, unsigned char *top)
{
struct memnode *mn;
int cnt = 0;
for (mn = &mp->mn; mn; mn = mn->next) {
if (mn->mem_area + mn->size > top) {
if (!mn->used && mn->mem_area < top)
cnt += top - mn->mem_area;
break;
}
if (!mn->used)
cnt += mn->size;
}
return cnt;
}
size_t smget_largest_free_area(struct mempool *mp)
{
struct memnode *mn;
size_t size = 0;
for (mn = &mp->mn; mn; mn = mn->next) {
if (!mn->used && mn->size > size)
size = mn->size;
}
return size;
}
int smget_area_size(struct mempool *mp, void *ptr)
{
struct memnode *mn;
if (!(mn = find_mn(mp, (unsigned char *)ptr, NULL))) {
smerror(mp, "SMALLOC: bad pointer passed to smget_area_size()\n");
return -1;
}
return mn->size;
}
void *smget_base_addr(struct mempool *mp)
{
return mp->mn.mem_area;
}
void smregister_error_notifier(struct mempool *mp,
void (*func)(int prio, const char *fmt, ...) FORMAT(printf, 2, 3))
{
mp->smerr = func;
}
void smregister_default_error_notifier(
void (*func)(int prio, const char *fmt, ...) FORMAT(printf, 2, 3))
{
smerr = func;
}