This is the mail archive of the gcc-patches@gcc.gnu.org mailing list for the GCC project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

PATCH: implement XScale scheduling


The following patch adds XScale-specific scheduling, particularly for
the XScale multiplier pipeline.  Benchmarks demonstated that
scheduling the multiplication instructions can have an adverse effect
on performance due to increased register pressure in the first
scheduling pass.  We opted to control multiplication instruction
scheduling with a switch, -mxscale-schedule-mult.

Tested on a NetBSD-x-xscale-elf cross-compiler, no regressions with or
without the switch in use.

Okay to commit?

Ben

2004-03-11  Ben Elliston  <bje@wasabisystems.com>

	* config/arm/xscale.md: New file.
	* config/arm/arm.md (generic_sched): Exclude tune=xscale, too.
	Include "xscale.md"--containing the XScale pipeline description.
	* config/arm/arm.h (XSCALE_FLAG_SCHEDULE_MULT): Define.
	(TARGET_XSCALE_SCHEDULE_MULT): Likewise.
	(TARGET_SWITCHES): Add "xscale-schedule-mult".
	(PREDICATE_CODES): Add "earliest_mult_constant",
	"earliest_umult_constant", "early_mult_constant",
	"early_umult_constant", "known_mult_constant" and
	"known_mmult_constant".
	* config/arm/arm.c (reg_known_value): Define.
	(enum mult_constant_type): Likewise.
	(mult_constant, earliest_mult_constant, earliest_umult_constant,
	early_mult_constant, early_umult_constant, known_mult_constant,
	known_umult_constant): New functions.
	* config/arm/arm-protos.h (earliest_mult_constant,
	early_mult_constant, earliest_umult_constant,
	early_umult_constant, known_mult_constant, known_umult_constant):
	Likewise.
	* doc/invoke.texi (ARM Options): Document -mxscale-schedule-mult.
 
Index: gcc/config/arm/arm-protos.h
===================================================================
RCS file: /cvs/gcc/gcc/gcc/config/arm/arm-protos.h,v
retrieving revision 1.63
diff -u -p -r1.63 arm-protos.h
--- gcc/config/arm/arm-protos.h	25 Feb 2004 17:03:26 -0000	1.63
+++ gcc/config/arm/arm-protos.h	12 Mar 2004 22:24:24 -0000
@@ -60,6 +60,12 @@ extern int neg_const_double_rtx_ok_for_f
 extern enum reg_class vfp_secondary_reload_class (enum machine_mode, rtx);
 
 /* Predicates.  */
+extern int earliest_mult_constant (rtx, enum machine_mode);
+extern int early_mult_constant (rtx, enum machine_mode);
+extern int earliest_umult_constant (rtx, enum machine_mode);
+extern int early_umult_constant (rtx, enum machine_mode);
+extern int known_mult_constant (rtx, enum machine_mode);
+extern int known_umult_constant (rtx, enum machine_mode);
 extern int s_register_operand (rtx, enum machine_mode);
 extern int arm_hard_register_operand (rtx, enum machine_mode);
 extern int arm_general_register_operand (rtx, enum machine_mode);
Index: gcc/config/arm/arm.c
===================================================================
RCS file: /cvs/gcc/gcc/gcc/config/arm/arm.c,v
retrieving revision 1.336
diff -u -p -r1.336 arm.c
--- gcc/config/arm/arm.c	3 Mar 2004 08:34:43 -0000	1.336
+++ gcc/config/arm/arm.c	12 Mar 2004 22:24:42 -0000
@@ -262,6 +262,8 @@ static int max_insns_skipped = 5;
 
 extern FILE * asm_out_file;
 
+extern rtx* reg_known_value;
+
 /* True if we are currently building a constant table.  */
 int making_const_table;
 
