[gjs/wip/gcampax/70-arg-cache: 33/38] function: introduce a new system for better caching of marshalling info



commit 315fa0cdf67e10fd55a1bf52cbf6bcca19674a2c
Author: Giovanni Campagna <gcampagna src gnome org>
Date:   Fri Apr 19 20:27:07 2013 +0200

    function: introduce a new system for better caching of marshalling info
    
    The marshalling metadata, as stored in the typelib, is not a format
    friendly to gjs, and requires to have multiple switches and a complex
    logic in the hot path of function calls.
    Instead, extend the current caching mechanism to allow arbitrary
    data to be attached to arguments, and to have it deal with JS arguments,
    rather than a mixture of C and GI arguments.
    
    (Philip Chimento: rebased and fixed coding style and bugs.)

 gi/arg-cache.cpp                        | 177 +++++++++++++++++++++++++++++++
 gi/arg-cache.h                          |  58 ++++++++++
 gi/function.cpp                         | 182 +++++++++++++++++---------------
 installed-tests/js/testGIMarshalling.js |   8 --
 meson.build                             |   1 +
 5 files changed, 331 insertions(+), 95 deletions(-)
---
diff --git a/gi/arg-cache.cpp b/gi/arg-cache.cpp
new file mode 100644
index 00000000..6a794b85
--- /dev/null
+++ b/gi/arg-cache.cpp
@@ -0,0 +1,177 @@
+/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (c) 2013 Giovanni Campagna <scampa giovanni gmail com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include <config.h>
+
+#include <string.h>
+
+#include <girepository.h>
+#include <glib.h>
+
+#include <js/TypeDecls.h>
+
+#include "gi/arg-cache.h"
+#include "gi/function.h"
+#include "gjs/jsapi-util.h"
+
+bool gjs_arg_cache_build_return(JSContext*, GjsArgumentCache* self,
+                                GjsParamType* param_types,
+                                GICallableInfo* callable,
+                                bool* inc_counter_out) {
+    g_assert(inc_counter_out && "forgot out parameter");
+
+    GITypeInfo return_type;
+    g_callable_info_load_return_type(callable, &return_type);
+
+    if (g_type_info_get_tag(&return_type) == GI_TYPE_TAG_VOID) {
+        *inc_counter_out = false;
+        self->param_type = PARAM_SKIPPED;
+        return true;
+    }
+
+    *inc_counter_out = true;
+    self->param_type = PARAM_NORMAL;
+
+    if (g_type_info_get_tag(&return_type) == GI_TYPE_TAG_ARRAY) {
+        int length_pos = g_type_info_get_array_length(&return_type);
+        if (length_pos >= 0) {
+            param_types[length_pos] = PARAM_SKIPPED;
+            return true;
+        }
+    }
+
+    return true;
+}
+
+bool gjs_arg_cache_build_arg(JSContext* cx, GjsArgumentCache* self,
+                             GjsParamType* param_types, int gi_index,
+                             GIDirection direction, GIArgInfo* arg,
+                             GICallableInfo* callable, bool* inc_counter_out) {
+    g_assert(inc_counter_out && "forgot out parameter");
+
+    GITypeInfo type_info;
+    g_arg_info_load_type(arg, &type_info);
+
+    self->param_type = PARAM_NORMAL;
+    *inc_counter_out = true;
+
+    GITypeTag type_tag = g_type_info_get_tag(&type_info);
+    if (type_tag == GI_TYPE_TAG_INTERFACE) {
+        GjsAutoBaseInfo interface_info = g_type_info_get_interface(&type_info);
+        if (interface_info.type() == GI_INFO_TYPE_CALLBACK) {
+            if (direction != GI_DIRECTION_IN) {
+                // Can't do callbacks for out or inout
+                gjs_throw(cx,
+                          "Function %s.%s has a callback out-argument %s, not "
+                          "supported",
+                          g_base_info_get_namespace(callable),
+                          g_base_info_get_name(callable),
+                          g_base_info_get_name(arg));
+                return false;
+            }
+
+            if (strcmp(interface_info.name(), "DestroyNotify") == 0 &&
+                strcmp(interface_info.ns(), "GLib") == 0) {
+                // We don't know (yet) what to do with GDestroyNotify appearing
+                // before a callback. If the callback comes later in the
+                // argument list, then PARAM_UNKNOWN will be overwritten with
+                // PARAM_SKIPPED. If no callback follows, then this is probably
+                // an unsupported function, so the value will remain
+                // PARAM_UNKNOWN.
+                self->param_type = PARAM_UNKNOWN;
+                *inc_counter_out = false;
+            } else {
+                self->param_type = PARAM_CALLBACK;
+
+                int destroy_pos = g_arg_info_get_destroy(arg);
+                int closure_pos = g_arg_info_get_closure(arg);
+
+                if (destroy_pos >= 0)
+                    param_types[destroy_pos] = PARAM_SKIPPED;
+
+                if (closure_pos >= 0)
+                    param_types[closure_pos] = PARAM_SKIPPED;
+
+                if (destroy_pos >= 0 && closure_pos < 0) {
+                    gjs_throw(cx,
+                              "Function %s.%s has a GDestroyNotify but no "
+                              "user_data, not supported",
+                              g_base_info_get_namespace(callable),
+                              g_base_info_get_name(callable));
+                    return false;
+                }
+            }
+        }
+    } else if (type_tag == GI_TYPE_TAG_ARRAY) {
+        if (g_type_info_get_array_type(&type_info) == GI_ARRAY_TYPE_C) {
+            int length_pos = g_type_info_get_array_length(&type_info);
+
+            if (length_pos >= 0) {
+                self->param_type = PARAM_ARRAY;
+                if (param_types[length_pos] != PARAM_SKIPPED) {
+                    param_types[length_pos] = PARAM_SKIPPED;
+                    if (length_pos < gi_index) {
+                        // we already collected length_pos, remove it
+                        *inc_counter_out = false;
+                    }
+                }
+            }
+        }
+    }
+
+    return true;
+}
+
+bool gjs_arg_cache_build_inout_arg(JSContext*, GjsArgumentCache* in_self,
+                                   GjsArgumentCache* out_self,
+                                   GjsParamType* param_types, int gi_index,
+                                   GIArgInfo* arg, bool* inc_counter_out) {
+    g_assert(inc_counter_out && "forgot out parameter");
+
+    GITypeInfo type_info;
+    g_arg_info_load_type(arg, &type_info);
+
+    in_self->param_type = PARAM_NORMAL;
+    out_self->param_type = PARAM_NORMAL;
+    *inc_counter_out = true;
+
+    GITypeTag type_tag = g_type_info_get_tag(&type_info);
+    if (type_tag == GI_TYPE_TAG_ARRAY) {
+        if (g_type_info_get_array_type(&type_info) == GI_ARRAY_TYPE_C) {
+            int length_pos = g_type_info_get_array_length(&type_info);
+
+            if (length_pos >= 0) {
+                param_types[length_pos] = PARAM_SKIPPED;
+                in_self->param_type = PARAM_ARRAY;
+                out_self->param_type = PARAM_ARRAY;
+
+                if (length_pos < gi_index) {
+                    // we already collected length_pos, remove it
+                    *inc_counter_out = false;
+                }
+            }
+        }
+    }
+
+    return true;
+}
diff --git a/gi/arg-cache.h b/gi/arg-cache.h
new file mode 100644
index 00000000..e3ddb7dd
--- /dev/null
+++ b/gi/arg-cache.h
@@ -0,0 +1,58 @@
+/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (c) 2013 Giovanni Campagna <scampa giovanni gmail com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#ifndef GI_ARG_CACHE_H_
+#define GI_ARG_CACHE_H_
+
+#include <config.h>
+
+#include <girepository.h>
+
+#include <js/TypeDecls.h>
+
+#include "gi/function.h"
+#include "gjs/macros.h"
+
+typedef struct _GjsArgumentCache {
+    // For compatibility
+    GjsParamType param_type;
+} GjsArgumentCache;
+
+GJS_JSAPI_RETURN_CONVENTION
+bool gjs_arg_cache_build_arg(JSContext* cx, GjsArgumentCache* self,
+                             GjsParamType* param_types, int gi_index,
+                             GIDirection direction, GIArgInfo* arg,
+                             GICallableInfo* callable, bool* inc_counter_out);
+
+GJS_JSAPI_RETURN_CONVENTION
+bool gjs_arg_cache_build_inout_arg(JSContext* cx, GjsArgumentCache* in_self,
+                                   GjsArgumentCache* out_self,
+                                   GjsParamType* param_types, int gi_index,
+                                   GIArgInfo* arg, bool* inc_counter_out);
+
+GJS_JSAPI_RETURN_CONVENTION
+bool gjs_arg_cache_build_return(JSContext* cx, GjsArgumentCache* self,
+                                GjsParamType* param_types, GICallableInfo* info,
+                                bool* inc_counter_out);
+
+#endif  // GI_ARG_CACHE_H_
diff --git a/gi/function.cpp b/gi/function.cpp
index 19594529..c6a196b7 100644
--- a/gi/function.cpp
+++ b/gi/function.cpp
@@ -50,6 +50,7 @@
 #include <jsfriendapi.h>  // for JS_GetObjectFunction
 #include <jspubtd.h>      // for JSProto_TypeError, JSTYPE_FUNCTION
 
