diff mbox

ARM fixed-point support [6/6]: target-specific parts

Message ID 20110526174558.5253ffca@rex.config
State New
Headers show

Commit Message

Julian Brown May 26, 2011, 4:45 p.m. UTC
On Fri, 13 May 2011 17:31:18 +0100
Julian Brown <julian@codesourcery.com> wrote:

> On Fri, 13 May 2011 14:54:47 +0000 (UTC)
> "Joseph S. Myers" <joseph@codesourcery.com> wrote:
> 
> > On Fri, 13 May 2011, Julian Brown wrote:
> > 
> > > although Joseph's comments have (hopefully) all been addressed.
> > > One must now configure with "--enable-fixed-point" to enable the
> > > fixed-point support.
> > 
> > No, that's not how this configure logic works; it enables fixed
> > point by default for the given targets.  The bit that enables it by
> > default for MIPS (and that you're changing to cover ARM as well) is
> > the code that is called if the user *doesn't* pass
> > --enable-fixed-point or --disable-fixed-point.
> 
> Yes, you're quite right: I had forgotten to run autoconf after
> changing configure.ac whilst testing. Sorry for the misinformation!

This is a new version of the patch, which fixes a couple of issues
discovered on our internal branch since the last version was posted.
This version handles "long long" fixed point types in the same way as
32-bit MIPS: basically the sizes of those types are clamped to 64 bits.

This means the testsuite changes in:

http://gcc.gnu.org/ml/gcc-patches/2011-05/msg00969.html

are no longer required.

Another couple of problems have also been fixed, relating to big-endian
mode. One was the use of simplify_gen_subreg with a hard-wired offset in
a several of the insn expanders, where I should have just used
gen_highpart -- which works in either endianness, and makes the intent
clearer anyway.

The other issue is ABI-related. My previous assertion that fixed-point
types (smaller than word size) should be passed to and returned from
functions "like integers" doesn't really work out very well after all
in big-endian mode, so needs re-thinking. The problem is basically that
such fixed-point types are incorrectly padded in big-endian mode, so
the data ends up in a different part of the passed word than expected.

The way chars and shorts is handled is as follows: when those types are
passed to a function, they are promoted to word-size ints, which is a
nop-op: the values can simply be reinterpreted as ints, and that works
fine in either endianness. We might hope to be able to do the same thing
for smaller-than-word-size fixed-point types, but unfortunately we
can't: e.g. a "short fract" or "fract" can't just be reinterpreted as a
"long fract" (the 32-bit fractional type), because the fixed-point is
in a different place for the wider type (and performing conversions
would itself require libcalls at present). It also doesn't work to
promote small fixed-point types to SImode: as well as being
conceptually messy, we hit ICEs very quickly attempting that.

So instead, I think that small fixed-point types should be handled the
same way as small aggregates or small complex-valued types. This means
effectively no change in little-endian mode, but in big-endian mode:

* for register arguments, fixed-point types are passed in the
  most-significant end of the register.

* for stack arguments, fixed-point types are passed in lower memory
  addresses.

* for return values, fixed-point types are again in the
  most-significant end of the return register (r0).

This is quite easy to set up in the backend code, but unfortunately I
hit a snag in libcall expansion in calls.c: padding of arguments and
return values is not properly supported, as it is for regular function
calls. I've implemented that, and will post it as a separate patch.

OK to apply? Tested alongside the rest of the patch series, in both big
& little-endian mode.

Julian

ChangeLog

    gcc/
    * configure.ac (fixed-point): Add ARM support.
    * configure: Regenerate.
    * config/arm/arm.c (arm_fixed_mode_set): New struct.
    (arm_set_fixed_optab_libfunc): New.
    (arm_set_fixed_conv_libfunc): New.
    (arm_init_libfuncs): Initialise fixed-point helper libfuncs with
    ARM-specific names.
    (aapcs_libcall_value): Return sub-word-size fixed-point libcall
    return values in SImode.
    (arm_return_in_msb): Return fixed-point types in the msb.
    (arm_pad_reg_upwards, arm_pad_arg_upwards): Pad fixed-point types
    upwards.
    (arm_scalar_mode_supported_p): Support fixed-point modes.
    (arm_vector_mode_supported_p): Support vector fixed-point modes.
    * config/arm/arm.h (SHORT_FRACT_TYPE_SIZE, FRACT_TYPE_SIZE)
    (LONG_FRACT_TYPE_SIZE, LONG_LONG_FRACT_TYPE_SIZE)
    (SHORT_ACCUM_TYPE_SIZE, ACCUM_TYPE_SIZE, LONG_ACCUM_TYPE_SIZE)
    (LONG_LONG_ACCUM_TYPE_SIZE, MAX_FIXED_MODE_SIZE): Define.
    * config/arm/arm-modes.def (FRACT, UFRACT, ACCUM, UACCUM): Declare
    vector modes.
    * config/arm/predicates.md (sat_shift_operator): New predicate.
    * config/arm/arm-fixed.md: New.
    * config/arm/arm.md: Include arm-fixed.md.
    * config/arm/t-arm (MD_INCLUDES): Add arm-fixed.md.
    * config/arm/fixed-bit-machine.h: New.
 
    libgcc/
    * config.host (arm*-*-linux*, arm*-*-uclinux*, arm*-*-eabi*)
    (arm*-*-symbianelf*): Add arm/t-fixed-point makefile fragment.
    target-specific or generic fixed_bit_machine_header to decorate the
    names of fixed-point helper functions.
    * config/arm/t-fixed-point: New.

    gcc/testsuite/
    * gcc.target/arm/fixed-point-exec.c: New test.

Comments

Richard Earnshaw June 30, 2011, 1:42 p.m. UTC | #1
On 26/05/11 17:45, Julian Brown wrote:
> On Fri, 13 May 2011 17:31:18 +0100
> Julian Brown <julian@codesourcery.com> wrote:
> 
>> On Fri, 13 May 2011 14:54:47 +0000 (UTC)
>> "Joseph S. Myers" <joseph@codesourcery.com> wrote:
>>
>>> On Fri, 13 May 2011, Julian Brown wrote:
>>>
>>>> although Joseph's comments have (hopefully) all been addressed.
>>>> One must now configure with "--enable-fixed-point" to enable the
>>>> fixed-point support.
>>>
>>> No, that's not how this configure logic works; it enables fixed
>>> point by default for the given targets.  The bit that enables it by
>>> default for MIPS (and that you're changing to cover ARM as well) is
>>> the code that is called if the user *doesn't* pass
>>> --enable-fixed-point or --disable-fixed-point.
>>
>> Yes, you're quite right: I had forgotten to run autoconf after
>> changing configure.ac whilst testing. Sorry for the misinformation!
> 
> This is a new version of the patch, which fixes a couple of issues
> discovered on our internal branch since the last version was posted.
> This version handles "long long" fixed point types in the same way as
> 32-bit MIPS: basically the sizes of those types are clamped to 64 bits.
> 
> This means the testsuite changes in:
> 
> http://gcc.gnu.org/ml/gcc-patches/2011-05/msg00969.html
> 
> are no longer required.
> 
> Another couple of problems have also been fixed, relating to big-endian
> mode. One was the use of simplify_gen_subreg with a hard-wired offset in
> a several of the insn expanders, where I should have just used
> gen_highpart -- which works in either endianness, and makes the intent
> clearer anyway.
> 
> The other issue is ABI-related. My previous assertion that fixed-point
> types (smaller than word size) should be passed to and returned from
> functions "like integers" doesn't really work out very well after all
> in big-endian mode, so needs re-thinking. The problem is basically that
> such fixed-point types are incorrectly padded in big-endian mode, so
> the data ends up in a different part of the passed word than expected.
> 
> The way chars and shorts is handled is as follows: when those types are
> passed to a function, they are promoted to word-size ints, which is a
> nop-op: the values can simply be reinterpreted as ints, and that works
> fine in either endianness. We might hope to be able to do the same thing
> for smaller-than-word-size fixed-point types, but unfortunately we
> can't: e.g. a "short fract" or "fract" can't just be reinterpreted as a
> "long fract" (the 32-bit fractional type), because the fixed-point is
> in a different place for the wider type (and performing conversions
> would itself require libcalls at present). It also doesn't work to
> promote small fixed-point types to SImode: as well as being
> conceptually messy, we hit ICEs very quickly attempting that.
> 
> So instead, I think that small fixed-point types should be handled the
> same way as small aggregates or small complex-valued types. This means
> effectively no change in little-endian mode, but in big-endian mode:
> 
> * for register arguments, fixed-point types are passed in the
>   most-significant end of the register.
> 
> * for stack arguments, fixed-point types are passed in lower memory
>   addresses.
> 
> * for return values, fixed-point types are again in the
>   most-significant end of the return register (r0).
> 
> This is quite easy to set up in the backend code, but unfortunately I
> hit a snag in libcall expansion in calls.c: padding of arguments and
> return values is not properly supported, as it is for regular function
> calls. I've implemented that, and will post it as a separate patch.
> 
> OK to apply? Tested alongside the rest of the patch series, in both big
> & little-endian mode.
> 
> Julian
> 
> ChangeLog
> 
>     gcc/
>     * configure.ac (fixed-point): Add ARM support.
>     * configure: Regenerate.
>     * config/arm/arm.c (arm_fixed_mode_set): New struct.
>     (arm_set_fixed_optab_libfunc): New.
>     (arm_set_fixed_conv_libfunc): New.
>     (arm_init_libfuncs): Initialise fixed-point helper libfuncs with
>     ARM-specific names.
>     (aapcs_libcall_value): Return sub-word-size fixed-point libcall
>     return values in SImode.
>     (arm_return_in_msb): Return fixed-point types in the msb.
>     (arm_pad_reg_upwards, arm_pad_arg_upwards): Pad fixed-point types
>     upwards.
>     (arm_scalar_mode_supported_p): Support fixed-point modes.
>     (arm_vector_mode_supported_p): Support vector fixed-point modes.
>     * config/arm/arm.h (SHORT_FRACT_TYPE_SIZE, FRACT_TYPE_SIZE)
>     (LONG_FRACT_TYPE_SIZE, LONG_LONG_FRACT_TYPE_SIZE)
>     (SHORT_ACCUM_TYPE_SIZE, ACCUM_TYPE_SIZE, LONG_ACCUM_TYPE_SIZE)
>     (LONG_LONG_ACCUM_TYPE_SIZE, MAX_FIXED_MODE_SIZE): Define.
>     * config/arm/arm-modes.def (FRACT, UFRACT, ACCUM, UACCUM): Declare
>     vector modes.
>     * config/arm/predicates.md (sat_shift_operator): New predicate.
>     * config/arm/arm-fixed.md: New.
>     * config/arm/arm.md: Include arm-fixed.md.
>     * config/arm/t-arm (MD_INCLUDES): Add arm-fixed.md.
>     * config/arm/fixed-bit-machine.h: New.
>  
>     libgcc/
>     * config.host (arm*-*-linux*, arm*-*-uclinux*, arm*-*-eabi*)
>     (arm*-*-symbianelf*): Add arm/t-fixed-point makefile fragment.
>     target-specific or generic fixed_bit_machine_header to decorate the
>     names of fixed-point helper functions.
>     * config/arm/t-fixed-point: New.
> 
>     gcc/testsuite/
>     * gcc.target/arm/fixed-point-exec.c: New test.
> 
> 

Please put the iterator definitions in iterators.md

Otherwise OK.

R.
diff mbox

Patch

commit b4ddde3e7fbc6a2961dd24695df53623f4970f1d
Author: Julian Brown <julian@henry8.codesourcery.com>
Date:   Thu May 26 09:12:05 2011 -0700

    Fixed-point extension support for ARM.

diff --git a/gcc/config/arm/arm-fixed.md b/gcc/config/arm/arm-fixed.md
new file mode 100644
index 0000000..df1c9ef
--- /dev/null
+++ b/gcc/config/arm/arm-fixed.md
@@ -0,0 +1,399 @@ 
+;; Copyright 2011 Free Software Foundation, Inc.
+;;
+;; This file is part of GCC.
+;;
+;; GCC is free software; you can redistribute it and/or modify it
+;; under the terms of the GNU General Public License as published
+;; by the Free Software Foundation; either version 3, or (at your
+;; option) any later version.
+;;
+;; GCC is distributed in the hope that it will be useful, but WITHOUT
+;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+;; or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
+;; License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with GCC; see the file COPYING3.  If not see
+;; <http://www.gnu.org/licenses/>.
+;;
+;; This file contains ARM instructions that support fixed-point operations.
+
+(define_mode_iterator FIXED [QQ HQ SQ UQQ UHQ USQ HA SA UHA USA])
+
+(define_mode_iterator ADDSUB [V4QQ V2HQ V2HA])
+
+(define_mode_iterator UQADDSUB [V4UQQ V2UHQ UQQ UHQ V2UHA UHA])
+
+(define_mode_iterator QADDSUB [V4QQ V2HQ QQ HQ V2HA HA SQ SA])
+
+(define_mode_iterator QMUL [HQ HA])
+
+(define_mode_attr qaddsub_suf [(V4UQQ "8") (V2UHQ "16") (UQQ "8") (UHQ "16")
+			       (V2UHA "16") (UHA "16")
+			       (V4QQ "8") (V2HQ "16") (QQ "8") (HQ "16")
+			       (V2HA "16") (HA "16") (SQ "") (SA "")])
+
+(define_insn "add<mode>3"
+  [(set (match_operand:FIXED 0 "s_register_operand" "=r")
+	(plus:FIXED (match_operand:FIXED 1 "s_register_operand" "r")
+		    (match_operand:FIXED 2 "s_register_operand" "r")))]
+  "TARGET_32BIT"
+  "add%?\\t%0, %1, %2"
+  [(set_attr "predicable" "yes")])
+
+(define_insn "add<mode>3"
+  [(set (match_operand:ADDSUB 0 "s_register_operand" "=r")
+	(plus:ADDSUB (match_operand:ADDSUB 1 "s_register_operand" "r")
+		     (match_operand:ADDSUB 2 "s_register_operand" "r")))]
+  "TARGET_INT_SIMD"
+  "sadd<qaddsub_suf>%?\\t%0, %1, %2"
+  [(set_attr "predicable" "yes")])
+
+(define_insn "usadd<mode>3"
+  [(set (match_operand:UQADDSUB 0 "s_register_operand" "=r")
+	(us_plus:UQADDSUB (match_operand:UQADDSUB 1 "s_register_operand" "r")
+			  (match_operand:UQADDSUB 2 "s_register_operand" "r")))]
+  "TARGET_INT_SIMD"
+  "uqadd<qaddsub_suf>%?\\t%0, %1, %2"
+  [(set_attr "predicable" "yes")])
+
+(define_insn "ssadd<mode>3"
+  [(set (match_operand:QADDSUB 0 "s_register_operand" "=r")
+	(ss_plus:QADDSUB (match_operand:QADDSUB 1 "s_register_operand" "r")
+			 (match_operand:QADDSUB 2 "s_register_operand" "r")))]
+  "TARGET_INT_SIMD"
+  "qadd<qaddsub_suf>%?\\t%0, %1, %2"
+  [(set_attr "predicable" "yes")])
+
+(define_insn "sub<mode>3"
+  [(set (match_operand:FIXED 0 "s_register_operand" "=r")
+	(minus:FIXED (match_operand:FIXED 1 "s_register_operand" "r")
+		     (match_operand:FIXED 2 "s_register_operand" "r")))]
+  "TARGET_32BIT"
+  "sub%?\\t%0, %1, %2"
+  [(set_attr "predicable" "yes")])
+
+(define_insn "sub<mode>3"
+  [(set (match_operand:ADDSUB 0 "s_register_operand" "=r")
+	(minus:ADDSUB (match_operand:ADDSUB 1 "s_register_operand" "r")
+		      (match_operand:ADDSUB 2 "s_register_operand" "r")))]
+  "TARGET_INT_SIMD"
+  "ssub<qaddsub_suf>%?\\t%0, %1, %2"
+  [(set_attr "predicable" "yes")])
+
+(define_insn "ussub<mode>3"
+  [(set (match_operand:UQADDSUB 0 "s_register_operand" "=r")
+	(us_minus:UQADDSUB
+	  (match_operand:UQADDSUB 1 "s_register_operand" "r")
+	  (match_operand:UQADDSUB 2 "s_register_operand" "r")))]
+  "TARGET_INT_SIMD"
+  "uqsub<qaddsub_suf>%?\\t%0, %1, %2"
+  [(set_attr "predicable" "yes")])
+
+(define_insn "sssub<mode>3"
+  [(set (match_operand:QADDSUB 0 "s_register_operand" "=r")
+	(ss_minus:QADDSUB (match_operand:QADDSUB 1 "s_register_operand" "r")
+			  (match_operand:QADDSUB 2 "s_register_operand" "r")))]
+  "TARGET_INT_SIMD"
+  "qsub<qaddsub_suf>%?\\t%0, %1, %2"
+  [(set_attr "predicable" "yes")])
+
+;; Fractional multiplies.
+
+; Note: none of these do any rounding.
+
+(define_expand "mulqq3"
+  [(set (match_operand:QQ 0 "s_register_operand" "")
+	(mult:QQ (match_operand:QQ 1 "s_register_operand" "")
+		 (match_operand:QQ 2 "s_register_operand" "")))]
+  "TARGET_DSP_MULTIPLY && arm_arch_thumb2"
+{
+  rtx tmp1 = gen_reg_rtx (HImode);
+  rtx tmp2 = gen_reg_rtx (HImode);
+  rtx tmp3 = gen_reg_rtx (SImode);
+  
+  emit_insn (gen_extendqihi2 (tmp1, gen_lowpart (QImode, operands[1])));
+  emit_insn (gen_extendqihi2 (tmp2, gen_lowpart (QImode, operands[2])));
+  emit_insn (gen_mulhisi3 (tmp3, tmp1, tmp2));
+  emit_insn (gen_extv (gen_lowpart (SImode, operands[0]), tmp3, GEN_INT (8),
+		       GEN_INT (7)));
+  DONE;
+})
+
+(define_expand "mulhq3"
+  [(set (match_operand:HQ 0 "s_register_operand" "")
+	(mult:HQ (match_operand:HQ 1 "s_register_operand" "")
+		 (match_operand:HQ 2 "s_register_operand" "")))]
+  "TARGET_DSP_MULTIPLY && arm_arch_thumb2"
+{
+  rtx tmp = gen_reg_rtx (SImode);
+
+  emit_insn (gen_mulhisi3 (tmp, gen_lowpart (HImode, operands[1]),
+			   gen_lowpart (HImode, operands[2])));
+  /* We're doing a s.15 * s.15 multiplication, getting an s.30 result.  Extract
+     an s.15 value from that.  This won't overflow/saturate for _Fract
+     values.  */
+  emit_insn (gen_extv (gen_lowpart (SImode, operands[0]), tmp,
+		       GEN_INT (16), GEN_INT (15)));
+  DONE;
+})
+
+(define_expand "mulsq3"
+  [(set (match_operand:SQ 0 "s_register_operand" "")
+	(mult:SQ (match_operand:SQ 1 "s_register_operand" "")
+		 (match_operand:SQ 2 "s_register_operand" "")))]
+  "TARGET_32BIT && arm_arch3m"
+{
+  rtx tmp1 = gen_reg_rtx (DImode);
+  rtx tmp2 = gen_reg_rtx (SImode);
+  rtx tmp3 = gen_reg_rtx (SImode);
+  
+  /* s.31 * s.31 -> s.62 multiplication.  */
+  emit_insn (gen_mulsidi3 (tmp1, gen_lowpart (SImode, operands[1]),
+			   gen_lowpart (SImode, operands[2])));
+  emit_insn (gen_lshrsi3 (tmp2, gen_lowpart (SImode, tmp1), GEN_INT (31)));
+  emit_insn (gen_ashlsi3 (tmp3, gen_highpart (SImode, tmp1), GEN_INT (1)));
+  emit_insn (gen_iorsi3 (gen_lowpart (SImode, operands[0]), tmp2, tmp3));
+
+  DONE;
+})
+
+;; Accumulator multiplies.
+
+(define_expand "mulsa3"
+  [(set (match_operand:SA 0 "s_register_operand" "")
+	(mult:SA (match_operand:SA 1 "s_register_operand" "")
+		 (match_operand:SA 2 "s_register_operand" "")))]
+  "TARGET_32BIT && arm_arch3m"
+{
+  rtx tmp1 = gen_reg_rtx (DImode);
+  rtx tmp2 = gen_reg_rtx (SImode);
+  rtx tmp3 = gen_reg_rtx (SImode);
+  
+  emit_insn (gen_mulsidi3 (tmp1, gen_lowpart (SImode, operands[1]),
+			   gen_lowpart (SImode, operands[2])));
+  emit_insn (gen_lshrsi3 (tmp2, gen_lowpart (SImode, tmp1), GEN_INT (15)));
+  emit_insn (gen_ashlsi3 (tmp3, gen_highpart (SImode, tmp1), GEN_INT (17)));
+  emit_insn (gen_iorsi3 (gen_lowpart (SImode, operands[0]), tmp2, tmp3));
+
+  DONE;
+})
+
+(define_expand "mulusa3"
+  [(set (match_operand:USA 0 "s_register_operand" "")
+	(mult:USA (match_operand:USA 1 "s_register_operand" "")
+		  (match_operand:USA 2 "s_register_operand" "")))]
+  "TARGET_32BIT && arm_arch3m"
+{
+  rtx tmp1 = gen_reg_rtx (DImode);
+  rtx tmp2 = gen_reg_rtx (SImode);
+  rtx tmp3 = gen_reg_rtx (SImode);
+  
+  emit_insn (gen_umulsidi3 (tmp1, gen_lowpart (SImode, operands[1]),
+			    gen_lowpart (SImode, operands[2])));
+  emit_insn (gen_lshrsi3 (tmp2, gen_lowpart (SImode, tmp1), GEN_INT (16)));
+  emit_insn (gen_ashlsi3 (tmp3, gen_highpart (SImode, tmp1), GEN_INT (16)));
+  emit_insn (gen_iorsi3 (gen_lowpart (SImode, operands[0]), tmp2, tmp3));
+  
+  DONE;
+})
+
+;; The code sequence emitted by this insn pattern uses the Q flag, which GCC
+;; doesn't generally know about, so we don't bother expanding to individual
+;; instructions.  It may be better to just use an out-of-line asm libcall for
+;; this.
+
+(define_insn "ssmulsa3"
+  [(set (match_operand:SA 0 "s_register_operand" "=r")
+	(ss_mult:SA (match_operand:SA 1 "s_register_operand" "r")
+		    (match_operand:SA 2 "s_register_operand" "r")))
+   (clobber (match_scratch:DI 3 "=r"))
+   (clobber (match_scratch:SI 4 "=r"))
+   (clobber (reg:CC CC_REGNUM))]
+  "TARGET_32BIT && arm_arch6"
+{
+  /* s16.15 * s16.15 -> s32.30.  */
+  output_asm_insn ("smull\\t%Q3, %R3, %1, %2", operands);
+
+  if (TARGET_ARM)
+    output_asm_insn ("msr\\tAPSR_nzcvq, #0", operands);
+  else
+    {
+      output_asm_insn ("mov\\t%4, #0", operands);
+      output_asm_insn ("msr\\tAPSR_nzcvq, %4", operands);
+    }
+
+  /* We have:
+      31  high word  0     31  low word  0 
+
+    [ S i i .... i i i ] [ i f f f ... f f ]
+                        |
+			v
+	     [ S i ... i f ... f f ]
+
+    Need 16 integral bits, so saturate at 15th bit of high word.  */
+
+  output_asm_insn ("ssat\\t%R3, #15, %R3", operands);
+  output_asm_insn ("mrs\\t%4, APSR", operands);
+  output_asm_insn ("tst\\t%4, #1<<27", operands);
+  if (TARGET_THUMB2)
+    output_asm_insn ("it\\tne", operands);
+  output_asm_insn ("mvnne\\t%Q3, %R3, asr #32", operands);
+  output_asm_insn ("mov\\t%0, %Q3, lsr #15", operands);
+  output_asm_insn ("orr\\t%0, %0, %R3, asl #17", operands);
+  return "";
+}
+  [(set_attr "conds" "clob")
+   (set (attr "length")
+	(if_then_else (eq_attr "is_thumb" "yes")
+		      (const_int 38)
+		      (const_int 32)))])
+
+;; Same goes for this.
+
+(define_insn "usmulusa3"
+  [(set (match_operand:USA 0 "s_register_operand" "=r")
+	(us_mult:USA (match_operand:USA 1 "s_register_operand" "r")
+		     (match_operand:USA 2 "s_register_operand" "r")))
+   (clobber (match_scratch:DI 3 "=r"))
+   (clobber (match_scratch:SI 4 "=r"))
+   (clobber (reg:CC CC_REGNUM))]
+  "TARGET_32BIT && arm_arch6"
+{
+  /* 16.16 * 16.16 -> 32.32.  */
+  output_asm_insn ("umull\\t%Q3, %R3, %1, %2", operands);
+
+  if (TARGET_ARM)
+    output_asm_insn ("msr\\tAPSR_nzcvq, #0", operands);
+  else
+    {
+      output_asm_insn ("mov\\t%4, #0", operands);
+      output_asm_insn ("msr\\tAPSR_nzcvq, %4", operands);
+    }
+
+  /* We have:
+      31  high word  0     31  low word  0 
+
+    [ i i i .... i i i ] [ f f f f ... f f ]
+                        |
+			v
+	     [ i i ... i f ... f f ]
+
+    Need 16 integral bits, so saturate at 16th bit of high word.  */
+
+  output_asm_insn ("usat\\t%R3, #16, %R3", operands);
+  output_asm_insn ("mrs\\t%4, APSR", operands);
+  output_asm_insn ("tst\\t%4, #1<<27", operands);
+  if (TARGET_THUMB2)
+    output_asm_insn ("it\\tne", operands);
+  output_asm_insn ("sbfxne\\t%Q3, %R3, #15, #1", operands);
+  output_asm_insn ("lsr\\t%0, %Q3, #16", operands);
+  output_asm_insn ("orr\\t%0, %0, %R3, asl #16", operands);
+  return "";
+}
+  [(set_attr "conds" "clob")
+   (set (attr "length")
+	(if_then_else (eq_attr "is_thumb" "yes")
+		      (const_int 38)
+		      (const_int 32)))])
+
+(define_expand "mulha3"
+  [(set (match_operand:HA 0 "s_register_operand" "")
+	(mult:HA (match_operand:HA 1 "s_register_operand" "")
+		 (match_operand:HA 2 "s_register_operand" "")))]
+  "TARGET_DSP_MULTIPLY && arm_arch_thumb2"
+{
+  rtx tmp = gen_reg_rtx (SImode);
+  
+  emit_insn (gen_mulhisi3 (tmp, gen_lowpart (HImode, operands[1]),
+			   gen_lowpart (HImode, operands[2])));
+  emit_insn (gen_extv (gen_lowpart (SImode, operands[0]), tmp, GEN_INT (16),
+		       GEN_INT (7)));
+
+  DONE;
+})
+
+(define_expand "muluha3"
+  [(set (match_operand:UHA 0 "s_register_operand" "")
+	(mult:UHA (match_operand:UHA 1 "s_register_operand" "")
+		  (match_operand:UHA 2 "s_register_operand" "")))]
+  "TARGET_DSP_MULTIPLY"
+{
+  rtx tmp1 = gen_reg_rtx (SImode);
+  rtx tmp2 = gen_reg_rtx (SImode);
+  rtx tmp3 = gen_reg_rtx (SImode);
+  
+  /* 8.8 * 8.8 -> 16.16 multiply.  */
+  emit_insn (gen_zero_extendhisi2 (tmp1, gen_lowpart (HImode, operands[1])));
+  emit_insn (gen_zero_extendhisi2 (tmp2, gen_lowpart (HImode, operands[2])));
+  emit_insn (gen_mulsi3 (tmp3, tmp1, tmp2));
+  emit_insn (gen_extzv (gen_lowpart (SImode, operands[0]), tmp3,
+			GEN_INT (16), GEN_INT (8)));
+
+  DONE;
+})
+
+(define_expand "ssmulha3"
+  [(set (match_operand:HA 0 "s_register_operand" "")
+	(ss_mult:HA (match_operand:HA 1 "s_register_operand" "")
+		    (match_operand:HA 2 "s_register_operand" "")))]
+  "TARGET_32BIT && TARGET_DSP_MULTIPLY && arm_arch6"
+{
+  rtx tmp = gen_reg_rtx (SImode);
+  rtx rshift;
+  
+  emit_insn (gen_mulhisi3 (tmp, gen_lowpart (HImode, operands[1]),
+			   gen_lowpart (HImode, operands[2])));
+
+  rshift = gen_rtx_ASHIFTRT (SImode, tmp, GEN_INT (7));
+
+  emit_insn (gen_rtx_SET (VOIDmode, gen_lowpart (HImode, operands[0]),
+			  gen_rtx_SS_TRUNCATE (HImode, rshift)));
+
+  DONE;
+})
+
+(define_expand "usmuluha3"
+  [(set (match_operand:UHA 0 "s_register_operand" "")
+	(us_mult:UHA (match_operand:UHA 1 "s_register_operand" "")
+		     (match_operand:UHA 2 "s_register_operand" "")))]
+  "TARGET_INT_SIMD"
+{
+  rtx tmp1 = gen_reg_rtx (SImode);
+  rtx tmp2 = gen_reg_rtx (SImode);
+  rtx tmp3 = gen_reg_rtx (SImode);
+  rtx rshift_tmp = gen_reg_rtx (SImode);
+  
+  /* Note: there's no smul[bt][bt] equivalent for unsigned multiplies.  Use a
+     normal 32x32->32-bit multiply instead.  */
+  emit_insn (gen_zero_extendhisi2 (tmp1, gen_lowpart (HImode, operands[1])));
+  emit_insn (gen_zero_extendhisi2 (tmp2, gen_lowpart (HImode, operands[2])));
+  
+  emit_insn (gen_mulsi3 (tmp3, tmp1, tmp2));
+
+  /* The operand to "usat" is signed, so we cannot use the "..., asr #8"
+     form of that instruction since the multiplication result TMP3 may have the
+     top bit set, thus be negative and saturate to zero.  Use a separate
+     logical right-shift instead.  */
+  emit_insn (gen_lshrsi3 (rshift_tmp, tmp3, GEN_INT (8)));
+  emit_insn (gen_arm_usatsihi (gen_lowpart (HImode, operands[0]), rshift_tmp));
+
+  DONE;
+})
+
+(define_insn "arm_ssatsihi_shift"
+  [(set (match_operand:HI 0 "s_register_operand" "=r")
+	(ss_truncate:HI (match_operator:SI 1 "sat_shift_operator"
+			  [(match_operand:SI 2 "s_register_operand" "r")
+			   (match_operand:SI 3 "immediate_operand" "I")])))]
+  "TARGET_32BIT && arm_arch6"
+  "ssat%?\\t%0, #16, %2%S1"
+  [(set_attr "predicable" "yes")
+   (set_attr "type" "alu_shift")])
+
+(define_insn "arm_usatsihi"
+  [(set (match_operand:HI 0 "s_register_operand" "=r")
+	(us_truncate:HI (match_operand:SI 1 "s_register_operand")))]
+  "TARGET_INT_SIMD"
+  "usat%?\\t%0, #16, %1"
+  [(set_attr "predicable" "yes")])
diff --git a/gcc/config/arm/arm-modes.def b/gcc/config/arm/arm-modes.def
index 24e3d90..7f19ebe 100644
--- a/gcc/config/arm/arm-modes.def
+++ b/gcc/config/arm/arm-modes.def
@@ -70,6 +70,12 @@  VECTOR_MODES (INT, 16);       /* V16QI V8HI V4SI V2DI */
 VECTOR_MODES (FLOAT, 8);      /*            V4HF V2SF */
 VECTOR_MODES (FLOAT, 16);     /*       V8HF V4SF V2DF */
 
+/* Fraction and accumulator vector modes.  */
+VECTOR_MODES (FRACT, 4);      /* V4QQ  V2HQ */
+VECTOR_MODES (UFRACT, 4);     /* V4UQQ V2UHQ */
+VECTOR_MODES (ACCUM, 4);      /*       V2HA */
+VECTOR_MODES (UACCUM, 4);     /*       V2UHA */
+
 /* Opaque integer modes for 3, 4, 6 or 8 Neon double registers (2 is
    TImode).  */
 INT_MODE (EI, 24);
diff --git a/gcc/config/arm/arm.c b/gcc/config/arm/arm.c
index cd32fe3..5a62802 100644
--- a/gcc/config/arm/arm.c
+++ b/gcc/config/arm/arm.c
@@ -986,6 +986,49 @@  bit_count (unsigned long value)
   return count;
 }
 
+typedef struct
+{
+  enum machine_mode mode;
+  const char *name;
+} arm_fixed_mode_set;
+
+/* A small helper for setting fixed-point library libfuncs.  */
+
+static void
+arm_set_fixed_optab_libfunc (optab optable, enum machine_mode mode,
+			     const char *funcname, const char *modename,
+			     int num_suffix)
+{
+  char buffer[50];
+  
+  if (num_suffix == 0)
+    sprintf (buffer, "__gnu_%s%s", funcname, modename);
+  else
+    sprintf (buffer, "__gnu_%s%s%d", funcname, modename, num_suffix);
+  
+  set_optab_libfunc (optable, mode, buffer);
+}
+
+static void
+arm_set_fixed_conv_libfunc (convert_optab optable, enum machine_mode to,
+			    enum machine_mode from, const char *funcname,
+			    const char *toname, const char *fromname)
+{
+  char buffer[50];
+  char *maybe_suffix_2 = "";
+  
+  /* Follow the logic for selecting a "2" suffix in fixed-bit.h.  */
+  if (ALL_FIXED_POINT_MODE_P (from) && ALL_FIXED_POINT_MODE_P (to)
+      && UNSIGNED_FIXED_POINT_MODE_P (from) == UNSIGNED_FIXED_POINT_MODE_P (to)
+      && ALL_FRACT_MODE_P (from) == ALL_FRACT_MODE_P (to))
+    maybe_suffix_2 = "2";
+  
+  sprintf (buffer, "__gnu_%s%s%s%s", funcname, fromname, toname,
+	   maybe_suffix_2);
+
+  set_conv_libfunc (optable, to, from, buffer);
+}
+
 /* Set up library functions unique to ARM.  */
 
 static void
@@ -1131,6 +1174,137 @@  arm_init_libfuncs (void)
       break;
     }
 
