2007-08-30 Jonathan Chambers <joncham@gmail.com>
[mono.git] / mono / mini / mini-arm.c
index 17e1fce38af12575f0a3d49293788ac19c20d745..68a3e6c9aa708023cfbd4b2f811e86b98d729678 100644 (file)
@@ -23,6 +23,9 @@
 #include "mono/arch/arm/arm-vfp-codegen.h"
 #endif
 
+static int v5_supported = 0;
+static int thumb_supported = 0;
+
 static int mono_arm_is_rotated_imm8 (guint32 val, gint *rot_amount);
 
 /*
@@ -131,6 +134,21 @@ emit_memcpy (guint8 *code, int size, int dreg, int doffset, int sreg, int soffse
        return code;
 }
 
+static guint8*
+emit_call_reg (guint8 *code, int reg)
+{
+       if (v5_supported) {
+               ARM_BLX_REG (code, reg);
+       } else {
+               ARM_MOV_REG_REG (code, ARMREG_LR, ARMREG_PC);
+               if (thumb_supported)
+                       ARM_BX (code, reg);
+               else
+                       ARM_MOV_REG_REG (code, ARMREG_PC, reg);
+       }
+       return code;
+}
+
 /*
  * mono_arch_get_argument_info:
  * @csig:  a method signature
@@ -205,6 +223,31 @@ guint32
 mono_arch_cpu_optimizazions (guint32 *exclude_mask)
 {
        guint32 opts = 0;
+       char buf [512];
+       char *line;
+       FILE *file = fopen ("/proc/cpuinfo", "r");
+       if (file) {
+               while ((line = fgets (buf, 512, file))) {
+                       if (strncmp (line, "Processor", 9) == 0) {
+                               char *ver = strstr (line, "(v");
+                               if (ver && (ver [2] == '5' || ver [2] == '6' || ver [2] == '7')) {
+                                       v5_supported = TRUE;
+                               }
+                               continue;
+                       }
+                       if (strncmp (line, "Features", 8) == 0) {
+                               char *th = strstr (line, "thumb");
+                               if (th) {
+                                       thumb_supported = TRUE;
+                                       if (v5_supported)
+                                               break;
+                               }
+                               continue;
+                       }
+               }
+               fclose (file);
+               /*printf ("features: v5: %d, thumb: %d\n", v5_supported, thumb_supported);*/
+       }
 
        /* no arm-specific optimizations yet */
        *exclude_mask = 0;
