[gjs/wip/gcampax/70-arg-cache: 3/11] arg-cache: be more fine-grained in the argument cache




commit 10e234b1bd9c6eb0b9edaabe22ecbde6edd459ce
Author: Giovanni Campagna <gcampagna src gnome org>
Date:   Sun Apr 21 20:20:32 2013 +0200

    arg-cache: be more fine-grained in the argument cache
    
    Use different virtual functions for different argument types, so
    that conditions and switches need only be evaluated once, and
    store useful information in the argument cache structure.
    
    (Philip Chimento: rebased and fixed coding style.)

 gi/arg-cache.cpp | 292 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 gi/arg-cache.h   |   8 ++
 2 files changed, 297 insertions(+), 3 deletions(-)
---
diff --git a/gi/arg-cache.cpp b/gi/arg-cache.cpp
index 963937ec..3796720e 100644
--- a/gi/arg-cache.cpp
+++ b/gi/arg-cache.cpp
@@ -30,8 +30,10 @@
 #include <girepository.h>
 #include <glib.h>
 
+#include <js/Conversions.h>
 #include <js/RootingAPI.h>
 #include <js/TypeDecls.h>
+#include <js/Utility.h>  // for UniqueChars
 #include <jsapi.h>        // for JS_TypeOfValue
 #include <jsfriendapi.h>  // for JS_GetObjectFunction
 #include <jspubtd.h>      // for JSTYPE_FUNCTION
@@ -40,8 +42,20 @@
 #include "gi/arg-inl.h"
 #include "gi/arg.h"
 #include "gi/function.h"
+#include "gi/gtype.h"
 #include "gjs/jsapi-util.h"
 
+enum ExpectedType {
+    OBJECT,
+    FUNCTION,
+    STRING,
+    LAST,
+};
+
+static const char* expected_type_names[] = {"object", "function", "string"};
+static_assert(G_N_ELEMENTS(expected_type_names) == ExpectedType::LAST,
+              "Names must match the values in ExpectedType");
+
 // 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) {
@@ -112,6 +126,30 @@ static void gjs_g_argument_set_array_length(GITypeTag tag, GIArgument* arg,
 #endif
 }
 
