[gjs: 4/7] object: Split ObjectPrototype from ObjectInstance



commit 4413e30d9529c63ecda30fbc352d522ea93641d0
Author: Philip Chimento <philip chimento gmail com>
Date:   Tue Jun 19 22:47:00 2018 -0700

    object: Split ObjectPrototype from ObjectInstance
    
    This creates two separate classes, ObjectPrototype and ObjectInstance, so
    that we don't have to have a copy of prototype-only data in each
    ObjectInstance. This should save a lot of memory.
    
    There is one element common to both classes: a list of GClosures (for
    prototypes, these are vfuncs, and for instances, connected signals.)
    Since we can only create a JSClass with one private type, we use a common
    base class ObjectBase for that type. ObjectBase contains the list of
    closures and a means of distinguishing whether a particular ObjectBase
    pointer holds an ObjectInstance or ObjectPrototype. For this we use an
    ObjectPrototype pointer, which is null in ObjectPrototypes, and in
    ObjectInstances points to the associated prototype. (This scheme is
    similar to what we do in fundamental.cpp.)
    
    Both ObjectInstance and ObjectPrototype have an associated GObjectInfo
    and GType, but now these are stored only on the prototype.
    
    Note that we do not use RTTI and dynamic_cast<> because SpiderMonkey
    supports compiling both with and without RTTI. That's the reason for this
    unusual scheme with the ObjectPrototype pointer and the to_prototype() /
    to_instance() methods. dynamic_cast<> would certainly be cleaner and more
    readable, so that might be something to investigate in the future.
    
    This also allows moving the PropertyCache and FieldCache into
    ObjectPrototype, instead of keeping them as qdata on the GType.
    
    We now define separate memory counters for ObjectPrototype and
    ObjectInstance.
    
    On x86_64 with Clang, ObjectInstance was previously 128 bytes and is now
    112. ObjectPrototype was effectively 288 bytes (128 for ObjectInstance,
    plus 160 hidden bytes for the PropertyCache and FieldCache stored on the
    GType) and is now 208 bytes.
    
    Closes: #165

 gi/gobject.cpp |  19 +-
 gi/object.cpp  | 618 ++++++++++++++++++++++++---------------------------------
 gi/object.h    | 400 ++++++++++++++++++++++++-------------
 gi/private.cpp |  10 +-
 gjs/mem.cpp    |   9 +-
 gjs/mem.h      |   3 +-
 6 files changed, 550 insertions(+), 509 deletions(-)
---
diff --git a/gi/gobject.cpp b/gi/gobject.cpp
index ae36e5d0..65acdbdb 100644
--- a/gi/gobject.cpp
+++ b/gi/gobject.cpp
@@ -75,13 +75,13 @@ static void jsobj_set_gproperty(JSContext* cx, JS::HandleObject object,
 }
 
 static void gjs_object_base_init(void* klass) {
-    auto* priv = ObjectInstance::for_gtype(G_OBJECT_CLASS_TYPE(klass));
+    auto* priv = ObjectPrototype::for_gtype(G_OBJECT_CLASS_TYPE(klass));
     if (priv)
         priv->ref_closures();
 }
 
 static void gjs_object_base_finalize(void* klass) {
-    auto* priv = ObjectInstance::for_gtype(G_OBJECT_CLASS_TYPE(klass));
+    auto* priv = ObjectPrototype::for_gtype(G_OBJECT_CLASS_TYPE(klass));
     if (priv)
         priv->unref_closures();
 }
