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



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

    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.
    
    (Philip Chimento: rebased and fixed coding style and bugs.)

 gi/arg-cache.cpp | 657 ++++++++++++++++++++++++++++++++++++-------
 gi/arg-cache.h   |  71 +++--
 gi/arg.cpp       |  50 +---
 gi/arg.h         |  10 +-
 gi/function.cpp  | 838 ++++++++++++-------------------------------------------
 gi/function.h    |   6 +
 6 files changed, 807 insertions(+), 825 deletions(-)
---
diff --git a/gi/arg-cache.cpp b/gi/arg-cache.cpp
index d7d039b7..746aba5b 100644
--- a/gi/arg-cache.cpp
+++ b/gi/arg-cache.cpp
@@ -28,159 +28,628 @@
 #include <girepository.h>
 #include <glib.h>
 
+#include <js/RootingAPI.h>
+#include <js/TypeDecls.h>
+#include <js/Value.h>
+#include <jsapi.h>        // for JS_TypeOfValue
+#include <jsfriendapi.h>  // for JS_GetObjectFunction
+
 #include "gi/arg-cache.h"
 #include "gi/arg.h"
 #include "gi/function.h"
 #include "gjs/jsapi-util.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);
+}
+
+// A helper function to retrieve array lengths from a GIArgument (letting the
+// compiler generate good instructions in case of big endian machines)
+GJS_USE
+static size_t gjs_g_argument_get_array_length(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_array_length(GITypeTag tag, GIArgument* arg,
+                                            size_t value) {
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+    // In a little endian system, the first bytes of an unsigned long value are
+    // the same value, downcasted, and no code is needed. Also, we ignore the
+    // sign, as we're just moving bits here.
+    (void)tag;
+    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.
+
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_marshal_skipped_in(JSContext*, GjsArgumentCache*,
+                                   GjsFunctionCallState*, GIArgument*,
+                                   JS::HandleValue) {
+    return true;
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_marshal_generic_in_in(JSContext* cx, GjsArgumentCache* self,
+                                      GjsFunctionCallState*, 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);
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+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;
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+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_array_length(self->contents.array.length_tag,
+                                    &state->in_cvalues[length_pos], length);
+    arg->v_pointer = data;
+    return true;
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+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;
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+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, JS::InformalValueTypeName(value));
+            return false;
+        }
+
+        JS::RootedFunction func(cx, JS_GetObjectFunction(&value.toObject()));
+        GjsAutoCallableInfo callable_info =
+            g_type_info_get_interface(&self->type_info);
+        trampoline = gjs_callback_trampoline_new(
+            cx, func, callable_info, self->contents.callback.scope,
+            // FIXME: is_object_method ? obj : nullptr
+            nullptr, false);
+        closure = trampoline->closure;
+    }
+
+    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;
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_marshal_generic_out_in(JSContext*, GjsArgumentCache* self,
+                                       GjsFunctionCallState* state,
+                                       GIArgument* arg, JS::HandleValue) {
+    // Default value in case a broken C function doesn't fill in the pointer
+    state->out_cvalues[self->arg_pos].v_pointer = nullptr;
+    arg->v_pointer = &state->out_cvalues[self->arg_pos];
+    return true;
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_marshal_caller_allocates_in(JSContext*, GjsArgumentCache* self,
+                                            GjsFunctionCallState* state,
+                                            GIArgument* arg, JS::HandleValue) {
+    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;
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_marshal_skipped_out(JSContext*, GjsArgumentCache*,
+                                    GjsFunctionCallState*, GIArgument*,
+                                    JS::MutableHandleValue) {
+    return true;
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_marshal_generic_out_out(JSContext* cx, GjsArgumentCache* self,
+                                        GjsFunctionCallState*, GIArgument* arg,
+                                        JS::MutableHandleValue value) {
+    return gjs_value_from_g_argument(cx, value, &self->type_info, arg, true);
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+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_array_length(length_tag, length_arg);
+
+    return gjs_value_from_explicit_array(cx, value, &self->type_info, arg,
+                                         length);
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_marshal_skipped_release(JSContext*, GjsArgumentCache*,
+                                        GjsFunctionCallState*,
+                                        GIArgument* in_arg G_GNUC_UNUSED,
+                                        GIArgument* out_arg G_GNUC_UNUSED) {
+    return true;
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_marshal_generic_in_release(JSContext* cx,
+                                           GjsArgumentCache* self,
+                                           GjsFunctionCallState*,
+                                           GIArgument* in_arg,
+                                           GIArgument* out_arg G_GNUC_UNUSED) {
+    return gjs_g_argument_release_in_arg(cx, self->transfer, &self->type_info,
+                                         in_arg);
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_marshal_generic_out_release(JSContext* cx,
+                                            GjsArgumentCache* self,
+                                            GjsFunctionCallState*,
+                                            GIArgument* in_arg G_GNUC_UNUSED,
+                                            GIArgument* out_arg) {
+    return gjs_g_argument_release(cx, self->transfer, &self->type_info,
+                                  out_arg);
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+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);
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_marshal_explicit_array_out_release(
+    JSContext* cx, GjsArgumentCache* self, GjsFunctionCallState* state,
+    GIArgument* in_arg G_GNUC_UNUSED, 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_array_length(length_tag, length_arg);
+
+    return gjs_g_argument_release_out_array(cx, self->transfer,
+                                            &self->type_info, length, out_arg);
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_marshal_explicit_array_in_release(
+    JSContext* cx, GjsArgumentCache* self, GjsFunctionCallState* state,
+    GIArgument* in_arg, GIArgument* out_arg G_GNUC_UNUSED) {
+    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_array_length(length_tag, length_arg);
+
+    return gjs_g_argument_release_in_array(cx, self->transfer, &self->type_info,
+                                           length, in_arg);
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_marshal_explicit_array_inout_release(
+    JSContext* cx, GjsArgumentCache* self, GjsFunctionCallState* state,
+    GIArgument* in_arg G_GNUC_UNUSED, 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_array_length(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);
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_marshal_caller_allocates_release(
+    JSContext*, GjsArgumentCache* self, GjsFunctionCallState*,
+    GIArgument* in_arg, GIArgument* out_arg G_GNUC_UNUSED) {
+    g_slice_free1(self->contents.caller_allocates_size, in_arg->v_pointer);
+    return true;
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_marshal_callback_release(JSContext*, GjsArgumentCache*,
+                                         GjsFunctionCallState*,
+                                         GIArgument* in_arg,
+                                         GIArgument* out_arg G_GNUC_UNUSED) {
+    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, GICallableInfo* info,
-                                bool* inc_counter_out) {
+                                GjsArgumentCache* arguments,
+                                GICallableInfo* info, bool* inc_counter_out) {
     g_assert(inc_counter_out && "forgot out parameter");
 
     GITypeInfo return_type;
     g_callable_info_load_return_type(info, &return_type);
 
-    if (g_type_info_get_tag(&return_type) != GI_TYPE_TAG_VOID) {
-        *inc_counter_out = true;
-        self->param_type = PARAM_NORMAL;
-    } else {
+    if (g_type_info_get_tag(&return_type) == GI_TYPE_TAG_VOID) {
         *inc_counter_out = false;
-        self->param_type = PARAM_SKIPPED;
+        gjs_arg_cache_set_skip_all(self);
+        return true;
+    }
+
+    *inc_counter_out = 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;
+        }
     }
 
-    int length_pos = g_type_info_get_array_length(&return_type);
-    if (length_pos >= 0)
-        param_types[length_pos] = 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_out,
-                                const char** message_out) {
+bool gjs_arg_cache_build_arg(GjsArgumentCache* self,
+                             GjsArgumentCache* arguments, int gi_index,
+                             GIDirection direction, GIArgInfo* arg_info,
+                             GICallableInfo* callable, bool* inc_counter_out,
+                             const char** message_out) {
     g_assert(inc_counter_out && message_out && "forgot out parameter");
 
     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_out = true;
 
+    if (direction == GI_DIRECTION_OUT &&
+        g_arg_info_is_caller_allocates(arg_info)) {
+        GjsAutoBaseInfo 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(interface_info);
+        } else if (interface_type == GI_INFO_TYPE_UNION) {
+            size = g_union_info_get_size(interface_info);
+        } else {
+            // Can't do caller allocates on anything else
+            return false;
+        }
+
+        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) {
         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
+                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;
+                // argument list, then the null marshaller will be
+                // overwritten with the 'skipped' one. If no callback follows,
+                // then this is probably an unsupported function, so the
+                // function invocation code will check this and throw.
+                self->marshal_in = nullptr;
+                self->marshal_out = gjs_marshal_skipped_out;
+                self->release = gjs_marshal_skipped_release;
                 *inc_counter_out = 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_pos = g_arg_info_get_destroy(arg_info);
                 int closure_pos = g_arg_info_get_closure(arg_info);
 
                 if (destroy_pos >= 0)
-                    param_types[destroy_pos] = PARAM_SKIPPED;
+                    gjs_arg_cache_set_skip_all(&arguments[destroy_pos]);
 
                 if (closure_pos >= 0)
-                    param_types[closure_pos] = PARAM_SKIPPED;
+                    gjs_arg_cache_set_skip_all(&arguments[closure_pos]);
 
                 if (destroy_pos >= 0 && closure_pos < 0) {
                     *message_out =
                         "GDestroyNotify without user_data is not supported";
                     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;
             }
-        }
-    } 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;
         }
     }
 
-    return true;
-}
-
-bool gjs_arg_cache_build_out_arg(GjsArgumentCache* self,
-                                 GjsParamType* param_types, int gi_index,
-                                 GIArgInfo* arg_info, bool* inc_counter_out) {
-    g_assert(inc_counter_out && "forgot out parameter");
-
-    GITypeInfo type_info;
-    g_arg_info_load_type(arg_info, &type_info);
-
-    self->param_type = PARAM_NORMAL;
-    *inc_counter_out = 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) {
+            // FIXME: if !is_skip_all(&arguments[length_pos])
+            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_out = false;
-                }
+            if (length_pos < gi_index) {
+                // we already collected length_pos, remove it
+                *inc_counter_out = 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_out) {
-    g_assert(inc_counter_out && "forgot out parameter");
-
-    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_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;
-                }
-            }
-        }
+    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 4bd316a0..b1575038 100644
--- a/gi/arg-cache.h
+++ b/gi/arg-cache.h
@@ -35,38 +35,65 @@
 #include "gjs/macros.h"
 
 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 {
-        int dummy;
+        // for explicit array only
+        struct {
+            int length_pos;
+            GITypeTag length_tag;
+        } array;
+
+        struct {
+            GIScopeType scope;
+            int closure_pos;
+            int destroy_pos;
+        } callback;
+
+        // out caller allocates (FIXME: should be in object)
+        size_t caller_allocates_size;
     } contents;
 } GjsArgumentCache;
 
 GJS_JSAPI_RETURN_CONVENTION
-bool gjs_arg_cache_build_in_arg(GjsArgumentCache* self,
-                                GjsParamType* param_types, int gi_index,
-                                GIArgInfo* arg, bool* inc_counter_out,
-                                const char** message_out);
+bool gjs_arg_cache_build_arg(GjsArgumentCache* self,
+                             GjsArgumentCache* arguments, int gi_index,
+                             GIDirection direction, GIArgInfo* arg,
+                             GICallableInfo* callable, bool* inc_counter_out,
+                             const char** message_out);
 
 GJS_JSAPI_RETURN_CONVENTION
-bool gjs_arg_cache_build_out_arg(GjsArgumentCache* self,
-                                 GjsParamType* param_types, int gi_index,
-                                 GIArgInfo* arg, bool* inc_counter_out);
+bool gjs_arg_cache_build_return(GjsArgumentCache* self,
+                                GjsArgumentCache* arguments,
+                                GICallableInfo* info, bool* inc_counter_out);
 
-GJS_JSAPI_RETURN_CONVENTION
-bool gjs_arg_cache_build_inout_arg(GjsArgumentCache* in_self,
-                                   GjsArgumentCache* out_self,
-                                   GjsParamType* param_types, int gi_index,
-                                   GIArgInfo* arg, bool* inc_counter_out);
+GJS_USE
+static inline bool gjs_arg_cache_is_skip_in(GjsArgumentCache* cache) {
+    return cache->skip_in;
+}
 
-GJS_JSAPI_RETURN_CONVENTION
-bool gjs_arg_cache_build_return(GjsArgumentCache* self,
-                                GjsParamType* param_types, GICallableInfo* info,
-                                bool* inc_counter_out);
+GJS_USE
+static inline bool gjs_arg_cache_is_skip_out(GjsArgumentCache* cache) {
+    return cache->skip_out;
+}
 
 #endif  // GI_ARG_CACHE_H_
diff --git a/gi/arg.cpp b/gi/arg.cpp
index b41cd3ea..8b04ae38 100644
--- a/gi/arg.cpp
+++ b/gi/arg.cpp
@@ -1326,17 +1326,11 @@ throw_invalid_argument(JSContext      *context,
 }
 
 GJS_JSAPI_RETURN_CONVENTION
-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 found_length;
 
     gjs_debug_marshal(
@@ -1995,15 +1989,9 @@ _Pragma("GCC diagnostic pop")
             }
         }
 
-        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;
         }
@@ -2211,28 +2199,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);
-}
-
 GJS_JSAPI_RETURN_CONVENTION
 static bool
 gjs_array_from_g_list (JSContext             *context,
diff --git a/gi/arg.h b/gi/arg.h
index ec09d6fd..42693d3e 100644
--- a/gi/arg.h
+++ b/gi/arg.h
@@ -57,11 +57,11 @@ bool gjs_value_to_arg(JSContext      *context,
                       GIArgument     *arg);
 
 GJS_JSAPI_RETURN_CONVENTION
-bool gjs_value_to_explicit_array(JSContext       *context,
-                                 JS::HandleValue  value,
-                                 GIArgInfo       *arg_info,
-                                 GIArgument      *arg,
-                                 size_t          *length_p);
+bool gjs_array_to_explicit_array(JSContext* cx, 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);
 
 void gjs_gi_argument_init_default(GITypeInfo* type_info, GIArgument* arg);
 
diff --git a/gi/function.cpp b/gi/function.cpp
index f7900413..8c78a9cb 100644
--- a/gi/function.cpp
+++ b/gi/function.cpp
@@ -75,9 +75,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;
@@ -509,18 +507,6 @@ out:
     gjs->schedule_gc_if_needed();
 }
 
