Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++/CLI: SIGFPE, _control87, _fpreset, porting ancient unmanaged Watcom C app to .NET

I have a several-thousand-line application that relies on SIGFPE (handled by a function pointer passed to signal()) to change state and have the code run correctly when certain floating point conditions happen. However, under C++/CLI in managed-mode, _control87 generates a System.ArithmeticException executing in a static lib written in C. _fpreset and _control87 are not supported.

How do I get classic, unmanaged SIGFPE operation to work in a C++/CLI application? The number of locations where floating point stuff happens in my application could be immense and I do not fully understand all of the numerical methods written years ago by other programmers.

I want old-school exception handling to work on a floating point division by zero, not an INF value. Platform invoke style doesn't work, and #pragma managed(off) doesn't do the trick either.

What options do I have?

like image 500
user343400 Avatar asked Jul 26 '10 20:07

user343400


1 Answers

There are several very serious pain points here. Enabling floating point exceptions is grossly incompatible with managed code execution. Down to the basics, you can easily crash the JIT-compiler. Which is the problem you are battling when you use _control87().

And yes, you'll get a CLR exception, it puts an exception backstop in place whenever it executes native code. A signal handler is only ever called when an exception is raised and there's no code to handle it. Inevitably the CLR sees the exception before the C runtime library can see it. So you'll never get the SIGFPE handler call.

The only decent way to have a shot at this is write a wrapper that catches the exception before the CLR can. Also very, very important that you carefully manage the FPU control word, you can only afford having FPU exceptions enabled while the native code is running. This takes a bunch of gritty code, up-front warning that you are not going to enjoy it very much.

You didn't post any snippet so I'll have to make up a silly example:

#include <Windows.h>
#include <signal.h>
#include <float.h>

#pragma managed(push, off)

double divisor;

void __cdecl fpehandler(int sig) {
    divisor = 1.0;
}

double badmath() {
    divisor = 0.0;
    return 1 / divisor;
}
#pragma managed(pop)

In order to get fpehandler() called, you need to call the exception handler inside the C runtime library. Luckily it is exposed and you can link it, you only need a declaration for it so you can call it:

// Exception filter in the CRT, it raises the signal
extern "C" int __cdecl _XcptFilter(unsigned long xcptnum, 
                                   PEXCEPTION_POINTERS pxcptinfoptrs);

You need to make sure that it is only ever called for floating point exceptions. So we need a wrapper that pays attention to the exception code:

int FloatingpointExceptionFilter(unsigned long xcptnum, PEXCEPTION_POINTERS pxcptinfoptrs) {
    // Only pass floating point exceptions to the CRT
    switch (xcptnum) {
        case STATUS_FLOAT_DIVIDE_BY_ZERO:
        case STATUS_FLOAT_INVALID_OPERATION:
        case STATUS_FLOAT_OVERFLOW:
        case STATUS_FLOAT_UNDERFLOW:
        case STATUS_FLOAT_DENORMAL_OPERAND:
        case STATUS_FLOAT_INEXACT_RESULT:
        case STATUS_FLOAT_STACK_CHECK:
        case STATUS_FLOAT_MULTIPLE_TRAPS:
        case STATUS_FLOAT_MULTIPLE_FAULTS:
            return _XcptFilter(xcptnum, pxcptinfoptrs);
            break;
        default:
            return EXCEPTION_CONTINUE_SEARCH;
    }
}

Now you can write a wrapper for badmath() that gets the signal handler called:

double badmathWrapper() {
    __try {
        return badmath();
    }
    __except (FloatingpointExceptionFilter(GetExceptionCode(), GetExceptionInformation())) {
    }
}

Which in turn can be called by a C++/CLI class that you can call from any managed code. It needs to ensure that floating point exceptions are enabled before the call and restored again after the call:

using namespace System;
using namespace System::Runtime::CompilerServices;

public ref class Wrapper {
public:
    static double example();
};

[MethodImplAttribute(MethodImplOptions::NoInlining)]
double Wrapper::example() {
    signal(SIGFPE, fpehandler);
    _clear87();
    unsigned oldcw = _control87(_EM_INEXACT, _MCW_EM);
    try {
        return badmathWrapper();
    }
    finally {
        _control87(oldcw, _MCW_EM);
        signal(SIGFPE, nullptr);
    }
}

Note the call to _control87(), it enables all floating exceptions except "inexact result". This is necessary to allow the code to be jitted. If you don't mask it then the CLR dies a horrible death, throwing exceptions over and over again until this site's name puts an end to it. Hopefully your signal handler doesn't need it.

like image 200
Hans Passant Avatar answered Nov 02 '22 22:11

Hans Passant