[net.bugs.4bsd] BSD C floating point bug affecting code everywhere

toddb@tekcrl.UUCP (Todd Brunhoff) (11/20/85)

The following program seems innocuous enough:
	int tst[1],i;

	main()
	{
		tst[0] = 0x20000020;
		i = 0;
		tst[i] += 0.0 * i;
	}

However, the code generated for the "tst[i] += 0.0 * i;" is incorrect
and even causes a core dump, which is how we came to notice this.  The
problem is based on the fact that if a destination operand for a
D-format floating point operation is a register, say n, then the vax
cpu uses register n and register n+1 for the result.  Here's the code
generated:

	...
L18:
	.double	0d0.00000000000000000000e+00
	.text
	cvtld	_i,r0		/* convert i to D-format floating point,
				 * putting result in r0 and r1.  I believe it
				 * is only an accident that something is not
				 * in r1 to get clobbered.
				 */
	muld3	r0,L18,r2	/* put "0.0 * i" into r2 ... and r3 as well */
	movl	_i,r0
	cvtld	_tst[r0],r1	/* convert long to D-format.  The result
				 * goes into r1 AND r2 thus clobbering the
				 * current value of r2 which was half of
				 * the expression "0.0 * i".
				 */
	addd2	r2,r1		/* Here's the worst.  We are actually adding
				 * the top half of the previous result to its
				 * bottom half (although the intent was
				 * different).  And with the particular
				 * constant 0x20000020, the result of the
				 * previous instruction put a 0x000080000
				 * in r2.  It just happens that this is a
				 * reserved operand (sign bit 15 = 1, exponent
				 * bits 14-7 = 0), and so we got a reserved
				 * operand fault.
				 */

We never would have known about this except for the reserved operand fault
caused by this particular constant, which means that vaxes running 4.2BSD unix
everywhere are just doing calculations flat wrong and no one knows it.
But if they get them right, it is purely by accident.  The correct code
should be:

	.double	0d0.00000000000000000000e+00
	.text
	cvtld	_i,r0		/* convert i to D-format (r0,r1) */
	muld3	r0,L18,r3	/* put "0.0 * i" into r3 and r4 */
	movl	_i,r0
	cvtld	_tst[r0],r1	/* convert long to D-format.  The result
				 * goes into r1 AND r2.
				 */
	addd2	r3,r1		/* add D-format (r3-r4) + (r1-r2) */

I haven't looked at the code generation modules, so it may be that only
the conversion operations forget about allocating register pairs.  How
about system V and Tartan's C-compiler?  It is not a well-known problem
here; is it known elsewhere?

This is awful!  Blech!