[runtime] Add some support for stack walks for AOT code on wasm. (#44431)
authormonojenkins <jo.shields+jenkins@xamarin.com>
Tue, 10 Nov 2020 17:02:16 +0000 (12:02 -0500)
committerGitHub <noreply@github.com>
Tue, 10 Nov 2020 17:02:16 +0000 (12:02 -0500)
For methods which need to show up during stack walks, push/pop an LMF
frame and have mono_arch_unwind_frame () handle it.

This is needed to be able to support methods which do stack walks like
Type.GetType () or Assembly.Load () in the future.

Co-authored-by: vargaz <vargaz@users.noreply.github.com>
src/mono/mono/metadata/object-offsets.h
src/mono/mono/mini/exceptions-wasm.c
src/mono/mono/mini/method-to-ir.c
src/mono/mono/mini/mini-exceptions.c
src/mono/mono/mini/mini-llvm.c
src/mono/mono/mini/mini-runtime.h
src/mono/mono/mini/mini-wasm.c
src/mono/mono/mini/mini-wasm.h

index f11bbf5..ab8fb9e 100644 (file)
@@ -189,6 +189,7 @@ DECL_OFFSET(MonoContext, wasm_sp)
 DECL_OFFSET(MonoContext, llvm_exc_reg)
 
 DECL_OFFSET(MonoLMF, lmf_addr)
+DECL_OFFSET(MonoLMF, method)
 
 #elif defined(TARGET_X86)
 DECL_OFFSET(MonoContext, eax)
index 605474c..f05aa98 100644 (file)
@@ -1,3 +1,4 @@
+#include "mini-runtime.h"
 #include "mini.h"
 
 static void
@@ -38,18 +39,43 @@ wasm_throw_corlib_exception (void)
 
 gboolean
 mono_arch_unwind_frame (MonoDomain *domain, MonoJitTlsData *jit_tls, 
-                                                        MonoJitInfo *ji, MonoContext *ctx, 
-                                                        MonoContext *new_ctx, MonoLMF **lmf,
-                                                        host_mgreg_t **save_locations,
-                                                        StackFrameInfo *frame)
+                                               MonoJitInfo *ji, MonoContext *ctx,
+                                               MonoContext *new_ctx, MonoLMF **lmf,
+                                               host_mgreg_t **save_locations,
+                                               StackFrameInfo *frame)
 {
-       if (ji)
-               g_error ("Can't unwind compiled code");
+       memset (frame, 0, sizeof (StackFrameInfo));
+       frame->ji = ji;
 
+       *new_ctx = *ctx;
+
+       g_assert (!ji);
+
+       /*
+        * Can't unwind native frames on WASM, so we only process the ones
+        * which push an LMF frame. See the needs_stack_walk code in
+        * method-to-ir.c.
+        */
        if (*lmf) {
-               if ((*lmf)->top_entry)
+               ERROR_DECL (error);
+
+               if (*lmf == jit_tls->first_lmf)
                        return FALSE;
-               g_error ("Can't handle non-top-entry LMFs\n");
+
+               /* This will compute the original method address */
+               g_assert ((*lmf)->method);
+               gpointer addr = mono_compile_method_checked ((*lmf)->method, error);
+               mono_error_assert_ok (error);
+
+               ji = mini_jit_info_table_find (domain, addr, NULL);
+               g_assert (ji);
+
+               frame->type = FRAME_TYPE_MANAGED;
+               frame->ji = ji;
+               frame->actual_method = (*lmf)->method;
+
+               *lmf = (MonoLMF *)(((guint64)(*lmf)->previous_lmf) & ~3);
+               return TRUE;
        }
 
        return FALSE;
index 3818622..6829bcb 100644 (file)
@@ -1790,6 +1790,13 @@ emit_push_lmf (MonoCompile *cfg)
        if (!cfg->lmf_addr_var)
                cfg->lmf_addr_var = mono_compile_create_var (cfg, mono_get_int_type (), OP_LOCAL);
 
+       if (!cfg->lmf_var) {
+               MonoInst *lmf_var = mono_compile_create_var (cfg, mono_get_int_type (), OP_LOCAL);
+               lmf_var->flags |= MONO_INST_VOLATILE;
+               lmf_var->flags |= MONO_INST_LMF;
+               cfg->lmf_var = lmf_var;
+       }
+
        lmf_ins = mono_create_tls_get (cfg, TLS_KEY_LMF_ADDR);
        g_assert (lmf_ins);
 
@@ -7139,6 +7146,7 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b
                        gboolean direct_icall; direct_icall = FALSE;
                        gboolean tailcall_calli; tailcall_calli = FALSE;
                        gboolean noreturn; noreturn = FALSE;
+                       gboolean needs_stack_walk; needs_stack_walk = FALSE;
 
                        // Variables shared by CEE_CALLI and CEE_CALL/CEE_CALLVIRT.
                        common_call = FALSE;
@@ -7224,6 +7232,9 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b
                        if (mono_security_core_clr_enabled ())
                                ensure_method_is_allowed_to_call_method (cfg, method, cil_method);
 
+                       if (cfg->llvm_only && cmethod && method_needs_stack_walk (cfg, cmethod))
+                               needs_stack_walk = TRUE;
+
                        if (!virtual_ && (cmethod->flags & METHOD_ATTRIBUTE_ABSTRACT)) {
                                if (!mono_class_is_interface (method->klass))
                                        emit_bad_image_failure (cfg, method, cil_method);
@@ -7923,6 +7934,23 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b
                                INLINE_FAILURE ("call");
                        common_call = TRUE;
 
+#ifdef TARGET_WASM
+                       /* Push an LMF so these frames can be enumerated during stack walks by mono_arch_unwind_frame () */
+                       if (needs_stack_walk) {
+                               MonoInst *method_ins;
+                               int lmf_reg;
+
+                               emit_push_lmf (cfg);
+
+                               EMIT_NEW_VARLOADA (cfg, ins, cfg->lmf_var, NULL);
+                               lmf_reg = ins->dreg;
+
+                               /* The lmf->method field will be used to look up the MonoJitInfo for this method */
+                               method_ins = emit_get_rgctx_method (cfg, mono_method_check_context_used (cfg->method), cfg->method, MONO_RGCTX_INFO_METHOD);
+                               EMIT_NEW_STORE_MEMBASE (cfg, ins, OP_STORE_MEMBASE_REG, lmf_reg, MONO_STRUCT_OFFSET (MonoLMF, method), method_ins->dreg);
+                       }
+#endif
+
 call_end:
                        // Check that the decision to tailcall would not have changed.
                        g_assert (!called_is_supported_tailcall || tailcall_method == method);
@@ -7944,6 +7972,11 @@ call_end:
                        if (cmethod)
                                ins = handle_call_res_devirt (cfg, cmethod, ins);
 
+#ifdef TARGET_WASM
+                       if (common_call && needs_stack_walk)
+                               emit_pop_lmf (cfg);
+#endif
+
                        if (noreturn) {
                                MONO_INST_NEW (cfg, ins, OP_NOT_REACHED);
                                MONO_ADD_INS (cfg->cbb, ins);
index 19d8b2b..b26c633 100644 (file)
@@ -1365,10 +1365,12 @@ mono_walk_stack_full (MonoJitStackWalk func, MonoContext *start_ctx, MonoDomain
 
                frame.il_offset = il_offset;
 
-               if ((unwind_options & MONO_UNWIND_LOOKUP_ACTUAL_METHOD) && frame.ji) {
-                       frame.actual_method = get_method_from_stack_frame (frame.ji, get_generic_info_from_stack_frame (frame.ji, &ctx));
-               } else {
-                       frame.actual_method = frame.method;
+               /* actual_method might already be set by mono_arch_unwind_frame () */
+               if (!frame.actual_method) {
+                       if ((unwind_options & MONO_UNWIND_LOOKUP_ACTUAL_METHOD) && frame.ji)
+                               frame.actual_method = get_method_from_stack_frame (frame.ji, get_generic_info_from_stack_frame (frame.ji, &ctx));
+                       else
+                               frame.actual_method = frame.method;
                }
 
                if (get_reg_locations)
index 73d77af..fa8703c 100644 (file)
@@ -3543,7 +3543,10 @@ emit_entry_bb (EmitContext *ctx, LLVMBuilderRef builder)
                                return;
                        /* Could be already created by an OP_VPHI */
                        if (!ctx->addresses [var->dreg]) {
-                               ctx->addresses [var->dreg] = build_alloca (ctx, var->inst_vtype);
+                               if (var->flags & MONO_INST_LMF)
+                                       ctx->addresses [var->dreg] = build_alloca_llvm_type (ctx, LLVMArrayType (LLVMInt8Type (), MONO_ABI_SIZEOF (MonoLMF)), sizeof (target_mgreg_t));
+                               else
+                                       ctx->addresses [var->dreg] = build_alloca (ctx, var->inst_vtype);
                                //LLVMSetValueName (ctx->addresses [var->dreg], g_strdup_printf ("vreg_loc_%d", var->dreg));
                        }
                        ctx->vreg_cli_types [var->dreg] = var->inst_vtype;
@@ -10143,6 +10146,8 @@ mono_llvm_create_vars (MonoCompile *cfg)
        } else {
                mono_arch_create_vars (cfg);
        }
+
+       cfg->lmf_ir = TRUE;
 }
 
 /*
index 47540a1..58e5a84 100644 (file)
@@ -148,6 +148,24 @@ struct MonoJitTlsData {
 #define MONO_LMFEXT_INTERP_EXIT_WITH_CTX 3
 
 /*
+ * The MonoLMF structure is arch specific, it includes at least these fields.
+ * LMF means 'last-managed-frame'. Originally, these were allocated
+ * on the stack to mark the last frame before transitioning to
+ * native code, but currently, they are used to mark all kinds of
+ * other transitions as well, see MonoLMFExt.
+ */
+#if 0
+typedef struct {
+       /*
+        * If the second lowest bit is set to 1, then this is a MonoLMFExt structure, and
+        * the other fields are not valid.
+        */
+       gpointer previous_lmf;
+       gpointer lmf_addr;
+} MonoLMF;
+#endif
+
+/*
  * This structure is an extension of MonoLMF and contains extra information.
  */
 typedef struct {
index 8c4ff8a..2cf652d 100644 (file)
@@ -226,11 +226,10 @@ mono_arch_create_vars (MonoCompile *cfg)
        if (cfg->gen_sdb_seq_points)
                g_error ("gen_sdb_seq_points not supported");
 
-       if (cfg->method->save_lmf)
+       if (cfg->method->save_lmf) {
                cfg->create_lmf_var = TRUE;
-
-       if (cfg->method->save_lmf)
                cfg->lmf_ir = TRUE;
+       }
 }
 
 void
index 26a97ff..95c5995 100644 (file)
@@ -52,15 +52,14 @@ struct MonoLMF {
        gpointer previous_lmf;
        gpointer lmf_addr;
 
-       /* This is set to signal this is the top lmf entry */
-       gboolean top_entry;
+       MonoMethod *method;
 };
 
 typedef struct {
        gpointer cinfo;
 } MonoCompileArch;
 
-#define MONO_ARCH_INIT_TOP_LMF_ENTRY(lmf) do { (lmf)->top_entry = TRUE; } while (0)
+#define MONO_ARCH_INIT_TOP_LMF_ENTRY(lmf) do { } while (0)
 
 #define MONO_CONTEXT_SET_LLVM_EXC_REG(ctx, exc) do { (ctx)->llvm_exc_reg = (gsize)exc; } while (0)