2014-04-02  Christian Bruel  <christian.bruel@st.com>

	* basic-block.h (pre_edge_lcm_avs): Declare.
	* doc/tm.texi (EMIT_MODE_TOGGLE): Document.
	* doc/tm.texi.in (EMIT_MODE_TOGGLE): Idem.
	* config/sh/sh.h (EMIT_MODE_TOGGLE): Define.
	* config/sh/sh-protos.h	(emit_fpu_toggle): Declare
	* config/sh/sh.c (emit_fpu_toggle): New function.
	* config/sh/sh.md (toggle_pr): Defined for TARGET_SH4_300 and
	TARGET_SH4A_FP.
	(in_delay_slot): fpscr_toggle don't go in delay slot.
	* lcm.c (pre_edge_lcm_avs): Renamed from pre_edge_lcm.
	Call clear_aux_for_edges. Fix comments.
	(pre_edge_lcm): New wrapper function to call pre_edge_lcm_avs.
	(pre_edge_rev_lcm): Idem.
	* mode-switching.c (init_modes_infos): New function.
	(free_modes_infos): Idem.
	(init_modes_infos): Idem
	(add_mode_set): Idem.
	(get_mode): Idem.
	(commit_mode_sets): Idem.
	(merge_modes): Idem.
	(set_flip_status): Idem
	(test_flip_status): Idem.
	(optimize_mode_switching): Add support to toggle modes.

2014-04-02  Christian Bruel  <christian.bruel@st.com>

	* gcc.target/sh/fpchg1.c: New test.

Index: gcc/basic-block.h
===================================================================
--- gcc/basic-block.h	(revision 208956)
+++ gcc/basic-block.h	(working copy)
@@ -711,6 +711,9 @@ extern void bitmap_union_of_preds (sbitmap, sbitma
 extern struct edge_list *pre_edge_lcm (int, sbitmap *, sbitmap *,
 				       sbitmap *, sbitmap *, sbitmap **,
 				       sbitmap **);
+extern struct edge_list *pre_edge_lcm_avs (int, sbitmap *, sbitmap *,
+					   sbitmap *, sbitmap *, sbitmap *,
+					   sbitmap *, sbitmap **, sbitmap **);
 extern struct edge_list *pre_edge_rev_lcm (int, sbitmap *,
 					   sbitmap *, sbitmap *,
 					   sbitmap *, sbitmap **,
Index: gcc/config/sh/sh-protos.h
===================================================================
--- gcc/config/sh/sh-protos.h	(revision 208956)
+++ gcc/config/sh/sh-protos.h	(working copy)
@@ -210,6 +210,7 @@ extern bool check_use_sfunc_addr (rtx, rtx);
 #ifdef HARD_CONST
 extern void fpscr_set_from_mem (int, HARD_REG_SET);
 #endif
+extern bool emit_fpu_toggle (int);
 
 extern void sh_pr_interrupt (struct cpp_reader *);
 extern void sh_pr_trapa (struct cpp_reader *);
Index: gcc/config/sh/sh.c
===================================================================
--- gcc/config/sh/sh.c	(revision 208956)
+++ gcc/config/sh/sh.c	(working copy)
@@ -10042,6 +10042,17 @@ get_free_reg (HARD_REG_SET regs_live)
   return gen_rtx_REG (Pmode, 7);
 }
 
+/* This function switches the fpscr.  */
+bool
+emit_fpu_toggle (int e ATTRIBUTE_UNUSED)
+{
+  emit_insn (gen_toggle_pr ());
+  if (TARGET_FMOVD)
+    emit_insn (gen_toggle_sz ());
+
+  return true;
+}
+
 /* This function will set the fpscr from memory.
    MODE is the mode we are setting it to.  */
 void
Index: gcc/config/sh/sh.h
===================================================================
--- gcc/config/sh/sh.h	(revision 208956)
+++ gcc/config/sh/sh.h	(working copy)
@@ -2259,6 +2259,9 @@ extern int current_function_interrupt;
 #define MODE_PRIORITY_TO_MODE(ENTITY, N) \
   ((TARGET_FPU_SINGLE != 0) ^ (N) ? FP_MODE_SINGLE : FP_MODE_DOUBLE)
 
+#define EMIT_MODE_TOGGLE(ENTITY, MODE) \
+  ((TARGET_SH4A_FP || TARGET_SH4_300) ? emit_fpu_toggle (ENTITY) : false)
+
 #define EMIT_MODE_SET(ENTITY, MODE, HARD_REGS_LIVE) \
   fpscr_set_from_mem ((MODE), (HARD_REGS_LIVE))
 
Index: gcc/config/sh/sh.md
===================================================================
--- gcc/config/sh/sh.md	(revision 208956)
+++ gcc/config/sh/sh.md	(working copy)
@@ -504,6 +504,7 @@
 (define_attr "in_delay_slot" "yes,no"
   (cond [(eq_attr "type" "cbranch") (const_string "no")
 	 (eq_attr "type" "pcload,pcload_si") (const_string "no")
+	 (eq_attr "type" "fpscr_toggle") (const_string "no")
 	 (eq_attr "needs_delay_slot" "yes") (const_string "no")
 	 (eq_attr "length" "2") (const_string "yes")
 	 ] (const_string "no")))
@@ -12182,15 +12183,10 @@ label:
   "fschg"
   [(set_attr "type" "fpscr_toggle") (set_attr "fp_set" "unknown")])
 
