[gjs/wip/gcampax/70-arg-cache: 1/5] FIXME arg-cache: make actual use of the argument cache



commit 34a826ada6a8697a8fb6bcf27b1b92e391736222
Author: Giovanni Campagna <gcampagna src gnome org>
Date:   Sun Apr 21 17:01:59 2013 +0200

    FIXME arg-cache: make actual use of the argument cache
    
    Move handling of very special arguments (arrays, callback, caller
    allocates) to the argcache virtual functions, and clean up
    function invocations.
    We now have three straight loops: marshal_in, marshal_out and
    release. Some pointer arithmetics is introduced to ensure that
    all indexes line up correctly.
    
    FIXME: js_arg_pos can overrun js_out_argc sometimes when marshalling out
    
    (Philip Chimento: rebased and fixed coding style.)

 gi/arg-cache.cpp | 716 ++++++++++++++++++++++++++++++++++++++++++++--------
 gi/arg-cache.h   |  79 ++++--
 gi/arg.cpp       |  54 +---
 gi/arg.h         |  10 +-
 gi/function.cpp  | 758 +++++++++++--------------------------------------------
 gi/function.h    |   6 +
 6 files changed, 840 insertions(+), 783 deletions(-)
---
diff --git a/gi/arg-cache.cpp b/gi/arg-cache.cpp
index 12d63e2..b515b58 100644
--- a/gi/arg-cache.cpp
+++ b/gi/arg-cache.cpp
@@ -32,164 +32,674 @@
 
 #include "arg.h"
 #include "arg-cache.h"
-#include "boxed.h"
-#include "closure.h"
 #include "function.h"
