diff mbox series

Generalized predicate/condition for parameter reference in IPA (PR ipa/91088)

Message ID BYAPR01MB48698316A913AF5080D65E37F7F20@BYAPR01MB4869.prod.exchangelabs.com
State New
Headers show
Series Generalized predicate/condition for parameter reference in IPA (PR ipa/91088) | expand

Commit Message

Feng Xue OS July 12, 2019, 11:24 a.m. UTC
Current IPA-cp only generates cost-evaluating predicate for conditional statement like
"if (param cmp const_val)", it is too simple and conservative. This patch generalizes the
process to handle the form as T(param), a mathematical transformation on the function
parameter, in which the parameter occurs once, and other operands are constant value.

Feng

----

Comments

Martin Jambor Aug. 29, 2019, 4:08 p.m. UTC | #1
Hi,

On Fri, Jul 12 2019, Feng Xue OS wrote:
> Current IPA-cp only generates cost-evaluating predicate for conditional statement like
> "if (param cmp const_val)", it is too simple and conservative. This patch generalizes the
> process to handle the form as T(param), a mathematical transformation on the function
> parameter, in which the parameter occurs once, and other operands are constant value.

thanks for working on this.  I cannot approve this, but I have had a
brief look and have the following comments:
>
> ----
> diff --git a/gcc/ChangeLog b/gcc/ChangeLog
> index 3d92250b520..0110446e09e 100644
> --- a/gcc/ChangeLog
> +++ b/gcc/ChangeLog
> @@ -1,3 +1,25 @@
> +2019-07-12  Feng Xue  <fxue@os.amperecomputing.com>
> +
> +	PR ipa/91088
> +	* ipa-predicat.h (struct expr_eval_op): New struct.
> +	(expr_eval_ops): New typedef.
> +	(struct condition): Add param_ops member.
> +	(add_condition): Add param_ops parameter.
> +	* ipa-predicat.c (expr_eval_ops_equal_p): New function.
> +	(predicate::add_clause): Add param_ops comparison.
> +	(dump_condition): Add debug dump for param_ops.
> +	(remap_after_inlining): Add param_ops argument to call to
> +	add_condition.
> +	(add_condition): Add parameter param_ops.
> +	* ipa-fnsummary.c (evaluate_conditions_for_known_args): Fold
> +	parameter expressions using param_ops.
> +	(decompose_param_expr):  New function.
> +	(set_cond_stmt_execution_predicate): Use call to decompose_param_expr
> +	to replace call to unmodified_parm_or_parm_agg_item.
> +	(set_switch_stmt_execution_predicate): Likewise.
> +	(inline_read_section): Read param_ops from summary stream.
> +	(ipa_fn_summary_write): Write param_ops to summary stream.
> +

(It's a bad idea to make ChangeLog entries part of the patch, it won't
apply to anyone, not even to you nowadays. )


>  2019-07-11  Sunil K Pandey  <sunil.k.pandey@intel.com>
>  
>  	PR target/90980
> diff --git a/gcc/ipa-fnsummary.c b/gcc/ipa-fnsummary.c
> index 09986211a1d..faf8bd39090 100644
> --- a/gcc/ipa-fnsummary.c
> +++ b/gcc/ipa-fnsummary.c
> @@ -301,9 +301,9 @@ set_hint_predicate (predicate **p, predicate new_predicate)
>  }
>  
>  
> -/* Compute what conditions may or may not hold given invormation about
> +/* Compute what conditions may or may not hold given information about
>     parameters.  RET_CLAUSE returns truths that may hold in a specialized copy,
> -   whie RET_NONSPEC_CLAUSE returns truths that may hold in an nonspecialized
> +   while RET_NONSPEC_CLAUSE returns truths that may hold in an nonspecialized
>     copy when called in a given context.  It is a bitmask of conditions. Bit
>     0 means that condition is known to be false, while bit 1 means that condition
>     may or may not be true.  These differs - for example NOT_INLINED condition
> @@ -336,6 +336,8 @@ evaluate_conditions_for_known_args (struct cgraph_node *node,
>      {
>        tree val;
>        tree res;
> +      int j;
> +      struct expr_eval_op *op;
>  
>        /* We allow call stmt to have fewer arguments than the callee function
>           (especially for K&R style programs).  So bound check here (we assume
> @@ -399,7 +401,18 @@ evaluate_conditions_for_known_args (struct cgraph_node *node,
>  	  continue;
>  	}
>  
> -      val = fold_unary (VIEW_CONVERT_EXPR, TREE_TYPE (c->val), val);
> +      for (j = 0; vec_safe_iterate (c->param_ops, j, &op); j++)
> +	{
> +	  if (!op->val)
> +	    val = fold_unary (op->code, op->type, val);
> +	  else if (op->val_is_rhs)
> +	    val = fold_binary_to_constant (op->code, op->type, val, op->val);
> +	  else
> +	    val = fold_binary_to_constant (op->code, op->type, op->val, val);
> +	  if (!val)
> +	    break;
> +	}
> +
>        res = val
>  	? fold_binary_to_constant (c->code, boolean_type_node, val, c->val)
>  	: NULL;
> @@ -1177,6 +1190,105 @@ eliminated_by_inlining_prob (ipa_func_body_info *fbi, gimple *stmt)
>      }
>  }
>  
> +/* Flatten a tree expression on parameter into a set of sequential operations.
> +   we only handle expression that is a mathematical transformation on the
> +   parameter, and in the expression, parameter occurs only once, and other
> +   operands are IPA invariant.  */