+#include "gi/arg-cache.h"
 #include "gi/arg.h"
 #include "gi/boxed.h"
 #include "gi/closure.h"
@@ -76,8 +77,10 @@ typedef struct {
     GICallableInfo* info;
 
     GjsParamType *param_types;
+    GjsArgumentCache* in_arguments;
+    GjsArgumentCache* out_arguments;
 
-    guint8 expected_js_argc;
+    uint8_t js_in_argc;
     guint8 js_out_argc;
     GIFunctionInvoker invoker;
 } Function;
@@ -828,20 +831,19 @@ static bool gjs_invoke_c_function(JSContext* context, Function* function,
     /* @c_argc is the number of arguments that the underlying C
      * function takes. @gi_argc is the number of arguments the
      * GICallableInfo describes (which does not include "this" or
-     * GError**). @function->expected_js_argc is the number of
+     * GError**). @function->js_in_argc is the number of
      * arguments we expect the JS function to take (which does not
      * include PARAM_SKIPPED args).
      *
      * @args.length() is the number of arguments that were actually passed.
      */
     GjsAutoChar name = format_function_name(function);
-    if (args.length() > function->expected_js_argc) {
+    if (args.length() > function->js_in_argc) {
         if (!JS::WarnUTF8(
                 context, "Too many arguments to %s: expected %d, got %u",
-                name.get(), function->expected_js_argc, args.length()))
+                name.get(), function->js_in_argc, args.length()))
             return false;
-    } else if (!args.requireAtLeast(context, name,
-                                    function->expected_js_argc)) {
+    } else if (!args.requireAtLeast(context, name, function->js_in_argc)) {
         return false;
     }
 
