diff mbox series

[06/18] rs6000: Builtin expansion, part 1

Message ID b15a4fe5211c33bd0cce99a1342591ab2b4af1c6.1630511335.git.wschmidt@linux.ibm.com
State New
Headers show
Series Replace the Power target-specific builtin machinery | expand

Commit Message

Bill Schmidt Sept. 1, 2021, 4:13 p.m. UTC
This patch and the subsequent five patches form the meat of the improvements
for this patch series.  We develop a replacement for rs6000_expand_builtin
and its supporting functions, which are inefficient and difficult to
maintain.  This patch implements rs6000_expand_new_builtin, and creates
stubs for the support functions that subsequent patches will fill out.

Differences between the old and new support in this patch include:
 - Make use of the new builtin data structures, directly looking up
   a function's information rather than searching for the function
   multiple times;
 - Test for enablement of builtins at expand time, to support #pragma
   target changes within a compilation unit;
 - Use the builtin function attributes (e.g., bif_is_cpu) to control
   special handling;
 - Refactor common code into one place; and
 - Provide common error handling in one place for operands that are
   restricted to specific values or ranges.

Note that these six patches must be pushed together, because otherwise
unused parameter warnings in the stub functions will prevent bootstrap.
If preferred, I can flag them unused to remove this restriction.

2021-08-31  Bill Schmidt  <wschmidt@linux.ibm.com>

gcc/
	* config/rs6000/rs6000-call.c (rs6000_expand_new_builtin): New
	forward decl.
	(rs6000_invalid_new_builtin): New stub function.
	(rs6000_expand_builtin): Call rs6000_expand_new_builtin.
	(rs6000_expand_ldst_mask): New stub function.
	(new_cpu_expand_builtin): Likewise.
	(elemrev_icode): Likewise.
	(ldv_expand_builtin): Likewise.
	(lxvrse_expand_builtin): Likewise.
	(lxvrze_expand_builtin): Likewise.
	(stv_expand_builtin): Likewise.
	(new_mma_expand_builtin): Likewise.
	(new_htm_expand_builtin): Likewise.
	(rs6000_expand_new_builtin): New function.
---
 gcc/config/rs6000/rs6000-call.c | 432 ++++++++++++++++++++++++++++++++
 1 file changed, 432 insertions(+)

Comments

Segher Boessenkool Oct. 31, 2021, 3:24 a.m. UTC | #1
Hi!

On Wed, Sep 01, 2021 at 11:13:42AM -0500, Bill Schmidt wrote:
> Differences between the old and new support in this patch include:
>  - Make use of the new builtin data structures, directly looking up
>    a function's information rather than searching for the function
>    multiple times;

Is that measurable, do you think?

>  - Test for enablement of builtins at expand time, to support #pragma
>    target changes within a compilation unit;

But not within a function, right?

> Note that these six patches must be pushed together, because otherwise
> unused parameter warnings in the stub functions will prevent bootstrap.

Must be *committed* as one commit, even.  Committing them as six
separate commits and pushing them all at once will do nothing for people
who try to bisect, etc.

Merging patches is easy with Git though :-)

> +/* Expand ALTIVEC_BUILTIN_MASK_FOR_LOAD.  */
> +rtx
> +rs6000_expand_ldst_mask (rtx target, tree arg0)
> + {
> +  return target;
> + }

Interesting leading spaces, heh.  Please fix.

> +/* Expand the HTM builtin in EXP and store the result in TARGET.  */
> +static rtx
> +new_htm_expand_builtin (bifdata *bifaddr, rs6000_gen_builtins fcode,
> +			tree exp, rtx target)
> +{
> +  return const0_rtx;
> +}

The function commment should say what the return value is.

> +/* Expand an expression EXP that calls a built-in function,
> +   with result going to TARGET if that's convenient
> +   (and in mode MODE if that's convenient).
> +   SUBTARGET may be used as the target for computing one of EXP's operands.
> +   IGNORE is nonzero if the value is to be ignored.
> +   Use the new builtin infrastructure.  */
> +static rtx
> +rs6000_expand_new_builtin (tree exp, rtx target,
> +			   rtx subtarget ATTRIBUTE_UNUSED,
> +			   machine_mode ignore_mode ATTRIBUTE_UNUSED,
> +			   int ignore ATTRIBUTE_UNUSED)

