Patchwork Tidy store_bit_field_1 & co.

login
register
mail settings
Submitter Richard Sandiford
Date Oct. 14, 2012, 7:39 p.m.
Message ID <87obk4lu63.fsf@talisman.home>
Download mbox | patch
Permalink /patch/191380/
State New
Headers show

Comments

Richard Sandiford - Oct. 14, 2012, 7:39 p.m.
insv, extv and extzv have an unusual interface: the structure operand is
supposed to have word_mode if stored in registers or byte_mode if stored
in memory.  Andrew's patch to try different insv modes:

   http://gcc.gnu.org/ml/gcc-patches/2012-09/msg00126.html

prompted me to try making the patterns more like other optabs.

The use of word and byte "units" for registers and memory respectively
is pretty deeply engrained into the current expand routines, even in the
parts that don't deal directly with the .md patterns.  E.g. the bitnum
parameter to store_bit_field always counts from the "leftmost" bit of OP0,
but store_bit_field_1 internally converts it to a trio of "unit",
"offset" (number of whole units) and "bitpos" (position within a unit).
The latter two are then also used in the interface to store_fixed_bit_field,
with the unit being implicit.  store_split_bit_field uses the original
"bitnum"-style parameter instead.

This patch makes the code use the original "bitnum" throughout,
and only separate into units where locally useful.

Also, if the field spans two words of a register OP0, store_bit_field_1
reduces OP0 to just the first word.   It then makes sure that we fall
through to store_fixed_bit_field, which in turn calls store_split_bit_field,
which knows that OP0 is only partial.  I think this is dangerous:
it's the only time that store_bit_field_1 trims OP0 to cover only
part of the field, and so adds another special case for the rest
of the function to handle and ignore.  It also makes the interface
to store_fixed_bit_field more complicated.

The patch instead makes store_bit_field_1 call store_split_bit_field
directly where appropriate.

diffstat for this patch and the one I'm about to post says:

 expmed.c |  640 +++++++++++++++++++++++++--------------------------------------
 1 file changed, 261 insertions(+), 379 deletions(-)

so I'd like to submit them as "clean ups" regardless of whether
I ever get around to the main patterns change.

The patch is probably quite hard to review, sorry.  I've made the changelog
a bit more detailed than usual in order to list the individual points.

Tested on x86_64-linux-gnu, powerpc64-linux-gnu, mipsisa64-elf (both -EL
and -EB) and mipsisa32-elf (also both -EL and -EB).  OK to install?

Richard


gcc/
	* expmed.c (store_bit_field_1): Remove unit, offset, bitpos and
	byte_offset from the outermost scope.  Express conditions in terms
	of bitnum rather than offset, bitpos and byte_offset.  Split the
	plain move cases into two, one for memory accesses and one for
	register accesses.  Allow simplify_gen_subreg to fail rather
	than calling validate_subreg.  Move the handling of multiword
	OP0s after the code that coerces VALUE to an integer mode.
	Use simplify_gen_subreg for this case and assert that it succeeds.
	If the field still spans several words, pass it directly to
	store_split_bit_field.  Assume after that point that both sources
	and register targets fit within a word.  Replace x-prefixed
	variables with non-prefixed forms.  Compute the bitpos for insv
	register operands directly in the chosen unit size, rather than
	going through an intermediate BITS_PER_WORD unit size.
	Update the call to store_fixed_bit_field.
	(store_fixed_bit_field): Replace the bitpos and offset parameters
	with a single bitnum parameter, of the same form as store_bit_field.
	Assume that OP0 contains the full field.  Simplify the memory offset
	calculation.  Assert that the processed OP0 has an integral mode.
	(store_split_bit_field): Update the call to store_fixed_bit_field.
Eric Botcazou - Oct. 17, 2012, 8:27 p.m.
> The patch is probably quite hard to review, sorry.  I've made the changelog
> a bit more detailed than usual in order to list the individual points.

You meant scary, didn't you? :-)

> 	* expmed.c (store_bit_field_1): Remove unit, offset, bitpos and
> 	byte_offset from the outermost scope.  Express conditions in terms
> 	of bitnum rather than offset, bitpos and byte_offset.  Split the
> 	plain move cases into two, one for memory accesses and one for
> 	register accesses.  Allow simplify_gen_subreg to fail rather
> 	than calling validate_subreg.  Move the handling of multiword
> 	OP0s after the code that coerces VALUE to an integer mode.
> 	Use simplify_gen_subreg for this case and assert that it succeeds.
> 	If the field still spans several words, pass it directly to
> 	store_split_bit_field.  Assume after that point that both sources
> 	and register targets fit within a word.  Replace x-prefixed
> 	variables with non-prefixed forms.  Compute the bitpos for insv
> 	register operands directly in the chosen unit size, rather than
> 	going through an intermediate BITS_PER_WORD unit size.
> 	Update the call to store_fixed_bit_field.
> 	(store_fixed_bit_field): Replace the bitpos and offset parameters
> 	with a single bitnum parameter, of the same form as store_bit_field.
> 	Assume that OP0 contains the full field.  Simplify the memory offset
> 	calculation.  Assert that the processed OP0 has an integral mode.
> 	(store_split_bit_field): Update the call to store_fixed_bit_field.

I agree that splitting the memory and register cases is a good idea, as well 
as unifying the interface.  A few remarks:

> -476,34 +468,36 @@ store_bit_field_1 (rtx str_rtx, unsigne
>      }
> 
>    /* If the target is a register, overwriting the entire object, or storing
> -     a full-word or multi-word field can be done with just a SUBREG. +    
> a full-word or multi-word field can be done with just a SUBREG.  */ +  if
> (!MEM_P (op0)
> +      && bitsize == GET_MODE_BITSIZE (fieldmode)
> +      && ((bitsize % BITS_PER_WORD == 0
> +	   && bitnum % BITS_PER_WORD == 0)
> +	  || (bitsize == GET_MODE_BITSIZE (GET_MODE (op0))
> +	      && bitnum == 0)))
> +    {
> +      /* Use the subreg machinery either to narrow OP0 to the required
> +	 words or to cope with mode punning between equal-sized modes.  */
> +      rtx sub = simplify_gen_subreg (fieldmode, op0, GET_MODE (op0),
> +				     bitnum / BITS_PER_UNIT);
> +      if (sub)
> +	{
> +	  emit_move_insn (sub, value);
> +	  return true;
> +	}
> +    }

