text: /**
   * \file
   * Support functions for generic sharing.
   *
   * Author:
   *   Mark Probst (mark.probst@gmail.com)
   *
   * Copyright 2007-2011 Novell, Inc (http://www.novell.com)
   * Copyright 2011 Xamarin, Inc (http://www.xamarin.com)
   * Licensed under the MIT license. See LICENSE file in the project root for full license information.
   */
  
  #include <config.h>
  
  #include <mono/metadata/class.h>
  #include <mono/metadata/method-builder.h>
  #include <mono/metadata/method-builder-ilgen.h>
  #include <mono/metadata/method-builder-ilgen-internals.h>
  #include <mono/metadata/reflection-internals.h>
  #include <mono/metadata/abi-details.h>
  #include <mono/utils/mono-counters.h>
  #include <mono/utils/atomic.h>
  #include <mono/utils/unlocked.h>
  
  #include "mini.h"
  #include "aot-runtime.h"
  #include "mini-runtime.h"
  #include "llvmonly-runtime.h"
  #include "interp/interp.h"
  
  #define ALLOW_PARTIAL_SHARING TRUE
  //#define ALLOW_PARTIAL_SHARING FALSE
  
  #if 0
  #define DEBUG(...) __VA_ARGS__
  #else
  #define DEBUG(...)
  #endif
  
  static void
  mono_class_unregister_image_generic_subclasses (MonoImage *image, gpointer user_data);
  
  /* Counters */
  static gint32 rgctx_template_num_allocated;
  static gint32 rgctx_template_bytes_allocated;
  static gint32 rgctx_oti_num_allocated;
  static gint32 rgctx_oti_bytes_allocated;
  static gint32 rgctx_oti_num_markers;
  static gint32 rgctx_oti_num_data;
  static gint32 rgctx_max_slot_number;
  static gint32 rgctx_num_allocated;
  static gint32 rgctx_num_arrays_allocated;
  static gint32 rgctx_bytes_allocated;
  static gint32 mrgctx_num_arrays_allocated;
  static gint32 mrgctx_bytes_allocated;
  static gint32 gsharedvt_num_trampolines;
  
  #define gshared_lock() mono_os_mutex_lock (&gshared_mutex)
  #define gshared_unlock() mono_os_mutex_unlock (&gshared_mutex)
  static mono_mutex_t gshared_mutex;
  
  static gboolean partial_supported = FALSE;
  
  static gboolean
  partial_sharing_supported (void)
  {
  	if (!ALLOW_PARTIAL_SHARING)
  		return FALSE;
  	/* Enable this when AOT compiling or running in full-aot mode */
  	if (mono_aot_only)
  		return TRUE;
  	if (partial_supported)
  		return TRUE;
  	return FALSE;
  }
  
  static int
  type_check_context_used (MonoType *type, gboolean recursive)
  {
  	switch (mono_type_get_type_internal (type)) {
  	case MONO_TYPE_VAR:
  		return MONO_GENERIC_CONTEXT_USED_CLASS;
  	case MONO_TYPE_MVAR:
  		return MONO_GENERIC_CONTEXT_USED_METHOD;
  	case MONO_TYPE_SZARRAY:
  		return mono_class_check_context_used (mono_type_get_class_internal (type));
  	case MONO_TYPE_ARRAY:
  		return mono_class_check_context_used (mono_type_get_array_type (type)->eklass);
  	case MONO_TYPE_CLASS:
  		if (recursive)
  			return mono_class_check_context_used (mono_type_get_class_internal (type));
  		else
  			return 0;
  	case MONO_TYPE_GENERICINST:
  		if (recursive) {
  			MonoGenericClass *gclass = type->data.generic_class;
  
  			g_assert (mono_class_is_gtd (gclass->container_class));
  			return mono_generic_context_check_used (&gclass->context);
  		} else {
  			return 0;
  		}
  	default:
  		return 0;
  	}
  }
  
  static int
  inst_check_context_used (MonoGenericInst *inst)
  {
  	int context_used = 0;
  	int i;
  
  	if (!inst)
  		return 0;
  
  	for (i = 0; i < inst->type_argc; ++i)
  		context_used |= type_check_context_used (inst->type_argv [i], TRUE);
  
  	return context_used;
  }
  
  /*
   * mono_generic_context_check_used:
   * @context: a generic context
   *
   * Checks whether the context uses a type variable.  Returns an int
   * with the bit MONO_GENERIC_CONTEXT_USED_CLASS set to reflect whether
   * the context's class instantiation uses type variables.
   */
  int
  mono_generic_context_check_used (MonoGenericContext *context)
  {
  	int context_used = 0;
  
  	context_used |= inst_check_context_used (context->class_inst);
  	context_used |= inst_check_context_used (context->method_inst);
  
  	return context_used;
  }
  
  /*
   * mono_class_check_context_used:
   * @class: a class
   *
   * Checks whether the class's generic context uses a type variable.
   * Returns an int with the bit MONO_GENERIC_CONTEXT_USED_CLASS set to
   * reflect whether the context's class instantiation uses type
   * variables.
   */
  int
  mono_class_check_context_used (MonoClass *klass)
  {
  	int context_used = 0;
  
  	context_used |= type_check_context_used (m_class_get_byval_arg (klass), FALSE);
  
  	if (mono_class_is_ginst (klass))
  		context_used |= mono_generic_context_check_used (&mono_class_get_generic_class (klass)->context);
  	else if (mono_class_is_gtd (klass))
  		context_used |= mono_generic_context_check_used (&mono_class_get_generic_container (klass)->context);
  
  	return context_used;
  }
  
  /*
   * LOCKING: loader lock
   */
  static MonoRuntimeGenericContextInfoTemplate*
  get_info_templates (MonoRuntimeGenericContextTemplate *template_, int type_argc)
  {
  	g_assert (type_argc >= 0);
  	if (type_argc == 0)
  		return template_->infos;
  	return (MonoRuntimeGenericContextInfoTemplate *)g_slist_nth_data (template_->method_templates, type_argc - 1);
  }
  
  /*
   * LOCKING: loader lock
   */
  static void
  set_info_templates (MonoImage *image, MonoRuntimeGenericContextTemplate *template_, int type_argc,
  	MonoRuntimeGenericContextInfoTemplate *oti)
  {
  	g_assert (type_argc >= 0);
  	if (type_argc == 0)
  		template_->infos = oti;
  	else {
  		int length = g_slist_length (template_->method_templates);
  		GSList *list;
  
  		/* FIXME: quadratic! */
  		while (length < type_argc) {
  			template_->method_templates = mono_g_slist_append_image (image, template_->method_templates, NULL);
  			length++;
  		}
  
  		list = g_slist_nth (template_->method_templates, type_argc - 1);
  		g_assert (list);
  		list->data = oti;
  	}
  }
  
  /*
   * LOCKING: loader lock
   */
  static int
  template_get_max_argc (MonoRuntimeGenericContextTemplate *template_)
  {
  	return g_slist_length (template_->method_templates);
  }
  
  /*
   * LOCKING: loader lock
   */
  static MonoRuntimeGenericContextInfoTemplate*
  rgctx_template_get_other_slot (MonoRuntimeGenericContextTemplate *template_, int type_argc, int slot)
  {
  	int i;
  	MonoRuntimeGenericContextInfoTemplate *oti;
  
  	g_assert (slot >= 0);
  
  	for (oti = get_info_templates (template_, type_argc), i = 0; i < slot; oti = oti->next, ++i) {
  		if (!oti)
  			return NULL;
  	}
  
  	return oti;
  }
  
  /*
   * LOCKING: loader lock
   */
  static int
  rgctx_template_num_infos (MonoRuntimeGenericContextTemplate *template_, int type_argc)
  {
  	MonoRuntimeGenericContextInfoTemplate *oti;
  	int i;
  
  	for (i = 0, oti = get_info_templates (template_, type_argc); oti; ++i, oti = oti->next)
  		;
  
  	return i;
  }
  
  /* Maps from uninstantiated generic classes to GList's of
   * uninstantiated generic classes whose parent is the key class or an
   * instance of the key class.
   *
   * LOCKING: loader lock
   */
  static GHashTable *generic_subclass_hash;
  
  /*
   * LOCKING: templates lock
   */
  static void
  class_set_rgctx_template (MonoClass *klass, MonoRuntimeGenericContextTemplate *rgctx_template)
  {
  	if (!m_class_get_image (klass)->rgctx_template_hash)
  		m_class_get_image (klass)->rgctx_template_hash = g_hash_table_new (mono_aligned_addr_hash, NULL);
  
  	g_hash_table_insert (m_class_get_image (klass)->rgctx_template_hash, klass, rgctx_template);
  }
  
  /*
   * LOCKING: loader lock
   */
  static MonoRuntimeGenericContextTemplate*
  class_lookup_rgctx_template (MonoClass *klass)
  {
  	MonoRuntimeGenericContextTemplate *template_;
  
  	if (!m_class_get_image (klass)->rgctx_template_hash)
  		return NULL;
  
  	template_ = (MonoRuntimeGenericContextTemplate *)g_hash_table_lookup (m_class_get_image (klass)->rgctx_template_hash, klass);
  
  	return template_;
  }
  
  /*
   * LOCKING: loader lock
   */
  static void
  register_generic_subclass (MonoClass *klass)
  {
  	MonoClass *parent = m_class_get_parent (klass);
  	MonoClass *subclass;
  	MonoRuntimeGenericContextTemplate *rgctx_template = class_lookup_rgctx_template (klass);
  
  	g_assert (rgctx_template);
  
  	if (mono_class_is_ginst (parent))
  		parent = mono_class_get_generic_class (parent)->container_class;
  
  	if (!generic_subclass_hash)
  		generic_subclass_hash = g_hash_table_new (mono_aligned_addr_hash, NULL);
  
  	subclass = (MonoClass *)g_hash_table_lookup (generic_subclass_hash, parent);
  	rgctx_template->next_subclass = subclass;
  	g_hash_table_insert (generic_subclass_hash, parent, klass);
  }
  
  static void
  move_subclasses_not_in_image_foreach_func (MonoClass *klass, MonoClass *subclass, MonoImage *image)
  {
  	MonoClass *new_list;
  
  	if (m_class_get_image (klass) == image) {
  		/* The parent class itself is in the image, so all the
  		   subclasses must be in the image, too.  If not,
  		   we're removing an image containing a class which
  		   still has a subclass in another image. */
  
  		while (subclass) {
  			g_assert (m_class_get_image (subclass) == image);
  			subclass = class_lookup_rgctx_template (subclass)->next_subclass;
  		}
  
  		return;
  	}
  
  	new_list = NULL;
  	while (subclass) {
  		MonoRuntimeGenericContextTemplate *subclass_template = class_lookup_rgctx_template (subclass);
  		MonoClass *next = subclass_template->next_subclass;
  
  		if (m_class_get_image (subclass) != image) {
  			subclass_template->next_subclass = new_list;
  			new_list = subclass;
  		}
  
  		subclass = next;
  	}
  
  	if (new_list)
  		g_hash_table_insert (generic_subclass_hash, klass, new_list);
  }
  
  /*
   * mono_class_unregister_image_generic_subclasses:
   * @image: an image
   *
   * Removes all classes of the image from the generic subclass hash.
   * Must be called when an image is unloaded.
   */
  static void
  mono_class_unregister_image_generic_subclasses (MonoImage *image, gpointer user_data)
  {
  	GHashTable *old_hash;
  
  	//g_print ("unregistering image %s\n", image->name);
  
  	if (!generic_subclass_hash)
  		return;
  
  	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);
  
  	mono_loader_unlock ();
  
  	g_hash_table_destroy (old_hash);
  }
  
  static MonoRuntimeGenericContextTemplate*
  alloc_template (MonoClass *klass)
  {
  	gint32 size = sizeof (MonoRuntimeGenericContextTemplate);
  
  	mono_atomic_inc_i32 (&rgctx_template_num_allocated);
  	mono_atomic_fetch_add_i32 (&rgctx_template_bytes_allocated, size);
  
  	return (MonoRuntimeGenericContextTemplate *)mono_image_alloc0 (m_class_get_image (klass), size);
  }
  
  /* LOCKING: Takes the loader lock */
  static MonoRuntimeGenericContextInfoTemplate*
  alloc_oti (MonoImage *image)
  {
  	gint32 size = sizeof (MonoRuntimeGenericContextInfoTemplate);
  
  	mono_atomic_inc_i32 (&rgctx_oti_num_allocated);
  	mono_atomic_fetch_add_i32 (&rgctx_oti_bytes_allocated, size);
  
  	return (MonoRuntimeGenericContextInfoTemplate *)mono_image_alloc0 (image, size);
  }
  
  #define MONO_RGCTX_SLOT_USED_MARKER	((gpointer)mono_get_object_type ())
  
  /*
   * Return true if this info type has the notion of identify.
   *
   * Some info types expect that each insert results in a new slot been assigned.
   */
  static int
  info_has_identity (MonoRgctxInfoType info_type)
  {
  	return info_type != MONO_RGCTX_INFO_CAST_CACHE;
  }
  
  /*
   * LOCKING: loader lock
   */
  static void
  rgctx_template_set_slot (MonoImage *image, MonoRuntimeGenericContextTemplate *template_, int type_argc,
  	int slot, gpointer data, MonoRgctxInfoType info_type)
  {
  	int i;
  	MonoRuntimeGenericContextInfoTemplate *list = get_info_templates (template_, type_argc);
  	MonoRuntimeGenericContextInfoTemplate **oti = &list;
  
  	g_assert (slot >= 0);
  	g_assert (data);
  
  	i = 0;
  	while (i <= slot) {
  		if (i > 0)
  			oti = &(*oti)->next;
  		if (!*oti)
  			*oti = alloc_oti (image);
  		++i;
  	}
  
  	g_assert (!(*oti)->data);
  	(*oti)->data = data;
  	(*oti)->info_type = info_type;
  
  	set_info_templates (image, template_, type_argc, list);
  
  	/* interlocked by loader lock (by definition) */
  	if (data == MONO_RGCTX_SLOT_USED_MARKER)
  		UnlockedIncrement (&rgctx_oti_num_markers);
  	else
  		UnlockedIncrement (&rgctx_oti_num_data);
  }
  
  /*
   * mono_method_get_declaring_generic_method:
   * @method: an inflated method
   *
   * Returns an inflated method's declaring method.
   */
  MonoMethod*
  mono_method_get_declaring_generic_method (MonoMethod *method)
  {
  	MonoMethodInflated *inflated;
  
  	g_assert (method->is_inflated);
  
  	inflated = (MonoMethodInflated*)method;
  
  	return inflated->declaring;
  }
  
  /*
   * mono_class_get_method_generic:
   * @klass: a class
   * @method: a method
   * @error: set on error
   *
   * Given a class and a generic method, which has to be of an
   * instantiation of the same class that klass is an instantiation of,
   * returns the corresponding method in klass.  Example:
   *
   * klass is Gen<string>
   * method is Gen<object>.work<int>
   *
   * returns: Gen<string>.work<int>
   *
   * On error sets @error and returns NULL.
   */
  MonoMethod*
  mono_class_get_method_generic (MonoClass *klass, MonoMethod *method, MonoError *error)
  {
  	MonoMethod *declaring, *m;
  	int i;
  
  	if (method->is_inflated)
  		declaring = mono_method_get_declaring_generic_method (method);
  	else
  		declaring = method;
  
  	m = NULL;
  	if (mono_class_is_ginst (klass)) {
  		m = mono_class_get_inflated_method (klass, declaring, error);
  		return_val_if_nok (error, NULL);
  	}
  
  	if (!m) {
  		mono_class_setup_methods (klass);
  		if (mono_class_has_failure (klass))
  			return NULL;
  		int mcount = mono_class_get_method_count (klass);
  		MonoMethod **klass_methods = m_class_get_methods (klass);
  		for (i = 0; i < mcount; ++i) {
  			m = klass_methods [i];
  			if (m == declaring)
  				break;
  			if (m->is_inflated && mono_method_get_declaring_generic_method (m) == declaring)
  				break;
  		}
  		if (i >= mcount)
  			return NULL;
  	}
  
  	if (method != declaring) {
  		MonoGenericContext context;
  
  		context.class_inst = NULL;
  		context.method_inst = mono_method_get_context (method)->method_inst;
  
  		m = mono_class_inflate_generic_method_checked (m, &context, error);
  		return_val_if_nok (error, NULL);
  	}
  
  	return m;
  }
  
  static gpointer
  inflate_info (MonoMemoryManager *mem_manager, MonoRuntimeGenericContextInfoTemplate *oti, MonoGenericContext *context, MonoClass *klass, gboolean temporary)
  {
  	gpointer data = oti->data;
  	MonoRgctxInfoType info_type = oti->info_type;
  	ERROR_DECL (error);
  
  	g_assert (data);
  
  	if (data == MONO_RGCTX_SLOT_USED_MARKER)
  		return MONO_RGCTX_SLOT_USED_MARKER;
  
  	switch (info_type)
  	{
  	case MONO_RGCTX_INFO_STATIC_DATA:
  	case MONO_RGCTX_INFO_KLASS:
  	case MONO_RGCTX_INFO_ELEMENT_KLASS:
  	case MONO_RGCTX_INFO_VTABLE:
  	case MONO_RGCTX_INFO_TYPE:
  	case MONO_RGCTX_INFO_REFLECTION_TYPE:
  	case MONO_RGCTX_INFO_CAST_CACHE:
  	case MONO_RGCTX_INFO_ARRAY_ELEMENT_SIZE:
  	case MONO_RGCTX_INFO_VALUE_SIZE:
  	case MONO_RGCTX_INFO_CLASS_SIZEOF:
  	case MONO_RGCTX_INFO_CLASS_BOX_TYPE:
  	case MONO_RGCTX_INFO_CLASS_IS_REF_OR_CONTAINS_REFS:
  	case MONO_RGCTX_INFO_MEMCPY:
  	case MONO_RGCTX_INFO_BZERO:
  	case MONO_RGCTX_INFO_LOCAL_OFFSET:
  	case MONO_RGCTX_INFO_NULLABLE_CLASS_BOX:
  	case MONO_RGCTX_INFO_NULLABLE_CLASS_UNBOX: {
  		gpointer result = mono_class_inflate_generic_type_with_mempool (temporary ? NULL : m_class_get_image (klass),
  			(MonoType *)data, context, error);
  		mono_error_assert_msg_ok (error, "Could not inflate generic type"); /* FIXME proper error handling */
  		return result;
  	}
  
  	case MONO_RGCTX_INFO_METHOD:
  	case MONO_RGCTX_INFO_METHOD_FTNDESC:
  	case MONO_RGCTX_INFO_GENERIC_METHOD_CODE:
  	case MONO_RGCTX_INFO_GSHAREDVT_OUT_WRAPPER:
  	case MONO_RGCTX_INFO_METHOD_RGCTX:
  	case MONO_RGCTX_INFO_METHOD_CONTEXT:
  	case MONO_RGCTX_INFO_REMOTING_INVOKE_WITH_CHECK:
  	case MONO_RGCTX_INFO_METHOD_DELEGATE_CODE:
  	case MONO_RGCTX_INFO_INTERP_METHOD:
  	case MONO_RGCTX_INFO_LLVMONLY_INTERP_ENTRY: {
  		MonoMethod *method = (MonoMethod *)data;
  		MonoMethod *inflated_method;
  		MonoType *inflated_type = mono_class_inflate_generic_type_checked (m_class_get_byval_arg (method->klass), context, error);
  		mono_error_assert_ok (error); /* FIXME don't swallow the error */
  
  		MonoClass *inflated_class = mono_class_from_mono_type_internal (inflated_type);
  
  		mono_metadata_free_type (inflated_type);
  
  		mono_class_init_internal (inflated_class);
  
  		g_assert (!method->wrapper_type);
  
  		if (m_class_get_byval_arg (inflated_class)->type == MONO_TYPE_ARRAY ||
  			m_class_get_byval_arg (inflated_class)->type == MONO_TYPE_SZARRAY) {
  			inflated_method = mono_method_search_in_array_class (inflated_class,
  				method->name, method->signature);
  		} else {
  			ERROR_DECL (error);
  			inflated_method = mono_class_inflate_generic_method_checked (method, context, error);
  			g_assert (is_ok (error)); /* FIXME don't swallow the error */
  		}
  		mono_class_init_internal (inflated_method->klass);
  		g_assert (inflated_method->klass == inflated_class);
  		return inflated_method;
  	}
  	case MONO_RGCTX_INFO_METHOD_GSHAREDVT_INFO: {
  		MonoGSharedVtMethodInfo *oinfo = (MonoGSharedVtMethodInfo *)data;
  		MonoGSharedVtMethodInfo *res;
  		int i;
  
  		res = (MonoGSharedVtMethodInfo *)mono_mem_manager_alloc0 (mem_manager, sizeof (MonoGSharedVtMethodInfo));
  		/*
  		res->nlocals = info->nlocals;
  		res->locals_types = g_new0 (MonoType*, info->nlocals);
  		for (i = 0; i < info->nlocals; ++i)
  			res->locals_types [i] = mono_class_inflate_generic_type (info->locals_types [i], context);
  		*/
  		res->num_entries = oinfo->num_entries;
  		res->entries = (MonoRuntimeGenericContextInfoTemplate *)mono_mem_manager_alloc0 (mem_manager, sizeof (MonoRuntimeGenericContextInfoTemplate) * oinfo->num_entries);
  		for (i = 0; i < oinfo->num_entries; ++i) {
  			MonoRuntimeGenericContextInfoTemplate *otemplate = &oinfo->entries [i];
  			MonoRuntimeGenericContextInfoTemplate *template_ = &res->entries [i];
  
  			memcpy (template_, otemplate, sizeof (MonoRuntimeGenericContextInfoTemplate));
  			template_->data = inflate_info (mem_manager, template_, context, klass, FALSE);
  		}
  		return res;
  	}
  	case MONO_RGCTX_INFO_METHOD_GSHAREDVT_OUT_TRAMPOLINE:
  	case MONO_RGCTX_INFO_METHOD_GSHAREDVT_OUT_TRAMPOLINE_VIRT: {
  		MonoJumpInfoGSharedVtCall *info = (MonoJumpInfoGSharedVtCall *)data;
  		MonoMethod *method = info->method;
  		MonoMethod *inflated_method;
  		MonoType *inflated_type = mono_class_inflate_generic_type_checked (m_class_get_byval_arg (method->klass), context, error);
  		mono_error_assert_ok (error); /* FIXME don't swallow the error */
  		WrapperInfo *winfo = NULL;
  
  		MonoClass *inflated_class = mono_class_from_mono_type_internal (inflated_type);
  		MonoJumpInfoGSharedVtCall *res;
  
  		res = (MonoJumpInfoGSharedVtCall *)mono_mem_manager_alloc0 (mem_manager, sizeof (MonoJumpInfoGSharedVtCall));
  		/* Keep the original signature */
  		res->sig = info->sig;
  
  		mono_metadata_free_type (inflated_type);
  
  		mono_class_init_internal (inflated_class);
  
  		if (method->wrapper_type) {
  			winfo = mono_marshal_get_wrapper_info (method);
  
  			g_assert (winfo);
  			g_assert (winfo->subtype == WRAPPER_SUBTYPE_SYNCHRONIZED_INNER);
  			method = winfo->d.synchronized_inner.method;
  		}
  
  		if (m_class_get_byval_arg (inflated_class)->type == MONO_TYPE_ARRAY ||
  			m_class_get_byval_arg (inflated_class)->type == MONO_TYPE_SZARRAY) {
  			inflated_method = mono_method_search_in_array_class (inflated_class,
  				method->name, method->signature);
  		} else {
  			ERROR_DECL (error);
  			inflated_method = mono_class_inflate_generic_method_checked (method, context, error);
  			g_assert (is_ok (error)); /* FIXME don't swallow the error */
  		}
  		mono_class_init_internal (inflated_method->klass);
  		g_assert (inflated_method->klass == inflated_class);
  
  		if (winfo) {
  			g_assert (winfo->subtype == WRAPPER_SUBTYPE_SYNCHRONIZED_INNER);
  			inflated_method = mono_marshal_get_synchronized_inner_wrapper (inflated_method);
  		}
  
  		res->method = inflated_method;
  
  		return res;
  	}
  
  	case MONO_RGCTX_INFO_CLASS_FIELD:
  	case MONO_RGCTX_INFO_FIELD_OFFSET: {
  		ERROR_DECL (error);
  		MonoClassField *field = (MonoClassField *)data;
  		MonoType *inflated_type = mono_class_inflate_generic_type_checked (m_class_get_byval_arg (m_field_get_parent (field)), context, error);
  		mono_error_assert_ok (error); /* FIXME don't swallow the error */
  
  		MonoClass *inflated_class = mono_class_from_mono_type_internal (inflated_type);
  		int i = field - m_class_get_fields (m_field_get_parent (field));
  		gpointer dummy = NULL;
  
  		mono_metadata_free_type (inflated_type);
  
  		mono_class_get_fields_internal (inflated_class, &dummy);
  		g_assert (m_class_get_fields (inflated_class));
  
  		return &m_class_get_fields (inflated_class) [i];
  	}
  	case MONO_RGCTX_INFO_SIG_GSHAREDVT_IN_TRAMPOLINE_CALLI:
  	case MONO_RGCTX_INFO_SIG_GSHAREDVT_OUT_TRAMPOLINE_CALLI: {
  		MonoMethodSignature *sig = (MonoMethodSignature *)data;
  		MonoMethodSignature *isig;
  		ERROR_DECL (error);
  
  		isig = mono_inflate_generic_signature (sig, context, error);
  		g_assert (is_ok (error));
  		return isig;
  	}
  	case MONO_RGCTX_INFO_VIRT_METHOD_CODE:
  	case MONO_RGCTX_INFO_VIRT_METHOD_BOX_TYPE: {
  		MonoJumpInfoVirtMethod *info = (MonoJumpInfoVirtMethod *)data;
  		MonoJumpInfoVirtMethod *res;
  		MonoType *t;
  		ERROR_DECL (error);
  
  		// FIXME: Temporary
  		res = (MonoJumpInfoVirtMethod *)mono_mem_manager_alloc0 (mem_manager, sizeof (MonoJumpInfoVirtMethod));
  		t = mono_class_inflate_generic_type_checked (m_class_get_byval_arg (info->klass), context, error);
  		mono_error_assert_ok (error); /* FIXME don't swallow the error */
  
  		res->klass = mono_class_from_mono_type_internal (t);
  		mono_metadata_free_type (t);
  
  		res->method = mono_class_inflate_generic_method_checked (info->method, context, error);
  		g_assert (is_ok (error)); /* FIXME don't swallow the error */
  
  		return res;
  	}
  	case MONO_RGCTX_INFO_DELEGATE_TRAMP_INFO: {
  		ERROR_DECL (error);
  		MonoDelegateClassMethodPair *dele_info = (MonoDelegateClassMethodPair*)data;
  
  		MonoType *t = mono_class_inflate_generic_type_checked (m_class_get_byval_arg (dele_info->klass), context, error);
  		mono_error_assert_msg_ok (error, "Could not inflate generic type"); /* FIXME proper error handling */
  
  		MonoClass *klass = mono_class_from_mono_type_internal (t);
  		mono_metadata_free_type (t);
  
  		MonoMethod *method = mono_class_inflate_generic_method_checked (dele_info->method, context, error);
  		mono_error_assert_msg_ok (error, "Could not inflate generic method"); /* FIXME proper error handling */
  
  		// FIXME: Temporary
  		MonoDelegateClassMethodPair *res = (MonoDelegateClassMethodPair *)mono_mem_manager_alloc0 (mem_manager, sizeof (MonoDelegateClassMethodPair));
  		res->is_virtual = dele_info->is_virtual;
  		res->method = method;
  		res->klass = klass;
  		return res;
  
  	}
  	default:
  		g_assert_not_reached ();
  	}
  	/* Not reached, quiet compiler */
  	return NULL;
  }
  
  static void
  free_inflated_info (MonoRgctxInfoType info_type, gpointer info)
  {
  	if (!info)
  		return;
  
  	switch (info_type) {
  	case MONO_RGCTX_INFO_STATIC_DATA:
  	case MONO_RGCTX_INFO_KLASS:
  	case MONO_RGCTX_INFO_ELEMENT_KLASS:
  	case MONO_RGCTX_INFO_VTABLE:
  	case MONO_RGCTX_INFO_TYPE:
  	case MONO_RGCTX_INFO_REFLECTION_TYPE:
  	case MONO_RGCTX_INFO_CAST_CACHE:
  		mono_metadata_free_type ((MonoType *)info);
  		break;
  	default:
  		break;
  	}
  }
  
  static MonoRuntimeGenericContextInfoTemplate
  class_get_rgctx_template_oti (MonoClass *klass, int type_argc, guint32 slot, gboolean temporary, gboolean shared, gboolean *do_free);
  
  static MonoClass*
  class_uninstantiated (MonoClass *klass)
  {
  	if (mono_class_is_ginst (klass))
  		return mono_class_get_generic_class (klass)->container_class;
  	return klass;
  }
  
  /*
   * get_shared_class:
   *
   *   Return the class used to store information when using generic sharing.
   */
  static MonoClass*
  get_shared_class (MonoClass *klass)
  {
  	return class_uninstantiated (klass);
  }
  
  /*
   * mono_class_get_runtime_generic_context_template:
   * @class: a class
   *
   * Looks up or constructs, if necessary, the runtime generic context template for class.
   * The template is the same for all instantiations of a class.
   */
  static MonoRuntimeGenericContextTemplate*
  mono_class_get_runtime_generic_context_template (MonoClass *klass)
  {
  	MonoRuntimeGenericContextTemplate *parent_template, *template_;
  	guint32 i;
  
  	klass = get_shared_class (klass);
  
  	mono_loader_lock ();
  	template_ = class_lookup_rgctx_template (klass);
  	mono_loader_unlock ();
  
  	if (template_)
  		return template_;
  
  	//g_assert (get_shared_class (class) == class);
  
  	template_ = alloc_template (klass);
  
  	mono_loader_lock ();
  
  	if (m_class_get_parent (klass)) {
  		guint32 num_entries;
  		int max_argc, type_argc;
  
  		parent_template = mono_class_get_runtime_generic_context_template (m_class_get_parent (klass));
  		max_argc = template_get_max_argc (parent_template);
  
  		for (type_argc = 0; type_argc <= max_argc; ++type_argc) {
  			num_entries = rgctx_template_num_infos (parent_template, type_argc);
  
  			/* FIXME: quadratic! */
  			for (i = 0; i < num_entries; ++i) {
  				MonoRuntimeGenericContextInfoTemplate oti;
  
  				oti = class_get_rgctx_template_oti (m_class_get_parent (klass), type_argc, i, FALSE, FALSE, NULL);
  				if (oti.data && oti.data != MONO_RGCTX_SLOT_USED_MARKER) {
  					rgctx_template_set_slot (m_class_get_image (klass), template_, type_argc, i,
  											 oti.data, oti.info_type);
  				}
  			}
  		}
  	}
  
  	if (class_lookup_rgctx_template (klass)) {
  		/* some other thread already set the template */
  		template_ = class_lookup_rgctx_template (klass);
  	} else {
  		class_set_rgctx_template (klass, template_);
  
  		if (m_class_get_parent (klass))
  			register_generic_subclass (klass);
  	}
  
  	mono_loader_unlock ();
  
  	return template_;
  }
  
  /*
   * class_get_rgctx_template_oti:
   *
   *   Return the info template of CLASS numbered TYPE_ARGC/SLOT.
   * temporary signifies whether the inflated info (oti.data) will be
   * used temporarily, in which case it might be heap-allocated, or
   * 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 MonoRuntimeGenericContextInfoTemplate
  class_get_rgctx_template_oti (MonoClass *klass, int type_argc, guint32 slot, gboolean temporary, gboolean shared, gboolean *do_free)
  {
  	g_assert ((temporary && do_free) || (!temporary && !do_free));
  
  	DEBUG (printf ("get slot: %s %d\n", mono_type_full_name (m_class_get_byval_arg (class)), slot));
  
  	if (mono_class_is_ginst (klass) && !shared) {
  		MonoRuntimeGenericContextInfoTemplate oti;
  		gboolean tmp_do_free;
  		// FIXME:
  		MonoJitMemoryManager *jit_mm = get_default_jit_mm ();
  
  		oti = class_get_rgctx_template_oti (mono_class_get_generic_class (klass)->container_class,
  											type_argc, slot, TRUE, FALSE, &tmp_do_free);
  		if (oti.data) {
  			gpointer info = oti.data;
  			oti.data = inflate_info (jit_mm->mem_manager, &oti, &mono_class_get_generic_class (klass)->context, klass, temporary);
  			if (tmp_do_free)
  				free_inflated_info (oti.info_type, info);
  		}
  		if (temporary)
  			*do_free = TRUE;
  
  		return oti;
  	} else {
  		MonoRuntimeGenericContextTemplate *template_;
  		MonoRuntimeGenericContextInfoTemplate *oti;
  
  		template_ = mono_class_get_runtime_generic_context_template (klass);
  		oti = rgctx_template_get_other_slot (template_, type_argc, slot);
  		g_assert (oti);
  
  		if (temporary)
  			*do_free = FALSE;
  
  		return *oti;
  	}
  }
  
  // FIXME Consolidate the multiple functions named get_method_nofail.
  static MonoMethod*
  get_method_nofail (MonoClass *klass, const char *method_name, int num_params, int flags)
  {
  	MonoMethod *method;
  	ERROR_DECL (error);
  	method = mono_class_get_method_from_name_checked (klass, method_name, num_params, flags, error);
  	mono_error_assert_ok (error);
  	g_assertf (method, "Could not lookup method %s in %s", method_name, m_class_get_name (klass));
  	return method;
  }
  
  static gpointer
  class_type_info (MonoMemoryManager *mem_manager, MonoClass *klass, MonoRgctxInfoType info_type, MonoError *error)
  {
  	error_init (error);
  
  	switch (info_type) {
  	case MONO_RGCTX_INFO_STATIC_DATA: {
  		MonoVTable *vtable = mono_class_vtable_checked (klass, error);
  		return_val_if_nok (error, NULL);
  		return mono_vtable_get_static_field_data (vtable);
  	}
  	case MONO_RGCTX_INFO_KLASS:
  		return klass;
  	case MONO_RGCTX_INFO_ELEMENT_KLASS:
  		return m_class_get_element_class (klass);
  	case MONO_RGCTX_INFO_VTABLE: {
  		MonoVTable *vtable = mono_class_vtable_checked (klass, error);
  		return_val_if_nok (error, NULL);
  		return vtable;
  	}
  	case MONO_RGCTX_INFO_CAST_CACHE: {
  		/*First slot is the cache itself, the second the vtable.*/
  		gpointer **cache_data = (gpointer **)mono_mem_manager_alloc0 (mem_manager, sizeof (gpointer) * 2);
  		cache_data [1] = (gpointer *)klass;
  		return cache_data;
  	}
  	case MONO_RGCTX_INFO_ARRAY_ELEMENT_SIZE:
  		return GUINT_TO_POINTER (mono_class_array_element_size (klass));
  	case MONO_RGCTX_INFO_VALUE_SIZE:
  		if (MONO_TYPE_IS_REFERENCE (m_class_get_byval_arg (klass)))
  			return GUINT_TO_POINTER (sizeof (gpointer));
  		else
  			return GUINT_TO_POINTER (mono_class_value_size (klass, NULL));
  	case MONO_RGCTX_INFO_CLASS_SIZEOF: {
  		int align;
  		return GINT_TO_POINTER (mono_type_size (m_class_get_byval_arg (klass), &align));
  	}
  	case MONO_RGCTX_INFO_CLASS_BOX_TYPE:
  		if (MONO_TYPE_IS_REFERENCE (m_class_get_byval_arg (klass)))
  			return GUINT_TO_POINTER (MONO_GSHAREDVT_BOX_TYPE_REF);
  		else if (mono_class_is_nullable (klass))
  			return GUINT_TO_POINTER (MONO_GSHAREDVT_BOX_TYPE_NULLABLE);
  		else
  			return GUINT_TO_POINTER (MONO_GSHAREDVT_BOX_TYPE_VTYPE);
  	case MONO_RGCTX_INFO_CLASS_IS_REF_OR_CONTAINS_REFS:
  		mono_class_init_internal (klass);
  		/* Can't return 0 */
  		if (MONO_TYPE_IS_REFERENCE (m_class_get_byval_arg (klass)) || m_class_has_references (klass))
  			return GUINT_TO_POINTER (2);
  		else
  			return GUINT_TO_POINTER (1);
  	case MONO_RGCTX_INFO_MEMCPY:
  	case MONO_RGCTX_INFO_BZERO: {
  		static MonoMethod *memcpy_method [17];
  		static MonoMethod *bzero_method [17];
  		MonoJitMemoryManager *jit_mm;
  		int size;
  		guint32 align;
  
  		/* The memcpy methods are in the default memory alc */
  		jit_mm = get_default_jit_mm ();
  
  		if (MONO_TYPE_IS_REFERENCE (m_class_get_byval_arg (klass))) {
  			size = sizeof (gpointer);
  			align = sizeof (gpointer);
  		} else {
  			size = mono_class_value_size (klass, &align);
  		}
  
  		if (size != 1 && size != 2 && size != 4 && size != 8)
  			size = 0;
  		if (align < size)
  			size = 0;
  
  		if (info_type == MONO_RGCTX_INFO_MEMCPY) {
  			if (!memcpy_method [size]) {
  				MonoMethod *m;
  				char name [32];
  
  				if (size == 0)
  					sprintf (name, "memcpy");
  				else
  					sprintf (name, "memcpy_aligned_%d", size);
  				m = get_method_nofail (mono_defaults.string_class, name, 3, 0);
  				g_assert (m);
  				mono_memory_barrier ();
  				memcpy_method [size] = m;
  			}
  			if (!jit_mm->memcpy_addr [size]) {
  				gpointer addr = mono_compile_method_checked (memcpy_method [size], error);
  				mono_memory_barrier ();
  				jit_mm->memcpy_addr [size] = (gpointer *)addr;
  				mono_error_assert_ok (error);
  			}
  			return jit_mm->memcpy_addr [size];
  		} else {
  			if (!bzero_method [size]) {
  				MonoMethod *m;
  				char name [32];
  
  				if (size == 0)
  					sprintf (name, "bzero");
  				else
  					sprintf (name, "bzero_aligned_%d", size);
  				m = get_method_nofail (mono_defaults.string_class, name, 2, 0);
  				g_assert (m);
  				mono_memory_barrier ();
  				bzero_method [size] = m;
  			}
  			if (!jit_mm->bzero_addr [size]) {
  				gpointer addr = mono_compile_method_checked (bzero_method [size], error);
  				mono_memory_barrier ();
  				jit_mm->bzero_addr [size] = (gpointer *)addr;
  				mono_error_assert_ok (error);
  			}
  			return jit_mm->bzero_addr [size];
  		}
  	}
  	case MONO_RGCTX_INFO_NULLABLE_CLASS_BOX:
  	case MONO_RGCTX_INFO_NULLABLE_CLASS_UNBOX: {
  		MonoMethod *method;
  		gpointer addr, arg;
  		MonoJitInfo *ji;
  		MonoMethodSignature *sig, *gsig;
  		MonoMethod *gmethod;
  
  		if (!mono_class_is_nullable (klass))
  			/* This can happen since all the entries in MonoGSharedVtMethodInfo are inflated, even those which are not used */
  			return NULL;
  
  		if (info_type == MONO_RGCTX_INFO_NULLABLE_CLASS_BOX)
  			method = mono_class_get_method_from_name_checked (klass, "Box", 1, 0, error);
  		else
  			method = mono_class_get_method_from_name_checked (klass, "Unbox", 1, 0, error);
  
  		return_val_if_nok (error, NULL);
  
  		addr = mono_jit_compile_method (method, error);
  		return_val_if_nok (error, NULL);
  
  		// The caller uses the gsharedvt call signature
  
  		if (mono_llvm_only) {
  			/* FIXME: We have no access to the gsharedvt signature/gsctx used by the caller, so have to construct it ourselves */
  			gmethod = mini_get_shared_method_full (method, SHARE_MODE_GSHAREDVT, error);
  			if (!gmethod)
  				return NULL;
  			sig = mono_method_signature_internal (method);
  			gsig = mono_method_signature_internal (gmethod);
  
  			addr = mini_llvmonly_add_method_wrappers (method, addr, TRUE, FALSE, &arg);
  			return mini_llvmonly_create_ftndesc (method, addr, arg);
  		}
  
  		ji = mini_jit_info_table_find (mono_get_addr_from_ftnptr (addr));
  		g_assert (ji);
  		if (mini_jit_info_is_gsharedvt (ji))
  			return mono_create_static_rgctx_trampoline (method, addr);
  		else {
  			/* Need to add an out wrapper */
  
  			/* FIXME: We have no access to the gsharedvt signature/gsctx used by the caller, so have to construct it ourselves */
  			gmethod = mini_get_shared_method_full (method, SHARE_MODE_GSHAREDVT, error);
  			if (!gmethod)
  				return NULL;
  			sig = mono_method_signature_internal (method);
  			gsig = mono_method_signature_internal (gmethod);
  
  			addr = mini_get_gsharedvt_wrapper (FALSE, addr, sig, gsig, -1, FALSE);
  			addr = mono_create_static_rgctx_trampoline (method, addr);
  			return addr;
  		}
  	}
  	default:
  		g_assert_not_reached ();
  	}
  	/* Not reached */
  	return NULL;
  }
  
  static gboolean
  ji_is_gsharedvt (MonoJitInfo *ji)
  {
  	if (ji && ji->has_generic_jit_info && (mono_jit_info_get_generic_sharing_context (ji)->is_gsharedvt))
  		return TRUE;
  	else
  		return FALSE;
  }
  
  /*
   * Describes the information used to construct a gsharedvt arg trampoline.
   */
  typedef struct {
  	gboolean is_in;
  	gboolean calli;
  	gint32 vcall_offset;
  	gpointer addr;
  	MonoMethodSignature *sig, *gsig;
  } GSharedVtTrampInfo;
  
  static guint
  tramp_info_hash (gconstpointer key)
  {
  	GSharedVtTrampInfo *tramp = (GSharedVtTrampInfo *)key;
  
  	return (gsize)tramp->addr;
  }
  
  static gboolean
  tramp_info_equal (gconstpointer a, gconstpointer b)
  {
  	GSharedVtTrampInfo *tramp1 = (GSharedVtTrampInfo *)a;
  	GSharedVtTrampInfo *tramp2 = (GSharedVtTrampInfo *)b;
  
  	/* The signatures should be internalized */
  	return tramp1->is_in == tramp2->is_in && tramp1->calli == tramp2->calli && tramp1->vcall_offset == tramp2->vcall_offset &&
  		tramp1->addr == tramp2->addr && tramp1->sig == tramp2->sig && tramp1->gsig == tramp2->gsig;
  }
  
  static GENERATE_GET_CLASS_WITH_CACHE (valuetuple_0, "Mono", "ValueTuple");
  static GENERATE_GET_CLASS_WITH_CACHE (valuetuple_1, "Mono", "ValueTuple`1");
  static GENERATE_GET_CLASS_WITH_CACHE (valuetuple_2, "Mono", "ValueTuple`2");
  static GENERATE_GET_CLASS_WITH_CACHE (valuetuple_3, "Mono", "ValueTuple`3");
  static GENERATE_GET_CLASS_WITH_CACHE (valuetuple_4, "Mono", "ValueTuple`4");
  static GENERATE_GET_CLASS_WITH_CACHE (valuetuple_5, "Mono", "ValueTuple`5");
  
  static MonoType*
  get_wrapper_shared_type (MonoType *t);
  static MonoType*
  get_wrapper_shared_type_full (MonoType *t, gboolean field);
  
  /*
   * get_wrapper_shared_vtype:
   *
   *   Return an instantiation of one of the Mono.ValueTuple types with the same
   * layout as the valuetype KLASS.
   */
  static MonoType*
  get_wrapper_shared_vtype (MonoType *t)
  {
  	ERROR_DECL (error);
  	MonoGenericContext ctx;
  	MonoType *args [16];
  	MonoClass *klass;
  	MonoClass *tuple_class = NULL;
  	int findex = 0;
  
  	// FIXME: Map 1 member structs to primitive types on platforms where its supported
  
  	klass = mono_class_from_mono_type_internal (t);
  	/* Under mono, auto and sequential layout are the same for valuetypes, see mono_class_layout_fields () */
  	if (((mono_class_get_flags (klass) & TYPE_ATTRIBUTE_LAYOUT_MASK) != TYPE_ATTRIBUTE_SEQUENTIAL_LAYOUT) &&
  		((mono_class_get_flags (klass) & TYPE_ATTRIBUTE_LAYOUT_MASK) != TYPE_ATTRIBUTE_AUTO_LAYOUT))
  		return NULL;
  	mono_class_setup_fields (klass);
  	if (mono_class_has_failure (klass))
  		return NULL;
  
  	if (m_class_get_type_token (klass) && mono_metadata_packing_from_typedef (m_class_get_image (klass), m_class_get_type_token (klass), NULL, NULL))
  		return NULL;
  
  	int num_fields = mono_class_get_field_count (klass);
  	MonoClassField *klass_fields = m_class_get_fields (klass);
  
  	for (int i = 0; i < num_fields; ++i) {
  		MonoClassField *field = &klass_fields [i];
  
  		if (field->type->attrs & (FIELD_ATTRIBUTE_STATIC | FIELD_ATTRIBUTE_HAS_FIELD_RVA))
  			continue;
  		MonoType *ftype = get_wrapper_shared_type_full (field->type, TRUE);
  		if (m_class_is_byreflike (mono_class_from_mono_type_internal (ftype)))
  			/* Cannot inflate generic params with byreflike types */
  			return NULL;
  		args [findex ++] = ftype;
  		if (findex >= 16)
  			break;
  	}
  
  #ifdef TARGET_WASM
  	guint32 align;
  	int size = mono_class_value_size (klass, &align);
  
  	/* Other platforms might pass small valuestypes or valuetypes with non-int fields differently */
  	if (align == 4 && size <= 4 * 5) {
  		findex = size / align;
  		for (int i = 0; i < findex; ++i)
  			args [i] = m_class_get_byval_arg (mono_get_int32_class ());
  	} else if (align == 8 && size <= 8 * 5) {
  		findex = size / align;
  		for (int i = 0; i < findex; ++i)
  			args [i] = m_class_get_byval_arg (mono_get_int64_class ());
  	} else {
  		if (findex > 5)
  			return NULL;
  	}
  #else
  	if (findex > 5)
  		return NULL;
  #endif
  
  	switch (findex) {
  	case 0:
  		tuple_class = mono_class_get_valuetuple_0_class ();
  		break;
  	case 1:
  		tuple_class = mono_class_get_valuetuple_1_class ();
  		break;
  	case 2:
  		tuple_class = mono_class_get_valuetuple_2_class ();
  		break;
  	case 3:
  		tuple_class = mono_class_get_valuetuple_3_class ();
  		break;
  	case 4:
  		tuple_class = mono_class_get_valuetuple_4_class ();
  		break;
  	case 5:
  		tuple_class = mono_class_get_valuetuple_5_class ();
  		break;
  	default:
  		g_assert_not_reached ();
  		break;
  	}
  	g_assert (tuple_class);
  
  	memset (&ctx, 0, sizeof (ctx));
  	ctx.class_inst = mono_metadata_get_generic_inst (findex, args);
  
  	MonoClass *tuple_inst = mono_class_inflate_generic_class_checked (tuple_class, &ctx, error);
  	mono_error_assert_ok (error);
  
  	//printf ("T: %s\n", mono_class_full_name (tuple_inst));
  
  	return m_class_get_byval_arg (tuple_inst);
  }
  
  /*
   * get_wrapper_shared_type:
   *
   *   Return a type which is handled identically wrt to calling conventions as T.
   */
  static MonoType*
  get_wrapper_shared_type_full (MonoType *t, gboolean is_field)
  {
  	if (m_type_is_byref (t))
  		return mono_class_get_byref_type (mono_defaults.int_class);
  	t = mini_get_underlying_type (t);
  
  	switch (t->type) {
  	case MONO_TYPE_I1:
  		/* This removes any attributes etc. */
  		return m_class_get_byval_arg (mono_defaults.sbyte_class);
  	case MONO_TYPE_U1:
  		return m_class_get_byval_arg (mono_defaults.byte_class);
  	case MONO_TYPE_I2:
  		return m_class_get_byval_arg (mono_defaults.int16_class);
  	case MONO_TYPE_U2:
  		return m_class_get_byval_arg (mono_defaults.uint16_class);
  	case MONO_TYPE_I4:
  		return mono_get_int32_type ();
  	case MONO_TYPE_U4:
  		return m_class_get_byval_arg (mono_defaults.uint32_class);
  	case MONO_TYPE_I8:
  #if TARGET_SIZEOF_VOID_P == 8
  		/* Use native int as its already used for byref */
  		return m_class_get_byval_arg (mono_defaults.int_class);
  #else
  		return m_class_get_byval_arg (mono_defaults.int64_class);
  #endif
  	case MONO_TYPE_U8:
  		return m_class_get_byval_arg (mono_defaults.uint64_class);
  	case MONO_TYPE_I:
  #if TARGET_SIZEOF_VOID_P == 8
  		return m_class_get_byval_arg (mono_defaults.int_class);
  #else
  		return m_class_get_byval_arg (mono_defaults.int32_class);
  #endif
  	case MONO_TYPE_U:
  #if TARGET_SIZEOF_VOID_P == 8
  		return m_class_get_byval_arg (mono_defaults.uint64_class);
  #else
  		return m_class_get_byval_arg (mono_defaults.uint32_class);
  #endif
  	case MONO_TYPE_R4:
  		return m_class_get_byval_arg (mono_defaults.single_class);
  	case MONO_TYPE_R8:
  		return m_class_get_byval_arg (mono_defaults.double_class);
  	case MONO_TYPE_OBJECT:
  	case MONO_TYPE_CLASS:
  	case MONO_TYPE_SZARRAY:
  	case MONO_TYPE_ARRAY:
  	case MONO_TYPE_PTR:
  		// FIXME: refs and intptr cannot be shared because
  		// they are treated differently when a method has a vret arg,
  		// see get_call_info ().
  		return mono_get_object_type ();
  		//return mono_get_int_type ();
  	case MONO_TYPE_GENERICINST: {
  		ERROR_DECL (error);
  		MonoClass *klass;
  		MonoGenericContext ctx;
  		MonoGenericContext *orig_ctx;
  		MonoGenericInst *inst;
  		MonoType *args [16];
  		int i;
  
  		if (!MONO_TYPE_ISSTRUCT (t))
  			return get_wrapper_shared_type (mono_get_object_type ());
  
  		klass = mono_class_from_mono_type_internal (t);
  		orig_ctx = &mono_class_get_generic_class (klass)->context;
  
  		memset (&ctx, 0, sizeof (MonoGenericContext));
  
  		inst = orig_ctx->class_inst;
  		if (inst) {
  			g_assert (inst->type_argc < 16);
  			for (i = 0; i < inst->type_argc; ++i)
  				args [i] = get_wrapper_shared_type_full (inst->type_argv [i], TRUE);
  			ctx.class_inst = mono_metadata_get_generic_inst (inst->type_argc, args);
  		}
  		inst = orig_ctx->method_inst;
  		if (inst) {
  			g_assert (inst->type_argc < 16);
  			for (i = 0; i < inst->type_argc; ++i)
  				args [i] = get_wrapper_shared_type_full (inst->type_argv [i], TRUE);
  			ctx.method_inst = mono_metadata_get_generic_inst (inst->type_argc, args);
  		}
  		klass = mono_class_inflate_generic_class_checked (mono_class_get_generic_class (klass)->container_class, &ctx, error);
  		mono_error_assert_ok (error); /* FIXME don't swallow the error */
  
  		t = m_class_get_byval_arg (klass);
  		MonoType *shared_type = get_wrapper_shared_vtype (t);
  		if (shared_type)
  			t = shared_type;
  		return t;
  	}
  	case MONO_TYPE_VALUETYPE: {
  		MonoType *shared_type = get_wrapper_shared_vtype (t);
  		if (shared_type)
  			t = shared_type;
  		return t;
  	}
  	default:
  		break;
  	}
  
  	//printf ("%s\n", mono_type_full_name (t));
  	return t;
  }
  
  static MonoType*
  get_wrapper_shared_type (MonoType *t)
  {
  	return get_wrapper_shared_type_full (t, FALSE);
  }
  
  
  /* Returns the intptr type for types that are passed in a single register */
  static MonoType*
  get_wrapper_shared_type_reg (MonoType *t, gboolean pinvoke)
  {
  	MonoType *orig_t = t;
  
  	t = get_wrapper_shared_type (t);
  	if (m_type_is_byref (t))
  		return t;
  
  	switch (t->type) {
  	case MONO_TYPE_BOOLEAN:
  	case MONO_TYPE_CHAR:
  	case MONO_TYPE_I1:
  	case MONO_TYPE_U1:
  	case MONO_TYPE_I2:
  	case MONO_TYPE_U2:
  	case MONO_TYPE_I4:
  	case MONO_TYPE_U4:
  	case MONO_TYPE_I:
  	case MONO_TYPE_U:
  #if TARGET_SIZEOF_VOID_P == 8
  	case MONO_TYPE_I8:
  	case MONO_TYPE_U8:
  		return mono_get_int_type ();
  #endif
  	case MONO_TYPE_OBJECT:
  	case MONO_TYPE_STRING:
  	case MONO_TYPE_CLASS:
  	case MONO_TYPE_SZARRAY:
  	case MONO_TYPE_ARRAY:
  	case MONO_TYPE_PTR:
  		return mono_get_int_type ();
  	case MONO_TYPE_GENERICINST:
  		if (orig_t->type == MONO_TYPE_VALUETYPE && pinvoke)
  			/*
  			 * These are translated to instances of Mono.ValueTuple, but generic types
  			 * cannot be passed in pinvoke.
  			 */
  			return orig_t;
  		else
  			return t;
  	default:
  		return t;
  	}
  }
  
  static MonoMethodSignature*
  mini_get_underlying_reg_signature (MonoMethodSignature *sig)
  {
  	MonoMethodSignature *res = mono_metadata_signature_dup (sig);
  	int i;
  
  	res->ret = get_wrapper_shared_type_reg (sig->ret, sig->pinvoke);
  	for (i = 0; i < sig->param_count; ++i)
  		res->params [i] = get_wrapper_shared_type_reg (sig->params [i], sig->pinvoke);
  	res->generic_param_count = 0;
  	res->is_inflated = 0;
  
  	return res;
  }
  
  static MonoMethodSignature*
  mini_get_underlying_signature (MonoMethodSignature *sig)
  {
  	MonoMethodSignature *res = mono_metadata_signature_dup (sig);
  	int i;
  
  	res->ret = get_wrapper_shared_type (sig->ret);
  	for (i = 0; i < sig->param_count; ++i)
  		res->params [i] = get_wrapper_shared_type (sig->params [i]);
  	res->generic_param_count = 0;
  	res->is_inflated = 0;
  
  	return res;
  }
  
  /*
   * mini_get_gsharedvt_in_sig_wrapper:
   *
   *   Return a wrapper to translate between the normal and gsharedvt calling conventions of SIG.
   * The returned wrapper has a signature of SIG, plus one extra argument, which is an <addr, rgctx> pair.
   * The extra argument is passed the same way as an rgctx to shared methods.
   * It calls <addr> using the gsharedvt version of SIG, passing in <rgctx> as an extra argument.
   */
  MonoMethod*
  mini_get_gsharedvt_in_sig_wrapper (MonoMethodSignature *sig)
  {
  	MonoMethodBuilder *mb;
  	MonoMethod *res, *cached;
  	WrapperInfo *info;
  	MonoMethodSignature *csig, *gsharedvt_sig;
  	int i, pindex;
  	static GHashTable *cache;
  
  	// FIXME: Memory management
  	sig = mini_get_underlying_signature (sig);
  
  	// FIXME: Normal cache
  	gshared_lock ();
  	if (!cache)
  		cache = g_hash_table_new_full ((GHashFunc)mono_signature_hash, (GEqualFunc)mono_metadata_signature_equal, NULL, NULL);
  	res = (MonoMethod*)g_hash_table_lookup (cache, sig);
  	gshared_unlock ();
  	if (res) {
  		g_free (sig);
  		return res;
  	}
  
  	/* Create the signature for the wrapper */
  	// FIXME:
  	csig = g_malloc0 (MONO_SIZEOF_METHOD_SIGNATURE + ((sig->param_count + 1) * sizeof (MonoType*)));
  	memcpy (csig, sig, mono_metadata_signature_size (sig));
  	csig->param_count ++;
  	csig->params [sig->param_count] = mono_get_int_type ();
  #ifdef ENABLE_ILGEN
  	char ** const param_names = g_new0 (char*, csig->param_count);
  	for (int i = 0; i < sig->param_count; ++i)
  		param_names [i] = g_strdup_printf ("%d", i);
  	param_names [sig->param_count] = g_strdup ("ftndesc");
  #endif
  
  	/* Create the signature for the gsharedvt callconv */
  	gsharedvt_sig = g_malloc0 (MONO_SIZEOF_METHOD_SIGNATURE + ((sig->param_count + 2) * sizeof (MonoType*)));
  	memcpy (gsharedvt_sig, sig, mono_metadata_signature_size (sig));
  	pindex = 0;
  	/* The return value is returned using an explicit vret argument */
  	if (sig->ret->type != MONO_TYPE_VOID) {
  		gsharedvt_sig->params [pindex ++] = mono_get_int_type ();
  		gsharedvt_sig->ret = mono_get_void_type ();
  	}
  	for (i = 0; i < sig->param_count; i++) {
  		gsharedvt_sig->params [pindex] = sig->params [i];
  		if (!m_type_is_byref (sig->params [i])) {
  			gsharedvt_sig->params [pindex] = mono_metadata_type_dup (NULL, gsharedvt_sig->params [pindex]);
  			gsharedvt_sig->params [pindex]->byref__ = 1;
  		}
  		pindex ++;
  	}
  	/* Rgctx arg */
  	gsharedvt_sig->params [pindex ++] = mono_get_int_type ();
  	gsharedvt_sig->param_count = pindex;
  
  	// FIXME: Use shared signatures
  	mb = mono_mb_new (mono_defaults.object_class, sig->hasthis ? "gsharedvt_in_sig" : "gsharedvt_in_sig_static", MONO_WRAPPER_OTHER);
  #ifdef ENABLE_ILGEN
  	mono_mb_set_param_names (mb, (const char**)param_names);
  #endif
  
  #ifndef DISABLE_JIT
  	int retval_var = 0;
  	if (sig->ret->type != MONO_TYPE_VOID)
  		retval_var = mono_mb_add_local (mb, sig->ret);
  
  	/* Make the call */
  	if (sig->hasthis)
  		mono_mb_emit_ldarg (mb, 0);
  	if (sig->ret->type != MONO_TYPE_VOID)
  		mono_mb_emit_ldloc_addr (mb, retval_var);
  	for (i = 0; i < sig->param_count; i++) {
  		if (m_type_is_byref (sig->params [i]))
  			mono_mb_emit_ldarg (mb, i + (sig->hasthis == TRUE));
  		else
  			mono_mb_emit_ldarg_addr (mb, i + (sig->hasthis == TRUE));
  	}
  	/* Rgctx arg */
  	mono_mb_emit_ldarg (mb, sig->param_count + (sig->hasthis ? 1 : 0));
  	mono_mb_emit_icon (mb, TARGET_SIZEOF_VOID_P);
  	mono_mb_emit_byte (mb, CEE_ADD);
  	mono_mb_emit_byte (mb, CEE_LDIND_I);
  	/* Method to call */
  	mono_mb_emit_ldarg (mb, sig->param_count + (sig->hasthis ? 1 : 0));
  	mono_mb_emit_byte (mb, CEE_LDIND_I);
  	mono_mb_emit_calli (mb, gsharedvt_sig);
  	if (sig->ret->type != MONO_TYPE_VOID)
  		mono_mb_emit_ldloc (mb, retval_var);
  	mono_mb_emit_byte (mb, CEE_RET);
  #endif
  
  	info = mono_wrapper_info_create (mb, WRAPPER_SUBTYPE_GSHAREDVT_IN_SIG);
  	info->d.gsharedvt.sig = sig;
  
  	res = mono_mb_create (mb, csig, sig->param_count + 16, info);
  #ifdef ENABLE_ILGEN
  	for (int i = 0; i < sig->param_count + 1; ++i)
  		g_free (param_names [i]);
  	g_free (param_names);
  #endif
  
  	gshared_lock ();
  	cached = (MonoMethod*)g_hash_table_lookup (cache, sig);
  	if (cached)
  		res = cached;
  	else
  		g_hash_table_insert (cache, sig, res);
  	gshared_unlock ();
  	return res;
  }
  
  /*
   * mini_get_gsharedvt_out_sig_wrapper:
   *
   *   Same as in_sig_wrapper, but translate between the gsharedvt and normal signatures.
   */
  MonoMethod*
  mini_get_gsharedvt_out_sig_wrapper (MonoMethodSignature *sig)
  {
  	MonoMethodBuilder *mb;
  	MonoMethod *res, *cached;
  	WrapperInfo *info;
  	MonoMethodSignature *normal_sig, *csig;
  	int i, pindex, args_start;
  	static GHashTable *cache;
  
  	// FIXME: Memory management
  	sig = mini_get_underlying_signature (sig);
  
  	// FIXME: Normal cache
  	gshared_lock ();
  	if (!cache)
  		cache = g_hash_table_new_full ((GHashFunc)mono_signature_hash, (GEqualFunc)mono_metadata_signature_equal, NULL, NULL);
  	res = (MonoMethod*)g_hash_table_lookup (cache, sig);
  	gshared_unlock ();
  	if (res) {
  		g_free (sig);
  		return res;
  	}
  
  	/* Create the signature for the wrapper */
  	// FIXME:
  	csig = g_malloc0 (MONO_SIZEOF_METHOD_SIGNATURE + ((sig->param_count + 2) * sizeof (MonoType*)));
  	memcpy (csig, sig, mono_metadata_signature_size (sig));
  	pindex = 0;
  	char ** const param_names = g_new0 (char*, sig->param_count + 2);
  	/* The return value is returned using an explicit vret argument */
  	if (sig->ret->type != MONO_TYPE_VOID) {
  		csig->params [pindex] = mono_get_int_type ();
  		csig->ret = mono_get_void_type ();
  		param_names [pindex] = g_strdup ("vret");
  		pindex ++;
  	}
  	args_start = pindex;
  	if (sig->hasthis)
  		args_start ++;
  	for (i = 0; i < sig->param_count; i++) {
  		csig->params [pindex] = sig->params [i];
  		param_names [pindex] = g_strdup_printf ("%d", i);
  		if (!m_type_is_byref (sig->params [i])) {
  			csig->params [pindex] = mono_metadata_type_dup (NULL, csig->params [pindex]);
  			csig->params [pindex]->byref__ = 1;
  		}
  		pindex ++;
  	}
  	/* Rgctx arg */
  	csig->params [pindex] = mono_get_int_type ();
  	param_names [pindex] = g_strdup ("ftndesc");
  	pindex  ++;
  	csig->param_count = pindex;
  
  	/* Create the signature for the normal callconv */
  	normal_sig = g_malloc0 (MONO_SIZEOF_METHOD_SIGNATURE + ((sig->param_count + 2) * sizeof (MonoType*)));
  	memcpy (normal_sig, sig, mono_metadata_signature_size (sig));
  	normal_sig->param_count ++;
  	normal_sig->params [sig->param_count] = mono_get_int_type ();
  
  	// FIXME: Use shared signatures
  	mb = mono_mb_new (mono_defaults.object_class, "gsharedvt_out_sig", MONO_WRAPPER_OTHER);
  #ifdef ENABLE_ILGEN
  	mono_mb_set_param_names (mb, (const char**)param_names);
  #endif
  
  #ifndef DISABLE_JIT
  	int ldind_op, stind_op;
  	if (sig->ret->type != MONO_TYPE_VOID)
  		/* Load return address */
  		mono_mb_emit_ldarg (mb, sig->hasthis ? 1 : 0);
  
  	/* Make the call */
  	if (sig->hasthis)
  		mono_mb_emit_ldarg (mb, 0);
  	for (i = 0; i < sig->param_count; i++) {
  		if (m_type_is_byref (sig->params [i])) {
  			mono_mb_emit_ldarg (mb, args_start + i);
  		} else {
  			ldind_op = mono_type_to_ldind (sig->params [i]);
  			mono_mb_emit_ldarg (mb, args_start + i);
  			// FIXME:
  			if (ldind_op == CEE_LDOBJ)
  				mono_mb_emit_op (mb, CEE_LDOBJ, mono_class_from_mono_type_internal (sig->params [i]));
  			else
  				mono_mb_emit_byte (mb, ldind_op);
  		}
  	}
  	/* Rgctx arg */
  	mono_mb_emit_ldarg (mb, args_start + sig->param_count);
  	mono_mb_emit_icon (mb, TARGET_SIZEOF_VOID_P);
  	mono_mb_emit_byte (mb, CEE_ADD);
  	mono_mb_emit_byte (mb, CEE_LDIND_I);
  	/* Method to call */
  	mono_mb_emit_ldarg (mb, args_start + sig->param_count);
  	mono_mb_emit_byte (mb, CEE_LDIND_I);
  	mono_mb_emit_calli (mb, normal_sig);
  	if (sig->ret->type != MONO_TYPE_VOID) {
  		/* Store return value */
  		stind_op = mono_type_to_stind (sig->ret);
  		// FIXME:
  		if (stind_op == CEE_STOBJ)
  			mono_mb_emit_op (mb, CEE_STOBJ, mono_class_from_mono_type_internal (sig->ret));
  		else if (stind_op == CEE_STIND_REF)
  			/* Avoid write barriers, the vret arg points to the stack */
  			mono_mb_emit_byte (mb, CEE_STIND_I);
  		else
  			mono_mb_emit_byte (mb, stind_op);
  	}
  	mono_mb_emit_byte (mb, CEE_RET);
  #endif
  
  	info = mono_wrapper_info_create (mb, WRAPPER_SUBTYPE_GSHAREDVT_OUT_SIG);
  	info->d.gsharedvt.sig = sig;
  
  	res = mono_mb_create (mb, csig, sig->param_count + 16, info);
  	for (int i = 0; i < sig->param_count + 1; ++i)
  		g_free (param_names [i]);
  	g_free (param_names);
  
  	gshared_lock ();
  	cached = (MonoMethod*)g_hash_table_lookup (cache, sig);
  	if (cached)
  		res = cached;
  	else
  		g_hash_table_insert (cache, sig, res);
  	gshared_unlock ();
  	return res;
  }
  
  static gboolean
  signature_equal_pinvoke (MonoMethodSignature *sig1, MonoMethodSignature *sig2)
  {
  	/* mono_metadata_signature_equal () doesn't do this check */
  	if (sig1->pinvoke != sig2->pinvoke)
  		return FALSE;
  	return mono_metadata_signature_equal (sig1, sig2);
  }
  
  /*
   * mini_get_interp_in_wrapper:
   *
   *   Return a wrapper which can be used to transition from compiled code to the interpreter.
   * The wrapper has the same signature as SIG. It is very similar to a gsharedvt_in wrapper,
   * except the 'extra_arg' is passed in the rgctx reg, so this wrapper needs to be
   * called through a static rgctx trampoline.
   * FIXME: Move this elsewhere.
   */
  MonoMethod*
  mini_get_interp_in_wrapper (MonoMethodSignature *sig)
  {
  	MonoMethodBuilder *mb;
  	MonoMethod *res, *cached;
  	WrapperInfo *info;
  	MonoMethodSignature *csig, *entry_sig;
  	int i, pindex;
  	static GHashTable *cache;
  	const char *name;
  	gboolean generic = FALSE;
  	gboolean return_native_struct;
  
  	sig = mini_get_underlying_reg_signature (sig);
  
  	gshared_lock ();
  	if (!cache)
  		cache = g_hash_table_new_full ((GHashFunc)mono_signature_hash, (GEqualFunc)signature_equal_pinvoke, NULL, NULL);
  	res = (MonoMethod*)g_hash_table_lookup (cache, sig);
  	gshared_unlock ();
  	if (res) {
  		g_free (sig);
  		return res;
  	}
  
  	if (sig->param_count > MAX_INTERP_ENTRY_ARGS)
  		/* Call the generic interpreter entry point, the specialized ones only handle a limited number of arguments */
  		generic = TRUE;
  
  	/*
  	 * If we need to return a native struct, we can't allocate a local and store it
  	 * there since that assumes a managed representation. Instead we allocate on the
  	 * stack, pass this address to the interp_entry and when we return it we use
  	 * CEE_MONO_LDNATIVEOBJ
  	 */
  	return_native_struct = sig->ret->type == MONO_TYPE_VALUETYPE && sig->pinvoke;
  
  	/* Create the signature for the wrapper */
  	csig = g_malloc0 (MONO_SIZEOF_METHOD_SIGNATURE + (sig->param_count * sizeof (MonoType*)));
  	memcpy (csig, sig, mono_metadata_signature_size (sig));
  
  	for (i = 0; i < sig->param_count; i++) {
  		if (m_type_is_byref (sig->params [i]))
  			csig->params [i] = mono_class_get_byref_type (mono_defaults.int_class);
  	}
  
  	MonoType *int_type = mono_get_int_type ();
  	/* Create the signature for the callee callconv */
  	if (generic) {
  		/*
  		 * The called function has the following signature:
  		 * interp_entry_general (gpointer this_arg, gpointer res, gpointer *args, gpointer rmethod)
  		 */
  		entry_sig = g_malloc0 (MONO_SIZEOF_METHOD_SIGNATURE + (4 * sizeof (MonoType*)));
  		entry_sig->ret = mono_get_void_type ();
  		entry_sig->param_count = 4;
  		entry_sig->params [0] = int_type;
  		entry_sig->params [1] = int_type;
  		entry_sig->params [2] = int_type;
  		entry_sig->params [3] = int_type;
  		name = "interp_in_generic";
  		generic = TRUE;
  	} else  {
  		/*
  		 * The called function has the following signature:
  		 * void entry(<optional this ptr>, <optional return ptr>, <arguments>, <extra arg>)
  		 */
  		entry_sig = g_malloc0 (MONO_SIZEOF_METHOD_SIGNATURE + ((sig->param_count + 2) * sizeof (MonoType*)));
  		memcpy (entry_sig, sig, mono_metadata_signature_size (sig));
  		pindex = 0;
  		/* The return value is returned using an explicit vret argument */
  		if (sig->ret->type != MONO_TYPE_VOID) {
  			entry_sig->params [pindex ++] = int_type;
  			entry_sig->ret = mono_get_void_type ();
  		}
  		for (i = 0; i < sig->param_count; i++) {
  			entry_sig->params [pindex] = sig->params [i];
  			if (!m_type_is_byref (sig->params [i])) {
  				entry_sig->params [pindex] = mono_metadata_type_dup (NULL, entry_sig->params [pindex]);
  				entry_sig->params [pindex]->byref__ = 1;
  			}
  			pindex ++;
  		}
  		/* Extra arg */
  		entry_sig->params [pindex ++] = int_type;
  		entry_sig->param_count = pindex;
  		name = sig->hasthis ? "interp_in" : "interp_in_static";
  	}
  
  	mb = mono_mb_new (mono_defaults.object_class, name, MONO_WRAPPER_OTHER);
  
  	/*
  	 * This is needed to be able to unwind out of interpreted code to managed.
  	 * When we are called from native code we can't unwind and we might also not
  	 * be attached.
  	 */
  	if (!sig->pinvoke)
  		mb->method->save_lmf = 1;
  
  #ifndef DISABLE_JIT
  	int retval_var = 0;
  	if (return_native_struct) {
  		retval_var = mono_mb_add_local (mb, int_type);
  		mono_mb_emit_icon (mb, mono_class_native_size (sig->ret->data.klass, NULL));
  		mono_mb_emit_byte (mb, CEE_PREFIX1);
  		mono_mb_emit_byte (mb, CEE_LOCALLOC);
  		mono_mb_emit_stloc (mb, retval_var);
  	} else if (sig->ret->type != MONO_TYPE_VOID) {
  		retval_var = mono_mb_add_local (mb, sig->ret);
  	}
  
  	/* Make the call */
  	if (generic) {
  		/* Collect arguments */
  		int args_var = mono_mb_add_local (mb, int_type);
  
  		mono_mb_emit_icon (mb, TARGET_SIZEOF_VOID_P * sig->param_count);
  		mono_mb_emit_byte (mb, CEE_PREFIX1);
  		mono_mb_emit_byte (mb, CEE_LOCALLOC);
  		mono_mb_emit_stloc (mb, args_var);
  
  		for (i = 0; i < sig->param_count; i++) {
  			mono_mb_emit_ldloc (mb, args_var);
  			mono_mb_emit_icon (mb, TARGET_SIZEOF_VOID_P * i);
  			mono_mb_emit_byte (mb, CEE_ADD);
  			if (m_type_is_byref (sig->params [i]))
  				mono_mb_emit_ldarg (mb, i + (sig->hasthis == TRUE));
  			else
  				mono_mb_emit_ldarg_addr (mb, i + (sig->hasthis == TRUE));
  			mono_mb_emit_byte (mb, CEE_STIND_I);
  		}
  
  		if (sig->hasthis)
  			mono_mb_emit_ldarg (mb, 0);
  		else
  			mono_mb_emit_byte (mb, CEE_LDNULL);
  		if (return_native_struct)
  			mono_mb_emit_ldloc (mb, retval_var);
  		else if (sig->ret->type != MONO_TYPE_VOID)
  			mono_mb_emit_ldloc_addr (mb, retval_var);
  		else
  			mono_mb_emit_byte (mb, CEE_LDNULL);
  		mono_mb_emit_ldloc (mb, args_var);
  	} else {
  		if (sig->hasthis)
  			mono_mb_emit_ldarg (mb, 0);
  		if (return_native_struct)
  			mono_mb_emit_ldloc (mb, retval_var);
  		else if (sig->ret->type != MONO_TYPE_VOID)
  			mono_mb_emit_ldloc_addr (mb, retval_var);
  		for (i = 0; i < sig->param_count; i++) {
  			if (m_type_is_byref (sig->params [i]))
  				mono_mb_emit_ldarg (mb, i + (sig->hasthis == TRUE));
  			else
  				mono_mb_emit_ldarg_addr (mb, i + (sig->hasthis == TRUE));
  		}
  	}
  	/* Extra arg */
  	mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX);
  	mono_mb_emit_byte (mb, CEE_MONO_GET_RGCTX_ARG);
  	mono_mb_emit_icon (mb, TARGET_SIZEOF_VOID_P);
  	mono_mb_emit_byte (mb, CEE_ADD);
  	mono_mb_emit_byte (mb, CEE_LDIND_I);
  	/* Method to call */
  	mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX);
  	mono_mb_emit_byte (mb, CEE_MONO_GET_RGCTX_ARG);
  	mono_mb_emit_byte (mb, CEE_LDIND_I);
  	mono_mb_emit_calli (mb, entry_sig);
  
  	if (return_native_struct) {
  		mono_mb_emit_ldloc (mb, retval_var);
  		mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX);
  		mono_mb_emit_op (mb, CEE_MONO_LDNATIVEOBJ, sig->ret->data.klass);
  	} else if (sig->ret->type != MONO_TYPE_VOID) {
  		mono_mb_emit_ldloc (mb, retval_var);
  	}
  	mono_mb_emit_byte (mb, CEE_RET);
  #endif
  
  	info = mono_wrapper_info_create (mb, WRAPPER_SUBTYPE_INTERP_IN);
  	info->d.interp_in.sig = csig;
  
  	res = mono_mb_create (mb, csig, sig->param_count + 16, info);
  
  	gshared_lock ();
  	cached = (MonoMethod*)g_hash_table_lookup (cache, sig);
  	if (cached) {
  		mono_free_method (res);
  		res = cached;
  	} else {
  		g_hash_table_insert (cache, sig, res);
  	}
  	gshared_unlock ();
  	mono_mb_free (mb);
  
  	return res;
  }
  
  /*
   *   This wrapper enables EH to resume directly to the code calling it. It is
   * needed so EH can resume directly into jitted code from interp, or into interp
   * when it needs to jump over native frames.
   */
  MonoMethod*
  mini_get_interp_lmf_wrapper (const char *name, gpointer target)
  {
  	static MonoMethod *cache [2];
  	g_assert (target == (gpointer)mono_interp_to_native_trampoline || target == (gpointer)mono_interp_entry_from_trampoline);
  	const int index = target == (gpointer)mono_interp_to_native_trampoline;
  	const MonoJitICallId jit_icall_id = index ? MONO_JIT_ICALL_mono_interp_to_native_trampoline : MONO_JIT_ICALL_mono_interp_entry_from_trampoline;
  
  	MonoMethod *res, *cached;
  	MonoMethodSignature *sig;
  	MonoMethodBuilder *mb;
  	WrapperInfo *info;
  
  	gshared_lock ();
  
  	res = cache [index];
  
  	gshared_unlock ();
  
  	if (res)
  		return res;
  
  	MonoType *int_type = mono_get_int_type ();
  
  	char *wrapper_name = g_strdup_printf ("__interp_lmf_%s", name);
  	mb = mono_mb_new (mono_defaults.object_class, wrapper_name, MONO_WRAPPER_OTHER);
  
  	sig = mono_metadata_signature_alloc (mono_defaults.corlib, 2);
  	sig->ret = mono_get_void_type ();
  	sig->params [0] = int_type;
  	sig->params [1] = int_type;
  
  	/* This is the only thing that the wrapper needs to do */
  	mb->method->save_lmf = 1;
  
  #ifndef DISABLE_JIT
  	mono_mb_emit_byte (mb, CEE_LDARG_0);
  	mono_mb_emit_byte (mb, CEE_LDARG_1);
  
  	mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX);
  	mono_mb_emit_byte (mb, CEE_MONO_ICALL);
  	mono_mb_emit_i4 (mb, jit_icall_id);
  
  	mono_mb_emit_byte (mb, CEE_RET);
  #endif
  	info = mono_wrapper_info_create (mb, WRAPPER_SUBTYPE_INTERP_LMF);
  	info->d.icall.jit_icall_id = jit_icall_id;
  	res = mono_mb_create (mb, sig, 4, info);
  
  	gshared_lock ();
  	cached = cache [index];
  	if (cached) {
  		mono_free_method (res);
  		res = cached;
  	} else {
  		cache [index] = res;
  	}
  	gshared_unlock ();
  	mono_mb_free (mb);
  
  	g_free (wrapper_name);
  
  	return res;
  }
  
  MonoMethodSignature*
  mini_get_gsharedvt_out_sig_wrapper_signature (gboolean has_this, gboolean has_ret, int param_count)
  {
  	MonoMethodSignature *sig = g_malloc0 (sizeof (MonoMethodSignature) + ((param_count + 3) * sizeof (MonoType*)));
  	int i, pindex;
  	MonoType *int_type = mono_get_int_type ();
  
  	sig->ret = mono_get_void_type ();
  	sig->sentinelpos = -1;
  	pindex = 0;
  	if (has_this)
  		/* this */
  		sig->params [pindex ++] = int_type;
  	if (has_ret)
  		/* vret */
  		sig->params [pindex ++] = int_type;
  	for (i = 0; i < param_count; ++i)
  		/* byref arguments */
  		sig->params [pindex ++] = int_type;
  	/* extra arg */
  	sig->params [pindex ++] = int_type;
  	sig->param_count = pindex;
  
  	return sig;
  }
  
  /*
   * mini_get_gsharedvt_wrapper:
   *
   *   Return a gsharedvt in/out wrapper for calling ADDR.
   */
  gpointer
  mini_get_gsharedvt_wrapper (gboolean gsharedvt_in, gpointer addr, MonoMethodSignature *normal_sig, MonoMethodSignature *gsharedvt_sig, gint32 vcall_offset, gboolean calli)
  {
  	ERROR_DECL (error);
  	gpointer res, info;
  	GSharedVtTrampInfo *tramp_info;
  	GSharedVtTrampInfo tinfo;
  	MonoJitMemoryManager *jit_mm;
  
  	if (mono_llvm_only) {
  		MonoMethod *wrapper;
  
  		if (gsharedvt_in)
  			wrapper = mini_get_gsharedvt_in_sig_wrapper (normal_sig);
  		else
  			wrapper = mini_get_gsharedvt_out_sig_wrapper (normal_sig);
  		res = mono_compile_method_checked (wrapper, error);
  		mono_error_assert_ok (error);
  		return res;
  	}
  
  	memset (&tinfo, 0, sizeof (tinfo));
  	tinfo.is_in = gsharedvt_in;
  	tinfo.calli = calli;
  	tinfo.vcall_offset = vcall_offset;
  	tinfo.addr = addr;
  	tinfo.sig = normal_sig;
  	tinfo.gsig = gsharedvt_sig;
  
  	// FIXME:
  	jit_mm = get_default_jit_mm ();
  
  	/*
  	 * The arg trampolines might only have a finite number in full-aot, so use a cache.
  	 */
  	jit_mm_lock (jit_mm);
  	if (!jit_mm->gsharedvt_arg_tramp_hash)
  		jit_mm->gsharedvt_arg_tramp_hash = g_hash_table_new (tramp_info_hash, tramp_info_equal);
  	res = g_hash_table_lookup (jit_mm->gsharedvt_arg_tramp_hash, &tinfo);
  	jit_mm_unlock (jit_mm);
  	if (res)
  		return res;
  
  	info = mono_arch_get_gsharedvt_call_info (jit_mm->mem_manager, addr, normal_sig, gsharedvt_sig, gsharedvt_in, vcall_offset, calli);
  
  	if (gsharedvt_in) {
  		static gpointer tramp_addr;
  		MonoMethod *wrapper;
  
  		if (!tramp_addr) {
  			wrapper = mono_marshal_get_gsharedvt_in_wrapper ();
  			addr = mono_compile_method_checked (wrapper, error);
  			mono_memory_barrier ();
  			mono_error_assert_ok (error);
  			tramp_addr = addr;
  		}
  		addr = tramp_addr;
  	} else {
  		static gpointer tramp_addr;
  		MonoMethod *wrapper;
  
  		if (!tramp_addr) {
  			wrapper = mono_marshal_get_gsharedvt_out_wrapper ();
  			addr = mono_compile_method_checked (wrapper, error);
  			mono_memory_barrier ();
  			mono_error_assert_ok (error);
  			tramp_addr = addr;
  		}
  		addr = tramp_addr;
  	}
  
  	if (mono_aot_only)
  		addr = mono_aot_get_gsharedvt_arg_trampoline (info, addr);
  	else
  		addr = mono_arch_get_gsharedvt_arg_trampoline (info, addr);
  
  	mono_atomic_inc_i32 (&gsharedvt_num_trampolines);
  
  	/* Cache it */
  	tramp_info = (GSharedVtTrampInfo *)mono_mem_manager_alloc0 (jit_mm->mem_manager, sizeof (GSharedVtTrampInfo));
  	*tramp_info = tinfo;
  
  	jit_mm_lock (jit_mm);
  	/* Duplicates are not a problem */
  	g_hash_table_insert (jit_mm->gsharedvt_arg_tramp_hash, tramp_info, addr);
  	jit_mm_unlock (jit_mm);
  
  	return addr;
  }
  
  /*
   * instantiate_info:
   *
   *   Instantiate the info given by OTI for context CONTEXT.
   */
  static gpointer
  instantiate_info (MonoMemoryManager *mem_manager, MonoRuntimeGenericContextInfoTemplate *oti,
  				  MonoGenericContext *context, MonoClass *klass, MonoError *error)
  {
  	gpointer data;
  	gboolean temporary;
  
  	error_init (error);
  
  	if (!oti->data)
  		return NULL;
  
  	switch (oti->info_type) {
  	case MONO_RGCTX_INFO_STATIC_DATA:
  	case MONO_RGCTX_INFO_KLASS:
  	case MONO_RGCTX_INFO_ELEMENT_KLASS:
  	case MONO_RGCTX_INFO_VTABLE:
  	case MONO_RGCTX_INFO_CAST_CACHE:
  		temporary = TRUE;
  		break;
  	default:
  		temporary = FALSE;
  	}
  
  	data = inflate_info (mem_manager, oti, context, klass, temporary);
  
  	switch (oti->info_type) {
  	case MONO_RGCTX_INFO_STATIC_DATA:
  	case MONO_RGCTX_INFO_KLASS:
  	case MONO_RGCTX_INFO_ELEMENT_KLASS:
  	case MONO_RGCTX_INFO_VTABLE:
  	case MONO_RGCTX_INFO_CAST_CACHE:
  	case MONO_RGCTX_INFO_ARRAY_ELEMENT_SIZE:
  	case MONO_RGCTX_INFO_VALUE_SIZE:
  	case MONO_RGCTX_INFO_CLASS_SIZEOF:
  	case MONO_RGCTX_INFO_CLASS_BOX_TYPE:
  	case MONO_RGCTX_INFO_CLASS_IS_REF_OR_CONTAINS_REFS:
  	case MONO_RGCTX_INFO_MEMCPY:
  	case MONO_RGCTX_INFO_BZERO:
  	case MONO_RGCTX_INFO_NULLABLE_CLASS_BOX:
  	case MONO_RGCTX_INFO_NULLABLE_CLASS_UNBOX: {
  		MonoClass *arg_class = mono_class_from_mono_type_internal ((MonoType *)data);
  
  		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 (mem_manager, arg_class, oti->info_type, error);
  	}
  	case MONO_RGCTX_INFO_TYPE:
  		return data;
  	case MONO_RGCTX_INFO_REFLECTION_TYPE: {
  		MonoReflectionType *ret = mono_type_get_object_checked ((MonoType *)data, error);
  
  		return ret;
  	}
  	case MONO_RGCTX_INFO_METHOD:
  		return data;
  	case MONO_RGCTX_INFO_GENERIC_METHOD_CODE: {
  		MonoMethod *m = (MonoMethod*)data;
  		gpointer addr;
  
  		g_assert (!mono_llvm_only);
  		addr = mono_compile_method_checked (m, error);
  		return_val_if_nok (error, NULL);
  		return mini_add_method_trampoline (m, addr, mono_method_needs_static_rgctx_invoke (m, FALSE), FALSE);
  	}
  	case MONO_RGCTX_INFO_METHOD_FTNDESC: {
  		MonoMethod *m = (MonoMethod*)data;
  
  		/* Returns an ftndesc */
  		g_assert (mono_llvm_only);
  		MonoJumpInfo ji;
  		ji.type = MONO_PATCH_INFO_METHOD_FTNDESC;
  		ji.data.method = m;
  		return mono_resolve_patch_target (m, NULL, &ji, FALSE, error);
  	}
  	case MONO_RGCTX_INFO_GSHAREDVT_OUT_WRAPPER: {
  		MonoMethod *m = (MonoMethod*)data;
  		gpointer addr;
  		gpointer arg = NULL;
  
  		g_assert (mono_llvm_only);
  
  		addr = mono_compile_method_checked (m, error);
  		return_val_if_nok (error, NULL);
  
  		MonoJitInfo *ji;
  		gboolean callee_gsharedvt;
  
  		ji = mini_jit_info_table_find (mono_get_addr_from_ftnptr (addr));
  		g_assert (ji);
  		callee_gsharedvt = mini_jit_info_is_gsharedvt (ji);
  		if (callee_gsharedvt)
  			callee_gsharedvt = mini_is_gsharedvt_variable_signature (mono_method_signature_internal (jinfo_get_method (ji)));
  		if (callee_gsharedvt) {
  			/* No need for a wrapper */
  			return mini_llvmonly_create_ftndesc (m, addr, mini_method_get_rgctx (m));
  		} else {
  			addr = mini_llvmonly_add_method_wrappers (m, addr, TRUE, FALSE, &arg);
  
  			/* Returns an ftndesc */
  			return mini_llvmonly_create_ftndesc (m, addr, arg);
  		}
  	}
  	case MONO_RGCTX_INFO_INTERP_METHOD: {
  		MonoMethod *m = (MonoMethod*)data;
  
  		return mini_get_interp_callbacks ()->get_interp_method (m, error);
  	}
  	case MONO_RGCTX_INFO_LLVMONLY_INTERP_ENTRY: {
  		MonoMethod *m = (MonoMethod*)data;
  
  		return mini_get_interp_callbacks ()->create_method_pointer_llvmonly (m, FALSE, error);
  	}
  	case MONO_RGCTX_INFO_VIRT_METHOD_CODE: {
  		MonoJumpInfoVirtMethod *info = (MonoJumpInfoVirtMethod *)data;
  		MonoClass *iface_class = info->method->klass;
  		MonoMethod *method;
  		int ioffset, slot;
  		gpointer addr;
  
  		mono_class_setup_vtable (info->klass);
  		// FIXME: Check type load
  		if (mono_class_is_interface (iface_class)) {
  			ioffset = mono_class_interface_offset (info->klass, iface_class);
  			g_assert (ioffset != -1);
  		} else {
  			ioffset = 0;
  		}
  		slot = mono_method_get_vtable_slot (info->method);
  		g_assert (slot != -1);
  		g_assert (m_class_get_vtable (info->klass));
  		method = m_class_get_vtable (info->klass) [ioffset + slot];
  
  		method = mono_class_inflate_generic_method_checked (method, context, error);
  		return_val_if_nok (error, NULL);
  
  		if (mono_llvm_only) {
   			gpointer arg = NULL;
  			addr = mini_llvmonly_load_method (method, FALSE, FALSE, &arg, error);
  
  			/* Returns an ftndesc */
  			return mini_llvmonly_create_ftndesc (method, addr, arg);
  		} else {
  			addr = mono_compile_method_checked (method, error);
  			return_val_if_nok (error, NULL);
  
  			return mini_add_method_trampoline (method, addr, mono_method_needs_static_rgctx_invoke (method, FALSE), FALSE);
  		}
  	}
  	case MONO_RGCTX_INFO_VIRT_METHOD_BOX_TYPE: {
  		MonoJumpInfoVirtMethod *info = (MonoJumpInfoVirtMethod *)data;
  		MonoClass *iface_class = info->method->klass;
  		MonoMethod *method;
  		MonoClass *impl_class;
  		int ioffset, slot;
  
  		mono_class_setup_vtable (info->klass);
  		// FIXME: Check type load
  		if (mono_class_is_interface (iface_class)) {
  			ioffset = mono_class_interface_offset (info->klass, iface_class);
  			g_assert (ioffset != -1);
  		} else {
  			ioffset = 0;
  		}
  		slot = mono_method_get_vtable_slot (info->method);
  		g_assert (slot != -1);
  		g_assert (m_class_get_vtable (info->klass));
  		method = m_class_get_vtable (info->klass) [ioffset + slot];
  
  		impl_class = method->klass;
  		if (MONO_TYPE_IS_REFERENCE (m_class_get_byval_arg (impl_class)))
  			return GUINT_TO_POINTER (MONO_GSHAREDVT_BOX_TYPE_REF);
  		else if (mono_class_is_nullable (impl_class))
  			return GUINT_TO_POINTER (MONO_GSHAREDVT_BOX_TYPE_NULLABLE);
  		else
  			return GUINT_TO_POINTER (MONO_GSHAREDVT_BOX_TYPE_VTYPE);
  	}
  	case MONO_RGCTX_INFO_METHOD_DELEGATE_CODE:
  		return mono_mem_manager_alloc0 (mem_manager, sizeof (gpointer));
  	case MONO_RGCTX_INFO_CLASS_FIELD:
  		return data;
  	case MONO_RGCTX_INFO_FIELD_OFFSET: {
  		MonoClassField *field = (MonoClassField *)data;
  
  		if (mono_class_field_is_special_static (field)) {
  			gpointer addr;
  
  			mono_class_vtable_checked (m_field_get_parent (field), error);
  			mono_error_assert_ok (error);
  
  			/* Return the TLS offset */
  			addr = mono_special_static_field_get_offset (field, error);
  			g_assert (addr);
  			return (guint8*)addr + 1;
  		}
  
  		/* The value is offset by 1 */
  		if (m_class_is_valuetype (m_field_get_parent (field)) && !(field->type->attrs & FIELD_ATTRIBUTE_STATIC))
  			return GUINT_TO_POINTER (field->offset - MONO_ABI_SIZEOF (MonoObject) + 1);
  		else
  			return GUINT_TO_POINTER (field->offset + 1);
  	}
  	case MONO_RGCTX_INFO_METHOD_RGCTX: {
  		MonoMethodInflated *method = (MonoMethodInflated *)data;
  
  		g_assert (method->method.method.is_inflated);
  
  		return mini_method_get_rgctx ((MonoMethod*)method);
  	}
  	case MONO_RGCTX_INFO_METHOD_CONTEXT: {
  		MonoMethodInflated *method = (MonoMethodInflated *)data;
  
  		g_assert (method->method.method.is_inflated);
  		g_assert (method->context.method_inst);
  
  		return method->context.method_inst;
  	}
  	case MONO_RGCTX_INFO_SIG_GSHAREDVT_IN_TRAMPOLINE_CALLI: {
  		MonoMethodSignature *gsig = (MonoMethodSignature *)oti->data;
  		MonoMethodSignature *sig = (MonoMethodSignature *)data;
  		gpointer addr;
  
  		/*
  		 * This is an indirect call to the address passed by the caller in the rgctx reg.
  		 */
  		addr = mini_get_gsharedvt_wrapper (TRUE, NULL, sig, gsig, -1, TRUE);
  		return addr;
  	}
  	case MONO_RGCTX_INFO_SIG_GSHAREDVT_OUT_TRAMPOLINE_CALLI: {
  		MonoMethodSignature *gsig = (MonoMethodSignature *)oti->data;
  		MonoMethodSignature *sig = (MonoMethodSignature *)data;
  		gpointer addr;
  
  		/*
  		 * This is an indirect call to the address passed by the caller in the rgctx reg.
  		 */
  		addr = mini_get_gsharedvt_wrapper (FALSE, NULL, sig, gsig, -1, TRUE);
  		return addr;
  	}
  	case MONO_RGCTX_INFO_METHOD_GSHAREDVT_OUT_TRAMPOLINE:
  	case MONO_RGCTX_INFO_METHOD_GSHAREDVT_OUT_TRAMPOLINE_VIRT: {
  		MonoJumpInfoGSharedVtCall *call_info = (MonoJumpInfoGSharedVtCall *)data;
  		MonoMethodSignature *call_sig;
  		MonoMethod *method;
  		gpointer addr;
  		MonoJitInfo *callee_ji;
  		gboolean virtual_ = oti->info_type == MONO_RGCTX_INFO_METHOD_GSHAREDVT_OUT_TRAMPOLINE_VIRT;
  		gint32 vcall_offset;
  		gboolean callee_gsharedvt;
  
  		/* This is the original generic signature used by the caller */
  		call_sig = call_info->sig;
  		/* This is the instantiated method which is called */
  		method = call_info->method;
  
  		g_assert (method->is_inflated);
  
  		if (mono_llvm_only && (method->iflags & METHOD_IMPL_ATTRIBUTE_SYNCHRONIZED))
  			method = mono_marshal_get_synchronized_wrapper (method);
  
  		if (!virtual_) {
  			addr = mono_compile_method_checked (method, error);
  			return_val_if_nok (error, NULL);
  		} else
  			addr = NULL;
  
  		if (virtual_) {
  			/* Same as in mono_emit_method_call_full () */
  			if ((m_class_get_parent (method->klass) == mono_defaults.multicastdelegate_class) && (!strcmp (method->name, "Invoke"))) {
  				/* See mono_emit_method_call_full () */
  				/* The gsharedvt trampoline will recognize this constant */
  				vcall_offset = MONO_GSHAREDVT_DEL_INVOKE_VT_OFFSET;
  			} else if (mono_class_is_interface (method->klass)) {
  				guint32 imt_slot = mono_method_get_imt_slot (method);
  				vcall_offset = ((gint32)imt_slot - MONO_IMT_SIZE) * TARGET_SIZEOF_VOID_P;
  			} else {
  				vcall_offset = G_STRUCT_OFFSET (MonoVTable, vtable) +
  					((mono_method_get_vtable_index (method)) * (TARGET_SIZEOF_VOID_P));
  			}
  		} else {
  			vcall_offset = -1;
  		}
  
  		// FIXME: This loads information in the AOT case
  		callee_ji = mini_jit_info_table_find (mono_get_addr_from_ftnptr (addr));
  		callee_gsharedvt = ji_is_gsharedvt (callee_ji);
  
  		/*
  		 * For gsharedvt calls made out of gsharedvt methods, the callee could end up being a gsharedvt method, or a normal
  		 * non-shared method. The latter call cannot be patched, so instead of using a normal call, we make an indirect
  		 * call through the rgctx, in effect patching the rgctx entry instead of the call site.
  		 * For virtual calls, the caller might be a normal or a gsharedvt method. Since there is only one vtable slot,
  		 * this difference needs to be handed on the caller side. This is currently implemented by adding a gsharedvt-in
  		 * trampoline to all gsharedvt methods and storing this trampoline into the vtable slot. Virtual calls made from
  		 * gsharedvt methods always go through a gsharedvt-out trampoline, so the calling sequence is:
  		 * caller -> out trampoline -> in trampoline -> callee
  		 * This is not very efficient, but it is easy to implement.
  		 */
  		if (virtual_ || !callee_gsharedvt) {
  			MonoMethodSignature *sig, *gsig;
  
  			g_assert (method->is_inflated);
  
  			sig = mono_method_signature_internal (method);
  			gsig = call_sig;
  
  			if (mono_llvm_only) {
  				if (mini_is_gsharedvt_variable_signature (call_sig)) {
  					/* The virtual case doesn't go through this code */
  					g_assert (!virtual_);
  
  					sig = mono_method_signature_internal (jinfo_get_method (callee_ji));
  					gpointer out_wrapper = mini_get_gsharedvt_wrapper (FALSE, NULL, sig, gsig, -1, FALSE);
  					MonoFtnDesc *out_wrapper_arg = mini_llvmonly_create_ftndesc (method, jinfo_get_ftnptr (callee_ji), mini_method_get_rgctx (method));
  
  					/* Returns an ftndesc */
  					addr = mini_llvmonly_create_ftndesc (method, out_wrapper, out_wrapper_arg);
  				} else {
  					addr = mini_llvmonly_create_ftndesc (method, addr, mini_method_get_rgctx (method));
  				}
  			} else {
  				addr = mini_get_gsharedvt_wrapper (FALSE, addr, sig, gsig, vcall_offset, FALSE);
  			}
  #if 0
  			if (virtual)
  				printf ("OUT-VCALL: %s\n", mono_method_full_name (method, TRUE));
  			else
  				printf ("OUT: %s\n", mono_method_full_name (method, TRUE));
  #endif
  		} else if (callee_gsharedvt) {
  			MonoMethodSignature *sig, *gsig;
  
  			/*
  			 * This is a combination of the out and in cases, since both the caller and the callee are gsharedvt methods.
  			 * The caller and the callee can use different gsharedvt signatures, so we have to add both an out and an in
  			 * trampoline, i.e.:
  			 * class Base<T> {
  			 *   public void foo<T1> (T1 t1, T t, object o) {}
  			 * }
  			 * class AClass : Base<long> {
  			 * public void bar<T> (T t, long time, object o) {
  			 *   foo (t, time, o);
  			 * }
  			 * }
  			 * Here, the caller uses !!0,long, while the callee uses !!0,!0
  			 * FIXME: Optimize this.
  			 */
  
  			if (mono_llvm_only) {
  				/* Both wrappers receive an extra <addr, rgctx> argument */
  				sig = mono_method_signature_internal (method);
  				gsig = mono_method_signature_internal (jinfo_get_method (callee_ji));
  
  				/* Return a function descriptor */
  
  				if (mini_is_gsharedvt_variable_signature (call_sig)) {
  					/*
  					 * This is not an optimization, but its needed, since the concrete signature 'sig'
  					 * might not exist at all in IL, so the AOT compiler cannot generate the wrappers
  					 * for it.
  					 */
  					addr = mini_llvmonly_create_ftndesc (method, jinfo_get_ftnptr (callee_ji), mini_method_get_rgctx (method));
  				} else if (mini_is_gsharedvt_variable_signature (gsig)) {
  					gpointer in_wrapper = mini_get_gsharedvt_wrapper (TRUE, jinfo_get_ftnptr (callee_ji), sig, gsig, -1, FALSE);
  
  					gpointer in_wrapper_arg = mini_llvmonly_create_ftndesc (method, jinfo_get_ftnptr (callee_ji), mini_method_get_rgctx (method));
  
  					addr = mini_llvmonly_create_ftndesc (method, in_wrapper, in_wrapper_arg);
  				} else {
  					addr = mini_llvmonly_create_ftndesc (method, addr, mini_method_get_rgctx (method));
  				}
  			} else if (call_sig == mono_method_signature_internal (method)) {
  			} else {
  				sig = mono_method_signature_internal (method);
  				gsig = mono_method_signature_internal (jinfo_get_method (callee_ji));
  
  				addr = mini_get_gsharedvt_wrapper (TRUE, jinfo_get_ftnptr (callee_ji), sig, gsig, -1, FALSE);
  
  				sig = mono_method_signature_internal (method);
  				gsig = call_sig;
  
  				addr = mini_get_gsharedvt_wrapper (FALSE, addr, sig, gsig, -1, FALSE);
  
  				//printf ("OUT-IN-RGCTX: %s\n", mono_method_full_name (method, TRUE));
  			}
  		}
  
  		return addr;
  	}
  	case MONO_RGCTX_INFO_METHOD_GSHAREDVT_INFO: {
  		MonoGSharedVtMethodInfo *info = (MonoGSharedVtMethodInfo *)data;
  		MonoGSharedVtMethodRuntimeInfo *res;
  		MonoType *t;
  		int i, offset, align, size;
  
  		// FIXME:
  		res = (MonoGSharedVtMethodRuntimeInfo *)g_malloc0 (sizeof (MonoGSharedVtMethodRuntimeInfo) + (info->num_entries * sizeof (gpointer)));
  
  		offset = 0;
  		for (i = 0; i < info->num_entries; ++i) {
  			MonoRuntimeGenericContextInfoTemplate *template_ = &info->entries [i];
  
  			switch (template_->info_type) {
  			case MONO_RGCTX_INFO_LOCAL_OFFSET:
  				t = (MonoType *)template_->data;
  
  				size = mono_type_size (t, &align);
  
  				if (align < sizeof (gpointer))
  					align = sizeof (gpointer);
  				if (MONO_TYPE_ISSTRUCT (t) && align < 2 * sizeof (gpointer))
  					align = 2 * sizeof (gpointer);
  
  				// FIXME: Do the same things as alloc_stack_slots
  				offset += align - 1;
  				offset &= ~(align - 1);
  				res->entries [i] = GINT_TO_POINTER (offset);
  				offset += size;
  				break;
  			default:
  				res->entries [i] = instantiate_info (mem_manager, template_, context, klass, error);
  				if (!is_ok (error))
  					return NULL;
  				break;
  			}
  		}
  		res->locals_size = offset;
  
  		return res;
  	}
  	case MONO_RGCTX_INFO_DELEGATE_TRAMP_INFO: {
  		MonoDelegateClassMethodPair *dele_info = (MonoDelegateClassMethodPair*)data;
  		gpointer trampoline;
  
  		if (dele_info->is_virtual)
  			trampoline = mono_create_delegate_virtual_trampoline (dele_info->klass, dele_info->method);
  		else
  			trampoline = mono_create_delegate_trampoline_info (dele_info->klass, dele_info->method);
  
  		g_assert (trampoline);
  		return trampoline;
  	}
  	default:
  		g_assert_not_reached ();
  	}
  	/* Not reached */
  	return NULL;
  }
  
  /*
   * LOCKING: loader lock
   */
  static void
  fill_in_rgctx_template_slot (MonoClass *klass, int type_argc, int index, gpointer data, MonoRgctxInfoType info_type)
  {
  	MonoRuntimeGenericContextTemplate *template_ = mono_class_get_runtime_generic_context_template (klass);
  	MonoClass *subclass;
  
  	rgctx_template_set_slot (m_class_get_image (klass), template_, type_argc, index, data, info_type);
  
  	/* Recurse for all subclasses */
  	if (generic_subclass_hash)
  		subclass = (MonoClass *)g_hash_table_lookup (generic_subclass_hash, klass);
  	else
  		subclass = NULL;
  
  	while (subclass) {
  		MonoRuntimeGenericContextInfoTemplate subclass_oti;
  		MonoRuntimeGenericContextTemplate *subclass_template = class_lookup_rgctx_template (subclass);
  
  		g_assert (subclass_template);
  
  		subclass_oti = class_get_rgctx_template_oti (m_class_get_parent (subclass), type_argc, index, FALSE, FALSE, NULL);
  		g_assert (subclass_oti.data);
  
  		fill_in_rgctx_template_slot (subclass, type_argc, index, subclass_oti.data, info_type);
  
  		subclass = subclass_template->next_subclass;
  	}
  }
  
  const char*
  mono_rgctx_info_type_to_str (MonoRgctxInfoType type)
  {
  	switch (type) {
  	case MONO_RGCTX_INFO_STATIC_DATA: return "STATIC_DATA";
  	case MONO_RGCTX_INFO_KLASS: return "KLASS";
  	case MONO_RGCTX_INFO_ELEMENT_KLASS: return "ELEMENT_KLASS";
  	case MONO_RGCTX_INFO_VTABLE: return "VTABLE";
  	case MONO_RGCTX_INFO_TYPE: return "TYPE";
  	case MONO_RGCTX_INFO_REFLECTION_TYPE: return "REFLECTION_TYPE";
  	case MONO_RGCTX_INFO_METHOD: return "METHOD";
  	case MONO_RGCTX_INFO_METHOD_FTNDESC: return "METHOD_FTNDESC";
  	case MONO_RGCTX_INFO_METHOD_GSHAREDVT_INFO: return "GSHAREDVT_INFO";
  	case MONO_RGCTX_INFO_GENERIC_METHOD_CODE: return "GENERIC_METHOD_CODE";
  	case MONO_RGCTX_INFO_GSHAREDVT_OUT_WRAPPER: return "GSHAREDVT_OUT_WRAPPER";
  	case MONO_RGCTX_INFO_INTERP_METHOD: return "INTERP_METHOD";
  	case MONO_RGCTX_INFO_LLVMONLY_INTERP_ENTRY: return "LLVMONLY_INTERP_ENTRY";
  	case MONO_RGCTX_INFO_CLASS_FIELD: return "CLASS_FIELD";
  	case MONO_RGCTX_INFO_METHOD_RGCTX: return "METHOD_RGCTX";
  	case MONO_RGCTX_INFO_METHOD_CONTEXT: return "METHOD_CONTEXT";
  	case MONO_RGCTX_INFO_REMOTING_INVOKE_WITH_CHECK: return "REMOTING_INVOKE_WITH_CHECK";
  	case MONO_RGCTX_INFO_METHOD_DELEGATE_CODE: return "METHOD_DELEGATE_CODE";
  	case MONO_RGCTX_INFO_CAST_CACHE: return "CAST_CACHE";
  	case MONO_RGCTX_INFO_ARRAY_ELEMENT_SIZE: return "ARRAY_ELEMENT_SIZE";
  	case MONO_RGCTX_INFO_VALUE_SIZE: return "VALUE_SIZE";
  	case MONO_RGCTX_INFO_CLASS_SIZEOF: return "CLASS_SIZEOF";
  	case MONO_RGCTX_INFO_CLASS_BOX_TYPE: return "CLASS_BOX_TYPE";
  	case MONO_RGCTX_INFO_CLASS_IS_REF_OR_CONTAINS_REFS: return "CLASS_IS_REF_OR_CONTAINS_REFS";
  	case MONO_RGCTX_INFO_FIELD_OFFSET: return "FIELD_OFFSET";
  	case MONO_RGCTX_INFO_METHOD_GSHAREDVT_OUT_TRAMPOLINE: return "METHOD_GSHAREDVT_OUT_TRAMPOLINE";
  	case MONO_RGCTX_INFO_METHOD_GSHAREDVT_OUT_TRAMPOLINE_VIRT: return "METHOD_GSHAREDVT_OUT_TRAMPOLINE_VIRT";
  	case MONO_RGCTX_INFO_SIG_GSHAREDVT_IN_TRAMPOLINE_CALLI: return "SIG_GSHAREDVT_IN_TRAMPOLINE_CALLI";
  	case MONO_RGCTX_INFO_SIG_GSHAREDVT_OUT_TRAMPOLINE_CALLI: return "SIG_GSHAREDVT_OUT_TRAMPOLINE_CALLI";
  	case MONO_RGCTX_INFO_MEMCPY: return "MEMCPY";
  	case MONO_RGCTX_INFO_BZERO: return "BZERO";
  	case MONO_RGCTX_INFO_NULLABLE_CLASS_BOX: return "NULLABLE_CLASS_BOX";
  	case MONO_RGCTX_INFO_NULLABLE_CLASS_UNBOX: return "NULLABLE_CLASS_UNBOX";
  	case MONO_RGCTX_INFO_VIRT_METHOD_CODE: return "VIRT_METHOD_CODE";
  	case MONO_RGCTX_INFO_VIRT_METHOD_BOX_TYPE: return "VIRT_METHOD_BOX_TYPE";
  	case MONO_RGCTX_INFO_DELEGATE_TRAMP_INFO: return "DELEGATE_TRAMP_INFO";
  	default:
  		return "<UNKNOWN RGCTX INFO TYPE>";
  	}
  }
  
  G_GNUC_UNUSED static char*
  rgctx_info_to_str (MonoRgctxInfoType info_type, gpointer data)
  {
  	switch (info_type) {
  	case MONO_RGCTX_INFO_VTABLE:
  		return mono_type_full_name ((MonoType*)data);
  	default:
  		return g_strdup_printf ("<%p>", data);
  	}
  }
  
  /*
   * LOCKING: loader lock
   */
  static int
  register_info (MonoClass *klass, int type_argc, gpointer data, MonoRgctxInfoType info_type)
  {
  	int i;
  	MonoRuntimeGenericContextTemplate *template_ = mono_class_get_runtime_generic_context_template (klass);
  	MonoClass *parent;
  	MonoRuntimeGenericContextInfoTemplate *oti;
  
  	for (i = 0, oti = get_info_templates (template_, type_argc); oti; ++i, oti = oti->next) {
  		if (!oti->data)
  			break;
  	}
  
  	DEBUG (printf ("set slot %s, infos [%d] = %s, %s\n", mono_type_get_full_name (class), i, mono_rgctx_info_type_to_str (info_type), rgctx_info_to_str (info_type, data)));
  
  	/* Mark the slot as used in all parent classes (until we find
  	   a parent class which already has it marked used). */
  	parent = m_class_get_parent (klass);
  	while (parent != NULL) {
  		MonoRuntimeGenericContextTemplate *parent_template;
  		MonoRuntimeGenericContextInfoTemplate *oti;
  
  		if (mono_class_is_ginst (parent))
  			parent = mono_class_get_generic_class (parent)->container_class;
  
  		parent_template = mono_class_get_runtime_generic_context_template (parent);
  		oti = rgctx_template_get_other_slot (parent_template, type_argc, i);
  
  		if (oti && oti->data)
  			break;
  
  		rgctx_template_set_slot (m_class_get_image (parent), parent_template, type_argc, i,
  								 MONO_RGCTX_SLOT_USED_MARKER, (MonoRgctxInfoType)0);
  
  		parent = m_class_get_parent (parent);
  	}
  
  	/* Fill in the slot in this class and in all subclasses
  	   recursively. */
  	fill_in_rgctx_template_slot (klass, type_argc, i, data, info_type);
  
  	return i;
  }
  
  static gboolean
  info_equal (gpointer data1, gpointer data2, MonoRgctxInfoType info_type)
  {
  	switch (info_type) {
  	case MONO_RGCTX_INFO_STATIC_DATA:
  	case MONO_RGCTX_INFO_KLASS:
  	case MONO_RGCTX_INFO_ELEMENT_KLASS:
  	case MONO_RGCTX_INFO_VTABLE:
  	case MONO_RGCTX_INFO_TYPE:
  	case MONO_RGCTX_INFO_REFLECTION_TYPE:
  	case MONO_RGCTX_INFO_CAST_CACHE:
  	case MONO_RGCTX_INFO_ARRAY_ELEMENT_SIZE:
  	case MONO_RGCTX_INFO_VALUE_SIZE:
  	case MONO_RGCTX_INFO_CLASS_SIZEOF:
  	case MONO_RGCTX_INFO_CLASS_BOX_TYPE:
  	case MONO_RGCTX_INFO_CLASS_IS_REF_OR_CONTAINS_REFS:
  	case MONO_RGCTX_INFO_MEMCPY:
  	case MONO_RGCTX_INFO_BZERO:
  	case MONO_RGCTX_INFO_NULLABLE_CLASS_BOX:
  	case MONO_RGCTX_INFO_NULLABLE_CLASS_UNBOX:
  		return mono_class_from_mono_type_internal ((MonoType *)data1) == mono_class_from_mono_type_internal ((MonoType *)data2);
  	case MONO_RGCTX_INFO_METHOD:
  	case MONO_RGCTX_INFO_METHOD_FTNDESC:
  	case MONO_RGCTX_INFO_METHOD_GSHAREDVT_INFO:
  	case MONO_RGCTX_INFO_GENERIC_METHOD_CODE:
  	case MONO_RGCTX_INFO_GSHAREDVT_OUT_WRAPPER:
  	case MONO_RGCTX_INFO_INTERP_METHOD:
  	case MONO_RGCTX_INFO_LLVMONLY_INTERP_ENTRY:
  	case MONO_RGCTX_INFO_CLASS_FIELD:
  	case MONO_RGCTX_INFO_FIELD_OFFSET:
  	case MONO_RGCTX_INFO_METHOD_RGCTX:
  	case MONO_RGCTX_INFO_METHOD_CONTEXT:
  	case MONO_RGCTX_INFO_REMOTING_INVOKE_WITH_CHECK:
  	case MONO_RGCTX_INFO_METHOD_DELEGATE_CODE:
  	case MONO_RGCTX_INFO_METHOD_GSHAREDVT_OUT_TRAMPOLINE:
  	case MONO_RGCTX_INFO_METHOD_GSHAREDVT_OUT_TRAMPOLINE_VIRT:
  	case MONO_RGCTX_INFO_SIG_GSHAREDVT_IN_TRAMPOLINE_CALLI:
  	case MONO_RGCTX_INFO_SIG_GSHAREDVT_OUT_TRAMPOLINE_CALLI:
  		return data1 == data2;
  	case MONO_RGCTX_INFO_VIRT_METHOD_CODE:
  	case MONO_RGCTX_INFO_VIRT_METHOD_BOX_TYPE: {
  		MonoJumpInfoVirtMethod *info1 = (MonoJumpInfoVirtMethod *)data1;
  		MonoJumpInfoVirtMethod *info2 = (MonoJumpInfoVirtMethod *)data2;
  
  		return info1->klass == info2->klass && info1->method == info2->method;
  	}
  	case MONO_RGCTX_INFO_DELEGATE_TRAMP_INFO: {
  		MonoDelegateClassMethodPair *dele1 = (MonoDelegateClassMethodPair *)data1;
  		MonoDelegateClassMethodPair *dele2 = (MonoDelegateClassMethodPair *)data2;
  
  		return dele1->is_virtual == dele2->is_virtual && dele1->method == dele2->method && dele1->klass == dele2->klass;
  	}
  	default:
  		g_assert_not_reached ();
  	}
  	/* never reached */
  	return FALSE;
  }
  
  /*
   * mini_rgctx_info_type_to_patch_info_type:
   *
   *   Return the type of the runtime object referred to by INFO_TYPE.
   */
  MonoJumpInfoType
  mini_rgctx_info_type_to_patch_info_type (MonoRgctxInfoType info_type)
  {
  	switch (info_type) {
  	case MONO_RGCTX_INFO_STATIC_DATA:
  	case MONO_RGCTX_INFO_KLASS:
  	case MONO_RGCTX_INFO_ELEMENT_KLASS:
  	case MONO_RGCTX_INFO_VTABLE:
  	case MONO_RGCTX_INFO_TYPE:
  	case MONO_RGCTX_INFO_REFLECTION_TYPE:
  	case MONO_RGCTX_INFO_CAST_CACHE:
  	case MONO_RGCTX_INFO_ARRAY_ELEMENT_SIZE:
  	case MONO_RGCTX_INFO_VALUE_SIZE:
  	case MONO_RGCTX_INFO_CLASS_SIZEOF:
  	case MONO_RGCTX_INFO_CLASS_BOX_TYPE:
  	case MONO_RGCTX_INFO_CLASS_IS_REF_OR_CONTAINS_REFS:
  	case MONO_RGCTX_INFO_MEMCPY:
  	case MONO_RGCTX_INFO_BZERO:
  	case MONO_RGCTX_INFO_NULLABLE_CLASS_BOX:
  	case MONO_RGCTX_INFO_NULLABLE_CLASS_UNBOX:
  	case MONO_RGCTX_INFO_LOCAL_OFFSET:
  		return MONO_PATCH_INFO_CLASS;
  	case MONO_RGCTX_INFO_CLASS_FIELD:
  	case MONO_RGCTX_INFO_FIELD_OFFSET:
  		return MONO_PATCH_INFO_FIELD;
  	case MONO_RGCTX_INFO_METHOD:
  	case MONO_RGCTX_INFO_METHOD_RGCTX:
  	case MONO_RGCTX_INFO_METHOD_FTNDESC:
  	case MONO_RGCTX_INFO_GSHAREDVT_OUT_WRAPPER:
  	case MONO_RGCTX_INFO_INTERP_METHOD:
  	case MONO_RGCTX_INFO_LLVMONLY_INTERP_ENTRY:
  		return MONO_PATCH_INFO_METHOD;
  	default:
  		printf ("%d\n", info_type);
  		g_assert_not_reached ();
  		return (MonoJumpInfoType)-1;
  	}
  }
  
  /*
   * lookup_or_register_info:
   * @method: a method
   * @in_mrgctx: whether to put the data into the MRGCTX
   * @data: the info data
   * @did_register: whether data was registered
   * @info_type: the type of info to register about data
   * @generic_context: a generic context
   *
   * Looks up and, if necessary, adds information about data/info_type in
   * method's or method's class runtime generic context.  Returns the
   * encoded slot number.
   */
  static guint32
  lookup_or_register_info (MonoMemoryManager *mem_manager, MonoClass *klass, MonoMethod *method, gboolean in_mrgctx, gpointer data, gboolean *did_register,
  						 MonoRgctxInfoType info_type, MonoGenericContext *generic_context)
  {
  	int type_argc = 0;
  
  	if (in_mrgctx) {
  		klass = method->klass;
  
  		MonoGenericInst *method_inst = mono_method_get_context (method)->method_inst;
  
  		if (method_inst) {
  			g_assert (method->is_inflated && method_inst);
  			type_argc = method_inst->type_argc;
  			g_assert (type_argc > 0);
  		}
  	}
  
  	MonoRuntimeGenericContextTemplate *rgctx_template =
  		mono_class_get_runtime_generic_context_template (klass);
  	MonoRuntimeGenericContextInfoTemplate *oti_list, *oti;
  	int i, index;
  
  	klass = get_shared_class (klass);
  
  	mono_loader_lock ();
  
  	index = -1;
  	if (info_has_identity (info_type)) {
  		oti_list = get_info_templates (rgctx_template, type_argc);
  
  		for (oti = oti_list, i = 0; oti; oti = oti->next, ++i) {
  			gpointer inflated_data;
  
  			if (oti->info_type != info_type || !oti->data)
  				continue;
  
  			inflated_data = inflate_info (mem_manager, oti, generic_context, klass, TRUE);
  
  			if (info_equal (data, inflated_data, info_type)) {
  				free_inflated_info (info_type, inflated_data);
  				index = i;
  				break;
  			}
  			free_inflated_info (info_type, inflated_data);
  		}
  	}
  
  	/* We haven't found the info */
  	if (index == -1)
  	{
  		index = register_info (klass, type_argc, data, info_type);
  		*did_register = TRUE;
  	}
  
  	/* interlocked by loader lock */
  	if (index > UnlockedRead (&rgctx_max_slot_number))
  		UnlockedWrite (&rgctx_max_slot_number, index);
  
  	mono_loader_unlock ();
  
  	//g_print ("rgctx item at index %d argc %d\n", index, type_argc);
  
  	if (in_mrgctx)
  		return MONO_RGCTX_SLOT_MAKE_MRGCTX (index);
  	else
  		return MONO_RGCTX_SLOT_MAKE_RGCTX (index);
  }
  
  static inline int
  class_rgctx_array_size (int n)
  {
  	return 32 << n;
  }
  
  static inline int
  method_rgctx_array_size (int n)
  {
  	return 6 << n;
  }
  
  /*
   * mono_class_rgctx_get_array_size:
   * @n: The number of the array
   * @mrgctx: Whether it's an MRGCTX as opposed to a RGCTX.
   *
   * Returns the number of slots in the n'th array of a (M)RGCTX.  That
   * number includes the slot for linking and - for MRGCTXs - the two
   * slots in the first array for additional information.
   */
  int
  mono_class_rgctx_get_array_size (int n, gboolean mrgctx)
  {
  	g_assert (n >= 0 && n < 30);
  
  	if (mrgctx)
  		return method_rgctx_array_size (n);
  	else
  		return class_rgctx_array_size (n);
  }
  
  static gpointer*
  alloc_rgctx_array (MonoMemoryManager *mem_manager, int n, gboolean is_mrgctx)
  {
  	gint32 size = mono_class_rgctx_get_array_size (n, is_mrgctx) * sizeof (gpointer);
  	gpointer *array = (gpointer *)mono_mem_manager_alloc0 (mem_manager, size);
  
  	if (is_mrgctx) {
  		UnlockedIncrement (&mrgctx_num_arrays_allocated);
  		UnlockedAdd (&mrgctx_bytes_allocated, size);
  	} else {
  		UnlockedIncrement (&rgctx_num_arrays_allocated);
  		UnlockedAdd (&rgctx_bytes_allocated, size);
  	}
  
  	return array;
  }
  
  static gpointer
  fill_runtime_generic_context (MonoVTable *class_vtable, MonoRuntimeGenericContext *rgctx, guint32 slot,
  							  MonoGenericInst *method_inst, gboolean is_mrgctx, MonoError *error)
  {
  	gpointer info;
  	int i, first_slot, size;
  	MonoClass *klass = class_vtable->klass;
  	MonoGenericContext *class_context;
  	MonoRuntimeGenericContextInfoTemplate oti;
  	MonoRuntimeGenericContext *orig_rgctx;
  	int rgctx_index;
  	gboolean do_free;
  	MonoJitMemoryManager *jit_mm;
  
  	/*
  	 * Need a fastpath since this is called without trampolines in llvmonly mode.
  	 */
  	orig_rgctx = rgctx;
  	if (!is_mrgctx) {
  		first_slot = 0;
  		size = class_rgctx_array_size (0);
  		for (i = 0; ; ++i) {
  			int offset = 0;
  
  			if (slot < first_slot + size - 1) {
  				rgctx_index = slot - first_slot + 1 + offset;
  				info = (MonoRuntimeGenericContext*)rgctx [rgctx_index];
  				if (info)
  					return info;
  				break;
  			}
  			if (!rgctx [offset + 0])
  				break;
  			rgctx = (void **)rgctx [offset + 0];
  			first_slot += size - 1;
  			size = class_rgctx_array_size (i + 1);
  		}
  	} else {
  		first_slot = 0;
  		size = method_rgctx_array_size (0);
  		size -= MONO_SIZEOF_METHOD_RUNTIME_GENERIC_CONTEXT / sizeof (gpointer);
  		for (i = 0; ; ++i) {
  			int offset = 0;
  
  			if (i == 0)
  				offset = MONO_SIZEOF_METHOD_RUNTIME_GENERIC_CONTEXT / sizeof (gpointer);
  
  			if (slot < first_slot + size - 1) {
  				rgctx_index = slot - first_slot + 1 + offset;
  				info = (MonoRuntimeGenericContext*)rgctx [rgctx_index];
  				if (info)
  					return info;
  				break;
  			}
  			if (!rgctx [offset + 0])
  				break;
  			rgctx = (void **)rgctx [offset + 0];
  			first_slot += size - 1;
  			size = method_rgctx_array_size (i + 1);
  		}
  	}
  	rgctx = orig_rgctx;
  
  	jit_mm = jit_mm_for_class (class_vtable->klass);
  
  	class_context = mono_class_is_ginst (klass) ? &mono_class_get_generic_class (klass)->context : NULL;
  	MonoGenericContext context = { class_context ? class_context->class_inst : NULL, method_inst };
  
  	mono_mem_manager_lock (jit_mm->mem_manager);
  
  	/* 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, is_mrgctx);
  	if (is_mrgctx)
  		size -= MONO_SIZEOF_METHOD_RUNTIME_GENERIC_CONTEXT / sizeof (gpointer);
  	for (i = 0; ; ++i) {
  		int offset;
  
  		if (is_mrgctx && i == 0)
  			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 = (MonoRuntimeGenericContext*)rgctx [rgctx_index];
  			if (info) {
  				mono_mem_manager_unlock (jit_mm->mem_manager);
  				return info;
  			}
  			break;
  		}
  		if (!rgctx [offset + 0]) {
  			gpointer *array = alloc_rgctx_array (jit_mm->mem_manager, i + 1, is_mrgctx);
  			/* Make sure that this array is zeroed if other threads access it */
  			mono_memory_write_barrier ();
  			rgctx [offset + 0] = array;
  		}
  		rgctx = (void **)rgctx [offset + 0];
  		first_slot += size - 1;
  		size = mono_class_rgctx_get_array_size (i + 1, is_mrgctx);
  	}
  
  	g_assert (!rgctx [rgctx_index]);
  
  	mono_mem_manager_unlock (jit_mm->mem_manager);
  
  	oti = class_get_rgctx_template_oti (get_shared_class (klass),
  										method_inst ? method_inst->type_argc : 0, slot, TRUE, TRUE, &do_free);
  	/* This might take the loader lock */
  	info = (MonoRuntimeGenericContext*)instantiate_info (jit_mm->mem_manager, &oti, &context, klass, error);
  	return_val_if_nok (error, NULL);
  	g_assert (info);
  
  	/*
  	if (method_inst)
  		g_print ("filling mrgctx slot %d table %d index %d\n", slot, i, rgctx_index);
  	*/
  
  	/*FIXME We should use CAS here, no need to take a lock.*/
  	mono_mem_manager_lock (jit_mm->mem_manager);
  
  	/* Check whether the slot hasn't been instantiated in the
  	   meantime. */
  	if (rgctx [rgctx_index]) {
  		info = (MonoRuntimeGenericContext*)rgctx [rgctx_index];
  	} else {
  		/* Make sure other threads see the contents of info */
  		mono_memory_write_barrier ();
  		rgctx [rgctx_index] = info;
  	}
  
  	mono_mem_manager_unlock (jit_mm->mem_manager);
  
  	if (do_free)
  		free_inflated_info (oti.info_type, oti.data);
  
  	return info;
  }
  
  /*
   * mono_class_fill_runtime_generic_context:
   * @class_vtable: a vtable
   * @slot: a slot index to be instantiated
   *
   * Instantiates a slot in the RGCTX, returning its value.
   */
  gpointer
  mono_class_fill_runtime_generic_context (MonoVTable *class_vtable, guint32 slot, MonoError *error)
  {
  	MonoRuntimeGenericContext *rgctx, *new_rgctx;
  	gpointer info;
  	MonoJitMemoryManager *jit_mm = jit_mm_for_class (class_vtable->klass);
  
  	error_init (error);
  
  	rgctx = class_vtable->runtime_generic_context;
  	if (G_UNLIKELY (!rgctx)) {
  		new_rgctx = alloc_rgctx_array (jit_mm->mem_manager, 0, FALSE);
  		/* Make sure that this array is zeroed if other threads access it */
  		mono_memory_write_barrier ();
  
  		jit_mm_lock (jit_mm);
  		rgctx = class_vtable->runtime_generic_context;
  		if (!rgctx) {
  			class_vtable->runtime_generic_context = new_rgctx;
  			UnlockedIncrement (&rgctx_num_allocated);
  			rgctx = new_rgctx;
  		}
  		jit_mm_unlock (jit_mm);
  	}
  
  	info = fill_runtime_generic_context (class_vtable, rgctx, slot, NULL, FALSE, error);
  
  	DEBUG (printf ("get rgctx slot: %s %d -> %p\n", mono_type_full_name (m_class_get_byval_arg (class_vtable->klass)), slot, info));
  
  	return info;
  }
  
  /*
   * mono_method_fill_runtime_generic_context:
   * @mrgctx: an MRGCTX
   * @slot: a slot index to be instantiated
   *
   * Instantiates a slot in the MRGCTX.
   */
  gpointer
  mono_method_fill_runtime_generic_context (MonoMethodRuntimeGenericContext *mrgctx, guint32 slot, MonoError *error)
  {
  	gpointer info;
  
  	info = fill_runtime_generic_context (mrgctx->class_vtable, (MonoRuntimeGenericContext*)mrgctx, slot, mrgctx->method_inst, TRUE, error);
  
  	return info;
  }
  
  static guint
  mrgctx_hash_func (gconstpointer key)
  {
  	const MonoMethodRuntimeGenericContext *mrgctx = (const MonoMethodRuntimeGenericContext *)key;
  
  	return mono_aligned_addr_hash (mrgctx->class_vtable) ^ mono_metadata_generic_inst_hash (mrgctx->method_inst);
  }
  
  static gboolean
  mrgctx_equal_func (gconstpointer a, gconstpointer b)
  {
  	const MonoMethodRuntimeGenericContext *mrgctx1 = (const MonoMethodRuntimeGenericContext *)a;
  	const MonoMethodRuntimeGenericContext *mrgctx2 = (const MonoMethodRuntimeGenericContext *)b;
  
  	return mrgctx1->class_vtable == mrgctx2->class_vtable &&
  		mono_metadata_generic_inst_equal (mrgctx1->method_inst, mrgctx2->method_inst);
  }
  
  /*
   * mini_method_get_mrgctx:
   * @class_vtable: a vtable
   * @method: an inflated method
   *
   * Returns the MRGCTX for METHOD.
   *
   */
  static MonoMethodRuntimeGenericContext*
  mini_method_get_mrgctx (MonoVTable *class_vtable, MonoMethod *method)
  {
  	MonoMethodRuntimeGenericContext *mrgctx;
  	MonoMethodRuntimeGenericContext key;
  	MonoGenericInst *method_inst = mini_method_get_context (method)->method_inst;
  	MonoJitMemoryManager *jit_mm;
  
  	g_assert (!mono_class_is_gtd (class_vtable->klass));
  
  	jit_mm = jit_mm_for_method (method);
  
  	if (!method_inst) {
  		g_assert (mini_method_is_default_method (method));
  
  		jit_mm_lock (jit_mm);
  		if (!jit_mm->mrgctx_hash)
  			jit_mm->mrgctx_hash = g_hash_table_new (NULL, NULL);
  		mrgctx = (MonoMethodRuntimeGenericContext*)g_hash_table_lookup (jit_mm->mrgctx_hash, method);
  		jit_mm_unlock (jit_mm);
  	} else {
  		g_assert (!method_inst->is_open);
  
  		jit_mm_lock (jit_mm);
  		if (!jit_mm->method_rgctx_hash)
  			jit_mm->method_rgctx_hash = g_hash_table_new (mrgctx_hash_func, mrgctx_equal_func);
  
  		key.class_vtable = class_vtable;
  		key.method_inst = method_inst;
  
  		mrgctx = (MonoMethodRuntimeGenericContext *)g_hash_table_lookup (jit_mm->method_rgctx_hash, &key);
  		jit_mm_unlock (jit_mm);
  	}
  
  	if (!mrgctx) {
  		mrgctx = (MonoMethodRuntimeGenericContext*)alloc_rgctx_array (jit_mm->mem_manager, 0, TRUE);
  		mrgctx->class_vtable = class_vtable;
  		mrgctx->method_inst = method_inst;
  
  		jit_mm_lock (jit_mm);
  		if (!method_inst)
  			g_hash_table_insert (jit_mm->mrgctx_hash, method, mrgctx);
  		else
  			g_hash_table_insert (jit_mm->method_rgctx_hash, mrgctx, mrgctx);
  		jit_mm_unlock (jit_mm);
  
  		/*
  		g_print ("mrgctx alloced for %s <", mono_type_get_full_name (class_vtable->klass));
  		for (int i = 0; i < method_inst->type_argc; ++i)
  			g_print ("%s, ", mono_type_full_name (method_inst->type_argv [i]));
  		g_print (">\n");
  		*/
  	}
  
  	g_assert (mrgctx);
  
  	return mrgctx;
  }
  
  static gboolean
  type_is_sharable (MonoType *type, gboolean allow_type_vars, gboolean allow_partial)
  {
  	if (allow_type_vars && (type->type == MONO_TYPE_VAR || type->type == MONO_TYPE_MVAR)) {
  		MonoType *constraint = type->data.generic_param->gshared_constraint;
  		if (!constraint)
  			return TRUE;
  		type = constraint;
  	}
  
  	if (MONO_TYPE_IS_REFERENCE (type))
  		return TRUE;
  
  	/* Allow non ref arguments if they are primitive types or enums (partial sharing). */
  	if (allow_partial && !m_type_is_byref (type) && (((type->type >= MONO_TYPE_BOOLEAN) && (type->type <= MONO_TYPE_R8)) || (type->type == MONO_TYPE_I) || (type->type == MONO_TYPE_U) || (type->type == MONO_TYPE_VALUETYPE && m_class_is_enumtype (type->data.klass))))
  		return TRUE;
  
  	if (allow_partial && !m_type_is_byref (type) && type->type == MONO_TYPE_GENERICINST && MONO_TYPE_ISSTRUCT (type)) {
  		MonoGenericClass *gclass = type->data.generic_class;
  
  		if (gclass->context.class_inst && !mini_generic_inst_is_sharable (gclass->context.class_inst, allow_type_vars, allow_partial))
  			return FALSE;
  		if (gclass->context.method_inst && !mini_generic_inst_is_sharable (gclass->context.method_inst, allow_type_vars, allow_partial))
  			return FALSE;
  		if (mono_class_is_nullable (mono_class_from_mono_type_internal (type)))
  			return FALSE;
  		return TRUE;
  	}
  
  	return FALSE;
  }
  
  gboolean
  mini_generic_inst_is_sharable (MonoGenericInst *inst, gboolean allow_type_vars,
  						  gboolean allow_partial)
  {
  	int i;
  
  	for (i = 0; i < inst->type_argc; ++i) {
  		if (!type_is_sharable (inst->type_argv [i], allow_type_vars, allow_partial))
  			return FALSE;
  	}
  
  	return TRUE;
  }
  
  /*
   * mono_is_partially_sharable_inst:
   *
   *   Return TRUE if INST has ref and non-ref type arguments.
   */
  gboolean
  mono_is_partially_sharable_inst (MonoGenericInst *inst)
  {
  	int i;
  	gboolean has_refs = FALSE, has_non_refs = FALSE;
  
  	for (i = 0; i < inst->type_argc; ++i) {
  		if (MONO_TYPE_IS_REFERENCE (inst->type_argv [i]) || inst->type_argv [i]->type == MONO_TYPE_VAR || inst->type_argv [i]->type == MONO_TYPE_MVAR)
  			has_refs = TRUE;
  		else
  			has_non_refs = TRUE;
  	}
  
  	return has_refs && has_non_refs;
  }
  
  /*
   * mono_generic_context_is_sharable_full:
   * @context: a generic context
   *
   * Returns whether the generic context is sharable.  A generic context
   * is sharable iff all of its type arguments are reference type, or some of them have a
   * reference type, and ALLOW_PARTIAL is TRUE.
   */
  gboolean
  mono_generic_context_is_sharable_full (MonoGenericContext *context,
  									   gboolean allow_type_vars,
  									   gboolean allow_partial)
  {
  	g_assert (context->class_inst || context->method_inst);
  
  	if (context->class_inst && !mini_generic_inst_is_sharable (context->class_inst, allow_type_vars, allow_partial))
  		return FALSE;
  
  	if (context->method_inst && !mini_generic_inst_is_sharable (context->method_inst, allow_type_vars, allow_partial))
  		return FALSE;
  
  	return TRUE;
  }
  
  gboolean
  mono_generic_context_is_sharable (MonoGenericContext *context, gboolean allow_type_vars)
  {
  	return mono_generic_context_is_sharable_full (context, allow_type_vars, partial_sharing_supported ());
  }
  
  static gboolean
  is_primitive_inst (MonoGenericInst *inst)
  {
  	for (int i = 0; i < inst->type_argc; ++i) {
  		if (!MONO_TYPE_IS_PRIMITIVE (inst->type_argv [i]))
  			return FALSE;
  	}
  	return TRUE;
  }
  
  /*
   * mono_method_is_generic_impl:
   * @method: a method
   *
   * Returns whether the method is either generic or part of a generic
   * class.
   */
  gboolean
  mono_method_is_generic_impl (MonoMethod *method)
  {
  	if (method->is_inflated)
  		return TRUE;
  	/* We don't treat wrappers as generic code, i.e., we never
  	   apply generic sharing to them.  This is especially
  	   important for static rgctx invoke wrappers, which only work
  	   if not compiled with sharing. */
  	if (method->wrapper_type != MONO_WRAPPER_NONE)
  		return FALSE;
  	if (mono_class_is_gtd (method->klass))
  		return TRUE;
  	return FALSE;
  }
  
  static gboolean
  has_constraints (MonoGenericContainer *container)
  {
  	int i;
  
  	g_assert (container->type_argc > 0);
  	g_assert (container->type_params);
  
  	for (i = 0; i < container->type_argc; ++i)
  		if (container->type_params [i].info.constraints)
  			return TRUE;
  	return FALSE;
  }
  
  /*
   * Return whenever GPARAM can be instantiated with an enum.
   */
  static gboolean
  gparam_can_be_enum (MonoGenericParam *gparam)
  {
  	if (!gparam->info.constraints)
  		return TRUE;
  	/*
  	 * If a constraint is an interface which is not implemented by Enum, then the gparam can't be
  	 * instantiated with an enum.
  	 */
  	for (int cindex = 0; gparam->info.constraints [cindex]; cindex ++) {
  		MonoClass *k = gparam->info.constraints [cindex];
  		if (MONO_CLASS_IS_INTERFACE_INTERNAL (k)) {
  			MonoClass **enum_ifaces = m_class_get_interfaces (mono_defaults.enum_class);
  			gboolean is_enum_iface = FALSE;
  			for (int i = 0; i < m_class_get_interface_count (mono_defaults.enum_class); i++) {
  				if (k == enum_ifaces [i]) {
  					is_enum_iface = TRUE;
  					break;
  				}
  			}
  			if (!is_enum_iface)
  				return FALSE;
  		}
  	}
  	return TRUE;
  }
  
  static gboolean
  mini_method_is_open (MonoMethod *method)
  {
  	if (method->is_inflated) {
  		MonoGenericContext *ctx = mono_method_get_context (method);
  
  		if (ctx->class_inst && ctx->class_inst->is_open)
  			return TRUE;
  		if (ctx->method_inst && ctx->method_inst->is_open)
  			return TRUE;
  	}
  	return FALSE;
  }
  
  /* Lazy class loading functions */
  static GENERATE_TRY_GET_CLASS_WITH_CACHE (iasync_state_machine, "System.Runtime.CompilerServices", "IAsyncStateMachine")
  
  static G_GNUC_UNUSED gboolean
  is_async_state_machine_class (MonoClass *klass)
  {
  	MonoClass *iclass;
  
  	return FALSE;
  
  	iclass = mono_class_try_get_iasync_state_machine_class ();
  
  	if (iclass && m_class_is_valuetype (klass) && mono_class_is_assignable_from_internal (iclass, klass))
  		return TRUE;
  	return FALSE;
  }
  
  static G_GNUC_UNUSED gboolean
  is_async_method (MonoMethod *method)
  {
  	ERROR_DECL (error);
  	MonoCustomAttrInfo *cattr;
  	MonoMethodSignature *sig;
  	gboolean res = FALSE;
  	MonoClass *attr_class;
  
  	return FALSE;
  
  	attr_class = mono_class_try_get_iasync_state_machine_class ();
  
  	/* Do less expensive checks first */
  	sig = mono_method_signature_internal (method);
  	if (attr_class && sig && ((sig->ret->type == MONO_TYPE_VOID) ||
  				(sig->ret->type == MONO_TYPE_CLASS && !strcmp (m_class_get_name (sig->ret->data.generic_class->container_class), "Task")) ||
  				(sig->ret->type == MONO_TYPE_GENERICINST && !strcmp (m_class_get_name (sig->ret->data.generic_class->container_class), "Task`1")))) {
  		//printf ("X: %s\n", mono_method_full_name (method, TRUE));
  		cattr = mono_custom_attrs_from_method_checked (method, error);
  		if (!is_ok (error)) {
  			mono_error_cleanup (error); /* FIXME don't swallow the error? */
  			return FALSE;
  		}
  		if (cattr) {
  			if (mono_custom_attrs_has_attr (cattr, attr_class))
  				res = TRUE;
  			mono_custom_attrs_free (cattr);
  		}
  	}
  	return res;
  }
  
  /*
   * mono_method_is_generic_sharable_full:
   * @method: a method
   * @allow_type_vars: whether to regard type variables as reference types
   * @allow_partial: whether to allow partial sharing
   * @allow_gsharedvt: whenever to allow sharing over valuetypes
   *
   * Returns TRUE iff the method is inflated or part of an inflated
   * class, its context is sharable and it has no constraints on its
   * type parameters.  Otherwise returns FALSE.
   */
  gboolean
  mono_method_is_generic_sharable_full (MonoMethod *method, gboolean allow_type_vars,
  										   gboolean allow_partial, gboolean allow_gsharedvt)
  {
  	if (!mono_method_is_generic_impl (method))
  		return FALSE;
  
  	/*
  	if (!mono_debug_count ())
  		allow_partial = FALSE;
  	*/
  
  	if (!partial_sharing_supported ())
  		allow_partial = FALSE;
  
  	if (mono_class_is_nullable (method->klass))
  		// FIXME:
  		allow_partial = FALSE;
  
  	if (m_class_get_image (method->klass)->dynamic)
  		/*
  		 * Enabling this causes corlib test failures because the JIT encounters generic instances whose
  		 * instance_size is 0.
  		 */
  		allow_partial = FALSE;
  
  	/*
  	 * Generic async methods have an associated state machine class which is a generic struct. This struct
  	 * is too large to be handled by gsharedvt so we make it visible to the AOT compiler by disabling sharing
  	 * of the async method and the state machine class.
  	 */
  	if (is_async_state_machine_class (method->klass))
  		return FALSE;
  
  	if (allow_gsharedvt && mini_is_gsharedvt_sharable_method (method)) {
  		if (is_async_method (method))
  			return FALSE;
  		return TRUE;
  	}
  
  	if (method->is_inflated) {
  		MonoMethodInflated *inflated = (MonoMethodInflated*)method;
  		MonoGenericContext *context = &inflated->context;
  
  		if (!mono_generic_context_is_sharable_full (context, allow_type_vars, allow_partial))
  			return FALSE;
  
  		g_assert (inflated->declaring);
  
  #if 0
  		if (inflated->declaring->is_generic) {
  			if (has_constraints (mono_method_get_generic_container (inflated->declaring))) {
  			}
  		}
  #endif
  	}
  
  	if (mono_class_is_ginst (method->klass)) {
  		MonoGenericContext *ctx = &mono_class_get_generic_class (method->klass)->context;
  		if (!mono_generic_context_is_sharable_full (ctx, allow_type_vars, allow_partial))
  			return FALSE;
  
  		g_assert (mono_class_get_generic_class (method->klass)->container_class &&
  				mono_class_is_gtd (mono_class_get_generic_class (method->klass)->container_class));
  
  		/*
  		 * If all the parameters are primitive types and constraints prevent
  		 * them from being instantiated with enums, then only the primitive
  		 * type instantiation is possible, thus sharing is not useful.
  		 * Happens with generic math interfaces.
  		 */
  		if ((!ctx->class_inst || is_primitive_inst (ctx->class_inst)) &&
  			(!ctx->method_inst || is_primitive_inst (ctx->method_inst))) {
  			MonoGenericContainer *container = mono_class_get_generic_container (mono_class_get_generic_class (method->klass)->container_class);
  			if (has_constraints (container)) {
  				g_assert (ctx->class_inst->type_argc == container->type_argc);
  				for (int i = 0; i < container->type_argc; ++i) {
  					if (!gparam_can_be_enum (&container->type_params [i]))
  						return FALSE;
  				}
  			}
  		}
  	}
  
  	if (mono_class_is_gtd (method->klass) && !allow_type_vars)
  		return FALSE;
  
  	/* This does potentially expensive cattr checks, so do it at the end */
  	if (is_async_method (method)) {
  		if (mini_method_is_open (method))
  			/* The JIT can't compile these without sharing */
  			return TRUE;
  		return FALSE;
  	}
  
  	return TRUE;
  }
  
  gboolean
  mono_method_is_generic_sharable (MonoMethod *method, gboolean allow_type_vars)
  {
  	return mono_method_is_generic_sharable_full (method, allow_type_vars, partial_sharing_supported (), TRUE);
  }
  
  /*
   * mono_method_needs_static_rgctx_invoke:
   *
   *   Return whenever METHOD needs an rgctx argument.
   * An rgctx argument is needed when the method is generic sharable, but it doesn't
   * have a this argument which can be used to load the rgctx.
   */
  gboolean
  mono_method_needs_static_rgctx_invoke (MonoMethod *method, gboolean allow_type_vars)
  {
  	if (!mono_class_generic_sharing_enabled (method->klass))
  		return FALSE;
  
  	if (!mono_method_is_generic_sharable (method, allow_type_vars))
  		return FALSE;
  
  	if (method->is_inflated && mono_method_get_context (method)->method_inst)
  		return TRUE;
  
  	return ((method->flags & METHOD_ATTRIBUTE_STATIC) ||
  			m_class_is_valuetype (method->klass) ||
  			mini_method_is_default_method (method)) &&
  		(mono_class_is_ginst (method->klass) || mono_class_is_gtd (method->klass));
  }
  
  static MonoGenericInst*
  get_object_generic_inst (int type_argc)
  {
  	MonoType **type_argv;
  	int i;
  
  	type_argv = g_newa (MonoType*, type_argc);
  
  	MonoType *object_type = mono_get_object_type ();
  	for (i = 0; i < type_argc; ++i)
  		type_argv [i] = object_type;
  
  	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 (!mono_class_is_ginst (method->klass));
  	if (mono_class_is_gtd (method->klass)) {
  		int type_argc = mono_class_get_generic_container (method->klass)->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;
  }
  
  static gboolean gshared_supported;
  
  void
  mono_set_generic_sharing_supported (gboolean supported)
  {
  	gshared_supported = supported;
  }
  
  
  void
  mono_set_partial_sharing_supported (gboolean supported)
  {
  	partial_supported = supported;
  }
  
  /*
   * mono_class_generic_sharing_enabled:
   * @class: a class
   *
   * Returns whether generic sharing is enabled for class.
   *
   * This is a stop-gap measure to slowly introduce generic sharing
   * until we have all the issues sorted out, at which time this
   * function will disappear and generic sharing will always be enabled.
   */
  gboolean
  mono_class_generic_sharing_enabled (MonoClass *klass)
  {
  	if (gshared_supported)
  		return TRUE;
  	else
  		return FALSE;
  }
  
  MonoGenericContext*
  mini_method_get_context (MonoMethod *method)
  {
  	return mono_method_get_context_general (method, TRUE);
  }
  
  /*
   * mono_method_check_context_used:
   * @method: a method
   *
   * Checks whether the method's generic context uses a type variable.
   * Returns an int with the bits MONO_GENERIC_CONTEXT_USED_CLASS and
   * MONO_GENERIC_CONTEXT_USED_METHOD set to reflect whether the
   * context's class or method instantiation uses type variables.
   */
  int
  mono_method_check_context_used (MonoMethod *method)
  {
  	MonoGenericContext *method_context = mini_method_get_context (method);
  	int context_used = 0;
  
  	if (!method_context) {
  		/* It might be a method of an array of an open generic type */
  		if (m_class_get_rank (method->klass))
  			context_used = mono_class_check_context_used (method->klass);
  	} else {
  		context_used = mono_generic_context_check_used (method_context);
  		context_used |= mono_class_check_context_used (method->klass);
  	}
  
  	return context_used;
  }
  
  static gboolean
  generic_inst_equal (MonoGenericInst *inst1, MonoGenericInst *inst2)
  {
  	int i;
  
  	if (!inst1) {
  		g_assert (!inst2);
  		return TRUE;
  	}
  
  	g_assert (inst2);
  
  	if (inst1->type_argc != inst2->type_argc)
  		return FALSE;
  
  	for (i = 0; i < inst1->type_argc; ++i)
  		if (!mono_metadata_type_equal (inst1->type_argv [i], inst2->type_argv [i]))
  			return FALSE;
  
  	return TRUE;
  }
  
  /*
   * mono_generic_context_equal_deep:
   * @context1: a generic context
   * @context2: a generic context
   *
   * Returns whether context1's type arguments are equal to context2's
   * type arguments.
   */
  gboolean
  mono_generic_context_equal_deep (MonoGenericContext *context1, MonoGenericContext *context2)
  {
  	return generic_inst_equal (context1->class_inst, context2->class_inst) &&
  		generic_inst_equal (context1->method_inst, context2->method_inst);
  }
  
  /*
   * mini_class_get_container_class:
   * @class: a generic class
   *
   * Returns the class's container class, which is the class itself if
   * it doesn't have generic_class set.
   */
  MonoClass*
  mini_class_get_container_class (MonoClass *klass)
  {
  	if (mono_class_is_ginst (klass))
  		return mono_class_get_generic_class (klass)->container_class;
  
  	g_assert (mono_class_is_gtd (klass));
  	return klass;
  }
  
  /*
   * mini_class_get_context:
   * @class: a generic class
   *
   * Returns the class's generic context.
   */
  MonoGenericContext*
  mini_class_get_context (MonoClass *klass)
  {
  	if (mono_class_is_ginst (klass))
  		return &mono_class_get_generic_class (klass)->context;
  
  	g_assert (mono_class_is_gtd (klass));
  	return &mono_class_get_generic_container (klass)->context;
  }
  
  /*
   * mini_get_basic_type_from_generic:
   * @type: a type
   *
   * Returns a closed type corresponding to the possibly open type
   * passed to it.
   */
  static MonoType*
  mini_get_basic_type_from_generic (MonoType *type)
  {
  	if (!m_type_is_byref (type) && (type->type == MONO_TYPE_VAR || type->type == MONO_TYPE_MVAR) && mini_is_gsharedvt_type (type))
  		return type;
  	else if (!m_type_is_byref (type) && (type->type == MONO_TYPE_VAR || type->type == MONO_TYPE_MVAR)) {
  		MonoType *constraint = type->data.generic_param->gshared_constraint;
  		/* The gparam constraint encodes the type this gparam can represent */
  		if (!constraint) {
  			return mono_get_object_type ();
  		} else {
  			MonoClass *klass;
  
  			g_assert (constraint != m_class_get_byval_arg (m_class_get_parent (mono_defaults.int_class)));
  			klass = mono_class_from_mono_type_internal (constraint);
  			return m_class_get_byval_arg (klass);
  		}
  	} else {
  		return mini_native_type_replace_type (mono_type_get_basic_type_from_generic (type));
  	}
  }
  
  /*
   * mini_type_get_underlying_type:
   *
   *   Return the underlying type of TYPE, taking into account enums, byref, bool, char, ref types and generic
   * sharing.
   */
  MonoType*
  mini_type_get_underlying_type (MonoType *type)
  {
  	type = mini_native_type_replace_type (type);
  
  	if (m_type_is_byref (type))
  		return mono_get_int_type ();
  	if (!m_type_is_byref (type) && (type->type == MONO_TYPE_VAR || type->type == MONO_TYPE_MVAR) && mini_is_gsharedvt_type (type))
  		return type;
  	type = mini_get_basic_type_from_generic (mono_type_get_underlying_type (type));
  	switch (type->type) {
  	case MONO_TYPE_BOOLEAN:
  		return m_class_get_byval_arg (mono_defaults.byte_class);
  	case MONO_TYPE_CHAR:
  		return m_class_get_byval_arg (mono_defaults.uint16_class);
  	case MONO_TYPE_STRING:
  	case MONO_TYPE_CLASS:
  	case MONO_TYPE_ARRAY:
  	case MONO_TYPE_SZARRAY:
  		return mono_get_object_type ();
  	default:
  		return type;
  	}
  }
  
  /*
   * mini_type_stack_size:
   * @t: a type
   * @align: Pointer to an int for returning the alignment
   *
   * Returns the type's stack size and the alignment in *align.
   */
  int
  mini_type_stack_size (MonoType *t, int *align)
  {
  	return mono_type_stack_size_internal (t, align, TRUE);
  }
  
  /*
   * mini_type_stack_size_full:
   *
   *   Same as mini_type_stack_size, but handle pinvoke data types as well.
   */
  int
  mini_type_stack_size_full (MonoType *t, guint32 *align, gboolean pinvoke)
  {
  	int size;
  
  	//g_assert (!mini_is_gsharedvt_type (t));
  
  	if (pinvoke) {
  		size = mono_type_native_stack_size (t, align);
  	} else {
  		int ialign;
  
  		if (align) {
  			size = mini_type_stack_size (t, &ialign);
  			*align = ialign;
  		} else {
  			size = mini_type_stack_size (t, NULL);
  		}
  	}
  
  	return size;
  }
  
  /*
   * mono_generic_sharing_init:
   *
   * Initialize the module.
   */
  void
  mono_generic_sharing_init (void)
  {
  	mono_counters_register ("RGCTX template num allocated", MONO_COUNTER_GENERICS | MONO_COUNTER_INT, &rgctx_template_num_allocated);
  	mono_counters_register ("RGCTX template bytes allocated", MONO_COUNTER_GENERICS | MONO_COUNTER_INT, &rgctx_template_bytes_allocated);
  	mono_counters_register ("RGCTX oti num allocated", MONO_COUNTER_GENERICS | MONO_COUNTER_INT, &rgctx_oti_num_allocated);
  	mono_counters_register ("RGCTX oti bytes allocated", MONO_COUNTER_GENERICS | MONO_COUNTER_INT, &rgctx_oti_bytes_allocated);
  	mono_counters_register ("RGCTX oti num markers", MONO_COUNTER_GENERICS | MONO_COUNTER_INT, &rgctx_oti_num_markers);
  	mono_counters_register ("RGCTX oti num data", MONO_COUNTER_GENERICS | MONO_COUNTER_INT, &rgctx_oti_num_data);
  	mono_counters_register ("RGCTX max slot number", MONO_COUNTER_GENERICS | MONO_COUNTER_INT, &rgctx_max_slot_number);
  	mono_counters_register ("RGCTX num allocated", MONO_COUNTER_GENERICS | MONO_COUNTER_INT, &rgctx_num_allocated);
  	mono_counters_register ("RGCTX num arrays allocated", MONO_COUNTER_GENERICS | MONO_COUNTER_INT, &rgctx_num_arrays_allocated);
  	mono_counters_register ("RGCTX bytes allocated", MONO_COUNTER_GENERICS | MONO_COUNTER_INT, &rgctx_bytes_allocated);
  	mono_counters_register ("MRGCTX num arrays allocated", MONO_COUNTER_GENERICS | MONO_COUNTER_INT, &mrgctx_num_arrays_allocated);
  	mono_counters_register ("MRGCTX bytes allocated", MONO_COUNTER_GENERICS | MONO_COUNTER_INT, &mrgctx_bytes_allocated);
  	mono_counters_register ("GSHAREDVT num trampolines", MONO_COUNTER_JIT | MONO_COUNTER_INT, &gsharedvt_num_trampolines);
  
  	mono_install_image_unload_hook (mono_class_unregister_image_generic_subclasses, NULL);
  
  	mono_os_mutex_init_recursive (&gshared_mutex);
  }
  
  /*
   * mini_type_var_is_vt:
   *
   *   Return whenever T is a type variable instantiated with a vtype.
   */
  gboolean
  mini_type_var_is_vt (MonoType *type)
  {
  	if (type->type == MONO_TYPE_VAR || type->type == MONO_TYPE_MVAR) {
  		return type->data.generic_param->gshared_constraint && (type->data.generic_param->gshared_constraint->type == MONO_TYPE_VALUETYPE || type->data.generic_param->gshared_constraint->type == MONO_TYPE_GENERICINST);
  	} else {
  		g_assert_not_reached ();
  		return FALSE;
  	}
  }
  
  gboolean
  mini_type_is_reference (MonoType *type)
  {
  	type = mini_type_get_underlying_type (type);
  	return mono_type_is_reference (type);
  }
  
  gboolean
  mini_method_is_default_method (MonoMethod *m)
  {
  	return MONO_CLASS_IS_INTERFACE_INTERNAL (m->klass) && !(m->flags & METHOD_ATTRIBUTE_ABSTRACT);
  }
  
  gboolean
  mini_method_needs_mrgctx (MonoMethod *m)
  {
  	if (mono_class_is_ginst (m->klass) && mini_method_is_default_method (m))
  		return TRUE;
  	return (mini_method_get_context (m) && mini_method_get_context (m)->method_inst);
  }
  
  /*
   * mini_method_get_rgctx:
   *
   *  Return the RGCTX which needs to be passed to M when it is called.
   */
  gpointer
  mini_method_get_rgctx (MonoMethod *m)
  {
  	ERROR_DECL (error);
  	MonoVTable *vt = mono_class_vtable_checked (m->klass, error);
  	mono_error_assert_ok (error);
  	if (mini_method_needs_mrgctx (m))
  		return mini_method_get_mrgctx (vt, m);
  	else
  		return vt;
  }
  
  /*
   * mini_type_is_vtype:
   *
   *   Return whenever T is a vtype, or a type param instantiated with a vtype.
   * Should be used in place of MONO_TYPE_ISSTRUCT () which can't handle gsharedvt.
   */
  gboolean
  mini_type_is_vtype (MonoType *t)
  {
  	t = mini_type_get_underlying_type (t);
  
  	return MONO_TYPE_ISSTRUCT (t) || mini_is_gsharedvt_variable_type (t);
  }
  
  gboolean
  mini_class_is_generic_sharable (MonoClass *klass)
  {
  	if (mono_class_is_ginst (klass) && is_async_state_machine_class (klass))
  		return FALSE;
  
  	return (mono_class_is_ginst (klass) && mono_generic_context_is_sharable (&mono_class_get_generic_class (klass)->context, FALSE));
  }
  
  gboolean
  mini_is_gsharedvt_variable_klass (MonoClass *klass)
  {
  	return mini_is_gsharedvt_variable_type (m_class_get_byval_arg (klass));
  }
  
  gboolean
  mini_is_gsharedvt_gparam (MonoType *t)
  {
  	/* Matches get_gsharedvt_type () */
  	return (t->type == MONO_TYPE_VAR || t->type == MONO_TYPE_MVAR) && t->data.generic_param->gshared_constraint && t->data.generic_param->gshared_constraint->type == MONO_TYPE_VALUETYPE;
  }
  
  static char*
  get_shared_gparam_name (MonoTypeEnum constraint, const char *name)
  {
  	if (constraint == MONO_TYPE_VALUETYPE) {
  		return g_strdup_printf ("%s_GSHAREDVT", name);
  	} else if (constraint == MONO_TYPE_OBJECT) {
  		return g_strdup_printf ("%s_REF", name);
  	} else if (constraint == MONO_TYPE_GENERICINST) {
  		return g_strdup_printf ("%s_INST", name);
  	} else {
  		MonoType t;
  		char *tname, *tname2, *res;
  
  		memset (&t, 0, sizeof (t));
  		t.type = constraint;
  		tname = mono_type_full_name (&t);
  		tname2 = g_utf8_strup (tname, strlen (tname));
  		res = g_strdup_printf ("%s_%s", name, tname2);
  		g_free (tname);
  		g_free (tname2);
  		return res;
  	}
  }
  
  static guint
  shared_gparam_hash (gconstpointer data)
  {
  	MonoGSharedGenericParam *p = (MonoGSharedGenericParam*)data;
  	guint hash;
  
  	hash = mono_metadata_generic_param_hash (p->parent);
  	hash = ((hash << 5) - hash) ^ mono_metadata_type_hash (p->param.gshared_constraint);
  
  	return hash;
  }
  
  static gboolean
  shared_gparam_equal (gconstpointer ka, gconstpointer kb)
  {
  	MonoGSharedGenericParam *p1 = (MonoGSharedGenericParam*)ka;
  	MonoGSharedGenericParam *p2 = (MonoGSharedGenericParam*)kb;
  
  	if (p1 == p2)
  		return TRUE;
  	if (p1->parent != p2->parent)
  		return FALSE;
  	if (!mono_metadata_type_equal (p1->param.gshared_constraint, p2->param.gshared_constraint))
  		return FALSE;
  	return TRUE;
  }
  
  /*
   * mini_get_shared_gparam:
   *
   *   Create an anonymous gparam from T with a constraint which encodes which types can match it.
   */
  MonoType*
  mini_get_shared_gparam (MonoType *t, MonoType *constraint)
  {
  	MonoMemoryManager *mm;
  	MonoGenericParam *par = t->data.generic_param;
  	MonoGSharedGenericParam *copy, key;
  	MonoType *res;
  	char *name;
  
  	mm = mono_mem_manager_merge (mono_metadata_get_mem_manager_for_type (t), mono_metadata_get_mem_manager_for_type (constraint));
  
  	memset (&key, 0, sizeof (key));
  	key.parent = par;
  	key.param.gshared_constraint = constraint;
  
  	g_assert (mono_generic_param_info (par));
  
  	/*
  	 * Need a cache to ensure the newly created gparam
  	 * is unique wrt T/CONSTRAINT.
  	 */
  	mono_mem_manager_lock (mm);
  	if (!mm->gshared_types) {
  		mm->gshared_types_len = MONO_TYPE_INTERNAL;
  		mm->gshared_types = g_new0 (GHashTable*, mm->gshared_types_len);
  	}
  	if (!mm->gshared_types [constraint->type])
  		mm->gshared_types [constraint->type] = g_hash_table_new (shared_gparam_hash, shared_gparam_equal);
  	res = (MonoType *)g_hash_table_lookup (mm->gshared_types [constraint->type], &key);
  	mono_mem_manager_unlock (mm);
  	if (res)
  		return res;
  	copy = (MonoGSharedGenericParam *)mono_mem_manager_alloc0 (mm, sizeof (MonoGSharedGenericParam));
  	memcpy (&copy->param, par, sizeof (MonoGenericParamFull));
  	copy->param.info.pklass = NULL;
  	// FIXME:
  	constraint = mono_metadata_type_dup (NULL, constraint);
  	name = get_shared_gparam_name (constraint->type, ((MonoGenericParamFull*)copy)->info.name);
  	copy->param.info.name = mono_mem_manager_strdup (mm, name);
  	g_free (name);
  
  	copy->param.owner = par->owner;
  	g_assert (!par->owner->is_anonymous);
  
  	copy->param.gshared_constraint = constraint;
  	copy->parent = par;
  	res = mono_metadata_type_dup (NULL, t);
  	res->data.generic_param = (MonoGenericParam*)copy;
  
  	mono_mem_manager_lock (mm);
  	/* Duplicates are ok */
  	g_hash_table_insert (mm->gshared_types [constraint->type], copy, res);
  	mono_mem_manager_unlock (mm);
  
  	return res;
  }
  
  static MonoGenericInst*
  get_shared_inst (MonoGenericInst *inst, MonoGenericInst *shared_inst, MonoGenericContainer *container, gboolean use_gsharedvt);
  
  static MonoType*
  get_shared_type (MonoType *t, MonoType *type)
  {
  	MonoTypeEnum ttype;
  
  	if (!m_type_is_byref (type) && type->type == MONO_TYPE_GENERICINST && MONO_TYPE_ISSTRUCT (type)) {
  		ERROR_DECL (error);
  		MonoGenericClass *gclass = type->data.generic_class;
  		MonoGenericContext context;
  		MonoClass *k;
  
  		memset (&context, 0, sizeof (context));
  		if (gclass->context.class_inst)
  			context.class_inst = get_shared_inst (gclass->context.class_inst, mono_class_get_generic_container (gclass->container_class)->context.class_inst, NULL, FALSE);
  		if (gclass->context.method_inst)
  			context.method_inst = get_shared_inst (gclass->context.method_inst, mono_class_get_generic_container (gclass->container_class)->context.method_inst, NULL, FALSE);
  
  		k = mono_class_inflate_generic_class_checked (gclass->container_class, &context, error);
  		mono_error_assert_ok (error); /* FIXME don't swallow the error */
  
  		return mini_get_shared_gparam (t, m_class_get_byval_arg (k));
  	} else if (MONO_TYPE_ISSTRUCT (type)) {
  		return type;
  	}
  
  	/* Create a type variable with a constraint which encodes which types can match it */
  	ttype = type->type;
  	if (type->type == MONO_TYPE_VALUETYPE) {
  		ttype = mono_class_enum_basetype_internal (type->data.klass)->type;
  	} else if (type->type == MONO_TYPE_GENERICINST && m_class_is_enumtype(type->data.generic_class->container_class)) {
  		ttype = mono_class_enum_basetype_internal (mono_class_from_mono_type_internal (type))->type;
  	} else if (MONO_TYPE_IS_REFERENCE (type)) {
  		ttype = MONO_TYPE_OBJECT;
  	} else if (type->type == MONO_TYPE_VAR || type->type == MONO_TYPE_MVAR) {
  		if (type->data.generic_param->gshared_constraint)
  			return mini_get_shared_gparam (t, type->data.generic_param->gshared_constraint);
  		ttype = MONO_TYPE_OBJECT;
  	}
  
  	{
  		MonoType t2;
  		MonoClass *klass;
  
  		memset (&t2, 0, sizeof (t2));
  		t2.type = ttype;
  		klass = mono_class_from_mono_type_internal (&t2);
  
  		return mini_get_shared_gparam (t, m_class_get_byval_arg (klass));
  	}
  }
  
  static MonoType*
  get_gsharedvt_type (MonoType *t)
  {
  	/* Use TypeHandle as the constraint type since its a valuetype */
  	return mini_get_shared_gparam (t, m_class_get_byval_arg (mono_defaults.typehandle_class));
  }
  
  static MonoGenericInst*
  get_shared_inst (MonoGenericInst *inst, MonoGenericInst *shared_inst, MonoGenericContainer *container, gboolean use_gsharedvt)
  {
  	MonoGenericInst *res;
  	MonoType **type_argv;
  	int i;
  
  	type_argv = g_new0 (MonoType*, inst->type_argc);
  	for (i = 0; i < inst->type_argc; ++i) {
  		if (use_gsharedvt) {
  			type_argv [i] = get_gsharedvt_type (shared_inst->type_argv [i]);
  		} else {
  			/* These types match the ones in mini_generic_inst_is_sharable () */
  			type_argv [i] = get_shared_type (shared_inst->type_argv [i], inst->type_argv [i]);
  		}
  	}
  
  	res = mono_metadata_get_generic_inst (inst->type_argc, type_argv);
  	g_free (type_argv);
  	return res;
  }
  
  /**
   * mini_get_shared_method_full:
   * \param method the method to find the shared version of.
   * \param flags controls what sort of shared version to find
   * \param error set if we hit any fatal error
   *
   * \returns The method which is actually compiled/registered when doing generic sharing.
  
   * If flags & SHARE_MODE_GSHAREDVT, produce a method using the gsharedvt instantiation.
   * \p method can be a non-inflated generic method.
   */
  MonoMethod*
  mini_get_shared_method_full (MonoMethod *method, GetSharedMethodFlags flags, MonoError *error)
  {
  
  	MonoGenericContext shared_context;
  	MonoMethod *declaring_method;
  	MonoGenericContainer *class_container, *method_container = NULL;
  	MonoGenericContext *context = mono_method_get_context (method);
  	MonoGenericInst *inst;
  	WrapperInfo *info = NULL;
  
  	error_init (error);
  
  	/*
  	 * Instead of creating a shared version of the wrapper, create a shared version of the original
  	 * method and construct a wrapper for it. Otherwise, we could end up with two copies of the
  	 * same wrapper, breaking AOT which assumes wrappers are unique.
  	 * FIXME: Add other cases.
  	 */
  	if (method->wrapper_type)
  		info = mono_marshal_get_wrapper_info (method);
  	switch (method->wrapper_type) {
  	case MONO_WRAPPER_SYNCHRONIZED: {
  		MonoMethod *wrapper = mono_marshal_method_from_wrapper (method);
  
  		MonoMethod *gwrapper = mini_get_shared_method_full (wrapper, flags, error);
  		return_val_if_nok (error, NULL);
  
  		return mono_marshal_get_synchronized_wrapper (gwrapper);
  	}
  	case MONO_WRAPPER_DELEGATE_INVOKE: {
  		if (info->subtype == WRAPPER_SUBTYPE_NONE) {
  			MonoMethod *ginvoke = mini_get_shared_method_full (info->d.delegate_invoke.method, flags, error);
  			return_val_if_nok (error, NULL);
  
  			return mono_marshal_get_delegate_invoke (ginvoke, NULL);
  		}
  		break;
  	}
  	case MONO_WRAPPER_DELEGATE_BEGIN_INVOKE:
  	case MONO_WRAPPER_DELEGATE_END_INVOKE: {
  		MonoMethod *ginvoke = mini_get_shared_method_full (info->d.delegate_invoke.method, flags, error);
  		return_val_if_nok (error, NULL);
  
  		if (method->wrapper_type == MONO_WRAPPER_DELEGATE_BEGIN_INVOKE)
  			return mono_marshal_get_delegate_begin_invoke (ginvoke);
  		else
  			return mono_marshal_get_delegate_end_invoke (ginvoke);
  	}
  	default:
  		break;
  	}
  
  	if (method->is_generic || (mono_class_is_gtd (method->klass) && !method->is_inflated)) {
  		declaring_method = method;
  	} else {
  		declaring_method = mono_method_get_declaring_generic_method (method);
  	}
  
  	/* shared_context is the context containing type variables. */
  	if (declaring_method->is_generic)
  		shared_context = mono_method_get_generic_container (declaring_method)->context;
  	else
  		shared_context = mono_class_get_generic_container (declaring_method->klass)->context;
  
  	gboolean use_gsharedvt_inst = FALSE;
  	if (flags & SHARE_MODE_GSHAREDVT)
  		use_gsharedvt_inst = TRUE;
  	else if (!mono_method_is_generic_sharable_full (method, FALSE, TRUE, FALSE))
  		use_gsharedvt_inst = mini_is_gsharedvt_sharable_method (method);
  
  	class_container = mono_class_try_get_generic_container (declaring_method->klass); //FIXME is this a case for a try_get?
  	method_container = mono_method_get_generic_container (declaring_method);
  
  	/*
  	 * Create the shared context by replacing the ref type arguments with
  	 * type parameters, and keeping the rest.
  	 */
  	if (context)
  		inst = context->class_inst;
  	else
  		inst = shared_context.class_inst;
  	if (inst)
  		shared_context.class_inst = get_shared_inst (inst, shared_context.class_inst, class_container, use_gsharedvt_inst);
  
  	if (context)
  		inst = context->method_inst;
  	else
  		inst = shared_context.method_inst;
  	if (inst)
  		shared_context.method_inst = get_shared_inst (inst, shared_context.method_inst, method_container, use_gsharedvt_inst);
  
  	return mono_class_inflate_generic_method_checked (declaring_method, &shared_context, error);
  }
  
  int
  mini_get_rgctx_entry_slot (MonoJumpInfoRgctxEntry *entry)
  {
  	gpointer entry_data = NULL;
  	gboolean did_register = FALSE;
  	guint32 result = -1;
  
  	switch (entry->data->type) {
  	case MONO_PATCH_INFO_CLASS:
  		entry_data = m_class_get_byval_arg (entry->data->data.klass);
  		break;
  	case MONO_PATCH_INFO_METHOD:
  	case MONO_PATCH_INFO_METHODCONST:
  		entry_data = entry->data->data.method;
  		break;
  	case MONO_PATCH_INFO_FIELD:
  		entry_data = entry->data->data.field;
  		break;
  	case MONO_PATCH_INFO_SIGNATURE:
  		entry_data = entry->data->data.sig;
  		break;
  	case MONO_PATCH_INFO_GSHAREDVT_CALL: {
  		// FIXME:
  		MonoJumpInfoGSharedVtCall *call_info = (MonoJumpInfoGSharedVtCall *)g_malloc0 (sizeof (MonoJumpInfoGSharedVtCall));
  
  		memcpy (call_info, entry->data->data.gsharedvt, sizeof (MonoJumpInfoGSharedVtCall));
  		entry_data = call_info;
  		break;
  	}
  	case MONO_PATCH_INFO_GSHAREDVT_METHOD: {
  		MonoGSharedVtMethodInfo *info;
  		MonoGSharedVtMethodInfo *oinfo = entry->data->data.gsharedvt_method;
  		int i;
  
  		// FIXME:
  		info = (MonoGSharedVtMethodInfo *)g_malloc0 (sizeof (MonoGSharedVtMethodInfo));
  		info->method = oinfo->method;
  		info->num_entries = oinfo->num_entries;
  		info->entries = (MonoRuntimeGenericContextInfoTemplate *)g_malloc0 (sizeof (MonoRuntimeGenericContextInfoTemplate) * info->num_entries);
  		for (i = 0; i < oinfo->num_entries; ++i) {
  			MonoRuntimeGenericContextInfoTemplate *otemplate = &oinfo->entries [i];
  			MonoRuntimeGenericContextInfoTemplate *template_ = &info->entries [i];
  
  			memcpy (template_, otemplate, sizeof (MonoRuntimeGenericContextInfoTemplate));
  		}
  		entry_data = info;
  		break;
  	}
  	case MONO_PATCH_INFO_VIRT_METHOD: {
  		MonoJumpInfoVirtMethod *info;
  		MonoJumpInfoVirtMethod *oinfo = entry->data->data.virt_method;
  
  		info = (MonoJumpInfoVirtMethod *)g_malloc0 (sizeof (MonoJumpInfoVirtMethod));
  		memcpy (info, oinfo, sizeof (MonoJumpInfoVirtMethod));
  		entry_data = info;
  		break;
  	}
  	case MONO_PATCH_INFO_DELEGATE_TRAMPOLINE: {
  		MonoDelegateClassMethodPair *info;
  		MonoDelegateClassMethodPair *oinfo = entry->data->data.del_tramp;
  
  		info = (MonoDelegateClassMethodPair *)g_malloc0 (sizeof (MonoDelegateClassMethodPair));
  		memcpy (info, oinfo, sizeof (MonoDelegateClassMethodPair));
  		entry_data = info;
  		break;
  	}
  	default:
  		g_assert_not_reached ();
  	case MONO_PATCH_INFO_NONE:
  		break;
  	}
  
  	// FIXME:
  	MonoJitMemoryManager *jit_mm = get_default_jit_mm ();
  
  	if (entry->in_mrgctx)
  		result = lookup_or_register_info (jit_mm->mem_manager, entry->d.method->klass, entry->d.method, entry->in_mrgctx, entry_data, &did_register, entry->info_type, mono_method_get_context (entry->d.method));
  	else
  		result = lookup_or_register_info (jit_mm->mem_manager, entry->d.klass, NULL, entry->in_mrgctx, entry_data, &did_register, entry->info_type, mono_class_get_context (entry->d.klass));
  
  	if (!did_register)
  		switch (entry->data->type) {
  		case MONO_PATCH_INFO_GSHAREDVT_CALL:
  		case MONO_PATCH_INFO_VIRT_METHOD:
  		case MONO_PATCH_INFO_DELEGATE_TRAMPOLINE:
  			g_free (entry_data);
  			break;
  		case MONO_PATCH_INFO_GSHAREDVT_METHOD: {
  			g_free (((MonoGSharedVtMethodInfo *) entry_data)->entries);
  			g_free (entry_data);
  			break;
  		}
  		default :
  			break;
  		}
  
  	return result;
  }
  
  static gboolean gsharedvt_supported;
  
  void
  mono_set_generic_sharing_vt_supported (gboolean supported)
  {
  	/* ensure we do not disable gsharedvt once it's been enabled */
  	if (!gsharedvt_supported  && supported)
  		gsharedvt_supported = TRUE;
  }
  
  #ifdef MONO_ARCH_GSHAREDVT_SUPPORTED
  
  /*
   * mini_is_gsharedvt_type:
   *
   *   Return whenever T references type arguments instantiated with gshared vtypes.
   */
  gboolean
  mini_is_gsharedvt_type (MonoType *t)
  {
  	int i;
  
  	if (m_type_is_byref (t))
  		return FALSE;
  	if ((t->type == MONO_TYPE_VAR || t->type == MONO_TYPE_MVAR) && t->data.generic_param->gshared_constraint && t->data.generic_param->gshared_constraint->type == MONO_TYPE_VALUETYPE)
  		return TRUE;
  	else if (t->type == MONO_TYPE_GENERICINST) {
  		MonoGenericClass *gclass = t->data.generic_class;
  		MonoGenericContext *context = &gclass->context;
  		MonoGenericInst *inst;
  
  		inst = context->class_inst;
  		if (inst) {
  			for (i = 0; i < inst->type_argc; ++i)
  				if (mini_is_gsharedvt_type (inst->type_argv [i]))
  					return TRUE;
  		}
  		inst = context->method_inst;
  		if (inst) {
  			for (i = 0; i < inst->type_argc; ++i)
  				if (mini_is_gsharedvt_type (inst->type_argv [i]))
  					return TRUE;
  		}
  
  		return FALSE;
  	} else {
  		return FALSE;
  	}
  }
  
  gboolean
  mini_is_gsharedvt_klass (MonoClass *klass)
  {
  	return mini_is_gsharedvt_type (m_class_get_byval_arg (klass));
  }
  
  gboolean
  mini_is_gsharedvt_signature (MonoMethodSignature *sig)
  {
  	int i;
  
  	if (sig->ret && mini_is_gsharedvt_type (sig->ret))
  		return TRUE;
  	for (i = 0; i < sig->param_count; ++i) {
  		if (mini_is_gsharedvt_type (sig->params [i]))
  			return TRUE;
  	}
  	return FALSE;
  }
  
  /*
   * mini_is_gsharedvt_variable_type:
   *
   *   Return whenever T refers to a GSHAREDVT type whose size differs depending on the values of type parameters.
   */
  gboolean
  mini_is_gsharedvt_variable_type (MonoType *t)
  {
  	if (!mini_is_gsharedvt_type (t))
  		return FALSE;
  	if (t->type == MONO_TYPE_GENERICINST) {
  		MonoGenericClass *gclass = t->data.generic_class;
  		MonoGenericContext *context = &gclass->context;
  		MonoGenericInst *inst;
  		int i;
  
  		if (m_class_get_byval_arg (t->data.generic_class->container_class)->type != MONO_TYPE_VALUETYPE || m_class_is_enumtype  (t->data.generic_class->container_class))
  			return FALSE;
  
  		inst = context->class_inst;
  		if (inst) {
  			for (i = 0; i < inst->type_argc; ++i)
  				if (mini_is_gsharedvt_variable_type (inst->type_argv [i]))
  					return TRUE;
  		}
  		inst = context->method_inst;
  		if (inst) {
  			for (i = 0; i < inst->type_argc; ++i)
  				if (mini_is_gsharedvt_variable_type (inst->type_argv [i]))
  					return TRUE;
  		}
  
  		return FALSE;
  	}
  	return TRUE;
  }
  
  static gboolean
  is_variable_size (MonoType *t)
  {
  	int i;
  
  	if (m_type_is_byref (t))
  		return FALSE;
  
  	if (t->type == MONO_TYPE_VAR || t->type == MONO_TYPE_MVAR) {
  		MonoGenericParam *param = t->data.generic_param;
  
  		if (param->gshared_constraint && param->gshared_constraint->type != MONO_TYPE_VALUETYPE && param->gshared_constraint->type != MONO_TYPE_GENERICINST)
  			return FALSE;
  		if (param->gshared_constraint && param->gshared_constraint->type == MONO_TYPE_GENERICINST)
  			return is_variable_size (param->gshared_constraint);
  		return TRUE;
  	}
  	if (t->type == MONO_TYPE_GENERICINST && m_class_get_byval_arg (t->data.generic_class->container_class)->type == MONO_TYPE_VALUETYPE) {
  		MonoGenericClass *gclass = t->data.generic_class;
  		MonoGenericContext *context = &gclass->context;
  		MonoGenericInst *inst;
  
  		inst = context->class_inst;
  		if (inst) {
  			for (i = 0; i < inst->type_argc; ++i)
  				if (is_variable_size (inst->type_argv [i]))
  					return TRUE;
  		}
  		inst = context->method_inst;
  		if (inst) {
  			for (i = 0; i < inst->type_argc; ++i)
  				if (is_variable_size (inst->type_argv [i]))
  					return TRUE;
  		}
  	}
  
  	return FALSE;
  }
  
  gboolean
  mini_is_gsharedvt_sharable_inst (MonoGenericInst *inst)
  {
  	int i;
  	gboolean has_vt = FALSE;
  
  	for (i = 0; i < inst->type_argc; ++i) {
  		MonoType *type = inst->type_argv [i];
  
  		if ((MONO_TYPE_IS_REFERENCE (type) || type->type == MONO_TYPE_VAR || type->type == MONO_TYPE_MVAR) && !mini_is_gsharedvt_type (type)) {
  		} else {
  			has_vt = TRUE;
  		}
  	}
  
  	return has_vt;
  }
  
  gboolean
  mini_is_gsharedvt_inst (MonoGenericInst *inst)
  {
  	int i;
  
  	for (i = 0; i < inst->type_argc; ++i) {
  		MonoType *type = inst->type_argv [i];
  
  		if (mini_is_gsharedvt_type (type))
  			return TRUE;
  	}
  
  	return FALSE;
  }
  
  gboolean
  mini_is_gsharedvt_sharable_method (MonoMethod *method)
  {
  	MonoMethodSignature *sig;
  
  	/*
  	 * A method is gsharedvt if:
  	 * - it has type parameters instantiated with vtypes
  	 */
  	if (!gsharedvt_supported)
  		return FALSE;
  	if (method->is_inflated) {
  		MonoMethodInflated *inflated = (MonoMethodInflated*)method;
  		MonoGenericContext *context = &inflated->context;
  		MonoGenericInst *inst;
  
  		if (context->class_inst && context->method_inst) {
  			/* At least one inst has to be gsharedvt sharable, and the other normal or gsharedvt sharable */
  			gboolean vt1 = mini_is_gsharedvt_sharable_inst (context->class_inst);
  			gboolean vt2 = mini_is_gsharedvt_sharable_inst (context->method_inst);
  
  			if ((vt1 && vt2) ||
  				(vt1 && mini_generic_inst_is_sharable (context->method_inst, TRUE, FALSE)) ||
  				(vt2 && mini_generic_inst_is_sharable (context->class_inst, TRUE, FALSE)))
  				;
  			else
  				return FALSE;
  		} else {
  			inst = context->class_inst;
  			if (inst && !mini_is_gsharedvt_sharable_inst (inst))
  				return FALSE;
  			inst = context->method_inst;
  			if (inst && !mini_is_gsharedvt_sharable_inst (inst))
  				return FALSE;
  		}
  	} else {
  		return FALSE;
  	}
  
  	sig = mono_method_signature_internal (mono_method_get_declaring_generic_method (method));
  	if (!sig)
  		return FALSE;
  
  	/*
  	if (mini_is_gsharedvt_variable_signature (sig))
  		return FALSE;
  	*/
  
  	//DEBUG ("GSHAREDVT SHARABLE: %s\n", mono_method_full_name (method, TRUE));
  
  	return TRUE;
  }
  
  /*
   * mini_is_gsharedvt_variable_signature:
   *
   *   Return whenever the calling convention used to call SIG varies depending on the values of type parameters used by SIG,
   * i.e. FALSE for swap(T[] arr, int i, int j), TRUE for T get_t ().
   */
  gboolean
  mini_is_gsharedvt_variable_signature (MonoMethodSignature *sig)
  {
  	int i;
  
  	if (sig->ret && is_variable_size (sig->ret))
  		return TRUE;
  	for (i = 0; i < sig->param_count; ++i) {
  		MonoType *t = sig->params [i];
  
  		if (is_variable_size (t))
  			return TRUE;
  	}
  	return FALSE;
  }
  
  MonoMethod*
  mini_method_to_shared (MonoMethod *method)
  {
  	if (!mono_method_is_generic_impl (method))
  		return NULL;
  
  	ERROR_DECL (error);
  
  	// This pattern is based on add_extra_method_with_depth.
  
  	if (mono_method_is_generic_sharable_full (method, TRUE, TRUE, FALSE))
  		// gshared over reference type
  		method = mini_get_shared_method_full (method, SHARE_MODE_NONE, error);
  	else if (mono_method_is_generic_sharable_full (method, FALSE, FALSE, TRUE))
  		// gshared over valuetype (or primitive?)
  		method = mini_get_shared_method_full (method, SHARE_MODE_GSHAREDVT, error);
  	else
  		return NULL;
  	mono_error_assert_ok (error);
  	return method;
  }
  
  #else
  
  gboolean
  mini_is_gsharedvt_type (MonoType *t)
  {
  	return FALSE;
  }
  
  gboolean
  mini_is_gsharedvt_klass (MonoClass *klass)
  {
  	return FALSE;
  }
  
  gboolean
  mini_is_gsharedvt_signature (MonoMethodSignature *sig)
  {
  	return FALSE;
  }
  
  gboolean
  mini_is_gsharedvt_variable_type (MonoType *t)
  {
  	return FALSE;
  }
  
  gboolean
  mini_is_gsharedvt_sharable_method (MonoMethod *method)
  {
  	return FALSE;
  }
  
  gboolean
  mini_is_gsharedvt_variable_signature (MonoMethodSignature *sig)
  {
  	return FALSE;
  }
  
  MonoMethod*
  mini_method_to_shared (MonoMethod *method)
  {
  	return NULL;
  }
  
  #endif /* !MONO_ARCH_GSHAREDVT_SUPPORTED */
  