8087 Emulation on 8086 Systems

A deep dive into the software emulation mechanisms of the 8087 FPU on 8086/8088 systems, highlighting the complex cooperation between compilers, linkers, and run-time libraries.
Not too long ago I had a need and an opportunity to re-acquaint myself with the mechanism used for software emulation of the 8087 FPU on 8086/8088 machines.
As mentioned elsewhere, the 8086 CPU (1978) had a generic co-processor interface first utilized by the Intel 8089 I/O processor (1979) and later the Intel 8087 FPU (1980), initially called the Numeric Processor Extension or NPX.
The 8087 was a somewhat expensive add-on, assuming that a given system actually had a socket to plug the 8087 into (IBM PCs did, but other 8086/8088 systems did not necessarily have one). There was a largish class of software which could significantly benefit from the 8087 (e.g. spreadsheets), but in the era of shrink-wrapped software, there was a significant incentive to ship software which could use an 8087 when present, yet would still run on a bare 8086/8088 machine with no FPU.
There was also a desire to develop and test floating-point software without having to install an 8087 into every system. Given the initial limited availability of 8087 chips, it was in Intel’s best interest to give developers a way to write 8087 software without requiring 8087 hardware.
Intel released the E8087 software emulation package together with the 8087 chip. This is evidenced by the original Numerics Supplement to The 8086 Family User’s Manual from July 1980, Intel order no. 121586-001. The Numerics Supplement outlines how the E8087 package works. Actually there were two packages — the full E8087 library, and also a “partial” PE8087 library which implemented just enough functionality for Intel’s PL/M language tools. Intel’s PL/M compiler was the first high-level language translator capable of utilizing the 8087.
Because the 8086 had no facility for emulating an FPU (unlike the 80286 and later processors), the emulation mechanism was somewhat complex and required tight cooperation of assemblers/compilers, linkers, and run-time libraries.
Assembler/Compiler – Intel Original
The assembler or compiler generated “emulatable” 8087 code. The translator in fact produced normal 8086/8087 code, but the object modules included special fix-ups for every 8087 ESC instruction and for (F)WAIT.
Early on, Intel established the convention that the WAIT mnemonic was translated directly to the WAIT opcode, while the FWAIT mnemonic could be emulated.
A key fact is that the language translator did not directly produce 8087 emulation code. It only prepared object modules for emulation while emitting regular 8087 instructions, and the actual decision whether to emulate or not was made at link time.
Linker – Intel Original
During the linking process, the decision to emulate or not was made. The user could link with a no-emulation library (8087.LIB), in which case the linker effectively left the object code alone.
Much more interesting things happened when linking with emulation libraries (E8087.LIB or PE8087.LIB). In that case, the special fix-ups caused the linker to replace the 90/Dx (NOP/ESC) or 98/Dx (WAIT/ESC) sequences with software INT instructions.
In Intel’s original implementation, ESC opcodes D8h-DFh were replaced with INT 18h-1Fh, as shown in the Intel ASM86 Reference Manual, order no. 121703-003 (1983).
Note that eight separate interrupt vectors were required to replace eight possible ESC opcode bytes. The emulator may (and likely does) use a single routine to handle all eight interrupt vectors, but the 8 vectors are needed to preserve the 3 bits of FPU opcode information from the ESC instruction.
Microsoft’s DOS Implementation
Intel’s 8087 emulation mechanism was adopted by Microsoft and with a few changes implemented in their DOS development tools. It was also used by several other vendors of DOS development tools (Borland, Watcom, and others).
For obvious reasons, Microsoft needed to change the range of software interrupts used by the 8087 emulator. Instead of interrupts 18h-1Fh, the DOS emulator uses vectors 34h-3Dh. Yes, that’s 10 vectors instead of 8. While Intel replaced WAIT instructions with NOP for emulation, Microsoft emulated WAIT instructions as well, and Microsoft also had a provision to emulate FPU instructions with ES segment override.
Emulator + 8087
Microsoft added one significant improvement compared to Intel’s original emulator. If the program with a built-in emulator was run on a system with an 8087 present, the emulator detected that during startup. Whenever an emulated instruction was executed (via INT 34h-3Dh), the emulator replaced the software INT instruction with the original NOP or WAIT plus the corresponding ESC opcode, and returned to execute the real floating-point instruction.
This mechanism had a minimal performance impact (emulated instructions were replaced with real 8087 instructions the first time they were executed) and ensured that programs with the emulator ran at effectively 100% speed on systems with an 8087, yet the same binary could still run on a system with no FPU.
This was often used for binaries shipped to end users, since the program could take advantage of an 8087 but didn’t require it.
MASM Implementation
The oldest implementation of Microsoft’s FPU emulation mechanism I could find was in MASM 1.12 and 1.25 from 1983. To assemble FPU instructions, the /R switch must be used. To generate emulation-ready code, the /E switch must be used as well. The /R /E switch combination causes MASM to produce almost the same object code as /R alone, but adds fix-ups to all FPU instructions and FWAIT. Problems with segment overrides were clearly noticed and fixed in Microsoft MASM 3.0 (1984).
Source: Hacker News
