+  /* Use names prefixed with __gnu_ for fixed-point helper functions.  */
+  {
+    const arm_fixed_mode_set fixed_arith_modes[] =
+      {
+	{ QQmode, "qq" },
+	{ UQQmode, "uqq" },
+	{ HQmode, "hq" },
+	{ UHQmode, "uhq" },
+	{ SQmode, "sq" },
+	{ USQmode, "usq" },
+	{ DQmode, "dq" },
+	{ UDQmode, "udq" },
+	{ TQmode, "tq" },
+	{ UTQmode, "utq" },
+	{ HAmode, "ha" },
+	{ UHAmode, "uha" },
+	{ SAmode, "sa" },
+	{ USAmode, "usa" },
+	{ DAmode, "da" },
+	{ UDAmode, "uda" },
+	{ TAmode, "ta" },
+	{ UTAmode, "uta" }
+      };
+    const arm_fixed_mode_set fixed_conv_modes[] =
+      {
+	{ QQmode, "qq" },
+	{ UQQmode, "uqq" },
+	{ HQmode, "hq" },
+	{ UHQmode, "uhq" },
+	{ SQmode, "sq" },
+	{ USQmode, "usq" },
+	{ DQmode, "dq" },
+	{ UDQmode, "udq" },
+	{ TQmode, "tq" },
+	{ UTQmode, "utq" },
+	{ HAmode, "ha" },
+	{ UHAmode, "uha" },
+	{ SAmode, "sa" },
+	{ USAmode, "usa" },
+	{ DAmode, "da" },
+	{ UDAmode, "uda" },
+	{ TAmode, "ta" },
+	{ UTAmode, "uta" },
+	{ QImode, "qi" },
+	{ HImode, "hi" },
+	{ SImode, "si" },
+	{ DImode, "di" },
+	{ TImode, "ti" },
+	{ SFmode, "sf" },
+	{ DFmode, "df" }
+      };
+    unsigned int i, j;
+
+    for (i = 0; i < ARRAY_SIZE (fixed_arith_modes); i++)
+      {
+	arm_set_fixed_optab_libfunc (add_optab, fixed_arith_modes[i].mode,
+				     "add", fixed_arith_modes[i].name, 3);
+	arm_set_fixed_optab_libfunc (ssadd_optab, fixed_arith_modes[i].mode,
+				     "ssadd", fixed_arith_modes[i].name, 3);
+	arm_set_fixed_optab_libfunc (usadd_optab, fixed_arith_modes[i].mode,
+				     "usadd", fixed_arith_modes[i].name, 3);
+	arm_set_fixed_optab_libfunc (sub_optab, fixed_arith_modes[i].mode,
+				     "sub", fixed_arith_modes[i].name, 3);
+	arm_set_fixed_optab_libfunc (sssub_optab, fixed_arith_modes[i].mode,
+				     "sssub", fixed_arith_modes[i].name, 3);
+	arm_set_fixed_optab_libfunc (ussub_optab, fixed_arith_modes[i].mode,
+				     "ussub", fixed_arith_modes[i].name, 3);
+	arm_set_fixed_optab_libfunc (smul_optab, fixed_arith_modes[i].mode,
+				     "mul", fixed_arith_modes[i].name, 3);
+	arm_set_fixed_optab_libfunc (ssmul_optab, fixed_arith_modes[i].mode,
+				     "ssmul", fixed_arith_modes[i].name, 3);
+	arm_set_fixed_optab_libfunc (usmul_optab, fixed_arith_modes[i].mode,
+				     "usmul", fixed_arith_modes[i].name, 3);
+	arm_set_fixed_optab_libfunc (sdiv_optab, fixed_arith_modes[i].mode,
+				     "div", fixed_arith_modes[i].name, 3);
+	arm_set_fixed_optab_libfunc (udiv_optab, fixed_arith_modes[i].mode,
+				     "udiv", fixed_arith_modes[i].name, 3);
+	arm_set_fixed_optab_libfunc (ssdiv_optab, fixed_arith_modes[i].mode,
+				     "ssdiv", fixed_arith_modes[i].name, 3);
+	arm_set_fixed_optab_libfunc (usdiv_optab, fixed_arith_modes[i].mode,
+				     "usdiv", fixed_arith_modes[i].name, 3);
+	arm_set_fixed_optab_libfunc (neg_optab, fixed_arith_modes[i].mode,
+				     "neg", fixed_arith_modes[i].name, 2);
+	arm_set_fixed_optab_libfunc (ssneg_optab, fixed_arith_modes[i].mode,
+				     "ssneg", fixed_arith_modes[i].name, 2);
+	arm_set_fixed_optab_libfunc (usneg_optab, fixed_arith_modes[i].mode,
+				     "usneg", fixed_arith_modes[i].name, 2);
+	arm_set_fixed_optab_libfunc (ashl_optab, fixed_arith_modes[i].mode,
+				     "ashl", fixed_arith_modes[i].name, 3);
+	arm_set_fixed_optab_libfunc (ashr_optab, fixed_arith_modes[i].mode,
+				     "ashr", fixed_arith_modes[i].name, 3);
+	arm_set_fixed_optab_libfunc (lshr_optab, fixed_arith_modes[i].mode,
+				     "lshr", fixed_arith_modes[i].name, 3);
+	arm_set_fixed_optab_libfunc (ssashl_optab, fixed_arith_modes[i].mode,
+				     "ssashl", fixed_arith_modes[i].name, 3);
+	arm_set_fixed_optab_libfunc (usashl_optab, fixed_arith_modes[i].mode,
+				     "usashl", fixed_arith_modes[i].name, 3);
+	arm_set_fixed_optab_libfunc (cmp_optab, fixed_arith_modes[i].mode,
+				     "cmp", fixed_arith_modes[i].name, 2);
+      }
+
+    for (i = 0; i < ARRAY_SIZE (fixed_conv_modes); i++)
+      for (j = 0; j < ARRAY_SIZE (fixed_conv_modes); j++)
+	{
+	  if (i == j
+	      || (!ALL_FIXED_POINT_MODE_P (fixed_conv_modes[i].mode)
+		  && !ALL_FIXED_POINT_MODE_P (fixed_conv_modes[j].mode)))
+	    continue;
+
+	  arm_set_fixed_conv_libfunc (fract_optab, fixed_conv_modes[i].mode,
+				      fixed_conv_modes[j].mode, "fract",
+				      fixed_conv_modes[i].name,
+				      fixed_conv_modes[j].name);
+	  arm_set_fixed_conv_libfunc (satfract_optab,
+				      fixed_conv_modes[i].mode,
+				      fixed_conv_modes[j].mode, "satfract",
+				      fixed_conv_modes[i].name,
+				      fixed_conv_modes[j].name);
+	  arm_set_fixed_conv_libfunc (fractuns_optab,
+				      fixed_conv_modes[i].mode,
+				      fixed_conv_modes[j].mode, "fractuns",
+				      fixed_conv_modes[i].name,
+				      fixed_conv_modes[j].name);
+	  arm_set_fixed_conv_libfunc (satfractuns_optab,
+				      fixed_conv_modes[i].mode,
+				      fixed_conv_modes[j].mode, "satfractuns",
+				      fixed_conv_modes[i].name,
+				      fixed_conv_modes[j].name);
+	}
+  }
+
   if (TARGET_AAPCS_BASED)
     synchronize_libfunc = init_one_libfunc ("__sync_synchronize");
 }
