expmed.c (extract_force_align_mem_bit_field): New.
authorRichard Henderson <rth@redhat.com>
Fri, 5 Nov 2004 23:58:39 +0000 (15:58 -0800)
committerRichard Henderson <rth@gcc.gnu.org>
Fri, 5 Nov 2004 23:58:39 +0000 (15:58 -0800)
        * expmed.c (extract_force_align_mem_bit_field): New.
        (extract_split_bit_field): Call it.

From-SVN: r90150

gcc/ChangeLog
gcc/expmed.c

index 5cf3086..d654bf8 100644 (file)
@@ -1,5 +1,10 @@
 2004-11-05  Richard Henderson  <rth@redhat.com>
 
+       * expmed.c (extract_force_align_mem_bit_field): New.
+       (extract_split_bit_field): Call it.
+
+2004-11-05  Richard Henderson  <rth@redhat.com>
+
        * ia64.md (UNSPEC_SHRP): New.
        (dshift_count_operand): New.
        (ashrti3, ashrti3_internal, lshrti3, lshrti3_internal, shrp): New.
index 6722441..475cdcf 100644 (file)
@@ -1853,6 +1853,144 @@ lshift_value (enum machine_mode mode, rtx value, int bitpos, int bitsize)
   return immed_double_const (low, high, mode);
 }
 \f