I'd use the following variant for the sake of symmetry with the MEM_P case:

  if (!MEM_P (op0)
      && bitnum % BITS_PER_WORD == 0
      && bitsize == GET_MODE_BITSIZE (fieldmode)
      && [...]

> -  /* If OP0 is a register, BITPOS must count within a word.
> -     But as we have it, it counts within whatever size OP0 now has.
> -     On a bigendian machine, these are not the same, so convert.  */
> -  if (BYTES_BIG_ENDIAN
> -      && !MEM_P (op0)
> -      && unit > GET_MODE_BITSIZE (GET_MODE (op0)))
> -    bitpos += unit - GET_MODE_BITSIZE (GET_MODE (op0));
> -
>    /* Storing an lsb-aligned field in a register
> -     can be done with a movestrict instruction.  */
> +     can be done with a movstrict instruction.  */
> 
>    if (!MEM_P (op0)
> -      && (BYTES_BIG_ENDIAN ? bitpos + bitsize == unit : bitpos == 0)
> +      && (BYTES_BIG_ENDIAN
> +	  ? bitnum + bitsize == GET_MODE_BITSIZE (GET_MODE (op0))
> +	  : bitnum == 0)
>        && bitsize == GET_MODE_BITSIZE (fieldmode)
>        && optab_handler (movstrict_optab, fieldmode) != CODE_FOR_nothing)
>      {
> @@ -558,8 +546,7 @@ store_bit_field_1 (rtx str_rtx, unsigned
>  	  arg0 = SUBREG_REG (arg0);
>  	}
> 
> -      subreg_off = (bitnum % BITS_PER_WORD) / BITS_PER_UNIT
> -		   + (offset * UNITS_PER_WORD);
> +      subreg_off = bitnum / BITS_PER_UNIT;
>        if (validate_subreg (fieldmode, GET_MODE (arg0), arg0, subreg_off))
>  	{
>  	  arg0 = gen_rtx_SUBREG (fieldmode, arg0, subreg_off);

Aren't you losing movstrict opportunities in big-endian mode?  If GET_MODE 
(op0) is 2 words and bitnum + bitsize == BITS_PER_WORD.

> @@ -638,34 +625,6 @@ store_bit_field_1 (rtx str_rtx, unsigned
>        return true;
>      }
> 
> -  /* From here on we can assume that the field to be stored in is
> -     a full-word (whatever type that is), since it is shorter than a word. 
> */ -
> -  /* OFFSET is the number of words or bytes (UNIT says which)
> -     from STR_RTX to the first word or byte containing part of the field. 
> */ -
> -  if (!MEM_P (op0))
> -    {
> -      if (offset != 0
> -	  || GET_MODE_SIZE (GET_MODE (op0)) > UNITS_PER_WORD)
> -	{
> -	  if (!REG_P (op0))
> -	    {
> -	      /* Since this is a destination (lvalue), we can't copy
> -		 it to a pseudo.  We can remove a SUBREG that does not
> -		 change the size of the operand.  Such a SUBREG may
> -		 have been added above.  */
> -	      gcc_assert (GET_CODE (op0) == SUBREG
> -			  && (GET_MODE_SIZE (GET_MODE (op0))
> -			      == GET_MODE_SIZE (GET_MODE (SUBREG_REG (op0)))));
> -	      op0 = SUBREG_REG (op0);
> -	    }
> -	  op0 = gen_rtx_SUBREG (mode_for_size (BITS_PER_WORD, MODE_INT, 0),
> -		                op0, (offset * UNITS_PER_WORD));
> -	}
> -      offset = 0;
> -    }
> -
>    /* If VALUE has a floating-point or complex mode, access it as an
>       integer of the corresponding size.  This can occur on a machine
>       with 64 bit registers that uses SFmode for float.  It can also
> @@ -679,9 +638,30 @@ store_bit_field_1 (rtx str_rtx, unsigned
>        emit_move_insn (gen_lowpart (GET_MODE (orig_value), value),
> orig_value); }
> 
> -  /* Now OFFSET is nonzero only if OP0 is memory
> -     and is therefore always measured in bytes.  */
> +  /* If OP0 is a multi-word register, narrow it to the affected word.
> +     If the region spans two words, defer to store_split_bit_field.  */
> +  if (!MEM_P (op0) && GET_MODE_SIZE (GET_MODE (op0)) > UNITS_PER_WORD)
> +    {
> +      op0 = simplify_gen_subreg (word_mode, op0, GET_MODE (op0),
> +				 bitnum / BITS_PER_WORD * UNITS_PER_WORD);
> +      gcc_assert (op0);
> +      bitnum %= BITS_PER_WORD;
> +      if (bitnum + bitsize > BITS_PER_WORD)
> +	{
> +	  if (!fallback_p)
> +	    return false;
> +
> +	  store_split_bit_field (op0, bitsize, bitnum, bitregion_start,
> +				 bitregion_end, value);
> +	  return true;
> +	}
> +    }
> +
> +  /* From here on we can assume that the field to be stored in fits
> +     within a word.  If the destination is a register, it too fits
> +     in a word.  */

I presume the reasoning is that the offset != 0 test is redundant with the 
test on the mode size because of the REG_P && ... early exit?

> +  enum machine_mode op_mode = mode_for_extraction (EP_insv, 3);
>    if (HAVE_insv
>        && GET_MODE (value) != BLKmode
>        && bitsize > 0
> @@ -690,25 +670,34 @@ store_bit_field_1 (rtx str_rtx, unsigned
>           -fstrict-volatile-bitfields is in effect.  */
>        && !(MEM_P (op0) && MEM_VOLATILE_P (op0)
>  	   && flag_strict_volatile_bitfields > 0)
> -      && ! ((REG_P (op0) || GET_CODE (op0) == SUBREG)
> -	    && (bitsize + bitpos > GET_MODE_BITSIZE (op_mode)))
>        /* Do not use insv if the bit region is restricted and
>  	 op_mode integer at offset doesn't fit into the
>  	 restricted region.  */
>        && !(MEM_P (op0) && bitregion_end
> -	   && bitnum - bitpos + GET_MODE_BITSIZE (op_mode)
> +	   && bitnum - (bitnum % BITS_PER_UNIT) + GET_MODE_BITSIZE (op_mode)
> 
>  	      > bitregion_end + 1))
> 
>      {
>        struct expand_operand ops[4];
> -      int xbitpos = bitpos;
> +      unsigned HOST_WIDE_INT bitpos = bitnum;
>        rtx value1;
>        rtx xop0 = op0;
>        rtx last = get_last_insn ();
>        bool copy_back = false;
> 
> -      /* Add OFFSET into OP0's address.  */
> +      unsigned int unit = GET_MODE_BITSIZE (op_mode);
>        if (MEM_P (xop0))
> -	xop0 = adjust_bitfield_address (xop0, byte_mode, offset);
> +	{
> +	  /* Get a reference to the first byte of the field.  */
> +	  xop0 = adjust_bitfield_address (xop0, byte_mode,
> +					  bitpos / BITS_PER_UNIT);
> +	  bitpos %= BITS_PER_UNIT;
> +	}
> +      else
> +	{
> +	  /* Convert from counting within OP0 to counting in OP_MODE.  */
> +	  if (BYTES_BIG_ENDIAN)
> +	    bitpos += unit - GET_MODE_BITSIZE (GET_MODE (op0));
> +	}
> 
>        /* If xop0 is a register, we need it in OP_MODE
>  	 to make it acceptable to the format of insv.  */
> @@ -735,20 +724,13 @@ store_bit_field_1 (rtx str_rtx, unsigned
>  	  copy_back = true;
>  	}
> 
> -      /* We have been counting XBITPOS within UNIT.
> -	 Count instead within the size of the register.  */
> -      if (BYTES_BIG_ENDIAN && !MEM_P (xop0))
> -	xbitpos += GET_MODE_BITSIZE (op_mode) - unit;
> -
> -      unit = GET_MODE_BITSIZE (op_mode);
> -
>        /* If BITS_BIG_ENDIAN is zero on a BYTES_BIG_ENDIAN machine, we count
> "backwards" from the size of the unit we are inserting into. Otherwise, we
> count bits from the most significant on a
>  	 BYTES/BITS_BIG_ENDIAN machine.  */
> 
>        if (BITS_BIG_ENDIAN != BYTES_BIG_ENDIAN)
> -	xbitpos = unit - bitsize - xbitpos;
> +	bitpos = unit - bitsize - bitpos;
> 
>        /* Convert VALUE to op_mode (which insv insn wants) in VALUE1.  */
>        value1 = value;

I guess I see the reasoning, but I cannot say whether it's right or wrong...

> -      total_bits = GET_MODE_BITSIZE (mode);
> -
> -      /* Make sure bitpos is valid for the chosen mode.  Adjust BITPOS to
> -	 be in the range 0 to total_bits-1, and put any excess bytes in
> -	 OFFSET.  */
> -      if (bitpos >= total_bits)
> -	{
> -	  offset += (bitpos / total_bits) * (total_bits / BITS_PER_UNIT);
> -	  bitpos -= ((bitpos / total_bits) * (total_bits / BITS_PER_UNIT)
> -		     * BITS_PER_UNIT);
> -	}
> -
> -      /* Get ref to an aligned byte, halfword, or word containing the
> field. -	 Adjust BITPOS to be position within a word,
> -	 and OFFSET to be the offset of that word.
> -	 Then alter OP0 to refer to that word.  */
> -      bitpos += (offset % (total_bits / BITS_PER_UNIT)) * BITS_PER_UNIT;
> -      offset -= (offset % (total_bits / BITS_PER_UNIT));
> -      op0 = adjust_bitfield_address (op0, mode, offset);
> +      HOST_WIDE_INT bit_offset = bitnum - bitnum % GET_MODE_BITSIZE (mode);
> +      op0 = adjust_bitfield_address (op0, mode, bit_offset /
> BITS_PER_UNIT); +      bitnum -= bit_offset;
>      }

Gorgeous.

>    mode = GET_MODE (op0);
> -
> -  /* Now MODE is either some integral mode for a MEM as OP0,
> -     or is a full-word for a REG as OP0.  TOTAL_BITS corresponds.
> -     The bit field is contained entirely within OP0.
> -     BITPOS is the starting bit number within OP0.
> -     (OP0's mode may actually be narrower than MODE.)  */
> +  gcc_assert (SCALAR_INT_MODE_P (mode));
> +  /* Note that bitsize + bitnum can be greater than GET_MODE_BITSIZE (mode)
> +     for invalid input, such as f5 from gcc.dg/pr48335-2.c.  */

Blank line after the assertion.

>    if (BYTES_BIG_ENDIAN)
> -      /* BITPOS is the distance between our msb
> -	 and that of the containing datum.
> -	 Convert it to the distance from the lsb.  */
> -      bitpos = total_bits - bitsize - bitpos;
> +    /* BITNUM is the distance between our msb
> +       and that of the containing datum.
> +       Convert it to the distance from the lsb.  */
> +    bitnum = GET_MODE_BITSIZE (mode) - bitsize - bitnum;

So bitnum can be negative?  Is that new or pre-existing?
Richard Sandiford - Oct. 23, 2012, 6:10 p.m.
Eric Botcazou <ebotcazou@adacore.com> writes:
>> +  enum machine_mode op_mode = mode_for_extraction (EP_insv, 3);
>>    if (HAVE_insv
>>        && GET_MODE (value) != BLKmode
>>        && bitsize > 0
>> @@ -690,25 +670,34 @@ store_bit_field_1 (rtx str_rtx, unsigned
>>           -fstrict-volatile-bitfields is in effect.  */
>>        && !(MEM_P (op0) && MEM_VOLATILE_P (op0)
>>  	   && flag_strict_volatile_bitfields > 0)
>> -      && ! ((REG_P (op0) || GET_CODE (op0) == SUBREG)
>> -	    && (bitsize + bitpos > GET_MODE_BITSIZE (op_mode)))
>>        /* Do not use insv if the bit region is restricted and
>>  	 op_mode integer at offset doesn't fit into the
>>  	 restricted region.  */
>>        && !(MEM_P (op0) && bitregion_end
>> -	   && bitnum - bitpos + GET_MODE_BITSIZE (op_mode)
>> +	   && bitnum - (bitnum % BITS_PER_UNIT) + GET_MODE_BITSIZE (op_mode)
>> 
>>  	      > bitregion_end + 1))
>> 
>>      {
>>        struct expand_operand ops[4];
>> -      int xbitpos = bitpos;
>> +      unsigned HOST_WIDE_INT bitpos = bitnum;
>>        rtx value1;
>>        rtx xop0 = op0;
>>        rtx last = get_last_insn ();
>>        bool copy_back = false;
>> 
>> -      /* Add OFFSET into OP0's address.  */
>> +      unsigned int unit = GET_MODE_BITSIZE (op_mode);
>>        if (MEM_P (xop0))
>> -	xop0 = adjust_bitfield_address (xop0, byte_mode, offset);
>> +	{
>> +	  /* Get a reference to the first byte of the field.  */
>> +	  xop0 = adjust_bitfield_address (xop0, byte_mode,
>> +					  bitpos / BITS_PER_UNIT);
>> +	  bitpos %= BITS_PER_UNIT;
>> +	}
>> +      else
>> +	{
>> +	  /* Convert from counting within OP0 to counting in OP_MODE.  */
>> +	  if (BYTES_BIG_ENDIAN)
>> +	    bitpos += unit - GET_MODE_BITSIZE (GET_MODE (op0));
>> +	}
>> 
>>        /* If xop0 is a register, we need it in OP_MODE
>>  	 to make it acceptable to the format of insv.  */
>> @@ -735,20 +724,13 @@ store_bit_field_1 (rtx str_rtx, unsigned
>>  	  copy_back = true;
>>  	}
>> 
>> -      /* We have been counting XBITPOS within UNIT.
>> -	 Count instead within the size of the register.  */
>> -      if (BYTES_BIG_ENDIAN && !MEM_P (xop0))
>> -	xbitpos += GET_MODE_BITSIZE (op_mode) - unit;
>> -
>> -      unit = GET_MODE_BITSIZE (op_mode);
>> -
>>        /* If BITS_BIG_ENDIAN is zero on a BYTES_BIG_ENDIAN machine, we count
>> "backwards" from the size of the unit we are inserting into. Otherwise, we
>> count bits from the most significant on a
>>  	 BYTES/BITS_BIG_ENDIAN machine.  */
>> 
>>        if (BITS_BIG_ENDIAN != BYTES_BIG_ENDIAN)
>> -	xbitpos = unit - bitsize - xbitpos;
>> +	bitpos = unit - bitsize - bitpos;
>> 
>>        /* Convert VALUE to op_mode (which insv insn wants) in VALUE1.  */
>>        value1 = value;
>
> I guess I see the reasoning, but I cannot say whether it's right or wrong...

I should probably have responded to this earlier, sorry.  I'm not sure
which part you mean, so here's an attempt at justifying the whole block:

1) WORDS_BIG_ENDIAN is deliberately ignored:

      /* The following line once was done only if WORDS_BIG_ENDIAN,
	 but I think that is a mistake.  WORDS_BIG_ENDIAN is
	 meaningful at a much higher level; when structures are copied
	 between memory and regs, the higher-numbered regs
	 always get higher addresses.  */

2) For MEM:

  The old code reached this "if" statement with:

    unsigned int unit = (MEM_P (str_rtx)) ? BITS_PER_UNIT : BITS_PER_WORD;
    offset = bitnum / unit;
    bitpos = bitnum % unit;

  I.e.:

    offset = bitnum / BITS_PER_UNIT;
    bitpos = bitnum % BITS_PER_UNIT;
  
  which the new code does explicitly with:

    unsigned HOST_WIDE_INT bitpos = bitnum;
    if (MEM_P (xop0))
      {
        /* Get a reference to the first byte of the field.  */
        xop0 = adjust_bitfield_address (xop0, byte_mode,
                                        bitpos / BITS_PER_UNIT);  <-- offset
        bitpos %= BITS_PER_UNIT;
      }
 
  The following:

    unit = GET_MODE_BITSIZE (op_mode);
    if (BITS_BIG_ENDIAN != BYTES_BIG_ENDIAN)
      xbitpos = unit - bitsize - xbitpos;

  code is the same as before.

3) For REG, !BYTES_BIG_ENDIAN, !BITS_BIG_ENDIAN:

  The easy case.  The old code reached this "if" statement with:

    unsigned int unit = (MEM_P (str_rtx)) ? BITS_PER_UNIT : BITS_PER_WORD;
    offset = bitnum / unit;
    bitpos = bitnum % unit;
    ...
    if (!MEM_P (op0) && ...)
      {
        ...set op0 to the word containing the field...
        offset = 0;
      }

  where the awkward thing is that OFFSET and BITPOS are now out of sync
  with BITNUM.  So before the "if" statement:

    offset = 0;
    bitpos = bitnum % BITS_PER_WORD;

  which if the !MEM_P block above had updated BITNUM too would just
  have been:

    offset = 0;
    bitpos = bitnum;

  The new code does update BITNUM:

    if (!MEM_P (op0) && ...)
      {
        ...set op0 to the word containing the field...
        bitnum %= BITS_PER_WORD;
      }
    ...
    unsigned HOST_WIDE_INT bitpos = bitnum;

  so both the following hold:

    offset = 0;
    bitpos = bitnum % BITS_PER_WORD;

    offset = 0;
    bitpos = bitnum;

  The "if" statement doesn't do any endian modification to (X)BITPOS
  before or after the patch.

4) For REG, !BYTES_BIG_ENDIAN, BITS_BIG_ENDIAN:

  Like (3), but at the end we also have:

    unit = GET_MODE_BITSIZE (op_mode);
    if (BITS_BIG_ENDIAN != BYTES_BIG_ENDIAN)
      xbitpos = unit - bitsize - xbitpos;

  This is the same as before, although the assignment to UNIT has moved
  up a bit.

5) For REG, BYTES_BIG_ENDIAN, BITS_BIG_ENDIAN:

  In this case the code leading up to the "if" statement was:

    unsigned int unit = (MEM_P (str_rtx)) ? BITS_PER_UNIT : BITS_PER_WORD;
    offset = bitnum / unit;
    bitpos = bitnum % unit;
    ...
    /* If OP0 is a register, BITPOS must count within a word.
       But as we have it, it counts within whatever size OP0 now has.
       On a bigendian machine, these are not the same, so convert.  */
    if (BYTES_BIG_ENDIAN
        && !MEM_P (op0)
        && unit > GET_MODE_BITSIZE (GET_MODE (op0)))
      bitpos += unit - GET_MODE_BITSIZE (GET_MODE (op0));
    ...
    if (!MEM_P (op0) && ...)
      {
        ...set op0 to the word containing the field...
        offset = 0;
      }

  So:

    offset = 0
    bitpos = bitnum % BITS_PER_WORD;
    if (...original op0 was smaller than a word...)
      bitpos += BITS_PER_WORD - <bits in op0>;

  The !MEM_P block narrows OP0s that are wider than a word to word_mode --
  it never narrows more than that -- so this is equivalent to:

    offset = 0
    bitpos = bitnum % BITS_PER_WORD;
    if (...current op0 is smaller than a word...)
      bitpos += BITS_PER_WORD - <bits in op0>;

  And thanks to the !MEM_P code, op0 is never _wider_ than a word at
  this point, so we have:

    offset = 0
    bitpos = bitnum % BITS_PER_WORD + BITS_PER_WORD - <bits in op0>;

  Then, inside the "if" statement we did:

    /* We have been counting XBITPOS within UNIT.
       Count instead within the size of the register.  */
    if (BYTES_BIG_ENDIAN && !MEM_P (xop0))
      xbitpos += GET_MODE_BITSIZE (op_mode) - unit;

  where UNIT is still BITS_PER_WORD.  So we have:

    offset = 0
    bitpos = (bitnum % BITS_PER_WORD + BITS_PER_WORD - <bits in op0>
              + <bits in op_mode> - BITS_PER_WORD);

  which cancels to:

    offset = 0
    bitpos = bitnum % BITS_PER_WORD + (<bits in op_mode> - <bits in op0>);

  As above, if the subreg code had updated BITNUM too, this would be
  equivalent to:

    offset = 0
    bitpos = bitnum + (<bits in op_mode> - <bits in op0>);

  but isn't as things stand.  The new code does update BITNUM:

    if (!MEM_P (op0) && ...)
      {
        ...set op0 to the word containing the field...
        bitnum %= BITS_PER_WORD;
      }

  and then calculates the final BITPOS directly:

    unsigned HOST_WIDE_INT bitpos = bitnum;
    unsigned int unit = GET_MODE_BITSIZE (op_mode);
    if (MEM_P (xop0))
      ...
    else
      {
	/* Convert from counting within OP0 to counting in OP_MODE.  */
	if (BYTES_BIG_ENDIAN)
	  bitpos += unit - GET_MODE_BITSIZE (GET_MODE (op0));
      }

  so both the following hold:

    offset = 0
    bitpos = bitnum % BITS_PER_WORD + (<bits in op_mode> - <bits in op0>);

    offset = 0
    bitpos = bitnum + (<bits in op_mode> - <bits in op0>);

6) For REG, BYTES_BIG_ENDIAN, !BITS_BIG_ENDIAN:

  Like (5) but we also have:

    unit = GET_MODE_BITSIZE (op_mode);
    if (BITS_BIG_ENDIAN != BYTES_BIG_ENDIAN)
      xbitpos = unit - bitsize - xbitpos;

  This is the same as before, except that the UNIT assignment became
  part of (5).