@@ -4137,6 +4311,10 @@  aapcs_allocate_return_reg (enum machine_mode mode, const_tree type,
 rtx
 aapcs_libcall_value (enum machine_mode mode)
 {
+  if (BYTES_BIG_ENDIAN && ALL_FIXED_POINT_MODE_P (mode)
+      && GET_MODE_SIZE (mode) <= 4)
+    mode = SImode;
+
   return aapcs_allocate_return_reg (mode, NULL_TREE, NULL_TREE);
 }
 
@@ -9037,8 +9215,9 @@  arm_return_in_msb (const_tree valtype)
 {
   return (TARGET_AAPCS_BASED
           && BYTES_BIG_ENDIAN
-          && (AGGREGATE_TYPE_P (valtype)
-              || TREE_CODE (valtype) == COMPLEX_TYPE));
+	  && (AGGREGATE_TYPE_P (valtype)
+	      || TREE_CODE (valtype) == COMPLEX_TYPE
+	      || FIXED_POINT_TYPE_P (valtype)));
 }
 
 /* Returns TRUE if INSN is an "LDR REG, ADDR" instruction.
@@ -11401,7 +11580,8 @@  arm_pad_reg_upward (enum machine_mode mode ATTRIBUTE_UNUSED,
 {
   if (TARGET_AAPCS_BASED
       && BYTES_BIG_ENDIAN
-      && (AGGREGATE_TYPE_P (type) || TREE_CODE (type) == COMPLEX_TYPE)
+      && (AGGREGATE_TYPE_P (type) || TREE_CODE (type) == COMPLEX_TYPE
+	  || FIXED_POINT_TYPE_P (type))
       && int_size_in_bytes (type) <= 4)
     return true;
 
@@ -19539,6 +19719,8 @@  arm_scalar_mode_supported_p (enum machine_mode mode)
 {
   if (mode == HFmode)
     return (arm_fp16_format != ARM_FP16_FORMAT_NONE);
+  else if (ALL_FIXED_POINT_MODE_P (mode))
+    return true;
   else
     return default_scalar_mode_supported_p (mode);
 }
@@ -22664,6 +22846,11 @@  arm_vector_mode_supported_p (enum machine_mode mode)
 	  || (mode == V8QImode)))
     return true;
 
+  if (TARGET_INT_SIMD && (mode == V4UQQmode || mode == V4QQmode
+      || mode == V2UHQmode || mode == V2HQmode || mode == V2UHAmode
+      || mode == V2HAmode))
+    return true;
+
   return false;
 }
 
diff --git a/gcc/config/arm/arm.h b/gcc/config/arm/arm.h
index 86d842d..7a5dd01 100644
--- a/gcc/config/arm/arm.h
+++ b/gcc/config/arm/arm.h
@@ -592,6 +592,20 @@  extern int arm_arch_hwdiv;
 #define WCHAR_TYPE_SIZE BITS_PER_WORD
 #endif
 
+/* Sized for fixed-point types.  */
+
+#define SHORT_FRACT_TYPE_SIZE 8
+#define FRACT_TYPE_SIZE 16
+#define LONG_FRACT_TYPE_SIZE 32
+#define LONG_LONG_FRACT_TYPE_SIZE 64
+
+#define SHORT_ACCUM_TYPE_SIZE 16
+#define ACCUM_TYPE_SIZE 32
+#define LONG_ACCUM_TYPE_SIZE 64
+#define LONG_LONG_ACCUM_TYPE_SIZE 64
+
+#define MAX_FIXED_MODE_SIZE 64
+
 #ifndef SIZE_TYPE
 #define SIZE_TYPE (TARGET_AAPCS_BASED ? "unsigned int" : "long unsigned int")
 #endif
