/* * heap_init -- initializes the heap * * If successful function returns zero. Otherwise an error number is returned. */ int heap_init(void *heap_start, uint64_t heap_size, struct pmem_ops *p_ops) { if (heap_size < HEAP_MIN_SIZE) return EINVAL; VALGRIND_DO_MAKE_MEM_UNDEFINED(heap_start, heap_size); struct heap_layout *layout = heap_start; heap_write_header(&layout->header, heap_size); pmemops_persist(p_ops, &layout->header, sizeof(struct heap_header)); unsigned zones = heap_max_zone(heap_size); for (unsigned i = 0; i < zones; ++i) { pmemops_memset_persist(p_ops, &ZID_TO_ZONE(layout, i)->header, 0, sizeof(struct zone_header)); pmemops_memset_persist(p_ops, &ZID_TO_ZONE(layout, i)->chunk_headers, 0, sizeof(struct chunk_header)); /* only explicitly allocated chunks should be accessible */ VALGRIND_DO_MAKE_MEM_NOACCESS( &ZID_TO_ZONE(layout, i)->chunk_headers, sizeof(struct chunk_header)); } return 0; }
/* * alloc_prep_block -- (internal) prepares a memory block for allocation * * Once the block is fully reserved and it's guaranteed that no one else will * be able to write to this memory region it is safe to write the allocation * header and call the object construction function. * * Because the memory block at this stage is only reserved in transient state * there's no need to worry about fail-safety of this method because in case * of a crash the memory will be back in the free blocks collection. */ static int alloc_prep_block(struct palloc_heap *heap, struct memory_block m, palloc_constr constructor, void *arg, uint64_t *offset_value) { void *block_data = heap_get_block_data(heap, m); void *userdatap = (char *)block_data + ALLOC_OFF; uint64_t unit_size = MEMBLOCK_OPS(AUTO, &m)-> block_size(&m, heap->layout); uint64_t real_size = unit_size * m.size_idx; ASSERT((uint64_t)block_data % ALLOC_BLOCK_SIZE == 0); ASSERT((uint64_t)userdatap % ALLOC_BLOCK_SIZE == 0); /* mark everything (including headers) as accessible */ VALGRIND_DO_MAKE_MEM_UNDEFINED(block_data, real_size); /* mark space as allocated */ VALGRIND_DO_MEMPOOL_ALLOC(heap->layout, userdatap, real_size - ALLOC_OFF); alloc_write_header(heap, block_data, m, real_size); int ret; if (constructor != NULL && (ret = constructor(heap->base, userdatap, real_size - ALLOC_OFF, arg)) != 0) { /* * If canceled, revert the block back to the free state in vg * machinery. Because the free operation is only performed on * the user data, the allocation header is made inaccessible * in a separate call. */ VALGRIND_DO_MEMPOOL_FREE(heap->layout, userdatap); VALGRIND_DO_MAKE_MEM_NOACCESS(block_data, ALLOC_OFF); /* * During this method there are several stores to pmem that are * not immediately flushed and in case of a cancellation those * stores are no longer relevant anyway. */ VALGRIND_SET_CLEAN(block_data, ALLOC_OFF); return ret; } /* flushes both the alloc and oob headers */ pmemops_persist(&heap->p_ops, block_data, ALLOC_OFF); /* * To avoid determining the user data pointer twice this method is also * responsible for calculating the offset of the object in the pool that * will be used to set the offset destination pointer provided by the * caller. */ *offset_value = PMALLOC_PTR_TO_OFF(heap, userdatap); return 0; }
/* * huge_prep_operation_hdr -- prepares the new value of a chunk header that will * be set after the operation concludes. */ static void huge_prep_operation_hdr(const struct memory_block *m, enum memblock_state op, struct operation_context *ctx) { struct chunk_header *hdr = heap_get_chunk_hdr(m->heap, m); /* * Depending on the operation that needs to be performed a new chunk * header needs to be prepared with the new chunk state. */ uint64_t val = chunk_get_chunk_hdr_value( op == MEMBLOCK_ALLOCATED ? CHUNK_TYPE_USED : CHUNK_TYPE_FREE, hdr->flags, m->size_idx); if (ctx == NULL) { util_atomic_store_explicit64((uint64_t *)hdr, val, memory_order_relaxed); pmemops_persist(&m->heap->p_ops, hdr, sizeof(*hdr)); } else { operation_add_entry(ctx, hdr, val, ULOG_OPERATION_SET); } VALGRIND_DO_MAKE_MEM_NOACCESS(hdr + 1, (hdr->size_idx - 1) * sizeof(struct chunk_header)); /* * In the case of chunks larger than one unit the footer must be * created immediately AFTER the persistent state is safely updated. */ 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); /* * It's only safe to write the footer AFTER the persistent part of * the operation have been successfully processed because the footer * pointer might point to a currently valid persistent state * of a different chunk. * The footer entry change is updated as transient because it will * be recreated at heap boot regardless - it's just needed for runtime * operations. */ if (ctx == NULL) { util_atomic_store_explicit64((uint64_t *)footer, val, memory_order_relaxed); VALGRIND_SET_CLEAN(footer, sizeof(*footer)); } else { operation_add_typed_entry(ctx, footer, val, ULOG_OPERATION_SET, LOG_TRANSIENT); } }
/* * huge_ensure_header_type -- checks the header type of a chunk and modifies * it if necessary. This is fail-safe atomic. */ static void huge_ensure_header_type(const struct memory_block *m, enum header_type t) { struct chunk_header *hdr = heap_get_chunk_hdr(m->heap, m); 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)); } }
/* * list_fill_entry_persist -- (internal) fill new entry using persist function * * Used for newly allocated objects. */ static void list_fill_entry_persist(PMEMobjpool *pop, struct list_entry *entry_ptr, uint64_t next_offset, uint64_t prev_offset) { LOG(15, NULL); VALGRIND_ADD_TO_TX(entry_ptr, sizeof(*entry_ptr)); entry_ptr->pe_next.pool_uuid_lo = pop->uuid_lo; entry_ptr->pe_next.off = next_offset; entry_ptr->pe_prev.pool_uuid_lo = pop->uuid_lo; entry_ptr->pe_prev.off = prev_offset; VALGRIND_REMOVE_FROM_TX(entry_ptr, sizeof(*entry_ptr)); pmemops_persist(&pop->p_ops, entry_ptr, sizeof(*entry_ptr)); }
/* * huge_ensure_header_type -- checks the header type of a chunk and modifies * it if necessery. This is fail-safe atomic. */ 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)); } }
/* * pvector_push_back -- bumps the number of values in the vector and returns * the pointer to the value position to which the caller must set the * value. Calling this method without actually setting the value will * result in an inconsistent vector state. */ uint64_t * pvector_push_back(struct pvector_context *ctx) { uint64_t idx = ctx->nvalues; struct array_spec s = pvector_get_array_spec(idx); if (s.idx >= PVECTOR_MAX_ARRAYS) { ERR("Exceeded maximum number of entries in persistent vector"); return NULL; } PMEMobjpool *pop = ctx->pop; /* * If the destination array does not exist, calculate its size * and allocate it. */ if (ctx->vec->arrays[s.idx] == 0) { if (s.idx == 0) { /* * In the case the vector is completely empty the * initial embedded array must be assigned as the first * element of the sequence. */ ASSERTeq(util_is_zeroed(ctx->vec, sizeof(*ctx->vec)), 1); ctx->vec->arrays[0] = OBJ_PTR_TO_OFF(pop, &ctx->vec->embedded); pmemops_persist(&pop->p_ops, &ctx->vec->arrays[0], sizeof(ctx->vec->arrays[0])); } else { size_t arr_size = sizeof(uint64_t) * (1ULL << (s.idx + PVECTOR_INIT_SHIFT)); if (pmalloc_construct(pop, &ctx->vec->arrays[s.idx], arr_size, pvector_array_constr, NULL, 0, OBJ_INTERNAL_OBJECT_MASK, 0) != 0) return NULL; } } ctx->nvalues++; uint64_t *arrp = OBJ_OFF_TO_PTR(pop, ctx->vec->arrays[s.idx]); return &arrp[s.pos]; }
/* * list_fill_entry_redo_log -- (internal) fill new entry using redo log * * Used to update entry in existing object. */ static size_t list_fill_entry_redo_log(PMEMobjpool *pop, struct redo_log *redo, size_t redo_index, struct list_args_common *args, uint64_t next_offset, uint64_t prev_offset, int set_uuid) { LOG(15, NULL); struct pmem_ops *ops = &pop->p_ops; ASSERTne(args->entry_ptr, NULL); ASSERTne(args->obj_doffset, 0); if (set_uuid) { VALGRIND_ADD_TO_TX(&(args->entry_ptr->pe_next.pool_uuid_lo), sizeof(args->entry_ptr->pe_next.pool_uuid_lo)); VALGRIND_ADD_TO_TX(&(args->entry_ptr->pe_prev.pool_uuid_lo), sizeof(args->entry_ptr->pe_prev.pool_uuid_lo)); /* don't need to fill pool uuid using redo log */ args->entry_ptr->pe_next.pool_uuid_lo = pop->uuid_lo; args->entry_ptr->pe_prev.pool_uuid_lo = pop->uuid_lo; VALGRIND_REMOVE_FROM_TX( &(args->entry_ptr->pe_next.pool_uuid_lo), sizeof(args->entry_ptr->pe_next.pool_uuid_lo)); VALGRIND_REMOVE_FROM_TX( &(args->entry_ptr->pe_prev.pool_uuid_lo), sizeof(args->entry_ptr->pe_prev.pool_uuid_lo)); pmemops_persist(ops, args->entry_ptr, sizeof(*args->entry_ptr)); } else { ASSERTeq(args->entry_ptr->pe_next.pool_uuid_lo, pop->uuid_lo); ASSERTeq(args->entry_ptr->pe_prev.pool_uuid_lo, pop->uuid_lo); } /* set current->next and current->prev using redo log */ uint64_t next_off_off = args->obj_doffset + NEXT_OFF; uint64_t prev_off_off = args->obj_doffset + PREV_OFF; u64_add_offset(&next_off_off, args->pe_offset); u64_add_offset(&prev_off_off, args->pe_offset); redo_log_store(pop->redo, redo, redo_index + 0, next_off_off, next_offset); redo_log_store(pop->redo, redo, redo_index + 1, prev_off_off, prev_offset); return redo_index + 2; }
/* * list_fill_entry_redo_log -- (internal) fill new entry using redo log * * Used to update entry in existing object. */ static size_t list_fill_entry_redo_log(PMEMobjpool *pop, struct operation_context *ctx, struct list_args_common *args, uint64_t next_offset, uint64_t prev_offset, int set_uuid) { LOG(15, NULL); struct pmem_ops *ops = &pop->p_ops; ASSERTne(args->entry_ptr, NULL); ASSERTne(args->obj_doffset, 0); if (set_uuid) { VALGRIND_ADD_TO_TX(&(args->entry_ptr->pe_next.pool_uuid_lo), sizeof(args->entry_ptr->pe_next.pool_uuid_lo)); VALGRIND_ADD_TO_TX(&(args->entry_ptr->pe_prev.pool_uuid_lo), sizeof(args->entry_ptr->pe_prev.pool_uuid_lo)); /* don't need to fill pool uuid using redo log */ args->entry_ptr->pe_next.pool_uuid_lo = pop->uuid_lo; args->entry_ptr->pe_prev.pool_uuid_lo = pop->uuid_lo; VALGRIND_REMOVE_FROM_TX( &(args->entry_ptr->pe_next.pool_uuid_lo), sizeof(args->entry_ptr->pe_next.pool_uuid_lo)); VALGRIND_REMOVE_FROM_TX( &(args->entry_ptr->pe_prev.pool_uuid_lo), sizeof(args->entry_ptr->pe_prev.pool_uuid_lo)); pmemops_persist(ops, args->entry_ptr, sizeof(*args->entry_ptr)); } else { ASSERTeq(args->entry_ptr->pe_next.pool_uuid_lo, pop->uuid_lo); ASSERTeq(args->entry_ptr->pe_prev.pool_uuid_lo, pop->uuid_lo); } /* set current->next and current->prev using redo log */ uint64_t next_off_off = args->obj_doffset + NEXT_OFF; uint64_t prev_off_off = args->obj_doffset + PREV_OFF; u64_add_offset(&next_off_off, args->pe_offset); u64_add_offset(&prev_off_off, args->pe_offset); void *next_ptr = (char *)pop + next_off_off; void *prev_ptr = (char *)pop + prev_off_off; operation_add_entry(ctx, next_ptr, next_offset, ULOG_OPERATION_SET); operation_add_entry(ctx, prev_ptr, prev_offset, ULOG_OPERATION_SET); return 0; }
/* * heap_chunk_init -- (internal) writes chunk header */ static void heap_chunk_init(struct palloc_heap *heap, struct chunk_header *hdr, uint16_t type, uint32_t size_idx) { struct chunk_header nhdr = { .type = type, .flags = 0, .size_idx = size_idx }; VALGRIND_DO_MAKE_MEM_UNDEFINED(hdr, sizeof(*hdr)); *hdr = nhdr; /* write the entire header (8 bytes) at once */ pmemops_persist(&heap->p_ops, hdr, sizeof(*hdr)); heap_chunk_write_footer(hdr, size_idx); } /* * heap_zone_init -- (internal) writes zone's first chunk and header */ static void heap_zone_init(struct palloc_heap *heap, uint32_t zone_id) { struct zone *z = ZID_TO_ZONE(heap->layout, zone_id); uint32_t size_idx = get_zone_size_idx(zone_id, heap->rt->max_zone, heap->size); heap_chunk_init(heap, &z->chunk_headers[0], CHUNK_TYPE_FREE, size_idx); struct zone_header nhdr = { .size_idx = size_idx, .magic = ZONE_HEADER_MAGIC, }; z->header = nhdr; /* write the entire header (8 bytes) at once */ pmemops_persist(&heap->p_ops, &z->header, sizeof(z->header)); } /* * heap_run_init -- (internal) creates a run based on a chunk */ static void heap_run_init(struct palloc_heap *heap, struct bucket *b, const struct memory_block *m) { struct alloc_class *c = b->aclass; ASSERTeq(c->type, CLASS_RUN); struct zone *z = ZID_TO_ZONE(heap->layout, m->zone_id); struct chunk_run *run = (struct chunk_run *)&z->chunks[m->chunk_id]; ASSERTne(m->size_idx, 0); size_t runsize = SIZEOF_RUN(run, m->size_idx); VALGRIND_DO_MAKE_MEM_UNDEFINED(run, runsize); /* add/remove chunk_run and chunk_header to valgrind transaction */ VALGRIND_ADD_TO_TX(run, runsize); run->block_size = c->unit_size; pmemops_persist(&heap->p_ops, &run->block_size, sizeof(run->block_size)); /* set all the bits */ memset(run->bitmap, 0xFF, sizeof(run->bitmap)); unsigned nval = c->run.bitmap_nval; ASSERT(nval > 0); /* clear only the bits available for allocations from this bucket */ memset(run->bitmap, 0, sizeof(uint64_t) * (nval - 1)); run->bitmap[nval - 1] = c->run.bitmap_lastval; run->incarnation_claim = heap->run_id; VALGRIND_SET_CLEAN(&run->incarnation_claim, sizeof(run->incarnation_claim)); VALGRIND_REMOVE_FROM_TX(run, runsize); pmemops_persist(&heap->p_ops, run->bitmap, sizeof(run->bitmap)); struct chunk_header run_data_hdr; run_data_hdr.type = CHUNK_TYPE_RUN_DATA; run_data_hdr.flags = 0; struct chunk_header *data_hdr; for (unsigned i = 1; i < m->size_idx; ++i) { data_hdr = &z->chunk_headers[m->chunk_id + i]; VALGRIND_DO_MAKE_MEM_UNDEFINED(data_hdr, sizeof(*data_hdr)); VALGRIND_ADD_TO_TX(data_hdr, sizeof(*data_hdr)); run_data_hdr.size_idx = i; *data_hdr = run_data_hdr; VALGRIND_REMOVE_FROM_TX(data_hdr, sizeof(*data_hdr)); } pmemops_persist(&heap->p_ops, &z->chunk_headers[m->chunk_id + 1], sizeof(struct chunk_header) * (m->size_idx - 1)); struct chunk_header *hdr = &z->chunk_headers[m->chunk_id]; ASSERT(hdr->type == CHUNK_TYPE_FREE); VALGRIND_ADD_TO_TX(hdr, sizeof(*hdr)); struct chunk_header run_hdr; run_hdr.size_idx = hdr->size_idx; run_hdr.type = CHUNK_TYPE_RUN; run_hdr.flags = header_type_to_flag[c->header_type]; *hdr = run_hdr; VALGRIND_REMOVE_FROM_TX(hdr, sizeof(*hdr)); pmemops_persist(&heap->p_ops, hdr, sizeof(*hdr)); } /* * heap_run_insert -- (internal) inserts and splits a block of memory into a run */ static void heap_run_insert(struct palloc_heap *heap, struct bucket *b, const struct memory_block *m, uint32_t size_idx, uint16_t block_off) { struct alloc_class *c = b->aclass; ASSERTeq(c->type, CLASS_RUN); ASSERT(size_idx <= BITS_PER_VALUE); ASSERT(block_off + size_idx <= c->run.bitmap_nallocs); uint32_t unit_max = c->run.unit_max; struct memory_block nm = *m; nm.size_idx = unit_max - (block_off % unit_max); nm.block_off = block_off; if (nm.size_idx > size_idx) nm.size_idx = size_idx; do { bucket_insert_block(b, &nm); ASSERT(nm.size_idx <= UINT16_MAX); ASSERT(nm.block_off + nm.size_idx <= UINT16_MAX); nm.block_off = (uint16_t)(nm.block_off + (uint16_t)nm.size_idx); size_idx -= nm.size_idx; nm.size_idx = size_idx > unit_max ? unit_max : size_idx; } while (size_idx != 0); } /* * heap_process_run_metadata -- (internal) parses the run bitmap */ static uint32_t heap_process_run_metadata(struct palloc_heap *heap, struct bucket *b, const struct memory_block *m) { struct alloc_class *c = b->aclass; ASSERTeq(c->type, CLASS_RUN); uint16_t block_off = 0; uint16_t block_size_idx = 0; uint32_t inserted_blocks = 0; struct zone *z = ZID_TO_ZONE(heap->layout, m->zone_id); struct chunk_run *run = (struct chunk_run *)&z->chunks[m->chunk_id]; for (unsigned i = 0; i < c->run.bitmap_nval; ++i) { ASSERT(i < MAX_BITMAP_VALUES); uint64_t v = run->bitmap[i]; ASSERT(BITS_PER_VALUE * i <= UINT16_MAX); block_off = (uint16_t)(BITS_PER_VALUE * i); if (v == 0) { heap_run_insert(heap, b, m, BITS_PER_VALUE, block_off); inserted_blocks += BITS_PER_VALUE; continue; } else if (v == UINT64_MAX) { continue; } for (unsigned j = 0; j < BITS_PER_VALUE; ++j) { if (BIT_IS_CLR(v, j)) { block_size_idx++; } else if (block_size_idx != 0) { ASSERT(block_off >= block_size_idx); heap_run_insert(heap, b, m, block_size_idx, (uint16_t)(block_off - block_size_idx)); inserted_blocks += block_size_idx; block_size_idx = 0; } if ((block_off++) == c->run.bitmap_nallocs) { i = MAX_BITMAP_VALUES; break; } } if (block_size_idx != 0) { ASSERT(block_off >= block_size_idx); heap_run_insert(heap, b, m, block_size_idx, (uint16_t)(block_off - block_size_idx)); inserted_blocks += block_size_idx; block_size_idx = 0; } } return inserted_blocks; } /* * heap_create_run -- (internal) initializes a new run on an existing free chunk */ static void heap_create_run(struct palloc_heap *heap, struct bucket *b, struct memory_block *m) { heap_run_init(heap, b, m); memblock_rebuild_state(heap, m); heap_process_run_metadata(heap, b, m); }
/* * memblock_huge_init -- initializes a new huge memory block */ struct memory_block memblock_huge_init(struct palloc_heap *heap, uint32_t chunk_id, uint32_t zone_id, uint32_t size_idx) { struct memory_block m = MEMORY_BLOCK_NONE; m.chunk_id = chunk_id; m.zone_id = zone_id; m.size_idx = size_idx; m.heap = heap; struct chunk_header nhdr = { .type = CHUNK_TYPE_FREE, .flags = 0, .size_idx = size_idx }; struct chunk_header *hdr = heap_get_chunk_hdr(heap, &m); VALGRIND_DO_MAKE_MEM_UNDEFINED(hdr, sizeof(*hdr)); VALGRIND_ANNOTATE_NEW_MEMORY(hdr, sizeof(*hdr)); *hdr = nhdr; /* write the entire header (8 bytes) at once */ pmemops_persist(&heap->p_ops, hdr, sizeof(*hdr)); huge_write_footer(hdr, size_idx); memblock_rebuild_state(heap, &m); return m; } /* * memblock_run_init -- initializes a new run memory block */ struct memory_block memblock_run_init(struct palloc_heap *heap, uint32_t chunk_id, uint32_t zone_id, uint32_t size_idx, uint16_t flags, uint64_t unit_size, uint64_t alignment) { ASSERTne(size_idx, 0); struct memory_block m = MEMORY_BLOCK_NONE; m.chunk_id = chunk_id; m.zone_id = zone_id; m.size_idx = size_idx; m.heap = heap; struct zone *z = ZID_TO_ZONE(heap->layout, zone_id); struct chunk_run *run = heap_get_chunk_run(heap, &m); size_t runsize = SIZEOF_RUN(run, size_idx); VALGRIND_DO_MAKE_MEM_UNDEFINED(run, runsize); /* add/remove chunk_run and chunk_header to valgrind transaction */ VALGRIND_ADD_TO_TX(run, runsize); run->hdr.block_size = unit_size; run->hdr.alignment = alignment; struct run_bitmap b; memblock_run_bitmap(&size_idx, flags, unit_size, alignment, run->content, &b); size_t bitmap_size = b.size; /* set all the bits */ memset(b.values, 0xFF, bitmap_size); /* clear only the bits available for allocations from this bucket */ memset(b.values, 0, sizeof(*b.values) * (b.nvalues - 1)); unsigned trailing_bits = b.nbits % RUN_BITS_PER_VALUE; uint64_t last_value = UINT64_MAX << trailing_bits; b.values[b.nvalues - 1] = last_value; VALGRIND_REMOVE_FROM_TX(run, runsize); pmemops_flush(&heap->p_ops, run, sizeof(struct chunk_run_header) + bitmap_size); struct chunk_header run_data_hdr; run_data_hdr.type = CHUNK_TYPE_RUN_DATA; run_data_hdr.flags = 0; VALGRIND_ADD_TO_TX(&z->chunk_headers[chunk_id], sizeof(struct chunk_header) * size_idx); struct chunk_header *data_hdr; for (unsigned i = 1; i < size_idx; ++i) { data_hdr = &z->chunk_headers[chunk_id + i]; VALGRIND_DO_MAKE_MEM_UNDEFINED(data_hdr, sizeof(*data_hdr)); VALGRIND_ANNOTATE_NEW_MEMORY(data_hdr, sizeof(*data_hdr)); run_data_hdr.size_idx = i; *data_hdr = run_data_hdr; } pmemops_persist(&heap->p_ops, &z->chunk_headers[chunk_id + 1], sizeof(struct chunk_header) * (size_idx - 1)); struct chunk_header *hdr = &z->chunk_headers[chunk_id]; ASSERT(hdr->type == CHUNK_TYPE_FREE); VALGRIND_ANNOTATE_NEW_MEMORY(hdr, sizeof(*hdr)); struct chunk_header run_hdr; run_hdr.size_idx = hdr->size_idx; run_hdr.type = CHUNK_TYPE_RUN; run_hdr.flags = flags; *hdr = run_hdr; pmemops_persist(&heap->p_ops, hdr, sizeof(*hdr)); VALGRIND_REMOVE_FROM_TX(&z->chunk_headers[chunk_id], sizeof(struct chunk_header) * size_idx); memblock_rebuild_state(heap, &m); return m; }