-#include "gerror.h"
-#include "object.h"
-#include "union.h"
-#include "util/log.h"
+
+/* The global entry point for any invocations of GDestroyNotify;
+ * look up the callback through the user_data and then free it.
+ */
+static void
+gjs_destroy_notify_callback(void *data)
+{
+    auto trampoline = static_cast<GjsCallbackTrampoline *>(data);
+
+    g_assert(trampoline);
+    gjs_callback_trampoline_unref(trampoline);
+}
+
+static unsigned long
+gjs_g_argument_get_ulong(GITypeTag   tag,
+                         GIArgument *arg)
+{
+    if (tag == GI_TYPE_TAG_INT8)
+        return arg->v_int8;
+    if (tag == GI_TYPE_TAG_UINT8)
+        return arg->v_uint8;
+    if (tag == GI_TYPE_TAG_INT16)
+        return arg->v_int16;
+    if (tag == GI_TYPE_TAG_UINT16)
+        return arg->v_uint16;
+    if (tag == GI_TYPE_TAG_INT32)
+        return arg->v_int32;
+    if (tag == GI_TYPE_TAG_UINT32)
+        return arg->v_uint32;
+    if (tag == GI_TYPE_TAG_INT64)
+        return arg->v_int64;
+    if (tag == GI_TYPE_TAG_UINT64)
+        return arg->v_uint64;
+    g_assert_not_reached();
+}
+
+static void
+gjs_g_argument_set_ulong(GITypeTag     tag,
+                         GIArgument   *arg,
+                         unsigned long value)
+{
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+    /* In a little endian system, the first byte of an unsigned long value is
+     * the same value, downcasted to uint8, and no code is needed. Also, we
+     * ignore the sign, as we're just moving bits here. */
+    arg->v_ulong = value;
+#else
+    switch(tag) {
+    case GI_TYPE_TAG_INT8:
+        arg->v_int8 = value;
+        break;
+    case GI_TYPE_TAG_UINT8:
+        arg->v_uint8 = value;
+        break;
+    case GI_TYPE_TAG_INT16:
+        arg->v_int16 = value;
+        break;
+    case GI_TYPE_TAG_UINT16:
+        arg->v_uint16 = value;
+        break;
+    case GI_TYPE_TAG_INT32:
+        arg->v_int32 = value;
+        break;
+    case GI_TYPE_TAG_UINT32:
+        arg->v_uint32 = value;
+        break;
+    case GI_TYPE_TAG_INT64:
+        arg->v_int64 = value;
+        break;
+    case GI_TYPE_TAG_UINT64:
+        arg->v_uint64 = value;
+        break;
+    }
+#endif
+}
+
+/*
+ * Marshallers:
+ *
+ * Each argument, irrespective of the direction, is processed in three phases:
+ * - before calling the C function [in]
+ * - after calling it, when converting the return value and out arguments [out]
+ * - at the end of the invocation, to release any allocated memory [release]
+ *
+ * The convention on the names is thus
+ * gjs_marshal_[argument type]_[direction]_[phase].
+ * Some types don't have direction (for example, caller_allocates is only out,
+ * and callback is only in), in which case it is implied.
+ */
+
+static bool
+gjs_marshal_skipped_in(JSContext            *cx,
+                       GjsArgumentCache     *self,
+                       GjsFunctionCallState *state,
+                       GIArgument           *arg,
+                       JS::HandleValue       value)
+{
+    return true;
+}
+
+static bool
+gjs_marshal_generic_in_in(JSContext            *cx,
+                          GjsArgumentCache     *self,
+                          GjsFunctionCallState *state,
+                          GIArgument           *arg,
+                          JS::HandleValue       value)
+{
+    return gjs_value_to_g_argument(cx, value, &self->type_info, self->arg_name,
+                                   self->is_return ? GJS_ARGUMENT_RETURN_VALUE :
+                                                     GJS_ARGUMENT_ARGUMENT,
+                                   self->transfer, self->nullable, arg);
+}
+
+static bool
+gjs_marshal_generic_inout_in(JSContext            *cx,
+                             GjsArgumentCache     *self,
+                             GjsFunctionCallState *state,
+                             GIArgument           *arg,
+                             JS::HandleValue       value)
+{
+    if (!gjs_marshal_generic_in_in(cx, self, state, arg, value))
+        return false;
+
+    int ix = self->arg_pos;
+    state->out_cvalues[ix] = state->inout_original_cvalues[ix] = *arg;
+    arg->v_pointer = &(state->out_cvalues[ix]);
+    return true;
+}
+
+static bool
+gjs_marshal_explicit_array_in_in(JSContext            *cx,
+                                 GjsArgumentCache     *self,
+                                 GjsFunctionCallState *state,
+                                 GArgument            *arg,
+                                 JS::HandleValue       value)
+{
+    void *data;
+    size_t length;
+
+    if (!gjs_array_to_explicit_array(cx, value, &self->type_info,
+                                     self->arg_name, GJS_ARGUMENT_ARGUMENT,
+                                     self->transfer, self->nullable,
+                                     &data, &length))
+        return false;
+
+    int length_pos = self->contents.array.length_pos;
+    gjs_g_argument_set_ulong(self->contents.array.length_tag,
+                             &state->in_cvalues[length_pos], length);
+    arg->v_pointer = data;
+    return false;
+}
+
+static bool
+gjs_marshal_explicit_array_inout_in(JSContext            *cx,
+                                    GjsArgumentCache     *self,
+                                    GjsFunctionCallState *state,
+                                    GIArgument           *arg,
+                                    JS::HandleValue       value)
+{
+    if (!gjs_marshal_explicit_array_in_in(cx, self, state, arg, value))
+        return false;
+
+    int length_pos = self->contents.array.length_pos;
+    int ix = self->arg_pos;
+
+    if (!arg->v_pointer) {
+        /* Special case where we were given JS null to also pass null for
+         * length, and not a pointer to an integer that derefs to 0. */
+        state->in_cvalues[length_pos].v_pointer = nullptr;
+        state->out_cvalues[length_pos].v_int = 0;
+        state->inout_original_cvalues[length_pos].v_int = 0;
+
+        state->out_cvalues[ix].v_pointer =
+            state->inout_original_cvalues[ix].v_pointer = nullptr;
+    } else {
+        state->out_cvalues[length_pos] =
+            state->inout_original_cvalues[length_pos] =
+            state->in_cvalues[length_pos];
+        state->in_cvalues[length_pos].v_pointer = &state->out_cvalues[length_pos];
+
+        state->out_cvalues[ix] = state->inout_original_cvalues[ix] = *arg;
+        arg->v_pointer = &(state->out_cvalues[ix]);
+    }
+
+    return true;
+}
+
+static bool
+gjs_marshal_callback_in(JSContext            *cx,
+                        GjsArgumentCache     *self,
+                        GjsFunctionCallState *state,
+                        GIArgument           *arg,
+                        JS::HandleValue       value)
+{
+    GjsCallbackTrampoline *trampoline;
+    ffi_closure *closure;
+
+    if (value.isNull() && self->nullable) {
+        closure = nullptr;
+        trampoline = nullptr;
+    } else {
+        if (!(JS_TypeOfValue(cx, value) == JSTYPE_FUNCTION)) {
+            gjs_throw(cx, "Expected function for callback argument %s, got %s",
+                      self->arg_name, gjs_get_type_name(value));
+            return false;
+        }
+
+        GICallableInfo *callable_info = g_type_info_get_interface(&self->type_info);
+        trampoline = gjs_callback_trampoline_new(cx,
+                                                 value,
+                                                 callable_info,
+                                                 self->contents.callback.scope,
+                                                 /* FIXME: is_object_method ? obj : NULL */
+                                                 nullptr,
+                                                 false);
+        closure = trampoline->closure;
+        g_base_info_unref(callable_info);
+    }
+
+    int destroy_pos = self->contents.callback.destroy_pos;
+    if (destroy_pos >= 0) {
+        state->in_cvalues[destroy_pos].v_pointer =
+            trampoline ? reinterpret_cast<void *>(gjs_destroy_notify_callback)
+                       : nullptr;
+    }
+    int closure_pos = self->contents.callback.closure_pos;
+    if (closure_pos >= 0) {
+        state->in_cvalues[closure_pos].v_pointer = trampoline;
+    }
+
+    if (trampoline && self->contents.callback.scope != GI_SCOPE_TYPE_CALL) {
+        /* Add an extra reference that will be cleared when collecting async
+         * calls, or when GDestroyNotify is called */
+        gjs_callback_trampoline_ref(trampoline);
+    }
+    arg->v_pointer = closure;
+
+    return true;
+}
+
+static bool
+gjs_marshal_generic_out_in(JSContext            *cx,
+                           GjsArgumentCache     *self,
+                           GjsFunctionCallState *state,
+                           GIArgument           *arg,
+                           JS::HandleValue       value)
+{
+    arg->v_pointer = &state->out_cvalues[self->arg_pos];
+    return true;
+}
+
+static bool
+gjs_marshal_caller_allocates_in(JSContext            *cx,
+                                GjsArgumentCache     *self,
+                                GjsFunctionCallState *state,
+                                GIArgument           *arg,
+                                JS::HandleValue       value)
+{
+    void *blob = g_slice_alloc0(self->contents.caller_allocates_size);
+    arg->v_pointer = blob;
+    state->out_cvalues[self->arg_pos].v_pointer = blob;
+
+    return true;
+}
+
+static bool
+gjs_marshal_skipped_out(JSContext             *cx,
+                        GjsArgumentCache      *self,
+                        GjsFunctionCallState  *state,
+                        GIArgument            *arg,
+                        JS::MutableHandleValue value)
+{
+    return true;
+}
+
+static bool
+gjs_marshal_generic_out_out(JSContext             *cx,
+                            GjsArgumentCache      *self,
+                            GjsFunctionCallState  *state,
+                            GIArgument            *arg,
+                            JS::MutableHandleValue value)
+{
+    return gjs_value_from_g_argument(cx, value, &self->type_info, arg, true);
+}
+
+static bool
+gjs_marshal_explicit_array_out_out(JSContext             *cx,
+                                   GjsArgumentCache      *self,
+                                   GjsFunctionCallState  *state,
+                                   GIArgument            *arg,
+                                   JS::MutableHandleValue value)
+{
+    int length_pos = self->contents.array.length_pos;
+    GIArgument *length_arg = &(state->out_cvalues[length_pos]);
+    GITypeTag length_tag = self->contents.array.length_tag;
+    size_t length = gjs_g_argument_get_ulong(length_tag, length_arg);
+
+    return gjs_value_from_explicit_array(cx, value, &self->type_info,
+                                         arg, length);
+}
+
+static bool
+gjs_marshal_skipped_release(JSContext            *cx,
+                            GjsArgumentCache     *self,
+                            GjsFunctionCallState *state,
+                            GIArgument           *in_arg,
+                            GIArgument           *out_arg)
+{
+    return true;
+}
+
+static bool
+gjs_marshal_generic_in_release(JSContext            *cx,
+                               GjsArgumentCache     *self,
+                               GjsFunctionCallState *state,
+                               GIArgument           *in_arg,
+                               GIArgument           *out_arg)
+{
+    return gjs_g_argument_release_in_arg(cx, self->transfer, &self->type_info,
+                                         in_arg);
+}
+
+static bool
+gjs_marshal_generic_out_release(JSContext            *cx,
+                                GjsArgumentCache     *self,
+                                GjsFunctionCallState *state,
+                                GIArgument           *in_arg,
+                                GIArgument           *out_arg)
+{
+    return gjs_g_argument_release(cx, self->transfer, &self->type_info, out_arg);
+}
+
+static bool
+gjs_marshal_generic_inout_release(JSContext            *cx,
+                                  GjsArgumentCache     *self,
+                                  GjsFunctionCallState *state,
+                                  GIArgument           *in_arg,
+                                  GIArgument           *out_arg)
+{
+    /* For inout, transfer refers to what we get back from the function; for
+     * the temporary C value we allocated, clearly we're responsible for
+     * freeing it. */
+
+    GIArgument *original_out_arg = &(state->inout_original_cvalues[self->arg_pos]);
+    if (!gjs_g_argument_release_in_arg(cx, GI_TRANSFER_NOTHING,
+                                       &self->type_info, original_out_arg))
+        return false;
+
+    return gjs_marshal_generic_out_release(cx, self, state, in_arg, out_arg);
+}
+
+static bool
+gjs_marshal_explicit_array_out_release(JSContext            *cx,
+                                       GjsArgumentCache     *self,
+                                       GjsFunctionCallState *state,
+                                       GIArgument           *in_arg,
+                                       GIArgument           *out_arg)
+{
+    int length_pos = self->contents.array.length_pos;
+    GIArgument *length_arg = &(state->out_cvalues[length_pos]);
+    GITypeTag length_tag = self->contents.array.length_tag;
+    size_t length = gjs_g_argument_get_ulong(length_tag, length_arg);
+
+    return gjs_g_argument_release_out_array(cx, self->transfer,
+                                            &self->type_info, length, out_arg);
+}
+
+static bool
+gjs_marshal_explicit_array_in_release(JSContext            *cx,
+                                      GjsArgumentCache     *self,
+                                      GjsFunctionCallState *state,
+                                      GIArgument           *in_arg,
+                                      GIArgument           *out_arg)
+{
+    int length_pos = self->contents.array.length_pos;
+    GIArgument *length_arg = &(state->in_cvalues[length_pos]);
+    GITypeTag length_tag = self->contents.array.length_tag;
+    size_t length = gjs_g_argument_get_ulong(length_tag, length_arg);
+
+    return gjs_g_argument_release_in_array(cx, self->transfer,
+                                           &self->type_info, length, in_arg);
+}
+
+static bool
+gjs_marshal_explicit_array_inout_release(JSContext            *cx,
+                                         GjsArgumentCache     *self,
+                                         GjsFunctionCallState *state,
+                                         GIArgument           *in_arg,
+                                         GIArgument           *out_arg)
+{
+    int length_pos = self->contents.array.length_pos;
+    GIArgument *length_arg = &(state->in_cvalues[length_pos]);
+    GITypeTag length_tag = self->contents.array.length_tag;
+    size_t length = gjs_g_argument_get_ulong(length_tag, length_arg);
+
+    /* For inout, transfer refers to what we get back from the function; for
+     * the temporary C value we allocated, clearly we're responsible for
+     * freeing it. */
+
+    GIArgument *original_out_arg = &(state->inout_original_cvalues[self->arg_pos]);
+    if (original_out_arg->v_pointer != out_arg->v_pointer &&
+        !gjs_g_argument_release_in_array(cx, GI_TRANSFER_NOTHING,
+                                         &self->type_info, length,
+                                         original_out_arg))
+        return false;
+
+    return gjs_g_argument_release_out_array(cx, self->transfer,
+                                            &self->type_info, length, out_arg);
+}
+
+static bool
+gjs_marshal_caller_allocates_release(JSContext            *cx,
+                                     GjsArgumentCache     *self,
+                                     GjsFunctionCallState *state,
+                                     GIArgument           *in_arg,
+                                     GIArgument           *out_arg)
+{
+    g_slice_free1(self->contents.caller_allocates_size, in_arg->v_pointer);
+    return true;
+}
+
+static bool
+gjs_marshal_callback_release(JSContext            *cx,
+                             GjsArgumentCache     *self,
+                             GjsFunctionCallState *state,
+                             GIArgument           *in_arg,
+                             GIArgument           *out_arg)
+{
+    auto closure = static_cast<ffi_closure *>(in_arg->v_pointer);
+
+    if (!closure)
+        return true;
+
+    auto trampoline = static_cast<GjsCallbackTrampoline *>(closure->user_data);
+    /* CallbackTrampolines are refcounted because for notified/async closures
+     * it is possible to destroy it while in call, and therefore we cannot
+     * check its scope at this point */
+    gjs_callback_trampoline_unref(trampoline);
+    in_arg->v_pointer = nullptr;
+
+    return true;
+}
+
+static inline void
+gjs_arg_cache_set_skip_all(GjsArgumentCache *self)
+{
+    self->marshal_in = gjs_marshal_skipped_in;
+    self->marshal_out = gjs_marshal_skipped_out;
+    self->release = gjs_marshal_skipped_release;
+    self->skip_in = self->skip_out = true;
+}
 
 bool
 gjs_arg_cache_build_return(GjsArgumentCache *self,
-                           GjsParamType     *param_types,
+                           GjsArgumentCache *arguments,
                            GICallableInfo   *info,
                            bool&             inc_counter)
 {
     GITypeInfo return_type;
     g_callable_info_load_return_type(info, &return_type);
 
-    inc_counter = g_type_info_get_tag(&return_type) != GI_TYPE_TAG_VOID;
+    if (g_type_info_get_tag(&return_type) == GI_TYPE_TAG_VOID) {
+        inc_counter = false;
+        gjs_arg_cache_set_skip_all(self);
+        return true;
+    }
 
-    int length_pos = g_type_info_get_array_length(&return_type);
-    if (length_pos >= 0)
-        param_types[length_pos] = PARAM_SKIPPED;
+    inc_counter = true;
+    self->arg_pos = -1;
+    self->arg_name = "return value";
+    g_callable_info_load_return_type(info, &self->type_info);
+    self->transfer = g_callable_info_get_caller_owns(info);
+    self->nullable = false;  /* We don't really care for return values */
+    self->is_return = true;
+
+    if (g_type_info_get_tag(&self->type_info) == GI_TYPE_TAG_ARRAY) {
+        int length_pos = g_type_info_get_array_length(&return_type);
+        if (length_pos >= 0) {
+            gjs_arg_cache_set_skip_all(&arguments[length_pos]);
+
+            /* Even if we skip the length argument most of the time, we need to
+             * do some basic initialization here. */
+            arguments[length_pos].arg_pos = length_pos;
+            arguments[length_pos].marshal_in = gjs_marshal_generic_out_in;
+
+            self->marshal_in = gjs_marshal_generic_out_in;
+            self->marshal_out = gjs_marshal_explicit_array_out_out;
+            self->release = gjs_marshal_explicit_array_out_release;
+
+            self->contents.array.length_pos = length_pos;
+
+            GIArgInfo length_arg;
+            g_callable_info_load_arg(info, length_pos, &length_arg);
+            GITypeInfo length_type;
+            g_arg_info_load_type(&length_arg, &length_type);
+            self->contents.array.length_tag = g_type_info_get_tag(&length_type);
+
+            return true;
+        }
+    }
 
-    if (inc_counter)
-        self->param_type = PARAM_NORMAL;
-    else
-        self->param_type = PARAM_SKIPPED;
+    /* marshal_in is ignored for the return value, but skip_in is not
+       (it is used in the failure release path) */
+    self->skip_in = true;
+    self->marshal_out = gjs_marshal_generic_out_out;
+    self->release = gjs_marshal_generic_out_release;
 
     return true;
 }
 
 bool