diff --git a/gcc/config/arm/arm.md b/gcc/config/arm/arm.md
index 12fd7ca..72f862e 100644
--- a/gcc/config/arm/arm.md
+++ b/gcc/config/arm/arm.md
@@ -10935,3 +10935,5 @@ 
 (include "neon.md")
 ;; Synchronization Primitives
 (include "sync.md")
+;; Fixed-point patterns
+(include "arm-fixed.md")
diff --git a/gcc/config/arm/fixed-bit-machine.h b/gcc/config/arm/fixed-bit-machine.h
new file mode 100644
index 0000000..9f3bf5b
--- /dev/null
+++ b/gcc/config/arm/fixed-bit-machine.h
@@ -0,0 +1,30 @@ 
+/* Machine-specific override for fixed-point support routine names.
+   Copyright (C) 2011 Free Software Foundation, Inc.
+
+   This file is part of GCC.
+
+   GCC is free software; you can redistribute it and/or modify it
+   under the terms of the GNU General Public License as published
+   by the Free Software Foundation; either version 3, or (at your
+   option) any later version.
+
+   GCC is distributed in the hope that it will be useful, but WITHOUT
+   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+   or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
+   License for more details.
+
+   Under Section 7 of GPL version 3, you are granted additional
+   permissions described in the GCC Runtime Library Exception, version
+   3.1, as published by the Free Software Foundation.
+
+   You should have received a copy of the GNU General Public License and
+   a copy of the GCC Runtime Library Exception along with this program;
+   see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
+   <http://www.gnu.org/licenses/>.  */
+
+/* To be EABI-compliant (until the EABI supports fixed-point functionality),
+   use the __gnu_ namespace for fixed-point helper functions.  */
+
+#ifdef __ARM_EABI__
+#define DECORATE_FIXED_NAME(X) __gnu_ ## X
+#endif
diff --git a/gcc/config/arm/predicates.md b/gcc/config/arm/predicates.md
index 891a974..f1800dc 100644
--- a/gcc/config/arm/predicates.md
+++ b/gcc/config/arm/predicates.md
@@ -227,6 +227,13 @@ 
 	    (match_code "ashift,ashiftrt,lshiftrt,rotatert"))
        (match_test "mode == GET_MODE (op)")))
 
