#include <inttypes.h>
#include "valgrind_internal.h"
#include "heap.h"
#include "lane.h"
#include "memops.h"
#include "obj.h"
#include "out.h"
#include "palloc.h"
#include "pmalloc.h"
#include "alloc_class.h"
#include "set.h"
#ifdef DEBUG
#define ALLOC_INPROGRESS_MARK ((void *)0x1)
#endif
struct redo_log *
pmalloc_redo_hold(PMEMobjpool *pop)
{
struct lane_section *lane;
lane_hold(pop, &lane, LANE_SECTION_ALLOCATOR);
#ifdef DEBUG
ASSERTeq(lane->runtime, NULL);
lane->runtime = ALLOC_INPROGRESS_MARK;
#endif
struct lane_alloc_layout *sec = (void *)lane->layout;
return sec->redo;
}
void
pmalloc_redo_release(PMEMobjpool *pop)
{
#ifdef DEBUG
struct lane_section *lane;
lane_hold(pop, &lane, LANE_SECTION_ALLOCATOR);
lane->runtime = NULL;
lane_release(pop);
#endif
lane_release(pop);
}
int
pmalloc_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)
{
#ifdef USE_VG_MEMCHECK
uint64_t tmp;
if (size && On_valgrind && dest_off == NULL)
dest_off = &tmp;
#endif
int ret = palloc_operation(heap, off, dest_off, size, constructor, arg,
extra_field, object_flags, class_id, ctx);
if (ret)
return ret;
return 0;
}
int
pmalloc(PMEMobjpool *pop, uint64_t *off, size_t size,
uint64_t extra_field, uint16_t object_flags)
{
struct redo_log *redo = pmalloc_redo_hold(pop);
struct operation_context ctx;
operation_init(&ctx, pop, pop->redo, redo);
int ret = pmalloc_operation(&pop->heap, 0, off, size, NULL, NULL,
extra_field, object_flags, 0, &ctx);
pmalloc_redo_release(pop);
return ret;
}
int
pmalloc_construct(PMEMobjpool *pop, uint64_t *off, size_t size,
palloc_constr constructor, void *arg,
uint64_t extra_field, uint16_t object_flags, uint16_t class_id)
{
struct redo_log *redo = pmalloc_redo_hold(pop);
struct operation_context ctx;
operation_init(&ctx, pop, pop->redo, redo);
int ret = pmalloc_operation(&pop->heap, 0, off, size, constructor, arg,
extra_field, object_flags, class_id, &ctx);
pmalloc_redo_release(pop);
return ret;
}
int
prealloc(PMEMobjpool *pop, uint64_t *off, size_t size,
uint64_t extra_field, uint16_t object_flags)
{
struct redo_log *redo = pmalloc_redo_hold(pop);
struct operation_context ctx;
operation_init(&ctx, pop, pop->redo, redo);
int ret = pmalloc_operation(&pop->heap, *off, off, size, NULL, NULL,
extra_field, object_flags, 0, &ctx);
pmalloc_redo_release(pop);
return ret;
}
void
pfree(PMEMobjpool *pop, uint64_t *off)
{
struct redo_log *redo = pmalloc_redo_hold(pop);
struct operation_context ctx;
operation_init(&ctx, pop, pop->redo, redo);
int ret = pmalloc_operation(&pop->heap, *off, off, 0, NULL, NULL,
0, 0, 0, &ctx);
ASSERTeq(ret, 0);
pmalloc_redo_release(pop);
}
static void *
pmalloc_construct_rt(PMEMobjpool *pop)
{
return NULL;
}
static void
pmalloc_destroy_rt(PMEMobjpool *pop, void *rt)
{
}
static int
pmalloc_recovery(PMEMobjpool *pop, void *data, unsigned length)
{
struct lane_alloc_layout *sec = data;
ASSERT(sizeof(*sec) <= length);
redo_log_recover(pop->redo, sec->redo, ALLOC_REDO_LOG_SIZE);
return 0;
}
static int
pmalloc_check(PMEMobjpool *pop, void *data, unsigned length)
{
LOG(3, "allocator lane %p", data);
struct lane_alloc_layout *sec = data;
int ret = redo_log_check(pop->redo, sec->redo, ALLOC_REDO_LOG_SIZE);
if (ret != 0)
ERR("allocator lane: redo log check failed");
return ret;
}
static int
pmalloc_boot(PMEMobjpool *pop)
{
int ret = palloc_boot(&pop->heap, (char *)pop + pop->heap_offset,
pop->set->poolsize - pop->heap_offset, &pop->heap_size,
pop, &pop->p_ops,
pop->stats, pop->set);
if (ret)
return ret;
#ifdef USE_VG_MEMCHECK
if (On_valgrind)
palloc_heap_vg_open(&pop->heap, pop->vg_boot);
#endif
ret = palloc_buckets_init(&pop->heap);
if (ret)
palloc_heap_cleanup(&pop->heap);
return ret;
}
static struct section_operations allocator_ops = {
.construct_rt = pmalloc_construct_rt,
.destroy_rt = pmalloc_destroy_rt,
.recover = pmalloc_recovery,
.check = pmalloc_check,
.boot = pmalloc_boot
};
SECTION_PARM(LANE_SECTION_ALLOCATOR, &allocator_ops);
static int
CTL_WRITE_HANDLER(desc)(PMEMobjpool *pop,
enum ctl_query_source source, void *arg, struct ctl_indexes *indexes)
{
uint8_t id;
struct alloc_class_collection *ac = heap_alloc_classes(&pop->heap);
struct pobj_alloc_class_desc *p = arg;
if (p->unit_size <= 0 || p->unit_size > PMEMOBJ_MAX_ALLOC_SIZE ||
p->units_per_block <= 0) {
errno = EINVAL;
return -1;
}
enum header_type lib_htype = MAX_HEADER_TYPES;
switch (p->header_type) {
case POBJ_HEADER_LEGACY:
lib_htype = HEADER_LEGACY;
break;
case POBJ_HEADER_COMPACT:
lib_htype = HEADER_COMPACT;
break;
case POBJ_HEADER_NONE:
lib_htype = HEADER_NONE;
break;
case MAX_POBJ_HEADER_TYPES:
default:
ERR("invalid header type");
errno = EINVAL;
return -1;
}
if (SLIST_EMPTY(indexes)) {
if (alloc_class_find_first_free_slot(ac, &id) != 0) {
ERR("no available free allocation class identifier");
errno = EINVAL;
return -1;
}
} else {
struct ctl_index *idx = SLIST_FIRST(indexes);
ASSERTeq(strcmp(idx->name, "class_id"), 0);
if (idx->value < 0 || idx->value >= MAX_ALLOCATION_CLASSES) {
ERR("class id outside of the allowed range");
errno = ERANGE;
return -1;
}
id = (uint8_t)idx->value;
if (alloc_class_reserve(ac, id) != 0) {
ERR("attempted to overwrite an allocation class");
errno = EEXIST;
return -1;
}
}
p->class_id = id;
struct alloc_class c;
c.id = id;
c.header_type = lib_htype;
c.type = CLASS_RUN;
c.unit_size = p->unit_size;
size_t runsize_bytes =
CHUNK_ALIGN_UP((p->units_per_block * p->unit_size) +
RUN_METASIZE);
c.run.size_idx = (uint32_t)(runsize_bytes / CHUNKSIZE);
if (c.run.size_idx > UINT16_MAX)
c.run.size_idx = UINT16_MAX;
alloc_class_generate_run_proto(&c.run, c.unit_size, c.run.size_idx);
struct alloc_class *realc = alloc_class_register(
heap_alloc_classes(&pop->heap), &c);
if (realc == NULL) {
errno = EINVAL;
return -1;
}
if (heap_create_alloc_class_buckets(&pop->heap, realc) != 0) {
alloc_class_delete(ac, realc);
return -1;
}
return 0;
}
static int
pmalloc_header_type_parser(const void *arg, void *dest, size_t dest_size)
{
const char *vstr = arg;
enum pobj_header_type *htype = dest;
ASSERTeq(dest_size, sizeof(enum pobj_header_type));
if (strcmp(vstr, "none") == 0) {
*htype = POBJ_HEADER_NONE;
} else if (strcmp(vstr, "compact") == 0) {
*htype = POBJ_HEADER_COMPACT;
} else if (strcmp(vstr, "legacy") == 0) {
*htype = POBJ_HEADER_LEGACY;
} else {
ERR("invalid header type");
errno = EINVAL;
return -1;
}
return 0;
}
static int
CTL_READ_HANDLER(desc)(PMEMobjpool *pop,
enum ctl_query_source source, void *arg, struct ctl_indexes *indexes)
{
uint8_t id;
struct ctl_index *idx = SLIST_FIRST(indexes);
ASSERTeq(strcmp(idx->name, "class_id"), 0);
if (idx->value < 0 || idx->value >= MAX_ALLOCATION_CLASSES) {
ERR("class id outside of the allowed range");
errno = ERANGE;
return -1;
}
id = (uint8_t)idx->value;
struct alloc_class *c = alloc_class_by_id(
heap_alloc_classes(&pop->heap), id);
if (c == NULL) {
ERR("class with the given id does not exist");
errno = ENOENT;
return -1;
}
enum pobj_header_type user_htype = MAX_POBJ_HEADER_TYPES;
switch (c->header_type) {
case HEADER_LEGACY:
user_htype = POBJ_HEADER_LEGACY;
break;
case HEADER_COMPACT:
user_htype = POBJ_HEADER_COMPACT;
break;
case HEADER_NONE:
user_htype = POBJ_HEADER_NONE;
break;
default:
ASSERT(0);
break;
}
struct pobj_alloc_class_desc *p = arg;
p->units_per_block = c->type == CLASS_HUGE ? 0 : c->run.bitmap_nallocs;
p->header_type = user_htype;
p->unit_size = c->unit_size;
p->class_id = c->id;
return 0;
}
static struct ctl_argument CTL_ARG(desc) = {
.dest_size = sizeof(struct pobj_alloc_class_desc),
.parsers = {
CTL_ARG_PARSER_STRUCT(struct pobj_alloc_class_desc,
unit_size, ctl_arg_integer),
CTL_ARG_PARSER_STRUCT(struct pobj_alloc_class_desc,
units_per_block, ctl_arg_integer),
CTL_ARG_PARSER_STRUCT(struct pobj_alloc_class_desc,
header_type, pmalloc_header_type_parser),
CTL_ARG_PARSER_END
}
};
static const struct ctl_node CTL_NODE(class_id)[] = {
CTL_LEAF_RW(desc),
CTL_NODE_END
};
static const struct ctl_node CTL_NODE(new)[] = {
CTL_LEAF_WO(desc),
CTL_NODE_END
};
static const struct ctl_node CTL_NODE(alloc_class)[] = {
CTL_INDEXED(class_id),
CTL_INDEXED(new),
CTL_NODE_END
};
static int
CTL_RUNNABLE_HANDLER(extend)(PMEMobjpool *pop,
enum ctl_query_source source, void *arg, struct ctl_indexes *indexes)
{
ssize_t arg_in = *(ssize_t *)arg;
if (arg_in < (ssize_t)PMEMOBJ_MIN_PART) {
ERR("incorrect size for extend, must be larger than %" PRIu64,
PMEMOBJ_MIN_PART);
return -1;
}
struct palloc_heap *heap = &pop->heap;
struct bucket *defb = heap_bucket_acquire_by_id(heap,
DEFAULT_ALLOC_CLASS_ID);
int ret = heap_extend(heap, defb, (size_t)arg_in) < 0 ? -1 : 0;
heap_bucket_release(heap, defb);
return ret;
}
static int
CTL_READ_HANDLER(granularity)(PMEMobjpool *pop,
enum ctl_query_source source, void *arg, struct ctl_indexes *indexes)
{
ssize_t *arg_out = arg;
*arg_out = (ssize_t)pop->heap.growsize;
return 0;
}
static int
CTL_WRITE_HANDLER(granularity)(PMEMobjpool *pop,
enum ctl_query_source source, void *arg, struct ctl_indexes *indexes)
{
ssize_t arg_in = *(int *)arg;
if (arg_in != 0 && arg_in < (ssize_t)PMEMOBJ_MIN_PART) {
ERR("incorrect grow size, must be 0 or larger than %" PRIu64,
PMEMOBJ_MIN_PART);
return -1;
}
pop->heap.growsize = (size_t)arg_in;
return 0;
}
static struct ctl_argument CTL_ARG(granularity) = CTL_ARG_LONG_LONG;
static const struct ctl_node CTL_NODE(size)[] = {
CTL_LEAF_RW(granularity),
CTL_LEAF_RUNNABLE(extend),
CTL_NODE_END
};
static const struct ctl_node CTL_NODE(heap)[] = {
CTL_CHILD(alloc_class),
CTL_CHILD(size),
CTL_NODE_END
};
void
pmalloc_ctl_register(PMEMobjpool *pop)
{
CTL_REGISTER_MODULE(pop->ctl, heap);
}