-gjs_arg_cache_build_in_arg(GjsArgumentCache *self,
-                           GjsParamType     *param_types,
-                           int               gi_index,
-                           GIArgInfo        *arg_info,
-                           bool&             inc_counter)
+gjs_arg_cache_build_arg(GjsArgumentCache *self,
+                        GjsArgumentCache *arguments,
+                        int               gi_index,
+                        GIDirection       direction,
+                        GIArgInfo        *arg_info,
+                        GICallableInfo   *callable,
+                        bool&             inc_counter)
 {
     GITypeInfo type_info;
     g_arg_info_load_type(arg_info, &type_info);
 
-    self->param_type = PARAM_NORMAL;
+    self->arg_pos = gi_index;
+    self->arg_name = g_base_info_get_name(arg_info);
+    g_arg_info_load_type(arg_info, &self->type_info);
+    self->transfer = g_arg_info_get_ownership_transfer(arg_info);
+    self->nullable = g_arg_info_may_be_null(arg_info);
+    self->is_return = false;
+
+    if (direction == GI_DIRECTION_IN)
+        self->skip_out = true;
+    else if (direction == GI_DIRECTION_OUT)
+        self->skip_in = true;
     inc_counter = true;
 
+    if (direction == GI_DIRECTION_OUT &&
+        g_arg_info_is_caller_allocates(arg_info)) {
+        GIInterfaceInfo *interface_info = g_type_info_get_interface(&type_info);
+        g_assert(interface_info);
+
+        GIInfoType interface_type = g_base_info_get_type(interface_info);
+
+        size_t size;
+        if (interface_type == GI_INFO_TYPE_STRUCT) {
+            size = g_struct_info_get_size((GIStructInfo*)interface_info);
+        } else if (interface_type == GI_INFO_TYPE_UNION) {
+            size = g_union_info_get_size((GIUnionInfo*)interface_info);
+        } else {
+            /* Can't do caller allocates on anything else */
+
+            g_base_info_unref((GIBaseInfo*)interface_info);
+            return false;
+        }
+
+        g_base_info_unref((GIBaseInfo*)interface_info);
+
+        self->marshal_in = gjs_marshal_caller_allocates_in;
+        self->marshal_out = gjs_marshal_generic_out_out;
+        self->release = gjs_marshal_caller_allocates_release;
+        self->contents.caller_allocates_size = size;
+
+        return true;
+    }
+
     GITypeTag type_tag = g_type_info_get_tag(&type_info);
     if (type_tag == GI_TYPE_TAG_INTERFACE) {
         GIBaseInfo *interface_info = g_type_info_get_interface(&type_info);
         GIInfoType interface_type = g_base_info_get_type(interface_info);
         if (interface_type == GI_INFO_TYPE_CALLBACK) {
+            if (direction != GI_DIRECTION_IN) {
+                /* Can't do callbacks for out or inout */
+                g_base_info_unref(interface_info);
+                return false;
+            }
+
             if (strcmp(g_base_info_get_name(interface_info), "DestroyNotify") == 0 &&
                 strcmp(g_base_info_get_namespace(interface_info), "GLib") == 0) {
                 /* Skip GDestroyNotify if they appear before the respective callback */
-                self->param_type = PARAM_SKIPPED;
+                gjs_arg_cache_set_skip_all(self);
                 inc_counter = false;
             } else {
-                self->param_type = PARAM_CALLBACK;
+                self->marshal_in = gjs_marshal_callback_in;
+                self->marshal_out = gjs_marshal_skipped_out;
+                self->release = gjs_marshal_callback_release;
 
-                int destroy = g_arg_info_get_destroy(arg_info);
-                int closure = g_arg_info_get_closure(arg_info);
+                int destroy_pos = g_arg_info_get_destroy(arg_info);
+                int closure_pos = g_arg_info_get_closure(arg_info);
 
-                if (destroy >= 0)
-                    param_types[destroy] = PARAM_SKIPPED;
+                if (destroy_pos >= 0)
+                    gjs_arg_cache_set_skip_all(&arguments[destroy_pos]);
 
-                if (closure >= 0)
-                    param_types[closure] = PARAM_SKIPPED;
+                if (closure_pos >= 0)
+                    gjs_arg_cache_set_skip_all(&arguments[closure_pos]);
 
-                if (destroy >= 0 && closure < 0) {
+                if (destroy_pos >= 0 && closure_pos < 0) {
                     /* Function has a GDestroyNotify but no user_data, not supported */
                     g_base_info_unref(interface_info);
                     return false;
                 }
+
+                self->contents.callback.scope = g_arg_info_get_scope(arg_info);
+                self->contents.callback.destroy_pos = destroy_pos;
+                self->contents.callback.closure_pos = closure_pos;
             }
 
             g_base_info_unref(interface_info);
+            return true;
         }
-    } 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) {
-                param_types[length_pos] = PARAM_SKIPPED;
-                self->param_type = PARAM_ARRAY;
 
-                if (length_pos < gi_index) {
-                    /* we already collected length_pos, remove it */
-                    inc_counter = false;
-                }
-            }
-        }
+        g_base_info_unref(interface_info);
     }
 
-    return true;
-}
-
-bool
-gjs_arg_cache_build_out_arg(GjsArgumentCache *self,
-                            GjsParamType     *param_types,
-                            int               gi_index,
-                            GIArgInfo        *arg_info,
-                            bool&             inc_counter)
-{
-    GITypeInfo type_info;
-    g_arg_info_load_type(arg_info, &type_info);
-
-    self->param_type = PARAM_NORMAL;
-    inc_counter = true;
+    if (type_tag == GI_TYPE_TAG_ARRAY &&
+        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) {
+            gjs_arg_cache_set_skip_all(&arguments[length_pos]);
+
+            if (direction == GI_DIRECTION_IN) {
+                self->marshal_in = gjs_marshal_explicit_array_in_in;
+                self->marshal_out = gjs_marshal_skipped_out;
+                self->release = gjs_marshal_explicit_array_in_release;
+            } else if (direction == GI_DIRECTION_INOUT) {
+                self->marshal_in = gjs_marshal_explicit_array_inout_in;
+                self->marshal_out = gjs_marshal_explicit_array_out_out;
+                self->release = gjs_marshal_explicit_array_inout_release;
+            } else {
+                /* Even if we skip the length argument most of time,
+                 * we need to do some basic initialization here. */
+                arguments[length_pos].arg_pos = length_pos;
+                arguments[length_pos].marshal_in = gjs_marshal_generic_out_in;
+
+                self->marshal_in = gjs_marshal_generic_out_in;
+                self->marshal_out = gjs_marshal_explicit_array_out_out;
+                self->release = gjs_marshal_explicit_array_out_release;
+            }
 
-    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);
+            self->contents.array.length_pos = length_pos;
 
-            if (length_pos >= 0) {
-                param_types[length_pos] = PARAM_SKIPPED;
-                self->param_type = PARAM_ARRAY;
+            GIArgInfo length_arg;
+            g_callable_info_load_arg(callable, length_pos, &length_arg);
+            GITypeInfo length_type;
+            g_arg_info_load_type(&length_arg, &length_type);
+            self->contents.array.length_tag = g_type_info_get_tag(&length_type);
 
-                if (length_pos < gi_index) {
-                    /* we already collected length_pos, remove it */
-                    inc_counter = false;
-                }
+            if (length_pos < gi_index) {
+                /* we already collected length_pos, remove it */
+                inc_counter = false;
             }
+
+            return true;
         }
     }
 