-;; There's no way we can use it today, since optimize mode switching
-;; doesn't enable us to know from which mode we're switching to the
-;; mode it requests, to tell whether we can use a relative mode switch
-;; (like toggle_pr) or an absolute switch (like loading fpscr from
-;; memory).
 (define_insn "toggle_pr"
   [(set (reg:PSI FPSCR_REG)
 	(xor:PSI (reg:PSI FPSCR_REG) (const_int 524288)))]
-  "TARGET_SH4A_FP && ! TARGET_FPU_SINGLE"
+   "(TARGET_SH4A_FP || TARGET_SH4_300)"
   "fpchg"
   [(set_attr "type" "fpscr_toggle")])
 
Index: gcc/doc/tm.texi
===================================================================
--- gcc/doc/tm.texi	(revision 208956)
+++ gcc/doc/tm.texi	(working copy)
@@ -9723,6 +9723,11 @@ If you define this macro, you also have to define
 are optional.
 @end defmac
 
+@defmac EMIT_MODE_TOGGLE (@var{entity}, @var{mode})
+Generates one or more insns to toggle @var{entity}. Return true if entity supports
+toggle. EMIT_MODE_SET is used instead.
+@end defmac
+
 @defmac NUM_MODES_FOR_MODE_SWITCHING
 If you define @code{OPTIMIZE_MODE_SWITCHING}, you have to define this as
 initializer for an array of integers.  Each initializer element
Index: gcc/doc/tm.texi.in
===================================================================
--- gcc/doc/tm.texi.in	(revision 208956)
+++ gcc/doc/tm.texi.in	(working copy)
@@ -7392,6 +7392,11 @@ If you define this macro, you also have to define
 are optional.
 @end defmac
 
+@defmac EMIT_MODE_TOGGLE (@var{entity}, @var{mode})
+Generates one or more insns to toggle @var{entity}. Return true if entity supports
+toggle. EMIT_MODE_SET is used instead.
+@end defmac
+
 @defmac NUM_MODES_FOR_MODE_SWITCHING
 If you define @code{OPTIMIZE_MODE_SWITCHING}, you have to define this as
 initializer for an array of integers.  Each initializer element
Index: gcc/lcm.c
===================================================================
--- gcc/lcm.c	(revision 208956)
+++ gcc/lcm.c	(working copy)
@@ -377,17 +377,17 @@ compute_insert_delete (struct edge_list *edge_list
     }
 }
 
-/* Given local properties TRANSP, ANTLOC, AVOUT, KILL return the insert and
-   delete vectors for edge based LCM.  Returns an edgelist which is used to
+/* Given local properties TRANSP, ANTLOC, AVLOC, KILL return the insert and
+   delete vectors for edge based LCM  and return the AVIN, AVOUT bitmap.
    map the insert vector to what edge an expression should be inserted on.  */
 
 struct edge_list *
