#include <string.h>
#include "heap.h"
#include "memblock.h"
#include "out.h"
#include "valgrind_internal.h"
const size_t header_type_to_size[MAX_HEADER_TYPES] = {
sizeof(struct allocation_header_legacy),
sizeof(struct allocation_header_compact),
0
};
const enum chunk_flags header_type_to_flag[MAX_HEADER_TYPES] = {
0,
CHUNK_FLAG_COMPACT_HEADER,
CHUNK_FLAG_HEADER_NONE
};
static enum header_type
memblock_header_type(const struct memory_block *m)
{
struct zone *z = ZID_TO_ZONE(m->heap->layout, m->zone_id);
struct chunk_header *hdr = &z->chunk_headers[m->chunk_id];
if (hdr->flags & CHUNK_FLAG_COMPACT_HEADER)
return HEADER_COMPACT;
if (hdr->flags & CHUNK_FLAG_HEADER_NONE)
return HEADER_NONE;
return HEADER_LEGACY;
}
static size_t
memblock_header_legacy_get_size(const struct memory_block *m)
{
struct allocation_header_legacy *hdr = m->m_ops->get_real_data(m);
return hdr->size;
}
static size_t
memblock_header_compact_get_size(const struct memory_block *m)
{
struct allocation_header_compact *hdr = m->m_ops->get_real_data(m);
return hdr->size & ALLOC_HDR_FLAGS_MASK;
}
static size_t
memblock_header_none_get_size(const struct memory_block *m)
{
return m->m_ops->block_size(m);
}
static uint64_t
memblock_header_legacy_get_extra(const struct memory_block *m)
{
struct allocation_header_legacy *hdr = m->m_ops->get_real_data(m);
return hdr->type_num;
}
static uint64_t
memblock_header_compact_get_extra(const struct memory_block *m)
{
struct allocation_header_compact *hdr = m->m_ops->get_real_data(m);
return hdr->extra;
}
static uint64_t
memblock_header_none_get_extra(const struct memory_block *m)
{
return 0;
}
static uint16_t
memblock_header_legacy_get_flags(const struct memory_block *m)
{
struct allocation_header_legacy *hdr = m->m_ops->get_real_data(m);
return (uint16_t)(hdr->root_size >> ALLOC_HDR_SIZE_SHIFT);
}
static uint16_t
memblock_header_compact_get_flags(const struct memory_block *m)
{
struct allocation_header_compact *hdr = m->m_ops->get_real_data(m);
return (uint16_t)(hdr->size >> ALLOC_HDR_SIZE_SHIFT);
}
static uint16_t
memblock_header_none_get_flags(const struct memory_block *m)
{
return 0;
}
static void
memblock_header_legacy_write(const struct memory_block *m,
size_t size, uint64_t extra, uint16_t flags)
{
struct allocation_header_legacy *hdr = m->m_ops->get_real_data(m);
VALGRIND_DO_MAKE_MEM_UNDEFINED(hdr, sizeof(*hdr));
VALGRIND_ADD_TO_TX(hdr, sizeof(*hdr));
hdr->size = size;
hdr->type_num = extra;
hdr->root_size = ((uint64_t)flags << ALLOC_HDR_SIZE_SHIFT);
VALGRIND_REMOVE_FROM_TX(hdr, sizeof(*hdr));
VALGRIND_DO_MAKE_MEM_NOACCESS(hdr->unused, sizeof(hdr->unused));
}
static void
memblock_header_compact_write(const struct memory_block *m,
size_t size, uint64_t extra, uint16_t flags)
{
struct allocation_header_compact *hdr = m->m_ops->get_real_data(m);
VALGRIND_DO_MAKE_MEM_UNDEFINED(hdr, sizeof(*hdr));
VALGRIND_ADD_TO_TX(hdr, sizeof(*hdr));
hdr->size = size | ((uint64_t)flags << ALLOC_HDR_SIZE_SHIFT);
hdr->extra = extra;
VALGRIND_REMOVE_FROM_TX(hdr, sizeof(*hdr));
}
static void
memblock_header_none_write(const struct memory_block *m,
size_t size, uint64_t extra, uint16_t flags)
{
}
static void
memblock_header_legacy_flush(const struct memory_block *m)
{
struct allocation_header_legacy *hdr = m->m_ops->get_real_data(m);
m->heap->p_ops.flush(m->heap->base, hdr, sizeof(*hdr));
}
static void
memblock_header_compact_flush(const struct memory_block *m)
{
struct allocation_header_compact *hdr = m->m_ops->get_real_data(m);
m->heap->p_ops.flush(m->heap->base, hdr, sizeof(*hdr));
}
static void
memblock_header_none_flush(const struct memory_block *m)
{
}
static void
memblock_header_legacy_invalidate(const struct memory_block *m)
{
struct allocation_header_legacy *hdr = m->m_ops->get_real_data(m);
VALGRIND_SET_CLEAN(hdr, sizeof(*hdr));
}
static void
memblock_header_compact_invalidate(const struct memory_block *m)
{
struct allocation_header_compact *hdr = m->m_ops->get_real_data(m);
VALGRIND_SET_CLEAN(hdr, sizeof(*hdr));
}
static void
memblock_header_none_invalidate(const struct memory_block *m)
{
}
static void
memblock_header_legacy_reinit(const struct memory_block *m)
{
struct allocation_header_legacy *hdr = m->m_ops->get_real_data(m);
VALGRIND_DO_MAKE_MEM_DEFINED(hdr, sizeof(*hdr));
VALGRIND_DO_MAKE_MEM_NOACCESS(hdr->unused, sizeof(hdr->unused));
}
static void
memblock_header_compact_reinit(const struct memory_block *m)
{
struct allocation_header_compact *hdr = m->m_ops->get_real_data(m);
VALGRIND_DO_MAKE_MEM_DEFINED(hdr, sizeof(*hdr));
}
static void
memblock_header_none_reinit(const struct memory_block *m)
{
}
static struct {
size_t (*get_size)(const struct memory_block *m);
uint64_t (*get_extra)(const struct memory_block *m);
uint16_t (*get_flags)(const struct memory_block *m);
void (*write)(const struct memory_block *m,
size_t size, uint64_t extra, uint16_t flags);
void (*flush)(const struct memory_block *m);
void (*invalidate)(const struct memory_block *m);
void (*reinit)(const struct memory_block *m);
} memblock_header_ops[MAX_HEADER_TYPES] = {
[HEADER_LEGACY] = {
memblock_header_legacy_get_size,
memblock_header_legacy_get_extra,
memblock_header_legacy_get_flags,
memblock_header_legacy_write,
memblock_header_legacy_flush,
memblock_header_legacy_invalidate,
memblock_header_legacy_reinit,
},
[HEADER_COMPACT] = {
memblock_header_compact_get_size,
memblock_header_compact_get_extra,
memblock_header_compact_get_flags,
memblock_header_compact_write,
memblock_header_compact_flush,
memblock_header_compact_invalidate,
memblock_header_compact_reinit,
},
[HEADER_NONE] = {
memblock_header_none_get_size,
memblock_header_none_get_extra,
memblock_header_none_get_flags,
memblock_header_none_write,
memblock_header_none_flush,
memblock_header_none_invalidate,
memblock_header_none_reinit,
}
};
static size_t
huge_block_size(const struct memory_block *m)
{
return CHUNKSIZE;
}
static size_t
run_block_size(const struct memory_block *m)
{
struct zone *z = ZID_TO_ZONE(m->heap->layout, m->zone_id);
struct chunk_run *run = (struct chunk_run *)&z->chunks[m->chunk_id];
return run->block_size;
}
static void *
huge_get_real_data(const struct memory_block *m)
{
struct zone *z = ZID_TO_ZONE(m->heap->layout, m->zone_id);
void *data = &z->chunks[m->chunk_id].data;
return (char *)data;
}
static void *
run_get_real_data(const struct memory_block *m)
{
struct zone *z = ZID_TO_ZONE(m->heap->layout, m->zone_id);
struct chunk_run *run =
(struct chunk_run *)&z->chunks[m->chunk_id].data;
ASSERT(run->block_size != 0);
return (char *)&run->data + (run->block_size * m->block_off);
}
static void *
block_get_user_data(const struct memory_block *m)
{
return (char *)m->m_ops->get_real_data(m) +
header_type_to_size[m->header_type];
}
static uint64_t
chunk_get_chunk_hdr_value(uint16_t type, uint16_t flags, uint32_t size_idx)
{
uint64_t val;
COMPILE_ERROR_ON(sizeof(struct chunk_header) != sizeof(uint64_t));
struct chunk_header hdr;
hdr.type = type;
hdr.flags = flags;
hdr.size_idx = size_idx;
memcpy(&val, &hdr, sizeof(val));
return val;
}
static void
huge_prep_operation_hdr(const struct memory_block *m, enum memblock_state op,
struct operation_context *ctx)
{
struct zone *z = ZID_TO_ZONE(m->heap->layout, m->zone_id);
struct chunk_header *hdr = &z->chunk_headers[m->chunk_id];
uint64_t val = chunk_get_chunk_hdr_value(
op == MEMBLOCK_ALLOCATED ? CHUNK_TYPE_USED : CHUNK_TYPE_FREE,
hdr->flags,
m->size_idx);
operation_add_entry(ctx, hdr, val, OPERATION_SET);
VALGRIND_DO_MAKE_MEM_NOACCESS(hdr + 1,
(hdr->size_idx - 1) * sizeof(struct chunk_header));
if (m->size_idx == 1)
return;
struct chunk_header *footer = hdr + m->size_idx - 1;
VALGRIND_DO_MAKE_MEM_UNDEFINED(footer, sizeof(*footer));
val = chunk_get_chunk_hdr_value(CHUNK_TYPE_FOOTER, 0, m->size_idx);
operation_add_typed_entry(ctx,
footer, val, OPERATION_SET, ENTRY_TRANSIENT);
}
static void
run_prep_operation_hdr(const struct memory_block *m, enum memblock_state op,
struct operation_context *ctx)
{
struct zone *z = ZID_TO_ZONE(m->heap->layout, m->zone_id);
struct chunk_run *r = (struct chunk_run *)&z->chunks[m->chunk_id];
ASSERT(m->size_idx <= BITS_PER_VALUE);
uint64_t bmask;
if (m->size_idx == BITS_PER_VALUE) {
ASSERTeq(m->block_off % BITS_PER_VALUE, 0);
bmask = UINT64_MAX;
} else {
bmask = ((1ULL << m->size_idx) - 1ULL) <<
(m->block_off % BITS_PER_VALUE);
}
int bpos = m->block_off / BITS_PER_VALUE;
if (op == MEMBLOCK_ALLOCATED) {
operation_add_entry(ctx, &r->bitmap[bpos],
bmask, OPERATION_OR);
} else if (op == MEMBLOCK_FREE) {
operation_add_entry(ctx, &r->bitmap[bpos],
~bmask, OPERATION_AND);
} else {
ASSERT(0);
}
}
static os_mutex_t *
huge_get_lock(const struct memory_block *m)
{
return NULL;
}
static os_mutex_t *
run_get_lock(const struct memory_block *m)
{
return heap_get_run_lock(m->heap, m->chunk_id);
}
static enum memblock_state
huge_get_state(const struct memory_block *m)
{
struct zone *z = ZID_TO_ZONE(m->heap->layout, m->zone_id);
struct chunk_header *hdr = &z->chunk_headers[m->chunk_id];
if (hdr->type == CHUNK_TYPE_USED)
return MEMBLOCK_ALLOCATED;
if (hdr->type == CHUNK_TYPE_FREE)
return MEMBLOCK_FREE;
return MEMBLOCK_STATE_UNKNOWN;
}
static enum memblock_state
run_get_state(const struct memory_block *m)
{
struct zone *z = ZID_TO_ZONE(m->heap->layout, m->zone_id);
struct chunk_header *hdr = &z->chunk_headers[m->chunk_id];
ASSERTeq(hdr->type, CHUNK_TYPE_RUN);
struct chunk_run *r = (struct chunk_run *)&z->chunks[m->chunk_id];
unsigned v = m->block_off / BITS_PER_VALUE;
uint64_t bitmap = r->bitmap[v];
unsigned b = m->block_off % BITS_PER_VALUE;
unsigned b_last = b + m->size_idx;
ASSERT(b_last <= BITS_PER_VALUE);
for (unsigned i = b; i < b_last; ++i) {
if (!BIT_IS_CLR(bitmap, i)) {
return MEMBLOCK_ALLOCATED;
}
}
return MEMBLOCK_FREE;
}
static void
huge_ensure_header_type(const struct memory_block *m,
enum header_type t)
{
struct zone *z = ZID_TO_ZONE(m->heap->layout, m->zone_id);
struct chunk_header *hdr = &z->chunk_headers[m->chunk_id];
ASSERTeq(hdr->type, CHUNK_TYPE_FREE);
if ((hdr->flags & header_type_to_flag[t]) == 0) {
VALGRIND_ADD_TO_TX(hdr, sizeof(*hdr));
uint16_t f = ((uint16_t)header_type_to_flag[t]);
hdr->flags |= f;
pmemops_persist(&m->heap->p_ops, hdr, sizeof(*hdr));
VALGRIND_REMOVE_FROM_TX(hdr, sizeof(*hdr));
}
}
static void
run_ensure_header_type(const struct memory_block *m,
enum header_type t)
{
#ifdef DEBUG
struct zone *z = ZID_TO_ZONE(m->heap->layout, m->zone_id);
struct chunk_header *hdr = &z->chunk_headers[m->chunk_id];
ASSERTeq(hdr->type, CHUNK_TYPE_RUN);
ASSERT((hdr->flags & header_type_to_flag[t]) == header_type_to_flag[t]);
#endif
}
static size_t
block_get_real_size(const struct memory_block *m)
{
if (m->size_idx != 0) {
return m->m_ops->block_size(m) * m->size_idx;
} else {
return memblock_header_ops[m->header_type].get_size(m);
}
}
static size_t
block_get_user_size(const struct memory_block *m)
{
return block_get_real_size(m) - header_type_to_size[m->header_type];
}
static void
block_write_header(const struct memory_block *m,
uint64_t extra_field, uint16_t flags)
{
memblock_header_ops[m->header_type].write(m,
block_get_real_size(m), extra_field, flags);
}
static void
block_flush_header(const struct memory_block *m)
{
memblock_header_ops[m->header_type].flush(m);
}
static void
block_invalidate_header(const struct memory_block *m)
{
memblock_header_ops[m->header_type].invalidate(m);
}
static void
block_reinit_header(const struct memory_block *m)
{
memblock_header_ops[m->header_type].reinit(m);
}
static uint64_t
block_get_extra(const struct memory_block *m)
{
return memblock_header_ops[m->header_type].get_extra(m);
}
static uint16_t
block_get_flags(const struct memory_block *m)
{
return memblock_header_ops[m->header_type].get_flags(m);
}
static const struct memory_block_ops mb_ops[MAX_MEMORY_BLOCK] = {
[MEMORY_BLOCK_HUGE] = {
.block_size = huge_block_size,
.prep_hdr = huge_prep_operation_hdr,
.get_lock = huge_get_lock,
.get_state = huge_get_state,
.get_user_data = block_get_user_data,
.get_real_data = huge_get_real_data,
.get_user_size = block_get_user_size,
.get_real_size = block_get_real_size,
.write_header = block_write_header,
.flush_header = block_flush_header,
.invalidate_header = block_invalidate_header,
.ensure_header_type = huge_ensure_header_type,
.reinit_header = block_reinit_header,
.get_extra = block_get_extra,
.get_flags = block_get_flags,
},
[MEMORY_BLOCK_RUN] = {
.block_size = run_block_size,
.prep_hdr = run_prep_operation_hdr,
.get_lock = run_get_lock,
.get_state = run_get_state,
.get_user_data = block_get_user_data,
.get_real_data = run_get_real_data,
.get_user_size = block_get_user_size,
.get_real_size = block_get_real_size,
.write_header = block_write_header,
.flush_header = block_flush_header,
.invalidate_header = block_invalidate_header,
.ensure_header_type = run_ensure_header_type,
.reinit_header = block_reinit_header,
.get_extra = block_get_extra,
.get_flags = block_get_flags,
}
};
static enum memory_block_type
memblock_detect_type(const struct memory_block *m, struct heap_layout *h)
{
enum memory_block_type ret;
switch (ZID_TO_ZONE(h, m->zone_id)->chunk_headers[m->chunk_id].type) {
case CHUNK_TYPE_RUN:
case CHUNK_TYPE_RUN_DATA:
ret = MEMORY_BLOCK_RUN;
break;
case CHUNK_TYPE_FREE:
case CHUNK_TYPE_USED:
case CHUNK_TYPE_FOOTER:
ret = MEMORY_BLOCK_HUGE;
break;
default:
FATAL("possible zone chunks metadata corruption");
}
return ret;
}
struct memory_block
memblock_from_offset_opt(struct palloc_heap *heap, uint64_t off, int size)
{
struct memory_block m = MEMORY_BLOCK_NONE;
m.heap = heap;
off -= HEAP_PTR_TO_OFF(heap, &heap->layout->zone0);
m.zone_id = (uint32_t)(off / ZONE_MAX_SIZE);
off -= (ZONE_MAX_SIZE * m.zone_id) + sizeof(struct zone);
m.chunk_id = (uint32_t)(off / CHUNKSIZE);
struct chunk_header *hdr = &ZID_TO_ZONE(heap->layout, m.zone_id)
->chunk_headers[m.chunk_id];
if (hdr->type == CHUNK_TYPE_RUN_DATA)
m.chunk_id -= hdr->size_idx;
off -= CHUNKSIZE * m.chunk_id;
m.header_type = memblock_header_type(&m);
off -= header_type_to_size[m.header_type];
m.type = off != 0 ? MEMORY_BLOCK_RUN : MEMORY_BLOCK_HUGE;
#ifdef DEBUG
enum memory_block_type t = memblock_detect_type(&m, heap->layout);
ASSERTeq(t, m.type);
#endif
m.m_ops = &mb_ops[m.type];
uint64_t unit_size = m.m_ops->block_size(&m);
if (off != 0) {
off -= RUN_METASIZE;
m.block_off = (uint16_t)(off / unit_size);
off -= m.block_off * unit_size;
}
m.size_idx = !size ? 0 : CALC_SIZE_IDX(unit_size,
memblock_header_ops[m.header_type].get_size(&m));
ASSERTeq(off, 0);
return m;
}
struct memory_block
memblock_from_offset(struct palloc_heap *heap, uint64_t off)
{
return memblock_from_offset_opt(heap, off, 1);
}
enum memblock_state
memblock_validate_offset(struct palloc_heap *heap, uint64_t off)
{
struct memory_block m = MEMORY_BLOCK_NONE;
m.heap = heap;
off -= HEAP_PTR_TO_OFF(heap, &heap->layout->zone0);
m.zone_id = (uint32_t)(off / ZONE_MAX_SIZE);
off -= (ZONE_MAX_SIZE * m.zone_id) + sizeof(struct zone);
m.chunk_id = (uint32_t)(off / CHUNKSIZE);
struct zone *z = ZID_TO_ZONE(heap->layout, m.zone_id);
struct chunk_header *hdr = &z->chunk_headers[m.chunk_id];
if (hdr->type == CHUNK_TYPE_RUN_DATA)
m.chunk_id -= hdr->size_idx;
off -= CHUNKSIZE * m.chunk_id;
for (uint32_t i = 0; i < z->header.size_idx; ) {
hdr = &z->chunk_headers[i];
if (i + hdr->size_idx > m.chunk_id && i < m.chunk_id) {
return MEMBLOCK_STATE_UNKNOWN;
} else if (m.chunk_id == i) {
break;
}
i += hdr->size_idx;
}
ASSERTne(hdr, NULL);
m.header_type = memblock_header_type(&m);
if (hdr->type != CHUNK_TYPE_RUN) {
if (header_type_to_size[m.header_type] != off)
return MEMBLOCK_STATE_UNKNOWN;
else if (hdr->type == CHUNK_TYPE_USED)
return MEMBLOCK_ALLOCATED;
else if (hdr->type == CHUNK_TYPE_FREE)
return MEMBLOCK_FREE;
else
return MEMBLOCK_STATE_UNKNOWN;
}
if (header_type_to_size[m.header_type] > off)
return MEMBLOCK_STATE_UNKNOWN;
off -= header_type_to_size[m.header_type];
m.type = off != 0 ? MEMORY_BLOCK_RUN : MEMORY_BLOCK_HUGE;
#ifdef DEBUG
enum memory_block_type t = memblock_detect_type(&m, heap->layout);
ASSERTeq(t, m.type);
#endif
m.m_ops = &mb_ops[m.type];
uint64_t unit_size = m.m_ops->block_size(&m);
if (off != 0) {
off -= RUN_METASIZE;
m.block_off = (uint16_t)(off / unit_size);
off -= m.block_off * unit_size;
}
m.size_idx = CALC_SIZE_IDX(unit_size,
memblock_header_ops[m.header_type].get_size(&m));
ASSERTeq(off, 0);
return m.m_ops->get_state(&m);
}
void
memblock_rebuild_state(struct palloc_heap *heap, struct memory_block *m)
{
m->heap = heap;
m->header_type = memblock_header_type(m);
m->type = memblock_detect_type(m, heap->layout);
m->m_ops = &mb_ops[m->type];
}