-    return true;
-}
-
-bool
-gjs_arg_cache_build_inout_arg(GjsArgumentCache *in_self,
-                              GjsArgumentCache *out_self,
-                              GjsParamType     *param_types,
-                              int               gi_index,
-                              GIArgInfo        *arg_info,
-                              bool&             inc_counter)
-{
-    GITypeInfo type_info;
-    g_arg_info_load_type(arg_info, &type_info);
-
-    in_self->param_type = PARAM_NORMAL;
-    out_self->param_type = PARAM_NORMAL;
-    inc_counter = 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 = false;
-                }
-            }
-        }
+    if (direction == GI_DIRECTION_IN) {
+        self->marshal_in = gjs_marshal_generic_in_in;
+        self->marshal_out = gjs_marshal_skipped_out;
+        self->release = gjs_marshal_generic_in_release;
+    } else if (direction == GI_DIRECTION_INOUT) {
+        self->marshal_in = gjs_marshal_generic_inout_in;
+        self->marshal_out = gjs_marshal_generic_out_out;
+        self->release = gjs_marshal_generic_inout_release;
+    } else {
+        self->marshal_in = gjs_marshal_generic_out_in;
+        self->marshal_out = gjs_marshal_generic_out_out;
+        self->release = gjs_marshal_generic_out_release;
     }
 
     return true;
diff --git a/gi/arg-cache.h b/gi/arg-cache.h
index 4d32d1d..d92a68a 100644
--- a/gi/arg-cache.h
+++ b/gi/arg-cache.h
@@ -33,42 +33,73 @@
 G_BEGIN_DECLS
 
 typedef struct _GjsArgumentCache {
-    bool (*marshal) (struct _GjsArgumentCache *, GIArgument *, JS::HandleValue);
-    bool (*release) (struct _GjsArgumentCache *, GIArgument *);
-    bool (*free) (struct _GjsArgumentCache *);
+    bool (*marshal_in) (JSContext                *cx,
+                        struct _GjsArgumentCache *cache,
+                        GjsFunctionCallState     *state,
+                        GIArgument               *in_argument,
+                        JS::HandleValue           value);
+    bool (*marshal_out) (JSContext                *cx,
+                         struct _GjsArgumentCache *cache,
+                         GjsFunctionCallState     *state,
+                         GIArgument               *out_argument,
+                         JS::MutableHandleValue    value);
+    bool (*release) (JSContext                *cx,
+                     struct _GjsArgumentCache *cache,
+                     GjsFunctionCallState     *state,
+                     GIArgument               *in_argument,
+                     GIArgument               *out_argument);
+    bool (*free) (struct _GjsArgumentCache *cache);
 
-    /* For compatibility */
-    GjsParamType param_type;
+    const char *arg_name;
+    int arg_pos;
+    GITypeInfo type_info;
+
+    bool skip_in : 1;
+    bool skip_out : 1;
+    GITransfer transfer : 2;
+    bool nullable : 1;
+    bool is_return : 1;
 
     union {
+        struct {
+            int length_pos;
+            GITypeTag length_tag;
+        } array;
+        struct {
+            GIScopeType scope;
+            int closure_pos;
+            int destroy_pos;
+        } callback;
+        size_t caller_allocates_size;
         int dummy;
     } contents;
 } GjsArgumentCache;
 
-bool gjs_arg_cache_build_in_arg(GjsArgumentCache *self,
-                                GjsParamType     *param_types,
-                                int               gi_index,
-                                GIArgInfo        *arg,
-                                bool&             inc_counter);
-
-bool gjs_arg_cache_build_out_arg(GjsArgumentCache *self,
-                                 GjsParamType     *param_types,
-                                 int               gi_index,
-                                 GIArgInfo        *arg,
-                                 bool&             inc_counter);
-
-bool gjs_arg_cache_build_inout_arg(GjsArgumentCache *in_self,
-                                   GjsArgumentCache *out_self,
-                                   GjsParamType     *param_types,
-                                   int               gi_index,
-                                   GIArgInfo        *arg,
-                                   bool&             inc_counter);
+bool gjs_arg_cache_build_arg(GjsArgumentCache *self,
+                             GjsArgumentCache *arguments,
+                             int               gi_index,
+                             GIDirection       direction,
+                             GIArgInfo        *arg,
+                             GICallableInfo   *callable,
+                             bool&             inc_counter);
 
 bool gjs_arg_cache_build_return(GjsArgumentCache *self,
-                                GjsParamType     *param_types,
+                                GjsArgumentCache *arguments,
                                 GICallableInfo   *info,
                                 bool&             inc_counter);
 
+static inline bool
+gjs_arg_cache_is_skip_in(GjsArgumentCache *cache)
+{
+    return cache->skip_in;
+}
+
+static inline bool
+gjs_arg_cache_is_skip_out(GjsArgumentCache *cache)
+{
+    return cache->skip_out;
+}
+
 G_END_DECLS
 
 #endif  /* GJS_ARG_CACHE_H */
diff --git a/gi/arg.cpp b/gi/arg.cpp
index cec0e9a..a1c9a81 100644
--- a/gi/arg.cpp
+++ b/gi/arg.cpp
@@ -1248,16 +1248,16 @@ throw_invalid_argument(JSContext      *context,
     g_free(display_name);
 }
 
-static bool
-gjs_array_to_explicit_array_internal(JSContext       *context,
-                                     JS::HandleValue  value,
-                                     GITypeInfo      *type_info,
-                                     const char      *arg_name,
-                                     GjsArgumentType  arg_type,
-                                     GITransfer       transfer,
-                                     bool             may_be_null,
-                                     gpointer        *contents,
-                                     gsize           *length_p)
+bool
+gjs_array_to_explicit_array(JSContext      *context,
+                            JS::HandleValue value,
+                            GITypeInfo     *type_info,
+                            const char     *arg_name,
+                            GjsArgumentType arg_type,
+                            GITransfer      transfer,
+                            bool            may_be_null,
+                            void          **contents,
+                            size_t         *length_p)
 {
     bool ret = false;
     GITypeInfo *param_info;
@@ -1957,15 +1957,9 @@ gjs_value_to_g_argument(JSContext      *context,
             }
         }
 
-        if (!gjs_array_to_explicit_array_internal(context,
-                                                  value,
-                                                  type_info,
-                                                  arg_name,
-                                                  arg_type,
-                                                  transfer,
-                                                  may_be_null,
-                                                  &data,
-                                                  &length)) {
+        if (!gjs_array_to_explicit_array(context, value, type_info, arg_name,
+                                         arg_type, transfer, may_be_null,
+                                         &data, &length)) {
             wrong = true;
             break;
         }
@@ -2176,28 +2170,6 @@ gjs_value_to_arg(JSContext      *context,
                                    arg);
 }
 