-pre_edge_lcm (int n_exprs, sbitmap *transp,
+pre_edge_lcm_avs (int n_exprs, sbitmap *transp,
 	      sbitmap *avloc, sbitmap *antloc, sbitmap *kill,
+	      sbitmap *avin, sbitmap *avout,
 	      sbitmap **insert, sbitmap **del)
 {
   sbitmap *antin, *antout, *earliest;
-  sbitmap *avin, *avout;
   sbitmap *later, *laterin;
   struct edge_list *edge_list;
   int num_edges;
@@ -413,10 +413,7 @@ struct edge_list *
 #endif
 
   /* Compute global availability.  */
-  avin = sbitmap_vector_alloc (last_basic_block_for_fn (cfun), n_exprs);
-  avout = sbitmap_vector_alloc (last_basic_block_for_fn (cfun), n_exprs);
   compute_available (avloc, kill, avout, avin);
-  sbitmap_vector_free (avin);
 
   /* Compute global anticipatability.  */
   antin = sbitmap_vector_alloc (last_basic_block_for_fn (cfun), n_exprs);
@@ -444,7 +441,6 @@ struct edge_list *
 
   sbitmap_vector_free (antout);
   sbitmap_vector_free (antin);
-  sbitmap_vector_free (avout);
 
   later = sbitmap_vector_alloc (num_edges, n_exprs);
 
@@ -485,6 +481,28 @@ struct edge_list *
   return edge_list;
 }
 
+/* Wrapper to allocate avin/avout and call pre_edge_lcm_avs.  */
+
+struct edge_list *
+pre_edge_lcm (int n_exprs, sbitmap *transp,
+	      sbitmap *avloc, sbitmap *antloc, sbitmap *kill,
+	      sbitmap **insert, sbitmap **del)
+{
+  struct edge_list *edge_list;
+  sbitmap *avin, *avout;
+
+  avin = sbitmap_vector_alloc (last_basic_block_for_fn (cfun), n_exprs);
+  avout = sbitmap_vector_alloc (last_basic_block_for_fn (cfun), n_exprs);
+
+  edge_list = pre_edge_lcm_avs (n_exprs, transp, avloc, antloc, kill,
+				 avin, avout, insert, del);
+
+  sbitmap_vector_free (avout);
+  sbitmap_vector_free (avin);
+
+  return edge_list;
+}
+
 /* Compute the AVIN and AVOUT vectors from the AVLOC and KILL vectors.
    Return the number of passes we performed to iterate to a solution.  */
 
Index: gcc/mode-switching.c
===================================================================
--- gcc/mode-switching.c	(revision 208956)
+++ gcc/mode-switching.c	(working copy)
@@ -95,6 +95,178 @@ static void reg_becomes_live (rtx, const_rtx, void
 static void make_preds_opaque (basic_block, int);
 
 
+/* To support mode switching, the algorithm cannot set the modes after
+   the insert and delete bitmaps are computed by pre_edge_lcm, because
+   'avin' is computed iteratively for each possible modes for each entity.
+   The mode emission will be done after all mode are processed.
+   (see commit_mode_sets).  */
+
+static int **modes_needed;  /* modes needs to be inserted on this edge.  */
+
+static const int num_modes[] = NUM_MODES_FOR_MODE_SWITCHING;
+
+#ifdef EMIT_MODE_TOGGLE
+
+/* Bitmap to compute mode flipping.  */
+
+static sbitmap *mode_in_flip;  /* flip in mode status for each basic blocks.  */
+static sbitmap *mode_out_flip; /* flip out mode status for each basic blocks.  */
+
+/* Test avin modes.
+   if 'out' is 1 we want to know if the mode out of the basic block
+   can be flipped. If 'in' is 1 we want to know if the mode entering the
+   basic block can be flipped.
+   If result is 0, we need to reset the mode.  */
+
+static bool
+test_flip_status(int entity, basic_block bb, bool out)
+{
+  if (out)
+    return bitmap_bit_p (mode_out_flip[bb->index], entity);
+  else
+    return bitmap_bit_p (mode_in_flip[bb->index], entity);
+}
+
+/* Merges the avin modes.  */
+
+static void
+set_flip_status (sbitmap *avin, sbitmap *avout)
+{
+  basic_block bb;
+
+  FOR_EACH_BB_FN (bb, cfun)
+    {
+      int i = bb->index;
+
+      /* Merge modes for each entity for each bb.
+	 If multiple avin modes are set for the same bb, they are not
+	 exclusive and a flip may not be emitted.  */
+      if (! bb_has_eh_pred (bb))
+	bitmap_xor (mode_in_flip[i], mode_in_flip[i], avin[i]);
+      bitmap_xor (mode_out_flip[i], mode_out_flip[i], avout[i]);
+    }
+}
+#endif /* EMIT_MODE_TOGGLE */
+
+/* Allocates and initializes modes_infos.  */
+
+static void
+init_modes_infos (int n_entities, int *entity_map)
+{
+  int num_edges = 0;
+  basic_block bb;
+
+  FOR_BB_BETWEEN (bb, ENTRY_BLOCK_PTR_FOR_FN (cfun),
+		  EXIT_BLOCK_PTR_FOR_FN (cfun), next_bb)
+    num_edges += EDGE_COUNT (bb->succs);
+
+  modes_needed = XNEWVEC (int *, n_entities);
+
+  for (int j = 0; j < n_entities; j++)
+    {
+      int e = entity_map[j];
+      int no_mode = num_modes[e];
+
+      modes_needed[j] = XNEWVEC (int, num_edges);
+      for (int ed = 0; ed < num_edges; ed++)
+	modes_needed[j][ed] = no_mode;
+    }
+
+  /* Allocates bitmaps for modes.  */
+#ifdef EMIT_MODE_TOGGLE
+  mode_in_flip = sbitmap_vector_alloc (last_basic_block_for_fn (cfun),
+				       n_entities);
+  mode_out_flip = sbitmap_vector_alloc (last_basic_block_for_fn (cfun),
+					n_entities);
+  bitmap_vector_clear (mode_in_flip, last_basic_block_for_fn (cfun));
+  bitmap_vector_clear (mode_out_flip, last_basic_block_for_fn (cfun));
+#endif
+}
+
+/* frees memory used to hold the modes information.  */
+
+static void
+free_modes_infos (int n_entities)
+{
+  int j;
+
+  for (j = 0; j < n_entities; j++)
+    free (modes_needed[j]);
+
+  free (modes_needed);
+#ifdef EMIT_MODE_TOGGLE
+  sbitmap_vector_free (mode_in_flip);
+  sbitmap_vector_free (mode_out_flip);
+#endif
+}
+
+/* records the mode associated with edge e for entity j.  */
+
+static void
+add_mode_set (int j, int e, int mode)
+{
+  modes_needed[j][e] = mode;
+}
+
+/* returns the mode needed on edge e for entity j. -1 if none.  */
+
+static int
+get_mode (int j, int e)
+{
+  return modes_needed[j][e];
+}
+
+/* Finally, after all the modes after been inserted after lcm, we can
+   process with the mode emission.  */
+
+static bool
+commit_mode_sets (struct edge_list *edge_list, int j, int *entity_map)
+{
+  bool need_commit = false;
+
+  for (int ed = NUM_EDGES (edge_list) - 1; ed >= 0; ed--)
+    {
+      int mode;
+      int e = entity_map[j];
+      int no_mode = num_modes[e];
+
+      if ((mode = get_mode (j, ed)) != no_mode)
+	{
+	  HARD_REG_SET live_at_edge;
+	  edge eg = INDEX_EDGE (edge_list, ed);
+	  basic_block src_bb = eg->src;
+	  rtx mode_set;
+
+	  REG_SET_TO_HARD_REG_SET (live_at_edge, df_get_live_out (src_bb));
+
+	  rtl_profile_for_edge (eg);
+	  start_sequence ();
+
+#ifdef EMIT_MODE_TOGGLE
+	  if (! test_flip_status (j, src_bb, true) ||
+	      ! EMIT_MODE_TOGGLE (entity_map[j], mode))
+#endif
+	  EMIT_MODE_SET (entity_map[j], mode, live_at_edge);
+
+	  mode_set = get_insns ();
+	  end_sequence ();
+	  default_rtl_profile ();
+
+	  /* Do not bother to insert empty sequence.  */
+	  if (mode_set == NULL_RTX)
+	    continue;
+
+	  /* We should not get an abnormal edge here.  */
+	  gcc_assert (! (eg->flags & EDGE_ABNORMAL));
+
+	  need_commit = true;
+	  insert_insn_on_edge (mode_set, eg);
+	}
+    }
+
+  return need_commit;
+}
+
 /* This function will allocate a new BBINFO structure, initialized
    with the MODE, INSN, and basic block BB parameters.  */
 
@@ -201,7 +373,7 @@ reg_becomes_live (rtx reg, const_rtx setter ATTRIB
    inserted before the exit block.  Otherwise return null.  */
 
 static basic_block
-create_pre_exit (int n_entities, int *entity_map, const int *num_modes)
+create_pre_exit (int n_entities, int *entity_map)
 {
   edge eg;
   edge_iterator ei;
@@ -455,10 +627,8 @@ optimize_mode_switching (void)
   rtx insn;
   int e;
   basic_block bb;
-  int need_commit = 0;
+  bool need_commit = false;
   sbitmap *kill;
-  struct edge_list *edge_list;
-  static const int num_modes[] = NUM_MODES_FOR_MODE_SWITCHING;
 #define N_ENTITIES ARRAY_SIZE (num_modes)
   int entity_map[N_ENTITIES];
   struct bb_info *bb_info[N_ENTITIES];
@@ -467,6 +637,8 @@ optimize_mode_switching (void)
   int max_num_modes = 0;
   bool emitted ATTRIBUTE_UNUSED = false;
   basic_block post_entry ATTRIBUTE_UNUSED, pre_exit ATTRIBUTE_UNUSED;
+  sbitmap *avin, *avout;
+  struct edge_list *edge_list = 0;
 
   for (e = N_ENTITIES - 1, n_entities = 0; e >= 0; e--)
     if (OPTIMIZE_MODE_SWITCHING (e))
@@ -494,7 +666,7 @@ optimize_mode_switching (void)
   /* Split the edge from the entry block, so that we can note that
      there NORMAL_MODE is supplied.  */
   post_entry = split_edge (single_succ_edge (ENTRY_BLOCK_PTR_FOR_FN (cfun)));
-  pre_exit = create_pre_exit (n_entities, entity_map, num_modes);
+  pre_exit = create_pre_exit (n_entities, entity_map);
 #endif
 
   df_analyze ();
@@ -504,6 +676,8 @@ optimize_mode_switching (void)
   antic = sbitmap_vector_alloc (last_basic_block_for_fn (cfun), n_entities);
   transp = sbitmap_vector_alloc (last_basic_block_for_fn (cfun), n_entities);
   comp = sbitmap_vector_alloc (last_basic_block_for_fn (cfun), n_entities);
+  avin = sbitmap_vector_alloc (last_basic_block_for_fn (cfun), n_entities);
+  avout = sbitmap_vector_alloc (last_basic_block_for_fn (cfun), n_entities);
 
   bitmap_vector_ones (transp, last_basic_block_for_fn (cfun));
 
@@ -610,6 +784,9 @@ optimize_mode_switching (void)
     }
 
   kill = sbitmap_vector_alloc (last_basic_block_for_fn (cfun), n_entities);
+
+  init_modes_infos (n_entities, entity_map);
+
   for (i = 0; i < max_num_modes; i++)
     {
       int current_mode[N_ENTITIES];
@@ -639,9 +816,14 @@ optimize_mode_switching (void)
 
       FOR_EACH_BB_FN (bb, cfun)
 	bitmap_not (kill[bb->index], transp[bb->index]);
-      edge_list = pre_edge_lcm (n_entities, transp, comp, antic,
-				kill, &insert, &del);
+      edge_list = pre_edge_lcm_avs (n_entities, transp, comp, antic,
+				    kill, avin, avout, &insert, &del);
 
+#ifdef EMIT_MODE_TOGGLE
+      /* Merge modes for all entities.  */
+      set_flip_status (avin, avout);
+#endif
+
       for (j = n_entities - 1; j >= 0; j--)
 	{
 	  /* Insert all mode sets that have been inserted by lcm.  */
@@ -657,10 +839,6 @@ optimize_mode_switching (void)
 	  for (e = NUM_EDGES (edge_list) - 1; e >= 0; e--)
 	    {
 	      edge eg = INDEX_EDGE (edge_list, e);
-	      int mode;
-	      basic_block src_bb;
-	      HARD_REG_SET live_at_edge;
-	      rtx mode_set;
 
 	      eg->aux = 0;
 
@@ -669,27 +847,8 @@ optimize_mode_switching (void)
 
 	      eg->aux = (void *)1;
 
-	      mode = current_mode[j];
-	      src_bb = eg->src;
-
-	      REG_SET_TO_HARD_REG_SET (live_at_edge, df_get_live_out (src_bb));
-
-	      rtl_profile_for_edge (eg);
-	      start_sequence ();
-	      EMIT_MODE_SET (entity_map[j], mode, live_at_edge);
-	      mode_set = get_insns ();
-	      end_sequence ();
-	      default_rtl_profile ();
-
-	      /* Do not bother to insert empty sequence.  */
-	      if (mode_set == NULL_RTX)
-		continue;
-
-	      /* We should not get an abnormal edge here.  */
-	      gcc_assert (! (eg->flags & EDGE_ABNORMAL));
-
-	      need_commit = 1;
-	      insert_insn_on_edge (mode_set, eg);
+	      /* Remember we need to emit it.  */
+	      add_mode_set(j, e, current_mode[j]);
 	    }
 
 	  FOR_EACH_BB_REVERSE_FN (bb, cfun)
@@ -704,7 +863,10 @@ optimize_mode_switching (void)
       sbitmap_vector_free (del);
       sbitmap_vector_free (insert);
       clear_aux_for_edges ();
-      free_edge_list (edge_list);
+
+      /* Keep an edge_list for later.  */
+      if (i != max_num_modes - 1)
+	free_edge_list (edge_list);
     }
 
   /* Now output the remaining mode sets in all the segments.  */
@@ -712,9 +874,18 @@ optimize_mode_switching (void)
     {
       int no_mode = num_modes[entity_map[j]];
 
+      /* In case there was no mode inserted. the mode information on the edge
+	 might not be complete.
+	 Update mode info on edges and commit pending mode sets.  */
+      need_commit |= commit_mode_sets (edge_list, j, entity_map);
+
       FOR_EACH_BB_REVERSE_FN (bb, cfun)
 	{
 	  struct seginfo *ptr, *next;
+#ifdef EMIT_MODE_TOGGLE
+	  bool toggle_p = test_flip_status (j, bb, false);
+#endif
+
 	  for (ptr = bb_info[j][bb->index].seginfo; ptr; ptr = next)
 	    {
 	      next = ptr->next;
@@ -724,7 +895,17 @@ optimize_mode_switching (void)
 
 		  rtl_profile_for_bb (bb);
 		  start_sequence ();
+
+#ifdef EMIT_MODE_TOGGLE
+		  if (! toggle_p ||
+		      ! EMIT_MODE_TOGGLE (entity_map[j], ptr->mode))
+#endif
 		  EMIT_MODE_SET (entity_map[j], ptr->mode, ptr->regs_live);
+
+#ifdef EMIT_MODE_TOGGLE
+		  toggle_p = true;
+#endif
+
 		  mode_set = get_insns ();
 		  end_sequence ();
 
@@ -748,11 +929,16 @@ optimize_mode_switching (void)
       free (bb_info[j]);
     }
 
+  free_edge_list (edge_list);
+  free_modes_infos (n_entities);
+
   /* Finished. Free up all the things we've allocated.  */
   sbitmap_vector_free (kill);
   sbitmap_vector_free (antic);
   sbitmap_vector_free (transp);
   sbitmap_vector_free (comp);
+  sbitmap_vector_free (avin);
+  sbitmap_vector_free (avout);
 
   if (need_commit)
     commit_edge_insertions ();
Index: gcc/testsuite/gcc.target/sh/fpchg1.c
===================================================================
--- gcc/testsuite/gcc.target/sh/fpchg1.c	(revision 0)
+++ gcc/testsuite/gcc.target/sh/fpchg1.c	(working copy)
@@ -0,0 +1,17 @@
+/* { dg-do compile } */
+/* { dg-options "-O1" } */
+/* { dg-skip-if "" { "sh*-*-*" } { "" } { "-m4a" "-m4-300" } }
+
+/* Make sure that fpchg is used.  */
+/* { dg-final { scan-assembler "fpchg" } } */
+/* { dg-final { scan-assembler-not "fpscr" } } */
+
+extern float c;
+
+void
+foo(int j)
+{
+  while (j--)
+    c++;
+
+}


