/*++

Copyright (c) 2001  Microsoft Corporation

Module Name:

    fpexception.c

Abstract:

    This module contains code to test i386 floating point exceptions.

Author:

Environment:

    User mode only.

Revision History:

--*/

#include "pch.h"

VOID
FPxInit(
    OUT  PFP_THREAD_DATA FpThreadData
    )
/*++

Routine Description:

    Initializes FPU state to known values.

Arguments:

    FpThreadData - FP thread data.

Return Value:

    None.

--*/
{
    USHORT cw = 0x27B;

    //
    // Fill in the initial thread values.
    //
    FpThreadData->FtagBad = 99.999;
    FpThreadData->ExpectedExceptionEIP = 0xbad;
    FpThreadData->ExceptionEIP = 0xbad;
    FpThreadData->BadEip = 1;
    FpThreadData->status = stOK;

    // unmask zero divide exception

    _asm {
        fninit
        fldcw   [cw]
    }
}


VOID
FPxLoadTag(
    IN OUT  PFP_THREAD_DATA FpThreadData,
    IN      UINT            Tag
    )
/*++

Routine Description:

    Loads a semi-unique tag value into the Npx for later validation.

Arguments:

    FpThreadData - FP thread data.

    Tag - Tag to load.

Return Value:

    None.

--*/
{
    double localCopy;

    FpThreadData->Ftag = localCopy = Tag * 1.41415926e3;

    _asm fld    [localCopy]
}


VOID
FPxPendDivideByZero(
    VOID
    )
/*++

Routine Description:

    Loads a divide-by-zero pending exception into the Npx.

Arguments:

    None.

Return Value:

    None.

--*/
{
    _asm {

        fld1
        fldz
        fdiv
    }
}


VOID
FPxDrain(
    IN OUT  PFP_THREAD_DATA FpThreadData
    )
/*++

Routine Description:

    Drains any pending exceptions in the Npx.

Arguments:

    FpThreadData - Updated with what should be the address of the pending
                   exception.

Return Value:

    None.

--*/
{
    UINT localExceptionEIP;

    _asm {
        mov localExceptionEIP, offset ExcAddr
    }

    FpThreadData->ExpectedExceptionEIP = localExceptionEIP;

    _asm {

      ExcAddr:
        fldpi
    }
}


FPXERR
FPxCheckTag(
    IN OUT  PFP_THREAD_DATA FpThreadData
    )
/*++

Routine Description:

    Makes sure the tag value that we loaded earlier is still present.

Arguments:

    FpThreadData - Used to retrieve expected tag, updated with current Npx tag.

Return Value:

    stOK if the tag is good, stBadTag if there's a mismatch.

--*/
{
    FPXERR rc = stOK;
    double localTagCopy, localBadTagCopy;

    //
    // We don't do an assignment here as we don't want to touch the FPU
    //
    memcpy(&localTagCopy, &FpThreadData->Ftag, sizeof(double));

    _asm {

        fnclex
        ffree   st(0)               ; move the tag to the top of stack
        ffree   st(1)
        fincstp
        fincstp
        fcomp   [localTagCopy]      ; is it our tag?
        fnstsw  ax
        sahf
        je  Ex

        mov     [rc], stBAD_TAG     ; not our tag!
        fst     [localBadTagCopy]
        fwait
      Ex:
    }

    //
    // We don't do an assignment here as we don't want to touch the FPU
    //
    memcpy(&FpThreadData->FtagBad, &localBadTagCopy, sizeof(double));

    return rc;
}


EXCEPTION_DISPOSITION
FPxUnexpectedExceptionFilter(
    IN      LPEXCEPTION_POINTERS    ExcInfo,
    IN OUT  PFP_THREAD_DATA         FpThreadData
    )
/*++

Routine Description:

    This handler is called when an Npx exception we *don't* expect occurs.

Arguments:

    ExcInfo - Exception record info.

    FpThreadData - Used to retrieve expected tag, updated with current Npx tag.

Return Value:

    How to handle the exception.

--*/
{
    FpThreadData->ExceptionEIP = ExcInfo->ContextRecord->Eip;
    return EXCEPTION_EXECUTE_HANDLER;
}


EXCEPTION_DISPOSITION
FPxExpectedExceptionFilter(
    IN      LPEXCEPTION_POINTERS    ExcInfo,
    IN OUT  PFP_THREAD_DATA         FpThreadData
    )
/*++

Routine Description:

    This handler is called when an Npx exception we *do* expect occurs.

Arguments:

    ExcInfo - Exception record info.

    FpThreadData - Used to retrieve expected tag, updated with current Npx tag.

Return Value:

    How to handle the exception.

--*/
{
    if (ExcInfo->ContextRecord->Eip != FpThreadData->ExpectedExceptionEIP) {

        FpThreadData->BadEip = ExcInfo->ContextRecord->Eip;
        FpThreadData->status = stBAD_EIP;

    } else {

        FpThreadData->status = stOK;

    }

    return EXCEPTION_EXECUTE_HANDLER;
}


FPXERR
FPxTestExceptions(
    IN      UINT                    Tag,
    IN      PFN_FPX_CALLBACK_FUNC   CallbackFunction,
    IN OUT  PFP_THREAD_DATA         FpThreadData,
    IN OUT  PVOID                   Context
    )
/*++

Routine Description:

    This handler tests NPX exceptions.

Arguments:

    Tag - Tag to test the FPU with.

    CallbackFunction - Called back between exception load and exception drains.
                       Must *not* access FPU in user mode as this will trash
                       loaded FPU state.

    FpThreadData - Cache of FPU information. Should be preinitialized with
                   FPxInit before the first call to this function. Does not
                   need to be preinited before subsequent invocations.

    Context - Context for callback func.

Return Value:

    FPXERR result.

--*/
{
    __try {

        //
        // Tag the Npx
        //
        FPxLoadTag(FpThreadData, Tag);

        __try {

            //
            // generate pending exception
            //
            FPxPendDivideByZero();

        } __except(FPxUnexpectedExceptionFilter(GetExceptionInformation(),
                                                FpThreadData)) {

            FpThreadData->status = stSPURIOUS_EXCEPTION;
        }

        if (FpThreadData->status == stOK) {

            //
            // Invoke the callback function.
            //
            CallbackFunction(Context);

            //
            // Drain the exception that should still be pending.
            //
            FPxDrain(FpThreadData);

            //
            // We shouldn't get here.
            //
            FpThreadData->status = stMISSING_EXCEPTION;
        }

    } __except(FPxExpectedExceptionFilter(GetExceptionInformation(),
                                          FpThreadData)) {

        if (FpThreadData->status == stOK) {

            __try {

                //
                // ST(2) should still have our tag value
                //
                FpThreadData->status = FPxCheckTag(FpThreadData);

            } __except(FPxUnexpectedExceptionFilter(GetExceptionInformation(),
                                                    FpThreadData)) {

                FpThreadData->status = stEXCEPTION_IN_HANDLER;
            }
        }
    }

    if (FpThreadData->status == stMISSING_EXCEPTION) {

        __try {

            FPxDrain(FpThreadData);
            FPxDrain(FpThreadData);

        } __except(EXCEPTION_EXECUTE_HANDLER) {

            FpThreadData->status = stMISSING_EXCEPTION_FOUND;
        }
    }

    return FpThreadData->status;
}