-bool
-gjs_value_to_explicit_array (JSContext      *context,
-                             JS::HandleValue value,
-                             GIArgInfo      *arg_info,
-                             GIArgument     *arg,
-                             size_t         *length_p)
-{
-    GITypeInfo type_info;
-
-    g_arg_info_load_type(arg_info, &type_info);
-
-    return gjs_array_to_explicit_array_internal(context,
-                                                value,
-                                                &type_info,
-                                                g_base_info_get_name((GIBaseInfo*) arg_info),
-                                                GJS_ARGUMENT_ARGUMENT,
-                                                g_arg_info_get_ownership_transfer(arg_info),
-                                                g_arg_info_may_be_null(arg_info),
-                                                &arg->v_pointer,
-                                                length_p);
-}
-
 static bool
 gjs_array_from_g_list (JSContext             *context,
                        JS::MutableHandleValue value_p,
diff --git a/gi/arg.h b/gi/arg.h
index 06a41b3..2e7d84e 100644
--- a/gi/arg.h
+++ b/gi/arg.h
@@ -48,10 +48,14 @@ bool gjs_value_to_arg(JSContext      *context,
                       GIArgInfo      *arg_info,
                       GIArgument     *arg);
 
-bool gjs_value_to_explicit_array(JSContext       *context,
+bool gjs_array_to_explicit_array(JSContext       *cx,
                                  JS::HandleValue  value,
-                                 GIArgInfo       *arg_info,
-                                 GIArgument      *arg,
+                                 GITypeInfo      *type_info,
+                                 const char      *arg_name,
+                                 GjsArgumentType  arg_type,
+                                 GITransfer       transfer,
+                                 bool             may_be_null,
+                                 void           **contents,
                                  size_t          *length_p);
 
 void gjs_g_argument_init_default (JSContext      *context,
diff --git a/gi/function.cpp b/gi/function.cpp
index 839f981..c377663 100644
--- a/gi/function.cpp
+++ b/gi/function.cpp
@@ -54,9 +54,7 @@
 typedef struct {
     GIFunctionInfo *info;
 
-    GjsParamType *param_types;
-    GjsArgumentCache *in_arguments;
-    GjsArgumentCache *out_arguments;
+    GjsArgumentCache *arguments;
 
     uint8_t js_in_argc;
     guint8 js_out_argc;
@@ -456,18 +454,6 @@ out:
     JS_EndRequest(context);
 }
 
-/* The global entry point for any invocations of GDestroyNotify;
- * look up the callback through the user_data and then free it.
- */
-static void
-gjs_destroy_notify_callback(gpointer data)
-{
-    GjsCallbackTrampoline *trampoline = (GjsCallbackTrampoline *) data;
-
-    g_assert(trampoline);
-    gjs_callback_trampoline_unref(trampoline);
-}
-
 GjsCallbackTrampoline*
 gjs_callback_trampoline_new(JSContext       *context,
                             JS::HandleValue  function,
@@ -569,31 +555,6 @@ gjs_callback_trampoline_new(JSContext       *context,
     return trampoline;
 }
 
-/* an helper function to retrieve array lengths from a GArgument
-   (letting the compiler generate good instructions in case of
-   big endian machines) */
-static unsigned long
-get_length_from_arg (GArgument *arg, GITypeTag tag)
-{
-    if (tag == GI_TYPE_TAG_INT8)
-        return arg->v_int8;
-    if (tag == GI_TYPE_TAG_UINT8)
-        return arg->v_uint8;
-    if (tag == GI_TYPE_TAG_INT16)
-        return arg->v_int16;
-    if (tag == GI_TYPE_TAG_UINT16)
-        return arg->v_uint16;
-    if (tag == GI_TYPE_TAG_INT32)
-        return arg->v_int32;
-    if (tag == GI_TYPE_TAG_UINT32)
-        return arg->v_uint32;
-    if (tag == GI_TYPE_TAG_INT64)
-        return arg->v_int64;
-    if (tag == GI_TYPE_TAG_UINT64)
-        return arg->v_uint64;
-    g_assert_not_reached();
-}
-
 static bool
 gjs_fill_method_instance(JSContext       *context,
                          JS::HandleObject obj,
@@ -745,40 +706,24 @@ gjs_invoke_c_function(JSContext                             *context,
                       mozilla::Maybe<JS::MutableHandleValue> js_rval,
                       GIArgument                            *r_value)
 {
-    /* These first four are arrays which hold argument pointers.
-     * @in_arg_cvalues: C values which are passed on input (in or inout)
-     * @out_arg_cvalues: C values which are returned as arguments (out or inout)
-     * @inout_original_arg_cvalues: For the special case of (inout) args, we need to
-     *  keep track of the original values we passed into the function, in case we
-     *  need to free it.
-     * @ffi_arg_pointers: For passing data to FFI, we need to create another layer
-     *  of indirection; this array is a pointer to an element in in_arg_cvalues
-     *  or out_arg_cvalues.
-     * @return_value: The actual return value of the C function, i.e. not an (out) param
-     */
-    GArgument *in_arg_cvalues;
-    GArgument *out_arg_cvalues;
-    GArgument *inout_original_arg_cvalues;
+    GjsFunctionCallState state;
     gpointer *ffi_arg_pointers;
     GIFFIReturnValue return_value;
     gpointer return_value_p; /* Will point inside the union return_value */
-    GArgument return_gargument;
 
-    guint8 processed_c_args = 0;
-    guint8 gi_argc, gi_arg_pos;
-    guint8 c_argc, c_arg_pos;
-    guint8 js_arg_pos;
+    int processed_c_args = 0;
+    int gi_argc, gi_arg_pos;
+    int ffi_argc, ffi_arg_pos;
+    int js_arg_pos;
     bool can_throw_gerror;
     bool did_throw_gerror = false;
-    GError *local_error = NULL;
+    GError *local_error = nullptr, **errorp;
     bool failed, postinvoke_release_failed;
 
     bool is_method;
     bool is_object_method = false;
-    GITypeInfo return_info;
     GITypeTag return_tag;
     JS::AutoValueVector return_values(context);
-    guint8 next_rval = 0; /* index into return_values */
     GSList *iter;
 
     /* Because we can't free a closure while we're in it, we defer
@@ -797,9 +742,6 @@ gjs_invoke_c_function(JSContext                             *context,
     is_method = g_callable_info_is_method(function->info);
     can_throw_gerror = g_callable_info_can_throw_gerror(function->info);
 
-    c_argc = function->invoker.cif.nargs;
-    gi_argc = g_callable_info_get_n_args( (GICallableInfo*) function->info);
-
     /* @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
@@ -822,217 +764,70 @@ gjs_invoke_c_function(JSContext                             *context,
         return false;
     }
 
-    g_callable_info_load_return_type( (GICallableInfo*) function->info, &return_info);
-    return_tag = g_type_info_get_tag(&return_info);
+    /* These first four are arrays which hold argument pointers.
+     * @in_arg_cvalues: C values which are passed on input (in or inout)
+     * @out_arg_cvalues: C values which are returned as arguments (out or inout)
+     * @inout_original_arg_cvalues: For the special case of (inout) args, we need to
+     *  keep track of the original values we passed into the function, in case we
+     *  need to free it.
+     * @ffi_arg_pointers: For passing data to FFI, we need to create another layer
+     *  of indirection; this array is a pointer to an element in in_arg_cvalues
+     *  or out_arg_cvalues.
+     * @return_value: The actual return value of the C function, i.e. not an (out) param
+     *
+     * The 3 GArgument arrays are indexed by the GI argument index,
+     * with the following exceptions:
+     * [-1] is the return value (which can be nothing/garbage if the
+     * function returns void)
+     * [-2] is the instance parameter, if present
+     * ffi_arg_pointers, on the other hand, represents the actual
+     * C arguments, in the way ffi expects them
+     *
+     * Use gi_arg_pos to index inside the GArgument array
+     * Use ffi_arg_pos to index inside ffi_arg_pointers
+    */
+
+    ffi_argc = function->invoker.cif.nargs;
+    gi_argc = g_callable_info_get_n_args( (GICallableInfo*) function->info);
+
+    if (is_method) {
+        state.in_cvalues = g_newa(GArgument, gi_argc + 2) + 2;
+        state.out_cvalues = g_newa(GArgument, gi_argc + 2) + 2;
+        state.inout_original_cvalues = g_newa(GArgument, gi_argc + 2) + 2;
+    } else {
+        state.in_cvalues = g_newa(GArgument, gi_argc + 1) + 1;
+        state.out_cvalues = g_newa(GArgument, gi_argc + 1) + 1;
+        state.inout_original_cvalues = g_newa(GArgument, gi_argc + 1) + 1;
+    }
 
-    in_arg_cvalues = g_newa(GArgument, c_argc);
-    ffi_arg_pointers = g_newa(gpointer, c_argc);
-    out_arg_cvalues = g_newa(GArgument, c_argc);
-    inout_original_arg_cvalues = g_newa(GArgument, c_argc);
+    ffi_arg_pointers = g_newa(gpointer, ffi_argc);
 
     failed = false;
-    c_arg_pos = 0; /* index into in_arg_cvalues, etc */
+    ffi_arg_pos = 0; /* index into ffi_arg_pointers */
     js_arg_pos = 0; /* index into argv */
 
     if (is_method) {
         if (!gjs_fill_method_instance(context, obj, function,
-                                      &in_arg_cvalues[0], is_object_method))
+                                      &state.in_cvalues[-2], is_object_method))
             return false;
-        ffi_arg_pointers[0] = &in_arg_cvalues[0];
-        ++c_arg_pos;
+        ffi_arg_pointers[0] = &state.in_cvalues[-2];
+        ++ffi_arg_pos;
     }
 
-    processed_c_args = c_arg_pos;
-    for (gi_arg_pos = 0; gi_arg_pos < gi_argc; gi_arg_pos++, c_arg_pos++) {
-        GIDirection direction;
-        GIArgInfo arg_info;
-        bool arg_removed = false;
-
-        /* gjs_debug(GJS_DEBUG_GFUNCTION, "gi_arg_pos: %d c_arg_pos: %d js_arg_pos: %d", gi_arg_pos, 
c_arg_pos, js_arg_pos); */
-
-        g_callable_info_load_arg( (GICallableInfo*) function->info, gi_arg_pos, &arg_info);
-        direction = g_arg_info_get_direction(&arg_info);
-
-        g_assert_cmpuint(c_arg_pos, <, c_argc);
-        ffi_arg_pointers[c_arg_pos] = &in_arg_cvalues[c_arg_pos];
-
-        if (direction == GI_DIRECTION_OUT) {
-            if (g_arg_info_is_caller_allocates(&arg_info)) {
-                GITypeTag type_tag;
-                GITypeInfo ainfo;
-
-                g_arg_info_load_type(&arg_info, &ainfo);
-                type_tag = g_type_info_get_tag(&ainfo);
-
-                if (type_tag == GI_TYPE_TAG_INTERFACE) {
-                    GIBaseInfo* interface_info;
-                    GIInfoType interface_type;
-                    gsize size;
-
-                    interface_info = g_type_info_get_interface(&ainfo);
-                    g_assert(interface_info != NULL);
-
-                    interface_type = g_base_info_get_type(interface_info);
-
-                    if (interface_type == GI_INFO_TYPE_STRUCT) {
-                        size = g_struct_info_get_size((GIStructInfo*)interface_info);
-                    } else if (interface_type == GI_INFO_TYPE_UNION) {
-                        size = g_union_info_get_size((GIUnionInfo*)interface_info);
-                    } else {
-                        failed = true;
-                    }
+    processed_c_args = ffi_arg_pos;
+    for (gi_arg_pos = 0; gi_arg_pos < gi_argc; gi_arg_pos++, ffi_arg_pos++) {
+        GjsArgumentCache *cache = &function->arguments[gi_arg_pos];
+        GIArgument *in_value = &state.in_cvalues[gi_arg_pos];
+        ffi_arg_pointers[ffi_arg_pos] = in_value;
 
-                    g_base_info_unref((GIBaseInfo*)interface_info);
-
-                    if (!failed) {
-                        in_arg_cvalues[c_arg_pos].v_pointer = g_slice_alloc0(size);
-                        out_arg_cvalues[c_arg_pos].v_pointer = in_arg_cvalues[c_arg_pos].v_pointer;
-                    }
-                } else {
-                    failed = true;
-                }
-                if (failed)
-                    gjs_throw(context, "Unsupported type %s for (out caller-allocates)", 
g_type_tag_to_string(type_tag));
-            } else {
-                out_arg_cvalues[c_arg_pos].v_pointer = NULL;
-                in_arg_cvalues[c_arg_pos].v_pointer = &out_arg_cvalues[c_arg_pos];
-            }
-        } else {
-            GArgument *in_value;
-            GITypeInfo ainfo;
-            GjsParamType param_type;
-
-            g_arg_info_load_type(&arg_info, &ainfo);
-
-            in_value = &in_arg_cvalues[c_arg_pos];
-
-            param_type = function->param_types[gi_arg_pos];
-
-            switch (param_type) {
-            case PARAM_CALLBACK: {
-                GICallableInfo *callable_info;
-                GIScopeType scope = g_arg_info_get_scope(&arg_info);
-                GjsCallbackTrampoline *trampoline;
-                ffi_closure *closure;
-                JS::HandleValue current_arg = args[js_arg_pos];
-
-                if (current_arg.isNull() && g_arg_info_may_be_null(&arg_info)) {
-                    closure = NULL;
-                    trampoline = NULL;
-                } else {
-                    if (!(JS_TypeOfValue(context, current_arg) == JSTYPE_FUNCTION)) {
-                        gjs_throw(context, "Error invoking %s.%s: Expected function for callback argument 
%s, got %s",
-                                  g_base_info_get_namespace( (GIBaseInfo*) function->info),
-                                  g_base_info_get_name( (GIBaseInfo*) function->info),
-                                  g_base_info_get_name( (GIBaseInfo*) &arg_info),
-                                  gjs_get_type_name(current_arg));
-                        failed = true;
-                        break;
-                    }
-
-                    callable_info = (GICallableInfo*) g_type_info_get_interface(&ainfo);
-                    trampoline = gjs_callback_trampoline_new(context,
-                                                             current_arg,
-                                                             callable_info,
-                                                             scope,
-                                                             is_object_method ? obj : nullptr,
-                                                             false);
-                    closure = trampoline->closure;
-                    g_base_info_unref(callable_info);
-                }
-
-                gint destroy_pos = g_arg_info_get_destroy(&arg_info);
-                gint closure_pos = g_arg_info_get_closure(&arg_info);
-                if (destroy_pos >= 0) {
-                    gint c_pos = is_method ? destroy_pos + 1 : destroy_pos;
-                    g_assert (function->param_types[destroy_pos] == PARAM_SKIPPED);
-                    in_arg_cvalues[c_pos].v_pointer = trampoline ? (gpointer) gjs_destroy_notify_callback : 
NULL;
-                }
-                if (closure_pos >= 0) {
-                    gint c_pos = is_method ? closure_pos + 1 : closure_pos;
-                    g_assert (function->param_types[closure_pos] == PARAM_SKIPPED);
-                    in_arg_cvalues[c_pos].v_pointer = trampoline;
-                }
-
-                if (trampoline && scope != GI_SCOPE_TYPE_CALL) {
-                    /* Add an extra reference that will be cleared when collecting
-                       async calls, or when GDestroyNotify is called */
-                    gjs_callback_trampoline_ref(trampoline);
-                }
-                in_value->v_pointer = closure;
-                break;
-            }
-            case PARAM_SKIPPED:
-                arg_removed = true;
-                break;
-            case PARAM_ARRAY: {
-                GIArgInfo array_length_arg;
-
-                gint array_length_pos = g_type_info_get_array_length(&ainfo);
-                gsize length;
-
-                if (!gjs_value_to_explicit_array(context, args[js_arg_pos],
-                                                 &arg_info, in_value, &length)) {
-                    failed = true;
-                    break;
-                }
-
-                g_callable_info_load_arg(function->info, array_length_pos, &array_length_arg);
-
-                array_length_pos += is_method ? 1 : 0;
-                JS::RootedValue v_length(context, JS::Int32Value(length));
-                if (!gjs_value_to_arg(context, v_length, &array_length_arg,
-                                      in_arg_cvalues + array_length_pos)) {
-                    failed = true;
-                    break;
-                }
-                /* Also handle the INOUT for the length here */
-                if (direction == GI_DIRECTION_INOUT) {
-                    if (in_value->v_pointer == NULL) { 
-                        /* Special case where we were given JS null to
-                         * also pass null for length, and not a
-                         * pointer to an integer that derefs to 0.
-                         */
-                        in_arg_cvalues[array_length_pos].v_pointer = NULL;
-                        out_arg_cvalues[array_length_pos].v_pointer = NULL;
-                        inout_original_arg_cvalues[array_length_pos].v_pointer = NULL;
-                    } else {
-                        out_arg_cvalues[array_length_pos] = inout_original_arg_cvalues[array_length_pos] = 
*(in_arg_cvalues + array_length_pos);
-                        in_arg_cvalues[array_length_pos].v_pointer = &out_arg_cvalues[array_length_pos];
-                    }
-                }
-                break;
-            }
-            case PARAM_NORMAL: {
-                /* Ok, now just convert argument normally */
-                g_assert_cmpuint(js_arg_pos, <, args.length());
-                if (!gjs_value_to_arg(context, args[js_arg_pos], &arg_info,
-                                      in_value))
-                    failed = true;
-
-                break;
-            }
-
-            default:
-                ;
-            }
-
-            if (direction == GI_DIRECTION_INOUT && !arg_removed && !failed) {
-                out_arg_cvalues[c_arg_pos] = inout_original_arg_cvalues[c_arg_pos] = 
in_arg_cvalues[c_arg_pos];
-                in_arg_cvalues[c_arg_pos].v_pointer = &out_arg_cvalues[c_arg_pos];
-            }
-
-            if (failed) {
-                /* Exit from the loop */
-                break;
-            }
-
-            if (!failed && !arg_removed)
-                ++js_arg_pos;
+        if (!cache->marshal_in(context, cache, &state, in_value,
+                               args[js_arg_pos])) {
+            failed = true;
+            break;
         }
 
-        if (failed)
-            break;
+        if (!gjs_arg_cache_is_skip_in(cache))
+            js_arg_pos++;
 
         processed_c_args++;
     }
@@ -1045,27 +840,33 @@ gjs_invoke_c_function(JSContext                             *context,
     }
 
     if (can_throw_gerror) {
-        g_assert_cmpuint(c_arg_pos, <, c_argc);
-        in_arg_cvalues[c_arg_pos].v_pointer = &local_error;
-        ffi_arg_pointers[c_arg_pos] = &(in_arg_cvalues[c_arg_pos]);
-        c_arg_pos++;
+        errorp = &local_error;
+        ffi_arg_pointers[ffi_arg_pos] = &errorp;
+        ffi_arg_pos++;
 
         /* don't update processed_c_args as we deal with local_error
          * separately */
     }
 
-    g_assert_cmpuint(c_arg_pos, ==, c_argc);
+    g_assert_cmpuint(ffi_arg_pos, ==, ffi_argc);
     g_assert_cmpuint(gi_arg_pos, ==, gi_argc);
 
-    /* See comment for GjsFFIReturnValue above */
-    if (return_tag == GI_TYPE_TAG_FLOAT)
-        return_value_p = &return_value.v_float;
-    else if (return_tag == GI_TYPE_TAG_DOUBLE)
-        return_value_p = &return_value.v_double;
-    else if (return_tag == GI_TYPE_TAG_INT64 || return_tag == GI_TYPE_TAG_UINT64)
-        return_value_p = &return_value.v_uint64;
-    else
-        return_value_p = &return_value.v_long;
+    if (!gjs_arg_cache_is_skip_out(&function->arguments[-1])) {
+        return_tag = g_type_info_get_tag(&function->arguments[-1].type_info);
+
+        /* See comment for GjsFFIReturnValue above */
+        if (return_tag == GI_TYPE_TAG_FLOAT)
+            return_value_p = &return_value.v_float;
+        else if (return_tag == GI_TYPE_TAG_DOUBLE)
+            return_value_p = &return_value.v_double;
+        else if (return_tag == GI_TYPE_TAG_INT64 || return_tag == GI_TYPE_TAG_UINT64)
+            return_value_p = &return_value.v_uint64;
+        else
+            return_value_p = &return_value.v_long;
+    } else {
+        return_value_p = nullptr;
+    }
+
     ffi_call(&(function->invoker.cif), FFI_FN(function->invoker.native_address), return_value_p, 
ffi_arg_pointers);
 
     /* Return value and out arguments are valid only if invocation doesn't
@@ -1080,255 +881,64 @@ gjs_invoke_c_function(JSContext                             *context,
     if (js_rval)
         js_rval.ref().setUndefined();
 
-    /* Only process return values if the function didn't throw */
-    if (function->js_out_argc > 0 && !did_throw_gerror) {
+    if (!gjs_arg_cache_is_skip_out(&function->arguments[-1])) {
+        gi_type_info_extract_ffi_return_value(&function->arguments[-1].type_info,
+                                              &return_value,
+                                              &state.out_cvalues[-1]);
+    }
+
+    if (function->js_out_argc > 0) {
         for (size_t i = 0; i < function->js_out_argc; i++)
             if (!return_values.append(JS::UndefinedValue()))
                 g_error("Unable to append to vector");
+    }
 
-        if (return_tag != GI_TYPE_TAG_VOID) {
-            GITransfer transfer = g_callable_info_get_caller_owns((GICallableInfo*) function->info);
-            bool arg_failed = false;
-            gint array_length_pos;
-
-            g_assert_cmpuint(next_rval, <, function->js_out_argc);
-
-            gi_type_info_extract_ffi_return_value(&return_info, &return_value, &return_gargument);
-
-            array_length_pos = g_type_info_get_array_length(&return_info);
-            if (array_length_pos >= 0) {
-                GIArgInfo array_length_arg;
-                GITypeInfo arg_type_info;
-                JS::RootedValue length(context);
-
-                g_callable_info_load_arg(function->info, array_length_pos, &array_length_arg);
-                g_arg_info_load_type(&array_length_arg, &arg_type_info);
-                array_length_pos += is_method ? 1 : 0;
-                arg_failed = !gjs_value_from_g_argument(context, &length,
-                                                        &arg_type_info,
-                                                        &out_arg_cvalues[array_length_pos],
-                                                        true);
-                if (!arg_failed && js_rval) {
-                    arg_failed = !gjs_value_from_explicit_array(context,
-                                                                return_values[next_rval],
-                                                                &return_info,
-                                                                &return_gargument,
-                                                                length.toInt32());
-                }
-                if (!arg_failed &&
-                    !r_value &&
-                    !gjs_g_argument_release_out_array(context,
-                                                      transfer,
-                                                      &return_info,
-                                                      length.toInt32(),
-                                                      &return_gargument))
-                    failed = true;
-            } else {
-                if (js_rval)
-                    arg_failed = !gjs_value_from_g_argument(context,
-                                                            return_values[next_rval],
-                                                            &return_info, &return_gargument,
-                                                            true);
-                /* Free GArgument, the JS::Value should have ref'd or copied it */
-                if (!arg_failed &&
-                    !r_value &&
-                    !gjs_g_argument_release(context,
-                                            transfer,
-                                            &return_info,
-                                            &return_gargument))
-                    failed = true;
-            }
-            if (arg_failed)
-                failed = true;
-
-            ++next_rval;
+    /* Process out arguments and return values. This loop is skipped if we fail
+     * the type conversion above, or if did_throw_gerror is true. */
+    js_arg_pos = 0;
+    // FIXME: js_arg_pos can overrun js_out_argc sometimes here
+    for (gi_arg_pos = -1; gi_arg_pos < gi_argc; gi_arg_pos++) {
+        GjsArgumentCache *cache = &function->arguments[gi_arg_pos];
+        GIArgument *out_value = &state.out_cvalues[gi_arg_pos];
+
+        if (!cache->marshal_out(context, cache, &state, out_value,
+                                return_values[js_arg_pos])) {
+            failed = true;
+            break;
         }
+
+        if (!gjs_arg_cache_is_skip_out(cache))
+            js_arg_pos++;
     }
 
+    g_assert(failed || did_throw_gerror || js_arg_pos == uint8_t(function->js_out_argc));
+
 release:
-    /* We walk over all args, release in args (if allocated) and convert
-     * all out args to JS
-     */
-    c_arg_pos = is_method ? 1 : 0;
+    /* In this loop we use ffi_arg_pos just to ensure we don't release stuff
+     * we haven't allocated yet, if we failed in type conversion above.
+     * Because we start from -1 (the return value), we need to process 1 more
+     * than processed_c_args */
+    ffi_arg_pos = is_method ? 1 : 0;
     postinvoke_release_failed = false;
-    for (gi_arg_pos = 0; gi_arg_pos < gi_argc && c_arg_pos < processed_c_args; gi_arg_pos++, c_arg_pos++) {
-        GIDirection direction;
-        GIArgInfo arg_info;
-        GITypeInfo arg_type_info;
-        GjsParamType param_type;
+    for (gi_arg_pos = -1; gi_arg_pos < gi_argc && ffi_arg_pos < (processed_c_args + 1); gi_arg_pos++, 
ffi_arg_pos++) {
+        GjsArgumentCache *cache = &function->arguments[gi_arg_pos];
+        GIArgument *in_value = &state.in_cvalues[gi_arg_pos];
+        GIArgument *out_value = &state.out_cvalues[gi_arg_pos];
 
-        g_callable_info_load_arg( (GICallableInfo*) function->info, gi_arg_pos, &arg_info);
-        direction = g_arg_info_get_direction(&arg_info);
-
-        g_arg_info_load_type(&arg_info, &arg_type_info);
-        param_type = function->param_types[gi_arg_pos];
-
-        if (direction == GI_DIRECTION_IN || direction == GI_DIRECTION_INOUT) {
-            GArgument *arg;
-            GITransfer transfer;
-
-            if (direction == GI_DIRECTION_IN) {
-                arg = &in_arg_cvalues[c_arg_pos];
-                transfer = g_arg_info_get_ownership_transfer(&arg_info);
-            } else {
-                arg = &inout_original_arg_cvalues[c_arg_pos];
-                /* For inout, transfer refers to what we get back from the function; for
-                 * the temporary C value we allocated, clearly we're responsible for
-                 * freeing it.
-                 */
-                transfer = GI_TRANSFER_NOTHING;
-            }
-            if (param_type == PARAM_CALLBACK) {
-                ffi_closure *closure = (ffi_closure *) arg->v_pointer;
-                if (closure) {
-                    GjsCallbackTrampoline *trampoline = (GjsCallbackTrampoline *) closure->user_data;
-                    /* CallbackTrampolines are refcounted because for notified/async closures
-                       it is possible to destroy it while in call, and therefore we cannot check
-                       its scope at this point */
-                    gjs_callback_trampoline_unref(trampoline);
-                    arg->v_pointer = NULL;
-                }
-            } else if (param_type == PARAM_ARRAY) {
-                gsize length;
-                GIArgInfo array_length_arg;
-                GITypeInfo array_length_type;
-                gint array_length_pos = g_type_info_get_array_length(&arg_type_info);
-
-                g_assert(array_length_pos >= 0);
-
-                g_callable_info_load_arg(function->info, array_length_pos, &array_length_arg);
-                g_arg_info_load_type(&array_length_arg, &array_length_type);
-
-                array_length_pos += is_method ? 1 : 0;
-
-                length = get_length_from_arg(in_arg_cvalues + array_length_pos,
-                                             g_type_info_get_tag(&array_length_type));
-
-                if (!gjs_g_argument_release_in_array(context,
-                                                     transfer,
-                                                     &arg_type_info,
-                                                     length,
-                                                     arg)) {
-                    postinvoke_release_failed = true;
-                }
-            } else if (param_type == PARAM_NORMAL) {
-                if (!gjs_g_argument_release_in_arg(context,
-                                                   transfer,
-                                                   &arg_type_info,
-                                                   arg)) {
-                    postinvoke_release_failed = true;
-                }
-            }
-        }
-
-        /* Don't free out arguments if function threw an exception or we failed
-         * earlier - note "postinvoke_release_failed" is separate from "failed".  We
-         * sync them up after this loop.
-         */
-        if (did_throw_gerror || failed)
+        /* Only process in or inout arguments if we failed, the rest is garbage */
+        if (failed && gjs_arg_cache_is_skip_in(cache))
             continue;
 
-        if ((direction == GI_DIRECTION_OUT || direction == GI_DIRECTION_INOUT) && param_type != 
PARAM_SKIPPED) {
-            GArgument *arg;
-            bool arg_failed = false;
-            gint array_length_pos;
-            JS::RootedValue array_length(context, JS::Int32Value(0));
-            GITransfer transfer;
-
-            g_assert(next_rval < function->js_out_argc);
-
-            arg = &out_arg_cvalues[c_arg_pos];
-
-            array_length_pos = g_type_info_get_array_length(&arg_type_info);
-
-            if (js_rval) {
-                if (array_length_pos >= 0) {
-                    GIArgInfo array_length_arg;
-                    GITypeInfo array_length_type_info;
-
-                    g_callable_info_load_arg(function->info, array_length_pos, &array_length_arg);
-                    g_arg_info_load_type(&array_length_arg, &array_length_type_info);
-                    array_length_pos += is_method ? 1 : 0;
-                    arg_failed = !gjs_value_from_g_argument(context, &array_length,
-                                                            &array_length_type_info,
-                                                            &out_arg_cvalues[array_length_pos],
-                                                            true);
-                    if (!arg_failed) {
-                        arg_failed = !gjs_value_from_explicit_array(context,
-                                                                    return_values[next_rval],
-                                                                    &arg_type_info,
-                                                                    arg,
-                                                                    array_length.toInt32());
-                    }
-                } else {
-                    arg_failed = !gjs_value_from_g_argument(context,
-                                                            return_values[next_rval],
-                                                            &arg_type_info,
-                                                            arg,
-                                                            true);
-                }
-            }
-
-            if (arg_failed)
-                postinvoke_release_failed = true;
-
-            /* Free GArgument, the JS::Value should have ref'd or copied it */
-            transfer = g_arg_info_get_ownership_transfer(&arg_info);
-            if (!arg_failed) {
-                if (array_length_pos >= 0) {
-                    gjs_g_argument_release_out_array(context,
-                                                     transfer,
-                                                     &arg_type_info,
-                                                     array_length.toInt32(),
-                                                     arg);
-                } else {
-                    gjs_g_argument_release(context,
-                                           transfer,
-                                           &arg_type_info,
-                                           arg);
-                }
-            }
-
-            /* For caller-allocates, what happens here is we allocate
-             * a structure above, then gjs_value_from_g_argument calls
-             * g_boxed_copy on it, and takes ownership of that.  So
-             * here we release the memory allocated above.  It would be
-             * better to special case this and directly hand JS the boxed
-             * object and tell gjs_boxed it owns the memory, but for now
-             * this works OK.  We could also alloca() the structure instead
-             * of slice allocating.
-             */
-            if (g_arg_info_is_caller_allocates(&arg_info)) {
-                GITypeTag type_tag;
-                GIBaseInfo* interface_info;
-                GIInfoType interface_type;
-                gsize size;
-
-                type_tag = g_type_info_get_tag(&arg_type_info);
-                g_assert(type_tag == GI_TYPE_TAG_INTERFACE);
-                interface_info = g_type_info_get_interface(&arg_type_info);
-                interface_type = g_base_info_get_type(interface_info);
-                if (interface_type == GI_INFO_TYPE_STRUCT) {
-                    size = g_struct_info_get_size((GIStructInfo*)interface_info);
-                } else if (interface_type == GI_INFO_TYPE_UNION) {
-                    size = g_union_info_get_size((GIUnionInfo*)interface_info);
-                } else {
-                    g_assert_not_reached();
-                }
-
-                g_slice_free1(size, out_arg_cvalues[c_arg_pos].v_pointer);
-                g_base_info_unref((GIBaseInfo*)interface_info);
-            }
-
-            ++next_rval;
+        if (!cache->release(context, cache, &state, in_value, out_value)) {
+            postinvoke_release_failed = true;
+            /* continue with the release even if we fail, to avoid leaks */
         }
     }
 
     if (postinvoke_release_failed)
         failed = true;
 
-    g_assert(failed || did_throw_gerror || next_rval == (guint8)function->js_out_argc);
-    g_assert_cmpuint(c_arg_pos, ==, processed_c_args);
+    g_assert_cmpuint(ffi_arg_pos, ==, processed_c_args + 1);
 
     if (function->js_out_argc > 0 && (!failed && !did_throw_gerror)) {
         /* if we have 1 return value or out arg, return that item
@@ -1350,7 +960,7 @@ release:
         }
 
         if (r_value) {
-            *r_value = return_gargument;
+            *r_value = state.out_cvalues[-1];
         }
     }
 
@@ -1401,10 +1011,12 @@ GJS_NATIVE_CONSTRUCTOR_DEFINE_ABSTRACT(function)
 static void
 uninit_cached_function_data (Function *function)
 {
-    if (function->info)
-        g_base_info_unref( (GIBaseInfo*) function->info);
-    if (function->param_types)
-        g_free(function->param_types);
+    g_clear_pointer(&function->info, g_base_info_unref);
+
+    /* Careful! function->arguments is one inside an array */
+    if (function->arguments)
+        g_free(&function->arguments[-1]);
+    function->arguments = nullptr;
 
     g_function_invoker_destroy(&function->invoker);
 }
@@ -1433,28 +1045,7 @@ get_num_arguments (JSContext *context,
                    JS::Value *vp)
 {
     GJS_GET_PRIV(context, argc, vp, rec, to, Function, priv);
-    int n_args, n_jsargs, i;
-
-    if (priv == NULL)
-        return false;
-
-    n_args = g_callable_info_get_n_args(priv->info);
-    n_jsargs = 0;
-    for (i = 0; i < n_args; i++) {
-        GIArgInfo arg_info;
-
-        if (priv->param_types[i] == PARAM_SKIPPED)
-            continue;
-
-        g_callable_info_load_arg(priv->info, i, &arg_info);
-
-        if (g_arg_info_get_direction(&arg_info) == GI_DIRECTION_OUT)
-            continue;
-
-        n_jsargs++;
-    }
-
-    rec.rval().setInt32(n_jsargs);
+    rec.rval().setInt32(priv->js_in_argc);
     return true;
 }
 
@@ -1485,7 +1076,7 @@ function_to_string (JSContext *context,
     for (i = 0; i < n_args; i++) {
         GIArgInfo arg_info;
 
-        if (priv->param_types[i] == PARAM_SKIPPED)
+        if (gjs_arg_cache_is_skip_in(&priv->arguments[i]))
             continue;
 
         g_callable_info_load_arg(priv->info, i, &arg_info);
@@ -1612,25 +1203,12 @@ init_cached_function_data (JSContext      *context,
 
     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, sizeof(GjsArgumentCache) * n_args, 0);
-      memset(out_args - 1, sizeof(GjsArgumentCache) * (n_args + 1), 0);*/
-    function->param_types = g_new0(GjsParamType, n_args);
+    /* arguments is one inside an array of n_args + 1, so
+     * arguments[-1] is the return value (if any) */
+    GjsArgumentCache *arguments = g_new0(GjsArgumentCache, n_args + 1) + 1;
 
     bool inc_counter;
-    if (!gjs_arg_cache_build_return(&out_args[-1], function->param_types,
+    if (!gjs_arg_cache_build_return(&arguments[-1], arguments,
                                     info, inc_counter)) {
         gjs_throw(context, "Function %s.%s cannot be called: the return value "
                   "is not introspectable.",
@@ -1645,79 +1223,35 @@ init_cached_function_data (JSContext      *context,
         GIDirection direction;
         GIArgInfo arg_info;
 
-        if (function->param_types[i] == PARAM_SKIPPED)
+        if (gjs_arg_cache_is_skip_in(&arguments[i]) ||
+            gjs_arg_cache_is_skip_out(&arguments[i]))
             continue;
 
         g_callable_info_load_arg((GICallableInfo*) info, i, &arg_info);
         direction = g_arg_info_get_direction(&arg_info);
 
-        if (direction == GI_DIRECTION_IN) {
-            if (!gjs_arg_cache_build_in_arg(&in_args[i], function->param_types,
-                                            i, &arg_info, inc_counter)) {
-                throw_not_introspectable_argument(context, info, &arg_info);
-                return false;
-            }
+        if (!gjs_arg_cache_build_arg(&arguments[i], arguments, i, direction,
+                                     &arg_info, info, inc_counter)) {
+            throw_not_introspectable_argument(context, info, &arg_info);
+            return false;
+        }
 
-            function->param_types[i] = in_args[i].param_type;
-            if (inc_counter)
+        if (inc_counter) {
+            if (direction == GI_DIRECTION_IN) {
                 in_argc++;
-        } else if (direction == GI_DIRECTION_INOUT) {
-            if (!gjs_arg_cache_build_inout_arg(&in_args[i], &out_args[i],
-                                               function->param_types,
-                                               i, &arg_info, inc_counter)) {
-                throw_not_introspectable_argument(context, info, &arg_info);
-                return false;
-            }
-
-            function->param_types[i] = in_args[i].param_type;
-            if (inc_counter) {
+            } else if (direction == GI_DIRECTION_INOUT) {
                 in_argc++;
                 out_argc++;
-            }
-        } else { /* GI_DIRECTION_OUT */
-            if (out_args[i].param_type == PARAM_SKIPPED)
-                continue;
-
-            if (!gjs_arg_cache_build_out_arg(&out_args[i],
-                                             function->param_types,
-                                             i, &arg_info, inc_counter)) {
-                throw_not_introspectable_argument(context, info, &arg_info);
-                return false;
-            }
-
-            function->param_types[i] = out_args[i].param_type;
-            if (inc_counter)
+            } else { /* GI_DIRECTION_OUT */
                 out_argc++;
+            }
         }
     }
 
-    function->in_arguments = g_new(GjsArgumentCache, in_argc);
-    function->out_arguments = g_new(GjsArgumentCache, out_argc);
-
-    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;
-    }
-
-    for (i = 0; i < n_args; i++) {
-        if (function->param_types[i] == PARAM_SKIPPED)
-            continue;
-
-        GIArgInfo arg_info;
-        g_callable_info_load_arg((GICallableInfo*) info, i, &arg_info);
-        GIDirection direction = g_arg_info_get_direction(&arg_info);
-
-        if (direction == GI_DIRECTION_IN || direction == GI_DIRECTION_INOUT) {
-            function->in_arguments[function->js_in_argc] = in_args[i];
-            function->js_in_argc++;
-        }
-        if (direction == GI_DIRECTION_OUT || direction == GI_DIRECTION_INOUT) {
-            function->out_arguments[function->js_out_argc] = out_args[i];
-            function->js_out_argc++;
-        }
-    }
+    function->arguments = arguments;
 
+    function->js_in_argc = in_argc;
+    function->js_out_argc = out_argc;
     function->info = info;
 
     g_base_info_ref((GIBaseInfo*) function->info);
diff --git a/gi/function.h b/gi/function.h
index 9d3283a..36b23c2 100644
--- a/gi/function.h
+++ b/gi/function.h
@@ -64,6 +64,12 @@ GjsCallbackTrampoline* gjs_callback_trampoline_new(JSContext       *context,
 void gjs_callback_trampoline_unref(GjsCallbackTrampoline *trampoline);
 void gjs_callback_trampoline_ref(GjsCallbackTrampoline *trampoline);
 
+typedef struct {
+    GIArgument *in_cvalues;
+    GIArgument *out_cvalues;
+    GIArgument *inout_original_cvalues;
+} GjsFunctionCallState;
+
 JSObject *gjs_define_function(JSContext       *context,
                               JS::HandleObject in_object,
                               GType            gtype,


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