@@ -1612,9 +1614,7 @@ init_cached_function_data (JSContext      *context,
                            GICallableInfo *info)
 {
     guint8 i, n_args;
-    int array_length_pos;
     GError *error = NULL;
-    GITypeInfo return_type;
     GIInfoType info_type;
 
     info_type = g_base_info_get_type((GIBaseInfo *)info);
@@ -1644,110 +1644,118 @@ init_cached_function_data (JSContext      *context,
         }
     }
 
-    g_callable_info_load_return_type((GICallableInfo*)info, &return_type);
-    if (g_type_info_get_tag(&return_type) != GI_TYPE_TAG_VOID)
-        function->js_out_argc += 1;
-
     n_args = g_callable_info_get_n_args((GICallableInfo*) info);
+
+    // We preallocate structures conservatively, then we copy in dynamic memory
+    // only those we need.
+    // Ideally, there would be a 1:1 mapping between GjsArgumentCache and JS
+    // arguments, but we need to handle the case of a length argument happening
+    // before its corresponding array, so we allow for "holes"
+    // (GjsArgumentCache whose param_type is PARAM_SKIPPED), and in_args maps
+    // to GI arguments, while out_args maps to GI arguments and the return
+    // value. To ease handling, out_args is actually one inside the array (so
+    // out_args[-1] is the return value).
+    // In any case, there is a 1:1 mapping between GI arguments and
+    // function->param_types.
+    GjsArgumentCache* in_args = g_newa(GjsArgumentCache, n_args);
+    GjsArgumentCache* out_args = g_newa(GjsArgumentCache, n_args + 1) + 1;
+    memset(in_args, 0, sizeof(GjsArgumentCache) * n_args);
+    memset(out_args - 1, 0, sizeof(GjsArgumentCache) * (n_args + 1));
     function->param_types = g_new0(GjsParamType, n_args);
 
