1 /*
2  *  Cast helpers.
3  *
4  *  C99+ coercion is challenging portability-wise because out-of-range casts
5  *  may invoke implementation defined or even undefined behavior.  See e.g.
6  *  http://blog.frama-c.com/index.php?post/2013/10/09/Overflow-float-integer.
7  *
8  *  Provide explicit cast helpers which try to avoid implementation defined
9  *  or undefined behavior.  These helpers can then be simplified in the vast
10  *  majority of cases where the implementation defined or undefined behavior
11  *  is not problematic.
12  */
13 
14 #include "duk_internal.h"
15 
16 /* Portable double-to-integer cast which avoids undefined behavior and avoids
17  * relying on fmin(), fmax(), or other intrinsics.  Out-of-range results are
18  * not assumed by caller, but here value is clamped, NaN converts to minval.
19  */
20 #define DUK__DOUBLE_INT_CAST1(tname,minval,maxval)  do { \
21 		if (DUK_LIKELY(x >= (duk_double_t) (minval))) { \
22 			DUK_ASSERT(!DUK_ISNAN(x)); \
23 			if (DUK_LIKELY(x <= (duk_double_t) (maxval))) { \
24 				return (tname) x; \
25 			} else { \
26 				return (tname) (maxval); \
27 			} \
28 		} else { \
29 			/* NaN or below minval.  Since we don't care about the result \
30 			 * for out-of-range values, just return the minimum value for \
31 			 * both. \
32 			 */ \
33 			return (tname) (minval); \
34 		} \
35 	} while (0)
36 
37 /* Rely on specific NaN behavior for duk_double_{fmin,fmax}(): if either
38  * argument is a NaN, return the second argument.  This avoids a
39  * NaN-to-integer cast which is undefined behavior.
40  */
41 #define DUK__DOUBLE_INT_CAST2(tname,minval,maxval)  do { \
42 		return (tname) duk_double_fmin(duk_double_fmax(x, (duk_double_t) (minval)), (duk_double_t) (maxval)); \
43 	} while (0)
44 
45 /* Another solution which doesn't need C99+ behavior for fmin() and fmax(). */
46 #define DUK__DOUBLE_INT_CAST3(tname,minval,maxval)  do { \
47 		if (DUK_ISNAN(x)) { \
48 			/* 0 or any other value is fine. */ \
49 			return (tname) 0; \
50 		} else \
51 			return (tname) DUK_FMIN(DUK_FMAX(x, (duk_double_t) (minval)), (duk_double_t) (maxval)); \
52 		} \
53 	} while (0)
54 
55 /* C99+ solution: relies on specific fmin() and fmax() behavior in C99: if
56  * one argument is NaN but the other isn't, the non-NaN argument is returned.
57  * Because the limits are non-NaN values, explicit NaN check is not needed.
58  * This may not work on all legacy platforms, and also doesn't seem to inline
59  * the fmin() and fmax() calls (unless one uses -ffast-math which we don't
60  * support).
61  */
62 #define DUK__DOUBLE_INT_CAST4(tname,minval,maxval)  do { \
63 		return (tname) DUK_FMIN(DUK_FMAX(x, (duk_double_t) (minval)), (duk_double_t) (maxval)); \
64 	} while (0)
65 
duk_double_to_int_t(duk_double_t x)66 DUK_INTERNAL duk_int_t duk_double_to_int_t(duk_double_t x) {
67 #if defined(DUK_USE_ALLOW_UNDEFINED_BEHAVIOR)
68 	/* Real world solution: almost any practical platform will provide
69 	 * an integer value without any guarantees what it is (which is fine).
70 	 */
71 	return (duk_int_t) x;
72 #else
73 	DUK__DOUBLE_INT_CAST1(duk_int_t, DUK_INT_MIN, DUK_INT_MAX);
74 #endif
75 }
76 
duk_double_to_uint_t(duk_double_t x)77 DUK_INTERNAL duk_uint_t duk_double_to_uint_t(duk_double_t x) {
78 #if defined(DUK_USE_ALLOW_UNDEFINED_BEHAVIOR)
79 	return (duk_uint_t) x;
80 #else
81 	DUK__DOUBLE_INT_CAST1(duk_uint_t, DUK_UINT_MIN, DUK_UINT_MAX);
82 #endif
83 }
84 
duk_double_to_int32_t(duk_double_t x)85 DUK_INTERNAL duk_int32_t duk_double_to_int32_t(duk_double_t x) {
86 #if defined(DUK_USE_ALLOW_UNDEFINED_BEHAVIOR)
87 	return (duk_int32_t) x;
88 #else
89 	DUK__DOUBLE_INT_CAST1(duk_int32_t, DUK_INT32_MIN, DUK_INT32_MAX);
90 #endif
91 }
92 
duk_double_to_uint32_t(duk_double_t x)93 DUK_INTERNAL duk_uint32_t duk_double_to_uint32_t(duk_double_t x) {
94 #if defined(DUK_USE_ALLOW_UNDEFINED_BEHAVIOR)
95 	return (duk_uint32_t) x;
96 #else
97 	DUK__DOUBLE_INT_CAST1(duk_uint32_t, DUK_UINT32_MIN, DUK_UINT32_MAX);
98 #endif
99 }
100 
101 /* Largest IEEE double that doesn't round to infinity in the default rounding
102  * mode.  The exact midpoint between (1 - 2^(-24)) * 2^128 and 2^128 rounds to
103  * infinity, at least on x64.  This number is one double unit below that
104  * midpoint.  See misc/float_cast.c.
105  */
106 #define DUK__FLOAT_ROUND_LIMIT      340282356779733623858607532500980858880.0
107 
108 /* Maximum IEEE float.  Double-to-float conversion above this would be out of
109  * range and thus technically undefined behavior.
110  */
111 #define DUK__FLOAT_MAX              340282346638528859811704183484516925440.0
112 
duk_double_to_float_t(duk_double_t x)113 DUK_INTERNAL duk_float_t duk_double_to_float_t(duk_double_t x) {
114 	/* Even a double-to-float cast is technically undefined behavior if
115 	 * the double is out-of-range.  C99 Section 6.3.1.5:
116 	 *
117 	 *   If the value being converted is in the range of values that can
118 	 *   be represented but cannot be represented exactly, the result is
119 	 *   either the nearest higher or nearest lower representable value,
120 	 *   chosen in an implementation-defined manner.  If the value being
121 	 *   converted is outside the range of values that can be represented,
122 	 *   the behavior is undefined.
123 	 */
124 #if defined(DUK_USE_ALLOW_UNDEFINED_BEHAVIOR)
125 	return (duk_float_t) x;
126 #else
127 	duk_double_t t;
128 
129 	t = DUK_FABS(x);
130 	DUK_ASSERT((DUK_ISNAN(x) && DUK_ISNAN(t)) ||
131 	           (!DUK_ISNAN(x) && !DUK_ISNAN(t)));
132 
133 	if (DUK_LIKELY(t <= DUK__FLOAT_MAX)) {
134 		/* Standard in-range case, try to get here with a minimum
135 		 * number of checks and branches.
136 		 */
137 		DUK_ASSERT(!DUK_ISNAN(x));
138 		return (duk_float_t) x;
139 	} else if (t <= DUK__FLOAT_ROUND_LIMIT) {
140 		/* Out-of-range, but rounds to min/max float. */
141 		DUK_ASSERT(!DUK_ISNAN(x));
142 		if (x < 0.0) {
143 			return (duk_float_t) -DUK__FLOAT_MAX;
144 		} else {
145 			return (duk_float_t) DUK__FLOAT_MAX;
146 		}
147 	} else if (DUK_ISNAN(x)) {
148 		/* Assumes double NaN -> float NaN considered "in range". */
149 		DUK_ASSERT(DUK_ISNAN(x));
150 		return (duk_float_t) x;
151 	} else {
152 		/* Out-of-range, rounds to +/- Infinity. */
153 		if (x < 0.0) {
154 			return (duk_float_t) -DUK_DOUBLE_INFINITY;
155 		} else {
156 			return (duk_float_t) DUK_DOUBLE_INFINITY;
157 		}
158 	}
159 #endif
160 }
161