@samitouri / QOSamiQemu / commits / e86c004fbd

vmstate: Allow vmstate_info_nullptr to emit non-NULL markers

We used to have one vmstate called "nullptr" which is only used to generate one-byte hint to say one pointer is NULL. Let's extend its use so that it will generate another byte to say the pointer is non-NULL. With that, the name of the info struct (or functions) do not apply anymore. Update correspondingly. Update analyze-migration.py to work with the new layout. No functional change intended yet. Reviewed-by: Fabiano Rosas <farosas@suse.de> Reviewed-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-8-peterx@redhat.com Signed-off-by: Fabiano Rosas <farosas@suse.de>

Peter Xu committed Apr 1, 2026 at 16:28 UTC e86c004fbda963191298905d7e02f8efdb0d05cc
4 files changed +48 -42
include/migration/vmstate.h
+7 -2
@@ -282,9 +282,14 @@ extern const VMStateInfo vmstate_info_uint32;
282 extern const VMStateInfo vmstate_info_uint64;
283 extern const VMStateInfo vmstate_info_fd;
284
285 -/** Put this in the stream when migrating a null pointer.*/
285 +/*
286 + * Put this in the stream when migrating a pointer to reflect either a NULL
287 + * or valid pointer.
288 + */
289 #define VMS_MARKER_PTR_NULL (0x30U) /* '0' */
287 -extern const VMStateInfo vmstate_info_nullptr;
290 +#define VMS_MARKER_PTR_VALID (0x31U) /* '1' */
291 +
292 +extern const VMStateInfo vmstate_info_ptr_marker;
293
294 extern const VMStateInfo vmstate_info_cpudouble;
295
migration/vmstate-types.c
+16 -18
@@ -359,36 +359,34 @@ const VMStateInfo vmstate_info_fd = {
359 .save = save_fd,
360 };
361
362 -static bool load_nullptr(QEMUFile *f, void *pv, size_t size,
363 - const VMStateField *field, Error **errp)
362 +static bool load_ptr_marker(QEMUFile *f, void *pv, size_t size,
363 + const VMStateField *field, Error **errp)
364
365 {
366 - if (qemu_get_byte(f) == VMS_MARKER_PTR_NULL) {
366 + int byte = qemu_get_byte(f);
367 +
368 + if (byte == VMS_MARKER_PTR_NULL || byte == VMS_MARKER_PTR_VALID) {
369 + /* TODO: process PTR_VALID case */
370 return true;
371 }
372
370 - error_setg(errp, "vmstate: load_nullptr expected VMS_NULLPTR_MARKER");
373 + error_setg(errp, "%s: unexpected ptr marker: %d", __func__, byte);
374 return false;
375 }
376
374 -static bool save_nullptr(QEMUFile *f, void *pv, size_t size,
375 - const VMStateField *field, JSONWriter *vmdesc,
376 - Error **errp)
377 +static bool save_ptr_marker(QEMUFile *f, void *pv, size_t size,
378 + const VMStateField *field, JSONWriter *vmdesc,
379 + Error **errp)
380
381 {
379 - if (pv == NULL) {
380 - qemu_put_byte(f, VMS_MARKER_PTR_NULL);
381 - return true;
382 - }
383 -
384 - error_setg(errp, "vmstate: save_nullptr must be called with pv == NULL");
385 - return false;
382 + qemu_put_byte(f, pv ? VMS_MARKER_PTR_VALID : VMS_MARKER_PTR_NULL);
383 + return true;
384 }
385
388 -const VMStateInfo vmstate_info_nullptr = {
389 - .name = "nullptr",
390 - .load = load_nullptr,
391 - .save = save_nullptr,
386 +const VMStateInfo vmstate_info_ptr_marker = {
387 + .name = "ptr-marker",
388 + .load = load_ptr_marker,
389 + .save = save_ptr_marker,
390 };
391
392 /* 64 bit unsigned int. See that the received value is the same than the one
migration/vmstate.c
+13 -12
@@ -55,12 +55,12 @@ vmstate_field_exists(const VMStateDescription *vmsd, const VMStateField *field,
55 }
56
57 /*
58 - * Create a fake nullptr field when there's a NULL pointer detected in the
58 + * Create a ptr marker field when there's a NULL pointer detected in the
59 * array of a VMS_ARRAY_OF_POINTER VMSD field. It's needed because we
60 * can't dereference the NULL pointer.
61 */
62 static const VMStateField *
63 -vmsd_create_fake_nullptr_field(const VMStateField *field)
63 +vmsd_create_ptr_marker_field(const VMStateField *field)
64 {
65 VMStateField *fake = g_new0(VMStateField, 1);
66
@@ -71,12 +71,12 @@ vmsd_create_fake_nullptr_field(const VMStateField *field)
71 fake->name = field->name;
72 fake->version_id = field->version_id;
73
74 - /* Do not need "field_exists" check as it always exists (which is null) */
74 + /* Do not need "field_exists" check as it always exists */
75 fake->field_exists = NULL;
76
77 - /* See vmstate_info_nullptr - use 1 byte to represent nullptr */
77 + /* See vmstate_info_ptr_marker - use 1 byte to represent ptr status */
78 fake->size = 1;
79 - fake->info = &vmstate_info_nullptr;
79 + fake->info = &vmstate_info_ptr_marker;
80 fake->flags = VMS_SINGLE;
81
82 /* All the rest fields shouldn't matter.. */
@@ -278,7 +278,7 @@ bool vmstate_load_vmsd(QEMUFile *f, const VMStateDescription *vmsd,
278 * an array of pointers), use null placeholder and do
279 * not follow.
280 */
281 - inner_field = vmsd_create_fake_nullptr_field(field);
281 + inner_field = vmsd_create_ptr_marker_field(field);
282 } else {
283 inner_field = field;
284 }
@@ -583,26 +583,27 @@ static bool vmstate_save_vmsd_v(QEMUFile *f, const VMStateDescription *vmsd,
583 for (i = 0; i < n_elems; i++) {
584 void *curr_elem = first_elem + size * i;
585 const VMStateField *inner_field;
586 - bool is_null;
586 /* maximum number of elements to compress in the JSON blob */
587 int max_elems = vmsd_can_compress(field) ? (n_elems - i) : 1;
588 + bool use_marker_field, is_null;
589
590 if (field->flags & VMS_ARRAY_OF_POINTER) {
591 assert(curr_elem);
592 curr_elem = *(void **)curr_elem;
593 }
594
595 - if (!curr_elem && size) {
595 + is_null = !curr_elem && size;
596 + use_marker_field = is_null;
597 +
598 + if (use_marker_field) {
599 /*
600 * If null pointer found (which should only happen in
601 * an array of pointers), use null placeholder and do
602 * not follow.
603 */
601 - inner_field = vmsd_create_fake_nullptr_field(field);
602 - is_null = true;
604 + inner_field = vmsd_create_ptr_marker_field(field);
605 } else {
606 inner_field = field;
605 - is_null = false;
607 }
608
609 /*
@@ -638,7 +639,7 @@ static bool vmstate_save_vmsd_v(QEMUFile *f, const VMStateDescription *vmsd,
639 i, max_elems, errp);
640
641 /* If we used a fake temp field.. free it now */
641 - if (is_null) {
642 + if (use_marker_field) {
643 g_clear_pointer((gpointer *)&inner_field, g_free);
644 }
645
scripts/analyze-migration.py
+12 -10
@@ -469,26 +469,26 @@ class VMSDFieldIntLE(VMSDFieldInt):
469 super(VMSDFieldIntLE, self).__init__(desc, file)
470 self.dtype = '<i%d' % self.size
471
472 -class VMSDFieldNull(VMSDFieldGeneric):
472 +class VMSDFieldPtrMarker(VMSDFieldGeneric):
473 NULL_PTR_MARKER = b'0'
474 + VALID_PTR_MARKER = b'1'
475
476 def __init__(self, desc, file):
476 - super(VMSDFieldNull, self).__init__(desc, file)
477 + super(VMSDFieldPtrMarker, self).__init__(desc, file)
478
479 def __repr__(self):
479 - # A NULL pointer is encoded in the stream as a '0' to
480 - # disambiguate from a mere 0x0 value and avoid consumers
481 - # trying to follow the NULL pointer. Displaying '0', 0x30 or
482 - # 0x0 when analyzing the JSON debug stream could become
480 + # A NULL / non-NULL pointer may be encoded in the stream as a
481 + # '0'/'1' to represent the status of the pointer. Displaying '0',
482 + # 0x30 or 0x0 when analyzing the JSON debug stream could become
483 # confusing, so use an explicit term instead.
484 - return "nullptr"
484 + return "null-ptr" if self.data == self.NULL_PTR_MARKER else "valid-ptr"
485
486 def __str__(self):
487 return self.__repr__()
488
489 def read(self):
490 - super(VMSDFieldNull, self).read()
491 - assert(self.data == self.NULL_PTR_MARKER)
490 + super(VMSDFieldPtrMarker, self).read()
491 + assert(self.data in [self.NULL_PTR_MARKER, self.VALID_PTR_MARKER])
492 return self.data
493
494 class VMSDFieldBool(VMSDFieldGeneric):
@@ -642,7 +642,9 @@ vmsd_field_readers = {
642 "bitmap" : VMSDFieldGeneric,
643 "struct" : VMSDFieldStruct,
644 "capability": VMSDFieldCap,
645 - "nullptr": VMSDFieldNull,
645 + # Keep the old nullptr for old binaries
646 + "nullptr": VMSDFieldPtrMarker,
647 + "ptr-marker": VMSDFieldPtrMarker,
648 "unknown" : VMSDFieldGeneric,
649 }
650