@@ -134,14 +134,14 @@ static GObject* gjs_object_constructor(
     if (!object)
         return nullptr;
 
-    auto* priv = ObjectInstance::for_js_nocheck(object);
+    auto* priv = ObjectBase::for_js_nocheck(object);
     /* Should have been set in init_impl() and pushed into object_init_list,
      * then popped from object_init_list in gjs_object_custom_init() */
     g_assert(priv);
     /* We only hold a toggle ref at this point, add back a ref that the
      * native code can own.
      */
-    return G_OBJECT(g_object_ref(priv->gobj()));
+    return G_OBJECT(g_object_ref(priv->to_instance()->gobj()));
 }
 
 static void gjs_object_set_gproperty(GObject* object, unsigned property_id,
@@ -181,7 +181,7 @@ static void gjs_object_class_init(void* class_pointer, void* user_data) {
 
     unsigned i = 0;
     for (GjsAutoParam& pspec : properties) {
-        g_param_spec_set_qdata(pspec, ObjectInstance::custom_property_quark(),
+        g_param_spec_set_qdata(pspec, ObjectBase::custom_property_quark(),
                                GINT_TO_POINTER(1));
         g_object_class_install_property(klass, ++i, pspec);
     }
@@ -194,10 +194,11 @@ static void gjs_object_custom_init(GTypeInstance* instance, void* klass) {
     JSContext *cx = current_context();
 
     JS::RootedObject object(cx, ObjectInstance::object_init_list.top());
-    auto* priv = ObjectInstance::for_js_nocheck(object);
-    g_assert(priv);  // Should have been set in init_impl()
+    auto* priv_base = ObjectBase::for_js_nocheck(object);
+    g_assert(priv_base);  // Should have been set in init_impl()
+    ObjectInstance* priv = priv_base->to_instance();
 
-    if (priv->gtype() != G_TYPE_FROM_INSTANCE(instance)) {
+    if (priv_base->gtype() != G_TYPE_FROM_INSTANCE(instance)) {
         /* This is not the most derived instance_init function,
            do nothing.
          */
@@ -234,7 +235,7 @@ static void gjs_interface_init(void* g_iface, void* iface_data) {
         return;
 
     for (GjsAutoParam& pspec : properties) {
-        g_param_spec_set_qdata(pspec, ObjectInstance::custom_property_quark(),
+        g_param_spec_set_qdata(pspec, ObjectBase::custom_property_quark(),
                                GINT_TO_POINTER(1));
         g_object_interface_install_property(g_iface, pspec);
     }
diff --git a/gi/object.cpp b/gi/object.cpp
index 4917dc9a..322c586c 100644
--- a/gi/object.cpp
+++ b/gi/object.cpp
@@ -54,17 +54,32 @@
 
 #include "js/GCHashTable.h"
 
+/* This is a trick to print out the sizes of the structs at compile time, in
+ * an error message. */
+// template <int s> struct Measure;
+// Measure<sizeof(ObjectInstance)> instance_size;
+// Measure<sizeof(ObjectPrototype)> prototype_size;
+
+#if defined(__x86_64__) && defined(__clang__)
+/* This isn't meant to be comprehensive, but should trip on at least one CI job
+ * if sizeof(ObjectInstance) is increased. */
+static_assert(sizeof(ObjectInstance) <= 112,
+              "Think very hard before increasing the size of ObjectInstance. "
+              "There can be tens of thousands of them alive in a typical "
+              "gnome-shell run.");
+#endif  // x86-64 clang
+
 std::stack<JS::PersistentRootedObject> ObjectInstance::object_init_list{};
 static bool weak_pointer_callback = false;
 ObjectInstance *ObjectInstance::wrapped_gobject_list = nullptr;
 JS::PersistentRootedSymbol ObjectInstance::hook_up_vfunc_root;
 
 extern struct JSClass gjs_object_instance_class;
-GJS_DEFINE_PRIV_FROM_JS(ObjectInstance, gjs_object_instance_class)
+GJS_DEFINE_PRIV_FROM_JS(ObjectBase, gjs_object_instance_class)
 
 // clang-format off
-G_DEFINE_QUARK(gjs::custom-type, ObjectInstance::custom_type)
-G_DEFINE_QUARK(gjs::custom-property, ObjectInstance::custom_property)
+G_DEFINE_QUARK(gjs::custom-type, ObjectBase::custom_type)
+G_DEFINE_QUARK(gjs::custom-property, ObjectBase::custom_property)
 // clang-format on
 
 static GQuark
@@ -77,20 +92,14 @@ gjs_object_priv_quark (void)
     return val;
 }
 
-static
-G_DEFINE_QUARK(gjs::property-cache, gjs_property_cache);
-
-static
-G_DEFINE_QUARK(gjs::field-cache, gjs_field_cache);
-
 /* Plain g_type_query fails and leaves @query uninitialized for
    dynamic types.
    See https://bugzilla.gnome.org/show_bug.cgi?id=687184 and
    https://bugzilla.gnome.org/show_bug.cgi?id=687211
 */
-void ObjectInstance::type_query_dynamic_safe(GTypeQuery* query) {
-    GType type = m_gtype;
-    while (g_type_get_qdata(type, ObjectInstance::custom_type_quark()))
+void ObjectBase::type_query_dynamic_safe(GTypeQuery* query) {
+    GType type = gtype();
+    while (g_type_get_qdata(type, ObjectBase::custom_type_quark()))
         type = g_type_parent(type);
 
     g_type_query(type, query);
@@ -153,6 +162,22 @@ void ObjectInstance::unlink(void) {
     m_instance_link.unlink();
 }
 
+GIObjectInfo* ObjectBase::info(void) const { return get_prototype()->m_info; }
+
+GType ObjectBase::gtype(void) const { return get_prototype()->m_gtype; }
+
+const void* ObjectBase::gobj_addr(void) const {
+    if (is_prototype())
+        return nullptr;
+    return to_instance()->m_gobj;
+}
+
+const void* ObjectBase::jsobj_addr(void) const {
+    if (is_prototype())
+        return nullptr;
+    return to_instance()->m_wrapper.get();
+}
+
 static bool
 throw_priv_is_null_error(JSContext *context)
 {
@@ -163,12 +188,12 @@ throw_priv_is_null_error(JSContext *context)
     return false;
 }
 
-bool ObjectInstance::check_is_instance(JSContext* cx,
-                                       const char* for_what) const {
+bool ObjectBase::check_is_instance(JSContext* cx, const char* for_what) const {
     if (!is_prototype())
         return true;
+    const ObjectPrototype* priv = to_prototype();
     gjs_throw(cx, "Can't %s on %s.%s.prototype; only on instances", for_what,
-              ns(), name());
+              priv->ns(), priv->name());
     return false;
 }
 
@@ -186,23 +211,18 @@ bool ObjectInstance::check_gobject_disposed(const char* for_what) const {
     return false;
 }
 
-/* Gets the ObjectInstance belonging to a particular JS object wrapper. Checks
+/* Gets the ObjectBase belonging to a particular JS object wrapper. Checks
  * that the wrapper object has the right JSClass (ObjectInstance::klass)
  * and returns null if not. */
-ObjectInstance *
-ObjectInstance::for_js(JSContext       *cx,
-                       JS::HandleObject wrapper)
-{
+ObjectBase* ObjectBase::for_js(JSContext* cx, JS::HandleObject wrapper) {
     return priv_from_js(cx, wrapper);
 }
 
 /* Use when you don't have a JSContext* available. This method is infallible
  * and cannot trigger a GC, so it's safe to use from finalize() and trace().
  * (It can return null if no instance has been set yet.) */
-ObjectInstance *
-ObjectInstance::for_js_nocheck(JSObject *wrapper)
-{
-    return static_cast<ObjectInstance *>(JS_GetPrivate(wrapper));
+ObjectBase* ObjectBase::for_js_nocheck(JSObject* wrapper) {
+    return static_cast<ObjectBase*>(JS_GetPrivate(wrapper));
 }
 
 ObjectInstance *
@@ -231,16 +251,12 @@ ObjectInstance::check_js_object_finalized(void)
     }
 }
 
-/* Prototypes only. */
-ObjectInstance *
-ObjectInstance::for_gtype(GType gtype)
-{
-    return static_cast<ObjectInstance *>(g_type_get_qdata(gtype,
-                                                          gjs_object_priv_quark()));
+ObjectPrototype* ObjectPrototype::for_gtype(GType gtype) {
+    return static_cast<ObjectPrototype*>(
+        g_type_get_qdata(gtype, gjs_object_priv_quark()));
 }
 
-void ObjectInstance::set_type_qdata(void) {
-    g_assert(is_prototype());
+void ObjectPrototype::set_type_qdata(void) {
     g_type_set_qdata(m_gtype, gjs_object_priv_quark(), this);
 }
 
@@ -256,26 +272,12 @@ ObjectInstance::unset_object_qdata(void)
     g_object_set_qdata(m_gobj, gjs_object_priv_quark(), nullptr);
 }
 
-ObjectInstance::PropertyCache* ObjectInstance::get_property_cache(void) {
-    void *data = g_type_get_qdata(m_gtype, gjs_property_cache_quark());
-    return static_cast<PropertyCache *>(data);
-}
-
-ObjectInstance::FieldCache* ObjectInstance::get_field_cache(void) {
-    void *data = g_type_get_qdata(m_gtype, gjs_field_cache_quark());
-    return static_cast<FieldCache *>(data);
-}
-
-GParamSpec *
-ObjectInstance::find_param_spec_from_id(JSContext       *cx,
-                                        JS::HandleString key)
-{
+GParamSpec* ObjectPrototype::find_param_spec_from_id(JSContext* cx,
+                                                     JS::HandleString key) {
     char *gname;
 
     /* First check for the ID in the cache */
-    PropertyCache *cache = get_property_cache();
-    g_assert(cache);
-    auto entry = cache->lookupForAdd(key);
+    auto entry = m_property_cache.lookupForAdd(key);
     if (entry)
         return entry->value().get();
 
@@ -294,22 +296,20 @@ ObjectInstance::find_param_spec_from_id(JSContext       *cx,
         return nullptr;
     }
 
-    if (!cache->add(entry, key, std::move(param_spec)))
+    if (!m_property_cache.add(entry, key, std::move(param_spec)))
         g_error("Out of memory adding param spec to cache");
     return entry->value().get();  /* owned by property cache */
 }
 
-/* Gets the ObjectInstance corresponding to obj.prototype. Cannot return null,
+/* Gets the ObjectPrototype corresponding to obj.prototype. Cannot return null,
  * and asserts so. */
-ObjectInstance *
-ObjectInstance::for_js_prototype(JSContext       *context,
-                                 JS::HandleObject obj)
-{
+ObjectPrototype* ObjectPrototype::for_js_prototype(JSContext* context,
+                                                   JS::HandleObject obj) {
     JS::RootedObject proto(context);
     JS_GetPrototype(context, obj, &proto);
-    ObjectInstance *retval = for_js(context, proto);
+    ObjectBase* retval = ObjectBase::for_js(context, proto);
     g_assert(retval);
-    return retval;
+    return retval->to_prototype();
 }
 
 /* A hook on adding a property to an object. This is called during a set
@@ -318,24 +318,21 @@ ObjectInstance::for_js_prototype(JSContext       *context,
  * custom state is set on it, because we need to keep the JS GObject wrapper
  * alive in order not to lose custom "expando" properties.
  */
-bool
-ObjectInstance::add_property(JSContext       *cx,
-                             JS::HandleObject obj,
-                             JS::HandleId     id,
-                             JS::HandleValue  value)
-{
-    ObjectInstance *priv = for_js(cx, obj);
+bool ObjectBase::add_property(JSContext* cx, JS::HandleObject obj,
+                              JS::HandleId id, JS::HandleValue value) {
+    auto* priv = ObjectBase::for_js(cx, obj);
 
     /* priv is null during init: property is not being added from JS */
     if (!priv) {
         debug_jsprop_static("Add property hook", id, obj);
         return true;
     }
+    if (priv->is_prototype())
+        return true;
 
-    return priv->add_property_impl(cx, obj, id, value);
+    return priv->to_instance()->add_property_impl(cx, obj, id, value);
 }
 
-
 bool
 ObjectInstance::add_property_impl(JSContext       *cx,
                                   JS::HandleObject obj,
@@ -344,19 +341,15 @@ ObjectInstance::add_property_impl(JSContext       *cx,
 {
     debug_jsprop("Add property hook", id, obj);
 
-    if (is_prototype() || is_custom_js_class() || m_gobj_disposed)
+    if (is_custom_js_class() || m_gobj_disposed)
         return true;
 
     ensure_uses_toggle_ref(cx);
     return true;
 }
 
-bool
-ObjectInstance::prop_getter(JSContext *cx,
-                            unsigned   argc,
-                            JS::Value *vp)
-{
-    GJS_GET_PRIV(cx, argc, vp, args, obj, ObjectInstance, priv);
+bool ObjectBase::prop_getter(JSContext* cx, unsigned argc, JS::Value* vp) {
+    GJS_GET_PRIV(cx, argc, vp, args, obj, ObjectBase, priv);
 
     JS::RootedString name(cx,
         gjs_dynamic_property_private_slot(&args.callee()).toString());
@@ -368,7 +361,7 @@ ObjectInstance::prop_getter(JSContext *cx,
         /* Ignore silently; note that this is different from what we do for
          * boxed types, for historical reasons */
 
-    return priv->prop_getter_impl(cx, obj, name, args.rval());
+    return priv->to_instance()->prop_getter_impl(cx, obj, name, args.rval());
 }
 
 bool
@@ -382,7 +375,7 @@ ObjectInstance::prop_getter_impl(JSContext             *cx,
 
     GValue gvalue = { 0, };
 
-    auto *proto_priv = ObjectInstance::for_js_prototype(cx, obj);
+    auto* proto_priv = ObjectBase::for_js(cx, obj)->get_prototype();
     GParamSpec *param = proto_priv->find_param_spec_from_id(cx, name);
 
     /* This is guaranteed because we resolved the property before */
@@ -431,14 +424,10 @@ lookup_field_info(GIObjectInfo *info,
     return retval;
 }
 
-GIFieldInfo *
-ObjectInstance::find_field_info_from_id(JSContext       *cx,
-                                        JS::HandleString key)
-{
+GIFieldInfo* ObjectPrototype::find_field_info_from_id(JSContext* cx,
+                                                      JS::HandleString key) {
     /* First check for the ID in the cache */
-    FieldCache *cache = get_field_cache();
-    g_assert(cache);
-    auto entry = cache->lookupForAdd(key);
+    auto entry = m_field_cache.lookupForAdd(key);
     if (entry)
         return entry->value().get();
 
@@ -453,17 +442,13 @@ ObjectInstance::find_field_info_from_id(JSContext       *cx,
         return nullptr;
     }
 
-    if (!cache->add(entry, key, std::move(field)))
+    if (!m_field_cache.add(entry, key, std::move(field)))
         g_error("Out of memory adding field info to cache");
     return entry->value().get();  /* owned by field cache */
 }
 
-bool
-ObjectInstance::field_getter(JSContext *cx,
-                             unsigned   argc,
-                             JS::Value *vp)
-{
-    GJS_GET_PRIV(cx, argc, vp, args, obj, ObjectInstance, priv);
+bool ObjectBase::field_getter(JSContext* cx, unsigned argc, JS::Value* vp) {
+    GJS_GET_PRIV(cx, argc, vp, args, obj, ObjectBase, priv);
 
     JS::RootedString name(cx,
         gjs_dynamic_property_private_slot(&args.callee()).toString());
@@ -475,7 +460,7 @@ ObjectInstance::field_getter(JSContext *cx,
         /* Ignore silently; note that this is different from what we do for
          * boxed types, for historical reasons */
 
-    return priv->field_getter_impl(cx, obj, name, args.rval());
+    return priv->to_instance()->field_getter_impl(cx, obj, name, args.rval());
 }
 
 bool
@@ -487,7 +472,7 @@ ObjectInstance::field_getter_impl(JSContext             *cx,
     if (!check_gobject_disposed("get any property from"))
         return true;
 
-    auto *proto_priv = ObjectInstance::for_js_prototype(cx, obj);
+    auto* proto_priv = ObjectInstance::for_js(cx, obj)->get_prototype();
     GIFieldInfo *field = proto_priv->find_field_info_from_id(cx, name);
     /* This is guaranteed because we resolved the property before */
     g_assert(field);
@@ -525,12 +510,8 @@ ObjectInstance::field_getter_impl(JSContext             *cx,
 
 /* Dynamic setter for GObject properties. Returns false on OOM/exception.
  * args.rval() becomes the "stored value" for the property. */
-bool
-ObjectInstance::prop_setter(JSContext *cx,
-                            unsigned   argc,
-                            JS::Value *vp)
-{
-    GJS_GET_PRIV(cx, argc, vp, args, obj, ObjectInstance, priv);
+bool ObjectBase::prop_setter(JSContext* cx, unsigned argc, JS::Value* vp) {
+    GJS_GET_PRIV(cx, argc, vp, args, obj, ObjectBase, priv);
 
     JS::RootedString name(cx,
         gjs_dynamic_property_private_slot(&args.callee()).toString());
@@ -545,7 +526,7 @@ ObjectInstance::prop_setter(JSContext *cx,
     /* Clear the JS stored value, to avoid keeping additional references */
     args.rval().setUndefined();
 
-    return priv->prop_setter_impl(cx, obj, name, args[0]);
+    return priv->to_instance()->prop_setter_impl(cx, obj, name, args[0]);
 }
 
 bool
@@ -557,7 +538,7 @@ ObjectInstance::prop_setter_impl(JSContext       *cx,
     if (!check_gobject_disposed("set any property on"))
         return true;
 
-    auto *proto_priv = ObjectInstance::for_js_prototype(cx, obj);
+    auto* proto_priv = ObjectInstance::for_js(cx, obj)->get_prototype();
     GParamSpec *param_spec = proto_priv->find_param_spec_from_id(cx, name);
     if (!param_spec)
         return false;
@@ -570,7 +551,7 @@ ObjectInstance::prop_setter_impl(JSContext       *cx,
 
     if (!(param_spec->flags & G_PARAM_WRITABLE))
         /* prevent setting the prop even in JS */
-        return _gjs_proxy_throw_readonly_field(cx, m_gtype, param_spec->name);
+        return _gjs_proxy_throw_readonly_field(cx, gtype(), param_spec->name);
 
     gjs_debug_jsprop(GJS_DEBUG_GOBJECT, "Setting GObject prop %s",
                      param_spec->name);
@@ -588,12 +569,8 @@ ObjectInstance::prop_setter_impl(JSContext       *cx,
     return true;
 }
 
-bool
-ObjectInstance::field_setter(JSContext *cx,
-                             unsigned   argc,
-                             JS::Value *vp)
-{
-    GJS_GET_PRIV(cx, argc, vp, args, obj, ObjectInstance, priv);
+bool ObjectBase::field_setter(JSContext* cx, unsigned argc, JS::Value* vp) {
+    GJS_GET_PRIV(cx, argc, vp, args, obj, ObjectBase, priv);
 
     JS::RootedString name(cx,
         gjs_dynamic_property_private_slot(&args.callee()).toString());
@@ -611,7 +588,7 @@ ObjectInstance::field_setter(JSContext *cx,
      * the field */
     args.rval().setUndefined();
 
-    return priv->field_setter_impl(cx, obj, name, args[0]);
+    return priv->to_instance()->field_setter_impl(cx, obj, name, args[0]);
 }
 
 bool
@@ -623,7 +600,7 @@ ObjectInstance::field_setter_impl(JSContext       *cx,
     if (!check_gobject_disposed("set GObject field on"))
         return true;
 
-    auto *proto_priv = ObjectInstance::for_js_prototype(cx, obj);
+    auto* proto_priv = ObjectInstance::for_js(cx, obj)->get_prototype();
     GIFieldInfo *field = proto_priv->find_field_info_from_id(cx, name);
     if (field == NULL)
         return false;
@@ -636,13 +613,11 @@ ObjectInstance::field_setter_impl(JSContext       *cx,
         return true;
     }
 
-    return _gjs_proxy_throw_readonly_field(cx, m_gtype,
+    return _gjs_proxy_throw_readonly_field(cx, gtype(),
                                            g_base_info_get_name(field));
 }
 
-bool
-ObjectInstance::is_vfunc_unchanged(GIVFuncInfo *info)
-{
+bool ObjectPrototype::is_vfunc_unchanged(GIVFuncInfo* info) {
     GType ptype = g_type_parent(m_gtype);
     GError *error = NULL;
     gpointer addr1, addr2;
@@ -692,12 +667,8 @@ find_vfunc_on_parents(GIObjectInfo *info,
     return vfunc;
 }
 
-bool
-ObjectInstance::resolve_no_info(JSContext       *cx,
-                                JS::HandleObject obj,
-                                bool            *resolved,
-                                const char      *name)
-{
+bool ObjectPrototype::resolve_no_info(JSContext* cx, JS::HandleObject obj,
+                                      bool* resolved, const char* name) {
     guint n_interfaces;
     guint i;
 
@@ -773,13 +744,9 @@ is_gobject_property_name(GIObjectInfo *info,
     return g_property_info_get_flags(prop_info) & G_PARAM_READABLE;
 }
 
-bool
-ObjectInstance::resolve(JSContext       *cx,
-                        JS::HandleObject obj,
-                        JS::HandleId     id,
-                        bool            *resolved)
-{
-    auto *priv = ObjectInstance::for_js(cx, obj);
+bool ObjectBase::resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id,
+                         bool* resolved) {
+    auto* priv = ObjectBase::for_js(cx, obj);
 
     if (!priv) {
         /* We won't have a private until the initializer is called, so
@@ -795,22 +762,18 @@ ObjectInstance::resolve(JSContext       *cx,
         return true;
     }
 
-    return priv->resolve_impl(cx, obj, id, resolved);
-}
-
-bool
-ObjectInstance::resolve_impl(JSContext       *context,
-                             JS::HandleObject obj,
-                             JS::HandleId     id,
-                             bool            *resolved)
-{
-    debug_jsprop("Resolve hook", id, obj);
-
-    if (!is_prototype()) {
+    if (!priv->is_prototype()) {
         *resolved = false;
         return true;
     }
 
+    return priv->to_prototype()->resolve_impl(cx, obj, id, resolved);
+}
+
+bool ObjectPrototype::resolve_impl(JSContext* context, JS::HandleObject obj,
+                                   JS::HandleId id, bool* resolved) {
+    debug_jsprop("Resolve hook", id, obj);
+
     GjsAutoJSChar name;
     if (!gjs_get_string_id(context, id, &name)) {
         *resolved = false;
@@ -880,10 +843,9 @@ ObjectInstance::resolve_impl(JSContext       *context,
         debug_jsprop("Defining lazy GObject property", id, obj);
 
         JS::RootedValue private_id(context, JS::StringValue(JSID_TO_STRING(id)));
-        if (!gjs_define_property_dynamic(context, proto, name, "gobject_prop",
-                                         &ObjectInstance::prop_getter,
-                                         &ObjectInstance::prop_setter,
-                                         private_id, GJS_MODULE_PROP_FLAGS))
+        if (!gjs_define_property_dynamic(
+                context, proto, name, "gobject_prop", &ObjectBase::prop_getter,
+                &ObjectBase::prop_setter, private_id, GJS_MODULE_PROP_FLAGS))
             return false;
 
         *resolved = true;
@@ -911,9 +873,9 @@ ObjectInstance::resolve_impl(JSContext       *context,
 
         JS::RootedValue private_id(context, JS::StringValue(JSID_TO_STRING(id)));
         if (!gjs_define_property_dynamic(context, proto, name, "gobject_field",
-                                         &ObjectInstance::field_getter,
-                                         &ObjectInstance::field_setter,
-                                         private_id, flags))
+                                         &ObjectBase::field_getter,
+                                         &ObjectBase::field_setter, private_id,
+                                         flags))
             return false;
 
         *resolved = true;
@@ -964,12 +926,10 @@ ObjectInstance::resolve_impl(JSContext       *context,
 
 /* Set properties from args to constructor (args[0] is supposed to be
  * a hash) */
-bool
-ObjectInstance::props_to_g_parameters(JSContext                  *context,
-                                      const JS::HandleValueArray& args,
-                                      std::vector<const char *>  *names,
-                                      AutoGValueVector           *values)
-{
+bool ObjectPrototype::props_to_g_parameters(JSContext* context,
+                                            const JS::HandleValueArray& args,
+                                            std::vector<const char*>* names,
+                                            AutoGValueVector* values) {
     size_t ix, length;
 
     if (args.length() == 0 || args[0].isUndefined())
@@ -1304,48 +1264,36 @@ ObjectInstance::new_for_js_object(JSContext       *cx,
     return priv;
 }
 
-ObjectInstance::ObjectInstance(JSContext       *cx,
-                               JS::HandleObject object)
-    : m_wrapper_finalized(0),
-      m_gobj_disposed(0),
-      m_uses_toggle_ref(0)
-{
-    GJS_INC_COUNTER(object);
-
+ObjectInstance::ObjectInstance(JSContext* cx, JS::HandleObject object)
+    : ObjectBase(ObjectPrototype::for_js_prototype(cx, object)) {
     g_assert(!JS_GetPrivate(object));
-    JS_SetPrivate(object, this);
-
-    auto *proto_priv = ObjectInstance::for_js_prototype(cx, object);
-
-    m_gtype = proto_priv->m_gtype;
-    m_info = proto_priv->m_info;
-    if (m_info)
-        g_base_info_ref(m_info);
 
+    GJS_INC_COUNTER(object_instance);
     debug_lifecycle("Instance constructor");
 }
 
-ObjectInstance::ObjectInstance(JSObject     *prototype,
-                               GIObjectInfo *info,
-                               GType         gtype)
-    : m_info(info),
-      m_gtype(gtype),
-      m_class(static_cast<GTypeClass *>(g_type_class_ref(gtype)))
-{
+ObjectPrototype* ObjectPrototype::new_for_js_object(GIObjectInfo* info,
+                                                    GType gtype) {
+    auto* priv = g_slice_new0(ObjectPrototype);
+    new (priv) ObjectPrototype(info, gtype);
+    return priv;
+}
+
+ObjectPrototype::ObjectPrototype(GIObjectInfo* info, GType gtype)
+    : ObjectBase(), m_info(info), m_gtype(gtype) {
     if (info)
         g_base_info_ref(info);
 
-    auto *property_cache = new PropertyCache();
-    if (!property_cache->init())
+    g_type_class_ref(gtype);
+
+    if (!m_property_cache.init())
         g_error("Out of memory for property cache of %s", type_name());
-    g_type_set_qdata(gtype, gjs_property_cache_quark(), property_cache);
 
-    auto *field_cache = new FieldCache();
-    if (!field_cache->init())
+    if (!m_field_cache.init())
         g_error("Out of memory for field cache of %s", type_name());
-    g_type_set_qdata(gtype, gjs_field_cache_quark(), field_cache);
 
-    JS_SetPrivate(prototype, this);
+    GJS_INC_COUNTER(object_prototype);
+    debug_lifecycle("Prototype constructor");
 }
 
 static void
@@ -1438,9 +1386,7 @@ ObjectInstance::ensure_uses_toggle_ref(JSContext *cx)
     g_object_unref(m_gobj);
 }
 
-void
-ObjectInstance::invalidate_all_closures(void)
-{
+void ObjectBase::invalidate_all_closures(void) {
     /* Can't loop directly through the items, since invalidating an item's
      * closure might have the effect of removing the item from the set in the
      * invalidate notifier */
@@ -1487,23 +1433,22 @@ ObjectInstance::init_impl(JSContext              *context,
 {
     GTypeQuery query;
 
-    g_assert(m_gtype != G_TYPE_NONE);
+    g_assert(gtype() != G_TYPE_NONE);
 
     std::vector<const char *> names;
     AutoGValueVector values;
-    auto *proto_priv = ObjectInstance::for_js_prototype(context, object);
-    if (!proto_priv->props_to_g_parameters(context, args, &names, &values))
+    if (!m_proto->props_to_g_parameters(context, args, &names, &values))
         return false;
 
     /* Mark this object in the construction stack, it
        will be popped in gjs_object_custom_init() later
        down.
     */
-    if (g_type_get_qdata(m_gtype, ObjectInstance::custom_type_quark()))
+    if (g_type_get_qdata(gtype(), ObjectInstance::custom_type_quark()))
         object_init_list.emplace(context, object);
 
     g_assert(names.size() == values.size());
-    GObject *gobj = g_object_new_with_properties(m_gtype, values.size(),
+    GObject* gobj = g_object_new_with_properties(gtype(), values.size(),
                                                  names.data(), values.data());
 
     ObjectInstance *other_priv = ObjectInstance::for_gobject(gobj);
@@ -1565,7 +1510,7 @@ GJS_NATIVE_CONSTRUCTOR_DECLARE(object_instance)
     /* Init the private variable before we do anything else. If a garbage
      * collection happens when calling the init function then this object
      * might be traced and we will end up dereferencing a null pointer */
-    (void) ObjectInstance::new_for_js_object(context, object);
+    JS_SetPrivate(object, ObjectInstance::new_for_js_object(context, object));
 
     if (!gjs_object_require_property(context, object, "GObject instance",
                                      GJS_STRING_GOBJECT_INIT, &initer))
@@ -1580,72 +1525,53 @@ GJS_NATIVE_CONSTRUCTOR_DECLARE(object_instance)
     return ret;
 }
 
-void
-ObjectInstance::trace(JSTracer *tracer,
-                      JSObject *obj)
-{
-    auto *priv = ObjectInstance::for_js_nocheck(obj);
+void ObjectBase::trace(JSTracer* tracer, JSObject* obj) {
+    auto* priv = ObjectBase::for_js_nocheck(obj);
     if (priv == NULL)
         return;
 
-    priv->trace_impl(tracer);
+    if (priv->is_prototype())
+        priv->to_prototype()->trace_impl(tracer);
+    else
+        priv->trace_impl(tracer);
 }
 
-void
-ObjectInstance::trace_impl(JSTracer *tracer)
-{
+void ObjectBase::trace_impl(JSTracer* tracer) {
     for (GClosure *closure : m_closures)
         gjs_closure_trace(closure, tracer);
+}
 
-    PropertyCache *property_cache = get_property_cache();
-    if (property_cache)
-        property_cache->trace(tracer);
-    FieldCache *field_cache = get_field_cache();
-    if (field_cache)
-        field_cache->trace(tracer);
+void ObjectPrototype::trace_impl(JSTracer* tracer) {
+    m_property_cache.trace(tracer);
+    m_field_cache.trace(tracer);
+    ObjectBase::trace_impl(tracer);
 }
 
-void
-ObjectInstance::finalize(JSFreeOp *fop,
-                         JSObject *obj)
-{
-    auto *priv = ObjectInstance::for_js_nocheck(obj);
+void ObjectBase::finalize(JSFreeOp* fop, JSObject* obj) {
+    auto* priv = ObjectBase::for_js_nocheck(obj);
     g_assert (priv != NULL);
 
-    priv->finalize_impl(fop, obj);
+    if (priv->is_prototype()) {
+        priv->to_prototype()->~ObjectPrototype();
+        g_slice_free(ObjectPrototype, priv);
+    } else {
+        priv->to_instance()->~ObjectInstance();
+        g_slice_free(ObjectInstance, priv);
+    }
 
-    priv->~ObjectInstance();
-    g_slice_free(ObjectInstance, priv);
+    /* Remove the pointer from the JSObject */
+    JS_SetPrivate(obj, nullptr);
 }
 
-void
-ObjectInstance::finalize_impl(JSFreeOp  *fop,
-                              JSObject  *obj)
-{
+ObjectInstance::~ObjectInstance() {
     debug_lifecycle("Finalize");
 
     TRACE(GJS_OBJECT_PROXY_FINALIZE(priv, m_gobj, ns(), name()));
 
-    /* Prototype only, although if priv->gobj is null it could also be an
-     * instance struct with a freed gobject. This is annoying. It means we have
-     * to always do this, and have an extra null check. */
-    PropertyCache *property_cache = get_property_cache();
-    if (property_cache) {
-        delete property_cache;
-        g_type_set_qdata(m_gtype, gjs_property_cache_quark(), nullptr);
-    }
-    FieldCache *field_cache = get_field_cache();
-    if (field_cache) {
-        delete field_cache;
-        g_type_set_qdata(m_gtype, gjs_field_cache_quark(), nullptr);
-    }
-
-    /* This applies only to instances, not prototypes, but it's possible that
-     * an instance's GObject is already freed at this point. */
     invalidate_all_closures();
 
-    /* Object is instance, not prototype, AND GObject is not already freed */
-    if (!is_prototype()) {
+    /* GObject is not already freed */
+    if (m_gobj) {
         bool had_toggle_up;
         bool had_toggle_down;
 
@@ -1681,13 +1607,16 @@ ObjectInstance::finalize_impl(JSFreeOp  *fop,
     }
     unlink();
 
-    g_clear_pointer(&m_info, g_base_info_unref);
-    g_clear_pointer(&m_class, g_type_class_unref);
+    GJS_DEC_COUNTER(object_instance);
+}
 
-    /* Remove the ObjectInstance pointer from the JSObject */
-    JS_SetPrivate(obj, nullptr);
+ObjectPrototype::~ObjectPrototype() {
+    invalidate_all_closures();
 
-    GJS_DEC_COUNTER(object);
+    g_clear_pointer(&m_info, g_base_info_unref);
+    g_type_class_unref(g_type_class_peek(m_gtype));
+
+    GJS_DEC_COUNTER(object_prototype);
 }
 
 JSObject* gjs_lookup_object_constructor_from_info(JSContext* context,
@@ -1760,43 +1689,37 @@ gjs_lookup_object_prototype(JSContext *context,
     return gjs_lookup_object_prototype_from_info(context, info, gtype);
 }
 
-void
-ObjectInstance::associate_closure(JSContext *cx,
-                                  GClosure  *closure)
-{
-    /* FIXME: Should be changed to g_assert(!is_prototype()) but due to the
-     * structs being the same, this could still be an instance, the GObject of
-     * which has been dissociated */
+void ObjectBase::associate_closure(JSContext* cx, GClosure* closure) {
     if (!is_prototype())
-        ensure_uses_toggle_ref(cx);
+        to_instance()->ensure_uses_toggle_ref(cx);
 
     /* This is a weak reference, and will be cleared when the closure is
      * invalidated */
     m_closures.insert(closure);
     g_closure_add_invalidate_notifier(closure, this,
-                                      &ObjectInstance::closure_invalidated_notify);
+                                      &ObjectBase::closure_invalidated_notify);
 }
 
-bool
-ObjectInstance::connect(JSContext *cx,
-                        unsigned   argc,
-                        JS::Value *vp)
-{
-    GJS_GET_PRIV(cx, argc, vp, args, obj, ObjectInstance, priv);
+bool ObjectBase::connect(JSContext* cx, unsigned argc, JS::Value* vp) {
+    GJS_GET_PRIV(cx, argc, vp, args, obj, ObjectBase, priv);
     if (!priv)
         return throw_priv_is_null_error(cx); /* wrong class passed in */
-    return priv->connect_impl(cx, args, false);
+
+    if (!priv->check_is_instance(cx, "connect to signals"))
+        return false;
+
+    return priv->to_instance()->connect_impl(cx, args, false);
 }
 
-bool
-ObjectInstance::connect_after(JSContext *cx,
-                              unsigned   argc,
-                              JS::Value *vp)
-{
-    GJS_GET_PRIV(cx, argc, vp, args, obj, ObjectInstance, priv);
+bool ObjectBase::connect_after(JSContext* cx, unsigned argc, JS::Value* vp) {
+    GJS_GET_PRIV(cx, argc, vp, args, obj, ObjectBase, priv);
     if (!priv)
         return throw_priv_is_null_error(cx); /* wrong class passed in */
-    return priv->connect_impl(cx, args, true);
+
+    if (!priv->check_is_instance(cx, "connect to signals"))
+        return false;
+
+    return priv->to_instance()->connect_impl(cx, args, true);
 }
 
 bool
@@ -1811,9 +1734,6 @@ ObjectInstance::connect_impl(JSContext          *context,
 
     gjs_debug_gsignal("connect obj %p priv %p", m_wrapper.get(), this);
 
-    if (!check_is_instance(context, "connect to signals"))
-        return false;
-
     if (!check_gobject_disposed("connect to any signal on"))
         return true;
 
@@ -1829,7 +1749,7 @@ ObjectInstance::connect_impl(JSContext          *context,
         return false;
     }
 
-    if (!g_signal_parse_name(signal_name, m_gtype, &signal_id, &signal_detail,
+    if (!g_signal_parse_name(signal_name, gtype(), &signal_id, &signal_detail,
                              true)) {
         gjs_throw(context, "No signal '%s' on object '%s'",
                   signal_name.get(), type_name());
@@ -1852,15 +1772,15 @@ ObjectInstance::connect_impl(JSContext          *context,
     return true;
 }
 
-bool
-ObjectInstance::emit(JSContext *cx,
-                     unsigned   argc,
-                     JS::Value *vp)
-{
-    GJS_GET_PRIV(cx, argc, vp, args, obj, ObjectInstance, priv);
+bool ObjectBase::emit(JSContext* cx, unsigned argc, JS::Value* vp) {
+    GJS_GET_PRIV(cx, argc, vp, args, obj, ObjectBase, priv);
     if (!priv)
         return throw_priv_is_null_error(cx); /* wrong class passed in */
-    return priv->emit_impl(cx, args);
+
+    if (!priv->check_is_instance(cx, "emit signal"))
+        return false;
+
+    return priv->to_instance()->emit_impl(cx, args);
 }
 
 bool
@@ -1878,9 +1798,6 @@ ObjectInstance::emit_impl(JSContext          *context,
     gjs_debug_gsignal("emit obj %p priv %p argc %d", m_wrapper.get(), this,
                       argv.length());
 
-    if (!check_is_instance(context, "emit signal"))
-        return false;
-
     if (!check_gobject_disposed("emit any signal on"))
         return true;
 
@@ -1889,7 +1806,7 @@ ObjectInstance::emit_impl(JSContext          *context,
                              "signal name", &signal_name))
         return false;
 
-    if (!g_signal_parse_name(signal_name, m_gtype, &signal_id, &signal_detail,
+    if (!g_signal_parse_name(signal_name, gtype(), &signal_id, &signal_detail,
                              false)) {
         gjs_throw(context, "No signal '%s' on object '%s'",
                   signal_name.get(), type_name());
@@ -1951,41 +1868,43 @@ ObjectInstance::emit_impl(JSContext          *context,
     return !failed;
 }
 
-bool
-ObjectInstance::to_string(JSContext *cx,
-                          unsigned   argc,
-                          JS::Value *vp)
-{
-    GJS_GET_PRIV(cx, argc, vp, args, obj, ObjectInstance, priv);
+bool ObjectBase::to_string(JSContext* cx, unsigned argc, JS::Value* vp) {
+    GJS_GET_PRIV(cx, argc, vp, args, obj, ObjectBase, priv);
     if (!priv)
         return throw_priv_is_null_error(cx);  /* wrong class passed in */
-    return priv->to_string_impl(cx, args);
+
+    if (priv->is_prototype())
+        return priv->to_prototype()->to_string_impl(cx, args);
+    return priv->to_instance()->to_string_impl(cx, args);
 }
 
 bool
 ObjectInstance::to_string_impl(JSContext          *cx,
                                const JS::CallArgs& args)
 {
-    return _gjs_proxy_to_string_func(cx, m_wrapper,
-                                     m_gobj_disposed ?
-                                        "object (FINALIZED)" : "object",
-                                     m_info, m_gtype,
-                                     m_gobj, args.rval());
+    return _gjs_proxy_to_string_func(
+        cx, m_wrapper, m_gobj_disposed ? "object (FINALIZED)" : "object",
+        info(), gtype(), m_gobj, args.rval());
+}
+
+bool ObjectPrototype::to_string_impl(JSContext* cx, const JS::CallArgs& args) {
+    return _gjs_proxy_to_string_func(cx, nullptr, "object prototype", info(),
+                                     gtype(), nullptr, args.rval());
 }
 
 static const struct JSClassOps gjs_object_class_ops = {
-    &ObjectInstance::add_property,
-    NULL,  /* deleteProperty */
-    nullptr,  /* getProperty */
-    nullptr,  /* setProperty */
-    NULL,  /* enumerate */
-    &ObjectInstance::resolve,
-    nullptr,  /* mayResolve */
-    &ObjectInstance::finalize,
+    &ObjectBase::add_property,
+    nullptr,  // deleteProperty
+    nullptr,  // getProperty
+    nullptr,  // setProperty
+    nullptr,  // enumerate
+    &ObjectBase::resolve,
+    nullptr,  // mayResolve
+    &ObjectBase::finalize,
     NULL,
     NULL,
     NULL,
-    &ObjectInstance::trace,
+    &ObjectBase::trace,
 };
 
 struct JSClass gjs_object_instance_class = {
@@ -1994,20 +1913,19 @@ struct JSClass gjs_object_instance_class = {
     &gjs_object_class_ops
 };
 
-bool
-ObjectInstance::init(JSContext *context,
-                     unsigned   argc,
-                     JS::Value *vp)
-{
+bool ObjectBase::init(JSContext* context, unsigned argc, JS::Value* vp) {
     GJS_GET_THIS(context, argc, vp, argv, obj);
 
     if (!do_base_typecheck(context, obj, true))
         return false;
 
-    ObjectInstance *priv = ObjectInstance::for_js(context, obj);
+    auto* priv = ObjectBase::for_js(context, obj);
     g_assert(priv);  /* Already checked by do_base_typecheck() */
 
-    return priv->init_impl(context, argv, &obj);
+    if (!priv->check_is_instance(context, "initialize"))
+        return false;
+
+    return priv->to_instance()->init_impl(context, argv, &obj);
 }
 
 JSPropertySpec gjs_object_instance_proto_props[] = {
@@ -2015,13 +1933,12 @@ JSPropertySpec gjs_object_instance_proto_props[] = {
 };
 
 JSFunctionSpec gjs_object_instance_proto_funcs[] = {
-    JS_FS("_init", &ObjectInstance::init, 0, 0),
-    JS_FS("connect", &ObjectInstance::connect, 0, 0),
-    JS_FS("connect_after", &ObjectInstance::connect_after, 0, 0),
-    JS_FS("emit", &ObjectInstance::emit, 0, 0),
-    JS_FS("toString", &ObjectInstance::to_string, 0, 0),
-    JS_FS_END
-};
+    JS_FS("_init", &ObjectBase::init, 0, 0),
+    JS_FS("connect", &ObjectBase::connect, 0, 0),
+    JS_FS("connect_after", &ObjectBase::connect_after, 0, 0),
+    JS_FS("emit", &ObjectBase::emit, 0, 0),
+    JS_FS("toString", &ObjectBase::to_string, 0, 0),
+    JS_FS_END};
 
 bool
 gjs_object_define_static_methods(JSContext       *context,
@@ -2096,7 +2013,6 @@ gjs_define_object_class(JSContext              *context,
     const char *constructor_name;
     JS::RootedObject parent_proto(context);
 
-    ObjectInstance *priv;
     const char *ns;
     GType parent_type;
 
@@ -2172,13 +2088,11 @@ gjs_define_object_class(JSContext              *context,
     JS::RootedId hook_up_vfunc(context,
         SYMBOL_TO_JSID(ObjectInstance::hook_up_vfunc_symbol(context)));
     if (!JS_DefineFunctionById(context, prototype, hook_up_vfunc,
-                               &ObjectInstance::hook_up_vfunc, 3,
+                               &ObjectBase::hook_up_vfunc, 3,
                                GJS_MODULE_PROP_FLAGS))
         return false;
 
-    GJS_INC_COUNTER(object);
-    priv = g_slice_new0(ObjectInstance);
-    new (priv) ObjectInstance(prototype, info, gtype);
+    JS_SetPrivate(prototype, ObjectPrototype::new_for_js_object(info, gtype));
 
     gjs_debug(GJS_DEBUG_GOBJECT, "Defined class for %s (%s), prototype %p, "
               "JSClass %p, in object %p", constructor_name, g_type_name(gtype),
@@ -2224,6 +2138,7 @@ gjs_object_from_g_object(JSContext    *context,
             return nullptr;
 
         priv = ObjectInstance::new_for_js_object(context, obj);
+        JS_SetPrivate(obj, priv);
 
         g_object_ref_sink(gobj);
         priv->associate_js_gobject(context, obj, gobj);
@@ -2241,14 +2156,15 @@ gjs_g_object_from_object(JSContext       *cx,
     if (!obj)
         return NULL;
 
-    auto *priv = ObjectInstance::for_js(cx, obj);
-    if (!priv)
+    auto* priv = ObjectBase::for_js(cx, obj);
+    if (!priv || priv->is_prototype())
         return nullptr;
 
-    if (!priv->check_gobject_disposed("access"))
+    ObjectInstance* instance = priv->to_instance();
+    if (!instance->check_gobject_disposed("access"))
         return nullptr;
 
-    return priv->gobj();
+    return instance->gobj();
 }
 
 bool
@@ -2259,25 +2175,28 @@ gjs_typecheck_is_object(JSContext       *context,
     return do_base_typecheck(context, object, throw_error);
 }
 
-bool
-gjs_typecheck_object(JSContext       *context,
-                     JS::HandleObject object,
-                     GType            expected_type,
-                     bool             throw_error)
-{
-    if (!do_base_typecheck(context, object, throw_error))
+bool gjs_typecheck_object(JSContext* cx, JS::HandleObject object,
+                          GType expected_type, bool throw_error) {
+    if (!do_base_typecheck(cx, object, throw_error))
         return false;
 
-    auto *priv = ObjectInstance::for_js(context, object);
+    auto* priv = ObjectBase::for_js(cx, object);
 
     if (priv == NULL) {
         if (throw_error)
-            return throw_priv_is_null_error(context);
+            return throw_priv_is_null_error(cx);
 
         return false;
     }
 
-    return priv->typecheck_object(context, expected_type, throw_error);
+    /* check_is_instance() throws, so only call if if throw_error.
+     * is_prototype() checks the same thing without throwing. */
+    if ((throw_error && !priv->check_is_instance(cx, "convert to GObject*")) ||
+        priv->is_prototype())
+        return false;
+
+    return priv->to_instance()->typecheck_object(cx, expected_type,
+                                                 throw_error);
 }
 
 bool
@@ -2285,15 +2204,11 @@ ObjectInstance::typecheck_object(JSContext *context,
                                  GType      expected_type,
                                  bool       throw_error)
 {
-    if ((throw_error && !check_is_instance(context, "convert to GObject*")) ||
-        is_prototype())
-        return false;
-
-    g_assert(m_gobj_disposed || m_gtype == G_OBJECT_TYPE(m_gobj));
+    g_assert(m_gobj_disposed || gtype() == G_OBJECT_TYPE(m_gobj));
 
     bool result;
     if (expected_type != G_TYPE_NONE)
-        result = g_type_is_a(m_gtype, expected_type);
+        result = g_type_is_a(gtype(), expected_type);
     else
         result = true;
 
@@ -2381,24 +2296,19 @@ find_vfunc_info (JSContext *context,
     return true;
 }
 
-bool
-ObjectInstance::hook_up_vfunc(JSContext *cx,
-                              unsigned   argc,
-                              JS::Value *vp)
-{
-    GJS_GET_PRIV(cx, argc, vp, args, object, ObjectInstance, priv);
+bool ObjectBase::hook_up_vfunc(JSContext* cx, unsigned argc, JS::Value* vp) {
+    GJS_GET_PRIV(cx, argc, vp, args, prototype, ObjectBase, priv);
     if (!priv)
         return throw_priv_is_null_error(cx);
 
-    return priv->hook_up_vfunc_impl(cx, args);
+    /* Normally we wouldn't assert is_prototype(), but this method can only be
+     * called internally so it's OK to crash if done wrongly */
+    return priv->to_prototype()->hook_up_vfunc_impl(cx, args, prototype);
 }
 
-bool
-ObjectInstance::hook_up_vfunc_impl(JSContext          *cx,
-                                   const JS::CallArgs& args)
-{
-    g_assert(is_prototype());
-
+bool ObjectPrototype::hook_up_vfunc_impl(JSContext* cx,
+                                         const JS::CallArgs& args,
+                                         JS::HandleObject prototype) {
     GjsAutoJSChar name;
     JS::RootedObject function(cx);
     if (!gjs_parse_call_args(cx, "hook_up_vfunc", args, "so",
@@ -2467,10 +2377,8 @@ ObjectInstance::hook_up_vfunc_impl(JSContext          *cx,
         method_ptr = G_STRUCT_MEMBER_P(implementor_vtable, offset);
 
         JS::RootedValue v_function(cx, JS::ObjectValue(*function));
-        JS::RootedObject wrapper(cx, m_wrapper);
-        trampoline = gjs_callback_trampoline_new(cx, v_function, vfunc,
-                                                 GI_SCOPE_TYPE_NOTIFIED,
-                                                 wrapper, true);
+        trampoline = gjs_callback_trampoline_new(
+            cx, v_function, vfunc, GI_SCOPE_TYPE_NOTIFIED, prototype, true);
 
         *((ffi_closure **)method_ptr) = trampoline->closure;
     }
@@ -2504,7 +2412,7 @@ gjs_object_associate_closure(JSContext       *cx,
                              JS::HandleObject object,
                              GClosure        *closure)
 {
-    ObjectInstance *priv = priv_from_js(cx, object);
+    auto* priv = ObjectBase::for_js(cx, object);
     if (!priv)
         return false;
 
diff --git a/gi/object.h b/gi/object.h
index cbc66ee9..7c4327c7 100644
--- a/gi/object.h
+++ b/gi/object.h
@@ -61,19 +61,266 @@ struct AutoGValueVector : public std::vector<GValue> {
     }
 };
 
-class ObjectInstance {
-    GIObjectInfo* m_info;
-    GObject* m_gobj;  // nullptr if we are the prototype and not an instance
-    GjsMaybeOwned<JSObject*> m_wrapper;
-    GType m_gtype;
+class ObjectPrototype;
+
+/* To conserve memory, we have two different kinds of private data for GObject
+ * JS wrappers: ObjectInstance, and ObjectPrototype. Both inherit from
+ * ObjectBase for their common functionality.
+ *
+ * It's important that ObjectBase and ObjectInstance not grow in size without a
+ * very good reason. There can be tens, maybe hundreds of thousands of these
+ * objects alive in a typical gnome-shell run, so even 8 more bytes will add up.
+ * It's less critical that ObjectPrototype stay small, since only one of these
+ * is allocated per GType.
+ *
+ * Sadly, we cannot have virtual methods in ObjectBase, because SpiderMonkey can
+ * be compiled with or without RTTI, so we cannot count on being able to cast
+ * ObjectBase to ObjectInstance or ObjectPrototype with dynamic_cast<>, and the
+ * vtable would take up just as much space anyway. Instead, we have the
+ * to_prototype() and to_instance() methods which will give you a pointer if the
+ * ObjectBase is of the correct type (and assert if not.)
+ */
+class ObjectBase {
+ protected:
+    /* nullptr if this is an ObjectPrototype; points to the corresponding
+     * ObjectPrototype if this is an ObjectInstance */
+    ObjectPrototype* m_proto;
 
     /* a list of all GClosures installed on this object (from
-     * signals, trampolines and explicit GClosures), used when tracing */
+     * signals, trampolines, explicit GClosures, and vfuncs on prototypes),
+     * used when tracing */
     std::set<GClosure*> m_closures;
 
-    /* the GObjectClass wrapped by this JS Object (only used for
-       prototypes) */
-    GTypeClass* m_class;
+    explicit ObjectBase(ObjectPrototype* proto = nullptr) : m_proto(proto) {}
+
+    /* Methods to get an existing ObjectBase */
+
+ public:
+    static ObjectBase* for_js(JSContext* cx, JS::HandleObject obj);
+    static ObjectBase* for_js_nocheck(JSObject* obj);
+
+    /* Methods for getting a pointer to the correct subclass. We don't use
+     * standard C++ subclasses because that would occupy another 8 bytes in
+     * ObjectInstance for a vtable. */
+
+    bool is_prototype(void) const { return !m_proto; }
+
+    /* The to_instance() and to_prototype() methods assert that this ObjectBase
+     * is of the correct subclass. If you don't want an assert, then either
+     * check beforehand or use get_prototype(). */
+
+    ObjectPrototype* to_prototype(void) {
+        g_assert(is_prototype());
+        return reinterpret_cast<ObjectPrototype*>(this);
+    }
+    const ObjectPrototype* to_prototype(void) const {
+        g_assert(is_prototype());
+        return reinterpret_cast<const ObjectPrototype*>(this);
+    }
+    ObjectInstance* to_instance(void) {
+        g_assert(!is_prototype());
+        return reinterpret_cast<ObjectInstance*>(this);
+    }
+    const ObjectInstance* to_instance(void) const {
+        g_assert(!is_prototype());
+        return reinterpret_cast<const ObjectInstance*>(this);
+    }
+
+    /* get_prototype() doesn't assert. If you call it on an ObjectPrototype, it
+     * returns you the same object cast to the correct type; if you call it on
+     * an ObjectInstance, it returns you the ObjectPrototype belonging to the
+     * corresponding JS prototype. */
+    ObjectPrototype* get_prototype(void) {
+        return is_prototype() ? to_prototype() : m_proto;
+    }
+    const ObjectPrototype* get_prototype(void) const {
+        return is_prototype() ? to_prototype() : m_proto;
+    }
+
+    /* Accessors */
+
+    /* Both ObjectInstance and ObjectPrototype have GIObjectInfo and GType,
+     * but for space reasons we store it only on ObjectPrototype. */
+    GIObjectInfo* info(void) const;
+    GType gtype(void) const;
+
+    const char* ns(void) const {
+        return info() ? g_base_info_get_namespace(info()) : "";
+    }
+    const char* name(void) const {
+        return info() ? g_base_info_get_name(info()) : type_name();
+    }
+    const char* type_name(void) const { return g_type_name(gtype()); }
+    bool is_custom_js_class(void) const { return !info(); }
+
+ private:
+    /* These are used in debug methods only. */
+    const void* gobj_addr(void) const;
+    const void* jsobj_addr(void) const;
+
+    /* Helper methods */
+
+ public:
+    bool check_is_instance(JSContext* cx, const char* for_what) const;
+
+ protected:
+    void debug_lifecycle(const char* message) const {
+        gjs_debug_lifecycle(GJS_DEBUG_GOBJECT,
+                            "[%p: GObject %p JS wrapper %p %s.%s (%s)] %s",
+                            this, gobj_addr(), jsobj_addr(), ns(), name(),
+                            type_name(), message);
+    }
+    void debug_jsprop_base(const char* message, const char* id,
+                           JSObject* obj) const {
+        gjs_debug_jsprop(GJS_DEBUG_GOBJECT,
+                         "[%p: GObject %p JS object %p %s.%s (%s)] %s '%s'",
+                         this, gobj_addr(), obj, ns(), name(), type_name(),
+                         message, id);
+    }
+    void debug_jsprop(const char* message, jsid id, JSObject* obj) const {
+        debug_jsprop_base(message, gjs_debug_id(id).c_str(), obj);
+    }
+    void debug_jsprop(const char* message, JSString* id, JSObject* obj) const {
+        debug_jsprop_base(message, gjs_debug_string(id).c_str(), obj);
+    }
+    static void debug_jsprop_static(const char* message, jsid id,
+                                    JSObject* obj) {
+        gjs_debug_jsprop(GJS_DEBUG_GOBJECT,
+                         "[JS object %p] %s '%s', no instance associated", obj,
+                         message, gjs_debug_id(id).c_str());
+    }
+
+ public:
+    void type_query_dynamic_safe(GTypeQuery* query);
+
+    /* Methods to manipulate the list of closures */
+
+ protected:
+    void invalidate_all_closures(void);
+
+ public:
+    void associate_closure(JSContext* cx, GClosure* closure);
+    static void closure_invalidated_notify(void* data, GClosure* closure) {
+        static_cast<ObjectBase*>(data)->m_closures.erase(closure);
+    }
+
+    /* JSClass operations */
+
+    static bool add_property(JSContext* cx, JS::HandleObject obj,
+                             JS::HandleId id, JS::HandleValue value);
+    static bool resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id,
+                        bool* resolved);
+    static void finalize(JSFreeOp* fop, JSObject* obj);
+    static void trace(JSTracer* tracer, JSObject* obj);
+
+ protected:
+    void trace_impl(JSTracer* tracer);
+
+    /* JS property getters/setters */
+
+ public:
+    static bool prop_getter(JSContext* cx, unsigned argc, JS::Value* vp);
+    static bool field_getter(JSContext* cx, unsigned argc, JS::Value* vp);
+    static bool prop_setter(JSContext* cx, unsigned argc, JS::Value* vp);
+    static bool field_setter(JSContext* cx, unsigned argc, JS::Value* vp);
+
+    /* JS methods */
+
+    static bool connect(JSContext* cx, unsigned argc, JS::Value* vp);
+    static bool connect_after(JSContext* cx, unsigned argc, JS::Value* vp);
+    static bool emit(JSContext* cx, unsigned argc, JS::Value* vp);
+    static bool to_string(JSContext* cx, unsigned argc, JS::Value* vp);
+    static bool init(JSContext* cx, unsigned argc, JS::Value* vp);
+    static bool hook_up_vfunc(JSContext* cx, unsigned argc, JS::Value* vp);
+
+    /* Quarks */
+
+    static GQuark custom_type_quark(void);
+    static GQuark custom_property_quark(void);
+};
+
+class ObjectPrototype : public ObjectBase {
+    // ObjectBase needs to call private methods (such as trace_impl) because
+    // of the unusual inheritance scheme
+    friend class ObjectBase;
+
+    using PropertyCache =
+        JS::GCHashMap<JS::Heap<JSString*>, GjsAutoParam,
+                      js::DefaultHasher<JSString*>, js::SystemAllocPolicy>;
+    using FieldCache =
+        JS::GCHashMap<JS::Heap<JSString*>, GjsAutoInfo<GIFieldInfo>,
+                      js::DefaultHasher<JSString*>, js::SystemAllocPolicy>;
+
+    GIObjectInfo* m_info;
+    GType m_gtype;
+
+    PropertyCache m_property_cache;
+    FieldCache m_field_cache;
+
+    ObjectPrototype(GIObjectInfo* info, GType gtype);
+    ~ObjectPrototype();
+
+ public:
+    /* Public constructor for instances (uses GSlice allocator) */
+    static ObjectPrototype* new_for_js_object(GIObjectInfo* info, GType gtype);
+
+    static ObjectPrototype* for_js(JSContext* cx, JS::HandleObject obj) {
+        return ObjectBase::for_js(cx, obj)->to_prototype();
+    }
+    static ObjectPrototype* for_gtype(GType gtype);
+    static ObjectPrototype* for_js_prototype(JSContext* cx,
+                                             JS::HandleObject obj);
+
+    /* Helper methods */
+ private:
+    bool is_vfunc_unchanged(GIVFuncInfo* info);
+    bool resolve_no_info(JSContext* cx, JS::HandleObject obj, bool* resolved,
+                         const char* name);
+
+ public:
+    void set_type_qdata(void);
+    GParamSpec* find_param_spec_from_id(JSContext* cx, JS::HandleString key);
+    GIFieldInfo* find_field_info_from_id(JSContext* cx, JS::HandleString key);
+    bool props_to_g_parameters(JSContext* cx, const JS::HandleValueArray& args,
+                               std::vector<const char*>* names,
+                               AutoGValueVector* values);
+
+    /* These are currently only needed in the GObject base init and finalize
+     * functions, for prototypes, even though m_closures is in ObjectBase. */
+    void ref_closures(void) {
+        for (GClosure* closure : m_closures)
+            g_closure_ref(closure);
+    }
+    void unref_closures(void) {
+        for (GClosure* closure : m_closures)
+            g_closure_unref(closure);
+    }
+
+    /* JSClass operations */
+ private:
+    bool resolve_impl(JSContext* cx, JS::HandleObject obj, JS::HandleId id,
+                      bool* resolved);
+    void trace_impl(JSTracer* tracer);
+
+    /* JS methods */
+ private:
+    bool to_string_impl(JSContext* cx, const JS::CallArgs& args);
+    bool hook_up_vfunc_impl(JSContext* cx, const JS::CallArgs& args,
+                            JS::HandleObject prototype);
+
+    ObjectPrototype(const ObjectPrototype& other) = delete;
+    ObjectPrototype(ObjectPrototype&& other) = delete;
+    ObjectPrototype& operator=(const ObjectPrototype& other) = delete;
+    ObjectPrototype& operator=(ObjectPrototype&& other) = delete;
+};
+
+class ObjectInstance : public ObjectBase {
+    // ObjectBase needs to call private methods (such as trace_impl) because
+    // of the unusual inheritance scheme
+    friend class ObjectBase;
+
+    GObject* m_gobj;  // may be null
+    GjsMaybeOwned<JSObject*> m_wrapper;
 
     GjsListLink m_instance_link;
 
@@ -88,57 +335,25 @@ class ObjectInstance {
  public:
     static std::stack<JS::PersistentRootedObject> object_init_list;
 
-    /* Static methods to get an existing ObjectInstance */
-
- private:
-    static ObjectInstance* for_js_prototype(JSContext* cx,
-                                            JS::HandleObject obj);
-
- public:
-    static ObjectInstance* for_gobject(GObject* gobj);
-    static ObjectInstance* for_js(JSContext* cx, JS::HandleObject obj);
-    static ObjectInstance* for_js_nocheck(JSObject* obj);
-    static ObjectInstance* for_gtype(GType gtype);  // Prototype-only
-
     /* Constructors */
 
  private:
-    /* Constructor for instances */
     ObjectInstance(JSContext* cx, JS::HandleObject obj);
+    ~ObjectInstance();
 
  public:
     /* Public constructor for instances (uses GSlice allocator) */
     static ObjectInstance* new_for_js_object(JSContext* cx,
                                              JS::HandleObject obj);
 
-    /* Constructor for prototypes (only called from gjs_define_object_class) */
-    ObjectInstance(JSObject* prototype, GIObjectInfo* info, GType gtype);
+    static ObjectInstance* for_gobject(GObject* gobj);
 
     /* Accessors */
 
  private:
-    bool is_prototype(void) const { return !m_gobj; }
-    bool is_custom_js_class(void) const { return !m_info; }
     bool has_wrapper(void) const { return !!m_wrapper; }
-    const char* ns(void) const {
-        return m_info ? g_base_info_get_namespace(m_info) : "";
-    }
-    const char* name(void) const {
-        return m_info ? g_base_info_get_name(m_info) : type_name();
-    }
-    const char* type_name(void) const { return g_type_name(m_gtype); }
-
-    using PropertyCache =
-        JS::GCHashMap<JS::Heap<JSString*>, GjsAutoParam,
-                      js::DefaultHasher<JSString*>, js::SystemAllocPolicy>;
-    using FieldCache =
-        JS::GCHashMap<JS::Heap<JSString*>, GjsAutoInfo<GIFieldInfo>,
-                      js::DefaultHasher<JSString*>, js::SystemAllocPolicy>;
-    PropertyCache* get_property_cache(void);  // Prototype-only
-    FieldCache* get_field_cache(void);        // Prototype-only
 
  public:
-    GType gtype(void) const { return m_gtype; }
     GObject* gobj(void) const { return m_gobj; }
     JSObject* wrapper(void) const { return m_wrapper; }
 
@@ -160,55 +375,7 @@ class ObjectInstance {
     void toggle_down(void);
     void toggle_up(void);
 
-    /* Methods to manipulate the list of closures */
-
- private:
-    void invalidate_all_closures(void);
-
- public:
-    void associate_closure(JSContext* cx, GClosure* closure);
-    void ref_closures(void) {
-        for (GClosure* closure : m_closures)
-            g_closure_ref(closure);
-    }
-    void unref_closures(void) {
-        for (GClosure* closure : m_closures)
-            g_closure_unref(closure);
-    }
-
-    /* Helper methods for both prototypes and instances */
-
- private:
-    bool check_is_instance(JSContext* cx, const char* for_what) const;
-    void debug_lifecycle(const char* message) const {
-        gjs_debug_lifecycle(
-            GJS_DEBUG_GOBJECT, "[%p: GObject %p JS wrapper %p %s.%s (%s)] %s",
-            this, m_gobj, m_wrapper.get(), ns(), name(), type_name(), message);
-    }
-    void debug_jsprop_base(const char* message, const char* id,
-                           JSObject* obj) const {
-        gjs_debug_jsprop(GJS_DEBUG_GOBJECT,
-                         "[%p: GObject %p JS object %p %s.%s (%s)] %s '%s'",
-                         this, m_gobj, obj, ns(), name(), type_name(), message,
-                         id);
-    }
-    void debug_jsprop(const char* message, jsid id, JSObject* obj) const {
-        debug_jsprop_base(message, gjs_debug_id(id).c_str(), obj);
-    }
-    void debug_jsprop(const char* message, JSString* id, JSObject* obj) const {
-        debug_jsprop_base(message, gjs_debug_string(id).c_str(), obj);
-    }
-    static void debug_jsprop_static(const char* message, jsid id,
-                                    JSObject* obj) {
-        gjs_debug_jsprop(GJS_DEBUG_GOBJECT,
-                         "[JS object %p] %s '%s', no instance associated", obj,
-                         message, gjs_debug_id(id).c_str());
-    }
-
- public:
-    void type_query_dynamic_safe(GTypeQuery* query);
-
-    /* Instance-only helper methods */
+    /* Helper methods */
 
  private:
     void set_object_qdata(void);
@@ -219,21 +386,6 @@ class ObjectInstance {
     void ensure_uses_toggle_ref(JSContext* cx);
     bool check_gobject_disposed(const char* for_what) const;
 
-    /* Prototype-only helper methods */
-
- private:
-    GParamSpec* find_param_spec_from_id(JSContext* cx, JS::HandleString key);
-    GIFieldInfo* find_field_info_from_id(JSContext* cx, JS::HandleString key);
-    bool props_to_g_parameters(JSContext* cx, const JS::HandleValueArray& args,
-                               std::vector<const char*>* names,
-                               AutoGValueVector* values);
-    bool is_vfunc_unchanged(GIVFuncInfo* info);
-    bool resolve_no_info(JSContext* cx, JS::HandleObject obj, bool* resolved,
-                         const char* name);
-
- public:
-    void set_type_qdata(void);
-
     /* Methods to manipulate the linked list of instances */
 
  private:
@@ -259,19 +411,8 @@ class ObjectInstance {
  private:
     bool add_property_impl(JSContext* cx, JS::HandleObject obj, JS::HandleId id,
                            JS::HandleValue value);
-    bool resolve_impl(JSContext* cx, JS::HandleObject obj, JS::HandleId id,
-                      bool* resolved);
-    void trace_impl(JSTracer* tracer);
     void finalize_impl(JSFreeOp* fop, JSObject* obj);
 
- public:
-    static bool add_property(JSContext* cx, JS::HandleObject obj,
-                             JS::HandleId id, JS::HandleValue value);
-    static bool resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id,
-                        bool* resolved);
-    static void finalize(JSFreeOp* fop, JSObject* obj);
-    static void trace(JSTracer* tracer, JSObject* obj);
-
     /* JS property getters/setters */
 
  private:
@@ -283,10 +424,6 @@ class ObjectInstance {
                           JS::HandleString name, JS::HandleValue value);
     bool field_setter_impl(JSContext* cx, JS::HandleObject obj,
                            JS::HandleString name, JS::HandleValue value);
-    static bool prop_getter(JSContext* cx, unsigned argc, JS::Value* vp);
-    static bool field_getter(JSContext* cx, unsigned argc, JS::Value* vp);
-    static bool prop_setter(JSContext* cx, unsigned argc, JS::Value* vp);
-    static bool field_setter(JSContext* cx, unsigned argc, JS::Value* vp);
 
     /* JS methods */
 
@@ -296,15 +433,6 @@ class ObjectInstance {
     bool to_string_impl(JSContext* cx, const JS::CallArgs& args);
     bool init_impl(JSContext* cx, const JS::CallArgs& args,
                    JS::MutableHandleObject obj);
-    bool hook_up_vfunc_impl(JSContext* cx, const JS::CallArgs& args);
-
- public:
-    static bool connect(JSContext* cx, unsigned argc, JS::Value* vp);
-    static bool connect_after(JSContext* cx, unsigned argc, JS::Value* vp);
-    static bool emit(JSContext* cx, unsigned argc, JS::Value* vp);
-    static bool to_string(JSContext* cx, unsigned argc, JS::Value* vp);
-    static bool init(JSContext* cx, unsigned argc, JS::Value* vp);
-    static bool hook_up_vfunc(JSContext* cx, unsigned argc, JS::Value* vp);
 
     /* Methods connected to "public" API */
  private:
@@ -318,13 +446,11 @@ class ObjectInstance {
 
     void gobj_dispose_notify(void);
     void context_dispose_notify(void);
-    static void closure_invalidated_notify(void* data, GClosure* closure) {
-        static_cast<ObjectInstance*>(data)->m_closures.erase(closure);
-    }
 
-    /* Quarks */
-    static GQuark custom_type_quark(void);
-    static GQuark custom_property_quark(void);
+    ObjectInstance(const ObjectInstance& other) = delete;
+    ObjectInstance(ObjectInstance&& other) = delete;
+    ObjectInstance& operator=(const ObjectInstance& other) = delete;
+    ObjectInstance& operator=(ObjectInstance&& other) = delete;
 };
 
 G_BEGIN_DECLS
diff --git a/gi/private.cpp b/gi/private.cpp
index 8f266c32..1dc32911 100644
--- a/gi/private.cpp
+++ b/gi/private.cpp
@@ -76,7 +76,7 @@ static bool gjs_override_property(JSContext* cx, unsigned argc, JS::Value* vp) {
 
     GjsAutoParam new_pspec = g_param_spec_override(name, pspec);
 
-    g_param_spec_set_qdata(new_pspec, ObjectInstance::custom_property_quark(),
+    g_param_spec_set_qdata(new_pspec, ObjectBase::custom_property_quark(),
                            GINT_TO_POINTER(1));
 
     args.rval().setObject(*gjs_param_from_g_param(cx, new_pspec.get()));
@@ -205,7 +205,7 @@ static bool gjs_register_interface(JSContext* cx, unsigned argc,
     GType interface_type = g_type_register_static(G_TYPE_INTERFACE, name,
         &type_info, GTypeFlags(0));
 
-    g_type_set_qdata(interface_type, ObjectInstance::custom_type_quark(),
+    g_type_set_qdata(interface_type, ObjectBase::custom_type_quark(),
                      GINT_TO_POINTER(1));
 
     if (!save_properties_for_class_init(cx, properties, n_properties,
@@ -271,7 +271,7 @@ static bool gjs_register_type(JSContext* cx, unsigned argc, JS::Value* vp) {
         return false;
     }
 
-    auto* parent_priv = ObjectInstance::for_js(cx, parent);
+    auto* parent_priv = ObjectPrototype::for_js(cx, parent);
     /* We checked parent above, in gjs_typecheck_is_object() */
     g_assert(parent_priv);
 
@@ -290,7 +290,7 @@ static bool gjs_register_type(JSContext* cx, unsigned argc, JS::Value* vp) {
     GType instance_type = g_type_register_static(parent_priv->gtype(), name,
                                                  &type_info, GTypeFlags(0));
 
-    g_type_set_qdata(instance_type, ObjectInstance::custom_type_quark(),
+    g_type_set_qdata(instance_type, ObjectBase::custom_type_quark(),
                      GINT_TO_POINTER(1));
 
     if (!save_properties_for_class_init(cx, properties, n_properties,
@@ -306,7 +306,7 @@ static bool gjs_register_type(JSContext* cx, unsigned argc, JS::Value* vp) {
     gjs_define_object_class(cx, module, nullptr, instance_type, &constructor,
                             &prototype);
 
-    ObjectInstance* priv = ObjectInstance::for_js(cx, prototype);
+    auto* priv = ObjectPrototype::for_js(cx, prototype);
     priv->set_type_qdata();
 
     argv.rval().setObject(*constructor);
diff --git a/gjs/mem.cpp b/gjs/mem.cpp
index 3fff516d..6ce2eb8a 100644
--- a/gjs/mem.cpp
+++ b/gjs/mem.cpp
@@ -42,13 +42,16 @@ GJS_DEFINE_COUNTER(gerror)
 GJS_DEFINE_COUNTER(importer)
 GJS_DEFINE_COUNTER(interface)
 GJS_DEFINE_COUNTER(ns)
-GJS_DEFINE_COUNTER(object)
+GJS_DEFINE_COUNTER(object_instance)
+GJS_DEFINE_COUNTER(object_prototype)
 GJS_DEFINE_COUNTER(param)
 GJS_DEFINE_COUNTER(repo)
 
 #define GJS_LIST_COUNTER(name) \
     & gjs_counter_ ## name
 
+// clang-format off
+// otherwise these are put into 2 columns?!
 static GjsMemCounter* counters[] = {
     GJS_LIST_COUNTER(boxed),
     GJS_LIST_COUNTER(closure),
@@ -58,10 +61,12 @@ static GjsMemCounter* counters[] = {
     GJS_LIST_COUNTER(importer),
     GJS_LIST_COUNTER(interface),
     GJS_LIST_COUNTER(ns),
-    GJS_LIST_COUNTER(object),
+    GJS_LIST_COUNTER(object_instance),
+    GJS_LIST_COUNTER(object_prototype),
     GJS_LIST_COUNTER(param),
     GJS_LIST_COUNTER(repo),
 };
+// clang-format on
 
 void
 gjs_memory_report(const char *where,
diff --git a/gjs/mem.h b/gjs/mem.h
index f3683a31..62389e57 100644
--- a/gjs/mem.h
+++ b/gjs/mem.h
@@ -48,7 +48,8 @@ GJS_DECLARE_COUNTER(gerror)
 GJS_DECLARE_COUNTER(importer)
 GJS_DECLARE_COUNTER(interface)
 GJS_DECLARE_COUNTER(ns)
-GJS_DECLARE_COUNTER(object)
+GJS_DECLARE_COUNTER(object_instance)
+GJS_DECLARE_COUNTER(object_prototype)
 GJS_DECLARE_COUNTER(param)
 GJS_DECLARE_COUNTER(repo)
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]