-    array_length_pos = g_type_info_get_array_length(&return_type);
-    if (array_length_pos >= 0 && array_length_pos < n_args)
-        function->param_types[array_length_pos] = PARAM_SKIPPED;
+    bool inc_counter;
+    if (!gjs_arg_cache_build_return(context, &out_args[-1],
+                                    function->param_types, info,
+                                    &inc_counter)) {
+        g_free(function->param_types);
+        return false;
+    }
+    int out_argc = inc_counter ? 1 : 0;
+    int in_argc = 0;
 
     for (i = 0; i < n_args; i++) {
         GIDirection direction;
         GIArgInfo arg_info;
-        GITypeInfo type_info;
-        int destroy = -1;
-        int closure = -1;
-        GITypeTag type_tag;
 
         if (function->param_types[i] == PARAM_SKIPPED)
             continue;
 
         g_callable_info_load_arg((GICallableInfo*) info, i, &arg_info);
-        g_arg_info_load_type(&arg_info, &type_info);
-
         direction = g_arg_info_get_direction(&arg_info);
-        type_tag = g_type_info_get_tag(&type_info);
 
-        if (type_tag == GI_TYPE_TAG_INTERFACE) {
-            GIBaseInfo* interface_info;
-            GIInfoType interface_type;
+        if (direction == GI_DIRECTION_IN) {
+            if (!gjs_arg_cache_build_arg(
+                    context, &in_args[i], function->param_types, i,
+                    GI_DIRECTION_IN, &arg_info, info, &inc_counter)) {
+                g_free(function->param_types);
+                return false;
+            }
 
-            interface_info = g_type_info_get_interface(&type_info);
-            interface_type = g_base_info_get_type(interface_info);
-            if (interface_type == GI_INFO_TYPE_CALLBACK) {
-                if (strcmp(g_base_info_get_name(interface_info), "DestroyNotify") == 0 &&
-                    strcmp(g_base_info_get_namespace(interface_info), "GLib") == 0) {
-                    // We don't know (yet) what to do with GDestroyNotify
-                    // appearing before a callback. If the callback comes later
-                    // in the argument list, then PARAM_UNKNOWN will be
-                    // overwritten with PARAM_SKIPPED. If no callback follows,
-                    // then this is probably an unsupported function, so the
-                    // value will remain PARAM_UNKNOWN.
-                    function->param_types[i] = PARAM_UNKNOWN;
-                } else {
-                    function->param_types[i] = PARAM_CALLBACK;
-                    function->expected_js_argc += 1;
+            function->param_types[i] = in_args[i].param_type;
+        } else if (direction == GI_DIRECTION_INOUT) {
+            if (!gjs_arg_cache_build_inout_arg(
+                    context, &in_args[i], &out_args[i], function->param_types,
+                    i, &arg_info, &inc_counter)) {
+                g_free(function->param_types);
+                return false;
+            }
 
-                    destroy = g_arg_info_get_destroy(&arg_info);
-                    closure = g_arg_info_get_closure(&arg_info);
+            function->param_types[i] = in_args[i].param_type;
+        } else {  // GI_DIRECTION_OUT
+            if (out_args[i].param_type == PARAM_SKIPPED)
+                continue;
 
-                    if (destroy >= 0 && destroy < n_args)
-                        function->param_types[destroy] = PARAM_SKIPPED;
+            if (!gjs_arg_cache_build_arg(
+                    context, &out_args[i], function->param_types, i,
+                    GI_DIRECTION_OUT, &arg_info, info, &inc_counter)) {
+                g_free(function->param_types);
+                return false;
+            }
 
-                    if (closure >= 0 && closure < n_args)
-                        function->param_types[closure] = PARAM_SKIPPED;
+            function->param_types[i] = out_args[i].param_type;
+        }
 
-                    if (destroy >= 0 && closure < 0) {
-                        gjs_throw(context, "Function %s.%s has a GDestroyNotify but no user_data, not 
supported",
-                                  g_base_info_get_namespace( (GIBaseInfo*) info),
-                                  g_base_info_get_name( (GIBaseInfo*) info));
-                        g_base_info_unref(interface_info);
-                        g_free(function->param_types);
-                        return false;
-                    }
-                }
+        if (inc_counter) {
+            if (direction == GI_DIRECTION_IN) {
+                in_argc++;
+            } else if (direction == GI_DIRECTION_INOUT) {
+                in_argc++;
+                out_argc++;
+            } else {  // GI_DIRECTION_OUT
+                out_argc++;
             }
-            g_base_info_unref(interface_info);
-        } else if (type_tag == GI_TYPE_TAG_ARRAY) {
-            if (g_type_info_get_array_type(&type_info) == GI_ARRAY_TYPE_C) {
-                array_length_pos = g_type_info_get_array_length(&type_info);
+        }
+    }
 
-                if (array_length_pos >= 0 && array_length_pos < n_args) {
-                    GIArgInfo length_arg_info;
+    function->in_arguments = g_new(GjsArgumentCache, in_argc);
+    function->out_arguments = g_new(GjsArgumentCache, out_argc);
 
-                    g_callable_info_load_arg((GICallableInfo*) info, array_length_pos, &length_arg_info);
-                    if (g_arg_info_get_direction(&length_arg_info) != direction) {
-                        gjs_throw(context, "Function %s.%s has an array with different-direction length arg, 
not supported",
-                                  g_base_info_get_namespace( (GIBaseInfo*) info),
-                                  g_base_info_get_name( (GIBaseInfo*) info));
-                        g_free(function->param_types);
-                        return false;
-                    }
+    function->js_in_argc = function->js_out_argc = 0;
+    if (out_args[-1].param_type != PARAM_SKIPPED) {
+        function->out_arguments[0] = out_args[-1];
+        function->js_out_argc = 1;
+    }
 
-                    function->param_types[array_length_pos] = PARAM_SKIPPED;
-                    function->param_types[i] = PARAM_ARRAY;
+    for (i = 0; i < n_args; i++) {
+        if (function->param_types[i] == PARAM_SKIPPED ||
+            function->param_types[i] == PARAM_UNKNOWN)
+            continue;
 
-                    if (array_length_pos < i) {
-                        /* we already collected array_length_pos, remove it */
-                        if (direction == GI_DIRECTION_IN || direction == GI_DIRECTION_INOUT)
-                            function->expected_js_argc -= 1;
-                        if (direction == GI_DIRECTION_OUT || direction == GI_DIRECTION_INOUT)
-                            function->js_out_argc -= 1;
-                    }
-                }
-            }
-        }
+        GIArgInfo arg_info;
+        g_callable_info_load_arg(info, i, &arg_info);
+        GIDirection direction = g_arg_info_get_direction(&arg_info);
 
-        if (function->param_types[i] == PARAM_NORMAL ||
-            function->param_types[i] == PARAM_ARRAY) {
-            if (direction == GI_DIRECTION_IN || direction == GI_DIRECTION_INOUT)
-                function->expected_js_argc += 1;
-            if (direction == GI_DIRECTION_OUT || direction == GI_DIRECTION_INOUT)
-                function->js_out_argc += 1;
+        if (direction == GI_DIRECTION_IN || direction == GI_DIRECTION_INOUT) {
+            g_assert(function->js_in_argc < in_argc &&
+                     "Mismatch in in-argument count");
+            function->in_arguments[function->js_in_argc] = in_args[i];
+            function->js_in_argc++;
+        }
+        if (direction == GI_DIRECTION_OUT || direction == GI_DIRECTION_INOUT) {
+            g_assert(function->js_out_argc < out_argc &&
+                     "Mismatch in out-argument count");
+            function->out_arguments[function->js_out_argc] = out_args[i];
+            function->js_out_argc++;
         }
     }
 
diff --git a/installed-tests/js/testGIMarshalling.js b/installed-tests/js/testGIMarshalling.js
index 1f772db3..6001e316 100644
--- a/installed-tests/js/testGIMarshalling.js
+++ b/installed-tests/js/testGIMarshalling.js
@@ -422,15 +422,7 @@ describe('C array with length', function () {
     it('marshals two arrays with the same length parameter', function () {
         const keys = ['one', 'two', 'three'];
         const values = [1, 2, 3];
-
-        // Intercept message; see https://gitlab.gnome.org/GNOME/gjs/issues/267
-        GLib.test_expect_message('Gjs', GLib.LogLevelFlags.LEVEL_MESSAGE,
-            '*Too many arguments*');
-
         expect(() => GIMarshallingTests.multi_array_key_value_in(keys, values)).not.toThrow();
-
-        GLib.test_assert_expected_messages_internal('Gjs',
-            'testGIMarshalling.js', 0, 'Ignore message');
     });
 
     // Run twice to ensure that copies are correctly made for (transfer full)
diff --git a/meson.build b/meson.build
index 574ae1b1..9cf69167 100644
--- a/meson.build
+++ b/meson.build
@@ -350,6 +350,7 @@ gjs_public_headers = [
 
 libgjs_sources = [
     'gi/arg.cpp', 'gi/arg.h',
+    'gi/arg-cache.cpp', 'gi/arg-cache.h',
     'gi/boxed.cpp', 'gi/boxed.h',
     'gi/closure.cpp', 'gi/closure.h',
     'gi/enumeration.cpp', 'gi/enumeration.h',


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