Skip to content

Commit bd4ee68

Browse files
authored
Fix JS number rounding on x87 (#1244)
Disable 80-bits extended precision, use standard 64-bits precision. The extra precision affects rounding and is user-observable, meaning it causes test262 tests to fail. The rounding mode is a per-CPU (or, more accurately, per-FPU) property, and since quickjs is often used as a library, take care to tweak the FPU control word before sensitive operations, and restore it afterwards. Only affects x87. SSE is IEEE-754 conforming. Fixes: #1236
1 parent 4aa5a67 commit bd4ee68

File tree

3 files changed

+50
-7
lines changed

3 files changed

+50
-7
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ jobs:
5858
- { os: ubuntu-latest, configType: asan+ubsan, runTest262: true }
5959
- { os: ubuntu-latest, configType: msan }
6060
- { os: ubuntu-latest, configType: tcc }
61-
- { os: ubuntu-latest, arch: x86 }
61+
- { os: ubuntu-latest, arch: x86, runTest262: true }
6262
- { os: ubuntu-latest, arch: riscv64 }
6363
- { os: ubuntu-latest, arch: s390x }
6464

@@ -97,7 +97,7 @@ jobs:
9797
uses: jirutka/setup-alpine@v1
9898
with:
9999
arch: ${{ matrix.config.arch }}
100-
packages: "build-base make cmake"
100+
packages: "build-base make cmake git"
101101

102102
- name: uname
103103
run: uname -a

cutils.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,25 @@ int js_thread_join(js_thread_t thrd);
634634

635635
#endif /* !defined(EMSCRIPTEN) && !defined(__wasi__) */
636636

637+
// JS requires strict rounding behavior. Turn on 64-bits double precision
638+
// and disable x87 80-bits extended precision for intermediate floating-point
639+
// results. 0x300 is extended precision, 0x200 is double precision.
640+
// Note that `*&cw` in the asm constraints looks redundant but isn't.
641+
#if defined(__i386__) && !defined(_MSC_VER)
642+
#define JS_X87_FPCW_SAVE_AND_ADJUST(cw) \
643+
unsigned short cw; \
644+
__asm__ __volatile__("fnstcw %0" : "=m"(*&cw)); \
645+
do { \
646+
unsigned short t = 0x200 | (cw & ~0x300); \
647+
__asm__ __volatile__("fldcw %0" : /*empty*/ : "m"(*&t)); \
648+
} while (0)
649+
#define JS_X87_FPCW_RESTORE(cw) \
650+
__asm__ __volatile__("fldcw %0" : /*empty*/ : "m"(*&cw))
651+
#else
652+
#define JS_X87_FPCW_SAVE_AND_ADJUST(cw)
653+
#define JS_X87_FPCW_RESTORE(cw)
654+
#endif
655+
637656
#ifdef __cplusplus
638657
} /* extern "C" { */
639658
#endif

quickjs.c

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13395,12 +13395,17 @@ static int js_is_array(JSContext *ctx, JSValueConst val)
1339513395

1339613396
static double js_math_pow(double a, double b)
1339713397
{
13398+
double d;
13399+
1339813400
if (unlikely(!isfinite(b)) && fabs(a) == 1) {
1339913401
/* not compatible with IEEE 754 */
13400-
return NAN;
13402+
d = NAN;
1340113403
} else {
13402-
return pow(a, b);
13404+
JS_X87_FPCW_SAVE_AND_ADJUST(fpcw);
13405+
d = pow(a, b);
13406+
JS_X87_FPCW_RESTORE(fpcw);
1340313407
}
13408+
return d;
1340413409
}
1340513410

