pack test failures with long doubles on x86/gcc
authorDavid Mitchell <davem@iabyn.com>
Mon, 21 Mar 2011 14:14:52 +0000 (14:14 +0000)
committerDavid Mitchell <davem@iabyn.com>
Mon, 21 Mar 2011 14:30:20 +0000 (14:30 +0000)
[perl #86534]

When using 80-bit extended precision floats, gcc-generated code
can sometimes copy 10 bytes and sometimes 12. This can lead to 2 random
bytes something appearing in the output of pack('F') and pack('D').

Work around this by using sv_2nv() rather than SvNV()
(which expands to (SvNOK(sv) ? SvNVX(sv) : sv_2nv(sv) )

The basic issue is that the NV return value of a function is returned
in a floating point register, which is then written to memory as 10 bytes,
whereas a direct assignment, e.g. NV nv1 = nv2, is done by a 12-byte
memory copy.

However, when the sv_2nv() function is called as part of the ?:
expression, its returned value is written as *10* bytes to a temp location
on the stack, which is then copied as a *12* byte value to its final
destination, picking up 2 bytes of random data from the stack, which then
appears in the output of pack(), and ultimately leads to a test failure.

pp_pack.c

index be9b115..5229fc3 100644 (file)
--- a/pp_pack.c
+++ b/pp_pack.c
@@ -3184,7 +3184,12 @@ extern const double _double_constants[];
            Zero(&anv, 1, NV); /* can be long double with unused bits */
            while (len-- > 0) {
                fromstr = NEXTFROM;
+#ifdef __GNUC__
+               /* to work round a gcc/x86 bug; don't use SvNV */
+               anv.nv = sv_2nv(fromstr);
+#else
                anv.nv = SvNV(fromstr);
+#endif
                DO_BO_PACK_N(anv, NV);
                PUSH_BYTES(utf8, cur, anv.bytes, sizeof(anv.bytes));
            }
@@ -3197,7 +3202,12 @@ extern const double _double_constants[];
            Zero(&aldouble, 1, long double);
            while (len-- > 0) {
                fromstr = NEXTFROM;
+#  ifdef __GNUC__
+               /* to work round a gcc/x86 bug; don't use SvNV */
+               aldouble.ld = (long double)sv_2nv(fromstr);
+#  else
                aldouble.ld = (long double)SvNV(fromstr);
+#  endif
                DO_BO_PACK_N(aldouble, long double);
                PUSH_BYTES(utf8, cur, aldouble.bytes, sizeof(aldouble.bytes));
            }