The Joy of Signalling NaNs! (A compiler patch)

Don nospam at nospam.com
Wed Mar 18 21:58:40 PDT 2009


It's great that D initializes floating-point variables to NaN, instead 
of whatever random garbage happened to be in RAM.
But, if your calculation ends up with a NaN, you have to work out where 
it came from. Worse, the NaN might not necessarily
be visible in your final results, but you results may nonetheless be wrong.

The hardware has excellent support for debugging these problems -- all 
you need to do is activitating the floating-point 'invalid' trap,
and you'll get a hardware exception whenever you _create_ a NaN. What 
about uninitialized variables?
Signalling NaNs are designed for exactly this situation. The instant you 
access a signalling NaN, a hardware exception occurs,
and you drop straight into your debugger (just like accessing a null 
pointer).
But this could only work if the compiler initialized every uninitialised 
floating-point variable to a signalling NaN.

Now that we have access to the backend (thanks Walter!), we can do 
exactly that. My patch(below) is enabled only when compiled with DMC.
real.nan is unchanged, and won't cause exceptions if you use it, but 
real.init is now a signalling nan.
This doesn't make any difference to anything, until you enable FP 
exceptions.
And when you do, if no exceptions occur, you can use the code coverage 
feature to give very high confidence that you're not using any 
uninitialised floating-point variables.

I propose that this should become part of DMD. It doesn't need to be in 
the spec, it's primarily for debugging.

Don.

==============
Example of usage:
==============

void main()
{
     double a, b, c;
     a*=7;        // Exactly the same as it is now, a is nan.

     enableExceptions();

     c = 6;      // ok, c is initialized now
     c *= 10;
     b *= 10;    // BANG ! Straight into the debugger
     b *= 5;

     disableExceptions();
}

-------

void enableExceptions() {
     version(D_InlineAsm_X86) {
          short cont;
          asm {
              fclex;
              fstcw cont;
              mov AX, cont;
              and AX, 0xFFFE; // enable invalid exception
              mov cont, AX;
              fldcw cont;
          }
      }
  }

void disableExceptions() {
     version(D_InlineAsm_X86) {
          short cont;
          asm {
              fclex;
              fstcw cont;
              mov AX, cont;
              or AX, 0x1; // disable invalid exception
              mov cont, AX;
              fldcw cont;
          }
      }
  }


=================================
Patches to DMD to turn all unitialized floats into SNANs.

Changes are in mytype.c and e2ir.c
=================================
mytype.c:
=================================
line 2150:

Expression *TypeBasic::defaultInit(Loc loc)
{   integer_t value = 0;
#if __DMC__
	// Note: could be up to 16 bytes long.
	unsigned short snan[8] = { 0xFFFF, 0xFFFF, 0xFFFF, 0xBFFF, 0x7FFF, 0, 
0, 0 };
	d_float80 fvalue = *(long double*)snan;
#endif

line 2177:

	case Tfloat80:
#if __DMC__
		return new RealExp(loc, fvalue, this);
#else
	    return getProperty(loc, Id::nan);
#endif

line 2186:

	case Tcomplex80:
#if __DMC__
		{   // Can't use fvalue + I*fvalue (the im part becomes a quiet NaN).
			complex_t cvalue;
			((real_t *)&cvalue)[0] = fvalue;
			((real_t *)&cvalue)[1] = fvalue;
			return new ComplexExp(loc, cvalue, this);
		}
#else
	    return getProperty(loc, Id::nan);
#endif
=================================
e2ir.c line 1182.
=================================

bool isSignallingNaN(real_t x)
{
#if __DMC__
	if (x>=0 || x<0) return false;
	return !((((unsigned short*)&x)[3])&0x4000);
#else
	return false;
#endif
}


elem *RealExp::toElem(IRState *irs)
{   union eve c;
     tym_t ty;

     //printf("RealExp::toElem(%p)\n", this);
     memset(&c, 0, sizeof(c));
     ty = type->toBasetype()->totym();
     switch (tybasic(ty))
     {
	case TYfloat:
	case TYifloat:
	    c.Vfloat = value;
		if (isSignallingNaN(value) ) {
			((unsigned int*)&c.Vfloat)[0] &= 0xFFBFFFFFL;
		}
	    break;

	case TYdouble:
	case TYidouble:
		c.Vdouble = value; // this unfortunately converts SNAN to QNAN.
		if ( isSignallingNaN(value) ) {
			((unsigned int*)&c.Vdouble)[1] &= 0xFFF7FFFFL;
		}
	    break;

	case TYldouble:
	case TYildouble:
	    c.Vldouble = value;
	    break;

	default:
	    print();
	    type->print();
	    type->toBasetype()->print();
	    printf("ty = %d, tym = %x\n", type->ty, ty);
	    assert(0);
     }
     return el_const(ty, &c);
}

elem *ComplexExp::toElem(IRState *irs)
{   union eve c;
     tym_t ty;
     real_t re;
     real_t im;

     re = creall(value);
     im = cimagl(value);

     memset(&c, 0, sizeof(c));
     ty = type->totym();
     switch (tybasic(ty))
     {
	case TYcfloat:
	    c.Vcfloat.re = (float) re;
	    c.Vcfloat.im = (float) im;
		if ( isSignallingNaN(re) && isSignallingNaN(im)) {
			((unsigned int*)&c.Vcfloat.re)[0] &= 0xFFBFFFFFL;
			((unsigned int*)&c.Vcfloat.im)[0] &= 0xFFBFFFFFL;
		}
	    break;

	case TYcdouble:
	    c.Vcdouble.re = (double) re;
	    c.Vcdouble.im = (double) im;
		if ( isSignallingNaN(re) && isSignallingNaN(im)) {
			((unsigned int*)&c.Vcdouble.re)[1] &= 0xFFF7FFFFL;
			((unsigned int*)&c.Vcdouble.im)[1] &= 0xFFF7FFFFL;
		}
	    break;

	case TYcldouble:
	    c.Vcldouble.re = re;
	    c.Vcldouble.im = im;
	    break;

	default:
	    assert(0);
     }
     return el_const(ty, &c);
}



More information about the Digitalmars-d mailing list