+;; True for shift operators which can be used with saturation instructions.
+(define_special_predicate "sat_shift_operator"
+  (and (match_code "ashift,ashiftrt")
+       (match_test "GET_CODE (XEXP (op, 1)) == CONST_INT
+		    && ((unsigned HOST_WIDE_INT) INTVAL (XEXP (op, 1)) <= 32)")
+       (match_test "mode == GET_MODE (op)")))
+
 ;; True for MULT, to identify which variant of shift_operator is in use.
 (define_special_predicate "mult_operator"
   (match_code "mult"))
diff --git a/gcc/config/arm/t-arm b/gcc/config/arm/t-arm
index 826ec0a..b970ec2 100644
--- a/gcc/config/arm/t-arm
+++ b/gcc/config/arm/t-arm
@@ -37,7 +37,8 @@  MD_INCLUDES= 	$(srcdir)/config/arm/arm-tune.md \
 		$(srcdir)/config/arm/iwmmxt.md \
 		$(srcdir)/config/arm/vfp.md \
 		$(srcdir)/config/arm/neon.md \
-		$(srcdir)/config/arm/thumb2.md
+		$(srcdir)/config/arm/thumb2.md \
+		$(srcdir)/config/arm/arm-fixed.md
 
 LIB1ASMSRC = arm/lib1funcs.asm
 LIB1ASMFUNCS = _thumb1_case_sqi _thumb1_case_uqi _thumb1_case_shi \
diff --git a/gcc/configure b/gcc/configure
index 274af3e..aba11bf 100755
diff --git a/gcc/configure.ac b/gcc/configure.ac
index 9ace66d..db783ea 100644
--- a/gcc/configure.ac
+++ b/gcc/configure.ac
@@ -638,6 +638,10 @@  AC_ARG_ENABLE(fixed-point,
 [],
 [
   case $target in
+    arm*)
+      enable_fixed_point=yes
+      ;;
+
     mips*-*-*)
       case $host in
 	mips*-sgi-irix*)