-/* 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::HandleFunction function,
     GICallableInfo* callable_info, GIScopeType scope,
@@ -627,32 +613,6 @@ GjsCallbackTrampoline* gjs_callback_trampoline_new(
     return trampoline;
 }
 
-/* an helper function to retrieve array lengths from a GArgument
-   (letting the compiler generate good instructions in case of
-   big endian machines) */
-GJS_USE
-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();
-}
-
 GJS_JSAPI_RETURN_CONVENTION
 static bool
 gjs_fill_method_instance(JSContext       *context,
@@ -802,40 +762,21 @@ 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;
-    gpointer *ffi_arg_pointers;
+    GjsFunctionCallState state;
     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 gi_argc, gi_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::RootedValueVector return_values(context);
-    guint8 next_rval = 0; /* index into return_values */
 
     /* Because we can't free a closure while we're in it, we defer
      * freeing until the next time a C function is invoked.  What
@@ -846,18 +787,14 @@ 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
-     * 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.
-     */
+    // ffi_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->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.
     if (args.length() > function->js_in_argc) {
         GjsAutoChar name = format_function_name(function, is_method);
         if (!JS::WarnUTF8(
@@ -873,233 +810,85 @@ 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 GIArgument 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 GIArgument array
+     * Use ffi_arg_pos to index inside ffi_arg_pointers
+     */
+
+    int ffi_argc = function->invoker.cif.nargs;
 
-    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);
+    if (is_method) {
+        state.in_cvalues = g_newa(GIArgument, gi_argc + 2) + 2;
+        state.out_cvalues = g_newa(GIArgument, gi_argc + 2) + 2;
+        state.inout_original_cvalues = g_newa(GIArgument, gi_argc + 2) + 2;
+    } else {
+        state.in_cvalues = g_newa(GIArgument, gi_argc + 1) + 1;
+        state.out_cvalues = g_newa(GIArgument, gi_argc + 1) + 1;
+        state.inout_original_cvalues = g_newa(GIArgument, gi_argc + 1) + 1;
+    }
+
+    void** ffi_arg_pointers = g_newa(void*, ffi_argc);
 
     failed = false;
-    c_arg_pos = 0; /* index into in_arg_cvalues, etc */
+    int 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;
-
-        g_callable_info_load_arg( (GICallableInfo*) function->info, gi_arg_pos, &arg_info);
-        direction = g_arg_info_get_direction(&arg_info);
-
-        gjs_debug_marshal(
-            GJS_DEBUG_GFUNCTION,
-            "Processing argument '%s' (direction %d), %d/%d GI args, "
-            "%d/%d C args, %d/%zu JS args",
-            g_base_info_get_name(&arg_info), direction, gi_arg_pos, gi_argc,
-            c_arg_pos, c_argc, js_arg_pos, args.length());
-
-        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;
-                    }
-
-                    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];
+    int 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;
 
-                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),
-                                  JS::InformalValueTypeName(current_arg));
-                        failed = true;
-                        break;
-                    }
-
-                    JS::RootedFunction func(
-                        context, JS_GetObjectFunction(&current_arg.toObject()));
-                    callable_info = (GICallableInfo*) g_type_info_get_interface(&ainfo);
-                    trampoline = gjs_callback_trampoline_new(
-                        context, func, 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;
-            }
-
-            case PARAM_UNKNOWN:
-                gjs_throw(context,
-                          "Error invoking %s.%s: impossible to determine what "
-                          "to pass to the '%s' argument. It may be that the "
-                          "function is unsupported, or there may be a bug in "
-                          "its annotations.",
-                          g_base_info_get_namespace(function->info),
-                          g_base_info_get_name(function->info),
-                          g_base_info_get_name(&arg_info));
-                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 (!cache->marshal_in) {
+            gjs_throw(context,
+                      "Error invoking %s.%s: impossible to determine what "
+                      "to pass to the '%s' argument. It may be that the "
+                      "function is unsupported, or there may be a bug in "
+                      "its annotations.",
+                      g_base_info_get_namespace(function->info),
+                      g_base_info_get_name(function->info), cache->arg_name);
+            failed = true;
+            break;
+        }
 
