Bug #79595 [Opn]: zend_init_fpu() alters FPU precision

From: Date: Wed, 20 May 2020 11:09:56 +0000
Subject: Bug #79595 [Opn]: zend_init_fpu() alters FPU precision
References: 1  Groups: php.bugs 
Request: Send a blank email to php-bugs+get-227091@lists.php.net to get a copy of this message
Edit report at https://bugs.php.net/bug.php?id=79595&edit=1 ID: 79595 Updated by: nikic@php.net Reported by: v-yitam at microsoft dot com Summary: zend_init_fpu() alters FPU precision Status: Open Type: Bug Package: Math related Operating System: Alpine Linux PHP Version: 7.4.5 Block user comment: N Private report: N New Comment: Are you interested in this for 32-bit or 64-bit builds? I think we can safely disable this code if compiling under __SSE__, because we know that PHP itself will not use x87 FPU in that case. For 32-bit builds targeting x87 FPU, I don't think we want to change this, as we do want normal floating point operations to be based on double precision. Previous Comments: ------------------------------------------------------------------------ [2020-05-19 17:20:16] v-yitam at microsoft dot com Thanks for getting back to us. Indeed, I suspect so: /* NOTE: This only sets internal precision. MSVC does NOT support double- extended precision! */ # define XPFPA_SWITCH_DOUBLE_EXTENDED() If I changed the macro XPFPA_SWITCH_DOUBLE() to XPFPA_SWITCH_DOUBLE_EXTENDED(), the problem was resolved in Alpine Linux. Hence, please investigate further why using XPFPA_SWITCH_DOUBLE() is necessary. On Linux the FPU should be left at 80-bit precision, especially Alpine, as musl libc's strtod() requires it to work properly, not to mention it also breaks any functions that expect the standard ABI on Linux. ------------------------------------------------------------------------ [2020-05-18 07:26:35] cmb@php.net From a comment in zend_float.h: | This header file defines several platform-dependent macros that | ensure equal and deterministic floating point behaviour across | several platforms, compilers and architectures. So, apparently, this is done deliberately. I'm not sure that this is a good idea, though. ------------------------------------------------------------------------ [2020-05-14 17:40:43] v-yitam at microsoft dot com I wanted to modify my original bug report but it didn't seem possible. Anyway, below is a more accurate example to illustrate the issue. It was copied when running the debugger and stepping through zend_init_fpu() in Zend/zend_float.c: 24 { (gdb) s 28 if (!EG(saved_fpu_cw_ptr)) { (gdb) n 29 EG(saved_fpu_cw_ptr) = (void*)&EG(saved_fpu_cw); (gdb) n 31 XPFPA_STORE_CW(EG(saved_fpu_cw_ptr)); (gdb) n 32 long double n = 1; (gdb) n 33 printf("Before %.17Le\n", n/100000); (gdb) n Before 1.00000000000000000e-05 35 XPFPA_SWITCH_DOUBLE(); (gdb) n 36 printf("After %.17Le\n", n/100000); (gdb) n After 1.00000000000000008e-05 40 } (gdb) ------------------------------------------------------------------------ [2020-05-13 23:48:13] v-yitam at microsoft dot com Description: ------------ A PHP extension which depends on the FPU precision being the default of the ABI (80 bit) will produce subtly incorrect results with floating point number. In particular, it breaks any musl libc-linked binaries that use floating point strtod(). In Alpine Linux, musl's strtod (https://git.musl-libc.org/cgit/musl/tree/src/internal/floatscan.c) depends on long double, which requires FPU set to 80-bit precision to actually work To illustrate, I wrote two simple test scripts (one cpp and one php), using the example provided in a similar issue (https://github.com/microsoft/WSL/issues/830): The output from the test program: 1.00000000000000000e-05 The output from running php: 1.00000000000000008e-5 Please see below. Test script: --------------- alpine:~$ more test_print.cpp #include <stdio.h> int main() { long double n = 1; printf("%.17Le\n", n / 100000); } alpine:~$ g++ -std=c++11 -static -o test_print test_print.cpp alpine:~$ ./test_print 1.00000000000000000e-05 alpine:~$ more test_print.cpp #include <stdio.h> int main() { long double n = 1; printf("%.17Le\n", n / 100000); } alpine:~$ g++ -std=c++11 -static -o test_print test_print.cpp alpine:~$ ./test_print 1.00000000000000000e-05 alpine:~$ more test_print.php <?php $n = 1; printf("%.17e\n", 1/100000); ?> alpine:~$ php test_print.php 1.00000000000000008e-5 Expected result: ---------------- FPU precision should not be changed Actual result: -------------- Have to save/restore FPU state and force it back to 80-bit ------------------------------------------------------------------------ -- Edit this bug report at https://bugs.php.net/bug.php?id=79595&edit=1

« previous php.bugs (#227091) next »