Bug 1519454 - Use a macro to declare interners and hook up memory reporters. r=emilio
authorBobby Holley <bobbyholley@gmail.com>
Fri, 11 Jan 2019 11:19:59 -0800
changeset 513633 33ca54452d38c1a597ee9178acd578304847aded
parent 513632 388b2d191167bbc0dbb12712d0b0454f145afeed
child 513634 f033eb7f5cee6276034ccba749c67b27382a4be8
push id1953
push userffxbld-merge
push dateMon, 11 Mar 2019 12:10:20 +0000
treeherdermozilla-release@9c35dcbaa899 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersemilio
bugs1519454
milestone66.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1519454 - Use a macro to declare interners and hook up memory reporters. r=emilio Differential Revision: https://phabricator.services.mozilla.com/D16356
gfx/thebes/gfxPlatform.cpp
gfx/webrender_bindings/webrender_ffi.h
gfx/webrender_bindings/webrender_ffi_generated.h
gfx/wr/webrender/src/intern.rs
gfx/wr/webrender/src/lib.rs
gfx/wr/webrender/src/render_backend.rs
gfx/wr/webrender/src/scene_builder.rs
gfx/wr/webrender_api/src/api.rs
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -675,16 +675,27 @@ struct WebRenderMemoryReporterHelper {
 static void FinishAsyncMemoryReport() {
   nsCOMPtr<nsIMemoryReporterManager> imgr =
       do_GetService("@mozilla.org/memory-reporter-manager;1");
   if (imgr) {
     imgr->EndReport();
   }
 }
 
+// clang-format off
+// (For some reason, clang-format gets the second macro right, but totally mangles the first).
+#define REPORT_INTERNER(id)                      \
+  helper.Report(aReport.interning.id##_interner, \
+                "interning/" #id "/interners");
+// clang-format on
+
+#define REPORT_DATA_STORE(id)                      \
+  helper.Report(aReport.interning.id##_data_store, \
+                "interning/" #id "/data-stores");
+
 NS_IMPL_ISUPPORTS(WebRenderMemoryReporter, nsIMemoryReporter)
 
 NS_IMETHODIMP
 WebRenderMemoryReporter::CollectReports(nsIHandleReportCallback* aHandleReport,
                                         nsISupports* aData, bool aAnonymize) {
   MOZ_ASSERT(XRE_IsParentProcess());
   MOZ_ASSERT(NS_IsMainThread());
   layers::CompositorManagerChild* manager =
@@ -693,29 +704,29 @@ WebRenderMemoryReporter::CollectReports(
     FinishAsyncMemoryReport();
     return NS_OK;
   }
 
   WebRenderMemoryReporterHelper helper(aHandleReport, aData);
   manager->SendReportMemory(
       [=](wr::MemoryReport aReport) {
         // CPU Memory.
-        helper.Report(aReport.primitive_stores, "primitive-stores");
         helper.Report(aReport.clip_stores, "clip-stores");
         helper.Report(aReport.gpu_cache_metadata, "gpu-cache/metadata");
         helper.Report(aReport.gpu_cache_cpu_mirror, "gpu-cache/cpu-mirror");
         helper.Report(aReport.render_tasks, "render-tasks");
         helper.Report(aReport.hit_testers, "hit-testers");
         helper.Report(aReport.fonts, "resource-cache/fonts");
         helper.Report(aReport.images, "resource-cache/images");
         helper.Report(aReport.rasterized_blobs,
                       "resource-cache/rasterized-blobs");
         helper.Report(aReport.shader_cache, "shader-cache");
-        helper.Report(aReport.data_stores, "interning/data-stores");
-        helper.Report(aReport.interners, "interning/interners");
+
+        WEBRENDER_FOR_EACH_INTERNER(REPORT_INTERNER);
+        WEBRENDER_FOR_EACH_INTERNER(REPORT_DATA_STORE);
 
         // GPU Memory.
         helper.ReportTexture(aReport.gpu_cache_textures, "gpu-cache");
         helper.ReportTexture(aReport.vertex_data_textures, "vertex-data");
         helper.ReportTexture(aReport.render_target_textures, "render-targets");
         helper.ReportTexture(aReport.texture_cache_textures, "texture-cache");
         helper.ReportTexture(aReport.depth_target_textures, "depth-targets");
         helper.ReportTexture(aReport.swap_chain, "swap-chains");
@@ -724,16 +735,19 @@ WebRenderMemoryReporter::CollectReports(
       },
       [](mozilla::ipc::ResponseRejectReason&& aReason) {
         FinishAsyncMemoryReport();
       });
 
   return NS_OK;
 }
 
+#undef REPORT_INTERNER
+#undef REPORT_DATA_STORE
+
 static const char* const WR_ROLLOUT_PREF = "gfx.webrender.all.qualified";
 static const char* const WR_ROLLOUT_PREF_DEFAULT =
     "gfx.webrender.all.qualified.default";
 static const char* const WR_ROLLOUT_PREF_OVERRIDE =
     "gfx.webrender.all.qualified.gfxPref-default-override";
 static const char* const WR_ROLLOUT_HW_QUALIFIED_OVERRIDE =
     "gfx.webrender.all.qualified.hardware-override";
 static const char* const PROFILE_BEFORE_CHANGE_TOPIC = "profile-before-change";
--- a/gfx/webrender_bindings/webrender_ffi.h
+++ b/gfx/webrender_bindings/webrender_ffi.h
@@ -29,20 +29,46 @@ void gecko_printf_stderr_output(const ch
 void* get_proc_address_from_glcontext(void* glcontext_ptr,
                                       const char* procname);
 void gecko_profiler_register_thread(const char* threadname);
 void gecko_profiler_unregister_thread();
 
 void gecko_profiler_start_marker(const char* name);
 void gecko_profiler_end_marker(const char* name);
 
+// IMPORTANT: Keep this synchronized with enumerate_interners in
+// gfx/wr/webrender_api
+#define WEBRENDER_FOR_EACH_INTERNER(macro) \
+  macro(clip);                             \
+  macro(prim);                             \
+  macro(normal_border);                    \
+  macro(image_border);                     \
+  macro(image);                            \
+  macro(yuv_image);                        \
+  macro(line_decoration);                  \
+  macro(linear_grad);                      \
+  macro(radial_grad);                      \
+  macro(picture);                          \
+  macro(text_run);
+
 // Prelude of types necessary before including webrender_ffi_generated.h
 namespace mozilla {
 namespace wr {
 
+// Because this struct is macro-generated on the Rust side, cbindgen can't see
+// it for some reason. Work around that by re-declaring it here.
+#define DECLARE_MEMBERS(id) \
+  uintptr_t id##_interner;  \
+  uintptr_t id##_data_store;
+struct InterningMemoryReport {
+  WEBRENDER_FOR_EACH_INTERNER(DECLARE_MEMBERS)
+};
+
+#undef DECLARE_MEMBERS
+
 struct FontInstanceFlags {
   uint32_t bits;
 
   bool operator==(const FontInstanceFlags& aOther) const {
     return bits == aOther.bits;
   }
 
   FontInstanceFlags& operator=(uint32_t aBits) {
--- a/gfx/webrender_bindings/webrender_ffi_generated.h
+++ b/gfx/webrender_bindings/webrender_ffi_generated.h
@@ -506,55 +506,32 @@ struct WrPipelineInfo {
   bool operator==(const WrPipelineInfo& aOther) const {
     return epochs == aOther.epochs &&
            removed_pipelines == aOther.removed_pipelines;
   }
 };
 
 /// Collection of heap sizes, in bytes.
 struct MemoryReport {
-  uintptr_t primitive_stores;
   uintptr_t clip_stores;
   uintptr_t gpu_cache_metadata;
   uintptr_t gpu_cache_cpu_mirror;
   uintptr_t render_tasks;
   uintptr_t hit_testers;
   uintptr_t fonts;
   uintptr_t images;
   uintptr_t rasterized_blobs;
   uintptr_t shader_cache;
-  uintptr_t data_stores;
-  uintptr_t interners;
+  InterningMemoryReport interning;
   uintptr_t gpu_cache_textures;
   uintptr_t vertex_data_textures;
   uintptr_t render_target_textures;
   uintptr_t texture_cache_textures;
   uintptr_t depth_target_textures;
   uintptr_t swap_chain;
-
-  bool operator==(const MemoryReport& aOther) const {
-    return primitive_stores == aOther.primitive_stores &&
-           clip_stores == aOther.clip_stores &&
-           gpu_cache_metadata == aOther.gpu_cache_metadata &&
-           gpu_cache_cpu_mirror == aOther.gpu_cache_cpu_mirror &&
-           render_tasks == aOther.render_tasks &&
-           hit_testers == aOther.hit_testers &&
-           fonts == aOther.fonts &&
-           images == aOther.images &&
-           rasterized_blobs == aOther.rasterized_blobs &&
-           shader_cache == aOther.shader_cache &&
-           data_stores == aOther.data_stores &&
-           interners == aOther.interners &&
-           gpu_cache_textures == aOther.gpu_cache_textures &&
-           vertex_data_textures == aOther.vertex_data_textures &&
-           render_target_textures == aOther.render_target_textures &&
-           texture_cache_textures == aOther.texture_cache_textures &&
-           depth_target_textures == aOther.depth_target_textures &&
-           swap_chain == aOther.swap_chain;
-  }
 };
 
 template<typename T, typename U>
 struct TypedSize2D {
   T width;
   T height;
 
   bool operator==(const TypedSize2D& aOther) const {
--- a/gfx/wr/webrender/src/intern.rs
+++ b/gfx/wr/webrender/src/intern.rs
@@ -1,22 +1,20 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{LayoutPrimitiveInfo, LayoutRect};
-use api::VoidPtrToSizeFn;
 use internal_types::FastHashMap;
 use malloc_size_of::MallocSizeOf;
 use profiler::ResourceProfileCounter;
 use std::fmt::Debug;
 use std::hash::Hash;
 use std::marker::PhantomData;
 use std::{mem, ops, u64};
-use std::os::raw::c_void;
 use std::sync::atomic::{AtomicUsize, Ordering};
 use util::VecHelper;
 
 /*
 
  The interning module provides a generic data structure
  interning container. It is similar in concept to a
  traditional string interning container, but it is
@@ -193,21 +191,16 @@ where
             }
         }
 
         let per_item_size = mem::size_of::<S>() + mem::size_of::<T>();
         profile_counter.set(self.items.len(), per_item_size * self.items.len());
 
         debug_assert!(data_iter.next().is_none());
     }
-
-    /// Reports CPU heap usage.
-    pub fn malloc_size_of(&self, op: VoidPtrToSizeFn) -> usize {
-        unsafe { op(self.items.as_ptr() as *const c_void) }
-    }
 }
 
 /// Retrieve an item from the store via handle
 impl<S, T, M> ops::Index<Handle<M>> for DataStore<S, T, M>
 where
     S: MallocSizeOf,
     T: MallocSizeOf,
     M: Copy
@@ -399,27 +392,16 @@ where
             epoch: self.current_epoch,
         };
 
         // Begin the next epoch
         self.current_epoch = Epoch(self.current_epoch.0 + 1);
 
         updates
     }
-
-    /// Reports CPU heap usage.
-    pub fn malloc_size_of(&self, op: VoidPtrToSizeFn, eop: VoidPtrToSizeFn) -> usize {
-        let mut bytes = 0;
-        unsafe {
-            bytes += op(self.local_data.as_ptr() as *const c_void);
-            bytes += self.map.values().next()
-                .map_or(0, |v| eop(v as *const _ as *const c_void));
-        }
-        bytes
-    }
 }
 
 /// Retrieve the local data for an item from the interner via handle
 impl<S, D, M> ops::Index<Handle<M>> for Interner<S, D, M>
 where
     S: Eq + Clone + Hash + Debug + MallocSizeOf,
     D: MallocSizeOf,
     M: Copy + Debug + MallocSizeOf
--- a/gfx/wr/webrender/src/lib.rs
+++ b/gfx/wr/webrender/src/lib.rs
@@ -193,16 +193,17 @@ extern crate ws;
 extern crate image as image_loader;
 #[cfg(feature = "debugger")]
 extern crate base64;
 #[cfg(all(feature = "capture", feature = "png"))]
 extern crate png;
 #[cfg(test)]
 extern crate rand;
 
+#[macro_use]
 pub extern crate webrender_api;
 extern crate webrender_build;
 
 #[doc(hidden)]
 pub use device::{build_shader_strings, ReadPixelsFormat, UploadMethod, VertexUsageHint};
 pub use device::{ProgramBinary, ProgramCache, ProgramCacheObserver};
 pub use device::Device;
 pub use frame_builder::ChasePrimitive;
--- a/gfx/wr/webrender/src/render_backend.rs
+++ b/gfx/wr/webrender/src/render_backend.rs
@@ -9,17 +9,17 @@
 //! how these two pieces interact.
 
 use api::{ApiMsg, BuiltDisplayList, ClearCache, DebugCommand, DebugFlags};
 #[cfg(feature = "debugger")]
 use api::{BuiltDisplayListIter, SpecificDisplayItem};
 use api::{DevicePixelScale, DeviceIntPoint, DeviceIntRect, DeviceIntSize};
 use api::{DocumentId, DocumentLayer, ExternalScrollId, FrameMsg, HitTestFlags, HitTestResult};
 use api::{IdNamespace, LayoutPoint, PipelineId, RenderNotifier, SceneMsg, ScrollClamping};
-use api::{MemoryReport, VoidPtrToSizeFn};
+use api::{MemoryReport};
 use api::{ScrollLocation, ScrollNodeState, TransactionMsg, ResourceUpdate, BlobImageKey};
 use api::{NotificationRequest, Checkpoint};
 use api::channel::{MsgReceiver, MsgSender, Payload};
 #[cfg(feature = "capture")]
 use api::CaptureBits;
 #[cfg(feature = "replay")]
 use api::CapturedDocument;
 use clip::ClipDataStore;
@@ -209,40 +209,42 @@ impl FrameStamp {
     /// An invalid sentinel FrameStamp.
     pub const INVALID: FrameStamp = FrameStamp {
         id: FrameId(0),
         time: UNIX_EPOCH,
         document_id: DocumentId::INVALID,
     };
 }
 
-// A collection of resources that are shared by clips, primitives
-// between display lists.
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-#[derive(Default)]
-pub struct FrameResources {
-    /// The store of currently active / available clip nodes. This is kept
-    /// in sync with the clip interner in the scene builder for each document.
-    pub clip_data_store: ClipDataStore,
+macro_rules! declare_frame_resources {
+    ( $( { $x: ident, $y: ty, $datastore_ident: ident, $datastore_type: ty } )+ ) => {
+        /// A collection of resources that are shared by clips, primitives
+        /// between display lists.
+        #[cfg_attr(feature = "capture", derive(Serialize))]
+        #[cfg_attr(feature = "replay", derive(Deserialize))]
+        #[derive(Default)]
+        pub struct FrameResources {
+            $(
+                pub $datastore_ident: $datastore_type,
+            )+
+        }
 
-    /// Currently active / available primitives. Kept in sync with the
-    /// primitive interner in the scene builder, per document.
-    pub prim_data_store: PrimitiveDataStore,
-    pub image_data_store: ImageDataStore,
-    pub image_border_data_store: ImageBorderDataStore,
-    pub line_decoration_data_store: LineDecorationDataStore,
-    pub linear_grad_data_store: LinearGradientDataStore,
-    pub normal_border_data_store: NormalBorderDataStore,
-    pub picture_data_store: PictureDataStore,
-    pub radial_grad_data_store: RadialGradientDataStore,
-    pub text_run_data_store: TextRunDataStore,
-    pub yuv_image_data_store: YuvImageDataStore,
+        impl FrameResources {
+            /// Reports CPU heap usage.
+            fn report_memory(&self, ops: &mut MallocSizeOfOps, r: &mut MemoryReport) {
+                $(
+                    r.interning.$datastore_ident += self.$datastore_ident.size_of(ops);
+                )+
+            }
+        }
+    }
 }
 
+enumerate_interners!(declare_frame_resources);
+
 impl FrameResources {
     pub fn as_common_data(
         &self,
         prim_inst: &PrimitiveInstance
     ) -> &PrimTemplateCommonData {
         match prim_inst.kind {
             PrimitiveInstanceKind::Rectangle { data_handle, .. } |
             PrimitiveInstanceKind::Clear { data_handle, .. } => {
@@ -282,25 +284,16 @@ impl FrameResources {
                 &prim_data.common
             }
             PrimitiveInstanceKind::YuvImage { data_handle, .. } => {
                 let prim_data = &self.yuv_image_data_store[data_handle];
                 &prim_data.common
             }
         }
     }
-
-    /// Reports CPU heap usage.
-    fn report_memory(&self, op: VoidPtrToSizeFn, r: &mut MemoryReport) {
-        r.data_stores += self.clip_data_store.malloc_size_of(op);
-        r.data_stores += self.prim_data_store.malloc_size_of(op);
-        r.data_stores += self.linear_grad_data_store.malloc_size_of(op);
-        r.data_stores += self.radial_grad_data_store.malloc_size_of(op);
-        r.data_stores += self.text_run_data_store.malloc_size_of(op);
-    }
 }
 
 struct Document {
     // The latest built scene, usable to build frames.
     // received from the scene builder thread.
     scene: Scene,
 
     // Temporary list of removed pipelines received from the scene builder
@@ -1559,17 +1552,17 @@ impl RenderBackend {
         let op = ops.size_of_op;
         report.gpu_cache_metadata = self.gpu_cache.size_of(ops);
         for (_id, doc) in &self.documents {
             if let Some(ref fb) = doc.frame_builder {
                 report.clip_stores += fb.clip_store.size_of(ops);
             }
             report.hit_testers += doc.hit_tester.size_of(ops);
 
-            doc.resources.report_memory(op, &mut report)
+            doc.resources.report_memory(ops, &mut report)
         }
 
         report += self.resource_cache.report_memory(op);
 
         // Send a message to report memory on the scene-builder thread, which
         // will add its report to this one and send the result back to the original
         // thread waiting on the request.
         self.scene_tx.send(SceneBuilderRequest::ReportMemory(report, tx)).unwrap();
--- a/gfx/wr/webrender/src/scene_builder.rs
+++ b/gfx/wr/webrender/src/scene_builder.rs
@@ -189,55 +189,49 @@ pub enum SceneBuilderResult {
 // Message from render backend to scene builder to indicate the
 // scene swap was completed. We need a separate channel for this
 // so that they don't get mixed with SceneBuilderRequest messages.
 pub enum SceneSwapResult {
     Complete(Sender<()>),
     Aborted,
 }
 
-// This struct contains all items that can be shared between
-// display lists. We want to intern and share the same clips,
-// primitives and other things between display lists so that:
-// - GPU cache handles remain valid, reducing GPU cache updates.
-// - Comparison of primitives and pictures between two
-//   display lists is (a) fast (b) done during scene building.
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-#[derive(Default)]
-pub struct DocumentResources {
-    pub clip_interner: ClipDataInterner,
-    pub prim_interner: PrimitiveDataInterner,
-    pub image_interner: ImageDataInterner,
-    pub image_border_interner: ImageBorderDataInterner,
-    pub line_decoration_interner: LineDecorationDataInterner,
-    pub linear_grad_interner: LinearGradientDataInterner,
-    pub normal_border_interner: NormalBorderDataInterner,
-    pub picture_interner: PictureDataInterner,
-    pub radial_grad_interner: RadialGradientDataInterner,
-    pub text_run_interner: TextRunDataInterner,
-    pub yuv_image_interner: YuvImageDataInterner,
+macro_rules! declare_document_resources {
+    ( $( { $interner_ident: ident, $interner_type: ty, $x: ident, $y: ty } )+ ) => {
+        /// This struct contains all items that can be shared between
+        /// display lists. We want to intern and share the same clips,
+        /// primitives and other things between display lists so that:
+        /// - GPU cache handles remain valid, reducing GPU cache updates.
+        /// - Comparison of primitives and pictures between two
+        ///   display lists is (a) fast (b) done during scene building.
+        #[cfg_attr(feature = "capture", derive(Serialize))]
+        #[cfg_attr(feature = "replay", derive(Deserialize))]
+        #[derive(Default)]
+        pub struct DocumentResources {
+            $(
+                pub $interner_ident: $interner_type,
+            )+
+        }
+
+        impl DocumentResources {
+            /// Reports CPU heap memory used by the interners.
+            fn report_memory(
+                &self,
+                ops: &mut MallocSizeOfOps,
+                r: &mut MemoryReport,
+            ) {
+                $(
+                    r.interning.$interner_ident += self.$interner_ident.size_of(ops);
+                )+
+            }
+        }
+    }
 }
 
-impl DocumentResources {
-    /// Reports CPU heap memory used by the interners.
-    fn report_memory(
-        &self,
-        ops: &mut MallocSizeOfOps,
-        r: &mut MemoryReport,
-    ) {
-        let op = ops.size_of_op;
-        let eop = ops.enclosing_size_of_op.unwrap();
-        r.interners += self.clip_interner.malloc_size_of(op, eop);
-        r.interners += self.prim_interner.malloc_size_of(op, eop);
-        r.interners += self.linear_grad_interner.malloc_size_of(op, eop);
-        r.interners += self.radial_grad_interner.malloc_size_of(op, eop);
-        r.interners += self.text_run_interner.malloc_size_of(op, eop);
-    }
-}
+enumerate_interners!(declare_document_resources);
 
 // Access to `DocumentResources` interners by `Internable`
 pub trait InternerMut<I: Internable>
 {
     fn interner_mut(&mut self) -> &mut Interner<I::Source, I::InternData, I::Marker>;
 }
 
 macro_rules! impl_internet_mut {
--- a/gfx/wr/webrender_api/src/api.rs
+++ b/gfx/wr/webrender_api/src/api.rs
@@ -811,61 +811,109 @@ pub type PipelineSourceId = u32;
 pub struct PipelineId(pub PipelineSourceId, pub u32);
 
 impl PipelineId {
     pub fn dummy() -> Self {
         PipelineId(0, 0)
     }
 }
 
+/// Meta-macro to enumerate the various interner identifiers and types.
+///
+/// IMPORTANT: Keep this synchronized with the list in mozilla-central located at
+/// gfx/webrender_bindings/webrender_ffi.h
+///
+/// Note that this could be a lot less verbose if concat_idents! were stable. :-(
+#[macro_export]
+macro_rules! enumerate_interners {
+    ($macro_name: ident) => {
+        $macro_name! {
+            { clip_interner, ClipDataInterner, clip_data_store, ClipDataStore }
+            { prim_interner, PrimitiveDataInterner, prim_data_store, PrimitiveDataStore }
+            { normal_border_interner, NormalBorderDataInterner, normal_border_data_store, NormalBorderDataStore }
+            { image_border_interner, ImageBorderDataInterner, image_border_data_store, ImageBorderDataStore }
+            { image_interner, ImageDataInterner, image_data_store, ImageDataStore }
+            { yuv_image_interner, YuvImageDataInterner, yuv_image_data_store, YuvImageDataStore }
+            { line_decoration_interner, LineDecorationDataInterner,
+              line_decoration_data_store, LineDecorationDataStore }
+            { linear_grad_interner, LinearGradientDataInterner, linear_grad_data_store, LinearGradientDataStore }
+            { radial_grad_interner, RadialGradientDataInterner, radial_grad_data_store, RadialGradientDataStore }
+            { picture_interner, PictureDataInterner, picture_data_store, PictureDataStore }
+            { text_run_interner, TextRunDataInterner, text_run_data_store, TextRunDataStore }
+        }
+    }
+}
+
+macro_rules! declare_interning_memory_report {
+    ( $( { $interner_ident: ident, $x: ty, $datastore_ident: ident, $y: ty } )+ ) => {
+        #[repr(C)]
+        #[derive(Clone, Debug, Default, Deserialize, Serialize)]
+        pub struct InterningMemoryReport {
+            $(
+                pub $interner_ident: usize,
+                pub $datastore_ident: usize,
+            )+
+        }
+
+        impl ::std::ops::AddAssign for InterningMemoryReport {
+            fn add_assign(&mut self, other: InterningMemoryReport) {
+                $(
+                    self.$interner_ident += other.$interner_ident;
+                    self.$datastore_ident += other.$datastore_ident;
+                )+
+            }
+        }
+    }
+}
+
+enumerate_interners!(declare_interning_memory_report);
+
 /// Collection of heap sizes, in bytes.
+/// cbindgen:derive-eq=false
 #[repr(C)]
 #[derive(Clone, Debug, Default, Deserialize, Serialize)]
 pub struct MemoryReport {
     //
     // CPU Memory.
     //
-    pub primitive_stores: usize,
     pub clip_stores: usize,
     pub gpu_cache_metadata: usize,
     pub gpu_cache_cpu_mirror: usize,
     pub render_tasks: usize,
     pub hit_testers: usize,
     pub fonts: usize,
     pub images: usize,
     pub rasterized_blobs: usize,
     pub shader_cache: usize,
-    pub data_stores: usize,
-    pub interners: usize,
+    pub interning: InterningMemoryReport,
 
     //
     // GPU memory.
     //
     pub gpu_cache_textures: usize,
     pub vertex_data_textures: usize,
     pub render_target_textures: usize,
     pub texture_cache_textures: usize,
     pub depth_target_textures: usize,
     pub swap_chain: usize,
 }
 
 impl ::std::ops::AddAssign for MemoryReport {
     fn add_assign(&mut self, other: MemoryReport) {
-        self.primitive_stores += other.primitive_stores;
         self.clip_stores += other.clip_stores;
         self.gpu_cache_metadata += other.gpu_cache_metadata;
         self.gpu_cache_cpu_mirror += other.gpu_cache_cpu_mirror;
         self.render_tasks += other.render_tasks;
         self.hit_testers += other.hit_testers;
         self.fonts += other.fonts;
         self.images += other.images;
         self.rasterized_blobs += other.rasterized_blobs;
         self.shader_cache += other.shader_cache;
-        self.data_stores += other.data_stores;
-        self.interners += other.interners;
+        self.interning += other.interning;
+
         self.gpu_cache_textures += other.gpu_cache_textures;
         self.vertex_data_textures += other.vertex_data_textures;
         self.render_target_textures += other.render_target_textures;
         self.texture_cache_textures += other.texture_cache_textures;
         self.depth_target_textures += other.depth_target_textures;
         self.swap_chain += other.swap_chain;
     }
 }