[jit] Implement proper tail call support on arm. Fixes #17654.
authorZoltan Varga <vargaz@gmail.com>
Sat, 1 Mar 2014 00:01:07 +0000 (19:01 -0500)
committerZoltan Varga <vargaz@gmail.com>
Sat, 1 Mar 2014 00:01:16 +0000 (19:01 -0500)
mono/mini/cpu-arm.md
mono/mini/iltests.il.in
mono/mini/mini-arm.c
mono/mini/mini-arm.h

index f47a3738a68025f87f21c2a98571d4781f413011..e0db648ab66c6b7daeb38b191f596a063184a8d3 100644 (file)
@@ -94,6 +94,7 @@ lcall_membase: dest:l src1:b len:16 clob:c
 vcall: len:20 clob:c
 vcall_reg: src1:i len:8 clob:c
 vcall_membase: src1:b len:16 clob:c
+tailcall: len:160 clob:c
 iconst: dest:i len:16
 r4const: dest:f len:24
 r8const: dest:f len:20
index 2ed020c9ce00b9cb53f4032c0b83dece0b4b173c..7da5f03532edf1adc68da5ffd2bd78930210209d 100644 (file)
@@ -2037,6 +2037,8 @@ HAS_VALUE:        ldc.i4.1
        // Code size 34 (0x22)
        .maxstack 17
        IL_0000:  ldarg.0 
+                         ldc.i4.1
+                         add
        IL_0001:  ldarg.1 
        IL_0002:  ldarg.2 
        IL_0003:  ldarg.3 
@@ -2051,7 +2053,11 @@ HAS_VALUE:       ldc.i4.1
        IL_0014:  ldarg.s 12
        IL_0016:  ldarg.s 13
        IL_0018:  ldarg.s 14
+                         ldc.i4.1
+                         add
        IL_001a:  ldarg.s 15
+                         ldc.i4.1
+                         add
                  tail.
        IL_001c:  call int32 class Tests::manyargs_callee(int32, int32, int32, int32, int32, int32, int32, int32, int32, int32, int32, int32, int32, int32, int32, int32)
        IL_0021:  ret 
@@ -2059,7 +2065,7 @@ HAS_VALUE:        ldc.i4.1
 
     // method line 4
     .method public static  hidebysig 
-           default int32 test_0_many_args_tail_call ()  cil managed 
+           default int32 test_139_many_args_tail_call ()  cil managed
     {
                // Some platforms might not be able to AOT tail calls
                .custom instance void class [TestDriver]CategoryAttribute::'.ctor'(string) =  (01 00 08 21 46 55 4C 4C 41 4F 54 00 00 ) // ...!FULLAOT..
@@ -2084,13 +2090,7 @@ HAS_VALUE:       ldc.i4.1
        IL_0014:  ldc.i4.s 0x0f
        IL_0016:  ldc.i4.s 0x10
        IL_0018:  call int32 class Tests::manyargs_tail_caller(int32, int32, int32, int32, int32, int32, int32, int32, int32, int32, int32, int32, int32, int32, int32, int32)
-       IL_001d:  ldc.i4 136
-       IL_0022:  beq IL_0029
-
-       IL_0027:  ldc.i4.1 
        IL_0028:  ret 
-       IL_0029:  ldc.i4.0 
-       IL_002a:  ret 
     } // end of method main::Main
 
        .class nested private auto ansi beforefieldinit R1
index e83a58b7881c993c5f54c6633dc0c01d07c2608c..cb0e7f8aa96f1cb0c3211c6370a8571f5a6e0c57 100644 (file)
@@ -1694,6 +1694,40 @@ get_call_info (MonoGenericSharingContext *gsctx, MonoMemPool *mp, MonoMethodSign
        return cinfo;
 }
 
+
+gboolean
+mono_arch_tail_call_supported (MonoCompile *cfg, MonoMethodSignature *caller_sig, MonoMethodSignature *callee_sig)
+{
+       MonoType *callee_ret;
+       CallInfo *c1, *c2;
+       gboolean res;
+
+       if (cfg->compile_aot && !cfg->full_aot)
+               /* OP_TAILCALL doesn't work with AOT */
+               return FALSE;
+
+       c1 = get_call_info (NULL, NULL, caller_sig);
+       c2 = get_call_info (NULL, NULL, callee_sig);
+
+       /*
+        * Tail calls with more callee stack usage than the caller cannot be supported, since
+        * the extra stack space would be left on the stack after the tail call.
+        */
+       res = c1->stack_usage >= c2->stack_usage;
+       callee_ret = mini_replace_type (callee_sig->ret);
+       if (callee_ret && MONO_TYPE_ISSTRUCT (callee_ret) && c2->ret.storage != RegTypeStructByVal)
+               /* An address on the callee's stack is passed as the first argument */
+               res = FALSE;
+
+       if (c2->stack_usage > 16 * 4)
+               res = FALSE;
+
+       g_free (c1);
+       g_free (c2);
+
+       return res;
+}
+
 #ifndef DISABLE_JIT
 
 static gboolean