@@ -318,6 +361,7 @@ mono_arch_flush_icache (guint8 *code, gint size)
 enum {
        RegTypeGeneral,
        RegTypeBase,
+       RegTypeBaseGen,
        RegTypeFP,
        RegTypeStructByVal,
        RegTypeStructByAddr
@@ -355,16 +399,30 @@ add_general (guint *gr, guint *stack_size, ArgInfo *ainfo, gboolean simple)
                        ainfo->reg = *gr;
                }
        } else {
-               if (*gr > ARMREG_R2) {
-                       /**stack_size += 7;
-                       *stack_size &= ~7;*/
+               if (*gr == ARMREG_R3
+#ifdef __ARM_EABI__
+                               && 0
+#endif
+                                       ) {
+                       /* first word in r3 and the second on the stack */
+                       ainfo->offset = *stack_size;
+                       ainfo->reg = ARMREG_SP; /* in the caller */
+                       ainfo->regtype = RegTypeBaseGen;
+                       *stack_size += 4;
+               } else if (*gr > ARMREG_R3) {
+#ifdef __ARM_EABI__
+                       *stack_size += 7;
+                       *stack_size &= ~7;
+#endif
                        ainfo->offset = *stack_size;
                        ainfo->reg = ARMREG_SP; /* in the caller */
                        ainfo->regtype = RegTypeBase;
                        *stack_size += 8;
                } else {
-                       /*if ((*gr) & 1)
-                               (*gr) ++;*/
+#ifdef __ARM_EABI__
+                       if ((*gr) & 1)
+                               (*gr) ++;
+#endif
                        ainfo->reg = *gr;
                }
                (*gr) ++;
@@ -688,7 +746,7 @@ mono_arch_allocate_vars (MonoCompile *m)
 
        curinst = 0;
        if (sig->hasthis) {
-               inst = m->varinfo [curinst];
+               inst = m->args [curinst];
                if (inst->opcode != OP_REGVAR) {
                        inst->opcode = OP_REGOFFSET;
                        inst->inst_basereg = frame_reg;
@@ -703,7 +761,7 @@ mono_arch_allocate_vars (MonoCompile *m)
        }
 
        for (i = 0; i < sig->param_count; ++i) {
-               inst = m->varinfo [curinst];
+               inst = m->args [curinst];
                if (inst->opcode != OP_REGVAR) {
                        inst->opcode = OP_REGOFFSET;
                        inst->inst_basereg = frame_reg;
@@ -796,7 +854,9 @@ mono_arch_call_opcode (MonoCompile *cfg, MonoBasicBlock* bb, MonoCallInst *call,
                                        call->used_iregs |= 1 << (ainfo->reg + 1);
                                if (arg->type == STACK_R8) {
                                        if (ainfo->size == 4) {
+#ifndef MONO_ARCH_SOFT_FLOAT
                                                arg->opcode = OP_OUTARG_R4;
+#endif
                                        } else {
                                                call->used_iregs |= 1 << (ainfo->reg + 1);
                                        }
@@ -820,6 +880,12 @@ mono_arch_call_opcode (MonoCompile *cfg, MonoBasicBlock* bb, MonoCallInst *call,
                        } else if (ainfo->regtype == RegTypeBase) {
                                arg->opcode = OP_OUTARG_MEMBASE;
                                arg->backend.arg_info = (ainfo->offset << 8) | ainfo->size;
+                       } else if (ainfo->regtype == RegTypeBaseGen) {
+                               call->used_iregs |= 1 << ARMREG_R3;
+                               arg->opcode = OP_OUTARG_MEMBASE;
+                               arg->backend.arg_info = (ainfo->offset << 8) | 0xff;
+                               if (arg->type == STACK_R8)
+                                       cfg->flags |= MONO_CFG_HAS_FPOUT;
                        } else if (ainfo->regtype == RegTypeFP) {
                                arg->backend.reg3 = ainfo->reg;
                                /* FP args are passed in int regs */
@@ -873,8 +939,7 @@ mono_arch_instrument_prolog (MonoCompile *cfg, void *func, void *p, gboolean ena
        code = mono_arm_emit_load_imm (code, ARMREG_R0, (guint32)cfg->method);
        ARM_MOV_REG_IMM8 (code, ARMREG_R1, 0); /* NULL ebp for now */
        code = mono_arm_emit_load_imm (code, ARMREG_R2, (guint32)func);
-       ARM_MOV_REG_REG (code, ARMREG_LR, ARMREG_PC);
-       ARM_MOV_REG_REG (code, ARMREG_PC, ARMREG_R2);
+       code = emit_call_reg (code, ARMREG_R2);
        return code;
 }
 
@@ -963,8 +1028,7 @@ mono_arch_instrument_epilog (MonoCompile *cfg, void *func, void *p, gboolean ena
 
        code = mono_arm_emit_load_imm (code, ARMREG_R0, (guint32)cfg->method);
        code = mono_arm_emit_load_imm (code, ARMREG_IP, (guint32)func);
-       ARM_MOV_REG_REG (code, ARMREG_LR, ARMREG_PC);
-       ARM_MOV_REG_REG (code, ARMREG_PC, ARMREG_IP);
+       code = emit_call_reg (code, ARMREG_IP);
 
        switch (save_mode) {
        case SAVE_TWO:
@@ -1117,15 +1181,8 @@ peephole_pass (MonoCompile *cfg, MonoBasicBlock *bb)
                        if (last_ins && (last_ins->opcode == OP_STOREI1_MEMBASE_REG) &&
                                        ins->inst_basereg == last_ins->inst_destbasereg &&
                                        ins->inst_offset == last_ins->inst_offset) {
-                               if (ins->dreg == last_ins->sreg1) {
-                                       last_ins->next = ins->next;                             
-                                       ins = ins->next;                                
-                                       continue;
-                               } else {
-                                       //static int c = 0; printf ("MATCHX %s %d\n", cfg->method->name,c++);
-                                       ins->opcode = OP_MOVE;
-                                       ins->sreg1 = last_ins->sreg1;
-                               }
+                               ins->opcode = (ins->opcode == OP_LOADI1_MEMBASE) ? CEE_CONV_I1 : CEE_CONV_U1;
+                               ins->sreg1 = last_ins->sreg1;                           
                        }
                        break;
                case OP_LOADU2_MEMBASE:
@@ -1133,15 +1190,8 @@ peephole_pass (MonoCompile *cfg, MonoBasicBlock *bb)
                        if (last_ins && (last_ins->opcode == OP_STOREI2_MEMBASE_REG) &&
                                        ins->inst_basereg == last_ins->inst_destbasereg &&
                                        ins->inst_offset == last_ins->inst_offset) {
-                               if (ins->dreg == last_ins->sreg1) {
-                                       last_ins->next = ins->next;                             
-                                       ins = ins->next;                                
-                                       continue;
-                               } else {
-                                       //static int c = 0; printf ("MATCHX %s %d\n", cfg->method->name,c++);
-                                       ins->opcode = OP_MOVE;
-                                       ins->sreg1 = last_ins->sreg1;
-                               }
+                               ins->opcode = (ins->opcode == OP_LOADI2_MEMBASE) ? CEE_CONV_I2 : CEE_CONV_U2;
+                               ins->sreg1 = last_ins->sreg1;                           
                        }
                        break;
                case CEE_CONV_I4:
@@ -1300,8 +1350,8 @@ mono_arch_lowering_pass (MonoCompile *cfg, MonoBasicBlock *bb)
        int rot_amount, imm8, low_imm;
 
        /* setup the virtual reg allocator */
-       if (bb->max_ireg > cfg->rs->next_vireg)
-               cfg->rs->next_vireg = bb->max_ireg;
+       if (bb->max_vreg > cfg->rs->next_vreg)
+               cfg->rs->next_vreg = bb->max_vreg;
 
        ins = bb->code;
        while (ins) {
@@ -1446,7 +1496,7 @@ loop_start:
                ins = ins->next;
        }
        bb->last_ins = last_ins;
-       bb->max_ireg = cfg->rs->next_vireg;
+       bb->max_vreg = cfg->rs->next_vreg;
 
 }
 
@@ -1535,7 +1585,10 @@ search_thunk_slot (void *data, int csize, int bsize, void *user_data) {
                                /* found a free slot instead: emit thunk */
                                code = (guchar*)thunks;
                                ARM_LDR_IMM (code, ARMREG_IP, ARMREG_PC, 0);
-                               ARM_MOV_REG_REG (code, ARMREG_PC, ARMREG_IP);
+                               if (thumb_supported)
+                                       ARM_BX (code, ARMREG_IP);
+                               else
+                                       ARM_MOV_REG_REG (code, ARMREG_PC, ARMREG_IP);
                                thunks [2] = (guint32)pdata->target;
                                mono_arch_flush_icache ((guchar*)thunks, 12);
 
@@ -1581,18 +1634,37 @@ handle_thunk (int absolute, guchar *code, const guchar *target) {
 void
 arm_patch (guchar *code, const guchar *target)
 {
-       guint32 ins = *(guint32*)code;
+       guint32 *code32 = (void*)code;
+       guint32 ins = *code32;
        guint32 prim = (ins >> 25) & 7;
+       guint32 tval = GPOINTER_TO_UINT (target);
 
        //g_print ("patching 0x%08x (0x%08x) to point to 0x%08x\n", code, ins, target);
        if (prim == 5) { /* 101b */
                /* the diff starts 8 bytes from the branch opcode */
                gint diff = target - code - 8;
+               gint tbits;
+               gint tmask = 0xffffffff;
+               if (tval & 1) { /* entering thumb mode */
+                       diff = target - 1 - code - 8;
+                       g_assert (thumb_supported);
+                       tbits = 0xf << 28; /* bl->blx bit pattern */
+                       g_assert ((ins & (1 << 24))); /* it must be a bl, not b instruction */
+                       /* this low bit of the displacement is moved to bit 24 in the instruction encoding */
+                       if (diff & 2) {
+                               tbits |= 1 << 24;
+                       }
+                       tmask = ~(1 << 24); /* clear the link bit */
+                       /*g_print ("blx to thumb: target: %p, code: %p, diff: %d, mask: %x\n", target, code, diff, tmask);*/
+               } else {
+                       tbits = 0;
+               }
                if (diff >= 0) {
                        if (diff <= 33554431) {
                                diff >>= 2;
                                ins = (ins & 0xff000000) | diff;
-                               *(guint32*)code = ins;
+                               ins &= tmask;
+                               *code32 = ins | tbits;
                                return;
                        }
                } else {
@@ -1600,7 +1672,8 @@ arm_patch (guchar *code, const guchar *target)
                        if (diff >= -33554432) {
                                diff >>= 2;
                                ins = (ins & 0xff000000) | (diff & ~0xff000000);
-                               *(guint32*)code = ins;
+                               ins &= tmask;
+                               *code32 = ins | tbits;
                                return;
                        }
                }
@@ -1609,24 +1682,47 @@ arm_patch (guchar *code, const guchar *target)
                return;
        }
 
-
+       /*
+        * The alternative call sequences looks like this:
+        *
+        *      ldr ip, [pc] // loads the address constant
+        *      b 1f         // jumps around the constant
+        *      address constant embedded in the code
+        *   1f:
+        *      mov lr, pc
+        *      mov pc, ip
+        *
+        * There are two cases for patching:
+        * a) at the end of method emission: in this case code points to the start
+        *    of the call sequence
+        * b) during runtime patching of the call site: in this case code points
+        *    to the mov pc, ip instruction
+        *
+        * We have to handle also the thunk jump code sequence:
+        *
+        *      ldr ip, [pc]
+        *      mov pc, ip
+        *      address constant // execution never reaches here
+        */
        if ((ins & 0x0ffffff0) == 0x12fff10) {
                /* branch and exchange: the address is constructed in a reg */
                g_assert_not_reached ();
        } else {
-               guint32 ccode [3];
+               guint32 ccode [4];
                guint32 *tmp = ccode;
-               ARM_LDR_IMM (tmp, ARMREG_IP, ARMREG_PC, 0);
-               ARM_MOV_REG_REG (tmp, ARMREG_LR, ARMREG_PC);
-               ARM_MOV_REG_REG (tmp, ARMREG_PC, ARMREG_IP);
+               guint8 *emit = (guint8*)tmp;
+               ARM_LDR_IMM (emit, ARMREG_IP, ARMREG_PC, 0);
+               ARM_MOV_REG_REG (emit, ARMREG_LR, ARMREG_PC);
+               ARM_MOV_REG_REG (emit, ARMREG_PC, ARMREG_IP);
+               ARM_BX (emit, ARMREG_IP);
                if (ins == ccode [2]) {
-                       tmp = (guint32*)code;
-                       tmp [-1] = (guint32)target;
+                       g_assert_not_reached (); // should be -2 ...
+                       code32 [-1] = (guint32)target;
                        return;
                }
                if (ins == ccode [0]) {
-                       tmp = (guint32*)code;
-                       tmp [2] = (guint32)target;
+                       /* handles both thunk jump code and the far call sequence */
+                       code32 [2] = (guint32)target;
                        return;
                }
                g_assert_not_reached ();
@@ -1742,7 +1838,7 @@ mono_arch_output_basic_block (MonoCompile *cfg, MonoBasicBlock *bb)
        while (ins) {
                offset = code - cfg->native_code;
 
-               max_len = ((guint8 *)arm_cpu_desc [ins->opcode])[MONO_INST_LEN];
+               max_len = ((guint8 *)ins_get_spec (ins->opcode))[MONO_INST_LEN];
 
                if (offset > (cfg->code_size - max_len - 16)) {
                        cfg->code_size *= 2;
@@ -1890,7 +1986,7 @@ mono_arch_output_basic_block (MonoCompile *cfg, MonoBasicBlock *bb)
                        g_assert (imm8 >= 0);
                        ARM_CMP_REG_IMM (code, ins->sreg1, imm8, rot_amount);
                        break;
-               case CEE_BREAK:
+               case OP_BREAK:
                        *(int*)code = 0xe7f001f0;
                        *(int*)code = 0xef9f0001;
                        code += 4;
@@ -2105,7 +2201,7 @@ mono_arch_output_basic_block (MonoCompile *cfg, MonoBasicBlock *bb)
                        ARM_CVTS (code, ins->dreg, ins->dreg);
 #endif
                        break;
-               case CEE_JMP:
+               case OP_JMP:
                        /*
                         * Keep in sync with mono_arch_emit_epilog
                         */
@@ -2146,8 +2242,7 @@ mono_arch_output_basic_block (MonoCompile *cfg, MonoBasicBlock *bb)
                                ARM_B (code, 0);
                                *(gpointer*)code = NULL;
                                code += 4;
-                               ARM_MOV_REG_REG (code, ARMREG_LR, ARMREG_PC);
-                               ARM_MOV_REG_REG (code, ARMREG_PC, ARMREG_IP);
+                               code = emit_call_reg (code, ARMREG_IP);
                        } else {
                                ARM_BL (code, 0);
                        }
@@ -2157,8 +2252,7 @@ mono_arch_output_basic_block (MonoCompile *cfg, MonoBasicBlock *bb)
                case OP_VCALL_REG:
                case OP_VOIDCALL_REG:
                case OP_CALL_REG:
-                       ARM_MOV_REG_REG (code, ARMREG_LR, ARMREG_PC);
-                       ARM_MOV_REG_REG (code, ARMREG_PC, ins->sreg1);
+                       code = emit_call_reg (code, ins->sreg1);
                        break;
                case OP_FCALL_MEMBASE:
                case OP_LCALL_MEMBASE:
@@ -2204,7 +2298,7 @@ mono_arch_output_basic_block (MonoCompile *cfg, MonoBasicBlock *bb)
                        g_assert_not_reached ();
                        ARM_MOV_REG_REG (code, ARMREG_PC, ARMREG_LR);
                        break;
-               case CEE_THROW: {
+               case OP_THROW: {
                        if (ins->sreg1 != ARMREG_R0)
                                ARM_MOV_REG_REG (code, ARMREG_R0, ins->sreg1);
                        mono_add_patch_info (cfg, code - cfg->native_code, MONO_PATCH_INFO_INTERNAL_METHOD, 
@@ -2214,8 +2308,7 @@ mono_arch_output_basic_block (MonoCompile *cfg, MonoBasicBlock *bb)
                                ARM_B (code, 0);
                                *(gpointer*)code = NULL;
                                code += 4;
-                               ARM_MOV_REG_REG (code, ARMREG_LR, ARMREG_PC);
-                               ARM_MOV_REG_REG (code, ARMREG_PC, ARMREG_IP);
+                               code = emit_call_reg (code, ARMREG_IP);
                        } else {
                                ARM_BL (code, 0);
                        }
@@ -2231,8 +2324,7 @@ mono_arch_output_basic_block (MonoCompile *cfg, MonoBasicBlock *bb)
                                ARM_B (code, 0);
                                *(gpointer*)code = NULL;
                                code += 4;
-                               ARM_MOV_REG_REG (code, ARMREG_LR, ARMREG_PC);
-                               ARM_MOV_REG_REG (code, ARMREG_PC, ARMREG_IP);
+                               code = emit_call_reg (code, ARMREG_IP);
                        } else {
                                ARM_BL (code, 0);
                        }
@@ -2258,7 +2350,7 @@ mono_arch_output_basic_block (MonoCompile *cfg, MonoBasicBlock *bb)
                        }
                        ARM_MOV_REG_REG (code, ARMREG_PC, ARMREG_IP);
                        break;
-               case CEE_ENDFINALLY:
+               case OP_ENDFINALLY:
                        if (arm_is_imm12 (ins->inst_left->inst_offset)) {
                                ARM_LDR_IMM (code, ARMREG_IP, ins->inst_left->inst_basereg, ins->inst_left->inst_offset);
                        } else {
@@ -2275,7 +2367,7 @@ mono_arch_output_basic_block (MonoCompile *cfg, MonoBasicBlock *bb)
                case OP_LABEL:
                        ins->inst_c0 = code - cfg->native_code;
                        break;
-               case CEE_BR:
+               case OP_BR:
                        if (ins->flags & MONO_INST_BRLABEL) {
                                /*if (ins->inst_i0->inst_c0) {
                                        ARM_B (code, 0);
@@ -2712,7 +2804,7 @@ mono_arch_output_basic_block (MonoCompile *cfg, MonoBasicBlock *bb)
                        EMIT_COND_BRANCH_FLAGS (ins, ARMCOND_VS); /* V set */
                        EMIT_COND_BRANCH_FLAGS (ins, ARMCOND_GE); /* swapped */
                        break;
-               case CEE_CKFINITE: {
+               case OP_CKFINITE: {
                        /*ppc_stfd (code, ins->sreg1, -8, ppc_sp);
                        ppc_lwz (code, ppc_r11, -8, ppc_sp);
                        ppc_rlwinm (code, ppc_r11, ppc_r11, 0, 1, 31);
@@ -2913,7 +3005,7 @@ mono_arch_emit_prolog (MonoCompile *cfg)
                        max_offset += 6; 
 
                while (ins) {
-                       max_offset += ((guint8 *)arm_cpu_desc [ins->opcode])[MONO_INST_LEN];
+                       max_offset += ((guint8 *)ins_get_spec (ins->opcode))[MONO_INST_LEN];
                        ins = ins->next;
                }
        }
@@ -2931,7 +3023,7 @@ mono_arch_emit_prolog (MonoCompile *cfg)
        }
        for (i = 0; i < sig->param_count + sig->hasthis; ++i) {
                ArgInfo *ainfo = cinfo->args + i;
-               inst = cfg->varinfo [pos];
+               inst = cfg->args [pos];
                
                if (cfg->verbose_level > 2)
                        g_print ("Saving argument %d (type: %d)\n", i, ainfo->regtype);
@@ -2984,6 +3076,12 @@ mono_arch_emit_prolog (MonoCompile *cfg)
                                        }
                                        break;
                                }
+                       } else if (ainfo->regtype == RegTypeBaseGen) {
+                               g_assert (arm_is_imm12 (prev_sp_offset + ainfo->offset));
+                               g_assert (arm_is_imm12 (inst->inst_offset));
+                               ARM_LDR_IMM (code, ARMREG_LR, ARMREG_SP, (prev_sp_offset + ainfo->offset));
+                               ARM_STR_IMM (code, ARMREG_LR, inst->inst_basereg, inst->inst_offset + 4);
+                               ARM_STR_IMM (code, ARMREG_R3, inst->inst_basereg, inst->inst_offset);
                        } else if (ainfo->regtype == RegTypeBase) {
                                g_assert (arm_is_imm12 (prev_sp_offset + ainfo->offset));
                                switch (ainfo->size) {
@@ -3056,8 +3154,7 @@ mono_arch_emit_prolog (MonoCompile *cfg)
                        ARM_B (code, 0);
                        *(gpointer*)code = NULL;
                        code += 4;
-                       ARM_MOV_REG_REG (code, ARMREG_LR, ARMREG_PC);
-                       ARM_MOV_REG_REG (code, ARMREG_PC, ARMREG_IP);
+                       code = emit_call_reg (code, ARMREG_IP);
                } else {
                        ARM_BL (code, 0);
                }
@@ -3120,7 +3217,7 @@ mono_arch_emit_epilog (MonoCompile *cfg)
        }
 
        /*
-        * Keep in sync with CEE_JMP
+        * Keep in sync with OP_JMP
         */
        code = cfg->native_code + cfg->code_len;
 
@@ -3156,6 +3253,7 @@ mono_arch_emit_epilog (MonoCompile *cfg)
                        code = mono_arm_emit_load_imm (code, ARMREG_IP, cfg->stack_usage);
                        ARM_ADD_REG_REG (code, ARMREG_SP, ARMREG_SP, ARMREG_IP);
                }
+               /* FIXME: add v4 thumb interworking support */
                ARM_POP_NWB (code, cfg->used_int_regs | ((1 << ARMREG_SP) | (1 << ARMREG_PC)));
        }