Improve tail call support on amd64 to support more methods, not just ones whose signa...
authorZoltan Varga <vargaz@gmail.com>
Tue, 16 Nov 2010 18:48:28 +0000 (19:48 +0100)
committerZoltan Varga <vargaz@gmail.com>
Tue, 16 Nov 2010 18:49:35 +0000 (19:49 +0100)
mono/mini/driver.c
mono/mini/exceptions.cs
mono/mini/method-to-ir.c
mono/mini/mini-amd64.c
mono/mini/mini-amd64.h

index 1b0c9cc4ed81eb7804ab03649099a9ae01dbadac..ce4d492a4d13572cb492b45f87b6af402d43dcad 100644 (file)
@@ -313,6 +313,7 @@ opt_sets [] = {
        MONO_OPT_BRANCH | MONO_OPT_PEEPHOLE | MONO_OPT_LINEARS | MONO_OPT_CFOLD,
        MONO_OPT_BRANCH | MONO_OPT_PEEPHOLE | MONO_OPT_LINEARS | MONO_OPT_COPYPROP | MONO_OPT_CONSPROP | MONO_OPT_DEADCE,
        MONO_OPT_BRANCH | MONO_OPT_PEEPHOLE | MONO_OPT_LINEARS | MONO_OPT_COPYPROP | MONO_OPT_CONSPROP | MONO_OPT_DEADCE | MONO_OPT_LOOP | MONO_OPT_INLINE | MONO_OPT_INTRINS,
+       MONO_OPT_BRANCH | MONO_OPT_PEEPHOLE | MONO_OPT_LINEARS | MONO_OPT_COPYPROP | MONO_OPT_CONSPROP | MONO_OPT_DEADCE | MONO_OPT_LOOP | MONO_OPT_INLINE | MONO_OPT_INTRINS | MONO_OPT_TAILC,
        MONO_OPT_BRANCH | MONO_OPT_PEEPHOLE | MONO_OPT_LINEARS | MONO_OPT_COPYPROP | MONO_OPT_CONSPROP | MONO_OPT_DEADCE | MONO_OPT_LOOP | MONO_OPT_INLINE | MONO_OPT_INTRINS | MONO_OPT_SSA,
        MONO_OPT_BRANCH | MONO_OPT_PEEPHOLE | MONO_OPT_LINEARS | MONO_OPT_COPYPROP | MONO_OPT_CONSPROP | MONO_OPT_DEADCE | MONO_OPT_LOOP | MONO_OPT_INLINE | MONO_OPT_INTRINS | MONO_OPT_EXCEPTION,
        MONO_OPT_BRANCH | MONO_OPT_PEEPHOLE | MONO_OPT_LINEARS | MONO_OPT_COPYPROP | MONO_OPT_CONSPROP | MONO_OPT_DEADCE | MONO_OPT_LOOP | MONO_OPT_INLINE | MONO_OPT_INTRINS | MONO_OPT_EXCEPTION | MONO_OPT_CMOV,
index c44f0d063a39689b70c3e05db327182cd811bd2e..b6c7a6208991666c931412d8635e39707c81e18f 100644 (file)
@@ -2240,6 +2240,8 @@ class Tests {
 
                public static void rethrow2 () {
                        rethrow1 ();
+                       /* This disables tailcall opts */
+                       Console.WriteLine ();
                }
        }
 
index 01e3b76fb2cdc82257cf2b1da32700c116e8e058..00cd7de900d7ce63066fdc02c528c61f2b13ba75 100644 (file)
@@ -5508,6 +5508,50 @@ is_jit_optimizer_disabled (MonoMethod *m)
        return val;
 }
 
