X-Git-Url: http://wien.tomnetworks.com/gitweb/?a=blobdiff_plain;f=mono%2Fmetadata%2Fgeneric-sharing.c;h=690c87ed70f6e644f20a4f6370b4647500c4078f;hb=426256b22909bf1cef2db7ada2c8ee9bcb73b658;hp=16075435fdd2d9bc6a93a0d67bf48ecae7c99da2;hpb=33028b1a2c0d0345efc3639f3b33d74dc96daf62;p=mono.git diff --git a/mono/metadata/generic-sharing.c b/mono/metadata/generic-sharing.c index 16075435fdd..690c87ed70f 100644 --- a/mono/metadata/generic-sharing.c +++ b/mono/metadata/generic-sharing.c @@ -4,11 +4,14 @@ * Author: * Mark Probst (mark.probst@gmail.com) * - * (C) 2007-2008 Novell, Inc. + * Copyright 2007-2009 Novell, Inc (http://www.novell.com) */ #include #include +#ifdef HAVE_ALLOCA_H +#include +#endif #ifdef _MSC_VER #include @@ -22,6 +25,7 @@ #include "marshal.h" #include "debug-helpers.h" #include "tabledefs.h" +#include "mempool-internals.h" static int type_check_context_used (MonoType *type, gboolean recursive) @@ -114,39 +118,7 @@ mono_class_check_context_used (MonoClass *class) } /* - * Guards the two global rgctx (template) hash tables and all rgctx - * templates. - * - * Ordering: The loader lock can be taken while the templates lock is - * held. - */ -static CRITICAL_SECTION templates_mutex; - -static void -templates_lock (void) -{ - static gboolean inited = FALSE; - - if (!inited) { - mono_loader_lock (); - if (!inited) { - InitializeCriticalSection (&templates_mutex); - inited = TRUE; - } - mono_loader_unlock (); - } - - EnterCriticalSection (&templates_mutex); -} - -static void -templates_unlock (void) -{ - LeaveCriticalSection (&templates_mutex); -} - -/* - * LOCKING: templates lock + * LOCKING: loader lock */ static MonoRuntimeGenericContextOtherInfoTemplate* get_other_info_templates (MonoRuntimeGenericContextTemplate *template, int type_argc) @@ -158,10 +130,10 @@ get_other_info_templates (MonoRuntimeGenericContextTemplate *template, int type_ } /* - * LOCKING: templates lock + * LOCKING: loader lock */ static void -set_other_info_templates (MonoRuntimeGenericContextTemplate *template, int type_argc, +set_other_info_templates (MonoImage *image, MonoRuntimeGenericContextTemplate *template, int type_argc, MonoRuntimeGenericContextOtherInfoTemplate *oti) { g_assert (type_argc >= 0); @@ -173,7 +145,7 @@ set_other_info_templates (MonoRuntimeGenericContextTemplate *template, int type_ /* FIXME: quadratic! */ while (length < type_argc) { - template->method_templates = g_slist_append (template->method_templates, NULL); + template->method_templates = g_slist_append_image (image, template->method_templates, NULL); length++; } @@ -184,7 +156,7 @@ set_other_info_templates (MonoRuntimeGenericContextTemplate *template, int type_ } /* - * LOCKING: templates lock + * LOCKING: loader lock */ static int template_get_max_argc (MonoRuntimeGenericContextTemplate *template) @@ -193,7 +165,7 @@ template_get_max_argc (MonoRuntimeGenericContextTemplate *template) } /* - * LOCKING: templates lock + * LOCKING: loader lock */ static MonoRuntimeGenericContextOtherInfoTemplate* rgctx_template_get_other_slot (MonoRuntimeGenericContextTemplate *template, int type_argc, int slot) @@ -212,7 +184,7 @@ rgctx_template_get_other_slot (MonoRuntimeGenericContextTemplate *template, int } /* - * LOCKING: templates lock + * LOCKING: loader lock */ static int rgctx_template_num_other_infos (MonoRuntimeGenericContextTemplate *template, int type_argc) @@ -230,7 +202,7 @@ rgctx_template_num_other_infos (MonoRuntimeGenericContextTemplate *template, int * uninstantiated generic classes whose parent is the key class or an * instance of the key class. * - * LOCKING: templates lock + * LOCKING: loader lock */ static GHashTable *generic_subclass_hash; @@ -247,7 +219,7 @@ class_set_rgctx_template (MonoClass *class, MonoRuntimeGenericContextTemplate *r } /* - * LOCKING: templates lock + * LOCKING: loader lock */ static MonoRuntimeGenericContextTemplate* class_lookup_rgctx_template (MonoClass *class) @@ -263,7 +235,7 @@ class_lookup_rgctx_template (MonoClass *class) } /* - * LOCKING: templates lock + * LOCKING: loader lock */ static void register_generic_subclass (MonoClass *class) @@ -338,21 +310,18 @@ mono_class_unregister_image_generic_subclasses (MonoImage *image) if (!generic_subclass_hash) return; - templates_lock (); + mono_loader_lock (); old_hash = generic_subclass_hash; generic_subclass_hash = g_hash_table_new (mono_aligned_addr_hash, NULL); g_hash_table_foreach (old_hash, (GHFunc)move_subclasses_not_in_image_foreach_func, image); - templates_unlock (); + mono_loader_unlock (); g_hash_table_destroy (old_hash); } -/* - * LOCKING: loader lock - */ static MonoRuntimeGenericContextTemplate* alloc_template (MonoClass *class) { @@ -374,9 +343,6 @@ alloc_template (MonoClass *class) return mono_image_alloc0 (class->image, size); } -/* - * LOCKING: loader lock - */ static MonoRuntimeGenericContextOtherInfoTemplate* alloc_oti (MonoImage *image) { @@ -401,7 +367,7 @@ alloc_oti (MonoImage *image) #define MONO_RGCTX_SLOT_USED_MARKER ((gpointer)&mono_defaults.object_class->byval_arg) /* - * LOCKING: templates lock + * LOCKING: loader lock */ static void rgctx_template_set_other_slot (MonoImage *image, MonoRuntimeGenericContextTemplate *template, int type_argc, @@ -424,8 +390,6 @@ rgctx_template_set_other_slot (MonoImage *image, MonoRuntimeGenericContextTempla g_assert (slot >= 0); g_assert (data); - mono_loader_lock (); - i = 0; while (i <= slot) { if (i > 0) @@ -435,13 +399,11 @@ rgctx_template_set_other_slot (MonoImage *image, MonoRuntimeGenericContextTempla ++i; } - mono_loader_unlock (); - g_assert (!(*oti)->data); (*oti)->data = data; (*oti)->info_type = info_type; - set_other_info_templates (template, type_argc, list); + set_other_info_templates (image, template, type_argc, list); if (data == MONO_RGCTX_SLOT_USED_MARKER) ++num_markers; @@ -492,16 +454,22 @@ mono_class_get_method_generic (MonoClass *klass, MonoMethod *method) else declaring = method; - mono_class_setup_methods (klass); - for (i = 0; i < klass->method.count; ++i) { - m = klass->methods [i]; - if (m == declaring) - break; - if (m->is_inflated && mono_method_get_declaring_generic_method (m) == declaring) - break; + m = NULL; + if (klass->generic_class) + m = mono_class_get_inflated_method (klass, declaring); + + if (!m) { + mono_class_setup_methods (klass); + for (i = 0; i < klass->method.count; ++i) { + m = klass->methods [i]; + if (m == declaring) + break; + if (m->is_inflated && mono_method_get_declaring_generic_method (m) == declaring) + break; + } + if (i >= klass->method.count) + return NULL; } - if (i >= klass->method.count) - return NULL; if (method != declaring) { MonoGenericContext context; @@ -530,12 +498,14 @@ inflate_other_data (gpointer data, int info_type, MonoGenericContext *context, M case MONO_RGCTX_INFO_VTABLE: case MONO_RGCTX_INFO_TYPE: case MONO_RGCTX_INFO_REFLECTION_TYPE: - return mono_class_inflate_generic_type_with_mempool (temporary ? NULL : class->image->mempool, + return mono_class_inflate_generic_type_with_mempool (temporary ? NULL : class->image, data, context); case MONO_RGCTX_INFO_METHOD: case MONO_RGCTX_INFO_GENERIC_METHOD_CODE: - case MONO_RGCTX_INFO_METHOD_RGCTX: { + case MONO_RGCTX_INFO_METHOD_RGCTX: + case MONO_RGCTX_INFO_METHOD_CONTEXT: + case MONO_RGCTX_INFO_REMOTING_INVOKE_WITH_CHECK: { MonoMethod *method = data; MonoMethod *inflated_method; MonoType *inflated_type = mono_class_inflate_generic_type (&method->klass->byval_arg, context); @@ -545,16 +515,15 @@ inflate_other_data (gpointer data, int info_type, MonoGenericContext *context, M mono_class_init (inflated_class); - if (method->wrapper_type != MONO_WRAPPER_NONE) { - g_assert (info_type != MONO_RGCTX_INFO_METHOD_RGCTX); - g_assert (method->wrapper_type == MONO_WRAPPER_STATIC_RGCTX_INVOKE); + g_assert (!method->wrapper_type); - method = mono_marshal_method_from_wrapper (method); - method = mono_class_inflate_generic_method (method, context); - method = mono_marshal_get_static_rgctx_invoke (method); + if (inflated_class->byval_arg.type == MONO_TYPE_ARRAY || + inflated_class->byval_arg.type == MONO_TYPE_SZARRAY) { + inflated_method = mono_method_search_in_array_class (inflated_class, + method->name, method->signature); + } else { + inflated_method = mono_class_inflate_generic_method (method, context); } - - inflated_method = mono_class_inflate_generic_method (method, context); mono_class_init (inflated_method->klass); g_assert (inflated_method->klass == inflated_class); return inflated_method; @@ -578,6 +547,8 @@ inflate_other_data (gpointer data, int info_type, MonoGenericContext *context, M default: g_assert_not_reached (); } + /* Not reached, quiet compiler */ + return NULL; } static gpointer @@ -620,28 +591,20 @@ static MonoRuntimeGenericContextTemplate* mono_class_get_runtime_generic_context_template (MonoClass *class) { MonoRuntimeGenericContextTemplate *parent_template, *template; - MonoGenericInst *inst; guint32 i; g_assert (!class->generic_class); - templates_lock (); + mono_loader_lock (); template = class_lookup_rgctx_template (class); - templates_unlock (); + mono_loader_unlock (); if (template) return template; - if (class->generic_container) - inst = class->generic_container->context.class_inst; - else - inst = NULL; - - mono_loader_lock (); template = alloc_template (class); - mono_loader_unlock (); - templates_lock (); + mono_loader_lock (); if (class->parent) { if (class->parent->generic_class) { @@ -697,7 +660,7 @@ mono_class_get_runtime_generic_context_template (MonoClass *class) register_generic_subclass (class); } - templates_unlock (); + mono_loader_unlock (); return template; } @@ -708,6 +671,8 @@ mono_class_get_runtime_generic_context_template (MonoClass *class) * permanently, in which case it will be mempool-allocated. If * temporary is set then *do_free will return whether the returned * data must be freed. + * + * LOCKING: loader lock */ static MonoRuntimeGenericContextOtherInfoTemplate class_get_rgctx_template_oti (MonoClass *class, int type_argc, guint32 slot, gboolean temporary, gboolean *do_free) @@ -757,15 +722,25 @@ static gpointer class_type_info (MonoDomain *domain, MonoClass *class, int info_type) { switch (info_type) { - case MONO_RGCTX_INFO_STATIC_DATA: - return mono_class_vtable (domain, class)->data; + case MONO_RGCTX_INFO_STATIC_DATA: { + MonoVTable *vtable = mono_class_vtable (domain, class); + if (!vtable) + mono_raise_exception (mono_class_get_exception_for_failure (class)); + return vtable->data; + } case MONO_RGCTX_INFO_KLASS: return class; - case MONO_RGCTX_INFO_VTABLE: - return mono_class_vtable (domain, class); + case MONO_RGCTX_INFO_VTABLE: { + MonoVTable *vtable = mono_class_vtable (domain, class); + if (!vtable) + mono_raise_exception (mono_class_get_exception_for_failure (class)); + return vtable; + } default: g_assert_not_reached (); } + /* Not reached */ + return NULL; } static gpointer @@ -799,6 +774,12 @@ instantiate_other_info (MonoDomain *domain, MonoRuntimeGenericContextOtherInfoTe free_inflated_info (oti->info_type, data); g_assert (arg_class); + /* The class might be used as an argument to + mono_value_copy(), which requires that its GC + descriptor has been computed. */ + if (oti->info_type == MONO_RGCTX_INFO_KLASS) + mono_class_compute_gc_descriptor (arg_class); + return class_type_info (domain, arg_class, oti->info_type); } case MONO_RGCTX_INFO_TYPE: @@ -808,25 +789,44 @@ instantiate_other_info (MonoDomain *domain, MonoRuntimeGenericContextOtherInfoTe case MONO_RGCTX_INFO_METHOD: return data; case MONO_RGCTX_INFO_GENERIC_METHOD_CODE: - return mono_compile_method (data); + return mono_create_ftnptr (mono_domain_get (), + mono_runtime_create_jump_trampoline (mono_domain_get (), data, TRUE)); + case MONO_RGCTX_INFO_REMOTING_INVOKE_WITH_CHECK: + return mono_create_ftnptr (mono_domain_get (), + mono_runtime_create_jump_trampoline (mono_domain_get (), + mono_marshal_get_remoting_invoke_with_check (data), TRUE)); case MONO_RGCTX_INFO_CLASS_FIELD: return data; case MONO_RGCTX_INFO_METHOD_RGCTX: { MonoMethodInflated *method = data; + MonoVTable *vtable; + + g_assert (method->method.method.is_inflated); + g_assert (method->context.method_inst); + + vtable = mono_class_vtable (domain, method->method.method.klass); + if (!vtable) + mono_raise_exception (mono_class_get_exception_for_failure (method->method.method.klass)); + + return mono_method_lookup_rgctx (vtable, method->context.method_inst); + } + case MONO_RGCTX_INFO_METHOD_CONTEXT: { + MonoMethodInflated *method = data; g_assert (method->method.method.is_inflated); g_assert (method->context.method_inst); - return mono_method_lookup_rgctx (mono_class_vtable (domain, method->method.method.klass), - method->context.method_inst); + return method->context.method_inst; } default: g_assert_not_reached (); } + /* Not reached */ + return NULL; } /* - * LOCKING: templates lock + * LOCKING: loader lock */ static void fill_in_rgctx_template_slot (MonoClass *class, int type_argc, int index, gpointer data, int info_type) @@ -861,7 +861,7 @@ fill_in_rgctx_template_slot (MonoClass *class, int type_argc, int index, gpointe } /* - * LOCKING: templates lock + * LOCKING: loader lock */ static int register_other_info (MonoClass *class, int type_argc, gpointer data, int info_type) @@ -921,10 +921,14 @@ other_info_equal (gpointer data1, gpointer data2, int info_type) case MONO_RGCTX_INFO_GENERIC_METHOD_CODE: case MONO_RGCTX_INFO_CLASS_FIELD: case MONO_RGCTX_INFO_METHOD_RGCTX: + case MONO_RGCTX_INFO_METHOD_CONTEXT: + case MONO_RGCTX_INFO_REMOTING_INVOKE_WITH_CHECK: return data1 == data2; default: g_assert_not_reached (); } + /* never reached */ + return FALSE; } static int @@ -936,33 +940,36 @@ lookup_or_register_other_info (MonoClass *class, int type_argc, gpointer data, i MonoRuntimeGenericContextTemplate *rgctx_template = mono_class_get_runtime_generic_context_template (class); - MonoRuntimeGenericContextOtherInfoTemplate *oti; + MonoRuntimeGenericContextOtherInfoTemplate *oti_list, *oti; int i; g_assert (!class->generic_class); g_assert (class->generic_container || type_argc); - templates_lock (); + mono_loader_lock (); - for (oti = get_other_info_templates (rgctx_template, type_argc), i = 0; oti; oti = oti->next, ++i) { + oti_list = get_other_info_templates (rgctx_template, type_argc); + + for (oti = oti_list, i = 0; oti; oti = oti->next, ++i) { gpointer inflated_data; - if (!oti || oti->info_type != info_type || !oti->data) + if (oti->info_type != info_type || !oti->data) continue; inflated_data = inflate_other_info (oti, generic_context, class, TRUE); if (other_info_equal (data, inflated_data, info_type)) { - templates_unlock (); - free_inflated_info (oti->info_type, inflated_data); + free_inflated_info (info_type, inflated_data); + mono_loader_unlock (); return i; } free_inflated_info (info_type, inflated_data); } + /* We haven't found the info */ i = register_other_info (class, type_argc, data, info_type); - templates_unlock (); + mono_loader_unlock (); if (!inited) { mono_counters_register ("RGCTX max slot number", MONO_COUNTER_GENERICS | MONO_COUNTER_INT, &max_slot); @@ -1067,9 +1074,6 @@ alloc_rgctx_array (MonoDomain *domain, int n, gboolean is_mrgctx) return array; } -/* - * LOCKING: domain lock - */ static gpointer fill_runtime_generic_context (MonoVTable *class_vtable, MonoRuntimeGenericContext *rgctx, guint32 slot, MonoGenericInst *method_inst) @@ -1086,26 +1090,30 @@ fill_runtime_generic_context (MonoVTable *class_vtable, MonoRuntimeGenericContex g_assert (rgctx); + mono_domain_lock (domain); + /* First check whether that slot isn't already instantiated. This might happen because lookup doesn't lock. Allocate arrays on the way. */ first_slot = 0; size = mono_class_rgctx_get_array_size (0, method_inst != NULL); if (method_inst) - size -= sizeof (MonoMethodRuntimeGenericContext) / sizeof (gpointer); + size -= MONO_SIZEOF_METHOD_RUNTIME_GENERIC_CONTEXT / sizeof (gpointer); for (i = 0; ; ++i) { int offset; if (method_inst && i == 0) - offset = sizeof (MonoMethodRuntimeGenericContext) / sizeof (gpointer); + offset = MONO_SIZEOF_METHOD_RUNTIME_GENERIC_CONTEXT / sizeof (gpointer); else offset = 0; if (slot < first_slot + size - 1) { rgctx_index = slot - first_slot + 1 + offset; info = rgctx [rgctx_index]; - if (info) + if (info) { + mono_domain_unlock (domain); return info; + } break; } if (!rgctx [offset + 0]) @@ -1117,15 +1125,29 @@ fill_runtime_generic_context (MonoVTable *class_vtable, MonoRuntimeGenericContex g_assert (!rgctx [rgctx_index]); + mono_domain_unlock (domain); + oti = class_get_rgctx_template_oti (class_uninstantiated (class), method_inst ? method_inst->type_argc : 0, slot, TRUE, &do_free); + /* This might take the loader lock */ + info = instantiate_other_info (domain, &oti, &context, class); /* if (method_inst) g_print ("filling mrgctx slot %d table %d index %d\n", slot, i, rgctx_index); */ - info = rgctx [rgctx_index] = instantiate_other_info (domain, &oti, &context, class); + /*FIXME We should use CAS here, no need to take a lock.*/ + mono_domain_lock (domain); + + /* Check whether the slot hasn't been instantiated in the + meantime. */ + if (rgctx [rgctx_index]) + info = rgctx [rgctx_index]; + else + rgctx [rgctx_index] = info; + + mono_domain_unlock (domain); if (do_free) free_inflated_info (oti.info_type, oti.data); @@ -1164,10 +1186,10 @@ mono_class_fill_runtime_generic_context (MonoVTable *class_vtable, guint32 slot) num_alloced++; } - info = fill_runtime_generic_context (class_vtable, rgctx, slot, 0); - mono_domain_unlock (domain); + info = fill_runtime_generic_context (class_vtable, rgctx, slot, 0); + return info; } @@ -1181,16 +1203,11 @@ mono_class_fill_runtime_generic_context (MonoVTable *class_vtable, guint32 slot) gpointer mono_method_fill_runtime_generic_context (MonoMethodRuntimeGenericContext *mrgctx, guint32 slot) { - MonoDomain *domain = mrgctx->class_vtable->domain; gpointer info; - mono_domain_lock (domain); - info = fill_runtime_generic_context (mrgctx->class_vtable, (MonoRuntimeGenericContext*)mrgctx, slot, mrgctx->method_inst); - mono_domain_unlock (domain); - return info; } @@ -1219,6 +1236,8 @@ mrgctx_equal_func (gconstpointer a, gconstpointer b) * * Returns the MRGCTX for the generic method(s) with the given * method_inst of the given class_vtable. + * + * LOCKING: Take the domain lock. */ MonoMethodRuntimeGenericContext* mono_method_lookup_rgctx (MonoVTable *class_vtable, MonoGenericInst *method_inst) @@ -1331,6 +1350,23 @@ mono_method_is_generic_impl (MonoMethod *method) return FALSE; } +static gboolean +has_constraints (MonoGenericContainer *container) +{ + //int i; + + return FALSE; + /* + g_assert (container->type_argc > 0); + g_assert (container->type_params); + + for (i = 0; i < container->type_argc; ++i) + if (container->type_params [i].constraints) + return TRUE; + return FALSE; + */ +} + /* * mono_method_is_generic_sharable_impl: * @method: a method @@ -1356,9 +1392,7 @@ mono_method_is_generic_sharable_impl (MonoMethod *method, gboolean allow_type_va g_assert (inflated->declaring); if (inflated->declaring->is_generic) { - g_assert (mono_method_get_generic_container (inflated->declaring)->type_params); - - if (mono_method_get_generic_container (inflated->declaring)->type_params->constraints) + if (has_constraints (mono_method_get_generic_container (inflated->declaring))) return FALSE; } } @@ -1368,10 +1402,9 @@ mono_method_is_generic_sharable_impl (MonoMethod *method, gboolean allow_type_va return FALSE; g_assert (method->klass->generic_class->container_class && - method->klass->generic_class->container_class->generic_container && - method->klass->generic_class->container_class->generic_container->type_params); + method->klass->generic_class->container_class->generic_container); - if (method->klass->generic_class->container_class->generic_container->type_params->constraints) + if (has_constraints (method->klass->generic_class->container_class->generic_container)) return FALSE; } @@ -1397,3 +1430,91 @@ mono_method_needs_static_rgctx_invoke (MonoMethod *method, gboolean allow_type_v method->klass->valuetype) && (method->klass->generic_class || method->klass->generic_container); } + +static MonoGenericInst* +get_object_generic_inst (int type_argc) +{ + MonoType **type_argv; + int i; + + type_argv = alloca (sizeof (MonoType*) * type_argc); + + for (i = 0; i < type_argc; ++i) + type_argv [i] = &mono_defaults.object_class->byval_arg; + + return mono_metadata_get_generic_inst (type_argc, type_argv); +} + +/* + * mono_method_construct_object_context: + * @method: a method + * + * Returns a generic context for method with all type variables for + * class and method instantiated with Object. + */ +MonoGenericContext +mono_method_construct_object_context (MonoMethod *method) +{ + MonoGenericContext object_context; + + g_assert (!method->klass->generic_class); + if (method->klass->generic_container) { + int type_argc = method->klass->generic_container->type_argc; + + object_context.class_inst = get_object_generic_inst (type_argc); + } else { + object_context.class_inst = NULL; + } + + if (mono_method_get_context_general (method, TRUE)->method_inst) { + int type_argc = mono_method_get_context_general (method, TRUE)->method_inst->type_argc; + + object_context.method_inst = get_object_generic_inst (type_argc); + } else { + object_context.method_inst = NULL; + } + + g_assert (object_context.class_inst || object_context.method_inst); + + return object_context; +} + +/* + * mono_domain_lookup_shared_generic: + * @domain: a domain + * @open_method: an open generic method + * + * Looks up the jit info for method via the domain's jit code hash. + */ +MonoJitInfo* +mono_domain_lookup_shared_generic (MonoDomain *domain, MonoMethod *open_method) +{ + static gboolean inited = FALSE; + static int lookups = 0; + static int failed_lookups = 0; + + MonoGenericContext object_context; + MonoMethod *object_method; + MonoJitInfo *ji; + + object_context = mono_method_construct_object_context (open_method); + object_method = mono_class_inflate_generic_method (open_method, &object_context); + + mono_domain_jit_code_hash_lock (domain); + ji = mono_internal_hash_table_lookup (&domain->jit_code_hash, object_method); + if (ji && !ji->has_generic_jit_info) + ji = NULL; + mono_domain_jit_code_hash_unlock (domain); + + if (!inited) { + mono_counters_register ("Shared generic lookups", MONO_COUNTER_INT|MONO_COUNTER_GENERICS, &lookups); + mono_counters_register ("Failed shared generic lookups", MONO_COUNTER_INT|MONO_COUNTER_GENERICS, &failed_lookups); + inited = TRUE; + } + + ++lookups; + if (!ji) + ++failed_lookups; + + return ji; +}