1340613411
JSValue JS_NewBigInt64(JSContext *ctx, int64_t v)
@@ -13820,11 +13825,15 @@ static no_inline __exception int js_binary_arith_slow(JSContext *ctx, JSValue *s
1382013825
}
1382113826
break;
1382213827
case OP_div:
13828+
JS_X87_FPCW_SAVE_AND_ADJUST(fpcw);
1382313829
sp[-2] = js_number((double)v1 / (double)v2);
13830+
JS_X87_FPCW_RESTORE(fpcw);
1382413831
return 0;
1382513832
case OP_mod:
1382613833
if (v1 < 0 || v2 <= 0) {
13834+
JS_X87_FPCW_SAVE_AND_ADJUST(fpcw);
1382713835
sp[-2] = js_number(fmod(v1, v2));
13836+
JS_X87_FPCW_RESTORE(fpcw);
1382813837
return 0;
1382913838
} else {
1383013839
v = (int64_t)v1 % (int64_t)v2;
@@ -13888,6 +13897,7 @@ static no_inline __exception int js_binary_arith_slow(JSContext *ctx, JSValue *s
1388813897
if (JS_ToFloat64Free(ctx, &d2, op2))
1388913898
goto exception;
1389013899
handle_float64:
13900+
JS_X87_FPCW_SAVE_AND_ADJUST(fpcw);
1389113901
switch(op) {
1389213902
case OP_sub:
1389313903
dr = d1 - d2;
@@ -13907,6 +13917,7 @@ static no_inline __exception int js_binary_arith_slow(JSContext *ctx, JSValue *s
1390713917
default:
1390813918
abort();
1390913919
}
13920+
JS_X87_FPCW_RESTORE(fpcw);
1391013921
sp[-2] = js_float64(dr);
1391113922
}
1391213923
return 0;
@@ -14023,7 +14034,9 @@ static no_inline __exception int js_add_slow(JSContext *ctx, JSValue *sp)
1402314034
}
1402414035
if (JS_ToFloat64Free(ctx, &d2, op2))
1402514036
goto exception;
14037+
JS_X87_FPCW_SAVE_AND_ADJUST(fpcw);
1402614038
sp[-2] = js_float64(d1 + d2);
14039+
JS_X87_FPCW_RESTORE(fpcw);
1402714040
}
1402814041
return 0;
1402914042
exception:
@@ -17998,8 +18011,10 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj,
1799818011
sp[-2] = js_int32(r);
1799918012
sp--;
1800018013
} else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2)) {
18014+
JS_X87_FPCW_SAVE_AND_ADJUST(fpcw);
1800118015
sp[-2] = js_float64(JS_VALUE_GET_FLOAT64(op1) +
1800218016
JS_VALUE_GET_FLOAT64(op2));
18017+
JS_X87_FPCW_RESTORE(fpcw);
1800318018
sp--;
1800418019
} else {
1800518020
add_slow:
@@ -18066,8 +18081,10 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj,
1806618081
sp[-2] = js_int32(r);
1806718082
sp--;
1806818083
} else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2)) {
18084+
JS_X87_FPCW_SAVE_AND_ADJUST(fpcw);
1806918085
sp[-2] = js_float64(JS_VALUE_GET_FLOAT64(op1) -
1807018086
JS_VALUE_GET_FLOAT64(op2));
18087+
JS_X87_FPCW_RESTORE(fpcw);
1807118088
sp--;
1807218089
} else {
1807318090
goto binary_arith_slow;
@@ -18098,7 +18115,9 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj,
1809818115
sp[-2] = js_int32(r);
1809918116
sp--;
1810018117
} else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2)) {
18118+
JS_X87_FPCW_SAVE_AND_ADJUST(fpcw);
1810118119
d = JS_VALUE_GET_FLOAT64(op1) * JS_VALUE_GET_FLOAT64(op2);
18120+
JS_X87_FPCW_RESTORE(fpcw);
1810218121
mul_fp_res:
1810318122
sp[-2] = js_float64(d);
1810418123
sp--;
@@ -52277,7 +52296,9 @@ static double set_date_fields(double fields[minimum_length(7)], int is_local) {
5227752296
int yi, mi, i;
5227852297
int64_t days;
5227952298
volatile double temp; /* enforce evaluation order */
52299+
double d = NAN;
5228052300

52301+
JS_X87_FPCW_SAVE_AND_ADJUST(fpcw);
5228152302
/* emulate 21.4.1.15 MakeDay ( year, month, date ) */
5228252303
y = fields[0];
5228352304
m = fields[1];
@@ -52287,7 +52308,7 @@ static double set_date_fields(double fields[minimum_length(7)], int is_local) {
5228752308
if (mn < 0)
5228852309
mn += 12;
5228952310
if (ym < -271821 || ym > 275760)
52290-
return NAN;
52311+
goto out;
5229152312

5229252313
yi = ym;
5229352314
mi = mn;
@@ -52319,14 +52340,17 @@ static double set_date_fields(double fields[minimum_length(7)], int is_local) {
5231952340
/* emulate 21.4.1.16 MakeDate ( day, time ) */
5232052341
tv = (temp = day * 86400000) + time; /* prevent generation of FMA */
5232152342
if (!isfinite(tv))
52322-
return NAN;
52343+
goto out;
5232352344

5232452345
/* adjust for local time and clip */
5232552346
if (is_local) {
5232652347
int64_t ti = tv < INT64_MIN ? INT64_MIN : tv >= 0x1p63 ? INT64_MAX : (int64_t)tv;
5232752348
tv += getTimezoneOffset(ti) * 60000;
5232852349
}
52329-
return time_clip(tv);
52350+
d = time_clip(tv);
52351+
out:
52352+
JS_X87_FPCW_RESTORE(fpcw);
52353+
return d;
5233052354
}
5233152355

5233252356
static JSValue get_date_field(JSContext *ctx, JSValueConst this_val,

0 commit comments

Comments
 (0)