-            if (failed) {
-                /* Exit from the loop */
-                break;
-            }
+        JS::RootedValue js_in_arg(context);
+        if (size_t(js_arg_pos) < args.length())
+            js_in_arg = args[js_arg_pos];
 
-            if (!failed && !arg_removed)
-                ++js_arg_pos;
+        if (!cache->marshal_in(context, cache, &state, in_value, js_in_arg)) {
+            failed = true;
+            break;
         }
 
-        if (failed)
-            break;
+        if (!gjs_arg_cache_is_skip_in(cache))
+            js_arg_pos++;
 
         processed_c_args++;
     }
@@ -1112,27 +901,34 @@ 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
@@ -1147,269 +943,77 @@ 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) {
-        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;
-        }
+    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]);
     }
 
-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;
-    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;
-
-        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];
+    // 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;
+    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 (direction == GI_DIRECTION_IN || direction == GI_DIRECTION_INOUT) {
-            GArgument *arg;
-            GITransfer transfer;
-
-            if (direction == GI_DIRECTION_IN) {
-                arg = &in_arg_cvalues[c_arg_pos];
-
-                // If we failed before calling the function, or if the function
-                // threw an exception, then any GI_TRANSFER_EVERYTHING or
-                // GI_TRANSFER_CONTAINER parameters were not transferred. Treat
-                // them as GI_TRANSFER_NOTHING so that they are freed.
-                if (!failed && !did_throw_gerror)
-                    transfer = g_arg_info_get_ownership_transfer(&arg_info);
-                else
-                    transfer = GI_TRANSFER_NOTHING;
-            } 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;
-            }
-
-            gjs_debug_marshal(
-                GJS_DEBUG_GFUNCTION,
-                "Releasing in-argument '%s' (direction %d, transfer %d), "
-                "%d/%d GI args, %d/%d C args",
-                g_base_info_get_name(&arg_info), direction, transfer,
-                gi_arg_pos, gi_argc, c_arg_pos, processed_c_args);
-
-            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;
-                }
+        JS::RootedValue js_out_arg(context);
+        if (js_rval) {
+            if (!cache->marshal_out(context, cache, &state, out_value,
+                                    &js_out_arg)) {
+                failed = true;
+                break;
             }
         }
 
