@samitouri / QOSamiQemu / commits / b1b2cc5889

system/ram-discard-manager: implement replay via is_populated iteration

Replace the source-level replay wrappers with a new replay_by_populated_state() helper that iterates the section at min-granularity, calls is_populated() for each chunk, and aggregates consecutive chunks of the same state before invoking the callback. This moves the iteration logic from individual sources into the manager, preparing for multi-source aggregation where the manager must combine state from multiple sources anyway. The replay_populated/replay_discarded vtable entries in RamDiscardSourceClass are no longer called but remain in the interface for now; they will be removed in follow-up commits along with the now-dead source implementations. Reviewed-by: Peter Xu <peterx@redhat.com> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com> Acked-by: David Hildenbrand <david@kernel.org> Link: https://lore.kernel.org/r/20260604-rdm5-v5-4-5768e6a0943d@redhat.com Signed-off-by: Peter Xu <peterx@redhat.com>

Marc-André Lureau committed Jun 4, 2026 at 17:43 UTC b1b2cc588957811d2b621dbd5b6d238d34127546
1 file changed +61 -24
system/ram-discard-manager.c
+61 -24
@@ -27,26 +27,65 @@ static bool ram_discard_source_is_populated(const RamDiscardSource *rds,
27 return rdsc->is_populated(rds, section);
28 }
29
30 -static int ram_discard_source_replay_populated(const RamDiscardSource *rds,
31 - const MemoryRegionSection *section,
32 - ReplayRamDiscardState replay_fn,
33 - void *opaque)
30 +/*
31 + * Iterate the section at source granularity, aggregating consecutive chunks
32 + * with matching populated state, and call replay_fn for each run.
33 + */
34 +static int replay_by_populated_state(const RamDiscardManager *rdm,
35 + const MemoryRegionSection *section,
36 + bool replay_populated,
37 + ReplayRamDiscardState replay_fn,
38 + void *opaque)
39 {
35 - RamDiscardSourceClass *rdsc = RAM_DISCARD_SOURCE_GET_CLASS(rds);
40 + uint64_t granularity, offset, size, end, pos, run_start = 0;
41 + bool in_run = false;
42 + int ret = 0;
43
37 - g_assert(rdsc->replay_populated);
38 - return rdsc->replay_populated(rds, section, replay_fn, opaque);
39 -}
44 + granularity = ram_discard_source_get_min_granularity(rdm->rds, rdm->mr);
45 + offset = section->offset_within_region;
46 + size = int128_get64(section->size);
47 + end = offset + size;
48 +
49 + /* Align iteration to granularity boundaries */
50 + pos = QEMU_ALIGN_DOWN(offset, granularity);
51 +
52 + for (; pos < end; pos += granularity) {
53 + MemoryRegionSection chunk = {
54 + .mr = section->mr,
55 + .offset_within_region = pos,
56 + .size = int128_make64(granularity),
57 + };
58 + bool populated = ram_discard_source_is_populated(rdm->rds, &chunk);
59 +
60 + if (populated == replay_populated) {
61 + if (!in_run) {
62 + run_start = pos;
63 + in_run = true;
64 + }
65 + } else if (in_run) {
66 + MemoryRegionSection tmp = *section;
67 +
68 + if (memory_region_section_intersect_range(&tmp, run_start,
69 + pos - run_start)) {
70 + ret = replay_fn(&tmp, opaque);
71 + if (ret) {
72 + return ret;
73 + }
74 + }
75 + in_run = false;
76 + }
77 + }
78
41 -static int ram_discard_source_replay_discarded(const RamDiscardSource *rds,
42 - const MemoryRegionSection *section,
43 - ReplayRamDiscardState replay_fn,
44 - void *opaque)
45 -{
46 - RamDiscardSourceClass *rdsc = RAM_DISCARD_SOURCE_GET_CLASS(rds);
79 + if (in_run) {
80 + MemoryRegionSection tmp = *section;
81
48 - g_assert(rdsc->replay_discarded);
49 - return rdsc->replay_discarded(rds, section, replay_fn, opaque);
82 + if (memory_region_section_intersect_range(&tmp, run_start,
83 + pos - run_start)) {
84 + ret = replay_fn(&tmp, opaque);
85 + }
86 + }
87 +
88 + return ret;
89 }
90
91 RamDiscardManager *ram_discard_manager_new(MemoryRegion *mr,
@@ -78,8 +117,7 @@ int ram_discard_manager_replay_populated(const RamDiscardManager *rdm,
117 ReplayRamDiscardState replay_fn,
118 void *opaque)
119 {
81 - return ram_discard_source_replay_populated(rdm->rds, section,
82 - replay_fn, opaque);
120 + return replay_by_populated_state(rdm, section, true, replay_fn, opaque);
121 }
122
123 int ram_discard_manager_replay_discarded(const RamDiscardManager *rdm,
@@ -87,8 +125,7 @@ int ram_discard_manager_replay_discarded(const RamDiscardManager *rdm,
125 ReplayRamDiscardState replay_fn,
126 void *opaque)
127 {
90 - return ram_discard_source_replay_discarded(rdm->rds, section,
91 - replay_fn, opaque);
128 + return replay_by_populated_state(rdm, section, false, replay_fn, opaque);
129 }
130
131 static void ram_discard_manager_initfn(Object *obj)
@@ -182,8 +219,8 @@ void ram_discard_manager_register_listener(RamDiscardManager *rdm,
219 rdl->section = memory_region_section_new_copy(section);
220 QLIST_INSERT_HEAD(&rdm->rdl_list, rdl, next);
221
185 - ret = ram_discard_source_replay_populated(rdm->rds, rdl->section,
186 - rdm_populate_cb, rdl);
222 + ret = ram_discard_manager_replay_populated(rdm, rdl->section,
223 + rdm_populate_cb, rdl);
224 if (ret) {
225 error_report("%s: Replaying populated ranges failed: %s", __func__,
226 strerror(-ret));
@@ -208,8 +245,8 @@ int ram_discard_manager_replay_populated_to_listeners(RamDiscardManager *rdm)
245 int ret = 0;
246
247 QLIST_FOREACH(rdl, &rdm->rdl_list, next) {
211 - ret = ram_discard_source_replay_populated(rdm->rds, rdl->section,
212 - rdm_populate_cb, rdl);
248 + ret = ram_discard_manager_replay_populated(rdm, rdl->section,
249 + rdm_populate_cb, rdl);
250 if (ret) {
251 break;
252 }