From 3b50de48485d0328874eb526e88c14a80a4886eb Mon Sep 17 00:00:00 2001 From: Charlie Gordon Date: Mon, 25 Mar 2024 08:29:04 +0100 Subject: [PATCH] Improve consistency of JS_NewFloat64 API (#319) * Improve consistency of JS_NewFloat64 API - `JS_NewFloat64()` always creates a `JS_TAG_FLOAT64` value - internal `js_float64()` always creates a `JS_TAG_FLOAT64` value - add `js_int64` internal function for consistency - rename `float_is_int32` as `double_is_int32` - handle `INT32_MIN` in `double_is_int32`, use (somewhat) faster alternative - add `js_number(d)` to create a `JS_TAG_FLOAT64` or a `JS_TAG_INT` value if possible - add `JS_NewNumber()` API for the same purpose - use non testing constructor for infinities in `js_atof2` - always store internal time value as a float64 - merge `JS_NewBigInt64_1` into `JS_NewBigInt64` - use comparisons instead of `(int32_t)` casts (implementation defined behavior) --- quickjs.c | 155 +++++++++++++++++++++++++++--------------------------- quickjs.h | 13 +++-- 2 files changed, 87 insertions(+), 81 deletions(-) diff --git a/quickjs.c b/quickjs.c index 30e14b6..8735a64 100644 --- a/quickjs.c +++ b/quickjs.c @@ -1264,40 +1264,27 @@ static const JSClassExoticMethods js_string_exotic_methods; static const JSClassExoticMethods js_proxy_exotic_methods; static const JSClassExoticMethods js_module_ns_exotic_methods; -// Special care is taken to not invoke UB when checking if the result fits -// in an int32_t. Leans on the fact that the input is integral if the lower -// 52 bits of the equation 2**e * (f + 2**52) are zero. -static BOOL float_is_int32(double d) +static inline BOOL double_is_int32(double d) { - uint64_t u, m, e, f; + uint64_t u, e; JSFloat64Union t; t.d = d; u = t.u64; - // special case -0 - m = 1ull << 63; - if (u == m) - return FALSE; - - e = (u >> 52) & 0x7FF; - if (e > 0) - e -= 1023; - - // too large, nan or inf? - if (e > 30) - return FALSE; - - // fractional or subnormal if low bits are non-zero - f = 0xFFFFFFFFFFFFFull & u; - m = 0xFFFFFFFFFFFFFull >> e; - return 0 == (f & m); + e = ((u >> 52) & 0x7FF) - 1023; + if (e > 30) { + // accept 0, INT32_MIN, reject too large, too small, nan, inf, -0 + return !u || (u == 0xc1e0000000000000); + } else { + // shift out sign, exponent and whole part bits + // value is fractional if remaining low bits are non-zero + return !(u << 12 << e); + } } static JSValue js_float64(double d) { - if (float_is_int32(d)) - return JS_MKVAL(JS_TAG_INT, (int32_t)d); return __JS_NewFloat64(d); } @@ -1315,7 +1302,31 @@ static JSValue js_uint32(uint32_t v) { if (v <= INT32_MAX) return js_int32(v); - return js_float64(v); + else + return js_float64(v); +} + +static JSValue js_int64(int64_t v) +{ + if (v >= INT32_MIN && v <= INT32_MAX) + return js_int32(v); + else + return js_float64(v); +} + +#define JS_NewInt64(ctx, val) js_int64(val) + +static JSValue js_number(double d) +{ + if (double_is_int32(d)) + return js_int32((int32_t)d); + else + return js_float64(d); +} + +JSValue JS_NewNumber(JSContext *ctx, double d) +{ + return js_number(d); } static JSValue js_bool(JS_BOOL v) @@ -3177,7 +3188,7 @@ static JSValue JS_AtomIsNumericIndex1(JSContext *ctx, JSAtom atom) /* -0 case is specific */ if (c == '0' && len == 2) { minus_zero: - return __JS_NewFloat64(-0.0); + return js_float64(-0.0); } } if (!is_num(c)) { @@ -7968,10 +7979,10 @@ static JSValue JS_GetPropertyValue(JSContext *ctx, JSValue this_obj, return JS_NewBigUint64(ctx, p->u.array.u.uint64_ptr[idx]); case JS_CLASS_FLOAT32_ARRAY: if (unlikely(idx >= p->u.array.count)) goto slow_path; - return __JS_NewFloat64(p->u.array.u.float_ptr[idx]); + return js_float64(p->u.array.u.float_ptr[idx]); case JS_CLASS_FLOAT64_ARRAY: if (unlikely(idx >= p->u.array.count)) goto slow_path; - return __JS_NewFloat64(p->u.array.u.double_ptr[idx]); + return js_float64(p->u.array.u.double_ptr[idx]); default: goto slow_path; } @@ -10294,7 +10305,7 @@ static JSValue js_atof2(JSContext *ctx, const char *str, const char **pp, double d; d = js_strtod(buf, radix, is_float); /* return int or float64 */ - val = js_float64(d); + val = js_number(d); } break; case ATOD_TYPE_BIG_INT: @@ -10503,7 +10514,7 @@ static __maybe_unused JSValue JS_ToIntegerFree(JSContext *ctx, JSValue val) } else { /* convert -0 to +0 */ d = trunc(d) + 0.0; - ret = js_float64(d); + ret = js_number(d); } } break; @@ -11763,12 +11774,7 @@ static double js_pow(double a, double b) } } -JSValue JS_NewFloat64(JSContext *ctx, double d) -{ - return js_float64(d); -} - -JSValue JS_NewBigInt64_1(JSContext *ctx, int64_t v) +JSValue JS_NewBigInt64(JSContext *ctx, int64_t v) { JSValue val; bf_t *a; @@ -11783,11 +11789,6 @@ JSValue JS_NewBigInt64_1(JSContext *ctx, int64_t v) return val; } -JSValue JS_NewBigInt64(JSContext *ctx, int64_t v) -{ - return JS_NewBigInt64_1(ctx, v); -} - JSValue JS_NewBigUint64(JSContext *ctx, uint64_t v) { JSValue val; @@ -12123,7 +12124,7 @@ static no_inline __exception int js_unary_arith_slow(JSContext *ctx, break; case OP_neg: if (v64 == 0) { - sp[-1] = __JS_NewFloat64(-0.0); + sp[-1] = js_float64(-0.0); return 0; } else { v64 = -v64; @@ -12157,7 +12158,7 @@ static no_inline __exception int js_unary_arith_slow(JSContext *ctx, default: abort(); } - sp[-1] = __JS_NewFloat64(d); + sp[-1] = js_float64(d); } break; } @@ -12350,23 +12351,23 @@ static no_inline __exception int js_binary_arith_slow(JSContext *ctx, JSValue *s case OP_mul: v = (int64_t)v1 * (int64_t)v2; if (v == 0 && (v1 | v2) < 0) { - sp[-2] = __JS_NewFloat64(-0.0); + sp[-2] = js_float64(-0.0); return 0; } break; case OP_div: - sp[-2] = __JS_NewFloat64((double)v1 / (double)v2); + sp[-2] = js_float64((double)v1 / (double)v2); return 0; case OP_mod: if (v1 < 0 || v2 <= 0) { - sp[-2] = js_float64(fmod(v1, v2)); + sp[-2] = js_number(fmod(v1, v2)); return 0; } else { v = (int64_t)v1 % (int64_t)v2; } break; case OP_pow: - sp[-2] = js_float64(js_pow(v1, v2)); + sp[-2] = js_number(js_pow(v1, v2)); return 0; default: abort(); @@ -12404,7 +12405,7 @@ static no_inline __exception int js_binary_arith_slow(JSContext *ctx, JSValue *s default: abort(); } - sp[-2] = __JS_NewFloat64(dr); + sp[-2] = js_float64(dr); } return 0; exception: @@ -12428,7 +12429,7 @@ static no_inline __exception int js_add_slow(JSContext *ctx, JSValue *sp) double d1, d2; d1 = JS_VALUE_GET_FLOAT64(op1); d2 = JS_VALUE_GET_FLOAT64(op2); - sp[-2] = __JS_NewFloat64(d1 + d2); + sp[-2] = js_float64(d1 + d2); return 0; } @@ -12487,7 +12488,7 @@ static no_inline __exception int js_add_slow(JSContext *ctx, JSValue *sp) } if (JS_ToFloat64Free(ctx, &d2, op2)) goto exception; - sp[-2] = __JS_NewFloat64(d1 + d2); + sp[-2] = js_float64(d1 + d2); } return 0; exception: @@ -14404,7 +14405,7 @@ static JSValue js_call_c_function(JSContext *ctx, JSValue func_obj, ret_val = JS_EXCEPTION; break; } - ret_val = js_float64(func.f_f(d1)); + ret_val = js_number(func.f_f(d1)); } break; case JS_CFUNC_f_f_f: @@ -14419,7 +14420,7 @@ static JSValue js_call_c_function(JSContext *ctx, JSValue func_obj, ret_val = JS_EXCEPTION; break; } - ret_val = js_float64(func.f_f_f(d1, d2)); + ret_val = js_number(func.f_f_f(d1, d2)); } break; case JS_CFUNC_iterator_next: @@ -16308,8 +16309,8 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValue func_obj, sp[-2] = js_int32(r); sp--; } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2)) { - sp[-2] = __JS_NewFloat64(JS_VALUE_GET_FLOAT64(op1) + - JS_VALUE_GET_FLOAT64(op2)); + sp[-2] = js_float64(JS_VALUE_GET_FLOAT64(op1) + + JS_VALUE_GET_FLOAT64(op2)); sp--; } else { add_slow: @@ -16374,8 +16375,8 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValue func_obj, sp[-2] = js_int32(r); sp--; } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2)) { - sp[-2] = __JS_NewFloat64(JS_VALUE_GET_FLOAT64(op1) - - JS_VALUE_GET_FLOAT64(op2)); + sp[-2] = js_float64(JS_VALUE_GET_FLOAT64(op1) - + JS_VALUE_GET_FLOAT64(op2)); sp--; } else { goto binary_arith_slow; @@ -16408,7 +16409,7 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValue func_obj, } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2)) { d = JS_VALUE_GET_FLOAT64(op1) * JS_VALUE_GET_FLOAT64(op2); mul_fp_res: - sp[-2] = __JS_NewFloat64(d); + sp[-2] = js_float64(d); sp--; } else { goto binary_arith_slow; @@ -16424,7 +16425,7 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValue func_obj, int v1, v2; v1 = JS_VALUE_GET_INT(op1); v2 = JS_VALUE_GET_INT(op2); - sp[-2] = js_float64((double)v1 / (double)v2); + sp[-2] = js_number((double)v1 / (double)v2); sp--; } else { goto binary_arith_slow; @@ -16495,7 +16496,7 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValue func_obj, } else if (JS_TAG_IS_FLOAT64(tag)) { d = -JS_VALUE_GET_FLOAT64(op1); neg_fp_res: - sp[-1] = __JS_NewFloat64(d); + sp[-1] = js_float64(d); } else { if (js_unary_arith_slow(ctx, sp, opcode)) goto exception; @@ -34297,7 +34298,7 @@ static JSValue JS_ReadObjectRec(BCReaderState *s) if (bc_get_u64(s, &u.u64)) return JS_EXCEPTION; bc_read_trace(s, "%g\n", u.d); - obj = __JS_NewFloat64(u.d); + obj = js_float64(u.d); } break; case BC_TAG_STRING: @@ -34600,7 +34601,7 @@ static int JS_InstantiateFunctionListItem(JSContext *ctx, JSValue obj, val = JS_NewInt64(ctx, e->u.i64); break; case JS_DEF_PROP_DOUBLE: - val = __JS_NewFloat64(e->u.f64); + val = js_float64(e->u.f64); break; case JS_DEF_PROP_UNDEFINED: val = JS_UNDEFINED; @@ -34664,7 +34665,7 @@ int JS_SetModuleExportList(JSContext *ctx, JSModuleDef *m, val = JS_NewInt64(ctx, e->u.i64); break; case JS_DEF_PROP_DOUBLE: - val = __JS_NewFloat64(e->u.f64); + val = js_float64(e->u.f64); break; case JS_DEF_OBJECT: val = JS_NewObject(ctx); @@ -36262,7 +36263,7 @@ static JSValue js_function_bind(JSContext *ctx, JSValue this_val, else d -= (double)arg_count; /* also converts -0 to +0 */ } - len_val = js_float64(d); + len_val = js_number(d); } else { JS_FreeValue(ctx, len_val); len_val = js_int32(0); @@ -38670,7 +38671,7 @@ static JSValue js_number_constructor(JSContext *ctx, JSValue new_target, double d; bf_get_float64(&p->num, &d, BF_RNDN); JS_FreeValue(ctx, val); - val = __JS_NewFloat64(d); + val = js_float64(d); } break; default: @@ -38826,7 +38827,7 @@ static JSValue js_number_toFixed(JSContext *ctx, JSValue this_val, if (f < 0 || f > 100) return JS_ThrowRangeError(ctx, "invalid number of digits"); if (fabs(d) >= 1e21) { - return JS_ToStringFree(ctx, __JS_NewFloat64(d)); + return JS_ToStringFree(ctx, js_float64(d)); } else { return js_dtoa(ctx, d, 10, f, JS_DTOA_FRAC_FORMAT); } @@ -38847,7 +38848,7 @@ static JSValue js_number_toExponential(JSContext *ctx, JSValue this_val, if (JS_ToInt32Sat(ctx, &f, argv[0])) return JS_EXCEPTION; if (!isfinite(d)) { - return JS_ToStringFree(ctx, __JS_NewFloat64(d)); + return JS_ToStringFree(ctx, js_float64(d)); } if (JS_IsUndefined(argv[0])) { flags = 0; @@ -38879,7 +38880,7 @@ static JSValue js_number_toPrecision(JSContext *ctx, JSValue this_val, return JS_EXCEPTION; if (!isfinite(d)) { to_string: - return JS_ToStringFree(ctx, __JS_NewFloat64(d)); + return JS_ToStringFree(ctx, js_float64(d)); } if (p < 1 || p > 100) return JS_ThrowRangeError(ctx, "invalid number of digits"); @@ -40740,7 +40741,7 @@ static JSValue js_math_min_max(JSContext *ctx, JSValue this_val, uint32_t tag; if (unlikely(argc == 0)) { - return __JS_NewFloat64(is_max ? NEG_INF : INF); + return js_float64(is_max ? NEG_INF : INF); } tag = JS_VALUE_GET_TAG(argv[0]); @@ -40780,7 +40781,7 @@ static JSValue js_math_min_max(JSContext *ctx, JSValue this_val, } i++; } - return js_float64(r); + return js_number(r); } } @@ -40909,7 +40910,7 @@ static JSValue js_math_random(JSContext *ctx, JSValue this_val, v = xorshift64star(&ctx->random_state); /* 1.0 <= u.d < 2 */ u.u64 = ((uint64_t)0x3ff << 52) | (v >> 12); - return __JS_NewFloat64(u.d - 1.0); + return js_float64(u.d - 1.0); } static const JSCFunctionListEntry js_math_funcs[] = { @@ -47150,7 +47151,7 @@ static JSValue get_date_field(JSContext *ctx, JSValue this_val, if (magic & 0x100) { // getYear fields[0] -= 1900; } - return js_float64(fields[n]); + return js_number(fields[n]); } static JSValue set_date_field(JSContext *ctx, JSValue this_val, @@ -49242,9 +49243,9 @@ static JSValue js_typed_array_at(JSContext *ctx, JSValue this_val, case JS_CLASS_UINT32_ARRAY: return js_uint32(p->u.array.u.uint32_ptr[idx]); case JS_CLASS_FLOAT32_ARRAY: - return __JS_NewFloat64(p->u.array.u.float_ptr[idx]); + return js_float64(p->u.array.u.float_ptr[idx]); case JS_CLASS_FLOAT64_ARRAY: - return __JS_NewFloat64(p->u.array.u.double_ptr[idx]); + return js_float64(p->u.array.u.double_ptr[idx]); case JS_CLASS_BIG_INT64_ARRAY: return JS_NewBigInt64(ctx, p->u.array.u.int64_ptr[idx]); case JS_CLASS_BIG_UINT64_ARRAY: @@ -50256,11 +50257,11 @@ static JSValue js_TA_get_uint64(JSContext *ctx, const void *a) { } static JSValue js_TA_get_float32(JSContext *ctx, const void *a) { - return __JS_NewFloat64(*(const float *)a); + return js_float64(*(const float *)a); } static JSValue js_TA_get_float64(JSContext *ctx, const void *a) { - return __JS_NewFloat64(*(const double *)a); + return js_float64(*(const double *)a); } struct TA_sort_context { @@ -50943,7 +50944,7 @@ static JSValue js_dataview_getValue(JSContext *ctx, if (is_swap) v = bswap32(v); u.i = v; - return __JS_NewFloat64(u.f); + return js_float64(u.f); } case JS_CLASS_FLOAT64_ARRAY: { @@ -50954,7 +50955,7 @@ static JSValue js_dataview_getValue(JSContext *ctx, u.i = get_u64(ptr); if (is_swap) u.i = bswap64(u.i); - return __JS_NewFloat64(u.f); + return js_float64(u.f); } default: abort(); diff --git a/quickjs.h b/quickjs.h index 639f0d8..4be4776 100644 --- a/quickjs.h +++ b/quickjs.h @@ -473,6 +473,11 @@ static js_force_inline JSValue JS_NewInt32(JSContext *ctx, int32_t val) return JS_MKVAL(JS_TAG_INT, val); } +static js_force_inline JSValue JS_NewFloat64(JSContext *ctx, double val) +{ + return __JS_NewFloat64(val); +} + static js_force_inline JSValue JS_NewCatchOffset(JSContext *ctx, int32_t val) { return JS_MKVAL(JS_TAG_CATCH_OFFSET, val); @@ -481,10 +486,10 @@ static js_force_inline JSValue JS_NewCatchOffset(JSContext *ctx, int32_t val) static js_force_inline JSValue JS_NewInt64(JSContext *ctx, int64_t val) { JSValue v; - if (val == (int32_t)val) { + if (val >= INT32_MIN && val <= INT32_MAX) { v = JS_NewInt32(ctx, val); } else { - v = __JS_NewFloat64(val); + v = JS_NewFloat64(ctx, val); } return v; } @@ -495,12 +500,12 @@ static js_force_inline JSValue JS_NewUint32(JSContext *ctx, uint32_t val) if (val <= 0x7fffffff) { v = JS_NewInt32(ctx, val); } else { - v = __JS_NewFloat64(val); + v = JS_NewFloat64(ctx, val); } return v; } -JS_EXTERN JSValue JS_NewFloat64(JSContext *ctx, double d); +JS_EXTERN JSValue JS_NewNumber(JSContext *ctx, double d); JS_EXTERN JSValue JS_NewBigInt64(JSContext *ctx, int64_t v); JS_EXTERN JSValue JS_NewBigUint64(JSContext *ctx, uint64_t v);