-        /* 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)
-            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 (!gjs_arg_cache_is_skip_out(cache)) {
             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 (!return_values.append(js_out_arg)) {
+                    JS_ReportOutOfMemory(context);
+                    failed = true;
+                    break;
                 }
             }
+            js_arg_pos++;
+        }
+    }
 
-            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) {
-                    if (!gjs_g_argument_release_out_array(
-                            context, transfer, &arg_type_info,
-                            array_length.toInt32(), arg))
-                        postinvoke_release_failed = true;
-                } else {
-                    if (!gjs_g_argument_release(context, transfer,
-                                                &arg_type_info, arg))
-                        postinvoke_release_failed = true;
-                }
-            }
+    g_assert(failed || did_throw_gerror ||
+             js_arg_pos == uint8_t(function->js_out_argc));
 
-            /* 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();
-                }
+release:
+    // 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 = -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];
+
+        // Only process in or inout arguments if we failed, the rest is garbage
+        if (failed && gjs_arg_cache_is_skip_in(cache))
+            continue;
 
-                g_slice_free1(size, out_arg_cvalues[c_arg_pos].v_pointer);
-                g_base_info_unref((GIBaseInfo*)interface_info);
-            }
+        // Save the return GIArgument if it was requested
+        if (r_value && gi_arg_pos == -1) {
+            *r_value = *out_value;
+            continue;
+        }
 
-            ++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(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
@@ -1429,10 +1033,6 @@ release:
                 }
             }
         }
-
-        if (r_value) {
-            *r_value = return_gargument;
-        }
     }
 
     if (!failed && did_throw_gerror) {
@@ -1482,10 +1082,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);
 }
@@ -1512,28 +1114,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;
 }
 
@@ -1562,7 +1143,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);
@@ -1683,27 +1264,13 @@ 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, 0, sizeof(GjsArgumentCache) * n_args);
-    memset(out_args - 1, 0, sizeof(GjsArgumentCache) * (n_args + 1));
-    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, info,
+    if (!gjs_arg_cache_build_return(&arguments[-1], arguments, info,
                                     &inc_counter)) {
-        g_free(function->param_types);
         gjs_throw(context,
                   "Function %s.%s cannot be called: the return value is not "
                   "introspectable.",
@@ -1717,89 +1284,36 @@ 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) {
-            const char* message = nullptr;
-            if (!gjs_arg_cache_build_in_arg(&in_args[i], function->param_types,
-                                            i, &arg_info, &inc_counter,
-                                            &message)) {
-                g_free(function->param_types);
-                return throw_not_introspectable_argument(context, info,
-                                                         &arg_info, message);
-            }
+        const char* message = nullptr;
+        if (!gjs_arg_cache_build_arg(&arguments[i], arguments, i, direction,
+                                     &arg_info, info, &inc_counter, &message)) {
+            return throw_not_introspectable_argument(context, info, &arg_info,
+                                                     message);
+        }
 
-            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)) {
-                g_free(function->param_types);
-                return throw_not_introspectable_argument(context, info,
-                                                         &arg_info, nullptr);
-            }
-
-            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)) {
-                g_free(function->param_types);
-                return throw_not_introspectable_argument(context, info,
-                                                         &arg_info, nullptr);
-            }
-
-            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 ||
-            function->param_types[i] == PARAM_UNKNOWN)
-            continue;
-
-        GIArgInfo arg_info;
-        g_callable_info_load_arg(info, i, &arg_info);
-        GIDirection direction = g_arg_info_get_direction(&arg_info);
-
-        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++;
-        }
-    }
+    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 1038f5fb..792317c4 100644
--- a/gi/function.h
+++ b/gi/function.h
@@ -67,6 +67,12 @@ GjsCallbackTrampoline* gjs_callback_trampoline_new(
 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;
+
 GJS_JSAPI_RETURN_CONVENTION
 JSObject *gjs_define_function(JSContext       *context,
                               JS::HandleObject in_object,


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