diff --git a/gcc/testsuite/gcc.target/arm/fixed-point-exec.c b/gcc/testsuite/gcc.target/arm/fixed-point-exec.c
new file mode 100644
index 0000000..6bc3b07
--- /dev/null
+++ b/gcc/testsuite/gcc.target/arm/fixed-point-exec.c
@@ -0,0 +1,301 @@ 
+/* { dg-do run { target { fixed_point } } } */
+/* { dg-options "-std=gnu99" } */
+
+/* Check basic arithmetic ops for ARM fixed-point/saturating operation support.
+   Not target-independent since we make various assumptions about precision and
+   magnitudes of various types.  */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+#include <stdfix.h>
+
+#define TEST(TYPE, OP, NAME, SUFFIX)				\
+  TYPE NAME##SUFFIX (TYPE A, TYPE B)				\
+    {								\
+      return A OP B;						\
+    }
+
+#define VARIANTS(TYPE, OP, NAME)				\
+  TEST (short TYPE, OP, NAME, _short);				\
+  TEST (TYPE, OP, NAME, _regular);				\
+  TEST (long TYPE, OP, NAME, _long);				\
+  TEST (_Sat short TYPE, OP, NAME, _sat_short);			\
+  TEST (_Sat TYPE, OP, NAME, _sat_regular);			\
+  TEST (_Sat long TYPE, OP, NAME, _sat_long);			\
+  TEST (unsigned short TYPE, OP, NAME, _uns_short);		\
+  TEST (unsigned TYPE, OP, NAME, _uns_regular);			\
+  TEST (unsigned long TYPE, OP, NAME, _uns_long);		\
+  TEST (unsigned _Sat short TYPE, OP, NAME, _uns_sat_short);	\
+  TEST (unsigned _Sat TYPE, OP, NAME, _uns_sat_regular);	\
+  TEST (unsigned _Sat long TYPE, OP, NAME, _uns_sat_long)
+
+VARIANTS (_Fract, +, plus_fract);
+VARIANTS (_Accum, +, plus_accum);
+VARIANTS (_Fract, -, minus_fract);
+VARIANTS (_Accum, -, minus_accum);
+VARIANTS (_Fract, *, mult_fract);
+VARIANTS (_Accum, *, mult_accum);
+VARIANTS (_Accum, /, div_accum);
+
+/* Inputs for signed add, multiply fractional tests.  */
+short _Fract sf_a = 0.9hr;
+short _Fract sf_b = -0.8hr;
+_Fract f_a = 0.9r;
+_Fract f_b = -0.8r;
+long _Fract lf_a = 0.9lr;
+long _Fract lf_b = -0.8lr;
+
+/* Inputs for signed subtract fractional tests.  */
+short _Fract sf_c = 0.7hr;
+short _Fract sf_d = 0.9hr;
+_Fract f_c = 0.7r;
+_Fract f_d = 0.9r;
+long _Fract lf_c = 0.7lr;
+long _Fract lf_d = 0.9lr;
+
+/* Inputs for unsigned add, subtract, multiply fractional tests.  */
+unsigned short _Fract usf_a = 0.4uhr;
+unsigned short _Fract usf_b = 0.3uhr;
+unsigned _Fract uf_a = 0.4ur;
+unsigned _Fract uf_b = 0.3ur;
+unsigned long _Fract ulf_a = 0.4ulr;
+unsigned long _Fract ulf_b = 0.3ulr;
+
+/* Inputs for saturating signed add tests.  */
+short _Sat _Fract sf_e = 0.8hr;
+short _Sat _Fract sf_f = 0.8hr;
+_Sat _Fract f_e = 0.8r;
+_Sat _Fract f_f = 0.8r;
+long _Sat _Fract lf_e = 0.8r;
+long _Sat _Fract lf_f = 0.8r;
+
+short _Sat _Fract sf_g = -0.8hr;
+short _Sat _Fract sf_h = -0.8hr;
+_Sat _Fract f_g = -0.8r;
+_Sat _Fract f_h = -0.8r;
+long _Sat _Fract lf_g = -0.8r;
+long _Sat _Fract lf_h = -0.8r;
+
+/* Inputs for saturating unsigned subtract tests.  */
+unsigned short _Sat _Fract usf_c = 0.3uhr;
+unsigned short _Sat _Fract usf_d = 0.4uhr;
+unsigned _Sat _Fract uf_c = 0.3ur;
+unsigned _Sat _Fract uf_d = 0.4ur;
+unsigned long _Sat _Fract ulf_c = 0.3ulr;
+unsigned long _Sat _Fract ulf_d = 0.4ulr;
+
+/* Inputs for signed accumulator tests.  */
+
+short _Accum sa_a = 1.25hk;
+short _Accum sa_b = -1.5hk;
+_Accum a_a = 100.25k;
+_Accum a_b = -100.5k;
+long _Accum la_a = 1000.25lk;
+long _Accum la_b = -1000.5lk;
+
+/* Inputs for unsigned accumulator tests.  */
+
+unsigned short _Accum usa_a = 2.5uhk;
+unsigned short _Accum usa_b = 1.75uhk;
+unsigned _Accum ua_a = 255.5uk;
+unsigned _Accum ua_b = 170.25uk;
+unsigned long _Accum ula_a = 1550.5ulk;
+unsigned long _Accum ula_b = 999.5ulk;
+
+/* Inputs for signed saturating accumulator tests.  */
+
+short _Sat _Accum sa_c = 240.0hk;
+short _Sat _Accum sa_d = 250.0hk;
+short _Sat _Accum sa_e = -240.0hk;
+short _Sat _Accum sa_f = -250.0hk;
+short _Sat _Accum sa_g = 0.5hk;
+
+_Sat _Accum a_c = 65000.0k;
+_Sat _Accum a_d = 20000.0k;
+_Sat _Accum a_e = -65000.0k;
+_Sat _Accum a_f = -20000.0k;
+_Sat _Accum a_g = 0.5k;
+
+long _Sat _Accum la_c = 3472883712.0lk;
+long _Sat _Accum la_d = 3456106496.0lk;
+long _Sat _Accum la_e = -3472883712.0lk;
+long _Sat _Accum la_f = -3456106496.0lk;
+long _Sat _Accum la_g = 0.5lk;
+
+/* Inputs for unsigned saturating accumulator tests.  */
+
+unsigned short _Sat _Accum usa_c = 250.0uhk;
+unsigned short _Sat _Accum usa_d = 240.0uhk;
+unsigned short _Sat _Accum usa_e = 0.5uhk;
+
+unsigned _Sat _Accum ua_c = 65000.0uk;
+unsigned _Sat _Accum ua_d = 20000.0uk;
+unsigned _Sat _Accum ua_e = 0.5uk;
+
+unsigned long _Sat _Accum ula_c = 3472883712.0ulk;
+unsigned long _Sat _Accum ula_d = 3456106496.0ulk;
+unsigned long _Sat _Accum ula_e = 0.5ulk;
+
+#define CHECK(FN, EXP) do {						     \
+  if (fabs ((float) (FN) - (EXP)) > 0.05)				     \
+    {									     \
+      fprintf (stderr, "result for " #FN " (as float): %f\n", (double) (FN));\
+      abort ();								     \
+    }									     \
+  } while (0)
+
+#define CHECK_EXACT(FN, EXP) do {					     \
+  if ((FN) != (EXP))							     \
+    {									     \
+      fprintf (stderr, "result for " #FN " (as float): %f, should be %f\n",  \
+	       (double) (FN), (double) (EXP));				     \
+      abort ();								     \
+    }									     \
+  } while (0)
+
+int
+main (int argc, char *argv[])
+{
+  /* Fract/fract operations, non-saturating.  */
+
+  CHECK (plus_fract_short (sf_a, sf_b), 0.1);
+  CHECK (plus_fract_regular (f_a, f_b), 0.1);
+  CHECK (plus_fract_long (lf_a, lf_b), 0.1);
+
+  CHECK (plus_fract_uns_short (usf_a, usf_b), 0.7);
+  CHECK (plus_fract_uns_regular (uf_a, uf_b), 0.7);
+  CHECK (plus_fract_uns_long (ulf_a, ulf_b), 0.7);
+
+  CHECK (minus_fract_short (sf_c, sf_d), -0.2);
+  CHECK (minus_fract_regular (f_c, f_d), -0.2);
+  CHECK (minus_fract_long (lf_c, lf_d), -0.2);
+
+  CHECK (minus_fract_uns_short (usf_a, usf_b), 0.1);
+  CHECK (minus_fract_uns_regular (uf_a, uf_b), 0.1);
+  CHECK (minus_fract_uns_long (ulf_a, ulf_b), 0.1);
+
+  CHECK (mult_fract_short (sf_a, sf_b), -0.72);
+  CHECK (mult_fract_regular (f_a, f_b), -0.72);
+  CHECK (mult_fract_long (lf_a, lf_b), -0.72);
+
+  CHECK (mult_fract_uns_short (usf_a, usf_b), 0.12);
+  CHECK (mult_fract_uns_regular (uf_a, uf_b), 0.12);
+  CHECK (mult_fract_uns_long (ulf_a, ulf_b), 0.12);
+
+  /* Fract/fract operations, saturating.  */
+
+  CHECK (plus_fract_sat_short (sf_e, sf_f), 1.0);
+  CHECK (plus_fract_sat_regular (f_e, f_f), 1.0);
+  CHECK (plus_fract_sat_long (lf_e, lf_f), 1.0);
+
+  CHECK (plus_fract_sat_short (sf_g, sf_h), -1.0);
+  CHECK (plus_fract_sat_regular (f_g, f_h), -1.0);
+  CHECK (plus_fract_sat_long (lf_g, lf_h), -1.0);
+  
+  CHECK (plus_fract_uns_sat_short (sf_e, sf_f), 1.0);
+  CHECK (plus_fract_uns_sat_regular (f_e, f_f), 1.0);
+  CHECK (plus_fract_uns_sat_long (lf_e, lf_f), 1.0);
+
+  CHECK (plus_fract_sat_short (sf_a, sf_b), 0.1);
+  CHECK (plus_fract_sat_regular (f_a, f_b), 0.1);
+  CHECK (plus_fract_sat_long (lf_a, lf_b), 0.1);
+  
+  CHECK (plus_fract_uns_sat_short (usf_a, usf_b), 0.7);
+  CHECK (plus_fract_uns_sat_regular (uf_a, uf_b), 0.7);
+  CHECK (plus_fract_uns_sat_long (ulf_a, ulf_b), 0.7);
+
+  CHECK (minus_fract_uns_sat_short (usf_c, usf_d), 0.0);
+  CHECK (minus_fract_uns_sat_regular (uf_c, uf_d), 0.0);
+  CHECK (minus_fract_uns_sat_short (ulf_c, ulf_d), 0.0);
+  
+  CHECK (minus_fract_sat_short (sf_c, sf_d), -0.2);
+  CHECK (minus_fract_sat_regular (f_c, f_d), -0.2);
+  CHECK (minus_fract_sat_long (lf_c, lf_d), -0.2);
+
+  /* Accum/accum operations, non-saturating.  */
+
+  CHECK (plus_accum_short (sa_a, sa_b), -0.25);
+  CHECK (plus_accum_regular (a_a, a_b), -0.25);
+  CHECK (plus_accum_long (la_a, la_b), -0.25);
+
+  CHECK (minus_accum_short (sa_a, sa_b), 2.75);
+  CHECK (minus_accum_regular (a_a, a_b), 200.75);
+  CHECK (minus_accum_long (la_a, la_b), 2000.75);
+  
+  CHECK (mult_accum_short (sa_a, sa_b), -1.875);
+  CHECK (mult_accum_regular (a_a, a_b), -10075.125);
+  CHECK (mult_accum_long (la_a, la_b), -1000750.125);
+
+  CHECK (div_accum_short (sa_a, sa_b), -1.25/1.5);
+  CHECK (div_accum_regular (a_a, a_b), -100.25/100.5);
+  CHECK (div_accum_long (la_a, la_b), -1000.25/1000.5);
+
+  /* Unsigned accum/accum operations, non-saturating.  */
+  
+  CHECK (plus_accum_uns_short (usa_a, usa_b), 4.25);
+  CHECK (plus_accum_uns_regular (ua_a, ua_b), 425.75);
+  CHECK (plus_accum_uns_long (ula_a, ula_b), 2550.0);
+
+  CHECK (minus_accum_uns_short (usa_a, usa_b), 0.75);
+  CHECK (minus_accum_uns_regular (ua_a, ua_b), 85.25);
+  CHECK (minus_accum_uns_long (ula_a, ula_b), 551.0);
+  
+  CHECK (mult_accum_uns_short (usa_a, usa_b), 4.375);
+  CHECK (mult_accum_uns_regular (ua_a, ua_b), 43498.875);
+  CHECK (mult_accum_uns_long (ula_a, ula_b), 1549724.75);
+
+  CHECK (div_accum_uns_short (usa_a, usa_b), 2.5/1.75);
+  CHECK (div_accum_uns_regular (ua_a, ua_b), 255.5/170.25);
+  CHECK (div_accum_uns_long (ula_a, ula_b), 1550.5/999.5);
+
+  /* Signed accum/accum operations, saturating.  */
+  
+  CHECK_EXACT (plus_accum_sat_short (sa_c, sa_d), SACCUM_MAX);
+  CHECK_EXACT (plus_accum_sat_short (sa_e, sa_f), SACCUM_MIN);
+  CHECK_EXACT (plus_accum_sat_regular (a_c, a_d), ACCUM_MAX);
+  CHECK_EXACT (plus_accum_sat_regular (a_e, a_f), ACCUM_MIN);
+  CHECK_EXACT (plus_accum_sat_long (la_c, la_d), LACCUM_MAX);
+  CHECK_EXACT (plus_accum_sat_long (la_e, la_f), LACCUM_MIN);
+
+  CHECK_EXACT (minus_accum_sat_short (sa_e, sa_d), SACCUM_MIN);
+  CHECK_EXACT (minus_accum_sat_short (sa_c, sa_f), SACCUM_MAX);
+  CHECK_EXACT (minus_accum_sat_regular (a_e, a_d), ACCUM_MIN);
+  CHECK_EXACT (minus_accum_sat_regular (a_c, a_f), ACCUM_MAX);
+  CHECK_EXACT (minus_accum_sat_long (la_e, la_d), LACCUM_MIN);
+  CHECK_EXACT (minus_accum_sat_long (la_c, la_f), LACCUM_MAX);
+
+  CHECK_EXACT (mult_accum_sat_short (sa_c, sa_d), SACCUM_MAX);
+  CHECK_EXACT (mult_accum_sat_short (sa_c, sa_e), SACCUM_MIN);
+  CHECK_EXACT (mult_accum_sat_regular (a_c, a_d), ACCUM_MAX);
+  CHECK_EXACT (mult_accum_sat_regular (a_c, a_e), ACCUM_MIN);
+  CHECK_EXACT (mult_accum_sat_long (la_c, la_d), LACCUM_MAX);
+  CHECK_EXACT (mult_accum_sat_long (la_c, la_e), LACCUM_MIN);
+  
+  CHECK_EXACT (div_accum_sat_short (sa_d, sa_g), SACCUM_MAX);
+  CHECK_EXACT (div_accum_sat_short (sa_e, sa_g), SACCUM_MIN);
+  CHECK_EXACT (div_accum_sat_regular (a_c, a_g), ACCUM_MAX);
+  CHECK_EXACT (div_accum_sat_regular (a_e, a_g), ACCUM_MIN);
+  CHECK_EXACT (div_accum_sat_long (la_d, la_g), LACCUM_MAX);
+  CHECK_EXACT (div_accum_sat_long (la_e, la_g), LACCUM_MIN);
+
+  /* Unsigned accum/accum operations, saturating.  */
+
+  CHECK_EXACT (plus_accum_uns_sat_short (usa_c, usa_d), USACCUM_MAX);
+  CHECK_EXACT (plus_accum_uns_sat_regular (ua_c, ua_d), UACCUM_MAX);
+  CHECK_EXACT (plus_accum_uns_sat_long (ula_c, ula_d), ULACCUM_MAX);
+  
+  CHECK_EXACT (minus_accum_uns_sat_short (usa_d, usa_c), 0uhk);
+  CHECK_EXACT (minus_accum_uns_sat_regular (ua_d, ua_c), 0uk);
+  CHECK_EXACT (minus_accum_uns_sat_long (ula_d, ula_c), 0ulk);
+
+  CHECK_EXACT (mult_accum_uns_sat_short (usa_c, usa_d), USACCUM_MAX);
+  CHECK_EXACT (mult_accum_uns_sat_regular (ua_c, ua_d), UACCUM_MAX);
+  CHECK_EXACT (mult_accum_uns_sat_long (ula_c, ula_d), ULACCUM_MAX);
+
+  CHECK_EXACT (div_accum_uns_sat_short (usa_c, usa_e), USACCUM_MAX);
+  CHECK_EXACT (div_accum_uns_sat_regular (ua_c, ua_e), UACCUM_MAX);
+  CHECK_EXACT (div_accum_uns_sat_long (ula_c, ula_e), ULACCUM_MAX);
+
+  return 0;
+}
diff --git a/libgcc/config.host b/libgcc/config.host
index e3c48bb..5f24004 100644
--- a/libgcc/config.host
+++ b/libgcc/config.host
@@ -207,12 +207,15 @@  arm*-*-freebsd*)
 arm*-*-netbsdelf*)
 	;;
 arm*-*-linux*)			# ARM GNU/Linux with ELF
+	tmake_file="${tmake_file} arm/t-fixed-point"
 	;;
 arm*-*-uclinux*)		# ARM ucLinux
+	tmake_file="${tmake_file} arm/t-fixed-point"
 	;;
 arm*-*-ecos-elf)
 	;;
 arm*-*-eabi* | arm*-*-symbianelf* )
+	tmake_file="${tmake_file} arm/t-fixed-point"
 	;;
 arm*-*-rtems*)
 	;;
diff --git a/libgcc/config/arm/t-fixed-point b/libgcc/config/arm/t-fixed-point
new file mode 100644
index 0000000..66346dd
--- /dev/null
+++ b/libgcc/config/arm/t-fixed-point
@@ -0,0 +1 @@ 
+fixed_bit_machine_header := arm/fixed-bit-machine.h