I understand describing these things is difficult, but flatten is
strange way to describe what the function does.  What about somthing
like the following?

Analyze EXPR if it represents a series of simple operations performed on
a function parameter and return true if so.  FBI, STMT, INDEX_P, SIZE_P
and AGGPOS have the same meaning like in
unmodified_parm_or_parm_agg_item.  Operations on the parameter are
recorded to PARAM_OPS_P if it is not NULL.


> +
> +static bool
> +decompose_param_expr (struct ipa_func_body_info *fbi,
> +		      gimple *stmt, tree expr,
> +		      int *index_p, HOST_WIDE_INT *size_p,
> +		      struct agg_position_info *aggpos,
> +		      expr_eval_ops *param_ops_p)
> +{
> +  if (param_ops_p)
> +    *param_ops_p = NULL;
> +
> +  while (true)
> +    {
> +      gimple_rhs_class rhs_class;
> +      expr_eval_op eval_op;
> +
> +      if (unmodified_parm_or_parm_agg_item (fbi, stmt, expr, index_p,
> +					    size_p, aggpos))
> +	{
> +	  if (param_ops_p)
> +	    {
> +	      /* Find use of parameter, add a convert operation to describe
> +		 result type, which may not be same as parameter type.  */
> +	      eval_op.val_is_rhs = false;
> +	      eval_op.val = NULL_TREE;
> +	      eval_op.code = VIEW_CONVERT_EXPR;
> +	      eval_op.type = TREE_TYPE (expr);
> +
> +	      vec_safe_insert (*param_ops_p, 0, eval_op);

If we get here in the first iteration of the loop, could we not insert
anything into the vector and handle such cases in
evaluate_conditions_for_known_args like we do today (well, with
fold_convert might be better)?  It could save quite some memory and it
is important to try keep the memory footprint down in IPA summaries.

Also, I think you want a parameter to limit the maximum length of
param_ops_p, at some point someone will come with some crazy
machine-generated code that will create huge vectors.

Thanks,

Martin
Feng Xue OS Aug. 30, 2019, 8:29 a.m. UTC | #2
> (It's a bad idea to make ChangeLog entries part of the patch, it won't
> apply to anyone, not even to you nowadays. )
Got it. Will not include this kind of info in later patches.

> I understand describing these things is difficult, but flatten is
> strange way to describe what the function does.  What about somthing
> like the following?
> 
> Analyze EXPR if it represents a series of simple operations performed on
> a function parameter and return true if so.  FBI, STMT, INDEX_P, SIZE_P
> and AGGPOS have the same meaning like in
> unmodified_parm_or_parm_agg_item.  Operations on the parameter are
> recorded to PARAM_OPS_P if it is not NULL.
Operations should be recorded in some place, and this is why PARAM_OPS_P
is used. Not quite understand this point.

>> +           /* Find use of parameter, add a convert operation to describe
>> +              result type, which may not be same as parameter type.  */
>> +           eval_op.val_is_rhs = false;
>> +           eval_op.val = NULL_TREE;
>> +           eval_op.code = VIEW_CONVERT_EXPR;
>> +           eval_op.type = TREE_TYPE (expr);
>> +
>> +           vec_safe_insert (*param_ops_p, 0, eval_op);

> If we get here in the first iteration of the loop, could we not insert
> anything into the vector and handle such cases in
> evaluate_conditions_for_known_args like we do today (well, with
> fold_convert might be better)?  It could save quite some memory and it
> is important to try keep the memory footprint down in IPA summaries.
Here is a little trick to make code of folding in evaluate_conditions_for_known_args ()
be simple. It does consume some memory for most cases. Will consider other way
and remove this.

> Also, I think you want a parameter to limit the maximum length of
> param_ops_p, at some point someone will come with some crazy
> machine-generated code that will create huge vectors.
Yes. Exactly.

Thanks,

Martin
Martin Jambor Aug. 30, 2019, 8:42 a.m. UTC | #3
Hi,

On Fri, Aug 30 2019, Feng Xue OS wrote:
>> (It's a bad idea to make ChangeLog entries part of the patch, it won't
>> apply to anyone, not even to you nowadays. )
> Got it. Will not include this kind of info in later patches.
>
>> I understand describing these things is difficult, but flatten is
>> strange way to describe what the function does.  What about somthing
>> like the following?
>> 
>> Analyze EXPR if it represents a series of simple operations performed on
>> a function parameter and return true if so.  FBI, STMT, INDEX_P, SIZE_P
>> and AGGPOS have the same meaning like in
>> unmodified_parm_or_parm_agg_item.  Operations on the parameter are
>> recorded to PARAM_OPS_P if it is not NULL.
> Operations should be recorded in some place, and this is why PARAM_OPS_P
> is used. Not quite understand this point.

I was merely suggesting a better comment describing the function you are
introducing.

>
>>> +           /* Find use of parameter, add a convert operation to describe
>>> +              result type, which may not be same as parameter type.  */
>>> +           eval_op.val_is_rhs = false;
>>> +           eval_op.val = NULL_TREE;
>>> +           eval_op.code = VIEW_CONVERT_EXPR;
>>> +           eval_op.type = TREE_TYPE (expr);
>>> +
>>> +           vec_safe_insert (*param_ops_p, 0, eval_op);
>
>> If we get here in the first iteration of the loop, could we not insert
>> anything into the vector and handle such cases in
>> evaluate_conditions_for_known_args like we do today (well, with
>> fold_convert might be better)?  It could save quite some memory and it
>> is important to try keep the memory footprint down in IPA summaries.
> Here is a little trick to make code of folding in evaluate_conditions_for_known_args ()
> be simple. It does consume some memory for most cases. Will consider other way
> and remove this.

Thinking about it a bit more, I think you simply do not want to ever
push the extra VIEW_CONVERT_EXPR to the vector and in
evaluate_conditions_for_known_args always do a fold_convert to the
desired type (similarly like we do today).

Thanks,

Martin

>
>> Also, I think you want a parameter to limit the maximum length of
>> param_ops_p, at some point someone will come with some crazy
>> machine-generated code that will create huge vectors.
> Yes. Exactly.
>
> Thanks,
>
> Martin
Feng Xue OS Aug. 30, 2019, 8:53 a.m. UTC | #4
> I was merely suggesting a better comment describing the function you are
> introducing.
Oh. I know.  Good wording. 

> Thinking about it a bit more, I think you simply do not want to ever
> push the extra VIEW_CONVERT_EXPR to the vector and in
> evaluate_conditions_for_known_args always do a fold_convert to the
> desired type (similarly like we do today).
Yes. It is, but at cost of creating an extra memory slot.

Feng
diff mbox series

Patch

diff --git a/gcc/ChangeLog b/gcc/ChangeLog
index 3d92250b520..0110446e09e 100644
--- a/gcc/ChangeLog
+++ b/gcc/ChangeLog
@@ -1,3 +1,25 @@ 
+2019-07-12  Feng Xue  <fxue@os.amperecomputing.com>
+
+	PR ipa/91088
+	* ipa-predicat.h (struct expr_eval_op): New struct.
+	(expr_eval_ops): New typedef.
+	(struct condition): Add param_ops member.
+	(add_condition): Add param_ops parameter.
+	* ipa-predicat.c (expr_eval_ops_equal_p): New function.
+	(predicate::add_clause): Add param_ops comparison.
+	(dump_condition): Add debug dump for param_ops.
+	(remap_after_inlining): Add param_ops argument to call to
+	add_condition.
+	(add_condition): Add parameter param_ops.
+	* ipa-fnsummary.c (evaluate_conditions_for_known_args): Fold
+	parameter expressions using param_ops.
+	(decompose_param_expr):  New function.
+	(set_cond_stmt_execution_predicate): Use call to decompose_param_expr
+	to replace call to unmodified_parm_or_parm_agg_item.
+	(set_switch_stmt_execution_predicate): Likewise.
+	(inline_read_section): Read param_ops from summary stream.
+	(ipa_fn_summary_write): Write param_ops to summary stream.
+
 2019-07-11  Sunil K Pandey  <sunil.k.pandey@intel.com>
 
 	PR target/90980
diff --git a/gcc/ipa-fnsummary.c b/gcc/ipa-fnsummary.c
index 09986211a1d..faf8bd39090 100644
--- a/gcc/ipa-fnsummary.c
+++ b/gcc/ipa-fnsummary.c
@@ -301,9 +301,9 @@  set_hint_predicate (predicate **p, predicate new_predicate)
 }
 
 
-/* Compute what conditions may or may not hold given invormation about
+/* Compute what conditions may or may not hold given information about
    parameters.  RET_CLAUSE returns truths that may hold in a specialized copy,
-   whie RET_NONSPEC_CLAUSE returns truths that may hold in an nonspecialized
+   while RET_NONSPEC_CLAUSE returns truths that may hold in an nonspecialized
    copy when called in a given context.  It is a bitmask of conditions. Bit
    0 means that condition is known to be false, while bit 1 means that condition
    may or may not be true.  These differs - for example NOT_INLINED condition
@@ -336,6 +336,8 @@  evaluate_conditions_for_known_args (struct cgraph_node *node,
     {
       tree val;
       tree res;
+      int j;
+      struct expr_eval_op *op;
 
       /* We allow call stmt to have fewer arguments than the callee function
          (especially for K&R style programs).  So bound check here (we assume
@@ -399,7 +401,18 @@  evaluate_conditions_for_known_args (struct cgraph_node *node,
 	  continue;
 	}
 
-      val = fold_unary (VIEW_CONVERT_EXPR, TREE_TYPE (c->val), val);
+      for (j = 0; vec_safe_iterate (c->param_ops, j, &op); j++)
+	{
+	  if (!op->val)
+	    val = fold_unary (op->code, op->type, val);
+	  else if (op->val_is_rhs)
+	    val = fold_binary_to_constant (op->code, op->type, val, op->val);
+	  else
+	    val = fold_binary_to_constant (op->code, op->type, op->val, val);
+	  if (!val)
+	    break;
+	}
+
       res = val
 	? fold_binary_to_constant (c->code, boolean_type_node, val, c->val)
 	: NULL;
@@ -1177,6 +1190,105 @@  eliminated_by_inlining_prob (ipa_func_body_info *fbi, gimple *stmt)
     }
 }
 
+/* Flatten a tree expression on parameter into a set of sequential operations.
+   we only handle expression that is a mathematical transformation on the
+   parameter, and in the expression, parameter occurs only once, and other
+   operands are IPA invariant.  */
+
+static bool
+decompose_param_expr (struct ipa_func_body_info *fbi,
+		      gimple *stmt, tree expr,
+		      int *index_p, HOST_WIDE_INT *size_p,
+		      struct agg_position_info *aggpos,
+		      expr_eval_ops *param_ops_p)
+{
+  if (param_ops_p)
+    *param_ops_p = NULL;
+
+  while (true)
+    {
+      gimple_rhs_class rhs_class;
+      expr_eval_op eval_op;
+
+      if (unmodified_parm_or_parm_agg_item (fbi, stmt, expr, index_p,
+					    size_p, aggpos))
+	{
+	  if (param_ops_p)
+	    {
+	      /* Find use of parameter, add a convert operation to describe
+		 result type, which may not be same as parameter type.  */
+	      eval_op.val_is_rhs = false;
+	      eval_op.val = NULL_TREE;
+	      eval_op.code = VIEW_CONVERT_EXPR;
+	      eval_op.type = TREE_TYPE (expr);
+
+	      vec_safe_insert (*param_ops_p, 0, eval_op);
+	    }
+	  return true;
+	}
+
+      if (TREE_CODE (expr) != SSA_NAME || SSA_NAME_IS_DEFAULT_DEF (expr))
+	break;
+
+      if (!is_gimple_assign (stmt = SSA_NAME_DEF_STMT (expr)))
+	break;
+
+      rhs_class = gimple_assign_rhs_class (stmt);
+
+      if (rhs_class == GIMPLE_SINGLE_RHS)
+	expr = gimple_assign_rhs1 (stmt);
+      else if (rhs_class == GIMPLE_UNARY_RHS)
+	{
+	  expr = gimple_assign_rhs1 (stmt);
+
+	  if (param_ops_p)
+	    {
+	      eval_op.val_is_rhs = false;
+	      eval_op.val = NULL_TREE;
+	      eval_op.code = gimple_assign_rhs_code (stmt);
+	      eval_op.type = TREE_TYPE (expr);
+
+	      vec_safe_insert (*param_ops_p, 0, eval_op);
+	    }
+	}
+      else if (rhs_class == GIMPLE_BINARY_RHS)
+	{
+	  tree op1 = gimple_assign_rhs1 (stmt);
+	  tree op2 = gimple_assign_rhs2 (stmt);
+
+	  if (is_gimple_ip_invariant (op1))
+	    {
+	      eval_op.val_is_rhs = false;
+	      eval_op.val = op1;
+	      expr = op2;
+	    }
+	  else if (is_gimple_ip_invariant (op2))
+	    {
+	      eval_op.val_is_rhs = true;
+	      eval_op.val = op2;
+	      expr = op1;
+	    }
+	  else
+	    break;
+
+	  if (param_ops_p)
+	    {
+	      eval_op.code = gimple_assign_rhs_code (stmt);
+	      eval_op.type = TREE_TYPE (expr);
+
+	      vec_safe_insert (*param_ops_p, 0, eval_op);
+	    }
+	}
+      else
+	break;
+    }
+
+  /* Failed to decompose, free resource and return.  */
+  if (param_ops_p)
+    vec_free (*param_ops_p);
+
+  return false;
+}
 
 /* If BB ends by a conditional we can turn into predicates, attach corresponding
    predicates to the CFG edges.   */
@@ -1196,6 +1308,7 @@  set_cond_stmt_execution_predicate (struct ipa_func_body_info *fbi,
   edge_iterator ei;
   gimple *set_stmt;
   tree op2;
+  expr_eval_ops param_ops;
 
   last = last_stmt (bb);
   if (!last || gimple_code (last) != GIMPLE_COND)
@@ -1203,10 +1316,8 @@  set_cond_stmt_execution_predicate (struct ipa_func_body_info *fbi,
   if (!is_gimple_ip_invariant (gimple_cond_rhs (last)))
     return;
   op = gimple_cond_lhs (last);
-  /* TODO: handle conditionals like
-     var = op0 < 4;
-     if (var != 0).  */
-  if (unmodified_parm_or_parm_agg_item (fbi, last, op, &index, &size, &aggpos))
+
+  if (decompose_param_expr (fbi, last, op, &index, &size, &aggpos, &param_ops))
     {
       code = gimple_cond_code (last);
       inverted_code = invert_tree_comparison (code, HONOR_NANS (op));
@@ -1222,13 +1333,13 @@  set_cond_stmt_execution_predicate (struct ipa_func_body_info *fbi,
 	    {
 	      predicate p
 		= add_condition (summary, index, size, &aggpos, this_code,
-				 unshare_expr_without_location
-				 (gimple_cond_rhs (last)));
+				 gimple_cond_rhs (last), param_ops);
 	      e->aux = edge_predicate_pool.allocate ();
 	      *(predicate *) e->aux = p;
 	    }
 	}
     }
+  vec_free (param_ops);
 
   if (TREE_CODE (op) != SSA_NAME)
     return;
@@ -1240,7 +1351,7 @@  set_cond_stmt_execution_predicate (struct ipa_func_body_info *fbi,
      Here we can predicate nonconstant_code.  We can't
      really handle constant_code since we have no predicate
      for this and also the constant code is not known to be
-     optimized away when inliner doen't see operand is constant.
+     optimized away when inliner doesn't see operand is constant.
      Other optimizers might think otherwise.  */
   if (gimple_cond_code (last) != NE_EXPR
       || !integer_zerop (gimple_cond_rhs (last)))
@@ -1250,8 +1361,7 @@  set_cond_stmt_execution_predicate (struct ipa_func_body_info *fbi,
       || gimple_call_num_args (set_stmt) != 1)
     return;
   op2 = gimple_call_arg (set_stmt, 0);
-  if (!unmodified_parm_or_parm_agg_item (fbi, set_stmt, op2, &index, &size,
-					 &aggpos))
+  if (!decompose_param_expr (fbi, set_stmt, op2, &index, &size, &aggpos, NULL))
     return;
   FOR_EACH_EDGE (e, ei, bb->succs) if (e->flags & EDGE_FALSE_VALUE)
     {
@@ -1280,13 +1390,15 @@  set_switch_stmt_execution_predicate (struct ipa_func_body_info *fbi,
   edge_iterator ei;
   size_t n;
   size_t case_idx;
+  expr_eval_ops param_ops;
 
   lastg = last_stmt (bb);
   if (!lastg || gimple_code (lastg) != GIMPLE_SWITCH)
     return;
   gswitch *last = as_a <gswitch *> (lastg);
   op = gimple_switch_index (last);
-  if (!unmodified_parm_or_parm_agg_item (fbi, last, op, &index, &size, &aggpos))
+  if (!decompose_param_expr (fbi, last, op, &index, &size, &aggpos,
+			     &param_ops))
     return;
 
   FOR_EACH_EDGE (e, ei, bb->succs)
@@ -1312,19 +1424,20 @@  set_switch_stmt_execution_predicate (struct ipa_func_body_info *fbi,
 	p = true;
       else if (!max)
 	p = add_condition (summary, index, size, &aggpos, EQ_EXPR,
-			   unshare_expr_without_location (min));
+			   min, param_ops);
       else
 	{
 	  predicate p1, p2;
 	  p1 = add_condition (summary, index, size, &aggpos, GE_EXPR,
-			      unshare_expr_without_location (min));
+			      min, param_ops);
 	  p2 = add_condition (summary, index, size, &aggpos, LE_EXPR,
-			      unshare_expr_without_location (max));
+			      max, param_ops);
 	  p = p1 & p2;
 	}
       *(class predicate *) e->aux
 	= p.or_with (summary->conds, *(class predicate *) e->aux);
     }
+  vec_free (param_ops);
 }
 
 
@@ -1491,7 +1604,7 @@  will_be_nonconstant_predicate (struct ipa_func_body_info *fbi,
   HOST_WIDE_INT size;
   struct agg_position_info aggpos;
 
-  /* What statments might be optimized away
+  /* What statements might be optimized away
      when their arguments are constant.  */
   if (gimple_code (stmt) != GIMPLE_ASSIGN
       && gimple_code (stmt) != GIMPLE_COND
@@ -1539,7 +1652,7 @@  will_be_nonconstant_predicate (struct ipa_func_body_info *fbi,
   if (is_load)
     op_non_const =
       add_condition (summary, base_index, size, &aggpos, predicate::changed,
-		     NULL);
+		     NULL_TREE);
   else
     op_non_const = false;
   FOR_EACH_SSA_TREE_OPERAND (use, stmt, iter, SSA_OP_USE)
@@ -3333,6 +3446,7 @@  inline_read_section (struct lto_file_decl_data *file_data, const char *data,
       for (j = 0; j < count2; j++)
 	{
 	  struct condition c;
+	  unsigned int k, count3;
 	  c.operand_num = streamer_read_uhwi (&ib);
 	  c.size = streamer_read_uhwi (&ib);
 	  c.code = (enum tree_code) streamer_read_uhwi (&ib);
@@ -3342,6 +3456,18 @@  inline_read_section (struct lto_file_decl_data *file_data, const char *data,
 	  c.by_ref = bp_unpack_value (&bp, 1);
 	  if (c.agg_contents)
 	    c.offset = streamer_read_uhwi (&ib);
+	  c.param_ops = NULL;
+	  count3 = streamer_read_uhwi (&ib);
+	  for (k = 0; k < count3; k++)
+	    {
+	      struct expr_eval_op op;
+	      op.code = (enum tree_code) streamer_read_uhwi (&ib);
+	      bp = streamer_read_bitpack (&ib);
+	      op.val_is_rhs = bp_unpack_value (&bp, 1);
+	      op.val = stream_read_tree (&ib, data_in);
+	      op.type = stream_read_tree (&ib, data_in);
+	      vec_safe_push (c.param_ops, op);
+	    }
 	  if (info)
 	    vec_safe_push (info->conds, c);
 	}
@@ -3490,6 +3616,9 @@  ipa_fn_summary_write (void)
 	  streamer_write_uhwi (ob, vec_safe_length (info->conds));
 	  for (i = 0; vec_safe_iterate (info->conds, i, &c); i++)
 	    {
+	      int j;
+	      struct expr_eval_op *op;
+
 	      streamer_write_uhwi (ob, c->operand_num);
 	      streamer_write_uhwi (ob, c->size);
 	      streamer_write_uhwi (ob, c->code);
@@ -3500,6 +3629,16 @@  ipa_fn_summary_write (void)
 	      streamer_write_bitpack (&bp);
 	      if (c->agg_contents)
 		streamer_write_uhwi (ob, c->offset);
+	      streamer_write_uhwi (ob, vec_safe_length (c->param_ops));
+	      for (j = 0; vec_safe_iterate (c->param_ops, j, &op); j++)
+		{
+		  streamer_write_uhwi (ob, op->code);
+		  bp = bitpack_create (ob->main_stream);
+		  bp_pack_value (&bp, op->val_is_rhs, 1);
+		  streamer_write_bitpack (&bp);
+		  stream_write_tree (ob, op->val, true);
+		  stream_write_tree (ob, op->type, true);
+		}
 	    }
 	  streamer_write_uhwi (ob, vec_safe_length (info->size_time_table));
 	  for (i = 0; vec_safe_iterate (info->size_time_table, i, &e); i++)
diff --git a/gcc/ipa-predicate.c b/gcc/ipa-predicate.c
index 49622e9cd33..537dd81685f 100644
--- a/gcc/ipa-predicate.c
+++ b/gcc/ipa-predicate.c
@@ -33,9 +33,35 @@  along with GCC; see the file COPYING3.  If not see
 #include "fold-const.h"
 #include "tree-pretty-print.h"
 #include "gimple.h"
+#include "gimplify.h"
 #include "data-streamer.h"
 
 
+/* Check whether two set of operations have same effects.  */
+static bool
+expr_eval_ops_equal_p (expr_eval_ops ops1, expr_eval_ops ops2)
+{
+  if (ops1)
+    {
+      if (!ops2 || ops1->length () != ops2->length ())
+	return false;
+
+      for (unsigned i = 0; i < ops1->length (); i++)
+	{
+	  expr_eval_op &op1 = (*ops1)[i];
+	  expr_eval_op &op2 = (*ops2)[i];
+
+	  if (op1.code != op2.code
+	      || op1.val_is_rhs != op2.val_is_rhs
+	      || !vrp_operand_equal_p (op1.val, op2.val)
+	      || op1.type != op2.type)
+	    return false;
+	}
+      return true;
+    }
+  return !ops2;
+}
+
 /* Add clause CLAUSE into the predicate P.
    When CONDITIONS is NULL do not perform checking whether NEW_CLAUSE
    is obviously true.  This is useful only when NEW_CLAUSE is known to be
@@ -110,14 +136,13 @@  predicate::add_clause (conditions conditions, clause_t new_clause)
 	for (c2 = c1 + 1; c2 < num_conditions; c2++)
 	  if (new_clause & (1 << c2))
 	    {
-	      condition *cc1 =
-		&(*conditions)[c1 - predicate::first_dynamic_condition];
 	      condition *cc2 =
 		&(*conditions)[c2 - predicate::first_dynamic_condition];
 	      if (cc1->operand_num == cc2->operand_num
-		  && cc1->val == cc2->val
+		  && vrp_operand_equal_p (cc1->val, cc2->val)
 		  && cc2->code != is_not_constant
-		  && cc2->code != predicate::changed
+		  && cc2->code != changed
+		  && expr_eval_ops_equal_p (cc1->param_ops, cc2->param_ops)
 		  && cc1->code == invert_tree_comparison (cc2->code,
 							  HONOR_NANS (cc1->val)))
 		return;
@@ -300,6 +325,46 @@  dump_condition (FILE *f, conditions conditions, int cond)
       if (c->agg_contents)
 	fprintf (f, "[%soffset: " HOST_WIDE_INT_PRINT_DEC "]",
 		 c->by_ref ? "ref " : "", c->offset);
+
+      for (unsigned i = 1; i < vec_safe_length (c->param_ops); i++)
+	{
+	  expr_eval_op &op = (*(c->param_ops))[i];
+	  const char *op_name = op_symbol_code (op.code);
+
+	  if (op_name == op_symbol_code (ERROR_MARK))
+	    op_name = get_tree_code_name (op.code);
+
+	  fprintf (f, ":(");
+
+	  if (!op.val)
+	    {
+	      switch (op.code)
+		{
+		  case VIEW_CONVERT_EXPR:
+		  CASE_CONVERT:
+		    fprintf (f, "(");
+		    print_generic_expr (f, op.type);
+		    fprintf (f, ")" );
+		    break;
+
+		  default:
+		    fprintf (f, "%s ", op_name);
+		}
+	      fprintf (f, "@%u", i);
+	    }
+	  else if (op.val_is_rhs)
+	    {
+	      fprintf (f, "@%u %s ", i, op_name);
+	      print_generic_expr (f, op.val);
+	    }
+	  else
+	    {
+	      print_generic_expr (f, op.val);
+	      fprintf (f, " %s @%u", op_name, i);
+	    }
+	  fprintf (f, ")");
+	}
+
       if (c->code == predicate::is_not_constant)
 	{
 	  fprintf (f, " not constant");
@@ -338,8 +403,8 @@  dump_clause (FILE *f, conditions conds, clause_t clause)
 }
 
 
-/* Dump THIS to F. CONDS a vector of conditions used when evauating
-   predicats. When NL is true new line is output at the end of dump.  */
+/* Dump THIS to F. CONDS a vector of conditions used when evaluating
+   predicates. When NL is true new line is output at the end of dump.  */
 
 void
 predicate::dump (FILE *f, conditions conds, bool nl) const
@@ -389,7 +454,7 @@  predicate::remap_after_duplication (clause_t possible_truths)
 
    INFO is ipa_fn_summary of function we are adding predicate into, CALLEE_INFO
    is summary of function predicate P is from. OPERAND_MAP is array giving
-   callee formal IDs the caller formal IDs. POSSSIBLE_TRUTHS is clausule of all
+   callee formal IDs the caller formal IDs. POSSSIBLE_TRUTHS is clause of all
    callee conditions that may be true in caller context.  TOPLEV_PREDICATE is
    predicate under which callee is executed.  OFFSET_MAP is an array of of
    offsets that need to be added to conditions, negative offset means that
@@ -463,7 +528,7 @@  predicate::remap_after_inlining (class ipa_fn_summary *info,
 		    cond_predicate = add_condition (info,
 						    operand_map[c->operand_num],
 						    c->size, &ap, c->code,
-						    c->val);
+						    c->val, c->param_ops);
 		  }
 	      }
 	    /* Fixed conditions remains same, construct single
@@ -516,21 +581,23 @@  predicate::stream_out (struct output_block *ob)
 }
 
 
-/* Add condition to condition list SUMMARY. OPERAND_NUM, SIZE, CODE and VAL
-   correspond to fields of condition structure.  AGGPOS describes whether the
-   used operand is loaded from an aggregate and where in the aggregate it is.
-   It can be NULL, which means this not a load from an aggregate.  */
+/* Add condition to condition list SUMMARY. OPERAND_NUM, SIZE, CODE, VAL and
+   PARAM_OPS correspond to fields of condition structure.  AGGPOS describes
+   whether the used operand is loaded from an aggregate and where in the
+   aggregate it is.  It can be NULL, which means this not a load from an
+   aggregate.  */
 
 predicate
 add_condition (class ipa_fn_summary *summary, int operand_num,
 	       HOST_WIDE_INT size, struct agg_position_info *aggpos,
-	       enum tree_code code, tree val)
+	       enum tree_code code, tree val, expr_eval_ops param_ops)
 {
-  int i;
+  int i, j;
   struct condition *c;
   struct condition new_cond;
   HOST_WIDE_INT offset;
   bool agg_contents, by_ref;
+  expr_eval_op *op;
 
   if (aggpos)
     {
@@ -551,8 +618,9 @@  add_condition (class ipa_fn_summary *summary, int operand_num,
       if (c->operand_num == operand_num
 	  && c->size == size
 	  && c->code == code
-	  && c->val == val
+	  && vrp_operand_equal_p (c->val, val)
 	  && c->agg_contents == agg_contents
+	  && expr_eval_ops_equal_p (c->param_ops, param_ops)
 	  && (!agg_contents || (c->offset == offset && c->by_ref == by_ref)))
 	return predicate::predicate_testing_cond (i);
     }
@@ -562,11 +630,17 @@  add_condition (class ipa_fn_summary *summary, int operand_num,
 
   new_cond.operand_num = operand_num;
   new_cond.code = code;
-  new_cond.val = val;
+  new_cond.val = val ? unshare_expr_without_location (val) : val;
   new_cond.agg_contents = agg_contents;
   new_cond.by_ref = by_ref;
   new_cond.offset = offset;
   new_cond.size = size;
+  new_cond.param_ops = vec_safe_copy (param_ops);
+
+  for (j = 0; vec_safe_iterate (new_cond.param_ops, j, &op); j++)
+    if (op->val)
+      op->val = unshare_expr_without_location (op->val);
+
   vec_safe_push (summary->conds, new_cond);
 
   return predicate::predicate_testing_cond (i);
diff --git a/gcc/ipa-predicate.h b/gcc/ipa-predicate.h
index c2adba30551..f00a64f3a59 100644
--- a/gcc/ipa-predicate.h
+++ b/gcc/ipa-predicate.h
@@ -22,8 +22,28 @@  along with GCC; see the file COPYING3.  If not see
    inlined into (i.e. known constant values of function parameters.
 
    Conditions that are interesting for function body are collected into CONDS
-   vector.  They are of simple for  function_param OP VAL, where VAL is
-   IPA invariant.  The conditions are then referred by predicates.  */
+   vector.  They are of simple as kind of a mathematical transformation on
+   function parameter, T(function_param), in which the parameter occurs only
+   once, and other operands are IPA invariant.  The conditions are then
+   referred by predicates.  */
+
+
+/* A simplified representation of tree node, only for unary and binary
+   operation.  A tree expression is flattened to a vector of this kind
+   of structure.  */
+struct GTY(()) expr_eval_op
+{
+  /* Operation code of expression.  */
+  ENUM_BITFIELD(tree_code) code : 16;
+  /* For binary expression, the below constant operand is rhs or lhs.  */
+  unsigned val_is_rhs : 1;
+  /* For binary expression, this is a constant value.  */
+  tree val;
+  /* Result type of expression.  */
+  tree type;
+};
+
+typedef vec<expr_eval_op, va_gc> *expr_eval_ops;
 
 struct GTY(()) condition
 {
@@ -41,6 +61,9 @@  struct GTY(()) condition
   /* If agg_contents is set, this differentiates between loads from data
      passed by reference and by value.  */
   unsigned by_ref : 1;
+  /* A set sequential operations on the parameter, which can be seen as
+     a mathmatical function on the parameter.  */
+  expr_eval_ops param_ops;
 };
 
 /* Information kept about parameter of call site.  */
@@ -58,7 +81,7 @@  struct inline_param_summary
 
 typedef vec<condition, va_gc> *conditions;
 
-/* Predicates are used to repesent function parameters (such as runtime)
+/* Predicates are used to represent function parameters (such as runtime)
    which depend on a context function is called in.
 
    Predicates are logical formulas in conjunctive-disjunctive form consisting
@@ -229,4 +252,5 @@  private:
 void dump_condition (FILE *f, conditions conditions, int cond);
 predicate add_condition (class ipa_fn_summary *summary, int operand_num,
 			 HOST_WIDE_INT size, struct agg_position_info *aggpos,
-			 enum tree_code code, tree val);
+			 enum tree_code code, tree val,
+			 expr_eval_ops param_ops = NULL);
diff --git a/gcc/testsuite/ChangeLog b/gcc/testsuite/ChangeLog
index 6c40496db9c..39886498305 100644
--- a/gcc/testsuite/ChangeLog
+++ b/gcc/testsuite/ChangeLog
@@ -1,3 +1,8 @@ 
+2019-07-12  Feng Xue  <fxue@os.amperecomputing.com>
+
+	PR ipa/91088
+	* gcc.dg/ipa/pr91088.c: New test.
+
 2019-07-11  Sunil K Pandey  <sunil.k.pandey@intel.com>
 
 	PR target/90980
diff --git a/gcc/testsuite/gcc.dg/ipa/pr91088.c b/gcc/testsuite/gcc.dg/ipa/pr91088.c
new file mode 100644
index 00000000000..1cbf0d2fa23
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/ipa/pr91088.c
@@ -0,0 +1,29 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O3 -fdump-ipa-cp-details -fdump-ipa-fnsummary-details -fno-inline" } */
+
+int foo();
+
+int callee(int v)
+{
+  if ((27 % ((1 - v) * 3)) < 6)
+    {
+      foo();
+      foo();
+      foo();
+      foo();
+      foo();
+      foo();
+      foo();
+      foo();
+      foo();
+    }
+  else
+    return 1;
+}
+
+int caller()
+{
+  return callee (-5);
+}
+
+/* { dg-final { scan-ipa-dump "Creating a specialized node of callee" "cp" } } */