+static gboolean
+is_supported_tail_call (MonoCompile *cfg, MonoMethod *method, MonoMethod *cmethod, MonoMethodSignature *fsig)
+{
+       gboolean supported_tail_call;
+       int i;
+
+#ifdef MONO_ARCH_USE_OP_TAIL_CALL
+       supported_tail_call = MONO_ARCH_USE_OP_TAIL_CALL (mono_method_signature (method), mono_method_signature (cmethod));
+#else
+       supported_tail_call = mono_metadata_signature_equal (mono_method_signature (method), mono_method_signature (cmethod)) && !MONO_TYPE_ISSTRUCT (mono_method_signature (cmethod)->ret);
+#endif
+
+       for (i = 0; i < fsig->param_count; ++i) {
+               if (fsig->params [i]->byref || fsig->params [i]->type == MONO_TYPE_PTR || fsig->params [i]->type == MONO_TYPE_FNPTR)
+                       /* These can point to the current method's stack */
+                       supported_tail_call = FALSE;
+       }
+       if (fsig->hasthis && cmethod->klass->valuetype)
+               /* this might point to the current method's stack */
+               supported_tail_call = FALSE;
+       if (cmethod->flags & METHOD_ATTRIBUTE_PINVOKE_IMPL)
+               supported_tail_call = FALSE;
+       if (cfg->method->save_lmf)
+               supported_tail_call = FALSE;
+       if (cmethod->wrapper_type && cmethod->wrapper_type != MONO_WRAPPER_DYNAMIC_METHOD)
+               supported_tail_call = FALSE;
+
+       /* Debugging support */
+#if 0
+       if (supported_tail_call) {
+               static int count = 0;
+               count ++;
+               if (getenv ("COUNT")) {
+                       if (count == atoi (getenv ("COUNT")))
+                               printf ("LAST: %s\n", mono_method_full_name (cmethod, TRUE));
+                       if (count > atoi (getenv ("COUNT")))
+                               supported_tail_call = FALSE;
+               }
+       }
+#endif
+
+       return supported_tail_call;
+}
+
 /*
  * mono_method_to_ir:
  *
@@ -6754,56 +6798,6 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b
                                break;
                        }
 
-#ifdef MONO_ARCH_USE_OP_TAIL_CALL
-                       supported_tail_call = cmethod && MONO_ARCH_USE_OP_TAIL_CALL (mono_method_signature (method), mono_method_signature (cmethod));
-#else
-                       supported_tail_call = cmethod && mono_metadata_signature_equal (mono_method_signature (method), mono_method_signature (cmethod)) && !MONO_TYPE_ISSTRUCT (mono_method_signature (cmethod)->ret);
-#endif
-
-                       /* Tail prefix */
-                       /* FIXME: runtime generic context pointer for jumps? */
-                       /* FIXME: handle this for generic sharing eventually */
-                       if ((ins_flag & MONO_INST_TAILCALL) && !cfg->generic_sharing_context && !vtable_arg && cmethod && (*ip == CEE_CALL) && supported_tail_call) {
-                               MonoCallInst *call;
-
-                               /* Prevent inlining of methods with tail calls (the call stack would be altered) */
-                               INLINE_FAILURE;
-
-#ifdef MONO_ARCH_USE_OP_TAIL_CALL
-                               /* Handle tail calls similarly to calls */
-                               call = mono_emit_call_args (cfg, mono_method_signature (cmethod), sp, FALSE, FALSE, TRUE);
-#else
-                               MONO_INST_NEW_CALL (cfg, call, OP_JMP);
-                               call->tail_call = TRUE;
-                               call->method = cmethod;
-                               call->signature = mono_method_signature (cmethod);
-
-                               /*
-                                * We implement tail calls by storing the actual arguments into the 
-                                * argument variables, then emitting a CEE_JMP.
-                                */
-                               for (i = 0; i < n; ++i) {
-                                       /* Prevent argument from being register allocated */
-                                       arg_array [i]->flags |= MONO_INST_VOLATILE;
-                                       EMIT_NEW_ARGSTORE (cfg, ins, i, sp [i]);
-                               }
-#endif
-
-                               ins = (MonoInst*)call;
-                               ins->inst_p0 = cmethod;
-                               ins->inst_p1 = arg_array [0];
-                               MONO_ADD_INS (bblock, ins);
-                               link_bblock (cfg, bblock, end_bblock);                  
-                               start_new_bblock = 1;
-
-                               CHECK_CFG_EXCEPTION;
-
-                               /* skip CEE_RET as well */
-                               ip += 6;
-                               ins_flag = 0;
-                               break;
-                       }
-
                        /*
                         * Implement a workaround for the inherent races involved in locking:
                         * Monitor.Enter ()
@@ -7040,6 +7034,68 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b
                                break;
                        }
 
+                       /* Tail prefix / tail call optimization */
+
+                       /* FIXME: Enabling TAILC breaks some inlining/stack trace/etc tests */
+                       /* FIXME: runtime generic context pointer for jumps? */
+                       /* FIXME: handle this for generic sharing eventually */
+                       supported_tail_call = cmethod && 
+                               ((((ins_flag & MONO_INST_TAILCALL) && (*ip == CEE_CALL))
+                                 ))//|| ((cfg->opt & MONO_OPT_TAILC) && *ip == CEE_CALL && ip [5] == CEE_RET))
+                               && !vtable_arg && !cfg->generic_sharing_context && is_supported_tail_call (cfg, method, cmethod, fsig);
+
+                       if (supported_tail_call) {
+                               MonoCallInst *call;
+
+                               /* Prevent inlining of methods with tail calls (the call stack would be altered) */
+                               INLINE_FAILURE;
+
+                               //printf ("HIT: %s -> %s\n", mono_method_full_name (cfg->method, TRUE), mono_method_full_name (cmethod, TRUE));
+
+#ifdef MONO_ARCH_USE_OP_TAIL_CALL
+                               /* Handle tail calls similarly to calls */
+                               call = mono_emit_call_args (cfg, mono_method_signature (cmethod), sp, FALSE, FALSE, TRUE);
+#else
+                               MONO_INST_NEW_CALL (cfg, call, OP_JMP);
+                               call->tail_call = TRUE;
+                               call->method = cmethod;
+                               call->signature = mono_method_signature (cmethod);
+
+                               /*
+                                * We implement tail calls by storing the actual arguments into the 
+                                * argument variables, then emitting a CEE_JMP.
+                                */
+                               for (i = 0; i < n; ++i) {
+                                       /* Prevent argument from being register allocated */
+                                       arg_array [i]->flags |= MONO_INST_VOLATILE;
+                                       EMIT_NEW_ARGSTORE (cfg, ins, i, sp [i]);
+                               }
+#endif
+
+                               ins = (MonoInst*)call;
+                               ins->inst_p0 = cmethod;
+                               ins->inst_p1 = arg_array [0];
+                               MONO_ADD_INS (bblock, ins);
+                               link_bblock (cfg, bblock, end_bblock);                  
+                               start_new_bblock = 1;
+
+                               CHECK_CFG_EXCEPTION;
+
+                               ip += 5;
+                               ins_flag = 0;
+
+                               // FIXME: Eliminate unreachable epilogs
+
+                               /*
+                                * OP_TAILCALL has no return value, so skip the CEE_RET if it is
+                                * only reachable from this call.
+                                */
+                               GET_BBLOCK (cfg, tblock, ip);
+                               if (tblock == bblock || tblock->in_count == 0)
+                                       ip += 1;
+                               break;
+                       }
+
                        /* Common call */
                        INLINE_FAILURE;
                        if (vtable_arg) {
index b9c5b570f63a935b414b3055811c12a96a2f74b9..6d0aaf905126b0e983d5624775840c9cb1b9db88 100644 (file)
@@ -882,6 +882,25 @@ mono_arch_get_argument_info (MonoMethodSignature *csig, int param_count, MonoJit
        return args_size;
 }
 
+gboolean
+mono_amd64_tail_call_supported (MonoMethodSignature *caller_sig, MonoMethodSignature *callee_sig)
+{
+       CallInfo *c1, *c2;
+       gboolean res;
+
+       c1 = get_call_info (NULL, NULL, caller_sig, FALSE);
+       c2 = get_call_info (NULL, NULL, callee_sig, FALSE);
+       res = c1->stack_usage >= c2->stack_usage;
+       if (callee_sig->ret && MONO_TYPE_ISSTRUCT (callee_sig->ret) && c2->ret.storage != ArgValuetypeInReg)
+               /* An address on the callee's stack is passed as the first argument */
+               res = FALSE;
+
+       g_free (c1);
+       g_free (c2);
+
+       return res;
+}
+
 static int 
 cpuid (int id, int* p_eax, int* p_ebx, int* p_ecx, int* p_edx)
 {
@@ -4055,10 +4074,7 @@ mono_arch_output_basic_block (MonoCompile *cfg, MonoBasicBlock *bb)
                        break;
                }
                case OP_TAILCALL: {
-                       /*
-                        * Note: this 'frame destruction' logic is useful for tail calls, too.
-                        * Keep in sync with the code in emit_epilog.
-                        */
+                       MonoCallInst *call = (MonoCallInst*)ins;
                        int pos = 0, i;
 
                        /* FIXME: no tracing support... */
@@ -4076,20 +4092,32 @@ mono_arch_output_basic_block (MonoCompile *cfg, MonoBasicBlock *bb)
                                                save_offset += 8;
                                        }
                                amd64_alu_reg_imm (code, X86_ADD, AMD64_RSP, cfg->arch.stack_alloc_size);
+
+                               // FIXME:
+                               if (call->stack_usage)
+                                       NOT_IMPLEMENTED;
                        }
                        else {
                                for (i = 0; i < AMD64_NREG; ++i)
                                        if (AMD64_IS_CALLEE_SAVED_REG (i) && (cfg->used_int_regs & (1 << i)))
                                                pos -= sizeof (gpointer);
-                       
-                               if (pos)
-                                       amd64_lea_membase (code, AMD64_RSP, AMD64_RBP, pos);
 
-                               /* Pop registers in reverse order */
-                               for (i = AMD64_NREG - 1; i > 0; --i)
+                               /* Restore callee-saved registers */
+                               for (i = AMD64_NREG - 1; i > 0; --i) {
                                        if (AMD64_IS_CALLEE_SAVED_REG (i) && (cfg->used_int_regs & (1 << i))) {
-                                               amd64_pop_reg (code, i);
+                                               amd64_mov_reg_membase (code, i, AMD64_RBP, pos, 8);
+                                               pos += 8;
                                        }
+                               }
+
+                               /* Copy arguments on the stack to our argument area */
+                               for (i = 0; i < call->stack_usage; i += 8) {
+                                       amd64_mov_reg_membase (code, AMD64_RAX, AMD64_RSP, i, 8);
+                                       amd64_mov_membase_reg (code, AMD64_RBP, 16 + i, AMD64_RAX, 8);
+                               }
+                       
+                               if (pos)
+                                       amd64_lea_membase (code, AMD64_RSP, AMD64_RBP, pos);
 
                                amd64_leave (code);
                        }
index c2f8a8637434eae8ec072017eddce2ba47a3c8e6..8a2da94a4fda63b26c4000f688083e4916dc2b72 100644 (file)
@@ -392,7 +392,10 @@ typedef struct {
 #define MONO_ARCH_HAVE_CARD_TABLE_WBARRIER 1
 
 
-#define MONO_ARCH_USE_OP_TAIL_CALL(caller_sig, callee_sig) mono_metadata_signature_equal ((caller_sig), (callee_sig))
+gboolean
+mono_amd64_tail_call_supported (MonoMethodSignature *caller_sig, MonoMethodSignature *callee_sig) MONO_INTERNAL;
+
+#define MONO_ARCH_USE_OP_TAIL_CALL(caller_sig, callee_sig) mono_amd64_tail_call_supported (caller_sig, callee_sig)
 
 /* Used for optimization, not complete */
 #define MONO_ARCH_IS_OP_MEMBASE(opcode) ((opcode) == OP_X86_PUSH_MEMBASE)