Don't use ATTRIBUTE_UNUSED?  We have C++ now, you can leave out the
parameter name, with the same effect (other than it does not make you go
blind ;-) ).  In the case where you still want to show the name, you can
do something like
rs6000_expand_new_builtin (tree exp, trx target, rtx /*subtarget*/,
			    machine_mode, int /*ignore*/)

(There is no argument MODE btw, the comment needs some tweaking).

> +  /* We have two different modes (KFmode, TFmode) that are the IEEE
> +     128-bit floating point type, depending on whether long double is the
> +     IBM extended double (KFmode) or long double is IEEE 128-bit (TFmode).

KFmode *always* is IEEE QP.  TFmode is the one that can be different.

> +     It is simpler if we only define one variant of the built-in function,
> +     and switch the code when defining it, rather than defining two built-
> +     ins and using the overload table in rs6000-c.c to switch between the
> +     two.  If we don't have the proper assembler, don't do this switch
> +     because CODE_FOR_*kf* and CODE_FOR_*tf* will be CODE_FOR_nothing.  */
> +  if (FLOAT128_IEEE_P (TFmode))
> +    switch (icode)
> +      {
> +      default:
> +	break;

default: goes at the *end*.  And you can usually leave it out.

> +      case CODE_FOR_sqrtkf2_odd:
> +	icode = CODE_FOR_sqrttf2_odd;
> +	break;

So please do this the other way?  In libgcc "tf" means double-double,
it is historical.  So let's do the clearer thing please: translate tf to
kf in this handling (when tf *does* mean kf ;-) )

