page ,132 subttl emexcept.asm - Microsoft exception handler ;*** ;emexcept.asm - Microsoft exception handler ; ; Copyright (c) 1987-89, Microsoft Corporation ; ;Purpose: ; Microsoft exception handler ; ; This Module contains Proprietary Information of Microsoft ; Corporation and should be treated as Confidential. ; ;Revision History: (Also see emulator.hst.) ; ; 12-08-89 WAJ Add fld tbyte ptr [mem] denormal check. ; ;******************************************************************************* ;---------------------------------------------------------------------- ; Structure for FSTENV and FLDENV, Store and Load 8087 Environment ;---------------------------------------------------------------------- glb glb glb glb ENV_DS EQU -4 ENV_BX EQU -2 ENV_ControlWord EQU 0 ENV_StatusWord EQU 2 ENV_TagWord EQU 4 ENV_IP EQU 6 ENV_Opcode EQU 8 ENV_OperandPointer EQU 10 ENV_ControlMask EQU 16 ENV_Temp EQU 18 ; Note ENV_Temp occupies ENV_CallOffset EQU 18 ; the same space as ENV_Call*. ENV_CallSegment EQU 20 ; This is possible because there ENV_CallFwait EQU 22 ; is never simultaneous use of ENV_Call8087Inst EQU 23 ; this space ENV_CallLongRet EQU 25 ENV_OldBP EQU 28 ENV_OldAX EQU 30 ENV_IRETadd EQU 32 ENV_Size EQU 28 ; UNDONE 386 version is bad - 387 environment is longer PAGE ;---------------------------------------------------------------------------- ; ; 8087 EXCEPTION HANDLER - Fields 8087 stack over flow and under flow. ; ;---------------------------------------------------------------------------- ; ; I. The 8087 state vector. ; ; Upon the execution of a FSAVE or FSTENV instruction the state of the ; 8087 is saved in a user defined state vector. The first seven words ; saved in the state vector by the two instructions are identical. The ; definition of the words is: ; ; Word. Bits. Bytes. Function. ; ----- ----- ------ --------- ; 0 15..0 1..0 Control word. ; 1 15..0 3..2 Status word. ( 8087 stack ; pointer and condition codes. ; 2 15..0 5..4 Tag word ( 8087 stack slot usage ; flags ). ; 3 15..0 7..6 Instruction pointer. Operator ; segment offset. ; 4 15..12 8 Operator paragraph bits ( ( ; bits 16..19 ) of address ). ; 4 11 8 Always zero. ; 4 10..8 8 Upper opcode bits ( major opcode ). ; 4 7..0 9 Lower opcode bits ( minor opcode ). ; 5 15..0 11..10 Operand Segment offset. ; 6 15..12 12 Operand paragraph bits. ; 6 11..0 Not used. Must be zero. ; ; II. Restarting instructions. ; ; Of interest in this handler is the necessity of restarting ; 8087 instructions which fail because of 8087 stack overflow ; and underflow. Even though the 8087 saves enough information ; to restart an instruction, it is incapable of doing so. The ; instruction restart must be done in software. ; ; There are two cases which must be considered after the stack ; exception has been dealt with. ; ; 1. The faulting instruction deals with top of stack. ; 2. The faulting instruction deals with memory. ; ; The first case is handled by changing the upper five bits ( ; 15..11 ) of vector word four ( 4 ) to "11011B". This changes ; word four into an "escape opcode" 8087 instruction. The ; modified opcode is placed in the interrupt code segment and ; executed. ; ; The second case is handled by changing the upper five bits ; ( 15..11 ) of vector word four ( 4 ) to "11011B", changing ; the MOD of the opcode to "00B" ( 0 displacement ), loading ; the operand address into DS:SI, and changing the RM field of ; the opcode to "100B" (SI+DISP addressing). The faulting ; instruction may be restarted as above. ; ; Instruction restart may also be accomplished by building an ; instruction stream in the interrupt stack and calling the ; instruction stream indirectly. This method is the preferred ; method because it is reentrant. ; ; III. Data Segment Considerations. ; ; DS is restored from the task interrupt vector. DS is used for ; stack overflow memory. ; ; ; Documentation of the invalid exception handling code for the stand-alone ; 8087/80287 emulator ; ; The emulator software is being enhanced for the cmerge 4.0 generation of ; languages to support a larger subset of the numeric processor instruction ; set. In addition to providing instructions which were not previously ; emulated, the model for representing the numeric processor stack is also ; being modified. The 4.0 languages and their predecessors are object compat- ; ible so it will be possible for programs to be developed which will contain ; code generated by the old model as well as the new model. For this reason ; it is important to understand the characteristics of both models and how ; the two models will interact. ; ; I. The Old Model: Infinite Stack ; ; The old model used an infinite stack model as the basis of its ; emulation of the numeric processor. Only the classical stack form of ; instructions with operands were emulated so only ST(0) (top of stack) ; and ST(1) (next to top of stack) were referenced by any given instruction. ; In addition, the stack was allowed to overflow beyond the eight registers ; available on the chip into a memory stack overflow area. The code genera- ; tor did not attempt to maintain all of its register data in the first eight ; register slots but instead made use of this overflow area. In order to ; maintain compatible behavior with or without the presence of the chip, this ; model made it necessary to handle and recover from stack overflow exceptions ; in the case where the chip is present as well as when it is being emulated. ; ; This stack overflow exception handling could in turn generate a recoverable ; stack underflow exception since a situation could arise where a desired ; operand had been pushed into the memory overflow area (during stack overflow) ; and was not available in the on-chip register area when needed. This ; scenario would signal an invalid exception due to stack underflow. ; It is recoverable because the required operand is still available in the ; overflow area and simply needs to be moved into a register on the chip. ; ; II. The New Model: Finite Stack ; ; The new model uses a finite stack model: only the eight registers on the ; chip are available for use, so in the new model the invalid exception ; would never be signalled due to stack overflow. In addition, it extends ; the emulated instruction set to include the general register form of ; instructions with operands (operands can be ST(i),ST or ST,ST(i)). Since ; the new code generator is aware of how many items it has placed on the stack, ; it does not allow stack overflow or stack underflow to occur. It can remove ; items from the registers either by storing to memory (FST or FSTP), or by ; using FFREE to mark the register as empty (this instruction is being added ; to the emulated instruction set). The new model uses FFREE in a well-defined ; manner: it will only free registers from the boundaries of the block of ; registers it is using. For example, if the new code is using ST(0)-ST(6), ; it must free the registers in the order ST(6),ST(5),ST(4),... and so on. ; It cannot create gaps of free registers within the block of registers ; it is using. ; ; III. The Hybrid Model: Combination of New and Old Code ; ; Due to the possibility of mixture of code generated using both of the above ; models, the new exception handling and emulation software has to be able to ; handle all situations which can arise as a result of the interaction of the ; two models. The following summarizes the behavior of the two models and ; restrictions placed on their interaction. ; ; New Code: ; ; 1. Cannot call anyone with any active entries on the stack. ; The new model is always at a conceptual stack level of zero ; when it makes external calls. Thus old code will never ; incorrectly make use of register data that was placed on the ; register stack by new code. ; ; 2. May create gaps of free registers in the register stack. ; It will not create gaps in the memory stack overflow area. ; ; 3. Only causes stack overflow by pushing old code entries off of ; the register stack and into the memory stack overflow area. ; It will never overflow its own entries into the memory stack ; overflow area. ; ; 4. Cannot cause stack underflow. ; ; ; Old Code: ; ; 1. Can only reference ST(0), ST(1). ; ; 2. Can cause stack overflow by pushing too many entries onto the ; register stack. ; ; 3. Can cause stack underflow in two situations: ; ; a. It is trying to get something that is in the memory stack ; overflow area (stack overflow occurred previously). ; ; b. There are free entries on the chip. This situation could ; arise if new code creates free entries then calls old code, ; so this is a situation that could not have existed before ; the new model was introduced. ; ; IV. Stack Overflow/Underflow Exception Handling ; ; The following algorithms will be used for detecting and recovering from ; stack overflow and underflow conditions (signalled via the invalid ; exception). All invalid exceptions are "before" exceptions so that ; the instruction has to be reexecuted once the exception has been handled. ; ; A. Stack Overflow ; ; If ST(7) is used (possible stack overflow) then { ; check for instructions which could cause stack overflow ; (includes FLD,FPTAN,...) ; if instruction could cause stack overflow then { ; save ST(7) in stack overflow area at [CURstk] ; mark ST(7) empty ; if FLD ST(7) instruction then ; FLD [CURstk] or rotate chip (clear exceptions) ; else reexecute the instruction with ST(7) empty ; } ; } ; ; B. Stack Underflow ; ; If ST(0) is free then assume stack underflow since the stack ; overflow case has already been handled (if the invalid ; is due to a denormal exception, the exception will occur ; again when the instruction is reexecuted): ; ; if chip has any registers in use (check the tag word) then { ; rotate chip until ST(0) is not empty ; rotate tag word to reflect state of chip ; } ; else (no registers in use) ; if operand is in stack overflow area then { ; load into ST(0) from stack overflow area ; mark ST(0) full ; } ; else { ; indicate true stack underflow ; go print error ; } ; if ST(1) is empty then { ; if any of ST(2) thru ST(7) are in use then { ; rotate chip until ST(1) is not empty ; (to share code with first chip rotation above: ; store pop st(0) into temp ; rotate chip until st(0) is not free ; load st(0) back onto chip) ; update tag word appropriately ; } ; else ; load ST(1) from overflow area if there ; } ; ; At this point, ST(0) and ST(1) have been filled if possible. ; Now we must categorize the instructions to determine which ; of these is required. Then we will either issue true stack ; underflow or reexecute the instruction with the compressed ; stack. ;---------------------------------------------------------------------------- ; ; References: ; Intel 8086 Family Numerics Supplement. 121586-001 Rev A. ; Intel iAPX 86,88 User's Manual. ; ;---------------------------------------------------------------------------- ;---------------------------------------------------------------------------- ; ; All registers must be saved by __FPEXCEPTION87 except CS,IP,SS,SP. ; ;---------------------------------------------------------------------------- ifdef WINDOWS ; stack consists of IRET frame and status word before FCLEX ; ; Since the environment is different in protect mode, reconstruct ; the opcode like in real mode. lab protiret iret lab protexskipsegovr inc bx ; bump past segment override jmp short protexsegovr ; try again public __FPEXCEPTION87P __FPEXCEPTION87P: lab protexception push eax ; save user ax push ebp sub esp,ENV_Size ; get Enough bytes for Environment mov ebp,esp ; set up for rational offsets. fstenv word ptr [ebp] ; save environment. mov eax,offset protiret ; set up for near return address xchg ax,[ebp+ENV_Size+4] ; swap status word and near ret addr mov ENV_StatusWord[ebp],ax ; save status word into environment push ebx push ds ; save a few more registers lds ebx,dword ptr ENV_IP[ebp] ; get address of instruction lab protexsegovr mov ax,[ebx] ; get 1st 2 bytes of instruction add al,28h ; add -(ESC 0) jnc protexskipsegovr ; wasn't ESC - skip seg. override xchg al,ah ; swap bytes to make real opcode mov ENV_Opcode[ebp],ax ; save it in environment sti jmp short exceptionhandler endif ;WINDOWS ifdef DOS5 ; stack consists of IRET frame and status word before FCLEX ; ; Since the environment is different in protect mode, reconstruct ; the opcode like in real mode. lab protiret iret lab protexskipsegovr inc bx ; bump past segment override jmp short protexsegovr ; try again lab protexception push eax ; save user ax push ebp sub esp,ENV_Size ; get Enough bytes for Environment mov ebp,esp ; set up for rational offsets. fstenv word ptr [ebp] ; save environment. mov eax,offset protiret ; set up for near return address xchg ax,[ebp+ENV_Size+4] ; swap status word and near ret addr mov ENV_StatusWord[ebp],ax ; save status word into environment push ebx push ds ; save a few more registers lds ebx,dword ptr ENV_IP[ebp] ; get address of instruction lab protexsegovr mov ax,[ebx] ; get 1st 2 bytes of instruction add al,28h ; add -(ESC 0) jnc protexskipsegovr ; wasn't ESC - skip seg. override xchg al,ah ; swap bytes to make real opcode mov ENV_Opcode[ebp],ax ; save it in environment endif ;DOS5 ifdef DOS3and5 jmp short exceptionhandler endif ;DOS3and5 ifdef DOS3 public __FPEXCEPTION87 __FPEXCEPTION87: PUSH AX ; Save user's AX next to IRET PUSH BP SUB SP,ENV_Size ; Get Enough bytes for Environment ; 8087 status. MOV BP,SP ; Set up for rational offsets. ;Caveat Programmer! ;FSTENV does an implicit set of all exception masks. FNSTENV WORD PTR [BP] ; Save environment. FCLEX ; Clear exceptions. STI ; Restore host interrupts. PUSH BX PUSH DS ; Need access to user data endif ;DOS3 ;---------------------------------------------------------------------------- ; In a multitasking environment one would not want to restore ; interrupts at this point. One would wait until the 8087 had been ; flushed and any operand data copied to a storage area. ;---------------------------------------------------------------------------- ;--------------- ; Inside of the while exception loop and Redo8087Instruction ; registers AX and BX must contain the values as described ; below: ; AL bit 0 = 1 indicates invalid exception ; bit 1 = 1 indicates denormal exception ; bit 2 = 1 indicates divide by zero exception ; bit 3 = 1 indicates numeric overflow ; bit 4 = 1 indicates numeric underflow ; bit 5 = 1 indicates precision loss ; bit 6 = unused by 8087 ; bit 7 = 1 indicates sqrt of negative number ; (this flag is not from the NPX status word, but ; is set after all other exceptions have been ; handled if the opcode is FSQRT) ; AH bit 0 = unused ; bit 1 = 1 indicates stack overflow ; bit 2 = 1 indicates stack underflow ; bit 3 = unused ; bit 4 = unused ; bit 5 = 1 indicates memory operand ; bit 6 = 1 indicates instruction was reexcuted ; bit 7 = 1 indicates ST relative operand ; BL = The complement of the exception masks copied from ; UserControlWord altered so that Denormal and Invalid ; exceptions are always unmasked, while the reserved ; bits are masked. ; BH bit 0 = 1 indicates 8087 only invalid handling complete ; bit 1 = 1 indicates 8087 only denormal handling complete ; bit 2 = 1 indicates 8087 only divide by zero handling complete ; bit 3 = 1 indicates 8087 only numeric overflow handling complete ; bit 4 = 1 indicates 8087 only numeric underflow handling complete ; bit 5 = 1 indicates 8087 only precision loss handling complete ; bit 6 = unused ; bit 7 = unused ; ; Algorithm: Handle 8087 exceptions which do not occur in the ; emulator and then jump to the common exception handling code. ; ; To handle 8087 only exceptions we must first determine if the ; exception occured before the 8087 executed the instruction ; or afterward. Invalid, denormal (except FLD) and divide by ; zero exceptions all occur before 8087 instruction execution, ; others occur afterward. "Before" exceptions must set the ; "before" flag in AH and then reexecute the instruction. After ; reexecution (while all exceptions are masked) all of the ; exceptions resulting from the current 8087 instruction will ; be known and can be handled as a group. "After" exceptions ; are handled individually since reexecution of an already ; executed instruction will destroy the validity of the 8087 stack. ; A flag in AH is used by Redo8087instruction to avoid reexecuting ; an instruction twice. At the beginning of Redo8087instruction ; the flag is checked, and if it is set the instruction is not ; redone. ; ; "Before" exceptions must be reexecuted because it is ; difficult to determine stack over/underflow if reexecution ; is not performed. Stack over/underflow is signaled by ; an invalid exception. The current algorithm for stack over/ ; underflow detection is as follows: ; ; ... ; ;--------------- ProfBegin EXCEPT lab exceptionhandler ifdef MTHREAD LOADthreadDS ; macro in emthread.asm ; loads thread's DS; trashes AX else ;MTHREAD ifdef standalone XOR AX,AX ; Prepare to access vector, clear flags MOV DS,AX MOV DS,DS:[4*TSKINT+2] ; DS = emulator task data segment elseifdef _COM_ mov ds, [__EmDataSeg] xor ax,ax else mov ax, edataBASE mov ds,ax xor ax,ax endif endif ;MTHREAD MOV AL,ENV_StatusWord[eBP] ; Get 8087 status flags. XOR BH,BH ; Clear out 8087 handling flags ;---------------------------------------------------------------------------- ; ; Can the interrupt be serviced by this routine? Dispatch exceptional ; conditions. ; ; Multi-pass algorithm ; Handle exception and reexcute instruction if necessary ; Loop back to WhileException and handle additional exceptions ; ; AX = status before exception handling ; BX = flag indicating exception handled ; ;---------------------------------------------------------------------------- cmp [ExtendStack], 0 ; check if the extended stack was jne WhileException ; turned off. or bh, Invalid lab WhileException ifndef _NOSTKEXCHLR ; no stack overflow/underflow handler TEST BH,Invalid ; stack over/underflow already handled? JNZ short NotOverUnderflow ; Yes - forget stack over/underflow TEST AL,Invalid ; Invalid exception? JZ short NotOverUnderflow ; No - bypass over/underflow checking OR BH,Invalid ; Indicate stack over/undeflow checked JMP ProcessOverUnderflow ; See about stack over/underflow endif ;_NOSTKEXCHLR lab NotOverUnderflow ; Either the exception was not an invalid or stack over/underflow has ; already been handled. ; check for denormal exception - completely resolved on pass 1 TEST AL,Denormal ; Denormal exception? JZ short NotDenormal ; No - bypass denormal handling JMP ProcessDenormal ; Process the denormal lab NotDenormal ; check for zero divide exception TEST BH,ZeroDivide ; Divide by zero already handled? JNZ short NotZeroDivide ; Yes - bypass divide by zero handling TEST AL,ZeroDivide ; Divide by zero exception? JZ short NotZeroDivide ; No - bypass divide by zero handling OR BH,ZeroDivide ; Indicate divide by zero handled CALL ReDo8087Instruction ; Process divide by zero exception JMP WhileException lab NotZeroDivide ; check for numeric overflow exception TEST BH,Overflow ; Overflow already handled? JNZ short AllExceptionsHandled ; Yes - bypass overflow handling TEST AL,Overflow ; Overflow exception? JZ short AllExceptionsHandled ; No - bypass overflow handling OR BH,Overflow ; Indicate overflow handled JMP ProcessNumericOverflow ; Process numeric overflow lab AllExceptionsHandled ; We have already handled any exceptions which require instruction ; reexecution. ; At this point 8087 instruction reexecution is done. We need ; to extract a little more information for error message ; generation. MOV BL, BYTE PTR UserControlWord ; 8087 exception masks OR BL, 0C0H ; Mask reserved AND BL, 0FDH ; Unmask denormal. DON'T unmask invalid ; here. (Otherwiae user has no way of ; masking invalids.) NOT BL ; complement AND AL, BL ; eliminate all masked exceptions ; from AL TEST AL,Invalid ; Possibly square root of neg? JZ short NotFLDshortorlongNaN ; No - don't set square root flag PUSH AX ; ... Use AX as scratch ... MOV AX,ENV_Opcode[eBP] ; Get the instruction op code AND AH,7 ; Mask off the junk CMP AX,001FAh ; Square root op code? JNE short NotSquareRootError ; No - don't set square root flag POP AX ; ... Restore AX ... OR AL,SquareRootNeg ; Set the square root flag JMP short NotFLDshortorlongNaN ;----------------------------------------------------------------------------- ; Test for invalid exception caused by an FLD of a NaN underflow or overflow. ;----------------------------------------------------------------------------- lab NotSquareRootError ; Next check for FLD of a NaN ; (only happens for SNaNs on ; the 80387; not for 8087/287) MOV AX,ENV_Opcode[eBP] AND AX,0338h ; Mask off the inessential bits CMP AX,0100h ; Check for possible FLD ; of short/long real from memory. ; We are assuming that an invalid ; exception means FLD of a NaN ; since stack over/under-flow ; has already been dealt with. ; (we don't handle FLD ST(n) or ; FLD temp real in this way) POP AX ; ... Restore AX ... JNE short NotFLDshortorlongNaN ; ; (MOD==11 case: no special code) ; We don't handle FLD ST(n) here since it isn't properly ; handled in our stack overlow checking code either and ; it doesn't generate an invalid in the case of an SNaN ; without a stack overflow; FFREE ST(n) will not cause ; an Invalid exception. ; ; FLD TBYTE PTR ... shouldn't cause an Invalid due to a NaN ; XOR AL,Invalid ; Turn off invalid exception. ; There should be a NaN in ST(0); ; we will just leave it there. lab NotFLDshortorlongNaN FCLEX FLDCW ENV_ControlWord[eBP] ; Restore original Control Word lab CleanUpHost or [UserStatusWord],ax ; OR into user status word POP DS POP eBX ADD eSP,ENV_Size ; Point to users BP POP eBP TEST AX,0FFFFh-Reexecuted ; exceptions? JNZ Exceptions8087 ; Process other exceptions as emulator POP eAX ; Now just IRET address on stack ret ; return to OEM interrupt exit routine lab Exceptions8087 ; toss OEM routine return address push eax push ebx mov ebx,esp ; UNDONE - this does not work for 386 mov eax,ss:[ebx+4] ; get original AX mov ss:[ebx+6],eax ; overwrite OEM routine return address pop ebx pop eax ifdef i386 add esp,4 ; remove original AX else add sp,2 ; remove original AX endif JMP CommonExceptions PAGE ;----------------------------------------------------------------------------- ; Test for stack underflow or overflow. ;----------------------------------------------------------------------------- ; There are eight sets of tag bits in the tag word. Each set ; denotes the state of one of the 8087 stack elements. ; 00 - normal ; 01 - true zero ; 10 - special: nan,infinity,unnormal ; 11 - empty ; If all are empty we have underflow, if all are full we have overflow ; There was an invalid exception: check to see if it was stack ; overflow or underflow. ; Register usage in this code block: ; BX = tag word, complemented ; CL = NPX stack ptr ifndef _NOSTKEXCHLR ; no stack overflow/underflow handler lab ProcessOverUnderflow PUSH eSI PUSH eBX ; Make room for local temps PUSH eCX PUSH eDX PUSH eDI MOV BX,ENV_TagWord[eBP] ; Get tag word. MOV CX,ENV_StatusWord[eBP] ; Get status word NOT BX ; Tag zero means empty, else full MOV CL,CH ; Get stack pointer into CL AND CL,038h ; Mask to stack pointer SHR CL,1 SHR CL,1 ; compute number of bits to shift ROR BX,CL ; tag ST(0) in low BL. ; To service stack overflow we must make sure there is an empty space ; above the top of stack before the instruction is reexecuted. If ; after reexecution we again get an invalid exception, then we ; know there was something besides stack overflow causing the invalid ; exception. ; We check for stack overflow by seeing if ST(7) is empty. We make ; the check by testing the complemented, rotated tag word in BX. TEST BH,0C0h ; Possible stack overflow? JZ short StackUnderflowCheck ; No - bypass offloading stack ; ST(7) is not empty, so we may have stack overflow. We verify that ; we have stack overflow by looking at the instruction to be sure ; that it can generate stack overflow (i.e., it puts more stuff on ; the stack than it removes). ; Note that a subset of the 287 instruction set is being decoded ; here; only those instructions which can generate invalid exceptions ; get to this point in the code (see Table 2-14 in the Numeric ; Supplement for list of instructions and possible exceptions). ; ; The instructions which can generate stack overflow are: ; all memory FLDs,FILDs,FBLDs,constant instructions, ; FPTAN and FXTRACT MOV DX,ENV_Opcode[eBP] ; Get the instruction op code XOR DX,001E0h ; Toggle arith, mod and special bits ; Test for mod of 0,1, or 2 (indicates memory operand) TEST DL,0C0h ; Memory operand instruction? JNZ short MemoryFLDCheck ; Yes - go see what kind ; Test bits 5 & 8 of instruction opcode: of remaining instructions, only those ; with stack relative operands do NOT have both of these bits as 1 in the opcode ; (remember these bits are toggled). TEST DX,00120h ; ST Relative Op group? JNZ short StackUnderflowCheck ; Yes - ST Relative Ops ; cannot cause stack overflow ; Test bit 4 of the instruction opcode: of remaining instructions, only the ; transcendentals have this bit set. TEST DL,010h ; Constant or arith instruction? JNZ short TransCheck ; No - must be Transcendental ; Test bit 3 of the instruction opcode: of remaining instructions, only the ; constant instructions have this bit set. TEST DL,008h ; Constant instruction? JNZ short StackOverflowVerified ; Yes, can cause stack overflow ; The instructions which get to this point are FCHS, FABS, FTST and FXAM. ; None of these can cause stack overflow. JMP StackUnderflowCheck ; so go check for stack underflow lab TransCheck ; The instruction was a transcendental. Of the transcendentals, only ; FPTAN and FXTRACT can cause stack overflow, so check for these. CMP DL,012h ; is this FPTAN JE short StackOverflowVerified ; yes, can cause stack overflow CMP DL,014h ; is this FXTRACT JE short StackOverflowVerified ; yes, can cause stack overflow JMP StackUnderflowCheck ; not either one, won't cause overflow lab MemoryFLDCheck TEST DX,00110h ; FLD memory instruction? JNZ short StackUnderflowCheck ; no - go check for stack underflow lab StackOverflowVerified ; ST(7) was not empty and the instruction can cause stack overflow. ; To recover from stack overflow, move ST(7) contents to the ; stack extension area, modifying the tag word appropriately. AND BH,0FFh-0C0h ; Indicate 1st above TOS is free PUSHST ; Let PUSHST make room for value. FDECSTP ; Point to bottom stack element. FSTP TBYTE PTR [eSI] ; Store out bottom stack element. JMP InvalidReexecute ; No - reexecute instruction lab StackUnderflowCheck ; To service stack underflow we must make sure all the operands the ; instruction requires are placed on the stack before the instruction ; is reexecuted. If after reexecution we again get an invalid ; exception, then its due to something else. TEST BL,003h ; Is ST(0) empty? JZ short UFMemoryFLDcheck ; yes - first check for memory FLD JMP ST1EmptyCheck ; No - Let's try to fill ST(1), too. ; We may need it! ; ; This block of code is for making sure that FLD memory operand is not ; among those instructions where stack underflow could occur; this is ; so FLD of SNaN can be detected (under the AllExceptionsHandled ; section) for the case of the 80387. ; lab UFMemoryFLDcheck MOV DX,ENV_Opcode[eBP] ; Get the instruction opcode XOR DX,001E0h ; Toggle arith, mod and special bits TEST DL,0C0h ; Memory operand instruction? JZ ST0Empty ; No - continue underflow processing ; Try to fill ST(0) TEST DX,00110h ; FLD memory instruction? JNZ ST0Empty ; No - continue underflow processing ; Try to fill ST(0) JMP ST1EmptyCheck ; Let's try to fill ST(1), too. ; We may need it! ; Formerly we did JMP InvalidReexecute here; but this caused ; an "invalid" to be reported for instructions with two stack ; operands. (Doing JMP ST1EMptyCheck fixes this bug: ; Fortran 4.01 BCP #1767.) ; ; This fixes the underflow-handling case of instructions ; needing both ST0 and ST1 under the conditions that ST0 ; is full but ST1 is empty. lab ST0Empty ; assume stack underflow since ST(0) is empty and we did not have ; stack overflow OR BX,BX ; Are any registers on the chip in ; use? (BX = 0 if not) JZ short LoadST0FromMemory ; No, load ST(0) from memory stack CALL RotateChip ; yes, then point ST(0) at first ; valid register and update tag in BX JMP ST1EmptyCheck ; go check if ST(1) is empty lab LoadST0FromMemory MOV eSI,[CURstk] ; Get pointer to memory stack CMP eSI,[BASstk] ; Anything in memory to load? JNE short LoadST0 ; Yes, go load it JMP TrueUnderflow ; No, go issue error lab LoadST0 OR BL,003h ; Indicate ST(0) is full FINCSTP ; Avoid altering stack pointer. FLD TBYTE PTR [eSI] ; Load value from memory. POPST ; Let POPST decrement memory pointer. lab ST1EmptyCheck TEST BL,00Ch ; Is ST(1) empty? JNZ short EndST1EmptyCheck ; No - so don't load from memory MOV SI,BX ; move tag word to SI AND SI,0FFF0h ; mask off ST(0),ST(1) OR SI,SI ; Are any of ST(2)-ST(7) in use? ; (SI = 0 if not) JZ short LoadST1FromMemory ; No, try to get ST(1) from memory FSTP TBYTE PTR [REG8087ST0] ; offload ST(0) temporarily SHR BX,1 SHR BX,1 ; ST(1) becomes ST(0) in tag word CALL RotateChip ; get 1st in-use register into ST(1) FLD TBYTE PTR [REG8087ST0] ; reload ST(0) SHL BX,1 SHL BX,1 ; adjust tag word for reloaded ST(0) OR BL,003h ; Indicate ST(0) is full JMP SHORT EndST1EmptyCheck ; ST(0) and ST(1) are full lab LoadST1FromMemory MOV eSI,[CURstk] ; Get pointer to memory stack CMP eSI,[BASstk] ; Anything in memory to load? JE short EndST1EmptyCheck ; No, so don't load it. OR BL,00Ch ; Indicate ST(1) is full FINCSTP ; Point to ST(1) FINCSTP ; Point to ST(2) FLD TBYTE PTR [eSI] ; Load value from memory into ST(1). FDECSTP ; Point to ST(0) POPST ; Let POPST decrement memory pointer. lab EndST1EmptyCheck ; At this point we know that ST(0) is full. ST(1) may or may not be full ; and may or may not be needed. ; Now we look at the instruction opcode and begin categorizing instructions ; to determine whether they can cause stack underflow and if so, whether ; they require ST(0) only or ST(1) as well. MOV DX,ENV_Opcode[eBP] ; Get the instruction op code XOR DX,001E0h ; Toggle arith, mod, and special bits ; Test for mod of 0,1, or 2 (indicates memory operand) TEST DL,0C0h ; Memory operand instruction? JNZ short StackUnderflowServiced ; Yes, then stack underflow cannot ; be a problem since memory instructions ; require at most one stack operand ; and we know that ST(0) is full ; Test bits 5 & 8 of instruction opcode: of remaining instructions, only those ; with stack relative operands do NOT have both of these bits as 1 in the opcode ; (remember these bits are toggled). TEST DX,00120h ; ST Relative Op group? JNZ short STRelativeOpGroup ; Yes - ST Relative Ops lab ConstOrTrans ; Test bit 4 of the instruction opcode: of remaining instructions, only the ; transcendentals have this bit set. TEST DL,010h ; Constant or arith instruction? JNZ short TranscendentalInst ; No - must be Transcendental ; The instructions that get to here are the constant instructions and ; FCHS, FABS, FTST and FXAM. The constant instructions do not have any ; stack operands; the others require ST(0) which we know is valid. ; Therefore, none of the remaining instructions can cause stack underflow. lab StackUnderflowServiced JMP InvalidReexecute ; Stack underflow corrected ; reexecute instruction lab TranscendentalInst ; Transcendentals may require one or two stack elements as operands. ; Here we decide whether or not ST(1) needs to be present. MOV CL,DL ; Need low op code in CL AND CL,00Fh ; Mask to low four bits ; Read the next block of comments column-wise. It shows the transcendental ; instructions represented by each bit in the constant loaded into DX below. ; Note: as it turns out, of the instructions requiring two operands below, ; only FSCALE and FPREM generate invalid exceptions when the second operand ; is missing. ; FFFFFRFFFFFRFFRR ; 2YPPXEDIPYSERSEE ; XLTATSENRLQSNCSS ; M2ATRECCE2REDAEE ; 1XNAARSSMXTRILRR ; ...NCVTT.P.VNEVV ; ....TEPP.1.ET.EE ; .....D.....D..DD MOV DX,0101000011000100b ; 1's for 2 operand instructions SHL DX,CL ; Get corresponding bit into sign JNS short StackUnderflowServiced ; If just ST(0) needed we're O.K. TEST BL,00Ch ; ST(1) full? JNZ short StackUnderflowServiced ; Yes - stack underflow no problem lab STRelativeOpGroup ; The following code block handles the general operand ST(x) even though ; the original code generator only uses ST(0) and ST(1) as operands. ; The current code generator uses ST(x) but will never cause stack underflow ; exceptions. AND DX,00007h ; Mask to relative register number SHL DL,1 ; Compute tag word shift amount MOV CX,DX ; Get amount into CL MOV DX,BX ; Get tag into DX ROR DX,CL ; Shift operand tag into low DL TEST DL,003h ; Is operand register empty? JNZ short InvalidReexecute ; No - go reexecute ; The following conditions could cause a true underflow error to be ; erroneously generated at this point: ; FST ST(x) signals an invalid because ST(0) is empty. ST(0) gets filled ; by the stack underflow recovery code in this handler, but then ; the instruction is classified as an STRelative instruction and the ; above paragraph of code checks if ST(x) is empty. HOWEVER, FST ST(x) does ; not require ST(x) to be empty so a true underflow error should not occur. ; This code should be changed if this situation can ever occur. JMP TrueUnderflow ; true stack underflow ;*** RotateChip - rotate coprocessor registers ; ; ENTRY ; BX: tag word, complemented ; ST(0): empty ; at least one other register on the chip is non-empty ; (or else this routine will loop infinitely) ; ; RETURNS ; BX: updated tag word, complemented ; ST(0): non-empty ; ; DESCRIPTION ; This routine rotates the registers on the coprocessor ; until the first in-use register is in ST(0). This ; will correct a stack underflow exception which has been ; caused by old model code encountering a gap of free ; registers created by new model code. The complemented ; tag word is also updated appropriately. ; lab RotateChip ROR BX,1 ; Rotate tag word ROR BX,1 FINCSTP ; Point to new ST(0) TEST BX,00003h ; Is this register empty? JZ short RotateChip ; No, go rotate again RET lab TrueUnderflow OR AH,StackUnderflow/256 ; indicate true stack underflow MOV BYTE PTR ENV_StatusWord[eBP],0 ; Clear exceptions FLDENV WORD PTR [eBP] ; Restore old environment. POP eDI POP eDX POP eCX POP eBX POP eSI JMP CleanUpHost ; Leave exception handler. lab InvalidReexecute AND AL,0FFH-Invalid ; Reset invalid flag. CALL ReDo8087Instruction ; Was invalid so redo instruction. POP eDI POP eDX POP eCX POP eBX POP eSI JMP WhileException endif ;_NOSTKEXCHLR ;---------------------------------------------------------------------------- PAGE lab ProcessDenormal ; Correct 8087 bug. The FLD instruction signals a denormal ; exception AFTER it has completed. Reexecuting FLD for a ; denormal exception would thus mess up the 8087 stack. INTEL ; documentation states denormal exceptions are BEFORE ; exceptions, so there is a contradiction. To avoid reexecution ; of FLD we do as follows: And op code with 138H to mask out ; MOD, RM, ESC and memory format bits. Compare with 100H to ; distinguish FLD from other instructions which could possibly ; generate a denormal exception. or byte ptr [UserStatusWord],Denormal ; set denorm bit push ecx mov cx,ENV_Opcode[eBP] ; see if we have a reg,reg operation and cl, bMOD cmp cl, bMOD ; if MOD = 11b then we have a reg,reg op je notMemOpDenormal mov cx,ENV_Opcode[eBP] and cx, not (0fc00h or bMOD or bRM) ; remove escape, OpSizeBit, MOD and R/M cmp cx,0008h ; check for FMUL real-memory je short isMemOpDenormal cmp cx,0010h ; check for FCOM real-memory je short isMemOpDenormal and cl,30h ; clear low opcode bit cmp cx,0030h ; check for FDIV/FDIVR real-memory jne short notMemOpDenormal ; have FDIV/FDIVR real-memory ; have FMUL real-memory ; have FCOM real-memory ; ; do the following steps ; 1. free ST(7) if not free to avoid stack overflow ; 2. change instruction to FLD real-memory and redo ; 3. normalize TOS ; 4. change instruction to FMUL or FDIV[R]P ST(1),ST and redo lab isMemOpDenormal TEST BH,0C0h ; 1. Possible stack overflow? JZ short nostkovr ; No - bypass offloading stack AND BH,0FFh-0C0h ; Indicate 1st above TOS is free PUSHST ; Let PUSHST make room for value. FDECSTP ; Point to bottom stack element. FSTP TBYTE PTR [eSI] ; Store out bottom stack element. lab nostkovr mov cx,ENV_Opcode[ebp] ; 2. get original instruction push cx ; save it for later and cx,0400h add cx,0104h ; changed to FLD real DS:[SI] mov ENV_Opcode[ebp],cx ; change for redo call ReDoIt ; do FLD denormal call normalize ; 3. normalize TOS pop cx ; 4. restore original instruction and cx,0038h ; reduce to operation cmp cl,08h ; is it FMUL je short isFMUL ; yes cmp cl,10h ; is it FCOM je short isFCOM ; yes xor cl,08h ; must be FDIV[R] - flip R bit lab isFMUL or cx,06C1h ; or to FoprP ST(1),ST mov ENV_Opcode[ebp],cx ; change for redo call ReDo8087Instruction ; do FDIV[R]P ST(1),ST jmp short denormaldone ; done with FDIV[R] denormal lab notMemOpDenormal MOV cx,ENV_Opcode[eBP] and cx, 0738h cmp cx, 0328h je short noredo ; check for FLD long double AND cx,0138H CMP cx,0100H ; check for FLD float/double JZ short noredo CALL ReDo8087Instruction ; redo other instructions lab noredo call normalize jmp short denormaldone ; FCOM is a little more complicated to recover because of status ; ; FCOM is like FDIV in that the operands need to be exchanged ; and the value loaded onto the chip needs to be popped. ; ; This routine is like a mini ReDo8087Instruction lab isFCOM OR AH,Reexecuted/256 ; Flag instruction reexecuted FCLEX ; clear exceptions FXCH ; swap ST(0) and ST(1) FCOM ST(1) ; so that ST(1) is the "source" FXCH FSTP ST(0) ; toss stack entry FSTSW [NewStatusWord] ; get status word FWAIT OR AL,BYTE PTR [NewStatusWord] ; Include new with unhandled exceptions lab denormaldone pop ecx AND AL,0FFh-Denormal ; clear denormal exception jmp WhileException lab normalize fstp tbyte ptr ENV_Temp[ebp] ; save denormal/unnormal fwait mov cx,ENV_Temp[ebp+8] ; get old exponent test cx,07FFFh ; test for zero exponent jz short isdenormal ; denormal temp real test byte ptr ENV_Temp[ebp+7],80h ; test for unnormal jnz short isnormal ; no - skip normalization fild qword ptr ENV_Temp[ebp] ; load mantissa as integer*8 fstp tbyte ptr ENV_Temp[ebp] ; save mantissa fwait cmp word ptr ENV_Temp[ebp+8],0 ; check for 0.0 je short isdenormal ; yes - we had a pseudo-zero sub cx,403Eh ; exponent adjust (3fff+3f) add ENV_Temp[ebp+8],cx ; add to mantissa exponent lab isnormal fld tbyte ptr ENV_Temp[ebp] ; reload normalized number ret lab isdenormal xor cx,cx ; make it into a zero mov ENV_Temp[ebp],cx mov ENV_Temp[ebp+2],cx mov ENV_Temp[ebp+4],cx mov ENV_Temp[ebp+6],cx mov ENV_Temp[ebp+8],cx jmp isnormal ; reload it as zero PAGE lab ProcessNumericOverflow ; We must reexecute for numeric overflow only if the instruction ; was an FST or FSTP. This is because only these instructions ; signal the exception before the instruction is executed. ; If we reexecute under other conditions the state of the 8087 ; will be destroyed. Only memory operand versions of FST and ; FSTP can produce the Overflow exception, and of all the ; non-arithmetic memory operand instructions, only FST and ; FSTP produce overflow exceptions. Thus it is sufficient ; to reexecute only in case of non-arithmetic memory operand ; instructions. To check for these and the op code with 001C0H ; to mask down to the arith and MOD fields, flip the arith ; bit by xoring with 00100H and if the result is below 000C0H ; then we have a non-arithmetic memory operand instruction. PUSH eAX MOV AX,ENV_Opcode[eBP] AND AX,001C0H XOR AH,001H CMP AX,000C0H POP eAX JAE short NumericOverflowRet CALL ReDo8087Instruction lab NumericOverflowRet JMP WhileException PAGE ;---------------------------------------------------------------------------- ; Reexecute aborted 8087 instruction, and include any exceptions in ENV [BP] ;---------------------------------------------------------------------------- ifdef WINDOWS lab ReDo8087InstructionRet ret endif lab ReDo8087Instruction TEST AH,Reexecuted/256 ; Already reexecuted? JNZ short ReDo8087InstructionRet ; If so don't do it again OR AH,Reexecuted/256 ; Flag instruction reexecuted lab ReDoIt PUSH DS PUSH eDI PUSH eSI PUSH eCX PUSH eBX FCLEX ; clear error summary flags ifdef WINDOWS mov di, ss ; assume SS mov bx, __WINFLAGS test bx, WF_PMODE jz SkipSSAlias push es ; push ax ; CHICAGO needs 32-bit register saves ... ; push dx push eax ; for CHICAGO push ebx ; for CHICAGO push ecx ; for CHICAGO push edx ; for CHICAGO push ebp ; for CHICAGO push esi ; for CHICAGO push edi ; for CHICAGO push ss call ALLOCDSTOCSALIAS pop edi ; for CHICAGO mov di, ax ; pop dx ; CHICAGO needs 32-bit register restores ; pop ax pop esi ; for CHICAGO pop ebp ; for CHICAGO pop edx ; for CHICAGO pop ecx ; for CHICAGO pop ebx ; for CHICAGO pop eax ; for CHICAGO pop es or di, di jz ReExecuteRestoreRet lab SkipSSAlias else ;not WINDOWS ifdef DOS3and5 mov di,ss ; assume SS cmp [protmode],0 ; check if protect mode je noSSalias ; no - don't get SS alias endif ;DOS3and5 ifdef DOS5 ifdef SQL_EMMT push ax push ss ; The SQL server may have switched stacks push ds ; so update SSalias. mov ax,offset SSalias push ax os2call DOSCREATECSALIAS pop ax endif ;SQL_EMMT mov di,[SSalias] ; Get segment alias to stack endif ;DOS5 endif ;not WINDOWS ifdef DOS3and5 lab noSSalias endif ;DOS3and5 MOV CX,ENV_Opcode[eBP] ; Get aborted 8087 instruction. MOV BX,CX ; Copy instruction. AND CH,07H ; Clear upper 5 bits. OR CH,0D8H ; "OR" in escape code. AND BL,0C0H ; Mask to MOD field. XOR BL,0C0H ; If MOD = "11" (no memory operand) JZ short REEXECUTE ; then address mode modification code ; must be bypassed. AND CL,38H ; Clear MOD and RM fields, OR CL,4H ; Turn on bits in MOD and RM fields ; to force DS:[SI+0] addressing. LDS SI,ENV_OperandPointer[eBP] ; DS:SI <-- operand address lab REEXECUTE XCHG CH,CL ; convert to non-byte swapped ; code format ; ; Stack restart method. Restart instruction in interrupt stack ; frame. Code is reentrant. ; ifdef WINDOWS mov ENV_CallSegment[ebp],di ; Code segment alias to stack elseifdef DOS5 mov ENV_CallSegment[ebp],di ; Code segment alias to stack else MOV ENV_CallSegment[eBP],SS ; Stack segment endif LEA eDI,ENV_CallFwait[eBP] ; Offset to code in stack. MOV ENV_CallOffset[BP],eDI MOV BYTE PTR ENV_CallFwait[eBP],09BH ; FWAIT. MOV ENV_Call8087Inst[eBP],CX ; 8087 instruction. MOV BYTE PTR ENV_CallLongRet[eBP],0CBH ; Intra segment return. CALL DWORD PTR ENV_CallOffset[eBP] ; Reexecute instruction. ifdef WINDOWS mov bx, __WINFLAGS test bx, WF_PMODE jz ReExecuteRestoreRet push es ; if in PMODE, free alias ; push ax ; CHICAGO needs 32-bit register saves ; push dx push eax ; for CHICAGO push ebx ; for CHICAGO push ecx ; for CHICAGO push edx ; for CHICAGO push ebp ; for CHICAGO push esi ; for CHICAGO push edi ; for CHICAGO push ENV_CallSegment[eBP] call FREESELECTOR ; pop dx ; CHICAGO needs 32-bit register restores ; pop ax pop edi ; for CHICAGO pop esi ; for CHICAGO pop ebp ; for CHICAGO pop edx ; for CHICAGO pop ecx ; for CHICAGO pop ebx ; for CHICAGO pop eax ; for CHICAGO pop es endif ;WINDOWS lab ReExecuteRestoreRet POP eBX POP eCX POP eSI POP eDI POP DS ifdef SQL_EMMT push ax ; free the ss alias because we always push [SSalias] ; get a new one for the SQL_EMMT os2call DOSFREESEG pop ax endif ;SQL_EMMT FSTSW [NewStatusWord] ; 8/18/84 GFW need proper DS set FWAIT OR AL,BYTE PTR [NewStatusWord] ; Include new with unhandled exceptions ifndef WINDOWS lab ReDo8087InstructionRet endif RET ProfEnd EXCEPT