Hope I've got that right...

Of course, the idea is that the new code should make sense from first
principles.  This was just trying to show that it isn't supposed to have
changed the behaviour.

Richard
Eric Botcazou - Oct. 23, 2012, 9:37 p.m.
> I should probably have responded to this earlier, sorry.  I'm not sure
> which part you mean, so here's an attempt at justifying the whole block:
> 
> 1) WORDS_BIG_ENDIAN is deliberately ignored:
> 
>       /* The following line once was done only if WORDS_BIG_ENDIAN,
> 	 but I think that is a mistake.  WORDS_BIG_ENDIAN is
> 	 meaningful at a much higher level; when structures are copied
> 	 between memory and regs, the higher-numbered regs
> 	 always get higher addresses.  */
> 
> 2) For MEM:
> 
>   The old code reached this "if" statement with:
> 
>     unsigned int unit = (MEM_P (str_rtx)) ? BITS_PER_UNIT : BITS_PER_WORD;
>     offset = bitnum / unit;
>     bitpos = bitnum % unit;
> 
>   I.e.:
> 
>     offset = bitnum / BITS_PER_UNIT;
>     bitpos = bitnum % BITS_PER_UNIT;
> 
>   which the new code does explicitly with:
> 
>     unsigned HOST_WIDE_INT bitpos = bitnum;
>     if (MEM_P (xop0))
>       {
>         /* Get a reference to the first byte of the field.  */
>         xop0 = adjust_bitfield_address (xop0, byte_mode,
>                                         bitpos / BITS_PER_UNIT);  <-- offset
> bitpos %= BITS_PER_UNIT;
>       }
> 
>   The following:
> 
>     unit = GET_MODE_BITSIZE (op_mode);
>     if (BITS_BIG_ENDIAN != BYTES_BIG_ENDIAN)
>       xbitpos = unit - bitsize - xbitpos;
> 
>   code is the same as before.
> 
> 3) For REG, !BYTES_BIG_ENDIAN, !BITS_BIG_ENDIAN:
> 
>   The easy case.  The old code reached this "if" statement with:
> 
>     unsigned int unit = (MEM_P (str_rtx)) ? BITS_PER_UNIT : BITS_PER_WORD;
>     offset = bitnum / unit;
>     bitpos = bitnum % unit;
>     ...
>     if (!MEM_P (op0) && ...)
>       {
>         ...set op0 to the word containing the field...
>         offset = 0;
>       }
> 
>   where the awkward thing is that OFFSET and BITPOS are now out of sync
>   with BITNUM.  So before the "if" statement:
> 
>     offset = 0;
>     bitpos = bitnum % BITS_PER_WORD;
> 
>   which if the !MEM_P block above had updated BITNUM too would just
>   have been:
> 
>     offset = 0;
>     bitpos = bitnum;
> 
>   The new code does update BITNUM:
> 
>     if (!MEM_P (op0) && ...)
>       {
>         ...set op0 to the word containing the field...
>         bitnum %= BITS_PER_WORD;
>       }
>     ...
>     unsigned HOST_WIDE_INT bitpos = bitnum;
> 
>   so both the following hold:
> 
>     offset = 0;
>     bitpos = bitnum % BITS_PER_WORD;
> 
>     offset = 0;
>     bitpos = bitnum;
> 
>   The "if" statement doesn't do any endian modification to (X)BITPOS
>   before or after the patch.
> 
> 4) For REG, !BYTES_BIG_ENDIAN, BITS_BIG_ENDIAN:
> 
>   Like (3), but at the end we also have:
> 
>     unit = GET_MODE_BITSIZE (op_mode);
>     if (BITS_BIG_ENDIAN != BYTES_BIG_ENDIAN)
>       xbitpos = unit - bitsize - xbitpos;
> 
>   This is the same as before, although the assignment to UNIT has moved
>   up a bit.
> 
> 5) For REG, BYTES_BIG_ENDIAN, BITS_BIG_ENDIAN:
> 
>   In this case the code leading up to the "if" statement was:
> 
>     unsigned int unit = (MEM_P (str_rtx)) ? BITS_PER_UNIT : BITS_PER_WORD;
>     offset = bitnum / unit;
>     bitpos = bitnum % unit;
>     ...
>     /* If OP0 is a register, BITPOS must count within a word.
>        But as we have it, it counts within whatever size OP0 now has.
>        On a bigendian machine, these are not the same, so convert.  */
>     if (BYTES_BIG_ENDIAN
>         && !MEM_P (op0)
>         && unit > GET_MODE_BITSIZE (GET_MODE (op0)))
>       bitpos += unit - GET_MODE_BITSIZE (GET_MODE (op0));
>     ...
>     if (!MEM_P (op0) && ...)
>       {
>         ...set op0 to the word containing the field...
>         offset = 0;
>       }
> 
>   So:
> 
>     offset = 0
>     bitpos = bitnum % BITS_PER_WORD;
>     if (...original op0 was smaller than a word...)
>       bitpos += BITS_PER_WORD - <bits in op0>;
> 
>   The !MEM_P block narrows OP0s that are wider than a word to word_mode --
>   it never narrows more than that -- so this is equivalent to:
> 
>     offset = 0
>     bitpos = bitnum % BITS_PER_WORD;
>     if (...current op0 is smaller than a word...)
>       bitpos += BITS_PER_WORD - <bits in op0>;
> 
>   And thanks to the !MEM_P code, op0 is never _wider_ than a word at
>   this point, so we have:
> 
>     offset = 0
>     bitpos = bitnum % BITS_PER_WORD + BITS_PER_WORD - <bits in op0>;
> 
>   Then, inside the "if" statement we did:
> 
>     /* We have been counting XBITPOS within UNIT.
>        Count instead within the size of the register.  */
>     if (BYTES_BIG_ENDIAN && !MEM_P (xop0))
>       xbitpos += GET_MODE_BITSIZE (op_mode) - unit;
> 
>   where UNIT is still BITS_PER_WORD.  So we have:
> 
>     offset = 0
>     bitpos = (bitnum % BITS_PER_WORD + BITS_PER_WORD - <bits in op0>
>               + <bits in op_mode> - BITS_PER_WORD);
> 
>   which cancels to:
> 
>     offset = 0
>     bitpos = bitnum % BITS_PER_WORD + (<bits in op_mode> - <bits in op0>);
> 
>   As above, if the subreg code had updated BITNUM too, this would be
>   equivalent to:
> 
>     offset = 0
>     bitpos = bitnum + (<bits in op_mode> - <bits in op0>);
> 
>   but isn't as things stand.  The new code does update BITNUM:
> 
>     if (!MEM_P (op0) && ...)
>       {
>         ...set op0 to the word containing the field...
>         bitnum %= BITS_PER_WORD;
>       }
> 
>   and then calculates the final BITPOS directly:
> 
>     unsigned HOST_WIDE_INT bitpos = bitnum;
>     unsigned int unit = GET_MODE_BITSIZE (op_mode);
>     if (MEM_P (xop0))
>       ...
>     else
>       {
> 	/* Convert from counting within OP0 to counting in OP_MODE.  */
> 	if (BYTES_BIG_ENDIAN)
> 	  bitpos += unit - GET_MODE_BITSIZE (GET_MODE (op0));
>       }
> 
>   so both the following hold:
> 
>     offset = 0
>     bitpos = bitnum % BITS_PER_WORD + (<bits in op_mode> - <bits in op0>);
> 
>     offset = 0
>     bitpos = bitnum + (<bits in op_mode> - <bits in op0>);
> 
> 6) For REG, BYTES_BIG_ENDIAN, !BITS_BIG_ENDIAN:
> 
>   Like (5) but we also have:
> 
>     unit = GET_MODE_BITSIZE (op_mode);
>     if (BITS_BIG_ENDIAN != BYTES_BIG_ENDIAN)
>       xbitpos = unit - bitsize - xbitpos;
> 
>   This is the same as before, except that the UNIT assignment became
>   part of (5).
> 
> Hope I've got that right...
> 
> Of course, the idea is that the new code should make sense from first
> principles.  This was just trying to show that it isn't supposed to have
> changed the behaviour.

