This is the mail archive of the gdb-patches@sourceware.org mailing list for the GDB 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 2/2] Wrap-up expression support for DFP.


This patch implements all the missing expression support for
decimal floating point that I could find:

- +, -, *, /, exp, <, = with operands of same or different sizes of DFP,
  or a DFP and an integer type;
- negation (C's !), unary +;
- casting to and from decimal float.

If anything else is missing I'll be glad to add it.

Notes:

- doesn't support conversion of 64-bit integers to decimal float,
  because of libdecnumber limitation;
- error checking in decimal float operations ignore underflow, overflow
  and divide by zero to imitate binary float implementation;
- decimal_from_floating is not very nice because it uses sprintf, but
  I couldn't think of a better way.

Tested with no regressions on Linux/ppc32 and Linux/ppc64.
Ok to commit?
-- 
[]'s
Thiago Jung Bauermann
Software Engineer
IBM Linux Technology Center


gdb/
2007-12-19  Thiago Jung Bauermann  <bauerman@br.ibm.com>

	* Makefile.in (dfp.o): Depend on expression.h, gdbtypes.h and value.h.
	(valarith.o): Depend on dfp.h.
	(valops.o): Likewise.
	* dfp.c: Include expression.h, gdbtypes.h, value.h and dfp.h.
	(set_decnumber_context): New function.
	(decimal_check_errors): Likewise.
	(decimal_from_number): Likewise.
	(decimal_to_number): Likewise.
	(decimal_from_string): Use set_decnumber_context and
	decimal_check_errors.
	(decimal_from_integral): New function.
	(decimal_from_floating): Likewise.
	(decimal_to_double): Likewise.
	(promote_decimal): Likewise.
	(decimal_binop): Likewise.
	(decimal_is_zero): Likewise.
	(decimal_compare): Likewise.
	(decimal_convert): Likewise.
	* dfp.h (decimal_from_integral): New prototype.
	(decimal_from_floating): Likewise.
	(decimal_to_double): Likewise.
	(decimal_binop): Likewise.
	(decimal_is_zero): Likewise.
	(decimal_compare): Likewise.
	(decimal_convert): Likewise.
	* eval.c (evaluate_subexp_standard): Remove expect_type argument from
	call to value_from_decfloat.
	* valarith.c: Include dfp.h.
	(value_args_as_decimal): New function.
	(value_binop): Add if block to handle TYPE_CODE_DECFLOAT values.
	(value_logical_not): Likewise.
	(value_equal): Likewise.
	(value_less): Likewise.
	(value_pos): Likewise.
	(value_neg): Formatting fix.
	* valops.c: Include dfp.h.
	(value_cast): Add if block to handle TYPE_CODE_DECFLOAT values.
	* value.c (unpack_long): Add case to handle TYPE_CODE_DECFLOAT.
	(unpack_double): Add if block to handle TYPE_CODE_DECFLOAT.
	(value_from_decfloat): Remove expect_type argument.
	* value.h (value_from_decfloat): Update prototype.

gdb/testsuite/
2007-12-19  Thiago Jung Bauermann  <bauerman@br.ibm.com>

	* gdb.base/dfp-exprs.exp (test_dfp_arithmetic_expressions): Add tests
	for expressions with decimal float values.
	(test_dfp_conversions): New function to test casts to and from
	decimal float types.
	Call test_dfp_conversions.
	* gdb.base/dfp-test.c (struct decstruct): Add float4 and double8
	elements.
	(main): Initialize ds.float4 and ds.double8 elements.
	* gdb.base/dfp-test.exp (d32_set_tests): Fix typo.  Adjust expect
	string to new error message.
	(d64_set_tests): Likewise.
	(d128_set_tests): Likewise.
	Add tests for expressions with decimal float variables.  Add tests for
	conversions to and from decimal float types.

Index: src-git/gdb/Makefile.in
===================================================================
--- src-git.orig/gdb/Makefile.in	2007-12-19 16:34:35.000000000 -0200
+++ src-git/gdb/Makefile.in	2007-12-19 16:55:05.000000000 -0200
@@ -2046,7 +2046,8 @@ dsrec.o: dsrec.c $(defs_h) $(serial_h) $
 dummy-frame.o: dummy-frame.c $(defs_h) $(dummy_frame_h) $(regcache_h) \
 	$(frame_h) $(inferior_h) $(gdb_assert_h) $(frame_unwind_h) \
 	$(command_h) $(gdbcmd_h) $(gdb_string_h)
-dfp.o: dfp.c $(defs_h) $(dfp_h) $(decimal128_h) $(decimal64_h) $(decimal32_h)
+dfp.o: dfp.c $(defs_h) $(expression_h) $(gdbtypes_h) $(value_h) $(dfp_h) \
+	$(decimal128_h) $(decimal64_h) $(decimal32_h)
 dwarf2expr.o: dwarf2expr.c $(defs_h) $(symtab_h) $(gdbtypes_h) $(value_h) \
 	$(gdbcore_h) $(elf_dwarf2_h) $(dwarf2expr_h)
 dwarf2-frame.o: dwarf2-frame.c $(defs_h) $(dwarf2expr_h) $(elf_dwarf2_h) \
@@ -2910,12 +2911,12 @@ v850-tdep.o: v850-tdep.c $(defs_h) $(fra
 	$(regcache_h) $(dis_asm_h) $(osabi_h)
 valarith.o: valarith.c $(defs_h) $(value_h) $(symtab_h) $(gdbtypes_h) \
 	$(expression_h) $(target_h) $(language_h) $(gdb_string_h) \
-	$(doublest_h) $(infcall_h)
+	$(doublest_h) $(dfp_h) $(infcall_h)
 valops.o: valops.c $(defs_h) $(symtab_h) $(gdbtypes_h) $(value_h) $(frame_h) \
 	$(inferior_h) $(gdbcore_h) $(target_h) $(demangle_h) $(language_h) \
 	$(gdbcmd_h) $(regcache_h) $(cp_abi_h) $(block_h) $(infcall_h) \
 	$(dictionary_h) $(cp_support_h) $(gdb_string_h) $(gdb_assert_h) \
-	$(cp_support_h) $(observer_h)
+	$(cp_support_h) $(observer_h) $(dfp_h)
 valprint.o: valprint.c $(defs_h) $(gdb_string_h) $(symtab_h) $(gdbtypes_h) \
 	$(value_h) $(gdbcore_h) $(gdbcmd_h) $(target_h) $(language_h) \
 	$(annotate_h) $(valprint_h) $(floatformat_h) $(doublest_h) \
Index: src-git/gdb/dfp.c
===================================================================
--- src-git.orig/gdb/dfp.c	2007-12-19 16:34:35.000000000 -0200
+++ src-git/gdb/dfp.c	2007-12-19 19:55:11.000000000 -0200
@@ -18,6 +18,10 @@
    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
 #include "defs.h"
+#include "expression.h"
+#include "gdbtypes.h"
+#include "value.h"
+#include "dfp.h"
 
 /* The order of the following headers is important for making sure
    decNumber structure is large enough to hold decimal128 digits.  */
@@ -50,6 +54,89 @@ match_endianness (const gdb_byte *from, 
   return;
 }
 
+/* Helper function to get the appropriate libdecnumber context for each size
+   of decimal float.  */
+static void
+set_decnumber_context (decContext *ctx, int len)
+{
+  switch (len)
+    {
+      case 4:
+	decContextDefault (ctx, DEC_INIT_DECIMAL32);
+	break;
+      case 8:
+	decContextDefault (ctx, DEC_INIT_DECIMAL64);
+	break;
+      case 16:
+	decContextDefault (ctx, DEC_INIT_DECIMAL128);
+	break;
+    }
+
+  ctx->traps = 0;
+}
+
+/* Check for errors signaled in the decimal context structure.  */
+static void
+decimal_check_errors (decContext *ctx)
+{
+  /* An error here could be a division by zero, an overflow, an underflow or
+     an invalid operation (from the DEC_Errors constant in decContext.h).
+     Since GDB doesn't complain about division by zero, overflow or underflow
+     errors for binary floating, we won't complain about them for decimal
+     floating either.  */
+  if (ctx->status & DEC_IEEE_854_Invalid_operation)
+    {
+      /* Leave only the error bits in the status flags.  */
+      ctx->status &= DEC_IEEE_854_Invalid_operation;
+      error (_("Cannot perform operation: %s"), decContextStatusToString (ctx));
+    }
+}
+
+/* Helper function to convert from libdecnumber's appropriate representation
+   for computation to each size of decimal float.  */
+static void
+decimal_from_number (const decNumber *from, gdb_byte *to, int len)
+{
+  decContext set;
+
+  set_decnumber_context (&set, len);
+
+  switch (len)
+    {
+      case 4:
+	decimal32FromNumber ((decimal32 *) to, from, &set);
+	break;
+      case 8:
+	decimal64FromNumber ((decimal64 *) to, from, &set);
+	break;
+      case 16:
+	decimal128FromNumber ((decimal128 *) to, from, &set);
+	break;
+    }
+}
+
+/* Helper function to convert each size of decimal float to libdecnumber's
+   appropriate representation for computation.  */
+static void
+decimal_to_number (const gdb_byte *from, int len, decNumber *to)
+{
+  switch (len)
+    {
+      case 4:
+	decimal32ToNumber ((decimal32 *) from, to);
+	break;
+      case 8:
+	decimal64ToNumber ((decimal64 *) from, to);
+	break;
+      case 16:
+	decimal128ToNumber ((decimal128 *) from, to);
+	break;
+      default:
+	error (_("Unknown decimal floating point type.\n"));
+	break;
+    }
+}
+
 /* Convert decimal type to its string representation.  LEN is the length
    of the decimal type, 4 bytes for decimal32, 8 bytes for decimal64 and
    16 bytes for decimal128.  */
@@ -59,6 +146,7 @@ decimal_to_string (const gdb_byte *decby
   gdb_byte dec[16];
 
   match_endianness (decbytes, len, dec);
+
   switch (len)
     {
       case 4:
@@ -85,21 +173,17 @@ decimal_from_string (gdb_byte *decbytes,
   decContext set;
   gdb_byte dec[16];
 
+  set_decnumber_context (&set, len);
+
   switch (len)
     {
       case 4:
-	decContextDefault (&set, DEC_INIT_DECIMAL32);
-	set.traps = 0;
 	decimal32FromString ((decimal32 *) dec, string, &set);
 	break;
       case 8:
-	decContextDefault (&set, DEC_INIT_DECIMAL64);
-	set.traps = 0;
 	decimal64FromString ((decimal64 *) dec, string, &set);
 	break;
       case 16:
-	decContextDefault (&set, DEC_INIT_DECIMAL128);
-	set.traps = 0;
 	decimal128FromString ((decimal128 *) dec, string, &set);
 	break;
       default:
@@ -109,5 +193,206 @@ decimal_from_string (gdb_byte *decbytes,
 
   match_endianness (dec, len, decbytes);
 
+  /* Check for errors in the DFP operation.  */
+  decimal_check_errors (&set);
+
   return 1;
 }
+
+/* Converts a value of an integral type to a decimal float of
+   specified LEN bytes.  */
+void
+decimal_from_integral (struct value *from, gdb_byte *to, int len)
+{
+  LONGEST l;
+  gdb_byte dec[16];
+  decNumber number;
+  struct type *type;
+
+  type = check_typedef (value_type (from));
+
+  if (TYPE_LENGTH (type) > 4)
+    /* libdecnumber can convert only 32-bit integers.  */
+    error (_("Conversion of large integer to a decimal floating type is not supported."));
+
+  l = value_as_long (from);
+
+  if (TYPE_UNSIGNED (type))
+    decNumberFromUInt32 (&number, (unsigned int) l);
+  else
+    decNumberFromInt32 (&number, (int) l);
+
+  decimal_from_number (&number, dec, len);
+  match_endianness (dec, len, to);
+}
+
+/* Converts a value of a float type to a decimal float of
+   specified LEN bytes.
+
+   This is an ugly way to do the conversion, but libdecnumber does
+   not offer a direct way to do it.  */
+void
+decimal_from_floating (struct value *from, gdb_byte *to, int len)
+{
+  /* The size of this buffer is a conservative guess: assumes an 128-bit
+     long double could have 40 decimal digits, plus 4 for exponent, plus
+     3 for mantissa and exponent signs and 'E', plus '\0'.  */
+  char buffer[48];
+
+  /* We cannot use snprintf here because it is defined only in C99.
+     We have to assume buffer size is sufficient.  */
+  sprintf (buffer, "%Lf", value_as_double (from));
+  decimal_from_string (to, len, buffer);
+}
+
+/* Converts a decimal float of LEN bytes to a double value.  */
+DOUBLEST
+decimal_to_double (const gdb_byte *from, int len)
+{
+  char buffer[MAX_DECIMAL_STRING];
+
+  /* This is an ugly way to do the conversion, but libdecnumber does
+     not offer a direct way to do it.  */
+  decimal_to_string (from, len, buffer);
+  return atof (buffer);
+}
+
+/* Check if operands have the same size and convert them to the
+   biggest of the two if necessary.  */
+static int
+promote_decimal (gdb_byte *x, int len_x, gdb_byte *y, int len_y)
+{
+  int len_result;
+  decNumber number;
+
+  if (len_x < len_y)
+    {
+      decimal_to_number (x, len_x, &number);
+      decimal_from_number (&number, x, len_y);
+      len_result = len_y;
+    }
+  else if (len_x > len_y)
+    {
+      decimal_to_number (y, len_y, &number);
+      decimal_from_number (&number, y, len_x);
+      len_result = len_x;
+    }
+  else
+    len_result = len_x;
+
+  return len_result;
+}
+
+/* Perform operation OP with operands X and Y and store value in RESULT.
+   If LEN_X and LEN_Y are not equal, RESULT will have the size of the biggest
+   of the two, and LEN_RESULT will be set accordingly.  */
+void
+decimal_binop (enum exp_opcode op, const gdb_byte *x, int len_x,
+	       const gdb_byte *y, int len_y, gdb_byte *result, int *len_result)
+{
+  decContext set;
+  decNumber number1, number2, number3;
+  gdb_byte dec1[16], dec2[16], dec3[16];
+
+  match_endianness (x, len_x, dec1);
+  match_endianness (y, len_y, dec2);
+
+  *len_result = promote_decimal (dec1, len_x, dec2, len_y);
+
+  /* Both operands are of size *len_result from now on.  */
+
+  decimal_to_number (dec1, *len_result, &number1);
+  decimal_to_number (dec2, *len_result, &number2);
+
+  set_decnumber_context (&set, *len_result);
+
+  switch (op)
+    {
+      case BINOP_ADD:
+	decNumberAdd (&number3, &number1, &number2, &set);
+	break;
+      case BINOP_SUB:
+	decNumberSubtract (&number3, &number1, &number2, &set);
+	break;
+      case BINOP_MUL:
+	decNumberMultiply (&number3, &number1, &number2, &set);
+	break;
+      case BINOP_DIV:
+	decNumberDivide (&number3, &number1, &number2, &set);
+	break;
+      case BINOP_EXP:
+	decNumberPower (&number3, &number1, &number2, &set);
+	break;
+      default:
+	error (_("Unknown decimal floating point operation."));
+	break;
+    }
+
+  /* Check for errors in the DFP operation.  */
+  decimal_check_errors (&set);
+
+  decimal_from_number (&number3, dec3, *len_result);
+
+  match_endianness (dec3, *len_result, result);
+}
+
+/* Returns true if X (which is LEN bytes wide) is the number zero.  */
+int
+decimal_is_zero (const gdb_byte *x, int len)
+{
+  decNumber number;
+  gdb_byte dec[16];
+
+  match_endianness (x, len, dec);
+  decimal_to_number (dec, len, &number);
+
+  return decNumberIsZero (&number);
+}
+
+/* Compares two numbers numerically.  If X is less than Y then the return value
+   will be -1.  If they are equal, then the return value will be 0.  If X is
+   greater than the Y then the return value will be 1.  */
+int
+decimal_compare (const gdb_byte *x, int len_x, const gdb_byte *y, int len_y)
+{
+  decNumber number1, number2, result;
+  decContext set;
+  gdb_byte dec1[16], dec2[16];
+  int len_result;
+
+  match_endianness (x, len_x, dec1);
+  match_endianness (y, len_y, dec2);
+
+  len_result = promote_decimal (dec1, len_x, dec2, len_y);
+
+  decimal_to_number (dec1, len_result, &number1);
+  decimal_to_number (dec2, len_result, &number2);
+
+  set_decnumber_context (&set, len_result);
+
+  decNumberCompare (&result, &number1, &number2, &set);
+
+  /* Check for errors in the DFP operation.  */
+  decimal_check_errors (&set);
+
+  if (decNumberIsNaN (&result))
+    error (_("Comparison with an invalid number (NaN)."));
+  else if (decNumberIsZero (&result))
+    return 0;
+  else if (decNumberIsNegative (&result))
+    return -1;
+  else
+    return 1;
+}
+
+/* Convert a decimal value from a decimal type with LEN_FROM bytes to a
+   decimal type with LEN_TO bytes.  */
+void
+decimal_convert (const gdb_byte *from, int len_from, gdb_byte *to,
+		 int len_to)
+{
+  decNumber number;
+
+  decimal_to_number (from, len_from, &number);
+  decimal_from_number (&number, to, len_to);
+}
Index: src-git/gdb/dfp.h
===================================================================
--- src-git.orig/gdb/dfp.h	2007-12-19 16:34:35.000000000 -0200
+++ src-git/gdb/dfp.h	2007-12-19 19:54:58.000000000 -0200
@@ -31,5 +31,14 @@
 
 extern void decimal_to_string (const gdb_byte *, int, char *);
 extern int decimal_from_string (gdb_byte *, int, const char *);
+extern void decimal_from_integral (struct value *from, gdb_byte *to, int len);
+extern void decimal_from_floating (struct value *from, gdb_byte *to, int len);
+extern DOUBLEST decimal_to_double (const gdb_byte *from, int len);
+extern void decimal_binop (enum exp_opcode, const gdb_byte *, int,
+			   const gdb_byte *, int, gdb_byte *, int *);
+extern int decimal_is_zero (const gdb_byte *x, int len);
+extern int decimal_compare (const gdb_byte *x, int len_x, const gdb_byte *y, int len_y);
+extern void decimal_convert (const gdb_byte *from, int len_from, gdb_byte *to,
+			     int len_to);
 
 #endif
Index: src-git/gdb/eval.c
===================================================================
--- src-git.orig/gdb/eval.c	2007-12-19 16:34:35.000000000 -0200
+++ src-git/gdb/eval.c	2007-12-19 16:55:05.000000000 -0200
@@ -458,8 +458,8 @@ evaluate_subexp_standard (struct type *e
 
     case OP_DECFLOAT:
       (*pos) += 3;
-      return value_from_decfloat (expect_type, exp->elts[pc + 1].type,
-				exp->elts[pc + 2].decfloatconst);
+      return value_from_decfloat (exp->elts[pc + 1].type,
+				  exp->elts[pc + 2].decfloatconst);
 
     case OP_VAR_VALUE:
       (*pos) += 3;
Index: src-git/gdb/testsuite/gdb.base/dfp-exprs.exp
===================================================================
--- src-git.orig/gdb/testsuite/gdb.base/dfp-exprs.exp	2007-12-19 16:34:35.000000000 -0200
+++ src-git/gdb/testsuite/gdb.base/dfp-exprs.exp	2007-12-19 16:55:05.000000000 -0200
@@ -74,14 +74,102 @@ proc test_dfp_literals_accepted {} {
 
 proc test_dfp_arithmetic_expressions {} {
 
-# Arithmetic operations for DFP types are not yet implemented in GDB.
-# These tests are to verify that they will generate expected error messages.
+    # _Decimal32 operands.
+    gdb_test "p 1.4df + 1.2df" " = 2.6"
+    gdb_test "p 1.4df - 1.2df" " = 0.2"
+    gdb_test "p 1.4df * 1.2df" " = 1.68"
+    gdb_test "p 1.4df / 1.2df" " = 1.166667"
+
+    # _Decimal64 operands.
+    gdb_test "p 1.4dd + 1.2dd" " = 2.6"
+    gdb_test "p 1.4dd - 1.2dd" " = 0.2"
+    gdb_test "p 1.4dd * 1.2dd" " = 1.68"
+    gdb_test "p 1.4dd / 1.2dd" " = 1.166666666666667"
+
+    # _Decimal128 operands.
+    gdb_test "p 1.4dl + 1.2dl" " = 2.6"
+    gdb_test "p 1.4dl - 1.2dl" " = 0.2"
+    gdb_test "p 1.4dl * 1.2dl" " = 1.68"
+    gdb_test "p 1.4dl / 1.2dl" " = 1.166666666666666666666666666666667"
+
+    # Test type of operation result.
+    gdb_test "ptype 2.df + 2.df" "= _Decimal32"
+    gdb_test "ptype 2.dd + 2.dd" "= _Decimal64"
+    gdb_test "ptype 2.dl + 2.dl" "= _Decimal128"
+
+    # Mixture of different _Decimal sizes.
+    gdb_test "p 2.1df + 2.7dd" "= 4.8"
+    gdb_test "p 2.1dd + 2.7df" "= 4.8"
+    gdb_test "p 2.6df + 2.7dl" "= 5.3"
+    gdb_test "p 2.6dl + 2.7df" "= 5.3"
+    gdb_test "p 2.3dd + 2.2dl" "= 4.5"
+    gdb_test "p 2.3dl + 2.2dd" "= 4.5"
+    gdb_test "ptype 2.df + 2.dd" "= _Decimal64"
+    gdb_test "ptype 2.df + 2.dl" "= _Decimal128"
+    gdb_test "ptype 2.dd + 2.dl" "= _Decimal128"
+
+    # Mixture of Decimal and integral operands
+    gdb_test "p 1.2df + 1" " = 2.2"
+    gdb_test "p 2 + 1.7dd" " = 3.7"
+    gdb_test "p 3 + 2.1dl" " = 5.1"
+    gdb_test "ptype 1.2df + 1" " = _Decimal32"
+    gdb_test "ptype 2 + 1.7dd" " = _Decimal64"
+    gdb_test "ptype 3 + 2.1dl" " = _Decimal128"
+
+    # Reject operation with integral larger than 32-bits
+    gdb_test "p 1.2df + 2ll" "Conversion of large integer to a decimal floating type is not supported."
+
+    # Reject operation with DFP and Binary FP
+    gdb_test "p 1.2df + 1.2f" "Mixing decimal floating types with other floating types is not allowed."
+
+    # Test other operations with DFP operands
+    gdb_test "p !0.df" " = 1"
+    gdb_test "p !0.dd" " = 1"
+    gdb_test "p !0.dl" " = 1"
+    gdb_test "p !0.5df" " = 0"
+    gdb_test "p !0.5dd" " = 0"
+    gdb_test "p !0.5dl" " = 0"
+
+    gdb_test "p 1.2df == 1.2df" " = 1"
+    gdb_test "p 1.2df == 1.2dd" " = 1"
+    gdb_test "p 1.2df == 1.2dl" " = 1"
+    gdb_test "p 1.2dd == 1.2df" " = 1"
+    gdb_test "p 1.2dd == 1.2dl" " = 1"
+    gdb_test "p 1.2dl == 1.2df" " = 1"
+    gdb_test "p 1.2dl == 1.2dd" " = 1"
+    gdb_test "p 1.2df == 1.3df" " = 0"
+    gdb_test "p 1.2df == 1.3dd" " = 0"
+    gdb_test "p 1.2df == 1.3dl" " = 0"
+    gdb_test "p 1.2dd == 1.3df" " = 0"
+    gdb_test "p 1.2dd == 1.3dl" " = 0"
+    gdb_test "p 1.2dl == 1.3df" " = 0"
+    gdb_test "p 1.2dl == 1.3dd" " = 0"
+
+    gdb_test "p +1.2df" " = 1.2"
+    gdb_test "p +1.2dd" " = 1.2"
+    gdb_test "p +1.2dl" " = 1.2"
+
+    gdb_test "p 1.2df < 1.3df" " = 1"
+    gdb_test "p 1.2df < 1.3dd" " = 1"
+    gdb_test "p 1.2dl < 1.3df" " = 1"
+    gdb_test "p 1.2dd < 1.3dd" " = 1"
+    gdb_test "p 1.2dd < 1.3dl" " = 1"
+    gdb_test "p 1.2dl < 1.3dl" " = 1"
+    gdb_test "p 1.2dl < 1.3df" " = 1"
+    gdb_test "p 1.2df > 1" " = 1"
+    gdb_test "p 1.2dl > 2" " = 0"
+    gdb_test "p 2 > 1.2dd" " = 1"
+    gdb_test "p 2 > 3.1dl" " = 0"
+}
 
-   gdb_test "p 1.4df + 1.2df" "Argument to arithmetic operation not a number or boolean.*"
-   gdb_test "p 1.4df - 1.2df" ".*Argument to arithmetic operation not a number or boolean.*"
-   gdb_test "p 1.4df * 1.2df" "Argument to arithmetic operation not a number or boolean.*"
-   gdb_test "p 1.4df / 1.2df" "Argument to arithmetic operation not a number or boolean.*"
+proc test_dfp_conversions {} {
+    # Test cast to and from DFP values, and between DFPs of different sizes
 
+    gdb_test "p (float) -0.1df" " = -0.099999994"
+    gdb_test "p (int) 8.3dd" " = 8"
+    gdb_test "p (_Decimal64) 3.1" " = 3.100000"
+    gdb_test "p (_Decimal128) 3.7df" " = 3.7"
+    gdb_test "p (_Decimal32) 4" " = 4"
 }
 
 # Start with a fresh gdb.
@@ -92,3 +180,4 @@ gdb_reinitialize_dir $srcdir/$subdir
 
 test_dfp_literals_accepted
 test_dfp_arithmetic_expressions
+test_dfp_conversions
Index: src-git/gdb/testsuite/gdb.base/dfp-test.c
===================================================================
--- src-git.orig/gdb/testsuite/gdb.base/dfp-test.c	2007-12-19 16:34:35.000000000 -0200
+++ src-git/gdb/testsuite/gdb.base/dfp-test.c	2007-12-19 16:55:05.000000000 -0200
@@ -26,6 +26,8 @@ struct decstruct
 {
   int int4;
   long long8;
+  float float4;
+  double double8;
   _Decimal32 dec32;
   _Decimal64 dec64;
   _Decimal128 dec128;
@@ -85,6 +87,8 @@ int main()
 
   ds.int4 = 1;
   ds.long8 = 2;
+  ds.float4 = 3.1;
+  ds.double8 = 4.2;
   ds.dec32 = 1.2345df;
   ds.dec64 = 1.2345dd;
   ds.dec128 = 1.2345dl;
Index: src-git/gdb/testsuite/gdb.base/dfp-test.exp
===================================================================
--- src-git.orig/gdb/testsuite/gdb.base/dfp-test.exp	2007-12-19 16:34:35.000000000 -0200
+++ src-git/gdb/testsuite/gdb.base/dfp-test.exp	2007-12-20 01:39:19.000000000 -0200
@@ -18,6 +18,53 @@
 # This file is part of the gdb testsuite.  It is intended to test that
 # gdb could correctly handle decimal floating point introduced in IEEE 754R.
 
+if $tracelevel then {
+  strace $tracelevel
+}
+
+set testfile "dfp-test"
+set srcfile ${testfile}.c
+set binfile ${objdir}/${subdir}/${testfile}
+
+# Try to compile the test case.  If we can't, assume the
+# toolchain does not yet provide DFP support and bail out.
+if { [gdb_compile "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {quiet debug}] != "" } {
+    verbose "Skipping DFP tests."
+    return -1
+}
+
+gdb_exit
+gdb_start
+gdb_reinitialize_dir $srcdir/$subdir
+gdb_load ${binfile}
+
+if ![runto_main] then {
+    perror "couldn't run to breakpoint"
+    continue
+}
+
+# Detect the size of the target's basic types (from gdb.base/long_long.exp).
+
+proc get_valueof { fmt exp default } {
+    global gdb_prompt
+    send_gdb "print${fmt} ${exp}\n"
+    gdb_expect {
+	-re "\\$\[0-9\]* = (\[-\]*\[0-9\]*).*$gdb_prompt $" {
+	    set val $expect_out(1,string)
+	}
+	timeout {
+	    set val ${default}
+	}
+    }
+    return ${val}
+}
+
+proc get_sizeof { type default } {
+    return [get_valueof "/d" "sizeof (${type})" $default]
+}
+
+set sizeof_long [get_sizeof "long" 4]
+
 proc d32_set_tests {} {
 
     gdb_test "p d32=123.45df" " = 123.45"
@@ -43,10 +90,10 @@ proc d32_set_tests {} {
     gdb_test "p d32=1.234567E+97df" " = Infinity" "1.234567E+97 is Infinity"
 
     # Test that gdb could detect the errors in the string representation of _Decimal32
-    gdb_test "p d32=12345.df" " = 12345" "12345. is an valid number"
+    gdb_test "p d32=12345.df" " = 12345" "12345. is a valid number"
     gdb_test "p d32=12345df" ".*Invalid number.*" "12345 is an invalid number"
-    gdb_test "p d32=1.23Edf" " = NaN" "1.23E is NaN (not a number)"
-    gdb_test "p d32=1.23E45Adf" " = NaN" "1.23E45A is NaN (not a number)"
+    gdb_test "p d32=1.23Edf" ".*Conversion syntax.*" "1.23E is an invalid number"
+    gdb_test "p d32=1.23E45Adf" ".*Conversion syntax.*" "1.23E45A is an invalid number"
 }
 
 proc d64_set_tests {} {
@@ -75,8 +122,8 @@ proc d64_set_tests {} {
 
     # Test that gdb could detect the errors in the string representation of _Decimal64
     gdb_test "p d64=12345dd" ".*Invalid number.*" "12345dd is an invalid number"
-    gdb_test "p d64=1.23Edd" " = NaN" "1.23E is NaN (not a number)"
-    gdb_test "p d64=1.23E45Add" "= NaN" "1.23E45A is NaN (not a number)"
+    gdb_test "p d64=1.23Edd" ".*Conversion syntax.*" "1.23E is an invalid number"
+    gdb_test "p d64=1.23E45Add" ".*Conversion syntax.*" "1.23E45A is an invalid number"
 }
 
 proc d128_set_tests {} {
@@ -104,33 +151,8 @@ proc d128_set_tests {} {
 
     # Test that gdb could detect the errors in the string representation of _Decimal128
     gdb_test "p d128=12345dl" ".*Invalid number.*" "12345dl is an invalid number"
-    gdb_test "p d128=1.23Edl" " = NaN" "1.23E is NaN (not a number)"
-    gdb_test "p d128=1.23E45Adl" "= NaN" "1.23E45A is NaN (not a number)"
-}
-
-
-if $tracelevel {
-    strace $tracelevel
-}
-
-set testfile "dfp-test"
-set srcfile ${testfile}.c
-set binfile ${objdir}/${subdir}/${testfile}
-# Try to compile the test case.  If we can't, assume the
-# toolchain does not yet provide DFP support and bail out.
-if { [gdb_compile "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {quiet debug}] != "" } {
-    verbose "Skipping DFP tests."
-    return -1
-}
-
-gdb_exit
-gdb_start
-gdb_reinitialize_dir $srcdir/$subdir
-gdb_load ${binfile}
-
-if ![runto_main] then {
-    perror "couldn't run to breakpoint"
-    continue
+    gdb_test "p d128=1.23Edl" ".*Conversion syntax.*" "1.23E is an invalid number"
+    gdb_test "p d128=1.23E45Adl" ".*Conversion syntax.*" "1.23E45A is an invalid number"
 }
 
 # Different tests on 32-bits decimal floating point, including the printing
@@ -237,6 +259,57 @@ gdb_test "print ds.dec32" " = 1.2345"
 gdb_test "print ds.dec64" " = 1.2345"
 gdb_test "print ds.dec128" " = 1.2345"
 
+# Test expressions with DFP variables.
+
+gdb_test "print d32 + ds.dec32" " = 1.3345"
+gdb_test "print d64 + ds.dec64" " = 1.3345"
+gdb_test "print d128 + ds.dec128" " = 1.3345"
+
+# Test conversion between different _Decimal sizes.
+
+gdb_test "ptype d64 + ds.dec32" " = volatile _Decimal64"
+gdb_test "ptype d128 + ds.dec32" " = volatile _Decimal128"
+gdb_test "ptype d128 + ds.dec64" " = volatile _Decimal128"
+
+# Mixture of Decimal and integral operands
+gdb_test "p d32 + 1" " = 1.1"
+gdb_test "p 2 + d64" " = 2.1"
+gdb_test "p ds.int4 + d128" " = 1.1"
+gdb_test "ptype d32 + 1" " = volatile _Decimal32"
+gdb_test "ptype ds.int4 + d128" " = volatile _Decimal128"
+
+# Test other operations with DFP operands
+gdb_test "p !d32" " = 0"
+gdb_test "p !d64" " = 0"
+gdb_test "p !d128" " = 0"
+gdb_test "p +d32" " = 0.1"
+gdb_test "p +d64" " = 0.1"
+gdb_test "p +d128" " = 0.1"
+gdb_test "p d64 == d128" " = 1"
+gdb_test "p d128 == ds.dec32" " = 0"
+gdb_test "p d128 == d32" " = 1"
+gdb_test "p ds.dec32 == ds.dec64" " = 1"
+gdb_test "p d32 < ds.dec32" " = 1"
+gdb_test "p d64 < ds.dec64" " = 1"
+gdb_test "p d128 < ds.dec128" " = 1"
+gdb_test "p ds.dec32 < d32" " = 0"
+gdb_test "p d64 > ds.dec64" " = 0"
+gdb_test "p ds.dec128 > d128 " " = 1"
+gdb_test "p d32 < ds.int4" " = 1"
+gdb_test "p ds.int4 > d32" " = 1"
+gdb_test "p ds.dec32 < ds.int4" " = 0"
+gdb_test "p ds.int4 > ds.dec64" " = 0"
+gdb_test "p ds.dec128 > ds.int4" " = 1"
+
+# Reject operation with integral larger than 32-bits
+if { ${sizeof_long} > 4 } {
+  gdb_test "p d32 + ds.long8" "Conversion of large integer to a decimal floating type is not supported."
+}
+
+# Reject operation with DFP and Binary FP
+gdb_test "p d64 + ds.float4" "Mixing decimal floating types with other floating types is not allowed."
+gdb_test "p ds.double8 + d128" "Mixing decimal floating types with other floating types is not allowed."
+
 # The following tests are intended to verify that gdb can handle "d1=d2"
 # and "d1=-d2" correctly.
 
@@ -246,3 +319,12 @@ gdb_test "print ds.dec128=d128" " = 0.1"
 gdb_test "print ds.dec32 = -d32" " = -0.1"
 gdb_test "print ds.dec64 = -d64" " = -0.1"
 gdb_test "print ds.dec128 = -d128" " = -0.1"
+
+# Test cast to and from DFP values
+
+gdb_test "print ds.double8 = ds.dec64" " = -0.10000000000000001"
+gdb_test "print ds.dec64 = ds.float4" " = 3.100000"
+gdb_test "print ds.dec128 = -ds.double8" " = 0.100000"
+gdb_test "print ds.dec128 = ds.dec32" " = -0.1"
+gdb_test "print ds.dec32 = ds.int4" " = 1"
+gdb_test "print ds.int4 = 7.3dl" " = 7"
Index: src-git/gdb/valarith.c
===================================================================
--- src-git.orig/gdb/valarith.c	2007-12-19 16:34:35.000000000 -0200
+++ src-git/gdb/valarith.c	2007-12-19 16:55:05.000000000 -0200
@@ -28,6 +28,7 @@
 #include "language.h"
 #include "gdb_string.h"
 #include "doublest.h"
+#include "dfp.h"
 #include <math.h>
 #include "infcall.h"
 
@@ -741,6 +742,60 @@ value_concat (struct value *arg1, struct
 }
 
 
+/* Obtain decimal value of arguments for binary operation, converting from
+   other types if one of them is not decimal floating point.  */
+static void
+value_args_as_decimal (struct value *arg1, struct value *arg2,
+		       gdb_byte *x, int *len_x, gdb_byte *y, int *len_y)
+{
+  struct type *type1, *type2;
+
+  type1 = check_typedef (value_type (arg1));
+  type2 = check_typedef (value_type (arg2));
+
+  /* At least one of the arguments must be of decimal float type.  */
+  gdb_assert (TYPE_CODE (type1) == TYPE_CODE_DECFLOAT
+	      || TYPE_CODE (type2) == TYPE_CODE_DECFLOAT);
+
+  if (TYPE_CODE (type1) == TYPE_CODE_FLT
+      || TYPE_CODE (type2) == TYPE_CODE_FLT)
+    /* The DFP extension to the C language does not allow mixing of
+     * decimal float types with other float types in expressions
+     * (see WDTR 24732, page 12).  */
+    error (_("Mixing decimal floating types with other floating types is not allowed."));
+
+  /* Obtain decimal value of arg1, converting from other types
+     if necessary.  */
+
+  if (TYPE_CODE (type1) == TYPE_CODE_DECFLOAT)
+    {
+      *len_x = TYPE_LENGTH (type1);
+      memcpy (x, value_contents (arg1), *len_x);
+    }
+  else if (is_integral_type (type1))
+    {
+      *len_x = TYPE_LENGTH (type2);
+      decimal_from_integral (arg1, x, *len_x);
+    }
+  else
+    error (_("Don't know how to convert to decimal floating type."));
+
+  /* Obtain decimal value of arg2, converting from other types
+     if necessary.  */
+
+  if (TYPE_CODE (type2) == TYPE_CODE_DECFLOAT)
+    {
+      *len_y = TYPE_LENGTH (type2);
+      memcpy (y, value_contents (arg2), *len_y);
+    }
+  else if (is_integral_type (type2))
+    {
+      *len_y = TYPE_LENGTH (type1);
+      decimal_from_integral (arg2, y, *len_y);
+    }
+  else
+    error (_("Don't know how to convert to decimal floating type."));
+}
 
 /* Perform a binary operation on two operands which have reasonable
    representations as integers or floats.  This includes booleans,
@@ -759,14 +814,55 @@ value_binop (struct value *arg1, struct 
   type1 = check_typedef (value_type (arg1));
   type2 = check_typedef (value_type (arg2));
 
-  if ((TYPE_CODE (type1) != TYPE_CODE_FLT && !is_integral_type (type1))
+  if ((TYPE_CODE (type1) != TYPE_CODE_FLT
+       && TYPE_CODE (type1) != TYPE_CODE_DECFLOAT && !is_integral_type (type1))
       ||
-      (TYPE_CODE (type2) != TYPE_CODE_FLT && !is_integral_type (type2)))
+      (TYPE_CODE (type2) != TYPE_CODE_FLT
+       && TYPE_CODE (type2) != TYPE_CODE_DECFLOAT && !is_integral_type (type2)))
     error (_("Argument to arithmetic operation not a number or boolean."));
 
-  if (TYPE_CODE (type1) == TYPE_CODE_FLT
+  if (TYPE_CODE (type1) == TYPE_CODE_DECFLOAT
       ||
-      TYPE_CODE (type2) == TYPE_CODE_FLT)
+      TYPE_CODE (type2) == TYPE_CODE_DECFLOAT)
+    {
+      struct type *v_type;
+      int len_v1, len_v2, len_v;
+      gdb_byte v1[16], v2[16];
+      gdb_byte v[16];
+
+      value_args_as_decimal (arg1, arg2, v1, &len_v1, v2, &len_v2);
+
+      switch (op)
+	{
+	case BINOP_ADD:
+	case BINOP_SUB:
+	case BINOP_MUL:
+	case BINOP_DIV:
+	case BINOP_EXP:
+	  decimal_binop (op, v1, len_v1, v2, len_v2, v, &len_v);
+	  break;
+
+	default:
+	  error (_("Integer-only operation on floating point number."));
+	}
+
+      if (TYPE_CODE (type1) != TYPE_CODE_DECFLOAT)
+	/* If arg1 is not a decimal float, the type of the result is the type
+	   of the decimal float argument, arg2.  */
+	v_type = type2;
+      else if (TYPE_CODE (type2) != TYPE_CODE_DECFLOAT)
+	/* Same logic, for the case where arg2 is not a decimal float.  */
+	v_type = type1;
+      else
+	/* len_v is equal either to len_v1 or to len_v2.  the type of the
+	   result is the type of the argument with the same length as v.  */
+	v_type = (len_v == len_v1)? type1 : type2;
+
+      val = value_from_decfloat (v_type, v);
+    }
+  else if (TYPE_CODE (type1) == TYPE_CODE_FLT
+	   ||
+	   TYPE_CODE (type2) == TYPE_CODE_FLT)
     {
       /* FIXME-if-picky-about-floating-accuracy: Should be doing this
          in target format.  real.c in GCC probably has the necessary
@@ -1177,6 +1273,8 @@ value_logical_not (struct value *arg1)
 
   if (TYPE_CODE (type1) == TYPE_CODE_FLT)
     return 0 == value_as_double (arg1);
+  else if (TYPE_CODE (type1) == TYPE_CODE_DECFLOAT)
+    return decimal_is_zero (value_contents (arg1), TYPE_LENGTH (type1));
 
   len = TYPE_LENGTH (type1);
   p = value_contents (arg1);
@@ -1255,6 +1353,16 @@ value_equal (struct value *arg1, struct 
       DOUBLEST d = value_as_double (arg1);
       return d == value_as_double (arg2);
     }
+  else if ((code1 == TYPE_CODE_DECFLOAT || is_int1)
+	   && (code2 == TYPE_CODE_DECFLOAT || is_int2))
+    {
+      gdb_byte v1[16], v2[16];
+      int len_v1, len_v2;
+
+      value_args_as_decimal (arg1, arg2, v1, &len_v1, v2, &len_v2);
+
+      return decimal_compare (v1, len_v1, v2, len_v2) == 0;
+    }
 
   /* FIXME: Need to promote to either CORE_ADDR or LONGEST, whichever
      is bigger.  */
@@ -1319,6 +1427,16 @@ value_less (struct value *arg1, struct v
       DOUBLEST d = value_as_double (arg1);
       return d < value_as_double (arg2);
     }
+  else if ((code1 == TYPE_CODE_DECFLOAT || is_int1)
+	   && (code2 == TYPE_CODE_DECFLOAT || is_int2))
+    {
+      gdb_byte v1[16], v2[16];
+      int len_v1, len_v2;
+
+      value_args_as_decimal (arg1, arg2, v1, &len_v1, v2, &len_v2);
+
+      return decimal_compare (v1, len_v1, v2, len_v2) == -1;
+    }
   else if (code1 == TYPE_CODE_PTR && code2 == TYPE_CODE_PTR)
     return value_as_address (arg1) < value_as_address (arg2);
 
@@ -1350,6 +1468,8 @@ value_pos (struct value *arg1)
 
   if (TYPE_CODE (type) == TYPE_CODE_FLT)
     return value_from_double (type, value_as_double (arg1));
+  else if (TYPE_CODE (type) == TYPE_CODE_DECFLOAT)
+    return value_from_decfloat (type, value_contents (arg1));
   else if (is_integral_type (type))
     {
       /* Perform integral promotion for ANSI C/C++.  FIXME: What about
@@ -1382,7 +1502,7 @@ value_neg (struct value *arg1)
       int len = TYPE_LENGTH (type);
       gdb_byte decbytes[16];  /* a decfloat is at most 128 bits long */
 
-      memcpy(decbytes, value_contents(arg1), len);
+      memcpy (decbytes, value_contents (arg1), len);
 
       if (gdbarch_byte_order (current_gdbarch) == BFD_ENDIAN_LITTLE)
 	decbytes[len-1] = decbytes[len - 1] | 0x80;
Index: src-git/gdb/valops.c
===================================================================
--- src-git.orig/gdb/valops.c	2007-12-19 16:34:35.000000000 -0200
+++ src-git/gdb/valops.c	2007-12-19 19:54:50.000000000 -0200
@@ -36,6 +36,7 @@
 #include "infcall.h"
 #include "dictionary.h"
 #include "cp-support.h"
+#include "dfp.h"
 
 #include <errno.h>
 #include "gdb_string.h"
@@ -338,7 +339,8 @@ value_cast (struct type *type, struct va
     code2 = TYPE_CODE_INT;
 
   scalar = (code2 == TYPE_CODE_INT || code2 == TYPE_CODE_FLT
-	    || code2 == TYPE_CODE_ENUM || code2 == TYPE_CODE_RANGE);
+	    || code2 == TYPE_CODE_DECFLOAT || code2 == TYPE_CODE_ENUM
+	    || code2 == TYPE_CODE_RANGE);
 
   if (code1 == TYPE_CODE_STRUCT
       && code2 == TYPE_CODE_STRUCT
@@ -357,6 +359,22 @@ value_cast (struct type *type, struct va
     }
   if (code1 == TYPE_CODE_FLT && scalar)
     return value_from_double (type, value_as_double (arg2));
+  else if (code1 == TYPE_CODE_DECFLOAT && scalar)
+    {
+      int dec_len = TYPE_LENGTH (type);
+      gdb_byte dec[16];
+
+      if (code2 == TYPE_CODE_FLT)
+	decimal_from_floating (arg2, dec, dec_len);
+      else if (code2 == TYPE_CODE_DECFLOAT)
+	decimal_convert (value_contents (arg2), TYPE_LENGTH (type2),
+			 dec, dec_len);
+      else
+	/* The only option left is an integral type.  */
+	decimal_from_integral (arg2, dec, dec_len);
+
+      return value_from_decfloat (type, dec);
+    }
   else if ((code1 == TYPE_CODE_INT || code1 == TYPE_CODE_ENUM
 	    || code1 == TYPE_CODE_RANGE)
 	   && (scalar || code2 == TYPE_CODE_PTR
Index: src-git/gdb/value.c
===================================================================
--- src-git.orig/gdb/value.c	2007-12-19 16:34:35.000000000 -0200
+++ src-git/gdb/value.c	2007-12-19 17:57:08.000000000 -0200
@@ -982,6 +982,7 @@ value_as_double (struct value *val)
     error (_("Invalid floating value found in program."));
   return foo;
 }
+
 /* Extract a value as a C pointer. Does not deallocate the value.  
    Note that val's type may not actually be a pointer; value_as_long
    handles all the cases.  */
@@ -1127,6 +1128,11 @@ unpack_long (struct type *type, const gd
     case TYPE_CODE_FLT:
       return extract_typed_floating (valaddr, type);
 
+    case TYPE_CODE_DECFLOAT:
+      /* libdecnumber has a function to convert from decimal to integer, but
+	 it doesn't work when the decimal number has a fractional part.  */
+      return decimal_to_double (valaddr, len);
+
     case TYPE_CODE_PTR:
     case TYPE_CODE_REF:
       /* Assume a CORE_ADDR can fit in a LONGEST (for now).  Not sure
@@ -1184,6 +1190,8 @@ unpack_double (struct type *type, const 
 
       return extract_typed_floating (valaddr, type);
     }
+  else if (code == TYPE_CODE_DECFLOAT)
+    return decimal_to_double (valaddr, len);
   else if (nosign)
     {
       /* Unsigned -- be sure we compensate for signed LONGEST.  */
@@ -1643,23 +1651,12 @@ value_from_double (struct type *type, DO
 }
 
 struct value *
-value_from_decfloat (struct type *expect_type, struct type *type,
-		      gdb_byte decbytes[16])
+value_from_decfloat (struct type *type, const gdb_byte *dec)
 {
   struct value *val = allocate_value (type);
-  int len = TYPE_LENGTH (type);
 
-  if (expect_type)
-    {
-      int expect_len = TYPE_LENGTH (expect_type);
-      char decstr[MAX_DECIMAL_STRING];
-      int real_len;
-
-      decimal_to_string (decbytes, len, decstr);
-      decimal_from_string (decbytes, expect_len, decstr);
-    }
+  memcpy (value_contents_raw (val), dec, TYPE_LENGTH (type));
 
-  memcpy (value_contents_raw (val), decbytes, len);
   return val;
 }
 
Index: src-git/gdb/value.h
===================================================================
--- src-git.orig/gdb/value.h	2007-12-19 16:34:35.000000000 -0200
+++ src-git/gdb/value.h	2007-12-19 16:55:05.000000000 -0200
@@ -280,9 +280,8 @@ extern void pack_long (gdb_byte *buf, st
 extern struct value *value_from_longest (struct type *type, LONGEST num);
 extern struct value *value_from_pointer (struct type *type, CORE_ADDR addr);
 extern struct value *value_from_double (struct type *type, DOUBLEST num);
-extern struct value *value_from_decfloat (struct type *expect_type,
-					  struct type *type,
-					  gdb_byte decbytes[16]);
+extern struct value *value_from_decfloat (struct type *type,
+					  const gdb_byte *decbytes);
 extern struct value *value_from_string (char *string);
 
 extern struct value *value_at (struct type *type, CORE_ADDR addr);

-- 
-- 
[]'s
Thiago Jung Bauermann
Software Engineer
IBM Linux Technology Center


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