From 106098f7181daa1b1dd1b215089632b220ce07c1 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aleksey=20Kliger=20=28=CE=BBgeek=29?= Date: Tue, 5 Jan 2021 21:29:58 -0500 Subject: [PATCH] [Mono][jit] Emit GC transitions for "calli unmanaged" instructions (#46491) * [metadata] Treat CallConv bit 0x09 as MONO_CALL_UNMANAGED_MD This is the C#9 function pointers "unmanaged"+ext calling convention. Additional calling convention details are encoded in the modopts of the return type. This PR doesn't handle the modopts yet * [marshal] Add mono_marshal_get_native_func_wrapper_indirect This will be used to add a wrapper around "calli sig" indirect calls where "sig" has an unmanaged calling convention. We need to do a GC transition before calling an unmanaged function from managed. So this wrapper does it. This is reusing much of the code implemented for mono_marshal_get_native_func_wrapper_aot which is used for delegates. Unfortunately that means that the function pointer is (pointlessly) boxed when it is passed as the first argument. * [jit] Use a wrapper for "calli unmanaged" instructions If there's a calli with an unmanaged signature, invoke a wrapper that does a transition to GC Safe mode and then do the call. The wrapper is always inlined into the callee. Because we're reusing much of the code of mono_marshal_get_native_func_wrapper_aot, the function pointer first has to be boxed before it's passed to the wrapper. In theory we should be able to just pass it directly and get much simpler code. But that will require changing the code in emit_native_wrapper_ilgen to get an unboxed function pointer arg * fixup don't emit GC transitions in runtime invoke wrapper * fixup don't emit two wrappers on dynamic methods * check that calli wrapper only gets blittable args * negate logic for when to add an indirection wrapper assume that if the callee is a wrapper, it doesn't need the transition, and only add it to normal managed methods. * add disabled debug printf * Add MonoNativeWrapperFlags arg to emit_native_wrapper Replace the 4 boolean args by a flags arg. Also add a new EMIT_NATIVE_WRAPPER_FUNC_PARAM_UNBOXED and use it in mono_marshal_get_native_func_wrapper_indirect. The new flag has no effect yet. * Address review feedback * Use an unboxed func ptr mono_marshal_get_native_func_wrapper_indirect Add support for the EMIT_NATIVE_WRAPPER_FUNC_PARAM_UNBOXED flag to mono_marshal_emit_native_wrapper --- src/mono/mono/metadata/cominterop.c | 2 +- src/mono/mono/metadata/image.c | 1 + src/mono/mono/metadata/marshal-ilgen.c | 16 ++++-- src/mono/mono/metadata/marshal-noilgen.c | 2 +- src/mono/mono/metadata/marshal.c | 88 +++++++++++++++++++++++++++-- src/mono/mono/metadata/marshal.h | 21 ++++++- src/mono/mono/metadata/metadata-internals.h | 1 + src/mono/mono/metadata/metadata.c | 1 + src/mono/mono/metadata/metadata.h | 6 +- src/mono/mono/mini/method-to-ir.c | 47 +++++++++++++++ 10 files changed, 170 insertions(+), 15 deletions(-) diff --git a/src/mono/mono/metadata/cominterop.c b/src/mono/mono/metadata/cominterop.c index 4c46107..e80cca9 100644 --- a/src/mono/mono/metadata/cominterop.c +++ b/src/mono/mono/metadata/cominterop.c @@ -1023,7 +1023,7 @@ cominterop_get_native_wrapper_adjusted (MonoMethod *method) } } - mono_marshal_emit_native_wrapper (m_class_get_image (method->klass), mb_native, sig_native, piinfo, mspecs, piinfo->addr, FALSE, TRUE, FALSE, FALSE); + mono_marshal_emit_native_wrapper (m_class_get_image (method->klass), mb_native, sig_native, piinfo, mspecs, piinfo->addr, EMIT_NATIVE_WRAPPER_CHECK_EXCEPTIONS); res = mono_mb_create_method (mb_native, sig_native, sig_native->param_count + 16); diff --git a/src/mono/mono/metadata/image.c b/src/mono/mono/metadata/image.c index 79d3921..fe5ec7d 100644 --- a/src/mono/mono/metadata/image.c +++ b/src/mono/mono/metadata/image.c @@ -2493,6 +2493,7 @@ mono_wrapper_caches_free (MonoWrapperCaches *cache) free_hash (cache->native_wrapper_aot_check_cache); free_hash (cache->native_func_wrapper_aot_cache); + free_hash (cache->native_func_wrapper_indirect_cache); free_hash (cache->remoting_invoke_cache); free_hash (cache->synchronized_cache); free_hash (cache->unbox_wrapper_cache); diff --git a/src/mono/mono/metadata/marshal-ilgen.c b/src/mono/mono/metadata/marshal-ilgen.c index 785556b..8146753 100644 --- a/src/mono/mono/metadata/marshal-ilgen.c +++ b/src/mono/mono/metadata/marshal-ilgen.c @@ -1995,13 +1995,19 @@ gc_safe_transition_builder_cleanup (GCSafeTransitionBuilder *builder) * \param method if non-NULL, the pinvoke method to call * \param check_exceptions Whenever to check for pending exceptions after the native call * \param func_param the function to call is passed as a boxed IntPtr as the first parameter + * \param func_param_unboxed combined with \p func_param, expect the function to call as an unboxed IntPtr as the first parameter * \param skip_gc_trans Whenever to skip GC transitions * * generates IL code for the pinvoke wrapper, the generated code calls \p func . */ static void -emit_native_wrapper_ilgen (MonoImage *image, MonoMethodBuilder *mb, MonoMethodSignature *sig, MonoMethodPInvoke *piinfo, MonoMarshalSpec **mspecs, gpointer func, gboolean aot, gboolean check_exceptions, gboolean func_param, gboolean skip_gc_trans) +emit_native_wrapper_ilgen (MonoImage *image, MonoMethodBuilder *mb, MonoMethodSignature *sig, MonoMethodPInvoke *piinfo, MonoMarshalSpec **mspecs, gpointer func, MonoNativeWrapperFlags flags) { + gboolean aot = (flags & EMIT_NATIVE_WRAPPER_AOT) != 0; + gboolean check_exceptions = (flags & EMIT_NATIVE_WRAPPER_CHECK_EXCEPTIONS) != 0; + gboolean func_param = (flags & EMIT_NATIVE_WRAPPER_FUNC_PARAM) != 0; + gboolean func_param_unboxed = (flags & EMIT_NATIVE_WRAPPER_FUNC_PARAM_UNBOXED) != 0; + gboolean skip_gc_trans = (flags & EMIT_NATIVE_WRAPPER_SKIP_GC_TRANS) != 0; EmitMarshalContext m; MonoMethodSignature *csig; MonoClass *klass; @@ -2139,9 +2145,11 @@ emit_native_wrapper_ilgen (MonoImage *image, MonoMethodBuilder *mb, MonoMethodSi /* call the native method */ if (func_param) { mono_mb_emit_byte (mb, CEE_LDARG_0); - mono_mb_emit_op (mb, CEE_UNBOX, mono_defaults.int_class); - mono_mb_emit_byte (mb, CEE_LDIND_I); - if (piinfo->piflags & PINVOKE_ATTRIBUTE_SUPPORTS_LAST_ERROR) { + if (!func_param_unboxed) { + mono_mb_emit_op (mb, CEE_UNBOX, mono_defaults.int_class); + mono_mb_emit_byte (mb, CEE_LDIND_I); + } + if (piinfo && (piinfo->piflags & PINVOKE_ATTRIBUTE_SUPPORTS_LAST_ERROR) != 0) { mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX); mono_mb_emit_byte (mb, CEE_MONO_SAVE_LAST_ERROR); } diff --git a/src/mono/mono/metadata/marshal-noilgen.c b/src/mono/mono/metadata/marshal-noilgen.c index 6555604..cb79e2f 100644 --- a/src/mono/mono/metadata/marshal-noilgen.c +++ b/src/mono/mono/metadata/marshal-noilgen.c @@ -350,7 +350,7 @@ emit_thunk_invoke_wrapper_noilgen (MonoMethodBuilder *mb, MonoMethod *method, Mo } static void -emit_native_wrapper_noilgen (MonoImage *image, MonoMethodBuilder *mb, MonoMethodSignature *sig, MonoMethodPInvoke *piinfo, MonoMarshalSpec **mspecs, gpointer func, gboolean aot, gboolean check_exceptions, gboolean func_param, gboolean skip_gc_trans) +emit_native_wrapper_noilgen (MonoImage *image, MonoMethodBuilder *mb, MonoMethodSignature *sig, MonoMethodPInvoke *piinfo, MonoMarshalSpec **mspecs, gpointer func, MonoNativeWrapperFlags flags) { } diff --git a/src/mono/mono/metadata/marshal.c b/src/mono/mono/metadata/marshal.c index cc7cd72..f0a04f4 100644 --- a/src/mono/mono/metadata/marshal.c +++ b/src/mono/mono/metadata/marshal.c @@ -116,6 +116,8 @@ static GENERATE_TRY_GET_CLASS_WITH_CACHE (suppress_gc_transition_attribute, "Sys static GENERATE_TRY_GET_CLASS_WITH_CACHE (unmanaged_callers_only_attribute, "System.Runtime.InteropServices", "UnmanagedCallersOnlyAttribute") #endif +static gboolean type_is_blittable (MonoType *type); + static MonoImage* get_method_image (MonoMethod *method) { @@ -3518,7 +3520,11 @@ mono_marshal_get_native_wrapper (MonoMethod *method, gboolean check_exceptions, } #endif - mono_marshal_emit_native_wrapper (get_method_image (mb->method), mb, csig, piinfo, mspecs, piinfo->addr, aot, check_exceptions, FALSE, skip_gc_trans); + MonoNativeWrapperFlags flags = aot ? EMIT_NATIVE_WRAPPER_AOT : (MonoNativeWrapperFlags)0; + flags |= check_exceptions ? EMIT_NATIVE_WRAPPER_CHECK_EXCEPTIONS : (MonoNativeWrapperFlags)0; + flags |= skip_gc_trans ? EMIT_NATIVE_WRAPPER_SKIP_GC_TRANS : (MonoNativeWrapperFlags)0; + + mono_marshal_emit_native_wrapper (get_method_image (mb->method), mb, csig, piinfo, mspecs, piinfo->addr, flags); info = mono_wrapper_info_create (mb, WRAPPER_SUBTYPE_PINVOKE); info->d.managed_to_native.method = method; @@ -3573,7 +3579,7 @@ mono_marshal_get_native_func_wrapper (MonoImage *image, MonoMethodSignature *sig mb = mono_mb_new (mono_defaults.object_class, name, MONO_WRAPPER_MANAGED_TO_NATIVE); mb->method->save_lmf = 1; - mono_marshal_emit_native_wrapper (image, mb, sig, piinfo, mspecs, func, FALSE, TRUE, FALSE, FALSE); + mono_marshal_emit_native_wrapper (image, mb, sig, piinfo, mspecs, func, EMIT_NATIVE_WRAPPER_CHECK_EXCEPTIONS); csig = mono_metadata_signature_dup_full (image, sig); csig->pinvoke = 0; @@ -3636,7 +3642,7 @@ mono_marshal_get_native_func_wrapper_aot (MonoClass *klass) mb = mono_mb_new (invoke->klass, name, MONO_WRAPPER_MANAGED_TO_NATIVE); mb->method->save_lmf = 1; - mono_marshal_emit_native_wrapper (image, mb, sig, piinfo, mspecs, NULL, FALSE, TRUE, TRUE, FALSE); + mono_marshal_emit_native_wrapper (image, mb, sig, piinfo, mspecs, NULL, EMIT_NATIVE_WRAPPER_CHECK_EXCEPTIONS | EMIT_NATIVE_WRAPPER_FUNC_PARAM); info = mono_wrapper_info_create (mb, WRAPPER_SUBTYPE_NATIVE_FUNC_AOT); info->d.managed_to_native.method = invoke; @@ -3659,6 +3665,78 @@ mono_marshal_get_native_func_wrapper_aot (MonoClass *klass) } /* + * Gets a wrapper for an indirect call to a function with the given signature. + * The actual function is passed as the first argument to the wrapper. + * + * The wrapper is + * + * retType wrapper (fnPtr, arg1... argN) { + * enter_gc_safe; + * ret = fnPtr (arg1, ... argN); + * exit_gc_safe; + * return ret; + * } + * + */ +MonoMethod* +mono_marshal_get_native_func_wrapper_indirect (MonoClass *caller_class, MonoMethodSignature *sig, + gboolean aot) +{ + caller_class = mono_class_get_generic_type_definition (caller_class); + MonoImage *image = m_class_get_image (caller_class); + g_assert (sig->pinvoke); + g_assert (!sig->hasthis && ! sig->explicit_this); + g_assert (!sig->is_inflated && !sig->has_type_parameters); + + g_assertf (type_is_blittable (sig->ret), "sig return type %s is not blittable\n", mono_type_full_name (sig->ret)); + + for (int i = 0; i < sig->param_count; ++i) { + MonoType *ty = sig->params [i]; + g_assertf (type_is_blittable (ty), "sig param %d (type %s) is not blittable\n", i, mono_type_full_name (ty)); + } + /* g_assert (every param and return type is blittable) */ + + GHashTable *cache = get_cache (&image->wrapper_caches.native_func_wrapper_indirect_cache, + (GHashFunc)mono_signature_hash, + (GCompareFunc)mono_metadata_signature_equal); + + MonoMethod *res; + if ((res = mono_marshal_find_in_cache (cache, sig))) + return res; + +#if 0 + fprintf (stderr, "generating wrapper for signature %s\n", mono_signature_full_name (sig)); +#endif + + /* FIXME: better wrapper name */ + char * name = g_strdup_printf ("wrapper_native_indirect_%p", sig); + MonoMethodBuilder *mb = mono_mb_new (caller_class, name, MONO_WRAPPER_MANAGED_TO_NATIVE); + mb->method->save_lmf = 1; + + WrapperInfo *info = mono_wrapper_info_create (mb, WRAPPER_SUBTYPE_NATIVE_FUNC_INDIRECT); + info->d.managed_to_native.method = NULL; + + MonoMethodPInvoke *piinfo = NULL; + MonoMarshalSpec **mspecs = g_new0 (MonoMarshalSpec *, 1 + sig->param_count); + MonoNativeWrapperFlags flags = aot ? EMIT_NATIVE_WRAPPER_AOT : (MonoNativeWrapperFlags)0; + flags |= EMIT_NATIVE_WRAPPER_FUNC_PARAM | EMIT_NATIVE_WRAPPER_FUNC_PARAM_UNBOXED; + mono_marshal_emit_native_wrapper (image, mb, sig, piinfo, mspecs, /*func*/NULL, flags); + g_free (mspecs); + + MonoMethodSignature *csig = mono_metadata_signature_dup_add_this (image, sig, mono_defaults.int_class); + csig->pinvoke = 0; + + MonoMethodSignature *key_sig = mono_metadata_signature_dup_full (image, sig); + + gboolean found; + res = mono_mb_create_and_cache_full (cache, key_sig, mb, csig, csig->param_count + 16, info, &found); + + mono_mb_free (mb); + + return res; +} + +/* * mono_marshal_emit_managed_wrapper: * * Emit the body of a native-to-managed wrapper. INVOKE_SIG is the signature of @@ -6383,9 +6461,9 @@ mono_marshal_lookup_pinvoke (MonoMethod *method) } void -mono_marshal_emit_native_wrapper (MonoImage *image, MonoMethodBuilder *mb, MonoMethodSignature *sig, MonoMethodPInvoke *piinfo, MonoMarshalSpec **mspecs, gpointer func, gboolean aot, gboolean check_exceptions, gboolean func_param, gboolean skip_gc_trans) +mono_marshal_emit_native_wrapper (MonoImage *image, MonoMethodBuilder *mb, MonoMethodSignature *sig, MonoMethodPInvoke *piinfo, MonoMarshalSpec **mspecs, gpointer func, MonoNativeWrapperFlags flags) { - get_marshal_cb ()->emit_native_wrapper (image, mb, sig, piinfo, mspecs, func, aot, check_exceptions, func_param, skip_gc_trans); + get_marshal_cb ()->emit_native_wrapper (image, mb, sig, piinfo, mspecs, func, flags); } static MonoMarshalCallbacks marshal_cb; diff --git a/src/mono/mono/metadata/marshal.h b/src/mono/mono/metadata/marshal.h index b8afb83..ebb61e4 100644 --- a/src/mono/mono/metadata/marshal.h +++ b/src/mono/mono/metadata/marshal.h @@ -115,6 +115,7 @@ typedef enum { /* Subtypes of MONO_WRAPPER_MANAGED_TO_NATIVE */ WRAPPER_SUBTYPE_ICALL_WRAPPER, // specifically JIT icalls WRAPPER_SUBTYPE_NATIVE_FUNC_AOT, + WRAPPER_SUBTYPE_NATIVE_FUNC_INDIRECT, WRAPPER_SUBTYPE_PINVOKE, /* Subtypes of MONO_WRAPPER_OTHER */ WRAPPER_SUBTYPE_SYNCHRONIZED_INNER, @@ -300,7 +301,17 @@ typedef enum { } MonoStelemrefKind; -#define MONO_MARSHAL_CALLBACKS_VERSION 4 +typedef enum { + EMIT_NATIVE_WRAPPER_AOT = 0x01, /* FIXME: what does "aot" mean here */ + EMIT_NATIVE_WRAPPER_CHECK_EXCEPTIONS = 0x02, + EMIT_NATIVE_WRAPPER_FUNC_PARAM = 0x04, + EMIT_NATIVE_WRAPPER_FUNC_PARAM_UNBOXED = 0x08, + EMIT_NATIVE_WRAPPER_SKIP_GC_TRANS=0x10, +} MonoNativeWrapperFlags; + +G_ENUM_FUNCTIONS(MonoNativeWrapperFlags); + +#define MONO_MARSHAL_CALLBACKS_VERSION 5 typedef struct { int version; @@ -325,7 +336,7 @@ typedef struct { void (*emit_virtual_stelemref) (MonoMethodBuilder *mb, const char **param_names, MonoStelemrefKind kind); void (*emit_stelemref) (MonoMethodBuilder *mb); void (*emit_array_address) (MonoMethodBuilder *mb, int rank, int elem_size); - void (*emit_native_wrapper) (MonoImage *image, MonoMethodBuilder *mb, MonoMethodSignature *sig, MonoMethodPInvoke *piinfo, MonoMarshalSpec **mspecs, gpointer func, gboolean aot, gboolean check_exceptions, gboolean func_param, gboolean skip_gc_trans); + void (*emit_native_wrapper) (MonoImage *image, MonoMethodBuilder *mb, MonoMethodSignature *sig, MonoMethodPInvoke *piinfo, MonoMarshalSpec **mspecs, gpointer func, MonoNativeWrapperFlags flags); void (*emit_managed_wrapper) (MonoMethodBuilder *mb, MonoMethodSignature *invoke_sig, MonoMarshalSpec **mspecs, EmitMarshalContext* m, MonoMethod *method, MonoGCHandle target_handle); void (*emit_runtime_invoke_body) (MonoMethodBuilder *mb, const char **param_names, MonoImage *image, MonoMethod *method, MonoMethodSignature *sig, MonoMethodSignature *callsig, gboolean virtual_, gboolean need_direct_wrapper); void (*emit_runtime_invoke_dynamic) (MonoMethodBuilder *mb); @@ -473,6 +484,10 @@ mono_marshal_get_native_func_wrapper (MonoImage *image, MonoMethodSignature *sig MonoMethod* mono_marshal_get_native_func_wrapper_aot (MonoClass *klass); +MonoMethod* +mono_marshal_get_native_func_wrapper_indirect (MonoClass *caller_class, MonoMethodSignature *sig, + gboolean aot); + MonoMethod * mono_marshal_get_struct_to_ptr (MonoClass *klass); @@ -671,7 +686,7 @@ mono_signature_no_pinvoke (MonoMethod *method); /* Called from cominterop.c/remoting.c */ void -mono_marshal_emit_native_wrapper (MonoImage *image, MonoMethodBuilder *mb, MonoMethodSignature *sig, MonoMethodPInvoke *piinfo, MonoMarshalSpec **mspecs, gpointer func, gboolean aot, gboolean check_exceptions, gboolean func_param, gboolean skip_gc_trans); +mono_marshal_emit_native_wrapper (MonoImage *image, MonoMethodBuilder *mb, MonoMethodSignature *sig, MonoMethodPInvoke *piinfo, MonoMarshalSpec **mspecs, gpointer func, MonoNativeWrapperFlags flags); void mono_marshal_emit_managed_wrapper (MonoMethodBuilder *mb, MonoMethodSignature *invoke_sig, MonoMarshalSpec **mspecs, EmitMarshalContext* m, MonoMethod *method, MonoGCHandle target_handle); diff --git a/src/mono/mono/metadata/metadata-internals.h b/src/mono/mono/metadata/metadata-internals.h index 3dbf9b8..329f839 100644 --- a/src/mono/mono/metadata/metadata-internals.h +++ b/src/mono/mono/metadata/metadata-internals.h @@ -268,6 +268,7 @@ typedef struct { GHashTable *native_wrapper_aot_check_cache; GHashTable *native_func_wrapper_aot_cache; + GHashTable *native_func_wrapper_indirect_cache; /* Indexed by MonoMethodSignature. Protected by the marshal lock */ GHashTable *remoting_invoke_cache; GHashTable *synchronized_cache; GHashTable *unbox_wrapper_cache; diff --git a/src/mono/mono/metadata/metadata.c b/src/mono/mono/metadata/metadata.c index d7a792b..73a6978 100644 --- a/src/mono/mono/metadata/metadata.c +++ b/src/mono/mono/metadata/metadata.c @@ -2355,6 +2355,7 @@ mono_metadata_parse_method_signature_full (MonoImage *m, MonoGenericContainer *c case MONO_CALL_STDCALL: case MONO_CALL_THISCALL: case MONO_CALL_FASTCALL: + case MONO_CALL_UNMANAGED_MD: method->pinvoke = 1; break; } diff --git a/src/mono/mono/metadata/metadata.h b/src/mono/mono/metadata/metadata.h index 965b06a..c44931d 100644 --- a/src/mono/mono/metadata/metadata.h +++ b/src/mono/mono/metadata/metadata.h @@ -37,7 +37,11 @@ typedef enum { MONO_CALL_STDCALL, MONO_CALL_THISCALL, MONO_CALL_FASTCALL, - MONO_CALL_VARARG + MONO_CALL_VARARG = 0x05, + /* unused, */ + /* unused, */ + /* unused, */ + MONO_CALL_UNMANAGED_MD = 0x09, /* default unmanaged calling convention, with additional attributed encoded in modopts */ } MonoCallConvention; /* ECMA lamespec: the old spec had more info... */ diff --git a/src/mono/mono/mini/method-to-ir.c b/src/mono/mono/mini/method-to-ir.c index 462ed7c..0419214 100644 --- a/src/mono/mono/mini/method-to-ir.c +++ b/src/mono/mono/mini/method-to-ir.c @@ -7080,6 +7080,53 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b addr = mono_emit_jit_icall (cfg, mono_get_native_calli_wrapper, args); } + if (!method->dynamic && fsig->pinvoke && + !method->wrapper_type) { + /* MONO_WRAPPER_DYNAMIC_METHOD dynamic method handled above in the + method->dynamic case; for other wrapper types assume the code knows + what its doing and added its own GC transitions */ + + /* TODO: unmanaged[SuppressGCTransition] call conv will set + * skip_gc_trans to TRUE*/ + gboolean skip_gc_trans = FALSE; + if (!skip_gc_trans) { +#if 0 + fprintf (stderr, "generating wrapper for calli in method %s with wrapper type %s\n", method->name, mono_wrapper_type_to_str (method->wrapper_type)); +#endif + /* Call the wrapper that will do the GC transition instead */ + MonoMethod *wrapper = mono_marshal_get_native_func_wrapper_indirect (method->klass, fsig, cfg->compile_aot); + + fsig = mono_method_signature_internal (wrapper); + + n = fsig->param_count - 1; /* wrapper has extra fnptr param */ + + CHECK_STACK (n); + + /* move the args to allow room for 'this' in the first position */ + while (n--) { + --sp; + sp [1] = sp [0]; + } + + sp[0] = addr; /* n+1 args, first arg is the address of the indirect method to call */ + + g_assert (!fsig->hasthis && !fsig->pinvoke); + + gboolean inline_wrapper = cfg->opt & MONO_OPT_INLINE || cfg->compile_aot; + if (inline_wrapper) { + int costs = inline_method (cfg, wrapper, fsig, sp, ip, cfg->real_offset, TRUE); + CHECK_CFG_EXCEPTION; + g_assert (costs > 0); + cfg->real_offset += 5; + inline_costs += costs; + ins = sp[0]; + } else { + ins = mono_emit_method_call (cfg, wrapper, /*args*/sp, NULL); + } + goto calli_end; + } + } + n = fsig->param_count + fsig->hasthis; CHECK_STACK (n); -- 2.7.4