diff mbox

Implement -fsanitize=float-cast-overflow

Message ID 20140516073654.GS10386@tucnak.redhat.com
State New
Headers show

Commit Message

Jakub Jelinek May 16, 2014, 7:36 a.m. UTC
On Thu, May 15, 2014 at 09:29:44PM +0000, Joseph S. Myers wrote:
> On Thu, 15 May 2014, Jakub Jelinek wrote:
>
> > But I think we can't use decimal_real_from_string, we'd need a variant
> > of that function that would allow specification of the rounding mode
>
> My point is that you can use "%.*RUe" or "%.*RDe" formats (for max and min
> respectively), with an appropriate precision, and let MPFR do the rounding
> to an appropriate number of decimal digits in the right direction (to
> produce a value that's exactly representable in the relevant DFP type, as
> long as it's in range).

You are right, that seems to work.  So new incremental patch.

BTW, for IBM long double
__int128_t f3 (long double x) { return x; }
the
u>= { -170141183460469231731687303715884105728.0 + -4194304.0 }
check is actually imprecise, while all correct long double values will be
properly accepted, if high double is exactly
-170141183460469231731687303715884105728.0 and low double is in
[-1.0, -4194304.0), then the unspecified conversion will not be
diagnosed, but I'm afraid there is nothing we can (easily) do about it,
because { -170141183460469231731687303715884105728.0 + -1.0 }, while
representable in IBM long double, is not representable in our
REAL_VALUE_TYPE, because we assume fixed precision, while IBM long double
has variable.  Guess the combination of IBM long double and __int128_t
(for long long it is still ok) is rare enough that we don't need to care
about it (alternative would be e.g. to compare against addition of
those two numbers with some optimization barrier/volatile that would
avoid it from being optimized into a single long double number.



	Jakub

Comments

Joseph Myers May 16, 2014, 3:54 p.m. UTC | #1
On Fri, 16 May 2014, Jakub Jelinek wrote:

> has variable.  Guess the combination of IBM long double and __int128_t
> (for long long it is still ok) is rare enough that we don't need to care
> about it (alternative would be e.g. to compare against addition of

There are existing problems with that combination anyway.  
fp-int-convert-timode.c has tests disabled for it because the compile-time 
and runtime conversions don't agree (whether for this issue or some other, 
I don't know).
diff mbox

Patch

--- gcc/convert.c
+++ gcc/convert.c
@@ -851,6 +851,8 @@ 
 	  expr = save_expr (expr);
 	  tree check = ubsan_instrument_float_cast (loc, type, expr);
 	  expr = build1 (FIX_TRUNC_EXPR, type, expr);
+	  if (check == NULL)
+	    return expr;
 	  return fold_build2 (COMPOUND_EXPR, TREE_TYPE (expr), check, expr);
 	}
       else
--- gcc/ubsan.c
+++ gcc/ubsan.c
@@ -47,6 +47,8 @@ 
 #include "asan.h"
 #include "gimplify-me.h"
 #include "intl.h"
+#include "realmpfr.h"
+#include "dfp.h"
 
 /* Map from a tree to a VAR_DECL tree.  */
 
@@ -903,17 +905,95 @@ 
 ubsan_instrument_float_cast (location_t loc, tree type, tree expr)
 {
   tree expr_type = TREE_TYPE (expr);
-  tree t, tt, fn;
+  tree t, tt, fn, min, max;
+  enum machine_mode mode = TYPE_MODE (expr_type);
+  int prec = TYPE_PRECISION (type);
+  bool uns_p = TYPE_UNSIGNED (type);
 
-  tree min = TYPE_MIN_VALUE (type);
-  tree max = TYPE_MAX_VALUE (type);
-  /* Add/subtract 1.0 so we can avoid truncating the value of EXPR.  */
-  min = fold_build2 (MINUS_EXPR, expr_type,
-		     build_real_from_int_cst (expr_type, min),
-		     build_one_cst (expr_type));
-  max = fold_build2 (PLUS_EXPR, expr_type,
-		     build_real_from_int_cst (expr_type, max),
-		     build_one_cst (expr_type));
+  /* Float to integer conversion first truncates toward zero, so
+     even signed char c = 127.875f; is not problematic.
+     Therefore, we should complain only if EXPR is unordered or smaller
+     or equal than TYPE_MIN_VALUE - 1.0 or greater or equal than
+     TYPE_MAX_VALUE + 1.0.  */
+  if (REAL_MODE_FORMAT (mode)->b == 2)
+    {
+      /* For maximum, TYPE_MAX_VALUE might not be representable
+	 in EXPR_TYPE, e.g. if TYPE is 64-bit long long and
+	 EXPR_TYPE is IEEE single float, but TYPE_MAX_VALUE + 1.0 is
+	 either representable or infinity.  */
+      REAL_VALUE_TYPE maxval = dconst1;
+      SET_REAL_EXP (&maxval, REAL_EXP (&maxval) + prec - !uns_p);
+      real_convert (&maxval, mode, &maxval);
+      max = build_real (expr_type, maxval);
+
+      /* For unsigned, assume -1.0 is always representable.  */
+      if (uns_p)
+	min = build_minus_one_cst (expr_type);
+      else
+	{
+	  /* TYPE_MIN_VALUE is generally representable (or -inf),
+	     but TYPE_MIN_VALUE - 1.0 might not be.  */
+	  REAL_VALUE_TYPE minval = dconstm1, minval2;
+	  SET_REAL_EXP (&minval, REAL_EXP (&minval) + prec - 1);
+	  real_convert (&minval, mode, &minval);
+	  real_arithmetic (&minval2, MINUS_EXPR, &minval, &dconst1);
+	  real_convert (&minval2, mode, &minval2);
+	  if (real_compare (EQ_EXPR, &minval, &minval2)
+	      && !real_isinf (&minval))
+	    {
+	      /* If TYPE_MIN_VALUE - 1.0 is not representable and
+		 rounds to TYPE_MIN_VALUE, we need to subtract
+		 more.  As REAL_MODE_FORMAT (mode)->p is the number
+		 of base digits, we want to subtract a number that
+		 will be 1 << (REAL_MODE_FORMAT (mode)->p - 1)
+		 times smaller than minval.  */
+	      minval2 = dconst1;
+	      gcc_assert (prec > REAL_MODE_FORMAT (mode)->p);
+	      SET_REAL_EXP (&minval2,
+			    REAL_EXP (&minval2) + prec - 1
+			    - REAL_MODE_FORMAT (mode)->p + 1);
+	      real_arithmetic (&minval2, MINUS_EXPR, &minval, &minval2);
+	      real_convert (&minval2, mode, &minval2);
+	    }
+	  min = build_real (expr_type, minval2);
+	}
+    }
+  else if (REAL_MODE_FORMAT (mode)->b == 10)
+    {
+      /* For _Decimal128 up to 34 decimal digits, - sign,
+	 dot, e, exponent.  */
+      char buf[64];
+      mpfr_t m;
+      int p = REAL_MODE_FORMAT (mode)->p;
+      REAL_VALUE_TYPE maxval, minval;
+
+      /* Use mpfr_snprintf rounding to compute the smallest
+	 representable decimal number greater or equal than
+	 1 << (prec - !uns_p).  */
+      mpfr_init2 (m, prec + 2);
+      mpfr_set_ui_2exp (m, 1, prec - !uns_p, GMP_RNDN);
+      mpfr_snprintf (buf, sizeof buf, "%.*RUe", p - 1, m);
+      decimal_real_from_string (&maxval, buf);
+      max = build_real (expr_type, maxval);
+
+      /* For unsigned, assume -1.0 is always representable.  */
+      if (uns_p)
+	min = build_minus_one_cst (expr_type);
+      else
+	{
+	  /* Use mpfr_snprintf rounding to compute the largest
+	     representable decimal number less or equal than
+	     (-1 << (prec - 1)) - 1.  */
+	  mpfr_set_si_2exp (m, -1, prec - 1, GMP_RNDN);
+	  mpfr_sub_ui (m, m, 1, GMP_RNDN);
+	  mpfr_snprintf (buf, sizeof buf, "%.*RDe", p - 1, m);
+	  decimal_real_from_string (&minval, buf);
+	  min = build_real (expr_type, minval);
+	}
+      mpfr_clear (m);
+    }
+  else
+    return NULL_TREE;
 
   if (flag_sanitize_undefined_trap_on_error)
     fn = build_call_expr_loc (loc, builtin_decl_explicit (BUILT_IN_TRAP), 0);