@@ -4977,6 +4979,114 @@ dominant_cc_register (rtx x, enum machin
     return FALSE;
 
   return cc_register (x, mode);
+}
+
+/* Examine register operands for known constant values that permit
+   early termination when used as operands to the XScale multiply
+   instructions.  This returns the type of constant--very early
+   termination, early termination, or normal/unknown termination.  The
+   constant type depends upon whether the multiplication is signed or
+   unsigned.  */
+
+enum mult_constant_type
+{
+  MULT_CONSTANT_EARLIEST,
+  MULT_CONSTANT_EARLY,
+  MULT_CONSTANT_UNKNOWN
+};
+
+static enum mult_constant_type
+mult_constant (x, unsigned_p)
+     rtx x;
+     int unsigned_p;
+{
+  if (GET_CODE (x) == REG
+      && REGNO (x) >= FIRST_PSEUDO_REGISTER
+      && GET_CODE (reg_known_value[REGNO (x)]) == CONST_INT)
+    {
+      unsigned HOST_WIDE_INT masked_rs;
+      HOST_WIDE_INT i = INTVAL (reg_known_value[REGNO (x)]);
+
+      masked_rs = i & (unsigned HOST_WIDE_INT) 0xffff8000UL;
+      if (masked_rs == 0UL
+	  || (! unsigned_p && masked_rs == 0xffff8000UL))
+	return MULT_CONSTANT_EARLIEST;
+
+      masked_rs = i & (unsigned HOST_WIDE_INT) 0xf8000000UL;
+      if (masked_rs == 0UL
+	  || (! unsigned_p && masked_rs == 0xf8000000UL))
+	return MULT_CONSTANT_EARLY;
+    }
+
+  return MULT_CONSTANT_UNKNOWN;
+}
+
+/* Returns TRUE if X refers to a register containing a value that
+   permits "earliest" termination for signed multiplication.  */
+
+int
+earliest_mult_constant (x, mode)
+     rtx x;
+     enum machine_mode mode ATTRIBUTE_UNUSED;
+{
+  return mult_constant (x, 0) == MULT_CONSTANT_EARLIEST;
+}
+
+/* Returns TRUE if X refers to a register containing a value that
+   permits "earliest" termination for unsigned multiplication.  */
+
+int
+earliest_umult_constant (x, mode)
+     rtx x;
+     enum machine_mode mode ATTRIBUTE_UNUSED;
+{
+  return mult_constant (x, 1) == MULT_CONSTANT_EARLIEST;
+}
+
+/* Returns TRUE if X refers to a register containing a value that
+   permits early termination for signed multiplication.  */
+
+int
+early_mult_constant (x, mode)
+     rtx x;
+     enum machine_mode mode ATTRIBUTE_UNUSED;
+{
+  return mult_constant (x, 0) == MULT_CONSTANT_EARLY;
+}
+
+/* Returns TRUE if X refers to a register containing a value that
+   permits early termination for unsigned multiplication.  */
+
+int
+early_umult_constant (x, mode)
+     rtx x;
+     enum machine_mode mode ATTRIBUTE_UNUSED;
+{
+  return mult_constant (x, 1) == MULT_CONSTANT_EARLY;
+}
+
+/* Returns TRUE if X refers to a register containing a value which
+   permits any sort of early termination for signed
+   multiplication.  */
+
+int
+known_mult_constant (x, mode)
+     rtx x;
+     enum machine_mode mode ATTRIBUTE_UNUSED;
+{
+  return mult_constant (x, 0) != MULT_CONSTANT_UNKNOWN;
+}
+
+/* Returns TRUE if X refers to a register containing a value which
+   permits any sort of early termination for signed
+   multiplication.  */
+
+int
+known_umult_constant (x, mode)
+     rtx x;
+     enum machine_mode mode ATTRIBUTE_UNUSED;
+{
+  return mult_constant (x, 1) != MULT_CONSTANT_UNKNOWN;
 }
 
 /* Return TRUE if X references a SYMBOL_REF.  */
Index: gcc/config/arm/arm.h
===================================================================
RCS file: /cvs/gcc/gcc/gcc/config/arm/arm.h,v
retrieving revision 1.225
diff -u -p -r1.225 arm.h
--- gcc/config/arm/arm.h	5 Mar 2004 16:59:53 -0000	1.225
+++ gcc/config/arm/arm.h	12 Mar 2004 22:24:47 -0000
@@ -439,6 +439,11 @@ extern GTY(()) rtx aof_pic_label;
 /* Fix invalid Cirrus instruction combinations by inserting NOPs.  */
 #define CIRRUS_FIX_INVALID_INSNS (1 << 22)
 
