Subject: casting double to unsigned long will round values
To: None <tech-toolchain@NetBSD.org>
From: Martin Husemann <martin@duskware.de>
List: tech-toolchain
Date: 05/01/2004 21:02:17
I found a stupid bug, that manifests in breaking perl 5.8.x big time, in
gcc on sparc64. If you do

	double v = 5.6;
	unsigned long u = (unsigned long)v;

the u will be 6.

I think it happens because the only conversion pattern that could match is
this (from gcc/config/sparc/sparc.md):

(define_expand "fixuns_trunctfdi2"
  [(set (match_operand:DI 0 "register_operand" "")
        (unsigned_fix:DI (match_operand:TF 1 "general_operand" "")))]
  "TARGET_FPU && TARGET_ARCH64 && ! TARGET_HARD_QUAD"
  "emit_tfmode_cvt (UNSIGNED_FIX, operands); DONE;")

This forces it's operand into TF (128 bit float) mode, so the emitted code
basically is  _Qp_qtoux(_Qp_dtoq(v))

Now, since _Qp_qtoux always rounds, the result is always rounded. Which, of
course, is wrong.

If you use "long" instead of "unsigned long", the result is OK, probably
because there are more conversion patterns for fix:DI target modes and the
complex version via 128 bit floats is not selected, but the fix_truncdfdi2
pattern, that just emits a "fdtox" instruction.

So the eays way to fix this is to add equivalents of all fix:DI conversion
patterns as unsigned_fix:DI too.

But: to me this looks like it works in that case by pure luck. The bogus
conversion via TF is still there, and if it ever is chosen, the result
will be bogus.

What I'd like to know is:

 - why other OSes apparently don't have this problem (the Perl bug report
   that triggered this got a response from a sparc64 64bit linux user
   stating that it works on his machine)
 - if this are bugs in sparc.md or if our _Qp_* functions are broken

If our _Qp_* functions are OK (and to me it looks like this), we probably
should bounce this to the gcc people.

Martin
P.S.: I've added a regression test in regress/lib/libc/convfp to spot this.