@samitouri / QOSamiQemu / commits / de62e5ba2c

vmstate: Implement VMS_ARRAY_OF_POINTER_AUTO_ALLOC

Introduce a new flag, VMS_ARRAY_OF_POINTER_AUTO_ALLOC, for VMSD field. It must be used together with VMS_ARRAY_OF_POINTER. It can be used to allow migration of an array of pointers where the pointers may point to NULLs. Note that we used to allow migration of a NULL pointer within an array that is being migrated. That corresponds to the code around vmstate_info_nullptr where we may get/put one byte showing that the element of an array is NULL. That usage is fine but very limited, it's because even if it will migrate a NULL pointer with a marker, it still works in a way that both src and dest QEMUs must know exactly which elements of the array are non-NULL, so instead of dynamically loading an array (which can have NULL pointers), it actually only verifies the known NULL pointers are still NULL pointers after migration. Also, in that case since dest QEMU knows exactly which element is NULL, which is not NULL, dest QEMU's device code will manage all allocations for the elements before invoking vmstate_load_vmsd(). That's not enough per evolving needs of new device states that may want to provide real dynamic array of pointers, like what Alexander proposed here with the NVMe device migration: https://lore.kernel.org/r/20260317102708.126725-1-alexander@mihalicyn.com This patch is an alternative approach to address the problem. Along with the flag, introduce two new macros: VMSTATE_VARRAY_OF_POINTER_TO_STRUCT_UINT{8|32}_ALLOC() Which will be used very soon in the NVMe series. Reviewed-by: Alexander Mikhalitsyn <aleksandr.mikhalitsyn@futurfusion.io> Tested-by: Alexander Mikhalitsyn <aleksandr.mikhalitsyn@futurfusion.io> Signed-off-by: Peter Xu <peterx@redhat.com> Reviewed-by: Juraj Marcin <jmarcin@redhat.com> Link: https://lore.kernel.org/qemu-devel/20260401202844.673494-10-peterx@redhat.com Signed-off-by: Fabiano Rosas <farosas@suse.de>