+/* Set if accurate modelling of the XScale multiply pipeline should be
+   used.  This is off by default because it causes excessive register
+   pressure in the first instruction scheduling pass.  */
+#define XSCALE_FLAG_SCHEDULE_MULT (1 << 23)
+
 #define TARGET_APCS_FRAME		(target_flags & ARM_FLAG_APCS_FRAME)
 #define TARGET_POKE_FUNCTION_NAME	(target_flags & ARM_FLAG_POKE)
 #define TARGET_FPE			(target_flags & ARM_FLAG_FPE)
@@ -472,6 +477,7 @@ extern GTY(()) rtx aof_pic_label;
 				         ? (target_flags & THUMB_FLAG_LEAF_BACKTRACE)	\
 				         : (target_flags & THUMB_FLAG_BACKTRACE))
 #define TARGET_CIRRUS_FIX_INVALID_INSNS	(target_flags & CIRRUS_FIX_INVALID_INSNS)
+#define TARGET_XSCALE_SCHEDULE_MULT	(target_flags & XSCALE_FLAG_SCHEDULE_MULT)
 
 /* SUBTARGET_SWITCHES is used to add flags on a per-config basis.  */
 #ifndef SUBTARGET_SWITCHES
@@ -554,6 +560,8 @@ extern GTY(()) rtx aof_pic_label;
    N_("Cirrus: Place NOPs to avoid invalid instruction combinations") },   \
   {"no-cirrus-fix-invalid-insns",  -CIRRUS_FIX_INVALID_INSNS,		   \
    N_("Cirrus: Do not break up invalid instruction combinations with NOPs") },\
+  {"xscale-schedule-mult",	    XSCALE_FLAG_SCHEDULE_MULT,		   \
+   N_("XScale: Perform accurate multiply instruction scheduling") },	   \
   SUBTARGET_SWITCHES							   \
   {"",				TARGET_DEFAULT, "" }			   \
 }
@@ -2842,7 +2850,14 @@ extern int making_const_table;
   {"cirrus_shift_const", {CONST_INT}},					\
   {"dominant_cc_register", {REG}},					\
   {"arm_float_compare_operand", {REG, CONST_DOUBLE}},			\
