[gjs: 1/2] object: Cache known unresolvable properties



commit e41aa04050374bba0d752160200b88aad7c18153
Author: Philip Chimento <philip chimento gmail com>
Date:   Fri Feb 14 22:12:12 2020 -0800

    object: Cache known unresolvable properties
    
    Based on a patch by Daniel van Vugt. This adds a negative cache to the
    resolve mechanism. Since on any particular GObject class's prototype we
    know whether a name is resolved or not, by consulting the introspection
    information, we can cache the name when it is not resolved, so that the
    resolve operation can quickly move on to the next parent class's
    prototype.
    
    The situation we are trying to avoid here, is looking up a property or
    method belonging to an ancestor class (e.g., GObject.prototype.notify)
    on an instance of a class that is many levels of inheritance deeper
    (e.g. some custom Gtk widget.) If you do myWidget.notify('foo'), then
    the name 'notify' will first be resolved on the instance (a no-op), then
    on the custom widget class prototype (with resolve_no_info), then on
    each parent (with resolve), consulting all implemented interfaces at
    each level, and finally resolve on GObject.prototype. Subsequent lookups
    will still walk through all the intermediate classes and interfaces
    every time, even though GObject.prototype now has the property. By using
    the negative cache, all the resolves on intermediate classes return
    early, and the interfaces are not consulted.
    
    I don't believe it's necessary to ever invalidate the cache, since new
    methods and properties cannot be added to introspection information at
    runtime.
    
    Closes: #302

 gi/object.cpp | 20 ++++++++++++++++++++
 gi/object.h   | 19 +++++++++++++++++++
 2 files changed, 39 insertions(+)
---
diff --git a/gi/object.cpp b/gi/object.cpp
index 3b1e87a7..195bb4a7 100644
--- a/gi/object.cpp
+++ b/gi/object.cpp
@@ -752,6 +752,25 @@ bool ObjectBase::id_is_never_lazy(jsid name, const GjsAtoms& atoms) {
 bool ObjectPrototype::resolve_impl(JSContext* context, JS::HandleObject obj,
                                    JS::HandleId id, const char* name,
                                    bool* resolved) {
+    if (m_unresolvable_cache.has(id)) {
+        *resolved = false;
+        return true;
+    }
+
+    if (!uncached_resolve(context, obj, id, name, resolved))
+        return false;
+
+    if (!*resolved && !m_unresolvable_cache.putNew(id)) {
+        JS_ReportOutOfMemory(context);
+        return false;
+    }
+
+    return true;
+}
+
+bool ObjectPrototype::uncached_resolve(JSContext* context, JS::HandleObject obj,
+                                       JS::HandleId id, const char* name,
+                                       bool* resolved) {
     /* If we have no GIRepository information (we're a JS GObject subclass),
      * we need to look at exposing interfaces. Look up our interfaces through
      * GType data, and then hope that *those* are introspectable. */
@@ -1578,6 +1597,7 @@ void ObjectBase::trace_impl(JSTracer* tracer) {
 void ObjectPrototype::trace_impl(JSTracer* tracer) {
     m_property_cache.trace(tracer);
     m_field_cache.trace(tracer);
+    m_unresolvable_cache.trace(tracer);
 }
 
 void ObjectInstance::finalize_impl(JSFreeOp* fop, JSObject* obj) {
diff --git a/gi/object.h b/gi/object.h
index 8995e437..29bc9932 100644
--- a/gi/object.h
+++ b/gi/object.h
@@ -211,6 +211,19 @@ class ObjectBase
     GJS_USE static GQuark custom_property_quark(void);
 };
 
+// See https://bugzilla.mozilla.org/show_bug.cgi?id=1614220
+struct IdHasher {
+    typedef jsid Lookup;
+    static mozilla::HashNumber hash(jsid id) {
+        if (MOZ_LIKELY(JSID_IS_ATOM(id)))
+            return js::DefaultHasher<JSAtom*>::hash(JSID_TO_ATOM(id));
+        if (JSID_IS_SYMBOL(id))
+            return js::DefaultHasher<JS::Symbol*>::hash(JSID_TO_SYMBOL(id));
+        return mozilla::HashGeneric(JSID_BITS(id));
+    }
+    static bool match(jsid id1, jsid id2) { return id1 == id2; }
+};
+
 class ObjectPrototype
     : public GIWrapperPrototype<ObjectBase, ObjectPrototype, ObjectInstance> {
     friend class GIWrapperPrototype<ObjectBase, ObjectPrototype,
@@ -223,9 +236,12 @@ class ObjectPrototype
     using FieldCache =
         JS::GCHashMap<JS::Heap<JSString*>, GjsAutoInfo<GI_INFO_TYPE_FIELD>,
                       js::DefaultHasher<JSString*>, js::SystemAllocPolicy>;
+    using NegativeLookupCache =
+        JS::GCHashSet<jsid, IdHasher, js::SystemAllocPolicy>;
 
     PropertyCache m_property_cache;
     FieldCache m_field_cache;
+    NegativeLookupCache m_unresolvable_cache;
 
     ObjectPrototype(GIObjectInfo* info, GType gtype);
     ~ObjectPrototype();
@@ -253,6 +269,9 @@ class ObjectPrototype
     bool resolve_no_info(JSContext* cx, JS::HandleObject obj, JS::HandleId id,
                          bool* resolved, const char* name,
                          ResolveWhat resolve_props);
+    GJS_JSAPI_RETURN_CONVENTION
+    bool uncached_resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id,
+                          const char* name, bool* resolved);
 
  public:
     void set_type_qdata(void);


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