+/* Extract a bit field from a memory by forcing the alignment of the
+   memory.  This efficient only if the field spans at least 4 boundaries.
+
+   OP0 is the MEM.
+   BITSIZE is the field width; BITPOS is the position of the first bit.
+   UNSIGNEDP is true if the result should be zero-extended.  */
+
+static rtx
+extract_force_align_mem_bit_field (rtx op0, unsigned HOST_WIDE_INT bitsize,
+                                  unsigned HOST_WIDE_INT bitpos,
+                                  int unsignedp)
+{
+  enum machine_mode mode, dmode;
+  unsigned int m_bitsize, m_size;
+  unsigned int sign_shift_up, sign_shift_dn;
+  rtx base, a1, a2, v1, v2, comb, shift, result, start;
+
+  /* Choose a mode that will fit BITSIZE.    */
+  mode = smallest_mode_for_size (bitsize, MODE_INT);
+  m_size = GET_MODE_SIZE (mode);
+  m_bitsize = GET_MODE_BITSIZE (mode);
+
+  /* Choose a mode twice as wide.  Fail if no such mode exists.  */
+  dmode = mode_for_size (m_bitsize * 2, MODE_INT, false);
+  if (dmode == BLKmode)
+    return NULL;
+
+  do_pending_stack_adjust ();
+  start = get_last_insn ();
+
+  /* At the end, we'll need an additional shift to deal with sign/zero
+     extension.  By default this will be a left+right shift of the
+     appropriate size.  But we may be able to elimitate one of them.  */
+  sign_shift_up = sign_shift_dn = m_bitsize - bitsize;
+
+  if (STRICT_ALIGNMENT)
+    {
+      base = plus_constant (XEXP (op0, 0), bitpos / BITS_PER_UNIT);
+      base = force_operand (base, NULL);
+      bitpos %= BITS_PER_UNIT;
+
+      /* Force alignment of the address; load two sequential values.  */
+      a1 = expand_simple_binop (Pmode, AND, base,
+                               GEN_INT (-(HOST_WIDE_INT)m_size),
+                               NULL, true, OPTAB_LIB_WIDEN);
+      mark_reg_pointer (a1, m_bitsize);
+      v1 = gen_rtx_MEM (mode, a1);
+      set_mem_align (v1, m_bitsize);
+      v1 = force_reg (mode, validize_mem (v1));
+
+      a2 = plus_constant (a1, GET_MODE_SIZE (mode));
+      v2 = gen_rtx_MEM (mode, a2);
+      set_mem_align (v2, m_bitsize);
+      v2 = force_reg (mode, validize_mem (v2));
+
+      /* Combine these two values into a double-word value.  */
+      if (m_bitsize == BITS_PER_WORD)
+       {
+         comb = gen_reg_rtx (dmode);
+         emit_insn (gen_rtx_CLOBBER (VOIDmode, comb));
+         emit_move_insn (gen_rtx_SUBREG (mode, comb, 0), v1);
+         emit_move_insn (gen_rtx_SUBREG (mode, comb, m_size), v2);
+       }
+      else
+       {
+         if (BYTES_BIG_ENDIAN)
+           comb = v1, v1 = v2, v2 = comb;
+         v1 = convert_modes (dmode, mode, v1, true);
+         if (v1 == NULL)
+           goto fail;
+         v2 = convert_modes (dmode, mode, v2, true);
+         v2 = expand_simple_binop (dmode, ASHIFT, v2, GEN_INT (m_bitsize),
+                                   NULL, true, OPTAB_LIB_WIDEN);
+         if (v2 == NULL)
+           goto fail;
+         comb = expand_simple_binop (dmode, IOR, v1, v2, NULL,
+                                     true, OPTAB_LIB_WIDEN);
+         if (comb == NULL)
+           goto fail;
+       }
+
+      shift = expand_simple_binop (Pmode, AND, base, GEN_INT (m_size - 1),
+                                  NULL, true, OPTAB_LIB_WIDEN);
+      shift = expand_mult (Pmode, shift, GEN_INT (BITS_PER_UNIT), NULL, 1);
+
+      if (bitpos != 0)
+       {
+         if (sign_shift_up <= bitpos)
+           bitpos -= sign_shift_up, sign_shift_up = 0;
+         shift = expand_simple_binop (Pmode, PLUS, shift, GEN_INT (bitpos),
+                                      NULL, true, OPTAB_LIB_WIDEN);
+       }
+    }
+  else
+    {
+      unsigned HOST_WIDE_INT offset = bitpos / BITS_PER_UNIT;
+      bitpos %= BITS_PER_UNIT;
+
+      /* When strict alignment is not required, we can just load directly
+        from memory without masking.  If the remaining BITPOS offset is
+        small enough, we may be able to do all operations in MODE as 
+        opposed to DMODE.  */
+      if (bitpos + bitsize <= m_bitsize)
+       dmode = mode;
+      comb = adjust_address (op0, dmode, offset);
+
+      if (sign_shift_up <= bitpos)
+       bitpos -= sign_shift_up, sign_shift_up = 0;
+      shift = GEN_INT (bitpos);
+    }
+
+  /* Shift down the double-word such that the requested value is at bit 0.  */
+  if (shift != const0_rtx)
+    comb = expand_simple_binop (dmode, unsignedp ? LSHIFTRT : ASHIFTRT,
+                               comb, shift, NULL, unsignedp, OPTAB_LIB_WIDEN);
+  if (comb == NULL)
+    goto fail;
+
+  /* If the field exactly matches MODE, then all we need to do is return the
+     lowpart.  Otherwise, shift to get the sign bits set properly.  */
+  result = force_reg (mode, gen_lowpart (mode, comb));
+
+  if (sign_shift_up)
+    result = expand_simple_binop (mode, ASHIFT, result,
+                                 GEN_INT (sign_shift_up),
+                                 NULL_RTX, 0, OPTAB_LIB_WIDEN);
+  if (sign_shift_dn)
+    result = expand_simple_binop (mode, unsignedp ? LSHIFTRT : ASHIFTRT,
+                                 result, GEN_INT (sign_shift_dn),
+                                 NULL_RTX, 0, OPTAB_LIB_WIDEN);
+
+  return result;
+
+ fail:
+  delete_insns_since (start);
+  return NULL;
+}
+
 /* Extract a bit field that is split across two words
    and return an RTX for the result.
 
@@ -1874,7 +2012,16 @@ extract_split_bit_field (rtx op0, unsigned HOST_WIDE_INT bitsize,
   if (REG_P (op0) || GET_CODE (op0) == SUBREG)
     unit = BITS_PER_WORD;
   else
-    unit = MIN (MEM_ALIGN (op0), BITS_PER_WORD);
+    {
+      unit = MIN (MEM_ALIGN (op0), BITS_PER_WORD);
+      if (bitsize / unit > 2)
+       {
+         rtx tmp = extract_force_align_mem_bit_field (op0, bitsize, bitpos,
+                                                      unsignedp);
+         if (tmp)
+           return tmp;
+       }
+    }
 
   while (bitsdone < bitsize)
     {