-  {"vfp_compare_operand", {REG, CONST_DOUBLE}},
+  {"vfp_compare_operand", {REG, CONST_DOUBLE}},				\
+  {"dominant_cc_register", {REG}},					\
+  {"earliest_mult_constant", {REG}},					\
+  {"early_mult_constant", {REG}},					\
+  {"known_mult_constant", {REG}},					\
+  {"earliest_umult_constant", {REG}},					\
+  {"early_umult_constant", {REG}},					\
+  {"known_umult_constant", {REG}},
 
 /* Define this if you have special predicates that know special things
    about modes.  Genrecog will warn about certain forms of
Index: gcc/config/arm/arm.md
===================================================================
RCS file: /cvs/gcc/gcc/gcc/config/arm/arm.md,v
retrieving revision 1.160
diff -u -p -r1.160 arm.md
--- gcc/config/arm/arm.md	11 Mar 2004 13:02:32 -0000	1.160
+++ gcc/config/arm/arm.md	12 Mar 2004 22:25:01 -0000
@@ -303,7 +303,7 @@
 
 (define_attr "generic_sched" "yes,no"
         (if_then_else 
-         (eq_attr "tune" "arm926ejs,arm1026ejs,arm1136js,arm1136jfs") 
+         (eq_attr "tune" "arm926ejs,arm1026ejs,arm1136js,arm1136jfs,xscale") 
          (const_string "no")
          (const_string "yes")))
 	
@@ -311,6 +311,7 @@
 (include "arm926ejs.md")
 (include "arm1026ejs.md")
 (include "arm1136jfs.md")
+(include "xscale.md")
 
 
 ;;---------------------------------------------------------------------------
Index: gcc/config/arm/xscale.md
===================================================================
RCS file: gcc/config/arm/xscale.md
diff -N gcc/config/arm/xscale.md
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ gcc/config/arm/xscale.md	12 Mar 2004 22:25:02 -0000
@@ -0,0 +1,251 @@
+;; Intel XScale Pipeline Description
+;; Copyright (C) 2004 Free Software Foundation, Inc.
+;; Contributed by Wasabi Systems, 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 2, 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 COPYING.  If not, write to the Free
+;; Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+;; 02111-1307, USA.  */
+
+;; This automaton provides a pipeline description for Intel XScale cores.
+
+;; Multiplier unit
+;
+(define_cpu_unit "mac" "arm")
+
+;; A regexp symbol that represents the mac writeback pipestage.
+(define_reservation "mac-finish" "nothing")
+
+(define_attr "xscale_mult_enabled" "no,yes"
+  (const (symbol_ref "TARGET_XSCALE_SCHEDULE_MULT")))
+
+(define_attr "xscale_mult_schedule" "no,yes"
+  (if_then_else (and (eq_attr "tune" "xscale")
+		     (eq_attr "xscale_mult_enabled" "yes"))
+		(const_string "yes")
+		(const_string "no")))
+
+;; NOTE: This is the default we use for multiplication without the
+;;       -mxscale-schedule-mult option.
+
+(define_insn_reservation "mult_xscale_unsched" 3
+  (and (eq_attr "tune" "xscale")
+       (and (eq_attr "xscale_mult_schedule" "no")
+	    (and (eq_attr "ldsched" "yes")
+		 (eq_attr "insn" "mla,mul,smlal,smlalxy,smlaxy,smull,smulxy,umlal,umull"))))
+  "core*2")
+
+(define_insn_reservation "mla_earliest" 2
+  (and (eq_attr "xscale_mult_schedule" "yes")
+       (and (eq_attr "insn" "mla")
+	    (match_operand 1 "earliest_mult_constant" "")))
+  "core,mac,mac-finish")
+
+(define_insn_reservation "mla_early" 3
+  (and (eq_attr "xscale_mult_schedule" "yes")
+       (and (eq_attr "insn" "mla")
+	    (match_operand 1 "early_mult_constant" "")))
+  "core,mac*2,mac-finish")
+
+(define_insn_reservation "mla" 4
+  (and (eq_attr "xscale_mult_schedule" "yes")
+       (and (eq_attr "insn" "mla")
+	    (not (match_operand 1 "known_mult_constant" ""))))
+  "core,mac*3,mac-finish")
+
+(define_insn_reservation "mul_earliest" 2
+  (and (eq_attr "xscale_mult_schedule" "yes")
+       (and (eq_attr "insn" "mul")
+	    (match_operand 1 "earliest_mult_constant" "")))
+  "core,mac,mac-finish")
+
+(define_insn_reservation "mul_early" 3
+  (and (eq_attr "xscale_mult_schedule" "yes")
+       (and (eq_attr "insn" "mul")
+	    (match_operand 1 "early_mult_constant" "")))
+  "core,mac*2,mac-finish")
+
+(define_insn_reservation "mul" 4
+  (and (eq_attr "xscale_mult_schedule" "yes")
+       (and (eq_attr "insn" "mul")
+	    (not (match_operand 1 "known_mult_constant" ""))))
+  "core,mac*3,mac-finish")
+
+(define_insn_reservation "smlal_earliest" 3
+  (and (eq_attr "xscale_mult_schedule" "yes")
+       (and (eq_attr "insn" "smlal")
+	    (match_operand 2 "earliest_mult_constant" "")))
+  "core,mac*2,mac-finish")
+
+(define_insn_reservation "smlal_early" 4
+  (and (eq_attr "xscale_mult_schedule" "yes")
+       (and (eq_attr "insn" "smlal")
+	    (match_operand 2 "early_mult_constant" "")))
+  "core,mac*3,mac-finish")
+
+(define_insn_reservation "smlal" 5
+  (and (eq_attr "xscale_mult_schedule" "yes")
+       (and (eq_attr "insn" "smlal")
+	    (not (match_operand 2 "known_mult_constant" ""))))
+  "core,mac*4,mac-finish")
+
+(define_insn_reservation "smlalxy" 3
+  (and (eq_attr "xscale_mult_schedule" "yes")
+       (eq_attr "insn" "smlalxy"))
+  "core,mac*2,mac-finish")
+
+(define_insn_reservation "smlaxy" 2
+  (and (eq_attr "xscale_mult_schedule" "yes")
+       (eq_attr "insn" "smlaxy"))
+  "core,mac,mac-finish")
+
+(define_insn_reservation "smull_earliest" 3
+  (and (eq_attr "xscale_mult_schedule" "yes")
+       (and (eq_attr "insn" "smull")
+	    (match_operand 2 "earliest_mult_constant" "")))
+  "core,mac*2,mac-finish")
+
+(define_insn_reservation "smull_early" 4
+  (and (eq_attr "xscale_mult_schedule" "yes")
+       (and (eq_attr "insn" "smull")
+	    (ior (match_operand 2 "early_mult_constant" "")
+		 (not (match_operand 2 "known_mult_constant" "")))))
+  "core,mac*3,mac-finish")
+
+(define_insn_reservation "smull" 5
+  (and (eq_attr "xscale_mult_schedule" "yes")
+       (and (eq_attr "insn" "smull")
+	    (not (match_operand 2 "known_mult_constant" ""))))
+  "core,mac*4,mac-finish")
+
+(define_insn_reservation "smulxy" 2
+  (and (eq_attr "xscale_mult_schedule" "yes")
+       (eq_attr "insn" "smulxy"))
+  "core,mac,mac-finish")
+
+(define_insn_reservation "umlal_earliest" 3
+  (and (eq_attr "xscale_mult_schedule" "yes")
+       (and (eq_attr "insn" "umlal")
+	    (match_operand 2 "earliest_umult_constant" "")))
+  "core,mac*2,mac-finish")
+
+(define_insn_reservation "umlal_early" 4
+  (and (eq_attr "xscale_mult_schedule" "yes")
+       (and (eq_attr "insn" "umlal")
+	    (match_operand 2 "early_umult_constant" "")))
+  "core,mac*3,mac-finish")
+
+(define_insn_reservation "umlal" 5
+  (and (eq_attr "xscale_mult_schedule" "yes")
+       (and (eq_attr "insn" "umlal")
+	    (not (match_operand 2 "known_umult_constant" ""))))
+  "core,mac*4,mac-finish")
+
+(define_insn_reservation "umull_earliest" 3
+  (and (eq_attr "xscale_mult_schedule" "yes")
+       (and (eq_attr "insn" "umull")
+            (match_operand 2 "earliest_umult_constant" "")))
+  "core,mac*2,mac-finish")
+
+(define_insn_reservation "umull_early" 4
+  (and (eq_attr "xscale_mult_schedule" "yes")
+       (and (eq_attr "insn" "umull")
+	    (match_operand 2 "early_umult_constant" "")))
+  "core,mac*3,mac-finish")
+
+(define_insn_reservation "umull" 5
+  (and (eq_attr "xscale_mult_schedule" "yes")
+       (and (eq_attr "insn" "umull")
+	    (not (match_operand 2 "known_mult_constant" ""))))
+  "core,mac*4,mac-finish")
+
+;; These are roughly the same as generic ARM cores.
+
+(define_insn_reservation "xscale_r_mem_f_wbuf" 5
+  (and (eq_attr "tune" "xscale")
+       (and (eq_attr "model_wbuf" "yes")
+	    (eq_attr "type" "r_mem_f")))
+  "core+write_buf*3")
+
+(define_insn_reservation "xscale_store_wbuf" 5
+  (and (eq_attr "tune" "xscale")
+       (and (eq_attr "model_wbuf" "yes")
+       	    (eq_attr "type" "store1")))
+  "core+write_buf*3+write_blockage*5")
+
+(define_insn_reservation "xscale_store2_wbuf" 7
+  (and (eq_attr "tune" "xscale")
+       (and (eq_attr "model_wbuf" "yes")
+	    (eq_attr "type" "store2")))
+  "core+write_buf*4+write_blockage*7")
+
+(define_insn_reservation "xscale_store3_wbuf" 9
+  (and (eq_attr "tune" "xscale")
+       (and (eq_attr "model_wbuf" "yes")
+	    (eq_attr "type" "store3")))
+  "core+write_buf*5+write_blockage*9")
+
+(define_insn_reservation "xscale_store4_wbuf" 11
+  (and (eq_attr "tune" "xscale")
+       (and (eq_attr "model_wbuf" "yes")
+            (eq_attr "type" "store4")))
+  "core+write_buf*6+write_blockage*11")
+
+(define_insn_reservation "xscale_store2" 3
+  (and (eq_attr "tune" "xscale")
+       (and (eq_attr "model_wbuf" "no")
+            (eq_attr "type" "store2")))
+  "core*3")
+
+(define_insn_reservation "xscale_store3" 4
+  (and (eq_attr "tune" "xscale")
+       (and (eq_attr "model_wbuf" "no")
+            (eq_attr "type" "store3")))
+  "core*4")
+
+(define_insn_reservation "xscale_store4" 5
+  (and (eq_attr "tune" "xscale")
+       (and (eq_attr "model_wbuf" "no")
+	    (eq_attr "type" "store4")))
+  "core*5")
+
+(define_insn_reservation "xscale_store1_ldsched" 1
+  (and (eq_attr "tune" "xscale")
+       (and (eq_attr "ldsched" "yes") 
+	    (eq_attr "type" "store1")))
+  "core")
+
+(define_insn_reservation "xscale_load_ldsched_xscale" 3
+  (and (eq_attr "tune" "xscale")
+       (and (eq_attr "ldsched" "yes") 
+	    (eq_attr "type" "load_byte,load1")))
+  "core")
+
+(define_insn_reservation "xscale_load_or_store" 2
+  (and (eq_attr "tune" "xscale")
+       (and (eq_attr "ldsched" "!yes") 
+	    (eq_attr "type" "load_byte,load1,load2,load3,load4,store1")))
+  "core*2")
+
+(define_insn_reservation "xscale_multi_cycle" 32
+  (and (eq_attr "tune" "xscale")
+       (and (eq_attr "core_cycles" "multi")
+            (eq_attr "type" "!mult,load_byte,load1,load2,load3,load4,store1,store2,store3,store4")))
+  "core*32")
+
+(define_insn_reservation "xscale_single_cycle" 1
+  (and (eq_attr "tune" "xscale")
+       (eq_attr "core_cycles" "single"))
+  "core")
Index: gcc/doc/invoke.texi
===================================================================
RCS file: /cvs/gcc/gcc/gcc/doc/invoke.texi,v
retrieving revision 1.426
diff -u -p -r1.426 invoke.texi
--- gcc/doc/invoke.texi	10 Mar 2004 21:43:35 -0000	1.426
+++ gcc/doc/invoke.texi	12 Mar 2004 22:25:27 -0000
@@ -385,6 +385,7 @@ in the following sections.
 -mpic-register=@var{reg} @gol
 -mnop-fun-dllimport @gol
 -mcirrus-fix-invalid-insns -mno-cirrus-fix-invalid-insns @gol
+-mxscale-schedule-mult @gol
 -mpoke-function-name @gol
 -mthumb  -marm @gol
 -mtpcs-frame  -mtpcs-leaf-frame @gol
@@ -6696,6 +6697,17 @@ point co-processor.  This option is not 
 problem is only present in older Maverick implementations.  The default
 can be re-enabled by use of the @option{-mno-cirrus-fix-invalid-insns}
 switch.
+
+@item -mxscale-schedule-mult
+@opindex mxscale-schedule-mult
+XScale processors have a non-pipelined multiplier unit with a variable
+result latency that requires accurate modelling to avoid pipeline
+stalls.  Using this option with some source code leads to good
+performance improvements, however in others, it causes the instruction
+scheduling pass to generate less suboptimal code.  This option may be
+used in conjunction with @option{-fno-schedule-insns} to disable the
+first instruction scheduling pass and improve the quality of generated
+code.
 
 @item -mpoke-function-name
 @opindex mpoke-function-name



Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]