#include "valgrind_internal.h"
#include "heap_layout.h"
#include "heap.h"
#include "alloc_class.h"
#include "out.h"
#include "sys_util.h"
#include "palloc.h"
struct pobj_action_internal {
enum pobj_action_type type;
uint32_t padding;
os_mutex_t *lock;
union {
struct {
uint64_t offset;
enum memblock_state new_state;
struct memory_block m;
int *resvp;
};
struct {
uint64_t *ptr;
uint64_t value;
};
uint64_t data2[14];
};
};
#define OBJ_HEAP_ACTION_INITIALIZER(off, nstate)\
{POBJ_ACTION_TYPE_HEAP, 0, NULL, {{off, nstate, MEMORY_BLOCK_NONE, NULL}}}
void
palloc_set_value(struct palloc_heap *heap, struct pobj_action *act,
uint64_t *ptr, uint64_t value)
{
act->type = POBJ_ACTION_TYPE_MEM;
struct pobj_action_internal *actp = (struct pobj_action_internal *)act;
actp->ptr = ptr;
actp->value = value;
actp->lock = NULL;
}
static int
alloc_prep_block(struct palloc_heap *heap, const struct memory_block *m,
palloc_constr constructor, void *arg,
uint64_t extra_field, uint16_t object_flags,
uint64_t *offset_value)
{
void *uptr = m->m_ops->get_user_data(m);
size_t usize = m->m_ops->get_user_size(m);
VALGRIND_DO_MEMPOOL_ALLOC(heap->layout, uptr, usize);
VALGRIND_DO_MAKE_MEM_UNDEFINED(uptr, usize);
VALGRIND_ANNOTATE_NEW_MEMORY(uptr, usize);
int ret;
if (constructor != NULL &&
(ret = constructor(heap->base, uptr, usize, arg)) != 0) {
VALGRIND_DO_MEMPOOL_FREE(heap->layout, uptr);
return ret;
}
m->m_ops->write_header(m, extra_field, object_flags);
*offset_value = HEAP_PTR_TO_OFF(heap, uptr);
return 0;
}
static int
palloc_reservation_create(struct palloc_heap *heap, size_t size,
palloc_constr constructor, void *arg,
uint64_t extra_field, uint16_t object_flags, uint16_t class_id,
struct pobj_action_internal *out)
{
int err = 0;
struct memory_block *new_block = &out->m;
ASSERT(class_id < UINT8_MAX);
struct alloc_class *c = class_id == 0 ?
heap_get_best_class(heap, size) :
alloc_class_by_id(heap_alloc_classes(heap),
(uint8_t)class_id);
if (c == NULL) {
ERR("no allocation class for size %lu bytes", size);
errno = EINVAL;
return -1;
}
ssize_t size_idx = alloc_class_calc_size_idx(c, size);
if (size_idx < 0) {
ERR("allocation class not suitable for size %lu bytes",
size);
errno = EINVAL;
return -1;
}
ASSERT(size_idx <= UINT32_MAX);
new_block->size_idx = (uint32_t)size_idx;
struct bucket *b = heap_bucket_acquire(heap, c);
err = heap_get_bestfit_block(heap, b, new_block);
if (err != 0)
goto out;
if (alloc_prep_block(heap, new_block, constructor, arg,
extra_field, object_flags, &out->offset) != 0) {
if (new_block->type == MEMORY_BLOCK_HUGE) {
bucket_insert_block(b, new_block);
}
err = ECANCELED;
goto out;
}
if ((out->resvp = bucket_current_resvp(b)) != NULL)
util_fetch_and_add64(out->resvp, 1);
out->lock = new_block->m_ops->get_lock(new_block);
out->new_state = MEMBLOCK_ALLOCATED;
out:
heap_bucket_release(heap, b);
if (err == 0)
return 0;
errno = err;
return -1;
}
static void
palloc_exec_heap_action(struct palloc_heap *heap,
const struct pobj_action_internal *act,
struct operation_context *ctx)
{
#ifdef DEBUG
if (act->m.m_ops->get_state(&act->m) == act->new_state) {
ERR("invalid operation or heap corruption");
ASSERT(0);
}
#endif
if (act->new_state == MEMBLOCK_ALLOCATED)
act->m.m_ops->flush_header(&act->m);
act->m.m_ops->prep_hdr(&act->m, act->new_state, ctx);
}
static void
palloc_restore_free_chunk_state(struct palloc_heap *heap,
struct memory_block *m)
{
VALGRIND_DO_MEMPOOL_FREE(heap->layout, m->m_ops->get_user_data(m));
if (m->type == MEMORY_BLOCK_HUGE) {
struct bucket *b = heap_bucket_acquire_by_id(heap,
DEFAULT_ALLOC_CLASS_ID);
heap_free_chunk_reuse(heap, b, m);
heap_bucket_release(heap, b);
}
}
static void
palloc_finalize_heap_action(struct palloc_heap *heap,
struct pobj_action_internal *act, int canceled)
{
if (act->new_state == MEMBLOCK_ALLOCATED) {
if (canceled) {
palloc_restore_free_chunk_state(heap, &act->m);
act->m.m_ops->invalidate_header(&act->m);
} else {
STATS_INC(heap->stats, persistent, heap_curr_allocated,
act->m.m_ops->get_real_size(&act->m));
}
if (act->resvp)
util_fetch_and_sub64(act->resvp, 1);
} else if (act->new_state == MEMBLOCK_FREE && !canceled) {
STATS_SUB(heap->stats, persistent, heap_curr_allocated,
act->m.m_ops->get_real_size(&act->m));
heap_memblock_on_free(heap, &act->m);
palloc_restore_free_chunk_state(heap, &act->m);
}
}
static void
palloc_exec_mem_action(struct palloc_heap *heap,
const struct pobj_action_internal *act,
struct operation_context *ctx)
{
operation_add_entry(ctx, act->ptr, act->value, OPERATION_SET);
}
static void
palloc_finalize_mem_action(struct palloc_heap *heap,
struct pobj_action_internal *act, int canceled)
{
}
static struct {
void (*exec)(struct palloc_heap *heap,
const struct pobj_action_internal *act,
struct operation_context *ctx);
void (*finalize)(struct palloc_heap *heap,
struct pobj_action_internal *act, int canceled);
} action_funcs[POBJ_MAX_ACTION_TYPE] = {
[POBJ_ACTION_TYPE_HEAP] = {
.exec = palloc_exec_heap_action,
.finalize = palloc_finalize_heap_action
},
[POBJ_ACTION_TYPE_MEM] = {
.exec = palloc_exec_mem_action,
.finalize = palloc_finalize_mem_action
}
};
static int
palloc_action_compare(const void *lhs, const void *rhs)
{
const struct pobj_action_internal *mlhs = lhs;
const struct pobj_action_internal *mrhs = rhs;
uintptr_t vlhs = (uintptr_t)(mlhs->lock);
uintptr_t vrhs = (uintptr_t)(mrhs->lock);
if (vlhs < vrhs)
return -1;
if (vlhs > vrhs)
return 1;
return 0;
}
static void
palloc_exec_actions(struct palloc_heap *heap,
struct operation_context *ctx,
struct pobj_action_internal *actv,
int actvcnt)
{
qsort(actv, (size_t)actvcnt, sizeof(struct pobj_action_internal),
palloc_action_compare);
struct pobj_action_internal *act;
for (int i = 0; i < actvcnt; ++i) {
act = &actv[i];
if (i == 0 || act->lock != actv[i - 1].lock) {
if (act->lock)
util_mutex_lock(act->lock);
}
action_funcs[act->type].exec(heap, act, ctx);
}
pmemops_drain(&heap->p_ops);
operation_process(ctx);
for (int i = 0; i < actvcnt; ++i) {
act = &actv[i];
action_funcs[act->type].finalize(heap, act, 0);
if (i == 0 || act->lock != actv[i - 1].lock) {
if (act->lock)
util_mutex_unlock(act->lock);
}
}
}
int
palloc_reserve(struct palloc_heap *heap, size_t size,
palloc_constr constructor, void *arg,
uint64_t extra_field, uint16_t object_flags, uint16_t class_id,
struct pobj_action *act)
{
COMPILE_ERROR_ON(sizeof(struct pobj_action) !=
sizeof(struct pobj_action_internal));
act->type = POBJ_ACTION_TYPE_HEAP;
return palloc_reservation_create(heap, size, constructor, arg,
extra_field, object_flags, class_id,
(struct pobj_action_internal *)act);
}
void
palloc_cancel(struct palloc_heap *heap,
struct pobj_action *actv, int actvcnt)
{
struct pobj_action_internal *act;
for (int i = 0; i < actvcnt; ++i) {
act = (struct pobj_action_internal *)&actv[i];
action_funcs[act->type].finalize(heap, act, 1);
}
}
void
palloc_publish(struct palloc_heap *heap,
struct pobj_action *actv, int actvcnt,
struct operation_context *ctx)
{
palloc_exec_actions(heap, ctx,
(struct pobj_action_internal *)actv, actvcnt);
}
int
palloc_operation(struct palloc_heap *heap,
uint64_t off, uint64_t *dest_off, size_t size,
palloc_constr constructor, void *arg,
uint64_t extra_field, uint16_t object_flags, uint16_t class_id,
struct operation_context *ctx)
{
struct pobj_action_internal alloc =
OBJ_HEAP_ACTION_INITIALIZER(0, MEMBLOCK_ALLOCATED);
struct pobj_action_internal dealloc =
OBJ_HEAP_ACTION_INITIALIZER(off, MEMBLOCK_FREE);
size_t user_size = 0;
int nops = 0;
struct pobj_action_internal ops[2];
if (dealloc.offset != 0) {
dealloc.m = memblock_from_offset(heap, dealloc.offset);
user_size = dealloc.m.m_ops->get_user_size(&dealloc.m);
if (user_size == size)
return 0;
}
if (size != 0) {
if (palloc_reservation_create(heap, size, constructor, arg,
extra_field, object_flags, class_id, &alloc) != 0)
return -1;
ops[nops++] = alloc;
}
if (dealloc.offset != 0) {
if (!MEMORY_BLOCK_IS_NONE(alloc.m)) {
size_t old_size = user_size;
size_t to_cpy = old_size > size ? size : old_size;
VALGRIND_ADD_TO_TX(
HEAP_OFF_TO_PTR(heap, alloc.offset),
to_cpy);
pmemops_memcpy_persist(&heap->p_ops,
HEAP_OFF_TO_PTR(heap, alloc.offset),
HEAP_OFF_TO_PTR(heap, off),
to_cpy);
VALGRIND_REMOVE_FROM_TX(
HEAP_OFF_TO_PTR(heap, alloc.offset),
to_cpy);
}
dealloc.lock = dealloc.m.m_ops->get_lock(&dealloc.m);
ops[nops++] = dealloc;
}
if (dest_off)
operation_add_entry(ctx, dest_off, alloc.offset, OPERATION_SET);
palloc_exec_actions(heap, ctx, ops, nops);
return 0;
}
size_t
palloc_usable_size(struct palloc_heap *heap, uint64_t off)
{
struct memory_block m = memblock_from_offset(heap, off);
return m.m_ops->get_user_size(&m);
}
uint64_t
palloc_extra(struct palloc_heap *heap, uint64_t off)
{
struct memory_block m = memblock_from_offset(heap, off);
return m.m_ops->get_extra(&m);
}
uint16_t
palloc_flags(struct palloc_heap *heap, uint64_t off)
{
struct memory_block m = memblock_from_offset(heap, off);
return m.m_ops->get_flags(&m);
}
static int
pmalloc_search_cb(const struct memory_block *m, void *arg)
{
struct memory_block *out = arg;
if (MEMORY_BLOCK_EQUALS(*m, *out))
return 0;
*out = *m;
return 1;
}
uint64_t
palloc_first(struct palloc_heap *heap)
{
struct memory_block search = MEMORY_BLOCK_NONE;
heap_foreach_object(heap, pmalloc_search_cb,
&search, MEMORY_BLOCK_NONE);
if (MEMORY_BLOCK_IS_NONE(search))
return 0;
void *uptr = search.m_ops->get_user_data(&search);
return HEAP_PTR_TO_OFF(heap, uptr);
}
uint64_t
palloc_next(struct palloc_heap *heap, uint64_t off)
{
struct memory_block m = memblock_from_offset(heap, off);
struct memory_block search = m;
heap_foreach_object(heap, pmalloc_search_cb, &search, m);
if (MEMORY_BLOCK_IS_NONE(search) ||
MEMORY_BLOCK_EQUALS(search, m))
return 0;
void *uptr = search.m_ops->get_user_data(&search);
return HEAP_PTR_TO_OFF(heap, uptr);
}
int
palloc_is_allocated(struct palloc_heap *heap, uint64_t off)
{
return memblock_validate_offset(heap, off) == MEMBLOCK_ALLOCATED;
}
int
palloc_boot(struct palloc_heap *heap, void *heap_start,
uint64_t heap_size, uint64_t *sizep,
void *base, struct pmem_ops *p_ops, struct stats *stats,
struct pool_set *set)
{
return heap_boot(heap, heap_start, heap_size, sizep,
base, p_ops, stats, set);
}
int
palloc_buckets_init(struct palloc_heap *heap)
{
return heap_buckets_init(heap);
}
int
palloc_init(void *heap_start, uint64_t heap_size, uint64_t *sizep,
struct pmem_ops *p_ops)
{
return heap_init(heap_start, heap_size, sizep, p_ops);
}
void *
palloc_heap_end(struct palloc_heap *h)
{
return heap_end(h);
}
int
palloc_heap_check(void *heap_start, uint64_t heap_size)
{
return heap_check(heap_start, heap_size);
}
int
palloc_heap_check_remote(void *heap_start, uint64_t heap_size,
struct remote_ops *ops)
{
return heap_check_remote(heap_start, heap_size, ops);
}
void
palloc_heap_cleanup(struct palloc_heap *heap)
{
heap_cleanup(heap);
}
#ifdef USE_VG_MEMCHECK
static int
palloc_vg_register_alloc(const struct memory_block *m, void *arg)
{
struct palloc_heap *heap = arg;
m->m_ops->reinit_header(m);
void *uptr = m->m_ops->get_user_data(m);
size_t usize = m->m_ops->get_user_size(m);
VALGRIND_DO_MEMPOOL_ALLOC(heap->layout, uptr, usize);
VALGRIND_DO_MAKE_MEM_DEFINED(uptr, usize);
return 0;
}
void
palloc_vg_register_off(struct palloc_heap *heap, uint64_t off)
{
struct memory_block m = memblock_from_offset_opt(heap, off, 0);
palloc_vg_register_alloc(&m, heap);
}
void
palloc_heap_vg_open(struct palloc_heap *heap, int objects)
{
heap_vg_open(heap, palloc_vg_register_alloc, heap, objects);
}
#endif