Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DRAFT][mono][aot] Implementation of nollvm init method #89074

Closed
wants to merge 17 commits into from
Closed
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/mono/cmake/config.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -960,6 +960,9 @@
/* define if clockgettime exists */
#cmakedefine HAVE_CLOCK_GETTIME 1

/* work in progress: nollvm method self initialization */
#cmakedefine ENABLE_WIP_METHOD_NOLLVM_SELF_INIT 0

#if defined(ENABLE_LLVM) && defined(HOST_WIN32) && defined(TARGET_WIN32) && (!defined(TARGET_AMD64) || !defined(_MSC_VER))
#error LLVM for host=Windows and target=Windows is only supported on x64 MSVC build.
#endif
Expand Down
1 change: 1 addition & 0 deletions src/mono/cmake/options.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ option (ENABLE_SIGALTSTACK "Enable support for using sigaltstack for SIGSEGV and
option (USE_MALLOC_FOR_MEMPOOLS "Use malloc for each single mempool allocation, so tools like Valgrind can run better")
option (STATIC_COMPONENTS "Compile mono runtime components as static (not dynamic) libraries")
option (ENABLE_WEBCIL "Enable the WebCIL loader")
option (ENABLE_WIP_METHOD_NOLLVM_SELF_INIT, "Enable work in progress nollvm method self initialization")

set (MONO_GC "sgen" CACHE STRING "Garbage collector implementation (sgen or boehm). Default: sgen")
set (GC_SUSPEND "default" CACHE STRING "GC suspend method (default, preemptive, coop, hybrid)")
Expand Down
1 change: 1 addition & 0 deletions src/mono/mono/cil/cil-opcodes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -328,4 +328,5 @@
<opcode name="mono_methodconst" input="Pop0" output="PushI" args="InlineI" o1="0xF0" o2="0x21" flow="next" />
<opcode name="mono_pinvoke_addr_cache" input="Pop0" output="PushI" args="InlineI" o1="0xF0" o2="0x22" flow="next" />
<opcode name="mono_remap_ovf_exc" input="Pop0" output="Push0" args="InlineI" o1="0xF0" o2="0x23" flow="next" />
<opcode name="aot_module" input="Pop0" output="PushI" args="InlineI" o1="0xF0" o2="0x24" flow="next" />
</opdesc>
1 change: 1 addition & 0 deletions src/mono/mono/cil/opcode.def
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@ OPDEF(CEE_MONO_GET_SP, "mono_get_sp", Pop0, PushI, InlineNone, 0, 2, 0xF0, 0x20,
OPDEF(CEE_MONO_METHODCONST, "mono_methodconst", Pop0, PushI, InlineI, 0, 2, 0xF0, 0x21, NEXT)
OPDEF(CEE_MONO_PINVOKE_ADDR_CACHE, "mono_pinvoke_addr_cache", Pop0, PushI, InlineI, 0, 2, 0xF0, 0x22, NEXT)
OPDEF(CEE_MONO_REMAP_OVF_EXC, "mono_remap_ovf_exc", Pop0, Push0, InlineI, 0, 2, 0xF0, 0x23, NEXT)
OPDEF(CEE_AOT_MODULE, "aot_module", Pop0, PushI, InlineI, 0, 2, 0xF0, 0x24, NEXT)
#ifndef OPALIAS
#define _MONO_CIL_OPALIAS_DEFINED_
#define OPALIAS(a,s,r)
Expand Down
1 change: 1 addition & 0 deletions src/mono/mono/metadata/jit-icall-reg.h
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ MONO_JIT_ICALL (cominterop_type_from_handle) \
MONO_JIT_ICALL (g_free) \
MONO_JIT_ICALL (interp_to_native_trampoline) \
MONO_JIT_ICALL (mini_llvm_init_method) \
MONO_JIT_ICALL (mini_nollvm_init_method) \
MONO_JIT_ICALL (mini_llvmonly_init_delegate) \
MONO_JIT_ICALL (mini_llvmonly_init_delegate_virtual) \
MONO_JIT_ICALL (mini_llvmonly_init_vtable_slot) \
Expand Down
14 changes: 14 additions & 0 deletions src/mono/mono/metadata/marshal-lightweight.c
Original file line number Diff line number Diff line change
Expand Up @@ -3360,6 +3360,19 @@ emit_return_ilgen (MonoMethodBuilder *mb)
mono_mb_emit_byte (mb, CEE_RET);
}

static void
emit_method_init_ilgen (MonoMethodBuilder *mb)
{
// load aot_module
mono_mb_emit_op (mb, CEE_AOT_MODULE, NULL);
// load method_index
mono_mb_emit_ldarg (mb, 0);

mono_mb_emit_icall_id (mb, MONO_JIT_ICALL_mini_nollvm_init_method);

mono_mb_emit_byte (mb, CEE_RET);
}

void
mono_marshal_lightweight_init (void)
{
Expand Down Expand Up @@ -3390,6 +3403,7 @@ mono_marshal_lightweight_init (void)
cb.emit_native_icall_wrapper = emit_native_icall_wrapper_ilgen;
cb.emit_icall_wrapper = emit_icall_wrapper_ilgen;
cb.emit_return = emit_return_ilgen;
cb.emit_method_init = emit_method_init_ilgen;
cb.emit_vtfixup_ftnptr = emit_vtfixup_ftnptr_ilgen;
cb.mb_skip_visibility = mb_skip_visibility_ilgen;
cb.mb_set_dynamic = mb_set_dynamic_ilgen;
Expand Down
9 changes: 8 additions & 1 deletion src/mono/mono/metadata/marshal.c
Original file line number Diff line number Diff line change
Expand Up @@ -2948,12 +2948,12 @@ MonoMethod *
mono_marshal_get_aot_init_wrapper (MonoAotInitSubtype subtype)
{
MonoMethodBuilder *mb;
const char *name = mono_marshal_get_aot_init_wrapper_name (subtype);
MonoMethod *res;
WrapperInfo *info;
MonoMethodSignature *csig = NULL;
MonoType *void_type = mono_get_void_type ();
MonoType *int_type = mono_get_int_type ();
const char *name = mono_marshal_get_aot_init_wrapper_name (subtype);

switch (subtype) {
case AOT_INIT_METHOD:
Expand All @@ -2977,6 +2977,13 @@ mono_marshal_get_aot_init_wrapper (MonoAotInitSubtype subtype)

mb = mono_mb_new (mono_defaults.object_class, name, MONO_WRAPPER_OTHER);

/* Uncomment when working on https://github.com/dotnet/runtime/issues/82224 */
#ifdef ENABLE_WIP_METHOD_NOLLVM_SELF_INIT
#ifndef ENABLE_LLVM
get_marshal_cb ()->emit_method_init (mb);
kotlarmilos marked this conversation as resolved.
Show resolved Hide resolved
#endif
Copy link
Member

@lambdageek lambdageek Jul 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't make sense to me. ENABLE_LLVM just means we're building the runtime with LLVM support. It doesn't mean that we're using LLVM for the current AOT compilation. I think you need two different wrapper subtypes AOT_INIT_METHOD_LLVM and AOT_INIT_METHOD_NOLLVM and at the callsite you would do something like mono_marshal_get_aot_init_wrapper (cfg->compile_aot && COMPILE_LLVM(cfg) ? AOT_INIT_METHOD_LLVM : AOT_INIT_METHOD_NOLLVM)

#endif

// Just stub out the method with a "CEE_RET"
// Our codegen backend generates other code here
get_marshal_cb ()->emit_return (mb);
Expand Down
1 change: 1 addition & 0 deletions src/mono/mono/metadata/marshal.h
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,7 @@ typedef struct {
void (*emit_native_icall_wrapper) (MonoMethodBuilder *mb, MonoMethod *method, MonoMethodSignature *csig, gboolean check_exceptions, gboolean aot, MonoMethodPInvoke *pinfo);
void (*emit_icall_wrapper) (MonoMethodBuilder *mb, MonoJitICallInfo *callinfo, MonoMethodSignature *csig2, gboolean check_exceptions);
void (*emit_return) (MonoMethodBuilder *mb);
void (*emit_method_init) (MonoMethodBuilder *mb);
void (*emit_vtfixup_ftnptr) (MonoMethodBuilder *mb, MonoMethod *method, int param_count, guint16 type);
void (*mb_skip_visibility) (MonoMethodBuilder *mb);
void (*mb_set_dynamic) (MonoMethodBuilder *mb);
Expand Down
18 changes: 15 additions & 3 deletions src/mono/mono/mini/aot-compiler.c
Original file line number Diff line number Diff line change
Expand Up @@ -4461,7 +4461,7 @@ add_jit_icall_wrapper (MonoAotCompile *acfg, MonoJitICallInfo *callinfo)
add_method (acfg, mono_marshal_get_icall_wrapper (callinfo, TRUE));
}

#if ENABLE_LLVM
// #if ENABLE_LLVM
lambdageek marked this conversation as resolved.
Show resolved Hide resolved

static void
add_lazy_init_wrappers (MonoAotCompile *acfg)
Expand All @@ -4470,7 +4470,7 @@ add_lazy_init_wrappers (MonoAotCompile *acfg)
add_method (acfg, mono_marshal_get_aot_init_wrapper ((MonoAotInitSubtype)i));
}

#endif
// #endif
lambdageek marked this conversation as resolved.
Show resolved Hide resolved

static MonoMethod*
get_runtime_invoke_sig (MonoMethodSignature *sig)
Expand Down Expand Up @@ -6809,7 +6809,9 @@ emit_and_reloc_code (MonoAotCompile *acfg, MonoMethod *method, guint8 *code, gui
/*
* This is a call from a JITted method to the init wrapper emitted by LLVM.
*/
#ifndef ENABLE_WIP_METHOD_NOLLVM_SELF_INIT
g_assert (acfg->aot_opts.llvm && acfg->aot_opts.direct_extern_calls);
#endif

const char *init_name = mono_marshal_get_aot_init_wrapper_name (info->d.aot_init.subtype);
char *symbol = g_strdup_printf ("%s%s_%s", acfg->user_symbol_prefix, acfg->global_prefix, init_name);
Expand Down Expand Up @@ -7305,6 +7307,7 @@ encode_patch (MonoAotCompile *acfg, MonoJumpInfo *patch_info, guint8 *buf, guint
}
case MONO_PATCH_INFO_SEQ_POINT_INFO:
case MONO_PATCH_INFO_AOT_MODULE:
case MONO_PATCH_INFO_INIT_BITSET:
break;
case MONO_PATCH_INFO_SIGNATURE:
case MONO_PATCH_INFO_GSHAREDVT_IN_WRAPPER:
Expand Down Expand Up @@ -7466,7 +7469,8 @@ emit_method_info (MonoAotCompile *acfg, MonoCompile *cfg)
if (patch_info->type == MONO_PATCH_INFO_GC_CARD_TABLE_ADDR ||
patch_info->type == MONO_PATCH_INFO_GC_NURSERY_START ||
patch_info->type == MONO_PATCH_INFO_GC_NURSERY_BITS ||
patch_info->type == MONO_PATCH_INFO_AOT_MODULE) {
patch_info->type == MONO_PATCH_INFO_AOT_MODULE ||
patch_info->type == MONO_PATCH_INFO_INIT_BITSET) {
/* Stored in a GOT slot initialized at module load time */
patch_info->type = MONO_PATCH_INFO_NONE;
continue;
Expand Down Expand Up @@ -14531,6 +14535,10 @@ add_preinit_got_slots (MonoAotCompile *acfg)
ji->type = MONO_PATCH_INFO_AOT_MODULE;
add_preinit_slot (acfg, ji);

ji = (MonoJumpInfo *)mono_mempool_alloc0 (acfg->mempool, sizeof (MonoJumpInfo));
ji->type = MONO_PATCH_INFO_INIT_BITSET;
lambdageek marked this conversation as resolved.
Show resolved Hide resolved
add_preinit_slot (acfg, ji);

ji = (MonoJumpInfo *)mono_mempool_alloc0 (acfg->mempool, sizeof (MonoJumpInfo));
ji->type = MONO_PATCH_INFO_GC_NURSERY_BITS;
add_preinit_slot (acfg, ji);
Expand Down Expand Up @@ -15199,6 +15207,10 @@ aot_assembly (MonoAssembly *ass, guint32 jit_opts, MonoAotOptions *aot_options)
}
#endif

#ifdef ENABLE_WIP_METHOD_NOLLVM_SELF_INIT
add_lazy_init_wrappers (acfg);
#endif

if (mono_aot_mode_is_interp (&acfg->aot_opts) && mono_is_corlib_image (acfg->image->assembly->image)) {
MonoMethod *wrapper = mini_get_interp_lmf_wrapper ("mono_interp_to_native_trampoline", (gpointer) mono_interp_to_native_trampoline);
add_method (acfg, wrapper);
Expand Down
34 changes: 34 additions & 0 deletions src/mono/mono/mini/aot-runtime.c
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ struct MonoAotModule {
guint8 *unwind_info;
/* Maps method index -> unbox tramp */
gpointer *unbox_tramp_per_method;
MonoBitSet *mono_inited;

/* Points to the mono EH data created by LLVM */
guint8 *mono_eh_frame;
Expand Down Expand Up @@ -2237,6 +2238,11 @@ load_aot_module (MonoAssemblyLoadContext *alc, MonoAssembly *assembly, gpointer
mscorlib_aot_module = amodule;
}

/*
* Methods init bitset used for initialization during the runtime
*/
amodule->mono_inited = mono_bitset_new (amodule->info.nmethods, 0);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As we discussed offline, would be good to think of memory leaks. Is it allocated within the mempool?


/* Compute method addresses */
amodule->methods = (void **)g_malloc0 (amodule->info.nmethods * sizeof (gpointer));
for (guint32 i = 0; i < amodule->info.nmethods; ++i) {
Expand Down Expand Up @@ -3548,6 +3554,12 @@ sort_methods (MonoAotModule *amodule)
g_free (method_indexes);
}

MonoBitSet*
mono_aot_get_mono_inited (MonoAotModule *amodule)
{
return amodule->mono_inited;
}

/*
* mono_aot_find_jit_info:
*
Expand Down Expand Up @@ -4015,6 +4027,10 @@ decode_patch (MonoAotModule *aot_module, MonoMemPool *mp, MonoJumpInfo *ji, guin
case MONO_PATCH_INFO_AOT_MODULE:
case MONO_PATCH_INFO_MSCORLIB_GOT_ADDR:
break;
case MONO_PATCH_INFO_INIT_BITSET: {
ji->data.target = aot_module->mono_inited;
break;
}
case MONO_PATCH_INFO_SIGNATURE:
case MONO_PATCH_INFO_GSHAREDVT_IN_WRAPPER:
ji->data.target = decode_signature (aot_module, p, &p);
Expand Down Expand Up @@ -4374,6 +4390,10 @@ load_method (MonoAotModule *amodule, MonoImage *image, MonoMethod *method, guint
res = init_method (amodule, NULL, method_index, method, NULL, error);
if (!res)
goto cleanup;
#ifdef ENABLE_WIP_METHOD_NOLLVM_SELF_INIT
else
mono_bitset_set (mono_aot_get_mono_inited(amodule), method_index);
#endif
Copy link
Member

@lambdageek lambdageek Jul 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you used to have an arm64 target check here (and also that LLVM was disabled). but now you don't.

Do you need to enable ENABLE_WIP_METHOD_NOLLVM_SELF_INIT in src/mono/CMakeLists.txt if we're targeting arm64?

}
}

Expand Down Expand Up @@ -5934,6 +5954,20 @@ no_specific_trampoline (void)
g_assert_not_reached ();
}

void
mini_nollvm_init_method (MonoAotModule* amodule, guint32 method_index)
{
MonoBitSet *inited_bitset = mono_aot_get_mono_inited (amodule);

ERROR_DECL (error);
if (!mono_bitset_test (inited_bitset, method_index)) {
if (init_method (amodule, NULL, method_index, NULL, NULL, error)) {
mono_bitset_set (inited_bitset, method_index);
}
}
mono_error_assert_ok (error);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vargaz do we want to propagate this error?

}

/*
* Return a specific trampoline from the AOT file.
*/
Expand Down
3 changes: 3 additions & 0 deletions src/mono/mono/mini/aot-runtime.h
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ guint32 mono_aot_get_plt_info_offset (gpointer aot_module, guint8 *plt_en
gboolean mono_aot_get_cached_class_info (MonoClass *klass, MonoCachedClassInfo *res);
gboolean mono_aot_get_class_from_name (MonoImage *image, const char *name_space, const char *name, MonoClass **klass);
MonoJitInfo* mono_aot_find_jit_info (MonoImage *image, gpointer addr);
MonoBitSet* mono_aot_get_mono_inited (MonoAotModule *amodule);
gpointer mono_aot_plt_resolve (gpointer aot_module, host_mgreg_t *regs, guint8 *code, MonoError *error);
void mono_aot_patch_plt_entry (gpointer aot_module, guint8 *code, guint8 *plt_entry, gpointer *got, host_mgreg_t *regs, guint8 *addr);
gpointer mono_aot_get_method_from_vt_slot (MonoVTable *vtable, int slot, MonoError *error);
Expand All @@ -278,6 +279,8 @@ void mono_aot_set_make_unreadable (gboolean unreadable);
gboolean mono_aot_is_pagefault (void *ptr);
void mono_aot_handle_pagefault (void *ptr);

void mini_nollvm_init_method (MonoAotModule* amodule, guint32 method_index);

guint32 mono_aot_find_method_index (MonoMethod *method);
gboolean mono_aot_init_llvm_method (gpointer aot_module, gpointer method_info, MonoClass *init_class, MonoError *error);
GHashTable *mono_aot_get_weak_field_indexes (MonoImage *image);
Expand Down
6 changes: 6 additions & 0 deletions src/mono/mono/mini/method-to-ir.c
Original file line number Diff line number Diff line change
Expand Up @@ -11258,6 +11258,12 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b
*sp++ = ins;
break;
}
case MONO_CEE_AOT_MODULE: {
g_assert (cfg->compile_aot);
EMIT_NEW_AOTCONST (cfg, ins, MONO_PATCH_INFO_AOT_MODULE, NULL);
*sp++ = ins;
break;
}
case MONO_CEE_MONO_NOT_TAKEN:
g_assert (method->wrapper_type != MONO_WRAPPER_NONE);
cfg->cbb->out_of_line = TRUE;
Expand Down
9 changes: 9 additions & 0 deletions src/mono/mono/mini/mini-arm64.c
Original file line number Diff line number Diff line change
Expand Up @@ -5924,6 +5924,15 @@ mono_arch_emit_prolog (MonoCompile *cfg)
code = emit_addx_imm (code, cfg->arch.args_reg, ARMREG_FP, cfg->stack_offset);
}

/* Call the init wrapper which checks if the methos needs to be initialised or not */
LeVladIonescu marked this conversation as resolved.
Show resolved Hide resolved
/* https://github.com/dotnet/runtime/pull/82711, https://github.com/dotnet/runtime/issues/83378, https://github.com/dotnet/runtime/issues/83379 */
#ifdef ENABLE_WIP_METHOD_NOLLVM_SELF_INIT
if (cfg->compile_aot) {
code = emit_imm (code, ARMREG_R0, cfg->method_index);
code = emit_call (cfg, code, MONO_PATCH_INFO_METHOD, NULL, (gconstpointer) mono_marshal_get_aot_init_wrapper (AOT_INIT_METHOD));
}
#endif

/* Save return area addr received in R8 */
if (cfg->vret_addr) {
MonoInst *ins = cfg->vret_addr;
Expand Down
3 changes: 3 additions & 0 deletions src/mono/mono/mini/mini-runtime.c
Original file line number Diff line number Diff line change
Expand Up @@ -1259,6 +1259,7 @@ mono_patch_info_hash (gconstpointer data)
case MONO_PATCH_INFO_GOT_OFFSET:
case MONO_PATCH_INFO_GC_SAFE_POINT_FLAG:
case MONO_PATCH_INFO_AOT_MODULE:
case MONO_PATCH_INFO_INIT_BITSET:
case MONO_PATCH_INFO_PROFILER_ALLOCATION_COUNT:
case MONO_PATCH_INFO_PROFILER_CLAUSE_COUNT:
case MONO_PATCH_INFO_SPECIFIC_TRAMPOLINES:
Expand Down Expand Up @@ -1536,6 +1537,7 @@ mono_resolve_patch_target_ext (MonoMemoryManager *mem_manager, MonoMethod *metho
case MONO_PATCH_INFO_FIELD:
case MONO_PATCH_INFO_SIGNATURE:
case MONO_PATCH_INFO_AOT_MODULE:
case MONO_PATCH_INFO_INIT_BITSET:
target = patch_info->data.target;
break;
case MONO_PATCH_INFO_IID:
Expand Down Expand Up @@ -5083,6 +5085,7 @@ register_icalls (void)
register_dyn_icall (mono_component_debugger ()->user_break, mono_debugger_agent_user_break, mono_icall_sig_void, FALSE);

register_icall (mini_llvm_init_method, mono_icall_sig_void_ptr_ptr_ptr_ptr, TRUE);
register_icall (mini_nollvm_init_method, mono_icall_sig_void_ptr_int, TRUE);
register_icall_no_wrapper (mini_llvmonly_resolve_iface_call_gsharedvt, mono_icall_sig_ptr_object_int_ptr_ptr);
register_icall_no_wrapper (mini_llvmonly_resolve_vcall_gsharedvt, mono_icall_sig_ptr_object_int_ptr_ptr);
register_icall_no_wrapper (mini_llvmonly_resolve_vcall_gsharedvt_fast, mono_icall_sig_ptr_object_int);
Expand Down
1 change: 1 addition & 0 deletions src/mono/mono/mini/patch-info.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ PATCH_INFO(VIRT_METHOD, "virt_method")
PATCH_INFO(GC_SAFE_POINT_FLAG, "gc_safe_point_flag")
PATCH_INFO(NONE, "none")
PATCH_INFO(AOT_MODULE, "aot_module")
PATCH_INFO(INIT_BITSET, "init_bitset")
PATCH_INFO(AOT_JIT_INFO, "aot_jit_info")
PATCH_INFO(GC_NURSERY_BITS, "gc_nursery_bits")
PATCH_INFO(GSHAREDVT_IN_WRAPPER, "gsharedvt_in_wrapper")
Expand Down
2 changes: 1 addition & 1 deletion src/mono/sample/HelloWorld/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,4 @@ run: publish
$(TOP)artifacts/bin/HelloWorld/$(MONO_ARCH)/$(MONO_CONFIG)/$(TARGET_OS)-$(MONO_ARCH)/publish/HelloWorld

clean:
rm -rf $(TOP)artifacts/bin/HelloWorld/
rm -rf $(TOP)artifacts/bin/HelloWorld/
2 changes: 1 addition & 1 deletion src/mono/sample/HelloWorld/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ private static void Main(string[] args)
Console.WriteLine(System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription);
}
}
}
}