+GJS_JSAPI_RETURN_CONVENTION
+static bool report_typeof_mismatch(JSContext* cx, const char* arg_name,
+                                   JS::HandleValue value,
+                                   ExpectedType expected) {
+    gjs_throw(cx, "Expected type %s for argument '%s' but got type %s",
+              expected_type_names[expected], arg_name,
+              JS::InformalValueTypeName(value));
+    return false;
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+static bool report_out_of_range(JSContext* cx, const char* arg_name,
+                                GITypeTag tag) {
+    gjs_throw(cx, "Argument %s: value is out of range for %s", arg_name,
+              g_type_tag_to_string(tag));
+    return false;
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+static bool report_invalid_null(JSContext* cx, const char* arg_name) {
+    gjs_throw(cx, "Argument %s may not be null", arg_name);
+    return false;
+}
+
 // Marshallers:
 //
 // Each argument, irrespective of the direction, is processed in three phases:
@@ -277,6 +315,155 @@ static bool gjs_marshal_caller_allocates_in(JSContext*, GjsArgumentCache* self,
     return true;
 }
 
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_marshal_null_in_in(JSContext*, GjsArgumentCache*,
+                                   GjsFunctionCallState*, GIArgument* arg,
+                                   JS::HandleValue) {
+    gjs_arg_unset<void*>(arg);
+    return true;
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_marshal_boolean_in_in(JSContext*, GjsArgumentCache*,
+                                      GjsFunctionCallState*, GIArgument* arg,
+                                      JS::HandleValue value) {
+    gjs_arg_set(arg, JS::ToBoolean(value));
+    return true;
+}
+
+// Type tags are alternated, signed / unsigned
+static int32_t min_max_ints[5][2] = {{G_MININT8, G_MAXINT8},
+                                     {0, G_MAXUINT8},
+                                     {G_MININT16, G_MAXINT16},
+                                     {0, G_MAXUINT16},
+                                     {G_MININT32, G_MAXINT32}};
+
+GJS_USE
+static inline bool value_in_range(int32_t number, GITypeTag tag) {
+    return (number >= min_max_ints[tag - GI_TYPE_TAG_INT8][0] &&
+            number <= min_max_ints[tag - GI_TYPE_TAG_INT8][1]);
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_marshal_integer_in_in(JSContext* cx, GjsArgumentCache* self,
+                                      GjsFunctionCallState*, GIArgument* arg,
+                                      JS::HandleValue value) {
+    GITypeTag tag = self->contents.number.number_tag;
+
+    if (self->contents.number.is_unsigned) {
+        uint32_t number;
+        if (!JS::ToUint32(cx, value, &number))
+            return false;
+
+        if (!value_in_range(number, tag))
+            return report_out_of_range(cx, self->arg_name, tag);
+
+        gjs_g_argument_set_array_length(tag, arg, number);
+    } else {
+        int32_t number;
+        if (!JS::ToInt32(cx, value, &number))
+            return false;
+
+        if (!value_in_range(number, tag))
+            return report_out_of_range(cx, self->arg_name, tag);
+
+        gjs_g_argument_set_array_length(tag, arg, number);
+    }
+
+    return true;
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_marshal_number_in_in(JSContext* cx, GjsArgumentCache* self,
+                                     GjsFunctionCallState*, GIArgument* arg,
+                                     JS::HandleValue value) {
+    double v;
+    if (!JS::ToNumber(cx, value, &v))
+        return false;
+
+    GITypeTag tag = self->contents.number.number_tag;
+    if (tag == GI_TYPE_TAG_DOUBLE) {
+        gjs_arg_set(arg, v);
+    } else if (tag == GI_TYPE_TAG_FLOAT) {
+        if (v < -G_MAXFLOAT || v > G_MAXFLOAT)
+            return report_out_of_range(cx, self->arg_name, GI_TYPE_TAG_FLOAT);
+        gjs_arg_set<float>(arg, v);
+    } else if (tag == GI_TYPE_TAG_INT64) {
+        if (v < G_MININT64 || v > G_MAXINT64)
+            return report_out_of_range(cx, self->arg_name, GI_TYPE_TAG_INT64);
+        gjs_arg_set<int64_t>(arg, v);
+    } else if (tag == GI_TYPE_TAG_UINT64) {
+        if (v < 0 || v > G_MAXUINT64)
+            return report_out_of_range(cx, self->arg_name, GI_TYPE_TAG_UINT64);
+        gjs_arg_set<uint64_t>(arg, v);
+    } else if (tag == GI_TYPE_TAG_UINT32) {
+        if (v < 0 || v > G_MAXUINT32)
+            return report_out_of_range(cx, self->arg_name, GI_TYPE_TAG_UINT32);
+        gjs_arg_set<uint32_t>(arg, v);
+    } else {
+        g_assert_not_reached();
+    }
+
+    return true;
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_marshal_unichar_in_in(JSContext* cx, GjsArgumentCache* self,
+                                      GjsFunctionCallState*, GIArgument* arg,
+                                      JS::HandleValue value) {
+    if (!value.isString())
+        return report_typeof_mismatch(cx, self->arg_name, value,
+                                      ExpectedType::STRING);
+
+    return gjs_unichar_from_string(cx, value, &gjs_arg_member<char32_t>(arg));
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_marshal_gtype_in_in(JSContext* cx, GjsArgumentCache* self,
+                                    GjsFunctionCallState*, GIArgument* arg,
+                                    JS::HandleValue value) {
+    if (value.isNull())
+        return report_invalid_null(cx, self->arg_name);
+    if (!value.isObject())
+        return report_typeof_mismatch(cx, self->arg_name, value,
+                                      ExpectedType::OBJECT);
+
+    JS::RootedObject gtype_obj(cx, &value.toObject());
+    return gjs_gtype_get_actual_gtype(
+        cx, gtype_obj, &gjs_arg_member<GType, GI_TYPE_TAG_GTYPE>(arg));
+}
+
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_marshal_string_in_in(JSContext* cx, GjsArgumentCache* self,
+                                     GjsFunctionCallState*, GIArgument* arg,
+                                     JS::HandleValue value) {
+    if (value.isNull()) {
+        if (!self->nullable)
+            return report_invalid_null(cx, self->arg_name);
+
+        gjs_arg_unset<void*>(arg);
+        return true;
+    }
+
+    if (!value.isString())
+        return report_typeof_mismatch(cx, self->arg_name, value,
+                                      ExpectedType::STRING);
+
+    if (self->contents.string_is_filename) {
+        GjsAutoChar str;
+        if (!gjs_string_to_filename(cx, value, &str))
+            return false;
+        gjs_arg_set(arg, str.release());
+        return true;
+    }
+
+    JS::UniqueChars str = gjs_string_to_utf8(cx, value);
+    if (!str)
+        return false;
+    gjs_arg_set(arg, g_strdup(str.get()));
+    return true;
+}
+
 GJS_JSAPI_RETURN_CONVENTION
 static bool gjs_marshal_skipped_out(JSContext*, GjsArgumentCache*,
                                     GjsFunctionCallState*, GIArgument*,
@@ -436,6 +623,15 @@ static bool gjs_marshal_callback_release(JSContext*, GjsArgumentCache*,
     return true;
 }
 
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_marshal_string_in_release(JSContext*, GjsArgumentCache*,
+                                          GjsFunctionCallState*,
+                                          GIArgument* in_arg,
+                                          GIArgument* out_arg G_GNUC_UNUSED) {
+    g_free(gjs_arg_get<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;
@@ -499,6 +695,94 @@ bool gjs_arg_cache_build_return(JSContext*, GjsArgumentCache* self,
     return true;
 }
 
+GJS_JSAPI_RETURN_CONVENTION
+static bool gjs_arg_cache_build_normal_in_arg(JSContext*,
+                                              GjsArgumentCache* self,
+                                              GITypeTag tag) {
+    // "Normal" in arguments are those arguments that don't require special
+    // processing, and don't touch other arguments.
+    // Main categories are:
+    // - void*
+    // - small numbers (fit in 32bit)
+    // - big numbers (need a double)
+    // - strings
+    // - enums/flags (different from numbers in the way they're exposed in GI)
+    // - objects (GObjects, boxed, unions, etc.)
+    // - hashes
+    // - sequences (null-terminated arrays, lists, etc.)
+
+    self->release = gjs_marshal_skipped_release;
+
+    switch (tag) {
+        case GI_TYPE_TAG_VOID:
+            self->marshal_in = gjs_marshal_null_in_in;
+            break;
+
+        case GI_TYPE_TAG_BOOLEAN:
+            self->marshal_in = gjs_marshal_boolean_in_in;
+            break;
+
+        case GI_TYPE_TAG_INT8:
+        case GI_TYPE_TAG_INT16:
+        case GI_TYPE_TAG_INT32:
+            self->marshal_in = gjs_marshal_integer_in_in;
+            self->contents.number.number_tag = tag;
+            self->contents.number.is_unsigned = false;
+            break;
+
+        case GI_TYPE_TAG_UINT8:
+        case GI_TYPE_TAG_UINT16:
+            self->marshal_in = gjs_marshal_integer_in_in;
+            self->contents.number.number_tag = tag;
+            self->contents.number.is_unsigned = true;
+            break;
+
+        case GI_TYPE_TAG_UINT32:
+        case GI_TYPE_TAG_INT64:
+        case GI_TYPE_TAG_UINT64:
+        case GI_TYPE_TAG_FLOAT:
+        case GI_TYPE_TAG_DOUBLE:
+            self->marshal_in = gjs_marshal_number_in_in;
+            self->contents.number.number_tag = tag;
+            break;
+
+        case GI_TYPE_TAG_UNICHAR:
+            self->marshal_in = gjs_marshal_unichar_in_in;
+            break;
+
+        case GI_TYPE_TAG_GTYPE:
+            self->marshal_in = gjs_marshal_gtype_in_in;
+            break;
+
+        case GI_TYPE_TAG_FILENAME:
+            self->marshal_in = gjs_marshal_string_in_in;
+            if (self->transfer == GI_TRANSFER_NOTHING)
+                self->release = gjs_marshal_string_in_release;
+            self->contents.string_is_filename = true;
+            break;
+
+        case GI_TYPE_TAG_UTF8:
+            self->marshal_in = gjs_marshal_string_in_in;
+            if (self->transfer == GI_TRANSFER_NOTHING)
+                self->release = gjs_marshal_string_in_release;
+            self->contents.string_is_filename = false;
+            break;
+
+        case GI_TYPE_TAG_INTERFACE:
+        case GI_TYPE_TAG_ARRAY:
+        case GI_TYPE_TAG_GLIST:
+        case GI_TYPE_TAG_GSLIST:
+        case GI_TYPE_TAG_GHASH:
+        case GI_TYPE_TAG_ERROR:
+        default:
+            // FIXME: Falling back to the generic marshaller
+            self->marshal_in = gjs_marshal_generic_in_in;
+            self->release = gjs_marshal_generic_in_release;
+    }
+
+    return true;
+}
+
 bool gjs_arg_cache_build_arg(JSContext* cx, GjsArgumentCache* self,
                              GjsArgumentCache* arguments, int gi_index,
                              GIDirection direction, GIArgInfo* arg,
@@ -649,10 +933,12 @@ bool gjs_arg_cache_build_arg(JSContext* cx, GjsArgumentCache* self,
     }
 
     if (direction == GI_DIRECTION_IN) {
-        self->marshal_in = gjs_marshal_generic_in_in;
+        bool ok = gjs_arg_cache_build_normal_in_arg(cx, self, type_tag);
         self->marshal_out = gjs_marshal_skipped_out;
-        self->release = gjs_marshal_generic_in_release;
-    } else if (direction == GI_DIRECTION_INOUT) {
+        return ok;
+    }
+
+    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;
diff --git a/gi/arg-cache.h b/gi/arg-cache.h
index a7591204..fc917cf1 100644
--- a/gi/arg-cache.h
+++ b/gi/arg-cache.h
@@ -71,6 +71,14 @@ typedef struct _GjsArgumentCache {
             int destroy_pos;
         } callback;
 
+        struct {
+            GITypeTag number_tag;
+            bool is_unsigned : 1;
+        } number;
+
+        // string / filename
+        bool string_is_filename : 1;
+
         // out caller allocates (FIXME: should be in object)
         size_t caller_allocates_size;
     } contents;


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