Thanks for the explanation (or rather the proof :-).  No objections by me.

Patch

Index: gcc/expmed.c
===================================================================
--- gcc/expmed.c	2012-10-13 19:46:00.862780569 +0100
+++ gcc/expmed.c	2012-10-14 11:41:48.692695324 +0100
@@ -49,7 +49,6 @@  static void store_fixed_bit_field (rtx,
 				   unsigned HOST_WIDE_INT,
 				   unsigned HOST_WIDE_INT,
 				   unsigned HOST_WIDE_INT,
-				   unsigned HOST_WIDE_INT,
 				   rtx);
 static void store_split_bit_field (rtx, unsigned HOST_WIDE_INT,
 				   unsigned HOST_WIDE_INT,
@@ -409,15 +408,9 @@  store_bit_field_1 (rtx str_rtx, unsigned
 		   enum machine_mode fieldmode,
 		   rtx value, bool fallback_p)
 {
-  unsigned int unit
-    = (MEM_P (str_rtx)) ? BITS_PER_UNIT : BITS_PER_WORD;
-  unsigned HOST_WIDE_INT offset, bitpos;
   rtx op0 = str_rtx;
-  int byte_offset;
   rtx orig_value;
 
-  enum machine_mode op_mode = mode_for_extraction (EP_insv, 3);
-
   while (GET_CODE (op0) == SUBREG)
     {
       /* The following line once was done only if WORDS_BIG_ENDIAN,
@@ -427,8 +420,7 @@  store_bit_field_1 (rtx str_rtx, unsigned
 	 always get higher addresses.  */
       int inner_mode_size = GET_MODE_SIZE (GET_MODE (SUBREG_REG (op0)));
       int outer_mode_size = GET_MODE_SIZE (GET_MODE (op0));
-
-      byte_offset = 0;
+      int byte_offset = 0;
 
       /* Paradoxical subregs need special handling on big endian machines.  */
       if (SUBREG_BYTE (op0) == 0 && inner_mode_size < outer_mode_size)
@@ -476,34 +468,36 @@  store_bit_field_1 (rtx str_rtx, unsigned
     }
 
   /* If the target is a register, overwriting the entire object, or storing
-     a full-word or multi-word field can be done with just a SUBREG.
+     a full-word or multi-word field can be done with just a SUBREG.  */
+  if (!MEM_P (op0)
+      && bitsize == GET_MODE_BITSIZE (fieldmode)
+      && ((bitsize % BITS_PER_WORD == 0
+	   && bitnum % BITS_PER_WORD == 0)
+	  || (bitsize == GET_MODE_BITSIZE (GET_MODE (op0))
+	      && bitnum == 0)))
+    {
+      /* Use the subreg machinery either to narrow OP0 to the required
+	 words or to cope with mode punning between equal-sized modes.  */
+      rtx sub = simplify_gen_subreg (fieldmode, op0, GET_MODE (op0),
+				     bitnum / BITS_PER_UNIT);
+      if (sub)
+	{
+	  emit_move_insn (sub, value);
+	  return true;
+	}
+    }
 
-     If the target is memory, storing any naturally aligned field can be
+  /* If the target is memory, storing any naturally aligned field can be
      done with a simple store.  For targets that support fast unaligned
      memory, any naturally sized, unit aligned field can be done directly.  */
-
-  offset = bitnum / unit;
-  bitpos = bitnum % unit;
-  byte_offset = (bitnum % BITS_PER_WORD) / BITS_PER_UNIT
-                + (offset * UNITS_PER_WORD);
-
-  if (bitpos == 0
+  if (MEM_P (op0)
+      && bitnum % BITS_PER_UNIT == 0
       && bitsize == GET_MODE_BITSIZE (fieldmode)
-      && (!MEM_P (op0)
-	  ? ((GET_MODE_SIZE (fieldmode) >= UNITS_PER_WORD
-	      || GET_MODE_SIZE (GET_MODE (op0)) == GET_MODE_SIZE (fieldmode))
-	     && ((GET_MODE (op0) == fieldmode && byte_offset == 0)
-		 || validate_subreg (fieldmode, GET_MODE (op0), op0,
-				     byte_offset)))
-	  : (! SLOW_UNALIGNED_ACCESS (fieldmode, MEM_ALIGN (op0))
-	     || (offset * BITS_PER_UNIT % bitsize == 0
-		 && MEM_ALIGN (op0) % GET_MODE_BITSIZE (fieldmode) == 0))))
-    {
-      if (MEM_P (op0))
-	op0 = adjust_bitfield_address (op0, fieldmode, offset);
-      else if (GET_MODE (op0) != fieldmode)
-	op0 = simplify_gen_subreg (fieldmode, op0, GET_MODE (op0),
-				   byte_offset);
+      && (!SLOW_UNALIGNED_ACCESS (fieldmode, MEM_ALIGN (op0))
+	  || (bitnum % bitsize == 0
+	      && MEM_ALIGN (op0) % bitsize == 0)))
+    {
+      op0 = adjust_bitfield_address (op0, fieldmode, bitnum / BITS_PER_UNIT);
       emit_move_insn (op0, value);
       return true;
     }
@@ -526,19 +520,13 @@  store_bit_field_1 (rtx str_rtx, unsigned
       }
   }
 
-  /* If OP0 is a register, BITPOS must count within a word.
-     But as we have it, it counts within whatever size OP0 now has.
-     On a bigendian machine, these are not the same, so convert.  */
-  if (BYTES_BIG_ENDIAN
-      && !MEM_P (op0)
-      && unit > GET_MODE_BITSIZE (GET_MODE (op0)))
-    bitpos += unit - GET_MODE_BITSIZE (GET_MODE (op0));
-
   /* Storing an lsb-aligned field in a register
-     can be done with a movestrict instruction.  */
+     can be done with a movstrict instruction.  */
 
   if (!MEM_P (op0)
-      && (BYTES_BIG_ENDIAN ? bitpos + bitsize == unit : bitpos == 0)
+      && (BYTES_BIG_ENDIAN
+	  ? bitnum + bitsize == GET_MODE_BITSIZE (GET_MODE (op0))
+	  : bitnum == 0)
       && bitsize == GET_MODE_BITSIZE (fieldmode)
       && optab_handler (movstrict_optab, fieldmode) != CODE_FOR_nothing)
     {
@@ -558,8 +546,7 @@  store_bit_field_1 (rtx str_rtx, unsigned
 	  arg0 = SUBREG_REG (arg0);
 	}
 
-      subreg_off = (bitnum % BITS_PER_WORD) / BITS_PER_UNIT
-		   + (offset * UNITS_PER_WORD);
+      subreg_off = bitnum / BITS_PER_UNIT;
       if (validate_subreg (fieldmode, GET_MODE (arg0), arg0, subreg_off))
 	{
 	  arg0 = gen_rtx_SUBREG (fieldmode, arg0, subreg_off);
@@ -638,34 +625,6 @@  store_bit_field_1 (rtx str_rtx, unsigned
       return true;
     }
 
-  /* From here on we can assume that the field to be stored in is
-     a full-word (whatever type that is), since it is shorter than a word.  */
-
-  /* OFFSET is the number of words or bytes (UNIT says which)
-     from STR_RTX to the first word or byte containing part of the field.  */
-
-  if (!MEM_P (op0))
-    {
-      if (offset != 0
-	  || GET_MODE_SIZE (GET_MODE (op0)) > UNITS_PER_WORD)
-	{
-	  if (!REG_P (op0))
-	    {
-	      /* Since this is a destination (lvalue), we can't copy
-		 it to a pseudo.  We can remove a SUBREG that does not
-		 change the size of the operand.  Such a SUBREG may
-		 have been added above.  */
-	      gcc_assert (GET_CODE (op0) == SUBREG
-			  && (GET_MODE_SIZE (GET_MODE (op0))
-			      == GET_MODE_SIZE (GET_MODE (SUBREG_REG (op0)))));
-	      op0 = SUBREG_REG (op0);
-	    }
-	  op0 = gen_rtx_SUBREG (mode_for_size (BITS_PER_WORD, MODE_INT, 0),
-		                op0, (offset * UNITS_PER_WORD));
-	}
-      offset = 0;
-    }
-
   /* If VALUE has a floating-point or complex mode, access it as an
      integer of the corresponding size.  This can occur on a machine
      with 64 bit registers that uses SFmode for float.  It can also
@@ -679,9 +638,30 @@  store_bit_field_1 (rtx str_rtx, unsigned
       emit_move_insn (gen_lowpart (GET_MODE (orig_value), value), orig_value);
     }
 
-  /* Now OFFSET is nonzero only if OP0 is memory
-     and is therefore always measured in bytes.  */
+  /* If OP0 is a multi-word register, narrow it to the affected word.
+     If the region spans two words, defer to store_split_bit_field.  */
+  if (!MEM_P (op0) && GET_MODE_SIZE (GET_MODE (op0)) > UNITS_PER_WORD)
+    {
+      op0 = simplify_gen_subreg (word_mode, op0, GET_MODE (op0),
+				 bitnum / BITS_PER_WORD * UNITS_PER_WORD);
+      gcc_assert (op0);
+      bitnum %= BITS_PER_WORD;
+      if (bitnum + bitsize > BITS_PER_WORD)
+	{
+	  if (!fallback_p)
+	    return false;
+
+	  store_split_bit_field (op0, bitsize, bitnum, bitregion_start,
+				 bitregion_end, value);
+	  return true;
+	}
+    }
+
+  /* From here on we can assume that the field to be stored in fits
+     within a word.  If the destination is a register, it too fits
+     in a word.  */
 
+  enum machine_mode op_mode = mode_for_extraction (EP_insv, 3);
   if (HAVE_insv
       && GET_MODE (value) != BLKmode
       && bitsize > 0
@@ -690,25 +670,34 @@  store_bit_field_1 (rtx str_rtx, unsigned
          -fstrict-volatile-bitfields is in effect.  */
       && !(MEM_P (op0) && MEM_VOLATILE_P (op0)
 	   && flag_strict_volatile_bitfields > 0)
-      && ! ((REG_P (op0) || GET_CODE (op0) == SUBREG)
-	    && (bitsize + bitpos > GET_MODE_BITSIZE (op_mode)))
       /* Do not use insv if the bit region is restricted and
 	 op_mode integer at offset doesn't fit into the
 	 restricted region.  */
       && !(MEM_P (op0) && bitregion_end
-	   && bitnum - bitpos + GET_MODE_BITSIZE (op_mode)
+	   && bitnum - (bitnum % BITS_PER_UNIT) + GET_MODE_BITSIZE (op_mode)
 	      > bitregion_end + 1))
     {
       struct expand_operand ops[4];
-      int xbitpos = bitpos;
+      unsigned HOST_WIDE_INT bitpos = bitnum;
       rtx value1;
       rtx xop0 = op0;
       rtx last = get_last_insn ();
       bool copy_back = false;
 
-      /* Add OFFSET into OP0's address.  */
+      unsigned int unit = GET_MODE_BITSIZE (op_mode);
       if (MEM_P (xop0))
-	xop0 = adjust_bitfield_address (xop0, byte_mode, offset);
+	{
+	  /* Get a reference to the first byte of the field.  */
+	  xop0 = adjust_bitfield_address (xop0, byte_mode,
+					  bitpos / BITS_PER_UNIT);
+	  bitpos %= BITS_PER_UNIT;
+	}
+      else
+	{
+	  /* Convert from counting within OP0 to counting in OP_MODE.  */
+	  if (BYTES_BIG_ENDIAN)
+	    bitpos += unit - GET_MODE_BITSIZE (GET_MODE (op0));
+	}
 
       /* If xop0 is a register, we need it in OP_MODE
 	 to make it acceptable to the format of insv.  */
@@ -735,20 +724,13 @@  store_bit_field_1 (rtx str_rtx, unsigned
 	  copy_back = true;
 	}
 
-      /* We have been counting XBITPOS within UNIT.
-	 Count instead within the size of the register.  */
-      if (BYTES_BIG_ENDIAN && !MEM_P (xop0))
-	xbitpos += GET_MODE_BITSIZE (op_mode) - unit;
-
-      unit = GET_MODE_BITSIZE (op_mode);
-
       /* If BITS_BIG_ENDIAN is zero on a BYTES_BIG_ENDIAN machine, we count
          "backwards" from the size of the unit we are inserting into.
 	 Otherwise, we count bits from the most significant on a
 	 BYTES/BITS_BIG_ENDIAN machine.  */
 
       if (BITS_BIG_ENDIAN != BYTES_BIG_ENDIAN)
-	xbitpos = unit - bitsize - xbitpos;
+	bitpos = unit - bitsize - bitpos;
 
       /* Convert VALUE to op_mode (which insv insn wants) in VALUE1.  */
       value1 = value;
@@ -787,7 +769,7 @@  store_bit_field_1 (rtx str_rtx, unsigned
 
       create_fixed_operand (&ops[0], xop0);
       create_integer_operand (&ops[1], bitsize);
-      create_integer_operand (&ops[2], xbitpos);
+      create_integer_operand (&ops[2], bitpos);
       create_input_operand (&ops[3], value1, op_mode);
       if (maybe_expand_insn (CODE_FOR_insv, 4, ops))
 	{
@@ -832,7 +814,8 @@  store_bit_field_1 (rtx str_rtx, unsigned
 	       && GET_MODE_BITSIZE (bestmode) > MEM_ALIGN (op0)))
 	{
 	  rtx last, tempreg, xop0;
-	  unsigned HOST_WIDE_INT xoffset, xbitpos;
+	  unsigned int unit;
+	  unsigned HOST_WIDE_INT offset, bitpos;
 
 	  last = get_last_insn ();
 
@@ -840,14 +823,14 @@  store_bit_field_1 (rtx str_rtx, unsigned
 	     that mode.  Compute the offset as a multiple of this unit,
 	     counting in bytes.  */
 	  unit = GET_MODE_BITSIZE (bestmode);
-	  xoffset = (bitnum / unit) * GET_MODE_SIZE (bestmode);
-	  xbitpos = bitnum % unit;
-	  xop0 = adjust_bitfield_address (op0, bestmode, xoffset);
+	  offset = (bitnum / unit) * GET_MODE_SIZE (bestmode);
+	  bitpos = bitnum % unit;
+	  xop0 = adjust_bitfield_address (op0, bestmode, offset);
 
 	  /* Fetch that unit, store the bitfield in it, then store
 	     the unit.  */
 	  tempreg = copy_to_reg (xop0);
-	  if (store_bit_field_1 (tempreg, bitsize, xbitpos,
+	  if (store_bit_field_1 (tempreg, bitsize, bitpos,
 				 bitregion_start, bitregion_end,
 				 fieldmode, orig_value, false))
 	    {
@@ -861,8 +844,8 @@  store_bit_field_1 (rtx str_rtx, unsigned
   if (!fallback_p)
     return false;
 
-  store_fixed_bit_field (op0, offset, bitsize, bitpos,
-			 bitregion_start, bitregion_end, value);
+  store_fixed_bit_field (op0, bitsize, bitnum, bitregion_start,
+			 bitregion_end, value);
   return true;
 }
 
@@ -918,25 +901,17 @@  store_bit_field (rtx str_rtx, unsigned H
     gcc_unreachable ();
 }
 
-/* Use shifts and boolean operations to store VALUE
-   into a bit field of width BITSIZE
-   in a memory location specified by OP0 except offset by OFFSET bytes.
-     (OFFSET must be 0 if OP0 is a register.)
-   The field starts at position BITPOS within the byte.
-    (If OP0 is a register, it may be a full word or a narrower mode,
-     but BITPOS still counts within a full word,
-     which is significant on bigendian machines.)  */
+/* Use shifts and boolean operations to store VALUE into a bit field of
+   width BITSIZE in OP0, starting at bit BITNUM.  */
 
 static void
-store_fixed_bit_field (rtx op0, unsigned HOST_WIDE_INT offset,
-		       unsigned HOST_WIDE_INT bitsize,
-		       unsigned HOST_WIDE_INT bitpos,
+store_fixed_bit_field (rtx op0, unsigned HOST_WIDE_INT bitsize,
+		       unsigned HOST_WIDE_INT bitnum,
 		       unsigned HOST_WIDE_INT bitregion_start,
 		       unsigned HOST_WIDE_INT bitregion_end,
 		       rtx value)
 {
   enum machine_mode mode;
-  unsigned int total_bits = BITS_PER_WORD;
   rtx temp;
   int all_zero = 0;
   int all_one = 0;
@@ -948,19 +923,7 @@  store_fixed_bit_field (rtx op0, unsigned
      and a field split across two bytes.
      Such cases are not supposed to be able to occur.  */
 
-  if (REG_P (op0) || GET_CODE (op0) == SUBREG)
-    {
-      gcc_assert (!offset);
-      /* Special treatment for a bit field split across two registers.  */
-      if (bitsize + bitpos > BITS_PER_WORD)
-	{
-	  store_split_bit_field (op0, bitsize, bitpos,
-				 bitregion_start, bitregion_end,
-				 value);
-	  return;
-	}
-    }
-  else
+  if (MEM_P (op0))
     {
       unsigned HOST_WIDE_INT maxbits = MAX_FIXED_MODE_SIZE;
 
@@ -983,58 +946,38 @@  store_fixed_bit_field (rtx op0, unsigned
 	  && flag_strict_volatile_bitfields > 0)
 	mode = GET_MODE (op0);
       else
-	mode = get_best_mode (bitsize, bitpos + offset * BITS_PER_UNIT,
-			      bitregion_start, bitregion_end,
+	mode = get_best_mode (bitsize, bitnum, bitregion_start, bitregion_end,
 			      MEM_ALIGN (op0), mode, MEM_VOLATILE_P (op0));
 
       if (mode == VOIDmode)
 	{
 	  /* The only way this should occur is if the field spans word
 	     boundaries.  */
-	  store_split_bit_field (op0, bitsize, bitpos + offset * BITS_PER_UNIT,
-				 bitregion_start, bitregion_end, value);
+	  store_split_bit_field (op0, bitsize, bitnum, bitregion_start,
+				 bitregion_end, value);
 	  return;
 	}
 
-      total_bits = GET_MODE_BITSIZE (mode);
-
-      /* Make sure bitpos is valid for the chosen mode.  Adjust BITPOS to
-	 be in the range 0 to total_bits-1, and put any excess bytes in
-	 OFFSET.  */
-      if (bitpos >= total_bits)
-	{
-	  offset += (bitpos / total_bits) * (total_bits / BITS_PER_UNIT);
-	  bitpos -= ((bitpos / total_bits) * (total_bits / BITS_PER_UNIT)
-		     * BITS_PER_UNIT);
-	}
-
-      /* Get ref to an aligned byte, halfword, or word containing the field.
-	 Adjust BITPOS to be position within a word,
-	 and OFFSET to be the offset of that word.
-	 Then alter OP0 to refer to that word.  */
-      bitpos += (offset % (total_bits / BITS_PER_UNIT)) * BITS_PER_UNIT;
-      offset -= (offset % (total_bits / BITS_PER_UNIT));
-      op0 = adjust_bitfield_address (op0, mode, offset);
+      HOST_WIDE_INT bit_offset = bitnum - bitnum % GET_MODE_BITSIZE (mode);
+      op0 = adjust_bitfield_address (op0, mode, bit_offset / BITS_PER_UNIT);
+      bitnum -= bit_offset;
     }
 
   mode = GET_MODE (op0);
-
-  /* Now MODE is either some integral mode for a MEM as OP0,
-     or is a full-word for a REG as OP0.  TOTAL_BITS corresponds.
-     The bit field is contained entirely within OP0.
-     BITPOS is the starting bit number within OP0.
-     (OP0's mode may actually be narrower than MODE.)  */
+  gcc_assert (SCALAR_INT_MODE_P (mode));
+  /* Note that bitsize + bitnum can be greater than GET_MODE_BITSIZE (mode)
+     for invalid input, such as f5 from gcc.dg/pr48335-2.c.  */
 
   if (BYTES_BIG_ENDIAN)
-      /* BITPOS is the distance between our msb
-	 and that of the containing datum.
-	 Convert it to the distance from the lsb.  */
-      bitpos = total_bits - bitsize - bitpos;
+    /* BITNUM is the distance between our msb
+       and that of the containing datum.
+       Convert it to the distance from the lsb.  */
+    bitnum = GET_MODE_BITSIZE (mode) - bitsize - bitnum;
 
-  /* Now BITPOS is always the distance between our lsb
+  /* Now BITNUM is always the distance between our lsb
      and that of OP0.  */
 
-  /* Shift VALUE left by BITPOS bits.  If VALUE is not constant,
+  /* Shift VALUE left by BITNUM bits.  If VALUE is not constant,
      we must first convert its mode to MODE.  */
 
   if (CONST_INT_P (value))
@@ -1051,12 +994,12 @@  store_fixed_bit_field (rtx op0, unsigned
 	       || (bitsize == HOST_BITS_PER_WIDE_INT && v == -1))
 	all_one = 1;
 
-      value = lshift_value (mode, value, bitpos, bitsize);
+      value = lshift_value (mode, value, bitnum, bitsize);
     }
   else
     {
       int must_and = (GET_MODE_BITSIZE (GET_MODE (value)) != bitsize
-		      && bitpos + bitsize != GET_MODE_BITSIZE (mode));
+		      && bitnum + bitsize != GET_MODE_BITSIZE (mode));
 
       if (GET_MODE (value) != mode)
 	value = convert_to_mode (mode, value, 1);
@@ -1065,9 +1008,9 @@  store_fixed_bit_field (rtx op0, unsigned
 	value = expand_binop (mode, and_optab, value,
 			      mask_rtx (mode, 0, bitsize, 0),
 			      NULL_RTX, 1, OPTAB_LIB_WIDEN);
-      if (bitpos > 0)
+      if (bitnum > 0)
 	value = expand_shift (LSHIFT_EXPR, mode, value,
-			      bitpos, NULL_RTX, 1);
+			      bitnum, NULL_RTX, 1);
     }
 
   /* Now clear the chosen bits in OP0,
@@ -1080,7 +1023,7 @@  store_fixed_bit_field (rtx op0, unsigned
   if (! all_one)
     {
       temp = expand_binop (mode, and_optab, temp,
-			   mask_rtx (mode, bitpos, bitsize, 1),
+			   mask_rtx (mode, bitnum, bitsize, 1),
 			   NULL_RTX, 1, OPTAB_LIB_WIDEN);
       temp = force_reg (mode, temp);
     }
@@ -1235,12 +1178,11 @@  store_split_bit_field (rtx op0, unsigned
       else
 	word = op0;
 
-      /* OFFSET is in UNITs, and UNIT is in bits.
-	 store_fixed_bit_field wants offset in bytes.  If WORD is const0_rtx,
+      /* OFFSET is in UNITs, and UNIT is in bits.  If WORD is const0_rtx,
 	 it is just an out-of-bounds access.  Ignore it.  */
       if (word != const0_rtx)
-	store_fixed_bit_field (word, offset * unit / BITS_PER_UNIT, thissize,
-			       thispos, bitregion_start, bitregion_end, part);
+	store_fixed_bit_field (word, thissize, offset * unit + thispos,
+			       bitregion_start, bitregion_end, part);
       bitsdone += thissize;
     }
 }