@@ -4638,6 +4672,66 @@ mono_arch_output_basic_block (MonoCompile *cfg, MonoBasicBlock *bb)
                                code = mono_arm_patchable_b (code, ARMCOND_AL);
                        }
                        break;
+               case OP_TAILCALL: {
+                       MonoCallInst *call = (MonoCallInst*)ins;
+
+                       /*
+                        * The stack looks like the following:
+                        * <caller argument area>
+                        * <saved regs etc>
+                        * <rest of frame>
+                        * <callee argument area>
+                        * Need to copy the arguments from the callee argument area to
+                        * the caller argument area, and pop the frame.
+                        */
+                       if (call->stack_usage) {
+                               int i, prev_sp_offset = 0;
+
+                               /* Compute size of saved registers restored below */
+                               if (iphone_abi)
+                                       prev_sp_offset = 2 * 4;
+                               else
+                                       prev_sp_offset = 1 * 4;
+                               for (i = 0; i < 16; ++i) {
+                                       if (cfg->used_int_regs & (1 << i))
+                                               prev_sp_offset += 4;
+                               }
+
+                               code = emit_big_add (code, ARMREG_IP, cfg->frame_reg, cfg->stack_usage + prev_sp_offset);
+
+                               /* Copy arguments on the stack to our argument area */
+                               for (i = 0; i < call->stack_usage; i += sizeof (mgreg_t)) {
+                                       ARM_LDR_IMM (code, ARMREG_LR, ARMREG_SP, i);
+                                       ARM_STR_IMM (code, ARMREG_LR, ARMREG_IP, i);
+                               }
+                       }
+
+                       /*
+                        * Keep in sync with mono_arch_emit_epilog
+                        */
+                       g_assert (!cfg->method->save_lmf);
+
+                       code = emit_big_add (code, ARMREG_SP, cfg->frame_reg, cfg->stack_usage);
+                       if (iphone_abi) {
+                               if (cfg->used_int_regs)
+                                       ARM_POP (code, cfg->used_int_regs);
+                               ARM_POP (code, (1 << ARMREG_R7) | (1 << ARMREG_LR));
+                       } else {
+                               ARM_POP (code, cfg->used_int_regs | (1 << ARMREG_LR));
+                       }
+
+                       mono_add_patch_info (cfg, (guint8*) code - cfg->native_code, MONO_PATCH_INFO_METHOD_JUMP, call->method);
+                       if (cfg->compile_aot) {
+                               ARM_LDR_IMM (code, ARMREG_IP, ARMREG_PC, 0);
+                               ARM_B (code, 0);
+                               *(gpointer*)code = NULL;
+                               code += 4;
+                               ARM_LDR_REG_REG (code, ARMREG_PC, ARMREG_PC, ARMREG_IP);
+                       } else {
+                               code = mono_arm_patchable_b (code, ARMCOND_AL);
+                       }
+                       break;
+               }
                case OP_CHECK_THIS:
                        /* ensure ins->sreg1 is not NULL */
                        ARM_LDRB_IMM (code, ARMREG_LR, ins->sreg1, 0);
index 9fcd6545d267adb5558ecd8eeb7ed03e1895c78f..b815804a4b327fccc711366891738374d10cd2a6 100644 (file)
@@ -258,6 +258,9 @@ typedef struct MonoCompileArch {
 #define MONO_ARCH_HAVE_GENERAL_RGCTX_LAZY_FETCH_TRAMPOLINE 1
 #define MONO_ARCH_HAVE_OPCODE_NEEDS_EMULATION 1
 #define MONO_ARCH_HAVE_OBJC_GET_SELECTOR 1
+#ifdef __linux__
+#define MONO_ARCH_HAVE_OP_TAIL_CALL 1
+#endif
 
 #if defined(__native_client__)
 #undef MONO_ARCH_SOFT_DEBUG_SUPPORTED