diff --git a/quickjs.c b/quickjs.c index 27a00b0..8ced5c2 100644 --- a/quickjs.c +++ b/quickjs.c @@ -1239,6 +1239,43 @@ 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) +{ + uint64_t u, m, e, f; + 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); +} + +static JSValue js_float64(double d) +{ + if (float_is_int32(d)) + return JS_MKVAL(JS_TAG_INT, (int32_t)d); + return __JS_NewFloat64(d); +} + static int compare_u32(uint32_t a, uint32_t b) { return -(a < b) + (b < a); // -1, 0 or 1 @@ -3103,7 +3140,7 @@ static JSValue JS_AtomIsNumericIndex1(JSContext *ctx, JSAtom atom) /* -0 case is specific */ if (c == '0' && len == 2) { minus_zero: - return __JS_NewFloat64(ctx, -0.0); + return __JS_NewFloat64(-0.0); } } if (!is_num(c)) { @@ -7775,10 +7812,10 @@ static JSValue JS_GetPropertyValue(JSContext *ctx, JSValueConst 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(ctx, p->u.array.u.float_ptr[idx]); + return __JS_NewFloat64(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(ctx, p->u.array.u.double_ptr[idx]); + return __JS_NewFloat64(p->u.array.u.double_ptr[idx]); default: goto slow_path; } @@ -10088,7 +10125,7 @@ static JSValue js_atof2(JSContext *ctx, const char *str, const char **pp, double d = 1.0 / 0.0; if (is_neg) d = -d; - val = JS_NewFloat64(ctx, d); + val = js_float64(d); goto done; } } @@ -10168,7 +10205,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_NewFloat64(ctx, d); + val = js_float64(d); } break; case ATOD_TYPE_BIG_INT: @@ -10377,7 +10414,7 @@ static __maybe_unused JSValue JS_ToIntegerFree(JSContext *ctx, JSValue val) } else { /* convert -0 to +0 */ d = trunc(d) + 0.0; - ret = JS_NewFloat64(ctx, d); + ret = js_float64(d); } } break; @@ -11554,43 +11591,9 @@ static double js_pow(double a, double b) } } -// 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) -{ - uint64_t u, m, e, f; - 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); -} - JSValue JS_NewFloat64(JSContext *ctx, double d) { - if (float_is_int32(d)) { - return JS_MKVAL(JS_TAG_INT, (int32_t)d); - } else { - return __JS_NewFloat64(ctx, d); - } + return js_float64(d); } JSValue JS_NewBigInt64_1(JSContext *ctx, int64_t v) @@ -11960,7 +11963,7 @@ static no_inline __exception int js_unary_arith_slow(JSContext *ctx, break; case OP_neg: if (v64 == 0) { - sp[-1] = __JS_NewFloat64(ctx, -0.0); + sp[-1] = __JS_NewFloat64(-0.0); return 0; } else { v64 = -v64; @@ -11994,7 +11997,7 @@ static no_inline __exception int js_unary_arith_slow(JSContext *ctx, default: abort(); } - sp[-1] = __JS_NewFloat64(ctx, d); + sp[-1] = __JS_NewFloat64(d); } break; } @@ -12189,23 +12192,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(ctx, -0.0); + sp[-2] = __JS_NewFloat64(-0.0); return 0; } break; case OP_div: - sp[-2] = __JS_NewFloat64(ctx, (double)v1 / (double)v2); + sp[-2] = __JS_NewFloat64((double)v1 / (double)v2); return 0; case OP_mod: if (v1 < 0 || v2 <= 0) { - sp[-2] = JS_NewFloat64(ctx, fmod(v1, v2)); + sp[-2] = js_float64(fmod(v1, v2)); return 0; } else { v = (int64_t)v1 % (int64_t)v2; } break; case OP_pow: - sp[-2] = JS_NewFloat64(ctx, js_pow(v1, v2)); + sp[-2] = js_float64(js_pow(v1, v2)); return 0; default: abort(); @@ -12243,7 +12246,7 @@ static no_inline __exception int js_binary_arith_slow(JSContext *ctx, JSValue *s default: abort(); } - sp[-2] = __JS_NewFloat64(ctx, dr); + sp[-2] = __JS_NewFloat64(dr); } return 0; exception: @@ -12267,7 +12270,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(ctx, d1 + d2); + sp[-2] = __JS_NewFloat64(d1 + d2); return 0; } @@ -12326,7 +12329,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(ctx, d1 + d2); + sp[-2] = __JS_NewFloat64(d1 + d2); } return 0; exception: @@ -14241,7 +14244,7 @@ static JSValue js_call_c_function(JSContext *ctx, JSValueConst func_obj, ret_val = JS_EXCEPTION; break; } - ret_val = JS_NewFloat64(ctx, func.f_f(d1)); + ret_val = js_float64(func.f_f(d1)); } break; case JS_CFUNC_f_f_f: @@ -14256,7 +14259,7 @@ static JSValue js_call_c_function(JSContext *ctx, JSValueConst func_obj, ret_val = JS_EXCEPTION; break; } - ret_val = JS_NewFloat64(ctx, func.f_f_f(d1, d2)); + ret_val = js_float64(func.f_f_f(d1, d2)); } break; case JS_CFUNC_iterator_next: @@ -16119,7 +16122,7 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, sp[-2] = js_int32(r); sp--; } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2)) { - sp[-2] = __JS_NewFloat64(ctx, JS_VALUE_GET_FLOAT64(op1) + + sp[-2] = __JS_NewFloat64(JS_VALUE_GET_FLOAT64(op1) + JS_VALUE_GET_FLOAT64(op2)); sp--; } else { @@ -16184,7 +16187,7 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, sp[-2] = js_int32(r); sp--; } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2)) { - sp[-2] = __JS_NewFloat64(ctx, JS_VALUE_GET_FLOAT64(op1) - + sp[-2] = __JS_NewFloat64(JS_VALUE_GET_FLOAT64(op1) - JS_VALUE_GET_FLOAT64(op2)); sp--; } else { @@ -16218,7 +16221,7 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst 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(ctx, d); + sp[-2] = __JS_NewFloat64(d); sp--; } else { goto binary_arith_slow; @@ -16234,7 +16237,7 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, int v1, v2; v1 = JS_VALUE_GET_INT(op1); v2 = JS_VALUE_GET_INT(op2); - sp[-2] = JS_NewFloat64(ctx, (double)v1 / (double)v2); + sp[-2] = js_float64((double)v1 / (double)v2); sp--; } else { goto binary_arith_slow; @@ -16305,7 +16308,7 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, } else if (JS_TAG_IS_FLOAT64(tag)) { d = -JS_VALUE_GET_FLOAT64(op1); neg_fp_res: - sp[-1] = __JS_NewFloat64(ctx, d); + sp[-1] = __JS_NewFloat64(d); } else { if (js_unary_arith_slow(ctx, sp, opcode)) goto exception; @@ -33698,7 +33701,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(ctx, u.d); + obj = __JS_NewFloat64(u.d); } break; case BC_TAG_STRING: @@ -34004,7 +34007,7 @@ static int JS_InstantiateFunctionListItem(JSContext *ctx, JSValueConst obj, val = JS_NewInt64(ctx, e->u.i64); break; case JS_DEF_PROP_DOUBLE: - val = __JS_NewFloat64(ctx, e->u.f64); + val = __JS_NewFloat64(e->u.f64); break; case JS_DEF_PROP_UNDEFINED: val = JS_UNDEFINED; @@ -34068,7 +34071,7 @@ int JS_SetModuleExportList(JSContext *ctx, JSModuleDef *m, val = JS_NewInt64(ctx, e->u.i64); break; case JS_DEF_PROP_DOUBLE: - val = __JS_NewFloat64(ctx, e->u.f64); + val = __JS_NewFloat64(e->u.f64); break; case JS_DEF_OBJECT: val = JS_NewObject(ctx); @@ -35676,7 +35679,7 @@ static JSValue js_function_bind(JSContext *ctx, JSValueConst this_val, else d -= (double)arg_count; /* also converts -0 to +0 */ } - len_val = JS_NewFloat64(ctx, d); + len_val = js_float64(d); } else { JS_FreeValue(ctx, len_val); len_val = js_int32(0); @@ -38040,7 +38043,7 @@ static JSValue js_number_constructor(JSContext *ctx, JSValueConst new_target, double d; bf_get_float64(&p->num, &d, BF_RNDN); JS_FreeValue(ctx, val); - val = __JS_NewFloat64(ctx, d); + val = __JS_NewFloat64(d); } break; default: @@ -38188,7 +38191,7 @@ static JSValue js_number_toFixed(JSContext *ctx, JSValueConst 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(ctx, d)); + return JS_ToStringFree(ctx, __JS_NewFloat64(d)); } else { return js_dtoa(ctx, d, 10, f, JS_DTOA_FRAC_FORMAT); } @@ -38209,7 +38212,7 @@ static JSValue js_number_toExponential(JSContext *ctx, JSValueConst this_val, if (JS_ToInt32Sat(ctx, &f, argv[0])) return JS_EXCEPTION; if (!isfinite(d)) { - return JS_ToStringFree(ctx, __JS_NewFloat64(ctx, d)); + return JS_ToStringFree(ctx, __JS_NewFloat64(d)); } if (JS_IsUndefined(argv[0])) { flags = 0; @@ -38241,7 +38244,7 @@ static JSValue js_number_toPrecision(JSContext *ctx, JSValueConst this_val, return JS_EXCEPTION; if (!isfinite(d)) { to_string: - return JS_ToStringFree(ctx, __JS_NewFloat64(ctx, d)); + return JS_ToStringFree(ctx, __JS_NewFloat64(d)); } if (p < 1 || p > 100) return JS_ThrowRangeError(ctx, "invalid number of digits"); @@ -40109,7 +40112,7 @@ static JSValue js_math_min_max(JSContext *ctx, JSValueConst this_val, uint32_t tag; if (unlikely(argc == 0)) { - return __JS_NewFloat64(ctx, is_max ? -1.0 / 0.0 : 1.0 / 0.0); + return __JS_NewFloat64(is_max ? -1.0 / 0.0 : 1.0 / 0.0); } tag = JS_VALUE_GET_TAG(argv[0]); @@ -40149,7 +40152,7 @@ static JSValue js_math_min_max(JSContext *ctx, JSValueConst this_val, } i++; } - return JS_NewFloat64(ctx, r); + return js_float64(r); } } @@ -40212,7 +40215,7 @@ static JSValue js_math_hypot(JSContext *ctx, JSValueConst this_val, } } } - return JS_NewFloat64(ctx, r); + return js_float64(r); } static double js_math_fround(double a) @@ -40280,7 +40283,7 @@ static JSValue js_math_random(JSContext *ctx, JSValueConst this_val, v = xorshift64star(&ctx->random_state); /* 1.0 <= u.d < 2 */ u.u64 = ((uint64_t)0x3ff << 52) | (v >> 12); - return __JS_NewFloat64(ctx, u.d - 1.0); + return __JS_NewFloat64(u.d - 1.0); } static const JSCFunctionListEntry js_math_funcs[] = { @@ -46382,7 +46385,7 @@ static JSValue JS_SetThisTimeValue(JSContext *ctx, JSValueConst this_val, double JSObject *p = JS_VALUE_GET_OBJ(this_val); if (p->class_id == JS_CLASS_DATE) { JS_FreeValue(ctx, p->u.object_data); - p->u.object_data = JS_NewFloat64(ctx, v); + p->u.object_data = js_float64(v); return JS_DupValue(ctx, p->u.object_data); } } @@ -46532,7 +46535,7 @@ static JSValue get_date_field(JSContext *ctx, JSValueConst this_val, if (magic & 0x100) { // getYear fields[0] -= 1900; } - return JS_NewFloat64(ctx, fields[n]); + return js_float64(fields[n]); } static JSValue set_date_field(JSContext *ctx, JSValueConst this_val, @@ -46746,7 +46749,7 @@ static JSValue js_date_constructor(JSContext *ctx, JSValueConst new_target, has_val: rv = js_create_from_ctor(ctx, new_target, JS_CLASS_DATE); if (!JS_IsException(rv)) - JS_SetObjectData(ctx, rv, JS_NewFloat64(ctx, val)); + JS_SetObjectData(ctx, rv, js_float64(val)); if (!JS_IsException(rv) && JS_IsUndefined(new_target)) { /* invoked as a function, return (new Date()).toString(); */ JSValue s; @@ -46779,7 +46782,7 @@ static JSValue js_Date_UTC(JSContext *ctx, JSValueConst this_val, if (i == 0 && fields[0] >= 0 && fields[0] < 100) fields[0] += 1900; } - return JS_NewFloat64(ctx, set_date_fields(fields, 0)); + return js_float64(set_date_fields(fields, 0)); } static void string_skip_spaces(JSString *sp, int *pp) { @@ -47066,7 +47069,7 @@ static JSValue js_Date_parse(JSContext *ctx, JSValueConst this_val, for(i = 0; i < 7; i++) fields1[i] = fields[i]; d = set_date_fields(fields1, is_local) - tz * 60000; - rv = JS_NewFloat64(ctx, d); + rv = js_float64(d); done: JS_FreeValue(ctx, s); @@ -47134,7 +47137,7 @@ static JSValue js_date_getTime(JSContext *ctx, JSValueConst this_val, if (JS_ThisTimeValue(ctx, &v, this_val)) return JS_EXCEPTION; - return JS_NewFloat64(ctx, v); + return js_float64(v); } static JSValue js_date_setTime(JSContext *ctx, JSValueConst this_val, @@ -47163,7 +47166,7 @@ static JSValue js_date_setYear(JSContext *ctx, JSValueConst this_val, if (y >= 0 && y < 100) y += 1900; } - args[0] = JS_NewFloat64(ctx, y); + args[0] = js_float64(y); return set_date_field(ctx, this_val, 1, args, 0x011); } @@ -47266,7 +47269,7 @@ JSValue JS_NewDate(JSContext *ctx, double epoch_ms) JSValue obj = js_create_from_ctor(ctx, JS_UNDEFINED, JS_CLASS_DATE); if (JS_IsException(obj)) return JS_EXCEPTION; - JS_SetObjectData(ctx, obj, JS_NewFloat64(ctx, time_clip(epoch_ms))); + JS_SetObjectData(ctx, obj, js_float64(time_clip(epoch_ms))); return obj; } @@ -48435,9 +48438,9 @@ static JSValue js_typed_array_at(JSContext *ctx, JSValueConst this_val, case JS_CLASS_UINT32_ARRAY: return JS_NewUint32(ctx, p->u.array.u.uint32_ptr[idx]); case JS_CLASS_FLOAT32_ARRAY: - return __JS_NewFloat64(ctx, p->u.array.u.float_ptr[idx]); + return __JS_NewFloat64(p->u.array.u.float_ptr[idx]); case JS_CLASS_FLOAT64_ARRAY: - return __JS_NewFloat64(ctx, p->u.array.u.double_ptr[idx]); + return __JS_NewFloat64(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: @@ -49394,11 +49397,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(ctx, *(const float *)a); + return __JS_NewFloat64(*(const float *)a); } static JSValue js_TA_get_float64(JSContext *ctx, const void *a) { - return __JS_NewFloat64(ctx, *(const double *)a); + return __JS_NewFloat64(*(const double *)a); } struct TA_sort_context { @@ -50067,7 +50070,7 @@ static JSValue js_dataview_getValue(JSContext *ctx, if (is_swap) v = bswap32(v); u.i = v; - return __JS_NewFloat64(ctx, u.f); + return __JS_NewFloat64(u.f); } case JS_CLASS_FLOAT64_ARRAY: { @@ -50078,7 +50081,7 @@ static JSValue js_dataview_getValue(JSContext *ctx, u.i = get_u64(ptr); if (is_swap) u.i = bswap64(u.i); - return __JS_NewFloat64(ctx, u.f); + return __JS_NewFloat64(u.f); } default: abort(); @@ -50791,7 +50794,7 @@ static uint64_t js__now_ms(void) static JSValue js_perf_now(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { - return JS_NewFloat64(ctx, js__now_ms() - ctx->time_origin); + return js_float64(js__now_ms() - ctx->time_origin); } static const JSCFunctionListEntry js_perf_proto_funcs[] = { @@ -50805,7 +50808,7 @@ void JS_AddPerformance(JSContext *ctx) JSValue performance = JS_NewObject(ctx); JS_SetPropertyFunctionList(ctx, performance, js_perf_proto_funcs, countof(js_perf_proto_funcs)); JS_DefinePropertyValueStr(ctx, performance, "timeOrigin", - JS_NewFloat64(ctx, ctx->time_origin), + js_float64(ctx->time_origin), JS_PROP_ENUMERABLE); JS_DefinePropertyValueStr(ctx, ctx->global_obj, "performance", JS_DupValue(ctx, performance), diff --git a/quickjs.h b/quickjs.h index 8b8b384..f11acec 100644 --- a/quickjs.h +++ b/quickjs.h @@ -111,7 +111,7 @@ typedef const struct __JSValue *JSValueConst; #define JS_NAN JS_MKVAL(JS_TAG_FLOAT64, 1) -static inline JSValue __JS_NewFloat64(JSContext *ctx, double d) +static inline JSValue __JS_NewFloat64(double d) { return JS_MKVAL(JS_TAG_FLOAT64, (int)d); } @@ -150,7 +150,7 @@ static inline double JS_VALUE_GET_FLOAT64(JSValue v) #define JS_NAN (0x7ff8000000000000 - ((uint64_t)JS_FLOAT64_TAG_ADDEND << 32)) -static inline JSValue __JS_NewFloat64(JSContext *ctx, double d) +static inline JSValue __JS_NewFloat64(double d) { union { double d; @@ -216,7 +216,7 @@ typedef struct JSValue { #define JS_NAN (JSValue){ .u.float64 = JS_FLOAT64_NAN, JS_TAG_FLOAT64 } -static inline JSValue __JS_NewFloat64(JSContext *ctx, double d) +static inline JSValue __JS_NewFloat64(double d) { JSValue v; v.tag = JS_TAG_FLOAT64; @@ -513,7 +513,7 @@ static js_force_inline JSValue JS_NewInt64(JSContext *ctx, int64_t val) if (val == (int32_t)val) { v = JS_NewInt32(ctx, val); } else { - v = __JS_NewFloat64(ctx, val); + v = __JS_NewFloat64(val); } return v; } @@ -524,7 +524,7 @@ static js_force_inline JSValue JS_NewUint32(JSContext *ctx, uint32_t val) if (val <= 0x7fffffff) { v = JS_NewInt32(ctx, val); } else { - v = __JS_NewFloat64(ctx, val); + v = __JS_NewFloat64(val); } return v; }