Peter Xu committed Apr 1, 2026 at 16:28 UTC de62e5ba2ca1671fa4bc55fbaf9b4c2d07e38790
3 files changed +199 -24
include/migration/vmstate.h
+50 -1
@@ -161,8 +161,21 @@ enum VMStateFlags {
161 * structure we are referencing to use. */
162 VMS_VSTRUCT = 0x8000,
163
164 + /*
165 + * This is a sub-flag for VMS_ARRAY_OF_POINTER. When this flag is set,
166 + * VMS_ARRAY_OF_POINTER must also be set. When set, it means array
167 + * elements can contain either valid or NULL pointers, vmstate core
168 + * will be responsible for synchronizing the pointer status, providing
169 + * proper memory allocations on the pointer when it is populated on the
170 + * source QEMU. It also means the user of the field must make sure all
171 + * the elements in the array are NULL pointers before loading. This
172 + * should also work with VMS_ALLOC when the array itself also needs to
173 + * be allocated.
174 + */
175 + VMS_ARRAY_OF_POINTER_AUTO_ALLOC = 0x10000,
176 +
177 /* Marker for end of list */
165 - VMS_END = 0x10000
178 + VMS_END = 0x20000,
179 };
180
181 typedef enum {
@@ -580,6 +593,42 @@ extern const VMStateInfo vmstate_info_qlist;
593 .offset = vmstate_offset_array(_s, _f, _type*, _n), \
594 }
595
596 +/*
597 + * For migrating a dynamically allocated uint{8,32}-indexed array of
598 + * pointers to structures (with NULL entries and with auto memory
599 + * allocation).
600 + *
601 + * _type: type of structure pointed to
602 + * _vmsd: VMSD for structure _type (when VMS_STRUCT is set)
603 + * _info: VMStateInfo for _type (when VMS_STRUCT is not set)
604 + * start: size of (_type) pointed to (for auto memory allocation)
605 + */
606 +#define VMSTATE_VARRAY_OF_POINTER_TO_STRUCT_UINT8_ALLOC(\
607 + _field, _state, _field_num, _version, _vmsd, _type) { \
608 + .name = (stringify(_field)), \
609 + .version_id = (_version), \
610 + .num_offset = vmstate_offset_value(_state, _field_num, uint8_t), \
611 + .vmsd = &(_vmsd), \
612 + .size = sizeof(_type), \
613 + .flags = VMS_POINTER | VMS_VARRAY_UINT8 | \
614 + VMS_ARRAY_OF_POINTER | VMS_STRUCT | \
615 + VMS_ARRAY_OF_POINTER_AUTO_ALLOC, \
616 + .offset = vmstate_offset_pointer(_state, _field, _type *), \
617 +}
618 +
619 +#define VMSTATE_VARRAY_OF_POINTER_TO_STRUCT_UINT32_ALLOC(\
620 + _field, _state, _field_num, _version, _vmsd, _type) { \
621 + .name = (stringify(_field)), \
622 + .version_id = (_version), \
623 + .num_offset = vmstate_offset_value(_state, _field_num, uint32_t), \
624 + .vmsd = &(_vmsd), \
625 + .size = sizeof(_type), \
626 + .flags = VMS_POINTER | VMS_VARRAY_UINT32 | \
627 + VMS_ARRAY_OF_POINTER | VMS_STRUCT | \
628 + VMS_ARRAY_OF_POINTER_AUTO_ALLOC, \
629 + .offset = vmstate_offset_pointer(_state, _field, _type *), \
630 +}
631 +
632 #define VMSTATE_VARRAY_OF_POINTER_UINT32(_field, _state, _field_num, _version, _info, _type) { \
633 .name = (stringify(_field)), \
634 .version_id = (_version), \
migration/savevm.c
+26 -1
@@ -869,8 +869,33 @@ static void vmstate_check(const VMStateDescription *vmsd)
869 if (field) {
870 while (field->name) {
871 if (field->flags & VMS_ARRAY_OF_POINTER) {
872 - assert(field->size == 0);
872 + if (field->flags & VMS_ARRAY_OF_POINTER_AUTO_ALLOC) {
873 + /*
874 + * Size must be provided because dest QEMU needs that
875 + * info to know what to allocate
876 + */
877 + assert(field->size || field->size_offset);
878 + } else {
879 + /*
880 + * Otherwise size info isn't useful (because it's
881 + * always the size of host pointer), detect accidental
882 + * setup of sizes in this case.
883 + */
884 + assert(field->size == 0 && field->size_offset == 0);
885 + }
886 + /*
887 + * VMS_ARRAY_OF_POINTER must be used only together with one
888 + * of VMS_(V)ARRAY* flags.
889 + */
890 + assert(field->flags & (VMS_ARRAY | VMS_VARRAY_INT32 |
891 + VMS_VARRAY_UINT16 | VMS_VARRAY_UINT8 |
892 + VMS_VARRAY_UINT32));
893 }
894 +
895 + if (field->flags & VMS_ARRAY_OF_POINTER_AUTO_ALLOC) {
896 + assert(field->flags & VMS_ARRAY_OF_POINTER);
897 + }
898 +
899 if (field->flags & (VMS_STRUCT | VMS_VSTRUCT)) {
900 /* Recurse to sub structures */
901 vmstate_check(field->vmsd);
migration/vmstate.c
+123 -22
@@ -153,6 +153,12 @@ static bool vmstate_ptr_marker_load(QEMUFile *f, bool *load_field,
153 return true;
154 }
155
156 + if (byte == VMS_MARKER_PTR_VALID) {
157 + /* We need to load the field right after the marker */
158 + *load_field = true;
159 + return true;
160 + }
161 +
162 error_setg(errp, "Unexpected ptr marker: %d", byte);
163 return false;
164 }
@@ -234,6 +240,76 @@ static bool vmstate_post_load(const VMStateDescription *vmsd,
240 return true;
241 }
242
243 +/*
244 + * Try to prepare loading the next element, the object pointer to be put
245 + * into @next_elem. When @next_elem is NULL, it means we should skip
246 + * loading this element.
247 + *
248 + * Returns false for errors, in which case *errp will be set, migration
249 + * must be aborted.
250 + */
251 +static bool vmstate_load_next(QEMUFile *f, const VMStateField *field,
252 + void *first_elem, void **next_elem,
253 + int size, int i, Error **errp)
254 +{
255 + bool auto_alloc = field->flags & VMS_ARRAY_OF_POINTER_AUTO_ALLOC;
256 + void *ptr = first_elem + size * i, **pptr;
257 + bool load_field;
258 +
259 + if (!(field->flags & VMS_ARRAY_OF_POINTER)) {
260 + /* Simplest case, no pointer involved */
261 + *next_elem = ptr;
262 + return true;
263 + }
264 +
265 + /*
266 + * We're loading an array of pointers, switch to use pptr to make it
267 + * easier to read later
268 + */
269 + pptr = (void **)ptr;
270 +
271 + /*
272 + * If auto_alloc is on, making sure the user provided an array of NULL
273 + * pointers to start with
274 + */
275 + assert(!auto_alloc || *pptr == NULL);
276 +
277 + /*
278 + * When pointer is null, we must expect a ptr marker first. Use cases:
279 + *
280 + * (1) _AUTO_ALLOC implies a ptr marker will always exist, or,
281 + *
282 + * (2) the element on destination is NULL, which expects the src to send a
283 + * NULL-only marker.
284 + *
285 + * Here, checking against a NULL pointer will work for both.
286 + */
287 + if (!*pptr) {
288 + if (!vmstate_ptr_marker_load(f, &load_field, errp)) {
289 + trace_vmstate_load_field_error(field->name, -EINVAL);
290 + return false;
291 + }
292 +
293 + /*
294 + * If loading is needed, do pre-allocation first (otherwise keeping
295 + * *pptr==NULL to imply a skip below)
296 + */
297 + if (load_field) {
298 + /* Only applies when auto_alloc=on on the field */
299 + assert(auto_alloc);
300 + /*
301 + * NOTE: do not use vmstate_size() here, because we need the
302 + * object size, not entry size of the array.
303 + */
304 + *pptr = g_malloc0(field->size);
305 + }
306 + }
307 +
308 + /* Move the cursor to the next element for loading */
309 + *next_elem = *pptr;
310 + return true;
311 +}
312 +
313 bool vmstate_load_vmsd(QEMUFile *f, const VMStateDescription *vmsd,
314 void *opaque, int version_id, Error **errp)
315 {
@@ -279,27 +355,22 @@ bool vmstate_load_vmsd(QEMUFile *f, const VMStateDescription *vmsd,
355 }
356
357 for (i = 0; i < n_elems; i++) {
282 - /* If we will process the load of field? */
283 - bool load_field = true;
284 - bool ok = true;
285 - void *curr_elem = first_elem + size * i;
358 + void *curr_elem;
359 + bool ok;
360
287 - if (field->flags & VMS_ARRAY_OF_POINTER) {
288 - curr_elem = *(void **)curr_elem;
289 - if (!curr_elem) {
290 - /* Read the marker instead of VMSD itself */
291 - if (!vmstate_ptr_marker_load(f, &load_field, errp)) {
292 - trace_vmstate_load_field_error(field->name,
293 - -EINVAL);
294 - return false;
295 - }
296 - }
361 + ok = vmstate_load_next(f, field, first_elem, &curr_elem,
362 + size, i, errp);
363 + if (!ok) {
364 + return false;
365 }
366
299 - if (load_field) {
300 - ok = vmstate_load_field(f, curr_elem, size, field, errp);
367 + if (!curr_elem) {
368 + /* Implies a skip */
369 + continue;
370 }
371
372 + ok = vmstate_load_field(f, curr_elem, size, field, errp);
373 +
374 if (ok) {
375 int ret = qemu_file_get_error(f);
376 if (ret < 0) {
@@ -397,6 +468,16 @@ static bool vmsd_can_compress(const VMStateField *field)
468 return false;
469 }
470
471 + if (field->flags & VMS_ARRAY_OF_POINTER_AUTO_ALLOC) {
472 + /*
473 + * This may involve two VMSD fields to be saved, one for the
474 + * marker to show if the pointer is NULL, followed by the real
475 + * vmstate object. To make it simple at least for now, skip
476 + * compression for this one.
477 + */
478 + return false;
479 + }
480 +
481 if (field->flags & VMS_STRUCT) {
482 const VMStateField *sfield = field->vmsd->fields;
483 while (sfield->name) {
@@ -583,6 +664,12 @@ static bool vmstate_save_vmsd_v(QEMUFile *f, const VMStateDescription *vmsd,
664 int size = vmstate_size(opaque, field);
665 JSONWriter *vmdesc_loop = vmdesc;
666 bool is_prev_null = false;
667 + /*
668 + * When this is enabled, it means we will always push a ptr
669 + * marker first for each element saying if it's populated.
670 + */
671 + bool use_dynamic_array =
672 + field->flags & VMS_ARRAY_OF_POINTER_AUTO_ALLOC;
673
674 trace_vmstate_save_state_loop(vmsd->name, field->name, n_elems);
675 if (field->flags & VMS_POINTER) {
@@ -603,14 +690,9 @@ static bool vmstate_save_vmsd_v(QEMUFile *f, const VMStateDescription *vmsd,
690 }
691
692 is_null = !curr_elem && size;
606 - use_marker_field = is_null;
693 + use_marker_field = use_dynamic_array || is_null;
694
695 if (use_marker_field) {
609 - /*
610 - * If null pointer found (which should only happen in
611 - * an array of pointers), use null placeholder and do
612 - * not follow.
613 - */
696 inner_field = vmsd_create_ptr_marker_field(field);
697 } else {
698 inner_field = field;
@@ -657,6 +739,25 @@ static bool vmstate_save_vmsd_v(QEMUFile *f, const VMStateDescription *vmsd,
739 goto out;
740 }
741
742 + /*
743 + * If we're using dynamic array and the element is
744 + * populated, save the real object right after the marker.
745 + */
746 + if (use_dynamic_array && curr_elem) {
747 + /*
748 + * NOTE: do not use vmstate_size() here because we want
749 + * to save the real VMSD object now.
750 + */
751 + ok = vmstate_save_field_with_vmdesc(f, curr_elem,
752 + field->size, vmsd,
753 + field, vmdesc_loop,
754 + i, max_elems, errp);
755 +
756 + if (!ok) {
757 + goto out;
758 + }
759 + }
760 +
761 /* Compressed arrays only care about the first element */
762 if (vmdesc_loop && max_elems > 1) {
763 vmdesc_loop = NULL;