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




commit 959b4f9c914a2ebd22ca38bb42aff5d33cfee6b8
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 | 617 +++++++++++++++++++++++++++++++++----
 gi/arg-cache.h   |  55 +++-
 gi/arg.cpp       |  46 +--
 gi/arg.h         |  10 +-
 gi/function.cpp  | 901 +++++++++++++------------------------------------------
 gi/function.h    |  13 +
 6 files changed, 825 insertions(+), 817 deletions(-)
---
diff --git a/gi/arg-cache.cpp b/gi/arg-cache.cpp
index 6a794b85..963937ec 100644
--- a/gi/arg-cache.cpp
+++ b/gi/arg-cache.cpp
@@ -23,61 +23,533 @@
 
 #include <config.h>
 
+#include <stdint.h>
 #include <string.h>
 
+#include <ffi.h>
 #include <girepository.h>
 #include <glib.h>
 
+#include <js/RootingAPI.h>
 #include <js/TypeDecls.h>
+#include <jsapi.h>        // for JS_TypeOfValue
+#include <jsfriendapi.h>  // for JS_GetObjectFunction
+#include <jspubtd.h>      // for JSTYPE_FUNCTION
 
 #include "gi/arg-cache.h"
+#include "gi/arg-inl.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 gjs_arg_get<int8_t>(arg);
+    if (tag == GI_TYPE_TAG_UINT8)
+        return gjs_arg_get<uint8_t>(arg);
+    if (tag == GI_TYPE_TAG_INT16)
+        return gjs_arg_get<int16_t>(arg);
+    if (tag == GI_TYPE_TAG_UINT16)
+        return gjs_arg_get<uint16_t>(arg);
+    if (tag == GI_TYPE_TAG_INT32)
+        return gjs_arg_get<int32_t>(arg);
+    if (tag == GI_TYPE_TAG_UINT32)
+        return gjs_arg_get<uint32_t>(arg);
+    if (tag == GI_TYPE_TAG_INT64)
+        return gjs_arg_get<int64_t>(arg);
+    if (tag == GI_TYPE_TAG_UINT64)
+        return gjs_arg_get<uint64_t>(arg);
+    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:
+            gjs_arg_set<int8_t>(arg, value);
+            break;
+        case GI_TYPE_TAG_UINT8:
+            gjs_arg_set<uint8_t>(arg, value);
+            break;
+        case GI_TYPE_TAG_INT16:
+            gjs_arg_set<int16_t>(arg, value);
+            break;
+        case GI_TYPE_TAG_UINT16:
+            gjs_arg_set<uint16_t>(arg, value);
+            break;
+        case GI_TYPE_TAG_INT32:
+            gjs_arg_set<int32_t>(arg, value);
+            break;
+        case GI_TYPE_TAG_UINT32:
+            gjs_arg_set<uint32_t>(arg, value);
+            break;
+        case GI_TYPE_TAG_INT64:
+            gjs_arg_set<int64_t>(arg, value);
+            break;
+        case GI_TYPE_TAG_UINT64:
+            gjs_arg_set<uint64_t>(arg, 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;
+    gjs_arg_set(arg, &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);
+    gjs_arg_set(arg, 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 (!gjs_arg_get<void*>(arg)) {
+        // 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.
+        gjs_arg_unset<void*>(&state->in_cvalues[length_pos]);
+        gjs_arg_unset<int>(&state->out_cvalues[length_pos]);
+        gjs_arg_unset<int>(&state->inout_original_cvalues[length_pos]);
+
+        gjs_arg_unset<void*>(&state->out_cvalues[ix]);
+        gjs_arg_unset<void*>(&state->inout_original_cvalues[ix]);
+    } else {
+        state->out_cvalues[length_pos] =
+            state->inout_original_cvalues[length_pos] =
+                state->in_cvalues[length_pos];
+        gjs_arg_set(&state->in_cvalues[length_pos],
+                    &state->out_cvalues[length_pos]);
+
+        state->out_cvalues[ix] = state->inout_original_cvalues[ix] = *arg;
+        gjs_arg_set(arg, &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,
+                                                 state->instance_object, false);
+        closure = trampoline->closure;
+    }
+
+    int destroy_pos = self->contents.callback.destroy_pos;
+    if (destroy_pos >= 0) {
+        gjs_arg_set(&state->in_cvalues[destroy_pos],
+                    trampoline ? gjs_destroy_notify_callback : nullptr);
+    }
+    int closure_pos = self->contents.callback.closure_pos;
+    if (closure_pos >= 0)
+        gjs_arg_set(&state->in_cvalues[closure_pos], 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);
+    }
+    gjs_arg_set(arg, 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
+    gjs_arg_unset<void*>(&state->out_cvalues[self->arg_pos]);
+    gjs_arg_set(arg,
+                &gjs_arg_member<void*>(&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);
+    gjs_arg_set(arg, blob);
+    gjs_arg_set(&state->out_cvalues[self->arg_pos], 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* state,
+                                           GIArgument* in_arg,
+                                           GIArgument* out_arg G_GNUC_UNUSED) {
+    GITransfer transfer =
+        state->call_completed ? self->transfer : GI_TRANSFER_NOTHING;
+    return gjs_g_argument_release_in_arg(cx, 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);
+
+    GITransfer transfer =
+        state->call_completed ? self->transfer : GI_TRANSFER_NOTHING;
+
+    return gjs_g_argument_release_in_array(cx, 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 (gjs_arg_get<void*>(original_out_arg) != gjs_arg_get<void*>(out_arg) &&
+        !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,
+                  gjs_arg_get<void*>(in_arg));
+    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 = gjs_arg_get<ffi_closure*>(in_arg);
+    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);
+    gjs_arg_unset<void*>(in_arg);
+    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(JSContext*, GjsArgumentCache* self,
-                                GjsParamType* param_types,
+                                GjsArgumentCache* arguments,
                                 GICallableInfo* callable,
                                 bool* inc_counter_out) {
     g_assert(inc_counter_out && "forgot out parameter");
 
-    GITypeInfo return_type;
-    g_callable_info_load_return_type(callable, &return_type);
+    g_callable_info_load_return_type(callable, &self->type_info);
 
-    if (g_type_info_get_tag(&return_type) == GI_TYPE_TAG_VOID) {
+    if (g_type_info_get_tag(&self->type_info) == 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->param_type = PARAM_NORMAL;
+    self->arg_pos = -1;
+    self->arg_name = "return value";
+    self->transfer = g_callable_info_get_caller_owns(callable);
+    self->nullable = false;  // We don't really care for return values
+    self->is_return = true;
 
-    if (g_type_info_get_tag(&return_type) == GI_TYPE_TAG_ARRAY) {
-        int length_pos = g_type_info_get_array_length(&return_type);
+    if (g_type_info_get_tag(&self->type_info) == GI_TYPE_TAG_ARRAY) {
+        int length_pos = g_type_info_get_array_length(&self->type_info);
         if (length_pos >= 0) {
-            param_types[length_pos] = PARAM_SKIPPED;
+            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(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);
+
             return true;
         }
     }
 
+    // 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_arg(JSContext* cx, GjsArgumentCache* self,
-                             GjsParamType* param_types, int gi_index,
+                             GjsArgumentCache* arguments, int gi_index,
                              GIDirection direction, GIArgInfo* arg,
                              GICallableInfo* callable, bool* inc_counter_out) {
     g_assert(inc_counter_out && "forgot out parameter");
 
-    GITypeInfo type_info;
-    g_arg_info_load_type(arg, &type_info);
+    self->arg_pos = gi_index;
+    self->arg_name = g_base_info_get_name(arg);
+    g_arg_info_load_type(arg, &self->type_info);
+    self->transfer = g_arg_info_get_ownership_transfer(arg);
+    self->nullable = g_arg_info_may_be_null(arg);
+    self->is_return = false;
 
-    self->param_type = PARAM_NORMAL;
+    if (direction == GI_DIRECTION_IN)
+        self->skip_out = true;
+    else if (direction == GI_DIRECTION_OUT)
+        self->skip_in = true;
     *inc_counter_out = true;
 
-    GITypeTag type_tag = g_type_info_get_tag(&type_info);
+    if (direction == GI_DIRECTION_OUT && g_arg_info_is_caller_allocates(arg)) {
+        GjsAutoBaseInfo interface_info =
+            g_type_info_get_interface(&self->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 {
+            gjs_throw(cx,
+                      "Unsupported type %s for argument %s with (out "
+                      "caller-allocates)",
+                      g_info_type_to_string(interface_type), self->arg_name);
+            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(&self->type_info);
     if (type_tag == GI_TYPE_TAG_INTERFACE) {
-        GjsAutoBaseInfo interface_info = g_type_info_get_interface(&type_info);
+        GjsAutoBaseInfo interface_info =
+            g_type_info_get_interface(&self->type_info);
         if (interface_info.type() == GI_INFO_TYPE_CALLBACK) {
             if (direction != GI_DIRECTION_IN) {
                 // Can't do callbacks for out or inout
@@ -85,8 +557,7 @@ bool gjs_arg_cache_build_arg(JSContext* cx, GjsArgumentCache* self,
                           "Function %s.%s has a callback out-argument %s, not "
                           "supported",
                           g_base_info_get_namespace(callable),
-                          g_base_info_get_name(callable),
-                          g_base_info_get_name(arg));
+                          g_base_info_get_name(callable), self->arg_name);
                 return false;
             }
 
@@ -94,23 +565,27 @@ bool gjs_arg_cache_build_arg(JSContext* cx, GjsArgumentCache* self,
                 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);
                 int closure_pos = g_arg_info_get_closure(arg);
 
                 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) {
                     gjs_throw(cx,
@@ -120,58 +595,72 @@ bool gjs_arg_cache_build_arg(JSContext* cx, GjsArgumentCache* self,
                               g_base_info_get_name(callable));
                     return false;
                 }
+
+                self->contents.callback.scope = g_arg_info_get_scope(arg);
+                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;
-}
+    if (type_tag == GI_TYPE_TAG_ARRAY &&
+        g_type_info_get_array_type(&self->type_info) == GI_ARRAY_TYPE_C) {
+        int length_pos = g_type_info_get_array_length(&self->type_info);
 
-bool gjs_arg_cache_build_inout_arg(JSContext*, GjsArgumentCache* in_self,
-                                   GjsArgumentCache* out_self,
-                                   GjsParamType* param_types, int gi_index,
-                                   GIArgInfo* arg, bool* inc_counter_out) {
-    g_assert(inc_counter_out && "forgot out parameter");
+        if (length_pos >= 0) {
+            gjs_arg_cache_set_skip_all(&arguments[length_pos]);
 
-    GITypeInfo type_info;
-    g_arg_info_load_type(arg, &type_info);
+            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;
 
-    in_self->param_type = PARAM_NORMAL;
-    out_self->param_type = PARAM_NORMAL;
-    *inc_counter_out = true;
+                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;
-                in_self->param_type = PARAM_ARRAY;
-                out_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;
         }
     }
 
+    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 e3ddb7dd..a7591204 100644
--- a/gi/arg-cache.h
+++ b/gi/arg-cache.h
@@ -26,33 +26,66 @@
 
 #include <config.h>
 
+#include <stddef.h>
+
 #include <girepository.h>
 
+#include <js/RootingAPI.h>
 #include <js/TypeDecls.h>
 
-#include "gi/function.h"
 #include "gjs/macros.h"
 
+struct GjsFunctionCallState;
+
 typedef struct _GjsArgumentCache {
-    // For compatibility
-    GjsParamType param_type;
+    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);
+
+    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 {
+        // 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_arg(JSContext* cx, GjsArgumentCache* self,
-                             GjsParamType* param_types, int gi_index,
+                             GjsArgumentCache* arguments, int gi_index,
                              GIDirection direction, GIArgInfo* arg,
                              GICallableInfo* callable, bool* inc_counter_out);
 
-GJS_JSAPI_RETURN_CONVENTION
-bool gjs_arg_cache_build_inout_arg(JSContext* cx, GjsArgumentCache* in_self,
-                                   GjsArgumentCache* out_self,
-                                   GjsParamType* param_types, int gi_index,
-                                   GIArgInfo* arg, bool* inc_counter_out);
-
 GJS_JSAPI_RETURN_CONVENTION
 bool gjs_arg_cache_build_return(JSContext* cx, GjsArgumentCache* self,
-                                GjsParamType* param_types, GICallableInfo* info,
+                                GjsArgumentCache* arguments,
+                                GICallableInfo* callable,
                                 bool* inc_counter_out);
 
 #endif  // GI_ARG_CACHE_H_
diff --git a/gi/arg.cpp b/gi/arg.cpp
index 038cffe8..220cc8b8 100644
--- a/gi/arg.cpp
+++ b/gi/arg.cpp
@@ -1496,17 +1496,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(
@@ -2172,15 +2166,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;
         }
@@ -2370,24 +2358,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(arg_info),
-        GJS_ARGUMENT_ARGUMENT, g_arg_info_get_ownership_transfer(arg_info),
-        g_arg_info_may_be_null(arg_info), &gjs_arg_member<void*>(arg),
-        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 12fcf516..7b75e578 100644
--- a/gi/function.cpp
+++ b/gi/function.cpp
@@ -44,10 +44,8 @@
 #include <js/Realm.h>  // for GetRealmFunctionPrototype
 #include <js/RootingAPI.h>
 #include <js/TypeDecls.h>
-#include <js/Value.h>
 #include <js/Warnings.h>
 #include <jsapi.h>        // for HandleValueArray, JS_GetElement
-#include <jsfriendapi.h>  // for JS_GetObjectFunction
 #include <jspubtd.h>      // for JSProto_TypeError, JSTYPE_FUNCTION
 
 #include "gi/arg-cache.h"
@@ -78,9 +76,7 @@
 typedef struct {
     GICallableInfo* info;
 
-    GjsParamType *param_types;
-    GjsArgumentCache* in_arguments;
-    GjsArgumentCache* out_arguments;
+    GjsArgumentCache* arguments;
 
     uint8_t js_in_argc;
     guint8 js_out_argc;
@@ -515,18 +511,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, bool has_scope_object,
@@ -623,33 +607,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 size_t get_length_from_arg(GArgument* arg, GITypeTag tag) {
-    switch (tag) {
-        case GI_TYPE_TAG_INT8:
-            return gjs_arg_get<int8_t>(arg);
-        case GI_TYPE_TAG_UINT8:
-            return gjs_arg_get<uint8_t>(arg);
-        case GI_TYPE_TAG_INT16:
-            return gjs_arg_get<int16_t>(arg);
-        case GI_TYPE_TAG_UINT16:
-            return gjs_arg_get<uint16_t>(arg);
-        case GI_TYPE_TAG_INT32:
-            return gjs_arg_get<int32_t>(arg);
-        case GI_TYPE_TAG_UINT32:
-            return gjs_arg_get<uint32_t>(arg);
-        case GI_TYPE_TAG_INT64:
-            return gjs_arg_get<int64_t>(arg);
-        case GI_TYPE_TAG_UINT64:
-            return gjs_arg_get<uint64_t>(arg);
-        default:
-            g_assert_not_reached();
-    }
-}
-
 GJS_JSAPI_RETURN_CONVENTION
 static bool
 gjs_fill_method_instance(JSContext       *context,
@@ -781,11 +738,14 @@ complete_async_calls(void)
 }
 
 static void* get_return_ffi_pointer_from_giargument(
-    GITypeInfo* return_info, GIFFIReturnValue* return_value) {
+    GjsArgumentCache* return_arg, GIFFIReturnValue* return_value) {
     // This should be the inverse of gi_type_info_extract_ffi_return_value().
+    if (return_arg->skip_out)
+        return nullptr;
+
     // FIXME: Note that v_long and v_ulong don't have type-safe template
     // overloads yet, and I don't understand why they won't compile
-    switch (g_type_info_get_tag(return_info)) {
+    switch (g_type_info_get_tag(&return_arg->type_info)) {
         case GI_TYPE_TAG_INT8:
         case GI_TYPE_TAG_INT16:
         case GI_TYPE_TAG_INT32:
@@ -805,7 +765,8 @@ static void* get_return_ffi_pointer_from_giargument(
         case GI_TYPE_TAG_DOUBLE:
             return &gjs_arg_member<double>(return_value);
         case GI_TYPE_TAG_INTERFACE: {
-            GjsAutoBaseInfo info = g_type_info_get_interface(return_info);
+            GjsAutoBaseInfo info =
+                g_type_info_get_interface(&return_arg->type_info);
 
             switch (g_base_info_get_type(info)) {
                 case GI_INFO_TYPE_ENUM:
@@ -833,28 +794,10 @@ static bool gjs_invoke_c_function(JSContext* context, Function* function,
     g_assert((args.isConstructing() || !this_obj) &&
              "If not a constructor, then pass the 'this' object via CallArgs");
 
-    /* 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;
+    void* return_value_p;  // will point inside the return GIArgument union
     GIFFIReturnValue 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;
     bool can_throw_gerror;
     bool did_throw_gerror = false;
     GError *local_error = NULL;
@@ -862,10 +805,7 @@ static bool gjs_invoke_c_function(JSContext* context, Function* function,
 
     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
@@ -876,18 +816,15 @@ static bool gjs_invoke_c_function(JSContext* context, Function* function,
     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;
+    unsigned ffi_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);
 
@@ -903,270 +840,106 @@ static bool gjs_invoke_c_function(JSContext* context, Function* function,
         return false;
     }
 
-    g_callable_info_load_return_type( (GICallableInfo*) function->info, &return_info);
-    return_tag = g_type_info_get_tag(&return_info);
+    // These arrays hold argument pointers.
+    // - state.in_cvalues: C values which are passed on input (in or inout)
+    // - state.out_cvalues: C values which are returned as arguments (out or
+    //   inout)
+    // - state.inout_original_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
+    //   state.in_cvalues or state.out_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
+    //   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.
+    GjsFunctionCallState state(context);
+    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;
+    }
 
-    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);
+    void** ffi_arg_pointers = g_newa(void*, ffi_argc);
 
     failed = false;
-    c_arg_pos = 0; /* index into in_arg_cvalues, etc */
-    js_arg_pos = 0; /* index into argv */
+    unsigned ffi_arg_pos = 0;  // index into ffi_arg_pointers
+    unsigned js_arg_pos = 0;   // index into args
 
     JS::RootedObject obj(context, this_obj);
     if (!args.isConstructing() && !args.computeThis(context, &obj))
         return false;
 
     if (is_method) {
-        if (!gjs_fill_method_instance(context, obj, function,
-                                      &in_arg_cvalues[0], is_object_method))
+        GIArgument* in_value = &state.in_cvalues[-2];
+        if (!gjs_fill_method_instance(context, obj, function, in_value,
+                                      is_object_method))
             return false;
-        ffi_arg_pointers[0] = &in_arg_cvalues[0];
-        ++c_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/%u 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) {
-                        gjs_arg_set(&in_arg_cvalues[c_arg_pos],
-                                    g_slice_alloc0(size));
-                        gjs_arg_set(
-                            &out_arg_cvalues[c_arg_pos],
-                            gjs_arg_get<void*>(&in_arg_cvalues[c_arg_pos]));
-                    }
-                } else {
-                    failed = true;
-                }
-                if (failed)
-                    gjs_throw(context, "Unsupported type %s for (out caller-allocates)", 
g_type_tag_to_string(type_tag));
-            } else {
-                gjs_arg_unset<void*>(&out_arg_cvalues[c_arg_pos]);
-                gjs_arg_set(
-                    &in_arg_cvalues[c_arg_pos],
-                    &gjs_arg_member<void*>(&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),
-                                  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,
-                        false);
-                    if (!trampoline) {
-                        failed = true;
-                        break;
-                    }
-                    if (scope == GI_SCOPE_TYPE_NOTIFIED && is_object_method) {
-                        auto* priv = ObjectInstance::for_js(context, obj);
-                        if (!priv) {
-                            gjs_throw(
-                                context,
-                                "Signal connected to wrong type of object");
-                            failed = true;
-                            break;
-                        }
-
-                        priv->associate_closure(context,
-                                                trampoline->js_function);
-                    }
-                    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);
-                    gjs_arg_set(
-                        &in_arg_cvalues[c_pos],
-                        trampoline ? gjs_destroy_notify_callback : nullptr);
-                }
-                if (closure_pos >= 0) {
-                    gint c_pos = is_method ? closure_pos + 1 : closure_pos;
-                    g_assert (function->param_types[closure_pos] == PARAM_SKIPPED);
-                    gjs_arg_set(&in_arg_cvalues[c_pos], trampoline);
-                }
+        ffi_arg_pointers[ffi_arg_pos] = in_value;
+        ++ffi_arg_pos;
 
-                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);
-                }
-                gjs_arg_set(in_value, 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 (!gjs_arg_get<void*>(in_value)) {
-                        /* 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.
-                         */
-                        gjs_arg_set(&in_arg_cvalues[array_length_pos], nullptr);
-                        gjs_arg_set(&out_arg_cvalues[array_length_pos],
-                                    nullptr);
-                        gjs_arg_set(
-                            &inout_original_arg_cvalues[array_length_pos],
-                            nullptr);
-                    } else {
-                        out_arg_cvalues[array_length_pos] = inout_original_arg_cvalues[array_length_pos] = 
*(in_arg_cvalues + array_length_pos);
-                        gjs_arg_set(&in_arg_cvalues[array_length_pos],
-                                    &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;
+        if (is_object_method)
+            state.instance_object = obj;
+    }
 
-                break;
-            }
+    unsigned 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];
 
-            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;
+        gjs_debug_marshal(GJS_DEBUG_GFUNCTION,
+                          "Marshalling argument '%s' in, %d/%d GI args, %u/%u "
+                          "C args, %u/%u JS args",
+                          cache->arg_name, gi_arg_pos, gi_argc, ffi_arg_pos,
+                          ffi_argc, js_arg_pos, args.length());
 
-            default:
-                ;
-            }
+        ffi_arg_pointers[ffi_arg_pos] = in_value;
 
-            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];
-                gjs_arg_set(&in_arg_cvalues[c_arg_pos],
-                            &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 (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 (!cache->skip_in)
+            js_arg_pos++;
 
         processed_c_args++;
     }
 
+    // This pointer needs to exist on the stack across the ffi_call() call
+    GError** errorp = &local_error;
+
     /* Did argument conversion fail?  In that case, skip invocation and jump to release
      * processing. */
     if (failed) {
@@ -1175,22 +948,21 @@ static bool gjs_invoke_c_function(JSContext* context, Function* function,
     }
 
     if (can_throw_gerror) {
-        g_assert_cmpuint(c_arg_pos, <, c_argc);
-        gjs_arg_set(&in_arg_cvalues[c_arg_pos], &local_error);
-        ffi_arg_pointers[c_arg_pos] = &(in_arg_cvalues[c_arg_pos]);
-        c_arg_pos++;
+        g_assert(ffi_arg_pos < ffi_argc && "GError** argument number mismatch");
+        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);
 
-    ffi_call(
-        &(function->invoker.cif), FFI_FN(function->invoker.native_address),
-        get_return_ffi_pointer_from_giargument(&return_info, &return_value),
-        ffi_arg_pointers);
+    return_value_p = get_return_ffi_pointer_from_giargument(
+        &function->arguments[-1], &return_value);
+    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
      * return error. In arguments need to be released always.
@@ -1204,282 +976,107 @@ static bool gjs_invoke_c_function(JSContext* context, Function* function,
     if (!r_value)
         args.rval().setUndefined();
 
-    /* Only process return values if the function didn't throw */
-    if (function->js_out_argc > 0 && !did_throw_gerror) {
-        if (!return_values.growBy(function->js_out_argc))
-            g_error("Unable to reserve space for 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);
+    if (!function->arguments[-1].skip_out) {
+        gi_type_info_extract_ffi_return_value(
+            &function->arguments[-1].type_info, &return_value,
+            &state.out_cvalues[-1]);
+    }
 
-            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);
+    // 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];
+
+        gjs_debug_marshal(GJS_DEBUG_GFUNCTION,
+                          "Marshalling argument '%s' out, %d/%d GI args",
+                          cache->arg_name, gi_arg_pos, gi_argc);
+
+        JS::RootedValue js_out_arg(context);
+        if (!r_value) {
+            if (!cache->marshal_out(context, cache, &state, out_value,
+                                    &js_out_arg)) {
+                failed = true;
+                break;
+            }
+        }
 
-                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 (!r_value) {
-                    if (!arg_failed) {
-                        arg_failed = !gjs_value_from_explicit_array(
-                            context, return_values[next_rval], &return_info,
-                            &return_gargument, length.toInt32());
-                    }
-                    if (!arg_failed && !gjs_g_argument_release_out_array(
-                                           context, transfer, &return_info,
-                                           length.toInt32(), &return_gargument))
-                        failed = true;
-                }
-            } else {
-                if (!r_value) {
-                    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 &&
-                        !gjs_g_argument_release(context, transfer, &return_info,
-                                                &return_gargument))
-                        failed = true;
+        if (!cache->skip_out) {
+            if (!r_value) {
+                if (!return_values.append(js_out_arg)) {
+                    JS_ReportOutOfMemory(context);
+                    failed = true;
+                    break;
                 }
             }
-            if (arg_failed)
-                failed = true;
-
-            ++next_rval;
+            js_arg_pos++;
         }
     }
 
+    g_assert(failed || did_throw_gerror || js_arg_pos == 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;
+    // If we failed before calling the function, or if the function threw an
+    // exception, then any GI_TRANSFER_EVERYTHING or GI_TRANSFER_CONTAINER
+    // in-parameters were not transferred. Treat them as GI_TRANSFER_NOTHING so
+    // that they are freed.
+    if (!failed && !did_throw_gerror)
+        state.call_completed = true;
+
+    // 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;
-
-        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];
-
-                // 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) {
-                auto* closure = gjs_arg_get<ffi_closure*>(arg);
-                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);
-                    gjs_arg_unset<void*>(arg);
-                }
-            } 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);
+    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];
 
-                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;
-                }
-            }
-        }
+        gjs_debug_marshal(
+            GJS_DEBUG_GFUNCTION,
+            "Releasing argument '%s', %d/%d GI args, %u/%u C args",
+            cache->arg_name, gi_arg_pos, gi_argc, ffi_arg_pos,
+            processed_c_args);
 
-        /* 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 && cache->skip_in)
             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 (!r_value) {
-                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) {
-                    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;
-                }
-            }
-
-            /* 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,
-                              gjs_arg_get<void*>(&out_arg_cvalues[c_arg_pos]));
-                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 (r_value) {
-            *r_value = return_gargument;
+    if (!r_value && function->js_out_argc > 0 &&
+        (!failed && !did_throw_gerror)) {
+        // If we have one return value or out arg, return that item on its
+        // own, otherwise return a JavaScript array with [return value,
+        // out arg 1, out arg 2, ...]
+        if (function->js_out_argc == 1) {
+            args.rval().set(return_values[0]);
         } else {
-            // If we have one return value or out arg, return that item on its
-            // own, otherwise return a JavaScript array with [return value,
-            // out arg 1, out arg 2, ...]
-            if (function->js_out_argc == 1) {
-                args.rval().set(return_values[0]);
+            JSObject* array = JS_NewArrayObject(context, return_values);
+            if (!array) {
+                failed = true;
             } else {
-                JSObject *array;
-                array = JS_NewArrayObject(context, return_values);
-                if (array == NULL) {
-                    failed = true;
-                } else {
-                    args.rval().setObject(*array);
-                }
+                args.rval().setObject(*array);
             }
         }
     }
@@ -1522,10 +1119,15 @@ GJS_NATIVE_CONSTRUCTOR_DEFINE_ABSTRACT(function)
 static void
 uninit_cached_function_data (Function *function)
 {
+    if (function->arguments) {
+        // Careful! function->arguments is offset by one element inside
+        // the allocated space, so we have to free index -1.
+        g_free(&function->arguments[-1]);
+        function->arguments = nullptr;
+    }
+
     if (function->info)
         g_base_info_unref( (GIBaseInfo*) function->info);
-    if (function->param_types)
-        g_free(function->param_types);
 
     g_function_invoker_destroy(&function->invoker);
 }
@@ -1552,28 +1154,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;
 }
 
@@ -1600,21 +1181,14 @@ function_to_string (JSContext *context,
     n_jsargs = 0;
     arg_names_str = g_string_new("");
     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)
+        if (priv->arguments[i].skip_in)
             continue;
 
         if (n_jsargs > 0)
             g_string_append(arg_names_str, ", ");
 
         n_jsargs++;
-        g_string_append(arg_names_str, g_base_info_get_name(&arg_info));
+        g_string_append(arg_names_str, priv->arguments[i].arg_name);
     }
     arg_names = g_string_free(arg_names_str, false);
 
@@ -1709,30 +1283,15 @@ 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(context, &out_args[-1],
-                                    function->param_types, info,
-                                    &inc_counter)) {
-        g_free(function->param_types);
+    if (!gjs_arg_cache_build_return(context, &arguments[-1], arguments, info,
+                                    &inc_counter))
         return false;
-    }
+
     int out_argc = inc_counter ? 1 : 0;
     int in_argc = 0;
 
@@ -1740,43 +1299,15 @@ init_cached_function_data (JSContext      *context,
         GIDirection direction;
         GIArgInfo arg_info;
 
-        if (function->param_types[i] == PARAM_SKIPPED)
+        if (arguments[i].skip_in || arguments[i].skip_out)
             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_arg(
-                    context, &in_args[i], function->param_types, i,
-                    GI_DIRECTION_IN, &arg_info, info, &inc_counter)) {
-                g_free(function->param_types);
-                return false;
-            }
-
-            function->param_types[i] = in_args[i].param_type;
-        } else if (direction == GI_DIRECTION_INOUT) {
-            if (!gjs_arg_cache_build_inout_arg(
-                    context, &in_args[i], &out_args[i], function->param_types,
-                    i, &arg_info, &inc_counter)) {
-                g_free(function->param_types);
-                return false;
-            }
-
-            function->param_types[i] = in_args[i].param_type;
-        } else {  // GI_DIRECTION_OUT
-            if (out_args[i].param_type == PARAM_SKIPPED)
-                continue;
-
-            if (!gjs_arg_cache_build_arg(
-                    context, &out_args[i], function->param_types, i,
-                    GI_DIRECTION_OUT, &arg_info, info, &inc_counter)) {
-                g_free(function->param_types);
-                return false;
-            }
-
-            function->param_types[i] = out_args[i].param_type;
-        }
+        if (!gjs_arg_cache_build_arg(context, &arguments[i], arguments, i,
+                                     direction, &arg_info, info, &inc_counter))
+            return false;
 
         if (inc_counter) {
             switch (direction) {
@@ -1795,38 +1326,10 @@ init_cached_function_data (JSContext      *context,
         }
     }
 
-    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 8b4b1c95..fdc11324 100644
--- a/gi/function.h
+++ b/gi/function.h
@@ -30,6 +30,7 @@
 #include <girepository.h>
 #include <glib-object.h>
 
+#include <js/RootingAPI.h>
 #include <js/TypeDecls.h>
 
 #include "gjs/macros.h"
@@ -67,6 +68,18 @@ GjsCallbackTrampoline* gjs_callback_trampoline_new(
 void gjs_callback_trampoline_unref(GjsCallbackTrampoline *trampoline);
 void gjs_callback_trampoline_ref(GjsCallbackTrampoline *trampoline);
 
+// Stack allocation only!
+struct GjsFunctionCallState {
+    GIArgument* in_cvalues;
+    GIArgument* out_cvalues;
+    GIArgument* inout_original_cvalues;
+    JS::RootedObject instance_object;
+    bool call_completed;
+
+    explicit GjsFunctionCallState(JSContext* cx)
+        : instance_object(cx), call_completed(false) {}
+};
+
 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]