diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Reflection/MethodBaseTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Reflection/MethodBaseTests.cs index 61c811fbbd8ef..f10dc27b86b80 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Reflection/MethodBaseTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Reflection/MethodBaseTests.cs @@ -55,27 +55,44 @@ public static void TestMethodBody() { MethodBase mbase = typeof(MethodBaseTests).GetMethod("MyOtherMethod", BindingFlags.Static | BindingFlags.Public); MethodBody mb = mbase.GetMethodBody(); + var codeSize = mb.GetILAsByteArray().Length; Assert.True(mb.InitLocals); // local variables are initialized -#if DEBUG - Assert.Equal(2, mb.MaxStackSize); - Assert.Equal(3, mb.LocalVariables.Count); - foreach (LocalVariableInfo lvi in mb.LocalVariables) + if (codeSize == 0) { - if (lvi.LocalIndex == 0) { Assert.Equal(typeof(int), lvi.LocalType); } - if (lvi.LocalIndex == 1) { Assert.Equal(typeof(string), lvi.LocalType); } - if (lvi.LocalIndex == 2) { Assert.Equal(typeof(bool), lvi.LocalType); } - } + // This condition is needed for running this test under WASM AOT mode. + // Because IL trim is enabled be default for WASM apps whenever AOT is enabled. + // And the method body of "MyOtherMethod" will be trimmed. +#if DEBUG + Assert.Equal(2, mb.MaxStackSize); #else - Assert.Equal(1, mb.MaxStackSize); - Assert.Equal(2, mb.LocalVariables.Count); - - foreach (LocalVariableInfo lvi in mb.LocalVariables) - { - if (lvi.LocalIndex == 0) { Assert.Equal(typeof(int), lvi.LocalType); } - if (lvi.LocalIndex == 1) { Assert.Equal(typeof(string), lvi.LocalType); } + Assert.Equal(1, mb.MaxStackSize); +#endif + Assert.Equal(0, mb.LocalVariables.Count); } + else + { +#if DEBUG + Assert.Equal(2, mb.MaxStackSize); + Assert.Equal(3, mb.LocalVariables.Count); + + foreach (LocalVariableInfo lvi in mb.LocalVariables) + { + if (lvi.LocalIndex == 0) { Assert.Equal(typeof(int), lvi.LocalType); } + if (lvi.LocalIndex == 1) { Assert.Equal(typeof(string), lvi.LocalType); } + if (lvi.LocalIndex == 2) { Assert.Equal(typeof(bool), lvi.LocalType); } + } +#else + Assert.Equal(1, mb.MaxStackSize); + Assert.Equal(2, mb.LocalVariables.Count); + + foreach (LocalVariableInfo lvi in mb.LocalVariables) + { + if (lvi.LocalIndex == 0) { Assert.Equal(typeof(int), lvi.LocalType); } + if (lvi.LocalIndex == 1) { Assert.Equal(typeof(string), lvi.LocalType); } + } #endif + } } private static int MyAnotherMethod(int x) diff --git a/src/mono/mono/eglib/gfile.c b/src/mono/mono/eglib/gfile.c index 6dac38dc52fbf..73597ad5b208d 100644 --- a/src/mono/mono/eglib/gfile.c +++ b/src/mono/mono/eglib/gfile.c @@ -104,3 +104,28 @@ g_file_error_from_errno (gint err_no) return G_FILE_ERROR_FAILED; } } + +FILE * +g_fopen (const char *path, const char *mode) +{ + FILE *fp; + + if (!path) + return NULL; + +#ifndef HOST_WIN32 + fp = fopen (path, mode); +#else + gunichar2 *wPath = g_utf8_to_utf16 (path, -1, 0, 0, 0); + gunichar2 *wMode = g_utf8_to_utf16 (mode, -1, 0, 0, 0); + + if (!wPath || !wMode) + return NULL; + + fp = _wfopen ((wchar_t *) wPath, (wchar_t *) wMode); + g_free (wPath); + g_free (wMode); +#endif + + return fp; +} diff --git a/src/mono/mono/eglib/glib.h b/src/mono/mono/eglib/glib.h index f645f8431b5fa..68192b2e6baeb 100644 --- a/src/mono/mono/eglib/glib.h +++ b/src/mono/mono/eglib/glib.h @@ -962,6 +962,7 @@ typedef enum { G_ENUM_FUNCTIONS (GFileTest) +FILE * g_fopen (const char *path, const char *mode); gboolean g_file_get_contents (const gchar *filename, gchar **contents, gsize *length, GError **gerror); GFileError g_file_error_from_errno (gint err_no); gint g_file_open_tmp (const gchar *tmpl, gchar **name_used, GError **gerror); diff --git a/src/mono/mono/mini/aot-compiler.c b/src/mono/mono/mini/aot-compiler.c index 2cd2dbc234f21..bc9e690f05fe0 100644 --- a/src/mono/mono/mini/aot-compiler.c +++ b/src/mono/mono/mini/aot-compiler.c @@ -9885,10 +9885,15 @@ compile_method (MonoAotCompile *acfg, MonoMethod *method) mono_atomic_inc_i32 (&acfg->stats.ccount); if (acfg->aot_opts.trimming_eligible_methods_outfile && acfg->trimming_eligible_methods_outfile != NULL) { - if (!mono_method_is_generic_impl (method) && method->token != 0 && !cfg->deopt && !cfg->interp_entry_only && mini_get_interp_callbacks ()->jit_call_can_be_supported (method, mono_method_signature_internal (method), acfg->aot_opts.llvm_only)) { - // The call back to jit_call_can_be_supported is necessary for WASM, because it would still interprete some methods sometimes even though they were already AOT'ed. + if (!mono_method_is_generic_impl (method) && method->token != 0 && !cfg->deopt && !cfg->interp_entry_only) { + // The call to mono_jit_call_can_be_supported_by_interp is necessary for WASM, because it would still interprete some methods sometimes even though they were already AOT'ed. // When that happens, interpreter needs to have the capability to call the AOT'ed version of that method, since the method body has already been trimmed. - fprintf (acfg->trimming_eligible_methods_outfile, "%x\n", method->token); + gboolean skip_trim = FALSE; + if (acfg->aot_opts.interp) { + skip_trim = (!mono_jit_call_can_be_supported_by_interp (method, mono_method_signature_internal (method), acfg->aot_opts.llvm_only) || (method->iflags & METHOD_IMPL_ATTRIBUTE_SYNCHRONIZED)); + } + if (!skip_trim) + fprintf (acfg->trimming_eligible_methods_outfile, "%x\n", method->token); } } } @@ -14917,7 +14922,7 @@ aot_assembly (MonoAssembly *ass, guint32 jit_opts, MonoAotOptions *aot_options) } if (acfg->aot_opts.trimming_eligible_methods_outfile && acfg->dedup_phase != DEDUP_COLLECT) { - acfg->trimming_eligible_methods_outfile = fopen (acfg->aot_opts.trimming_eligible_methods_outfile, "w+"); + acfg->trimming_eligible_methods_outfile = g_fopen (acfg->aot_opts.trimming_eligible_methods_outfile, "w"); if (!acfg->trimming_eligible_methods_outfile) aot_printerrf (acfg, "Unable to open trimming-eligible-methods-outfile specified file %s\n", acfg->aot_opts.trimming_eligible_methods_outfile); else { diff --git a/src/mono/mono/mini/ee.h b/src/mono/mono/mini/ee.h index 1e88e9aaaa9d8..caa04bef85872 100644 --- a/src/mono/mono/mini/ee.h +++ b/src/mono/mono/mini/ee.h @@ -65,7 +65,6 @@ typedef gpointer MonoInterpFrameHandle; MONO_EE_CALLBACK (void, entry_llvmonly, (gpointer res, gpointer *args, gpointer imethod)) \ MONO_EE_CALLBACK (gpointer, get_interp_method, (MonoMethod *method)) \ MONO_EE_CALLBACK (MonoJitInfo*, compile_interp_method, (MonoMethod *method, MonoError *error)) \ - MONO_EE_CALLBACK (gboolean, jit_call_can_be_supported, (MonoMethod *method, MonoMethodSignature *sig, gboolean is_llvm_only)) \ typedef struct _MonoEECallbacks { diff --git a/src/mono/mono/mini/interp-stubs.c b/src/mono/mono/mini/interp-stubs.c index 0ade0c95e0440..8d1487c8876ea 100644 --- a/src/mono/mono/mini/interp-stubs.c +++ b/src/mono/mono/mini/interp-stubs.c @@ -252,12 +252,6 @@ stub_compile_interp_method (MonoMethod *method, MonoError *error) return NULL; } -static gboolean -stub_jit_call_can_be_supported (MonoMethod *method, MonoMethodSignature *sig, gboolean is_llvm_only) -{ - return TRUE; -} - #undef MONO_EE_CALLBACK #define MONO_EE_CALLBACK(ret, name, sig) stub_ ## name, diff --git a/src/mono/mono/mini/interp/interp-internals.h b/src/mono/mono/mini/interp/interp-internals.h index 4e0be7db04334..9be34b46d8457 100644 --- a/src/mono/mono/mini/interp/interp-internals.h +++ b/src/mono/mono/mini/interp/interp-internals.h @@ -320,9 +320,6 @@ mono_mint_type (MonoType *type); int mono_interp_type_size (MonoType *type, int mt, int *align_p); -gboolean -interp_jit_call_can_be_supported (MonoMethod *method, MonoMethodSignature *sig, gboolean is_llvm_only); - #if HOST_BROWSER gboolean diff --git a/src/mono/mono/mini/interp/interp.c b/src/mono/mono/mini/interp/interp.c index 295861c5703e1..c35a61ded1167 100644 --- a/src/mono/mono/mini/interp/interp.c +++ b/src/mono/mono/mini/interp/interp.c @@ -3837,7 +3837,7 @@ mono_interp_exec_method (InterpFrame *frame, ThreadContext *context, FrameClause ip += 3; MINT_IN_BREAK; MINT_IN_CASE(MINT_NIY) - g_printf ("MONO interpreter: NIY encountered in method %s\n", frame->imethod->method->name); + g_printf ("MONO interpreter: NIY encountered in method %s\n", mono_method_full_name (frame->imethod->method, TRUE)); g_assert_not_reached (); MINT_IN_BREAK; MINT_IN_CASE(MINT_BREAK) @@ -3937,20 +3937,49 @@ mono_interp_exec_method (InterpFrame *frame, ThreadContext *context, FrameClause guint16 params_offset = ip [1]; guint16 params_size = ip [3]; - // Copy the params to their location at the start of the frame - memmove (frame->stack, (guchar*)frame->stack + params_offset, params_size); new_method = (InterpMethod*)frame->imethod->data_items [ip [2]]; if (*ip == MINT_TAILCALL_VIRT) { gint16 slot = (gint16)ip [4]; - MonoObject *this_arg = LOCAL_VAR (0, MonoObject*); + MonoObject **this_arg_p = (MonoObject **)((guchar*)frame->stack + params_offset); + MonoObject *this_arg = *this_arg_p; new_method = get_virtual_method_fast (new_method, this_arg->vtable, slot); if (m_class_is_valuetype (this_arg->vtable->klass) && m_class_is_valuetype (new_method->method->klass)) { /* unbox */ gpointer unboxed = mono_object_unbox_internal (this_arg); - LOCAL_VAR (0, gpointer) = unboxed; + *this_arg_p = unboxed; + } + + InterpMethodCodeType code_type = new_method->code_type; + + g_assert (code_type == IMETHOD_CODE_UNKNOWN || + code_type == IMETHOD_CODE_INTERP || + code_type == IMETHOD_CODE_COMPILED); + + if (G_UNLIKELY (code_type == IMETHOD_CODE_UNKNOWN)) { + // FIXME push/pop LMF + MonoMethodSignature *sig = mono_method_signature_internal (new_method->method); + if (mono_interp_jit_call_supported (new_method->method, sig)) + code_type = IMETHOD_CODE_COMPILED; + else + code_type = IMETHOD_CODE_INTERP; + new_method->code_type = code_type; + } + + if (code_type == IMETHOD_CODE_COMPILED) { + error_init_reuse (error); + do_jit_call (context, frame->retval, (stackval*)((guchar*)frame->stack + params_offset), frame, new_method, error); + if (!is_ok (error)) { + MonoException *call_ex = interp_error_convert_to_exception (frame, error, ip); + THROW_EX (call_ex, ip); + } + + goto exit_frame; } } + + // Copy the params to their location at the start of the frame + memmove (frame->stack, (guchar*)frame->stack + params_offset, params_size); } else { new_method = (InterpMethod*)frame->imethod->data_items [ip [1]]; } @@ -4057,39 +4086,7 @@ mono_interp_exec_method (InterpFrame *frame, ThreadContext *context, FrameClause } ip += 6; - InterpMethodCodeType code_type = cmethod->code_type; - - g_assert (code_type == IMETHOD_CODE_UNKNOWN || - code_type == IMETHOD_CODE_INTERP || - code_type == IMETHOD_CODE_COMPILED); - - if (G_UNLIKELY (code_type == IMETHOD_CODE_UNKNOWN)) { - // FIXME push/pop LMF - MonoMethodSignature *sig = mono_method_signature_internal (cmethod->method); - if (mono_interp_jit_call_supported (cmethod->method, sig)) - code_type = IMETHOD_CODE_COMPILED; - else - code_type = IMETHOD_CODE_INTERP; - cmethod->code_type = code_type; - } - - if (code_type == IMETHOD_CODE_INTERP) { - - goto call; - - } else if (code_type == IMETHOD_CODE_COMPILED) { - frame->state.ip = ip; - error_init_reuse (error); - do_jit_call (context, (stackval*)(locals + return_offset), (stackval*)(locals + call_args_offset), frame, cmethod, error); - if (!is_ok (error)) { - MonoException *call_ex = interp_error_convert_to_exception (frame, error, ip); - THROW_EX (call_ex, ip); - } - - CHECK_RESUME_STATE (context); - } - - MINT_IN_BREAK; + goto jit_call; } MINT_IN_CASE(MINT_CALLI) { gboolean need_unbox; @@ -4111,7 +4108,7 @@ mono_interp_exec_method (InterpFrame *frame, ThreadContext *context, FrameClause } ip += 4; - goto call; + goto jit_call; } MINT_IN_CASE(MINT_CALLI_NAT_FAST) { MintICallSig icall_sig = (MintICallSig)ip [4]; @@ -4141,7 +4138,7 @@ mono_interp_exec_method (InterpFrame *frame, ThreadContext *context, FrameClause cmethod = mono_interp_get_native_func_wrapper (frame->imethod, csignature, code); ip += 5; - goto call; + goto jit_call; } MINT_IN_CASE(MINT_CALLI_NAT) { MonoMethodSignature *csignature = (MonoMethodSignature*)frame->imethod->data_items [ip [4]]; @@ -4181,39 +4178,41 @@ mono_interp_exec_method (InterpFrame *frame, ThreadContext *context, FrameClause LOCAL_VAR (call_args_offset, gpointer) = unboxed; } - InterpMethodCodeType code_type = cmethod->code_type; +jit_call: + { + InterpMethodCodeType code_type = cmethod->code_type; - g_assert (code_type == IMETHOD_CODE_UNKNOWN || - code_type == IMETHOD_CODE_INTERP || - code_type == IMETHOD_CODE_COMPILED); + g_assert (code_type == IMETHOD_CODE_UNKNOWN || + code_type == IMETHOD_CODE_INTERP || + code_type == IMETHOD_CODE_COMPILED); - if (G_UNLIKELY (code_type == IMETHOD_CODE_UNKNOWN)) { - // FIXME push/pop LMF - MonoMethodSignature *sig = mono_method_signature_internal (cmethod->method); - if (mono_interp_jit_call_supported (cmethod->method, sig)) - code_type = IMETHOD_CODE_COMPILED; - else - code_type = IMETHOD_CODE_INTERP; - cmethod->code_type = code_type; - } + if (G_UNLIKELY (code_type == IMETHOD_CODE_UNKNOWN)) { + // FIXME push/pop LMF + MonoMethodSignature *sig = mono_method_signature_internal (cmethod->method); + if (mono_interp_jit_call_supported (cmethod->method, sig)) + code_type = IMETHOD_CODE_COMPILED; + else + code_type = IMETHOD_CODE_INTERP; + cmethod->code_type = code_type; + } - if (code_type == IMETHOD_CODE_INTERP) { + if (code_type == IMETHOD_CODE_INTERP) { - goto call; + goto interp_call; - } else if (code_type == IMETHOD_CODE_COMPILED) { - frame->state.ip = ip; - error_init_reuse (error); - do_jit_call (context, (stackval*)(locals + return_offset), (stackval*)(locals + call_args_offset), frame, cmethod, error); - if (!is_ok (error)) { - MonoException *call_ex = interp_error_convert_to_exception (frame, error, ip); - THROW_EX (call_ex, ip); - } + } else if (code_type == IMETHOD_CODE_COMPILED) { + frame->state.ip = ip; + error_init_reuse (error); + do_jit_call (context, (stackval*)(locals + return_offset), (stackval*)(locals + call_args_offset), frame, cmethod, error); + if (!is_ok (error)) { + MonoException *call_ex = interp_error_convert_to_exception (frame, error, ip); + THROW_EX (call_ex, ip); + } - CHECK_RESUME_STATE (context); + CHECK_RESUME_STATE (context); + } + MINT_IN_BREAK; } - - MINT_IN_BREAK; } MINT_IN_CASE(MINT_CALL_VARARG) { // Same as MINT_CALL, except at ip [4] we have the index for the csignature, @@ -4222,7 +4221,7 @@ mono_interp_exec_method (InterpFrame *frame, ThreadContext *context, FrameClause return_offset = ip [1]; call_args_offset = ip [2]; ip += 6; - goto call; + goto jit_call; } MINT_IN_CASE(MINT_CALL) { @@ -4235,7 +4234,8 @@ mono_interp_exec_method (InterpFrame *frame, ThreadContext *context, FrameClause #else ip += 4; #endif -call: + +interp_call: /* * Make a non-recursive call by loading the new interpreter state based on child frame, * and going back to the main loop. @@ -5651,7 +5651,7 @@ MINT_IN_CASE(MINT_BRTRUE_I8_SP) ZEROP_SP(gint64, !=); MINT_IN_BREAK; // by the call, even though the call has void return (?!). LOCAL_VAR (call_args_offset, gpointer) = NULL; ip += 4; - goto call; + goto jit_call; } MINT_IN_CASE(MINT_NEWOBJ_STRING_UNOPT) { // Same as MINT_NEWOBJ_STRING but copy params into right place on stack @@ -5666,7 +5666,7 @@ MINT_IN_CASE(MINT_BRTRUE_I8_SP) ZEROP_SP(gint64, !=); MINT_IN_BREAK; call_args_offset = aligned_call_args_offset; LOCAL_VAR (call_args_offset, gpointer) = NULL; ip += 4; - goto call; + goto jit_call; } MINT_IN_CASE(MINT_NEWOBJ) { MonoVTable *vtable = (MonoVTable*) frame->imethod->data_items [ip [4]]; @@ -5689,8 +5689,8 @@ MINT_IN_CASE(MINT_BRTRUE_I8_SP) ZEROP_SP(gint64, !=); MINT_IN_BREAK; ip += 5; cmethod = (InterpMethod*)frame->imethod->data_items [imethod_index]; - goto call; - MINT_IN_BREAK; + + goto jit_call; } MINT_IN_CASE(MINT_NEWOBJ_INLINED) { MonoVTable *vtable = (MonoVTable*) frame->imethod->data_items [ip [2]]; @@ -5723,8 +5723,7 @@ MINT_IN_CASE(MINT_BRTRUE_I8_SP) ZEROP_SP(gint64, !=); MINT_IN_BREAK; ip += 5; cmethod = (InterpMethod*)frame->imethod->data_items [imethod_index]; - goto call; - MINT_IN_BREAK; + goto jit_call; } MINT_IN_CASE(MINT_NEWOBJ_VT_INLINED) { guint16 ret_size = ip [3]; @@ -5766,7 +5765,7 @@ MINT_IN_CASE(MINT_BRTRUE_I8_SP) ZEROP_SP(gint64, !=); MINT_IN_BREAK; mono_interp_error_cleanup (error); // FIXME: do not swallow the error EXCEPTION_CHECKPOINT; ip += 4; - goto call; + goto jit_call; } MINT_IN_CASE(MINT_ROL_I4_IMM) { @@ -8602,31 +8601,6 @@ interp_sufficient_stack (gsize size) return (context->stack_pointer + size) < (context->stack_start + INTERP_STACK_SIZE); } -gboolean -interp_jit_call_can_be_supported (MonoMethod *method, MonoMethodSignature *sig, gboolean is_llvm_only) -{ - if (sig->param_count > 10) - return FALSE; - if (sig->pinvoke) - return FALSE; - if (method->flags & METHOD_ATTRIBUTE_PINVOKE_IMPL) - return FALSE; - if (method->iflags & METHOD_IMPL_ATTRIBUTE_INTERNAL_CALL) - return FALSE; - if (!is_llvm_only && method->is_inflated) - return FALSE; - if (method->string_ctor) - return FALSE; - if (method->wrapper_type != MONO_WRAPPER_NONE) - return FALSE; - - if (method->flags & METHOD_ATTRIBUTE_REQSECOBJ) - /* Used to mark methods containing StackCrawlMark locals */ - return FALSE; - - return TRUE; -} - static void interp_cleanup (void) { diff --git a/src/mono/mono/mini/interp/transform.c b/src/mono/mono/mini/interp/transform.c index 0df4de58a075d..aabe294d172c2 100644 --- a/src/mono/mono/mini/interp/transform.c +++ b/src/mono/mono/mini/interp/transform.c @@ -1214,7 +1214,7 @@ mono_interp_jit_call_supported (MonoMethod *method, MonoMethodSignature *sig) { GSList *l; - if (!interp_jit_call_can_be_supported (method, sig, mono_llvm_only)) + if (!mono_jit_call_can_be_supported_by_interp (method, sig, mono_llvm_only)) return FALSE; if (mono_aot_only && m_class_get_image (method->klass)->aot_module && !(method->iflags & METHOD_IMPL_ATTRIBUTE_SYNCHRONIZED)) { @@ -3550,10 +3550,20 @@ interp_transform_call (TransformData *td, MonoMethod *method, MonoMethod *target } CHECK_STACK_RET (td, csignature->param_count + csignature->hasthis, FALSE); + + gboolean skip_tailcall = FALSE; + if (tailcall && !is_virtual && target_method != NULL) { + MonoMethodHeader *mh = interp_method_get_header (target_method, error); + if (mh != NULL && mh->code_size == 0) + skip_tailcall = TRUE; + mono_metadata_free_mh (mh); + } + if (tailcall && !td->gen_sdb_seq_points && !calli && op == -1 && (target_method->flags & METHOD_ATTRIBUTE_PINVOKE_IMPL) == 0 && (target_method->iflags & METHOD_IMPL_ATTRIBUTE_INTERNAL_CALL) == 0 && - !(target_method->iflags & METHOD_IMPL_ATTRIBUTE_NOINLINING)) { + !(target_method->iflags & METHOD_IMPL_ATTRIBUTE_NOINLINING) && + !skip_tailcall) { (void)mono_class_vtable_checked (target_method->klass, error); return_val_if_nok (error, FALSE); diff --git a/src/mono/mono/mini/mini-runtime.c b/src/mono/mono/mini/mini-runtime.c index b7aabc74549ce..257ca8fb0d4c3 100644 --- a/src/mono/mono/mini/mini-runtime.c +++ b/src/mono/mono/mini/mini-runtime.c @@ -5562,3 +5562,28 @@ mono_invoke_runtime_init_callback (void) mono_atomic_xchg_i64 ((volatile gint64 *)&runtime_init_thread_id, (gint64)G_MAXUINT64); } } + +gboolean +mono_jit_call_can_be_supported_by_interp (MonoMethod *method, MonoMethodSignature *sig, gboolean is_llvm_only) +{ + if (sig->param_count > 10) + return FALSE; + if (sig->pinvoke) + return FALSE; + if (method->flags & METHOD_ATTRIBUTE_PINVOKE_IMPL) + return FALSE; + if (method->iflags & METHOD_IMPL_ATTRIBUTE_INTERNAL_CALL) + return FALSE; + if (!is_llvm_only && method->is_inflated) + return FALSE; + if (method->string_ctor) + return FALSE; + if (method->wrapper_type != MONO_WRAPPER_NONE) + return FALSE; + + if (method->flags & METHOD_ATTRIBUTE_REQSECOBJ) + /* Used to mark methods containing StackCrawlMark locals */ + return FALSE; + + return TRUE; +} diff --git a/src/mono/mono/mini/mini-runtime.h b/src/mono/mono/mini/mini-runtime.h index 19826d4c3f86b..1e4cb5dc1eb8e 100644 --- a/src/mono/mono/mini/mini-runtime.h +++ b/src/mono/mono/mini/mini-runtime.h @@ -659,6 +659,9 @@ mono_post_native_crash_handler (const char *signal, MonoContext *mctx, MONO_SIG_ gboolean mono_is_addr_implicit_null_check (void *addr); +gboolean +mono_jit_call_can_be_supported_by_interp (MonoMethod *method, MonoMethodSignature *sig, gboolean is_llvm_only); + /* * Signal handling */ diff --git a/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets index b15c7298c8f26..13a8719e62240 100644 --- a/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets +++ b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets @@ -414,8 +414,16 @@ Copyright (c) .NET Foundation. All rights reserved. <_WasmEmitSourceMapPublish Condition="'$(_WasmEmitSourceMapPublish)' == ''">false + + <_WasmResolvedFilesToPublish Include="@(ResolvedFileToPublish)" /> + + + <_WasmResolvedFilesToPublish Include="@(WasmAssembliesFinal)" /> + <_WasmResolvedFilesToPublish Include="@(ResolvedFileToPublish)" Condition="'%(Extension)' != '.dll'" /> + + - + diff --git a/src/mono/wasi/build/WasiApp.targets b/src/mono/wasi/build/WasiApp.targets index 2dc3755948814..1811e61146446 100644 --- a/src/mono/wasi/build/WasiApp.targets +++ b/src/mono/wasi/build/WasiApp.targets @@ -382,6 +382,7 @@ ExtraFilesToDeploy="@(WasmExtraFilesToDeploy)" NativeAssets="@(WasmNativeAsset)" DebugLevel="$(WasmDebugLevel)" + RuntimeConfigJsonPath="$(_WasmRuntimeConfigFilePath)" /> diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/BuildPublishTests.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/BuildPublishTests.cs index 2f5ef58e6bf29..28130327e719b 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Blazor/BuildPublishTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Blazor/BuildPublishTests.cs @@ -84,8 +84,8 @@ public void DefaultTemplate_NoAOT_WithWorkload(string config, bool testUnicode) public void DefaultTemplate_AOT_WithWorkload(string config, bool testUnicode) { string id = testUnicode ? - $"blz_no_aot_{config}_{GetRandomId()}_{s_unicodeChar}" : - $"blz_no_aot_{config}_{GetRandomId()}"; + $"blz_aot_{config}_{GetRandomId()}_{s_unicodeChar}" : + $"blz_aot_{config}_{GetRandomId()}"; CreateBlazorWasmTemplateProject(id); BlazorBuild(new BlazorBuildOptions(id, config, NativeFilesType.FromRuntimePack)); @@ -173,4 +173,28 @@ void AssertResourcesDlls(string basePath) } } } + + [Theory] + [InlineData("", false)] // Default case + [InlineData("true", true)] // the other case + public async Task Test_WasmStripILAfterAOT(string stripILAfterAOT, bool expectILStripping) + { + string config = "Release"; + string id = $"blz_WasmStripILAfterAOT_{config}_{GetRandomId()}"; + string projectFile = CreateBlazorWasmTemplateProject(id); + string projectDirectory = Path.GetDirectoryName(projectFile)!; + + string extraProperties = "true"; + if (!string.IsNullOrEmpty(stripILAfterAOT)) + extraProperties += $"{stripILAfterAOT}"; + AddItemsPropertiesToProject(projectFile, extraProperties); + + BlazorPublish(new BlazorBuildOptions(id, config, NativeFilesType.AOT, AssertAppBundle : false)); + await BlazorRunForPublishWithWebServer(new BlazorRunOptions() { Config = config }); + + string frameworkDir = Path.Combine(projectDirectory, "bin", config, BuildTestBase.DefaultTargetFrameworkForBlazor, "publish", "wwwroot", "_framework"); + string objBuildDir = Path.Combine(projectDirectory, "obj", config, BuildTestBase.DefaultTargetFrameworkForBlazor, "wasm", "for-publish"); + + WasmTemplateTests.TestWasmStripILAfterAOTOutput(objBuildDir, frameworkDir, expectILStripping, _testOutput); + } } diff --git a/src/mono/wasm/Wasm.Build.Tests/Common/Utils.cs b/src/mono/wasm/Wasm.Build.Tests/Common/Utils.cs index d064829fe289b..b6d087a013575 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Common/Utils.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Common/Utils.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.IO.Compression; using System.Runtime.InteropServices; using System.Text; using Xunit; @@ -58,4 +59,16 @@ public static void DirectoryCopy(string sourceDirName, string destDirName, Func< } } + public static string GZipCompress(string fileSelected) + { + FileInfo fileToCompress = new FileInfo(fileSelected); + string compressedFileName = fileToCompress.FullName + ".gz"; + + using FileStream originalFileStream = fileToCompress.OpenRead(); + using FileStream compressedFileStream = File.Create(compressedFileName); + using GZipStream compressionStream = new(compressedFileStream, CompressionMode.Compress); + originalFileStream.CopyTo(compressionStream); + + return compressedFileName; + } } diff --git a/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/FlagsChangeRebuildTest.cs b/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/FlagsChangeRebuildTest.cs index 8d495f923626c..9f18293b3c848 100644 --- a/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/FlagsChangeRebuildTest.cs +++ b/src/mono/wasm/Wasm.Build.Tests/NativeRebuildTests/FlagsChangeRebuildTest.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.IO; using System.Collections.Generic; using System.Linq; diff --git a/src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTests.cs b/src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTests.cs index 9b14bedd59b50..626ae725b1524 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTests.cs @@ -491,5 +491,103 @@ public void BuildAndRunForDifferentOutputPaths(string config, bool appendRID, bo .ExecuteWithCapturedOutput($"run --no-silent --no-build -c {config} x y z") .EnsureSuccessful(); } + + [Theory] + [InlineData("", false)] // Default case + [InlineData("true", true)] // the other case + public void Test_WasmStripILAfterAOT(string stripILAfterAOT, bool expectILStripping) + { + string config = "Release"; + string id = $"strip_{config}_{GetRandomId()}"; + string projectFile = CreateWasmTemplateProject(id, "wasmconsole"); + string projectName = Path.GetFileNameWithoutExtension(projectFile); + string projectDirectory = Path.GetDirectoryName(projectFile)!; + bool aot = true; + + UpdateProgramCS(); + UpdateConsoleMainJs(); + + string extraProperties = "true"; + if (!string.IsNullOrEmpty(stripILAfterAOT)) + extraProperties += $"{stripILAfterAOT}"; + AddItemsPropertiesToProject(projectFile, extraProperties); + + var buildArgs = new BuildArgs(projectName, config, aot, id, null); + buildArgs = ExpandBuildArgs(buildArgs); + + BuildTemplateProject(buildArgs, + id: id, + new BuildProjectOptions( + CreateProject: false, + HasV8Script: false, + MainJS: "main.mjs", + Publish: true, + TargetFramework: BuildTestBase.DefaultTargetFramework, + UseCache: false, + IsBrowserProject: false, + AssertAppBundle: false)); + + string runArgs = $"run --no-silent --no-build -c {config}"; + var res = new RunCommand(s_buildEnv, _testOutput, label: id) + .WithWorkingDirectory(_projectDir!) + .ExecuteWithCapturedOutput(runArgs) + .EnsureExitCode(42); + + string frameworkDir = Path.Combine(projectDirectory, "bin", config, BuildTestBase.DefaultTargetFramework, "browser-wasm", "AppBundle", "_framework"); + string objBuildDir = Path.Combine(projectDirectory, "obj", config, BuildTestBase.DefaultTargetFramework, "browser-wasm", "wasm", "for-publish"); + TestWasmStripILAfterAOTOutput(objBuildDir, frameworkDir, expectILStripping, _testOutput); + } + + internal static void TestWasmStripILAfterAOTOutput(string objBuildDir, string frameworkDir, bool expectILStripping, ITestOutputHelper testOutput) + { + string origAssemblyDir = Path.Combine(objBuildDir, "aot-in"); + string strippedAssemblyDir = Path.Combine(objBuildDir, "stripped"); + Assert.True(Directory.Exists(origAssemblyDir), $"Could not find the original AOT input assemblies dir: {origAssemblyDir}"); + if (expectILStripping) + Assert.True(Directory.Exists(strippedAssemblyDir), $"Could not find the stripped assemblies dir: {strippedAssemblyDir}"); + else + Assert.False(Directory.Exists(strippedAssemblyDir), $"Expected {strippedAssemblyDir} to not exist"); + + string assemblyToExamine = "System.Private.CoreLib.dll"; + string originalAssembly = Path.Combine(objBuildDir, origAssemblyDir, assemblyToExamine); + string strippedAssembly = Path.Combine(objBuildDir, strippedAssemblyDir, assemblyToExamine); + string bundledAssembly = Path.Combine(frameworkDir, Path.ChangeExtension(assemblyToExamine, ProjectProviderBase.WasmAssemblyExtension)); + Assert.True(File.Exists(originalAssembly), $"Expected {nameof(originalAssembly)} {originalAssembly} to exist"); + Assert.True(File.Exists(bundledAssembly), $"Expected {nameof(bundledAssembly)} {bundledAssembly} to exist"); + if (expectILStripping) + Assert.True(File.Exists(strippedAssembly), $"Expected {nameof(strippedAssembly)} {strippedAssembly} to exist"); + else + Assert.False(File.Exists(strippedAssembly), $"Expected {strippedAssembly} to not exist"); + + string compressedOriginalAssembly = Utils.GZipCompress(originalAssembly); + string compressedBundledAssembly = Utils.GZipCompress(bundledAssembly); + FileInfo compressedOriginalAssembly_fi = new FileInfo(compressedOriginalAssembly); + FileInfo compressedBundledAssembly_fi = new FileInfo(compressedBundledAssembly); + + testOutput.WriteLine ($"compressedOriginalAssembly_fi: {compressedOriginalAssembly_fi.Length}, {compressedOriginalAssembly}"); + testOutput.WriteLine ($"compressedBundledAssembly_fi: {compressedBundledAssembly_fi.Length}, {compressedBundledAssembly}"); + + if (expectILStripping) + { + if (!UseWebcil) + { + string compressedStrippedAssembly = Utils.GZipCompress(strippedAssembly); + FileInfo compressedStrippedAssembly_fi = new FileInfo(compressedStrippedAssembly); + testOutput.WriteLine ($"compressedStrippedAssembly_fi: {compressedStrippedAssembly_fi.Length}, {compressedStrippedAssembly}"); + Assert.True(compressedOriginalAssembly_fi.Length > compressedStrippedAssembly_fi.Length, $"Expected original assembly({compressedOriginalAssembly}) size ({compressedOriginalAssembly_fi.Length}) " + + $"to be bigger than the stripped assembly ({compressedStrippedAssembly}) size ({compressedStrippedAssembly_fi.Length})"); + Assert.True(compressedBundledAssembly_fi.Length == compressedStrippedAssembly_fi.Length, $"Expected bundled assembly({compressedBundledAssembly}) size ({compressedBundledAssembly_fi.Length}) " + + $"to be the same as the stripped assembly ({compressedStrippedAssembly}) size ({compressedStrippedAssembly_fi.Length})"); + } + } + else + { + if (!UseWebcil) + { + // FIXME: The bundled file would be .wasm in case of webcil, so can't compare size + Assert.True(compressedOriginalAssembly_fi.Length == compressedBundledAssembly_fi.Length); + } + } + } } } diff --git a/src/mono/wasm/build/WasmApp.LocalBuild.targets b/src/mono/wasm/build/WasmApp.LocalBuild.targets index 15d3a7b584938..d41a00d53853f 100644 --- a/src/mono/wasm/build/WasmApp.LocalBuild.targets +++ b/src/mono/wasm/build/WasmApp.LocalBuild.targets @@ -22,6 +22,7 @@ + diff --git a/src/mono/wasm/build/WasmApp.Native.targets b/src/mono/wasm/build/WasmApp.Native.targets index 6f4b748b3fbec..9c22d0d7be94a 100644 --- a/src/mono/wasm/build/WasmApp.Native.targets +++ b/src/mono/wasm/build/WasmApp.Native.targets @@ -714,14 +714,15 @@ - + - + + + <_WasmAssembliesInternal Remove="@(_WasmAssembliesInternal)"/> + <_WasmAssembliesInternal Include="@(_UpdatedAssembliesAfterILStrip)"/> + <_BitcodeFile Include="%(_WasmAssembliesInternal.LlvmBitcodeFile)" /> diff --git a/src/mono/wasm/build/WasmApp.targets b/src/mono/wasm/build/WasmApp.targets index db47100639c4f..6da897b0e81ce 100644 --- a/src/mono/wasm/build/WasmApp.targets +++ b/src/mono/wasm/build/WasmApp.targets @@ -87,7 +87,7 @@ - AppBundle directly contains user files - AppBundle/_framework contains generated files (dlls, runtime scripts, icu) - AppBundle/_content contains web files from nuget packages (css, js, etc) - - $(WasmStripILAfterAOT) - Set to true to enable trimming away AOT compiled methods body (IL code) + - $(WasmStripILAfterAOT) - Set to true to enable trimming away AOT compiled methods body (IL code) Defaults to false. Public items: @@ -150,7 +150,6 @@ .wasm .dll - false _framework @@ -456,6 +455,7 @@ WasmIcuDataFileName="$(WasmIcuDataFileName)" RuntimeAssetsLocation="$(WasmRuntimeAssetsLocation)" CacheBootResources="$(BlazorCacheBootResources)" + RuntimeConfigJsonPath="$(_WasmRuntimeConfigFilePath)" > @@ -509,6 +509,7 @@ + diff --git a/src/tasks/AotCompilerTask/MonoAOTCompiler.cs b/src/tasks/AotCompilerTask/MonoAOTCompiler.cs index 0a761b44a4c8f..bee003cada5b4 100644 --- a/src/tasks/AotCompilerTask/MonoAOTCompiler.cs +++ b/src/tasks/AotCompilerTask/MonoAOTCompiler.cs @@ -15,7 +15,6 @@ using Microsoft.Build.Framework; using Microsoft.Build.Utilities; using System.Reflection.PortableExecutable; -using System.Text.Json.Serialization; public class MonoAOTCompiler : Microsoft.Build.Utilities.Task { @@ -296,6 +295,7 @@ public class MonoAOTCompiler : Microsoft.Build.Utilities.Task private IList? _assembliesToCompile; private ConcurrentDictionary compiledAssemblies = new(); + private BuildPropertiesTable? _propertiesTable; private MonoAotMode parsedAotMode; private MonoAotOutputType parsedOutputType; @@ -467,6 +467,7 @@ private bool ProcessAndValidateArguments() return !Log.HasLoggedErrors; } + public override bool Execute() { try @@ -491,6 +492,9 @@ private bool ExecuteInternal() if (!ProcessAndValidateArguments()) return false; + string propertiesTableFilePath = Path.Combine(IntermediateOutputPath, "monoAotPropertyValues.txt"); + _propertiesTable = new BuildPropertiesTable(propertiesTableFilePath); + IEnumerable managedAssemblies = FilterOutUnmanagedAssemblies(Assemblies); managedAssemblies = EnsureAllAssembliesInTheSameDir(managedAssemblies); _assembliesToCompile = managedAssemblies.Where(f => !ShouldSkipForAOT(f)).ToList(); @@ -567,6 +571,8 @@ all assigned to that one partition. } CheckExportSymbolsFile(_assembliesToCompile); + _propertiesTable.Table[nameof(CollectTrimmingEligibleMethods)] = CollectTrimmingEligibleMethods.ToString(); + _propertiesTable.Save(propertiesTableFilePath, Log); CompiledAssemblies = ConvertAssembliesDictToOrderedList(compiledAssemblies, _assembliesToCompile).ToArray(); return !Log.HasLoggedErrors; } @@ -654,7 +660,7 @@ private PrecompileArguments GetPrecompileArgumentsFor(ITaskItem assemblyItem, st { string assembly = assemblyItem.GetMetadata("FullPath"); string assemblyDir = Path.GetDirectoryName(assembly)!; - var aotAssembly = new TaskItem(assembly); + var aotAssembly = new TaskItem(assembly, assemblyItem.CloneCustomMetadata()); var aotArgs = new List(); var processArgs = new List(); bool isDedup = Path.GetFileName(assembly) == Path.GetFileName(DedupAssembly); @@ -1272,6 +1278,45 @@ public PrecompileArguments(string ResponseFilePath, IDictionary public ITaskItem AOTAssembly { get; private set; } public IList ProxyFiles { get; private set; } } + + private sealed class BuildPropertiesTable + { + public Dictionary Table { get; private set; } + + public BuildPropertiesTable(string propertiesFilePath) + { + Table = Read(propertiesFilePath) ?? new(); + } + + public bool GetBool(string propertyName, bool defaultValue) + => bool.TryParse(Table[propertyName], out bool outValue) ? outValue : defaultValue; + + private static Dictionary? Read(string propertiesFilePath) + { + if (!File.Exists(propertiesFilePath)) + return null; + + string text = File.ReadAllText(propertiesFilePath); + if (text.Length == 0) + return null; + + try + { + return JsonSerializer.Deserialize>(text); + } + catch (Exception e) + { + throw new LogAsErrorException($"Failed to parse properties table from {propertiesFilePath}: {e}"); + } + } + + public void Save(string filePath, TaskLoggingHelper log) + { + string jsonString = JsonSerializer.Serialize(Table); + File.WriteAllText(filePath, jsonString); + log.LogMessage(MessageImportance.Low, $"Logged Mono AOT Properties in {filePath}"); + } + } } public enum MonoAotMode diff --git a/src/tasks/MonoTargetsTasks/ILStrip/ILStrip.cs b/src/tasks/MonoTargetsTasks/ILStrip/ILStrip.cs index 719064d9387b8..6a25bd26168b9 100644 --- a/src/tasks/MonoTargetsTasks/ILStrip/ILStrip.cs +++ b/src/tasks/MonoTargetsTasks/ILStrip/ILStrip.cs @@ -16,6 +16,7 @@ using System.Reflection.Metadata.Ecma335; using System.Reflection.PortableExecutable; using System.Buffers; +using System.Collections.Concurrent; public class ILStrip : Microsoft.Build.Utilities.Task { @@ -37,15 +38,22 @@ public class ILStrip : Microsoft.Build.Utilities.Task public bool TrimIndividualMethods { get; set; } /// - /// Assembilies got trimmed successfully. + /// The location to store the trimmed assemblies, when provided. + /// + public string? IntermediateOutputPath { get; set; } + + /// + /// Contains the updated list of assemblies comparing to the input variable Assemblies. + /// Replaced the trimmed ones with their new location. /// - /// Successful trimming will set the following metadata on the items: - /// - TrimmedAssemblyFileName + /// Added two metadata for trimmed items: + /// - UntrimmedAssemblyFilePath + /// - ILStripped: set to true to indicate this item is trimmed /// [Output] - public ITaskItem[]? TrimmedAssemblies { get; set; } + public ITaskItem[]? UpdatedAssemblies { get; set; } - private readonly List _trimmedAssemblies = new(); + private ConcurrentDictionary _processedAssemblies = new(); public override bool Execute() { @@ -54,6 +62,18 @@ public override bool Execute() throw new ArgumentException($"'{nameof(Assemblies)}' is required.", nameof(Assemblies)); } + string trimmedAssemblyFolder = string.Empty; + if (TrimIndividualMethods) + { + trimmedAssemblyFolder = string.IsNullOrEmpty(IntermediateOutputPath) ? "stripped" : Path.Combine(IntermediateOutputPath, "stripped"); + if (!Directory.Exists(trimmedAssemblyFolder)) + { + Directory.CreateDirectory(trimmedAssemblyFolder); + } + } + + Log.LogMessage(MessageImportance.High, "IL stripping assemblies"); + int allowedParallelism = DisableParallelStripping ? 1 : Math.Min(Assemblies.Length, Environment.ProcessorCount); if (BuildEngine is IBuildEngine9 be9) allowedParallelism = be9.RequestCores(allowedParallelism); @@ -68,14 +88,14 @@ public override bool Execute() } else { - if (!TrimMethods(assemblyItem)) + if (!TrimMethods(assemblyItem, trimmedAssemblyFolder)) state.Stop(); } }); if (TrimIndividualMethods) { - TrimmedAssemblies = _trimmedAssemblies.ToArray(); + UpdatedAssemblies = ConvertAssembliesDictToOrderedList(_processedAssemblies, Assemblies).ToArray(); } if (!result.IsCompleted && !Log.HasLoggedErrors) @@ -114,7 +134,7 @@ private bool StripAssembly(ITaskItem assemblyItem) return true; } - private bool TrimMethods(ITaskItem assemblyItem) + private bool TrimMethods(ITaskItem assemblyItem, string trimmedAssemblyFolder) { string assemblyFilePathArg = assemblyItem.ItemSpec; string methodTokenFile = assemblyItem.GetMetadata("MethodTokenFile"); @@ -143,7 +163,27 @@ private bool TrimMethods(ITaskItem assemblyItem) return true; } - string trimmedAssemblyFilePath = ComputeTrimmedAssemblyPath(assemblyFilePath); + string trimmedAssemblyFilePath = ComputeTrimmedAssemblyPath(trimmedAssemblyFolder, assemblyFilePath); + if (File.Exists(trimmedAssemblyFilePath)) + { + if (IsInputNewerThanOutput(assemblyFilePath, trimmedAssemblyFilePath)) + { + Log.LogMessage(MessageImportance.Low, $"Re-trimming {assemblyFilePath} because {trimmedAssemblyFilePath} is older than {assemblyFilePath} ."); + Log.LogMessage(MessageImportance.Low, $"Deleting {trimmedAssemblyFilePath} ."); + File.Delete(trimmedAssemblyFilePath); + } + else + { + Log.LogMessage(MessageImportance.Low, $"Skip trimming {assemblyFilePath} because {trimmedAssemblyFilePath} is newer than {assemblyFilePath} ."); + _processedAssemblies.GetOrAdd(assemblyItem.ItemSpec, GetTrimmedAssemblyItem(assemblyItem, trimmedAssemblyFilePath, assemblyFilePathArg)); + return true; + } + } + else + { + Log.LogMessage(MessageImportance.Low, $"Trimming {assemblyFilePath} ."); + } + bool isTrimmed = false; using FileStream fs = File.Open(assemblyFilePath, FileMode.Open); using PEReader peReader = new(fs, PEStreamOptions.LeaveOpen); @@ -164,26 +204,30 @@ private bool TrimMethods(ITaskItem assemblyItem) CreateTrimmedAssembly(peReader, trimmedAssemblyFilePath, fs, methodBodyUses); } - if (isTrimmed) - { - AddItemToTrimmedList(assemblyFilePathArg, trimmedAssemblyFilePath); - } + var outAssemblyItem = isTrimmed ? GetTrimmedAssemblyItem(assemblyItem, trimmedAssemblyFilePath, assemblyFilePathArg) : assemblyItem; + _processedAssemblies.GetOrAdd(assemblyItem.ItemSpec, outAssemblyItem); return true; } - private static string ComputeTrimmedAssemblyPath(string assemblyFilePath) + private static string ComputeTrimmedAssemblyPath(string trimmedAssemblyFolder, string assemblyFilePath) { - string? assemblyPath = Path.GetDirectoryName(assemblyFilePath); - string? assemblyName = Path.GetFileNameWithoutExtension(assemblyFilePath); - if (string.IsNullOrEmpty(assemblyPath)) - { - return (assemblyName + "_trimmed.dll"); - } - else + string? assemblyName = Path.GetFileName(assemblyFilePath); + return Path.Combine(trimmedAssemblyFolder, assemblyName); + } + + private static bool IsInputNewerThanOutput(string inFile, string outFile) + => File.GetLastWriteTimeUtc(inFile) > File.GetLastWriteTimeUtc(outFile); + + private static List ConvertAssembliesDictToOrderedList(ConcurrentDictionary dict, IList originalAssemblies) + { + List outItems = new(originalAssemblies.Count); + foreach (ITaskItem item in originalAssemblies) { - return Path.Combine(assemblyPath, (assemblyName + "_trimmed.dll")); + if (dict.TryGetValue(item.GetMetadata("FullPath"), out ITaskItem? dictItem)) + outItems.Add(dictItem); } + return outItems; } private static string ComputeGuid(MetadataReader mr) @@ -303,10 +347,12 @@ private static void ZeroOutMethodBody(ref MemoryStream memStream, int methodSize ArrayPool.Shared.Return(zeroBuffer); } - private void AddItemToTrimmedList(string assemblyFilePath, string trimmedAssemblyFilePath) + private static TaskItem GetTrimmedAssemblyItem(ITaskItem assemblyItem, string trimmedAssemblyFilePath, string originAssemblyFilePath) { - var trimmedAssemblyItem = new TaskItem(assemblyFilePath); - trimmedAssemblyItem.SetMetadata("TrimmedAssemblyFileName", trimmedAssemblyFilePath); - _trimmedAssemblies.Add(trimmedAssemblyItem); + TaskItem newAssemblyItem = new(assemblyItem); + newAssemblyItem.ItemSpec = trimmedAssemblyFilePath; + newAssemblyItem.SetMetadata("UntrimmedAssemblyFilePath", originAssemblyFilePath); + newAssemblyItem.SetMetadata("ILStripped", "true"); + return newAssemblyItem; } } diff --git a/src/tasks/WasmAppBuilder/WasmAppBuilderBaseTask.cs b/src/tasks/WasmAppBuilder/WasmAppBuilderBaseTask.cs index c533e404c4173..c580fcd80eff1 100644 --- a/src/tasks/WasmAppBuilder/WasmAppBuilderBaseTask.cs +++ b/src/tasks/WasmAppBuilder/WasmAppBuilderBaseTask.cs @@ -22,6 +22,8 @@ public abstract class WasmAppBuilderBaseTask : Task [Required] public string[] Assemblies { get; set; } = Array.Empty(); + public string? RuntimeConfigJsonPath { get; set; } + // files like dotnet.native.wasm, icudt.dat etc [NotNull] [Required] @@ -89,6 +91,15 @@ protected void ProcessSatelliteAssemblies(Action<(string fullPath, string cultur protected virtual void UpdateRuntimeConfigJson() { + if (string.IsNullOrEmpty(RuntimeConfigJsonPath)) + return; + + if (!File.Exists(RuntimeConfigJsonPath)) + { + Log.LogMessage(MessageImportance.Low, $"Could not find {nameof(RuntimeConfigJsonPath)}={RuntimeConfigJsonPath}. Ignoring."); + return; + } + string[] matchingAssemblies = Assemblies.Where(asm => Path.GetFileName(asm) == MainAssemblyName).ToArray(); if (matchingAssemblies.Length == 0) throw new LogAsErrorException($"Could not find main assembly named {MainAssemblyName} in the list of assemblies"); @@ -96,23 +107,16 @@ protected virtual void UpdateRuntimeConfigJson() if (matchingAssemblies.Length > 1) throw new LogAsErrorException($"Found more than one assembly matching the main assembly name {MainAssemblyName}: {string.Join(",", matchingAssemblies)}"); - string runtimeConfigPath = Path.ChangeExtension(matchingAssemblies[0], ".runtimeconfig.json"); - if (!File.Exists(runtimeConfigPath)) - { - Log.LogMessage(MessageImportance.Low, $"Could not find {runtimeConfigPath}. Ignoring."); - return; - } - - var rootNode = JsonNode.Parse(File.ReadAllText(runtimeConfigPath), + var rootNode = JsonNode.Parse(File.ReadAllText(RuntimeConfigJsonPath), new JsonNodeOptions { PropertyNameCaseInsensitive = true }); if (rootNode == null) - throw new LogAsErrorException($"Failed to parse {runtimeConfigPath}"); + throw new LogAsErrorException($"Failed to parse {RuntimeConfigJsonPath}"); JsonObject? rootObject = rootNode.AsObject(); if (!rootObject.TryGetPropertyValue("runtimeOptions", out JsonNode? runtimeOptionsNode) || !(runtimeOptionsNode is JsonObject runtimeOptionsObject)) { - throw new LogAsErrorException($"Could not find node named 'runtimeOptions' in {runtimeConfigPath}"); + throw new LogAsErrorException($"Could not find node named 'runtimeOptions' in {RuntimeConfigJsonPath}"); } JsonObject wasmHostProperties = runtimeOptionsObject.GetOrCreate("wasmHostProperties", () => new JsonObject()); @@ -151,13 +155,13 @@ protected virtual void UpdateRuntimeConfigJson() AddToRuntimeConfig(wasmHostProperties: wasmHostProperties, runtimeArgsArray: runtimeArgsArray, perHostConfigs: perHostConfigs); - string dstPath = Path.Combine(AppDir!, Path.GetFileName(runtimeConfigPath)); + string dstPath = Path.Combine(AppDir!, Path.GetFileName(RuntimeConfigJsonPath)); using FileStream? fs = new FileStream(dstPath, FileMode.Create, FileAccess.Write, FileShare.None); using var writer = new Utf8JsonWriter(fs, new JsonWriterOptions { Indented = true }); rootObject.WriteTo(writer); _fileWrites.Add(dstPath); - Log.LogMessage(MessageImportance.Low, $"Generated {dstPath} from {runtimeConfigPath}"); + Log.LogMessage(MessageImportance.Low, $"Generated {dstPath} from {RuntimeConfigJsonPath}"); } protected virtual void AddToRuntimeConfig(JsonObject wasmHostProperties, JsonArray runtimeArgsArray, JsonArray perHostConfigs)