> +  /* In case of "#pragma target" changes, we initialize all builtins
> +     but check for actual availability now, during expand time.  For
> +     invalid builtins, generate a normal call.  */
> +  bifdata *bifaddr = &rs6000_builtin_info_x[uns_fcode];
> +  bif_enable e = bifaddr->enable;
> +
> +  if (e != ENB_ALWAYS
> +      && (e != ENB_P5       || !TARGET_POPCNTB)

  if (!(e == ENB_ALWAYS
	|| (e == ENB_P5 && TARGET_POPCNTB)

etc.

Computers are better at De Morgan than humans are.  It is much more
important to write clear code.  This often means using fewer negations.

> +  const int MAX_BUILTIN_ARGS = 6;
> +  tree arg[MAX_BUILTIN_ARGS];
> +  rtx op[MAX_BUILTIN_ARGS];
> +  machine_mode mode[MAX_BUILTIN_ARGS + 1];

Arrays are always better with a short comment.
Why is the "mode" array one entry longer, btw?

> +  rtx pat;
> +  bool void_func = TREE_TYPE (TREE_TYPE (fndecl)) == void_type_node;
> +  int k;

These things should be declared where they are first used.  The
initialisation of void_func should not be hidden amidst boring
declarations.

"k" needs a comment.

> +      if (!insn_data[icode].operand[i+k].mode)
> +	mode[i+k] = TARGET_64BIT ? Pmode : SImode;

That is
  mode[i+k] = Pmode;
always.

Does this depend on VOIDmode being equal to 0?  That is guaranteed, but
if you write out VOIDmode elsewhere, do it here as well?

> +      else
> +	mode[i+k] = insn_data[icode].operand[i+k].mode;
> +    }

So this is
  mode[i+k] = insn_data[icode].operand[i+k].mode;
  if (!mode[i+k])
    mode[i+k] = Pmode;

> +      switch (bifaddr->restr[i])
> +	{
> +	default:

default: goes at the end.

> +	case RES_BITS:
> +	  {
> +	    size_t mask = (1 << bifaddr->restr_val1[i]) - 1;

1 is an int, it can overflow much before a size_t would.
  size_t mask = 1;
  mask <<= bifaddr->restr_val1[i];
  mask--;

> +	    tree restr_arg = arg[bifaddr->restr_opnd[i] - 1];
> +	    STRIP_NOPS (restr_arg);
> +	    if (TREE_CODE (restr_arg) != INTEGER_CST
> +		|| TREE_INT_CST_LOW (restr_arg) & ~mask)

Manual De Morgan again?  More later too, ugh.  Well at least these
are trivial either way.

> +	case RES_VAR_RANGE:
> +	  {
> +	    tree restr_arg = arg[bifaddr->restr_opnd[i] - 1];
> +	    STRIP_NOPS (restr_arg);
> +	    if (TREE_CODE (restr_arg) == INTEGER_CST
> +		&& !IN_RANGE (tree_to_shwi (restr_arg),
> +			      bifaddr->restr_val1[i],
> +			      bifaddr->restr_val2[i]))
> +	      {
> +		error ("argument %d must be a variable or a literal "
> +		       "between %d and %d, inclusive",
> +		       bifaddr->restr_opnd[i], bifaddr->restr_val1[i],
> +		       bifaddr->restr_val2[i]);
> +		return CONST0_RTX (mode[0]);
> +	      }
> +	    break;
> +	  }

This error check is incongruent with the rest, and with its error
message?  If it is not an INTEGER_CST, it does not check anything about
it.  That sounds like trouble later.

> +  if (fcode == RS6000_BIF_PACK_IF
> +      && TARGET_LONG_DOUBLE_128 && !TARGET_IEEEQUAD)

Curious line breaks.  Break before each &&, or don't break before the
first one?

> +    {
> +      icode = CODE_FOR_packtf;
> +      fcode = RS6000_BIF_PACK_TF;

The fcode was for IF, can you use TF now?

> +      uns_fcode = (size_t)fcode;
> +    }

(space after cast)

> +  else if (fcode == RS6000_BIF_UNPACK_IF
> +	   && TARGET_LONG_DOUBLE_128 && !TARGET_IEEEQUAD)
> +    {
> +      icode = CODE_FOR_unpacktf;
> +      fcode = RS6000_BIF_UNPACK_TF;
> +      uns_fcode = (size_t)fcode;
> +    }

(same issues)

> +  switch (nargs)
> +    {
> +    default:

Just Say No.  Don't write  0 == err  and don't put default: first.  It
does not improve your code.  It makes it worse, instead.  Yoda can
understand Yoda-speak, it comes natural to him.  Yoda is not amongst the
readers of your code though, so please write in the common idiom :-)


This is okay for trunk with these things fixed.  Thanks!


Segher
diff mbox series

Patch

diff --git a/gcc/config/rs6000/rs6000-call.c b/gcc/config/rs6000/rs6000-call.c
index 558f06cfd6c..583efc9e98e 100644
--- a/gcc/config/rs6000/rs6000-call.c
+++ b/gcc/config/rs6000/rs6000-call.c
@@ -190,6 +190,7 @@  static tree builtin_function_type (machine_mode, machine_mode,
 static void rs6000_common_init_builtins (void);
 static void htm_init_builtins (void);
 static void mma_init_builtins (void);
+static rtx rs6000_expand_new_builtin (tree, rtx, rtx, machine_mode, int);
 static bool rs6000_gimple_fold_new_builtin (gimple_stmt_iterator *gsi);
 
 
@@ -11664,6 +11665,14 @@  rs6000_invalid_builtin (enum rs6000_builtins fncode)
     error ("%qs is not supported with the current options", name);
 }
 
+/* Raise an error message for a builtin function that is called without the
+   appropriate target options being set.  */
+
+static void
+rs6000_invalid_new_builtin (enum rs6000_gen_builtins fncode)
+{
+}
+
 /* Target hook for early folding of built-ins, shamelessly stolen
    from ia64.c.  */
 
@@ -14234,6 +14243,9 @@  rs6000_expand_builtin (tree exp, rtx target, rtx subtarget ATTRIBUTE_UNUSED,
 		       machine_mode mode ATTRIBUTE_UNUSED,
 		       int ignore ATTRIBUTE_UNUSED)
 {
+  if (new_builtins_are_live)
+    return rs6000_expand_new_builtin (exp, target, subtarget, mode, ignore);
+
   tree fndecl = TREE_OPERAND (CALL_EXPR_FN (exp), 0);
   enum rs6000_builtins fcode
     = (enum rs6000_builtins) DECL_MD_FUNCTION_CODE (fndecl);
@@ -14526,6 +14538,426 @@  rs6000_expand_builtin (tree exp, rtx target, rtx subtarget ATTRIBUTE_UNUSED,
   gcc_unreachable ();
 }
 
+/* Expand ALTIVEC_BUILTIN_MASK_FOR_LOAD.  */
+rtx
+rs6000_expand_ldst_mask (rtx target, tree arg0)
+ {
+  return target;
+ }
+
+/* Expand the CPU builtin in FCODE and store the result in TARGET.  */
+static rtx
+new_cpu_expand_builtin (enum rs6000_gen_builtins fcode,
+			tree exp ATTRIBUTE_UNUSED, rtx target)
+{
+  return target;
+}
+
+static insn_code
+elemrev_icode (rs6000_gen_builtins fcode)
+{
+  return (insn_code) 0;
+}
+
+static rtx
+ldv_expand_builtin (rtx target, insn_code icode, rtx *op, machine_mode tmode)
+{
+  return target;
+}
+
+static rtx
+lxvrse_expand_builtin (rtx target, insn_code icode, rtx *op,
+		       machine_mode tmode, machine_mode smode)
+{
+  return target;
+}
+
+static rtx
+lxvrze_expand_builtin (rtx target, insn_code icode, rtx *op,
+		       machine_mode tmode, machine_mode smode)
+{
+  return target;
+}
+
+static rtx
+stv_expand_builtin (insn_code icode, rtx *op,
+		    machine_mode tmode, machine_mode smode)
+{
+  return NULL_RTX;
+}
+
+/* Expand the MMA built-in in EXP.  */
+static rtx
+new_mma_expand_builtin (tree exp, rtx target, insn_code icode,
+			rs6000_gen_builtins fcode)
+{
+  return target;
+}
+
+/* Expand the HTM builtin in EXP and store the result in TARGET.  */
+static rtx
+new_htm_expand_builtin (bifdata *bifaddr, rs6000_gen_builtins fcode,
+			tree exp, rtx target)
+{
+  return const0_rtx;
+}
+
+/* Expand an expression EXP that calls a built-in function,
+   with result going to TARGET if that's convenient
+   (and in mode MODE if that's convenient).
+   SUBTARGET may be used as the target for computing one of EXP's operands.
+   IGNORE is nonzero if the value is to be ignored.
+   Use the new builtin infrastructure.  */
+static rtx
+rs6000_expand_new_builtin (tree exp, rtx target,
+			   rtx subtarget ATTRIBUTE_UNUSED,
+			   machine_mode ignore_mode ATTRIBUTE_UNUSED,
+			   int ignore ATTRIBUTE_UNUSED)
+{
+  tree fndecl = TREE_OPERAND (CALL_EXPR_FN (exp), 0);
+  enum rs6000_gen_builtins fcode
+    = (enum rs6000_gen_builtins) DECL_MD_FUNCTION_CODE (fndecl);
+  size_t uns_fcode = (size_t)fcode;
+  enum insn_code icode = rs6000_builtin_info_x[uns_fcode].icode;
+
+  /* We have two different modes (KFmode, TFmode) that are the IEEE
+     128-bit floating point type, depending on whether long double is the
+     IBM extended double (KFmode) or long double is IEEE 128-bit (TFmode).
+     It is simpler if we only define one variant of the built-in function,
+     and switch the code when defining it, rather than defining two built-
+     ins and using the overload table in rs6000-c.c to switch between the
+     two.  If we don't have the proper assembler, don't do this switch
+     because CODE_FOR_*kf* and CODE_FOR_*tf* will be CODE_FOR_nothing.  */
+  if (FLOAT128_IEEE_P (TFmode))
+    switch (icode)
+      {
+      default:
+	break;
+      case CODE_FOR_sqrtkf2_odd:
+	icode = CODE_FOR_sqrttf2_odd;
+	break;
+      case CODE_FOR_trunckfdf2_odd:
+	icode = CODE_FOR_trunctfdf2_odd;
+	break;
+      case CODE_FOR_addkf3_odd:
+	icode = CODE_FOR_addtf3_odd;
+	break;
+      case CODE_FOR_subkf3_odd:
+	icode = CODE_FOR_subtf3_odd;
+	break;
+      case CODE_FOR_mulkf3_odd:
+	icode = CODE_FOR_multf3_odd;
+	break;
+      case CODE_FOR_divkf3_odd:
+	icode = CODE_FOR_divtf3_odd;
+	break;
+      case CODE_FOR_fmakf4_odd:
+	icode = CODE_FOR_fmatf4_odd;
+	break;
+      case CODE_FOR_xsxexpqp_kf:
+	icode = CODE_FOR_xsxexpqp_tf;
+	break;
+      case CODE_FOR_xsxsigqp_kf:
+	icode = CODE_FOR_xsxsigqp_tf;
+	break;
+      case CODE_FOR_xststdcnegqp_kf:
+	icode = CODE_FOR_xststdcnegqp_tf;
+	break;
+      case CODE_FOR_xsiexpqp_kf:
+	icode = CODE_FOR_xsiexpqp_tf;
+	break;
+      case CODE_FOR_xsiexpqpf_kf:
+	icode = CODE_FOR_xsiexpqpf_tf;
+	break;
+      case CODE_FOR_xststdcqp_kf:
+	icode = CODE_FOR_xststdcqp_tf;
+	break;
+      case CODE_FOR_xscmpexpqp_eq_kf:
+	icode = CODE_FOR_xscmpexpqp_eq_tf;
+	break;
+      case CODE_FOR_xscmpexpqp_lt_kf:
+	icode = CODE_FOR_xscmpexpqp_lt_tf;
+	break;
+      case CODE_FOR_xscmpexpqp_gt_kf:
+	icode = CODE_FOR_xscmpexpqp_gt_tf;
+	break;
+      case CODE_FOR_xscmpexpqp_unordered_kf:
+	icode = CODE_FOR_xscmpexpqp_unordered_tf;
+	break;
+      }
+
+  /* In case of "#pragma target" changes, we initialize all builtins
+     but check for actual availability now, during expand time.  For
+     invalid builtins, generate a normal call.  */
+  bifdata *bifaddr = &rs6000_builtin_info_x[uns_fcode];
+  bif_enable e = bifaddr->enable;
+
+  if (e != ENB_ALWAYS
+      && (e != ENB_P5       || !TARGET_POPCNTB)
+      && (e != ENB_P6       || !TARGET_CMPB)
+      && (e != ENB_ALTIVEC  || !TARGET_ALTIVEC)
+      && (e != ENB_CELL     || !TARGET_ALTIVEC || rs6000_cpu != PROCESSOR_CELL)
+      && (e != ENB_VSX      || !TARGET_VSX)
+      && (e != ENB_P7       || !TARGET_POPCNTD)
+      && (e != ENB_P7_64    || !TARGET_POPCNTD || !TARGET_POWERPC64)
+      && (e != ENB_P8       || !TARGET_DIRECT_MOVE)
+      && (e != ENB_P8V      || !TARGET_P8_VECTOR)
+      && (e != ENB_P9       || !TARGET_MODULO)
+      && (e != ENB_P9_64    || !TARGET_MODULO || !TARGET_POWERPC64)
+      && (e != ENB_P9V      || !TARGET_P9_VECTOR)
+      && (e != ENB_IEEE128_HW || !TARGET_FLOAT128_HW)
+      && (e != ENB_DFP      || !TARGET_DFP)
+      && (e != ENB_CRYPTO   || !TARGET_CRYPTO)
+      && (e != ENB_HTM      || !TARGET_HTM)
+      && (e != ENB_P10      || !TARGET_POWER10)
+      && (e != ENB_P10_64   || !TARGET_POWER10 || !TARGET_POWERPC64)
+      && (e != ENB_MMA      || !TARGET_MMA))
+    {
+      rs6000_invalid_new_builtin (fcode);
+      return expand_call (exp, target, ignore);
+    }
+
+  if (bif_is_nosoft (*bifaddr)
+      && rs6000_isa_flags & OPTION_MASK_SOFT_FLOAT)
+    {
+      error ("%<%s%> not supported with %<-msoft-float%>",
+	     bifaddr->bifname);
+      return const0_rtx;
+    }
+
+  if (bif_is_no32bit (*bifaddr) && TARGET_32BIT)
+    fatal_error (input_location,
+		 "%<%s%> is not supported in 32-bit mode",
+		 bifaddr->bifname);
+
+  if (bif_is_cpu (*bifaddr))
+    return new_cpu_expand_builtin (fcode, exp, target);
+
+  if (bif_is_init (*bifaddr))
+    return altivec_expand_vec_init_builtin (TREE_TYPE (exp), exp, target);
+
+  if (bif_is_set (*bifaddr))
+    return altivec_expand_vec_set_builtin (exp);
+
+  if (bif_is_extract (*bifaddr))
+    return altivec_expand_vec_ext_builtin (exp, target);
+
+  if (bif_is_predicate (*bifaddr))
+    return altivec_expand_predicate_builtin (icode, exp, target);
+
+  if (bif_is_htm (*bifaddr))
+    return new_htm_expand_builtin (bifaddr, fcode, exp, target);
+
+  rtx pat;
+  const int MAX_BUILTIN_ARGS = 6;
+  tree arg[MAX_BUILTIN_ARGS];
+  rtx op[MAX_BUILTIN_ARGS];
+  machine_mode mode[MAX_BUILTIN_ARGS + 1];
+  bool void_func = TREE_TYPE (TREE_TYPE (fndecl)) == void_type_node;
+  int k;
+
+  int nargs = bifaddr->nargs;
+  gcc_assert (nargs <= MAX_BUILTIN_ARGS);
+
+  if (void_func)
+    k = 0;
+  else
+    {
+      k = 1;
+      mode[0] = insn_data[icode].operand[0].mode;
+    }
+
+  for (int i = 0; i < nargs; i++)
+    {
+      arg[i] = CALL_EXPR_ARG (exp, i);
+      if (arg[i] == error_mark_node)
+	return const0_rtx;
+      STRIP_NOPS (arg[i]);
+      op[i] = expand_normal (arg[i]);
+      /* We have a couple of pesky patterns that don't specify the mode...  */
+      if (!insn_data[icode].operand[i+k].mode)
+	mode[i+k] = TARGET_64BIT ? Pmode : SImode;
+      else
+	mode[i+k] = insn_data[icode].operand[i+k].mode;
+    }
+
+  /* Check for restricted constant arguments.  */
+  for (int i = 0; i < 2; i++)
+    {
+      switch (bifaddr->restr[i])
+	{
+	default:
+	case RES_NONE:
+	  break;
+	case RES_BITS:
+	  {
+	    size_t mask = (1 << bifaddr->restr_val1[i]) - 1;
+	    tree restr_arg = arg[bifaddr->restr_opnd[i] - 1];
+	    STRIP_NOPS (restr_arg);
+	    if (TREE_CODE (restr_arg) != INTEGER_CST
+		|| TREE_INT_CST_LOW (restr_arg) & ~mask)
+	      {
+		error ("argument %d must be a %d-bit unsigned literal",
+		       bifaddr->restr_opnd[i], bifaddr->restr_val1[i]);
+		return CONST0_RTX (mode[0]);
+	      }
+	    break;
+	  }
+	case RES_RANGE:
+	  {
+	    tree restr_arg = arg[bifaddr->restr_opnd[i] - 1];
+	    STRIP_NOPS (restr_arg);
+	    if (TREE_CODE (restr_arg) != INTEGER_CST
+		|| !IN_RANGE (tree_to_shwi (restr_arg),
+			      bifaddr->restr_val1[i],
+			      bifaddr->restr_val2[i]))
+	      {
+		error ("argument %d must be a literal between %d and %d,"
+		       " inclusive",
+		       bifaddr->restr_opnd[i], bifaddr->restr_val1[i],
+		       bifaddr->restr_val2[i]);
+		return CONST0_RTX (mode[0]);
+	      }
+	    break;
+	  }
+	case RES_VAR_RANGE:
+	  {
+	    tree restr_arg = arg[bifaddr->restr_opnd[i] - 1];
+	    STRIP_NOPS (restr_arg);
+	    if (TREE_CODE (restr_arg) == INTEGER_CST
+		&& !IN_RANGE (tree_to_shwi (restr_arg),
+			      bifaddr->restr_val1[i],
+			      bifaddr->restr_val2[i]))
+	      {
+		error ("argument %d must be a variable or a literal "
+		       "between %d and %d, inclusive",
+		       bifaddr->restr_opnd[i], bifaddr->restr_val1[i],
+		       bifaddr->restr_val2[i]);
+		return CONST0_RTX (mode[0]);
+	      }
+	    break;
+	  }
+	case RES_VALUES:
+	  {
+	    tree restr_arg = arg[bifaddr->restr_opnd[i] - 1];
+	    STRIP_NOPS (restr_arg);
+	    if (TREE_CODE (restr_arg) != INTEGER_CST
+		|| (tree_to_shwi (restr_arg) != bifaddr->restr_val1[i]
+		    && tree_to_shwi (restr_arg) != bifaddr->restr_val2[i]))
+	      {
+		error ("argument %d must be either a literal %d or a "
+		       "literal %d",
+		       bifaddr->restr_opnd[i], bifaddr->restr_val1[i],
+		       bifaddr->restr_val2[i]);
+		return CONST0_RTX (mode[0]);
+	      }
+	    break;
+	  }
+	}
+    }
+
+  if (bif_is_ldstmask (*bifaddr))
+    return rs6000_expand_ldst_mask (target, arg[0]);
+
+  if (bif_is_stvec (*bifaddr))
+    {
+      if (bif_is_reve (*bifaddr))
+	icode = elemrev_icode (fcode);
+      return stv_expand_builtin (icode, op, mode[0], mode[1]);
+    }
+
+  if (bif_is_ldvec (*bifaddr))
+    {
+      if (bif_is_reve (*bifaddr))
+	icode = elemrev_icode (fcode);
+      return ldv_expand_builtin (target, icode, op, mode[0]);
+    }
+
+  if (bif_is_lxvrse (*bifaddr))
+    return lxvrse_expand_builtin (target, icode, op, mode[0], mode[1]);
+
+  if (bif_is_lxvrze (*bifaddr))
+    return lxvrze_expand_builtin (target, icode, op, mode[0], mode[1]);
+
+  if (bif_is_mma (*bifaddr))
+    return new_mma_expand_builtin (exp, target, icode, fcode);
+
+  if (fcode == RS6000_BIF_PACK_IF
+      && TARGET_LONG_DOUBLE_128 && !TARGET_IEEEQUAD)
+    {
+      icode = CODE_FOR_packtf;
+      fcode = RS6000_BIF_PACK_TF;
+      uns_fcode = (size_t)fcode;
+    }
+  else if (fcode == RS6000_BIF_UNPACK_IF
+	   && TARGET_LONG_DOUBLE_128 && !TARGET_IEEEQUAD)
+    {
+      icode = CODE_FOR_unpacktf;
+      fcode = RS6000_BIF_UNPACK_TF;
+      uns_fcode = (size_t)fcode;
+    }
+
+  if (TREE_TYPE (TREE_TYPE (fndecl)) == void_type_node)
+    target = NULL_RTX;
+  else if (target == 0
+	   || GET_MODE (target) != mode[0]
+	   || !insn_data[icode].operand[0].predicate (target, mode[0]))
+    target = gen_reg_rtx (mode[0]);
+
+  for (int i = 0; i < nargs; i++)
+    if (!insn_data[icode].operand[i+k].predicate (op[i], mode[i+k]))
+      op[i] = copy_to_mode_reg (mode[i+k], op[i]);
+
+  switch (nargs)
+    {
+    default:
+      gcc_assert (MAX_BUILTIN_ARGS == 6);
+      gcc_unreachable ();
+    case 0:
+      pat = (void_func
+	     ? GEN_FCN (icode) ()
+	     : GEN_FCN (icode) (target));
+      break;
+    case 1:
+      pat = (void_func
+	     ? GEN_FCN (icode) (op[0])
+	     : GEN_FCN (icode) (target, op[0]));
+      break;
+    case 2:
+      pat = (void_func
+	     ? GEN_FCN (icode) (op[0], op[1])
+	     : GEN_FCN (icode) (target, op[0], op[1]));
+      break;
+    case 3:
+      pat = (void_func
+	     ? GEN_FCN (icode) (op[0], op[1], op[2])
+	     : GEN_FCN (icode) (target, op[0], op[1], op[2]));
+      break;
+    case 4:
+      pat = (void_func
+	     ? GEN_FCN (icode) (op[0], op[1], op[2], op[3])
+	     : GEN_FCN (icode) (target, op[0], op[1], op[2], op[3]));
+      break;
+    case 5:
+      pat = (void_func
+	     ? GEN_FCN (icode) (op[0], op[1], op[2], op[3], op[4])
+	     : GEN_FCN (icode) (target, op[0], op[1], op[2], op[3], op[4]));
+      break;
+    case 6:
+      pat = (void_func
+	     ? GEN_FCN (icode) (op[0], op[1], op[2], op[3], op[4], op[5])
+	     : GEN_FCN (icode) (target, op[0], op[1],
+				op[2], op[3], op[4], op[5]));
+      break;
+    }
+
+  if (!pat)
+    return 0;
+  emit_insn (pat);
+
+  return target;
+}
+
 /* Create a builtin vector type with a name.  Taking care not to give
    the canonical type a name.  */