Rendered at 23:36:40 GMT+0000 (Coordinated Universal Time) with Cloudflare Workers.
garganzol 1 days ago [-]
100x speed improvement of math operations by 8087 is not an overestimation. The difference for apps relying on math was crazy back then. I experienced this first-hand on my 80286 machine, where it was 3-second vs 300-second calculation results.
One neat feature of 8087 instruction set is that it can be interspersed with x86 instructions in the code stream, giving you a simultaneous access to two processor chips working in parallel. This combo forms a real asymmetrical multi-processor system with certain opportunities for hardware-assisted code parallelization. If a thoughtful instruction scheduling is used, floating operations executed by 8087 work in parallel with the usual integer x86 code.
rwmj 10 hours ago [-]
I remember getting a 80387 (coprocessor for the 80386) and POVRAY renders going from running for days to still many minutes but you could sit and watch it.
mgaunard 1 days ago [-]
Any modern processor has different execution ports specialized in different things and replicated a different number of times, and all of them can execute instructions in parallel.
It schedules to these transparently for you, that's known as superscalar execution. To maximize occupation, out-of-order execution and simultaneous multithreading are used.
Sharlin 1 days ago [-]
Sure, but this was five (or six?) generations before actual superscalar x86 processors.
vlovich123 1 days ago [-]
Three - after 8086 you had 80286, 80386, 80486 and then Pentium (superscalar).
dpq 20 hours ago [-]
80186 is often forgotten to have existed because it didn't see much success in the market / because IBM skipped it and went with 80286 for the AT, but it did exist.
hyperman1 10 hours ago [-]
80186 Was not IBM PC compatible. It had e.g. a PIC, DMA and timer built-in, and these were incompatible with the chips in a 8086 based IBM PC.
There were a few new instructions, too, mostly closing holes. You could left or right shift with a constant, while the 8086 had only 1 or the CX register. I think mul also gained a constant.
The fact that Intel released a CPU that could not be put in a PC probably indicates how low they estimated the survivability of the PC.
icedchai 8 hours ago [-]
There were "PC compatibles" that used it, like the Tandy 2000. You are correct in that they may not have been entirely compatible, but they did exist.
vlovich123 19 hours ago [-]
The 80186 didn’t really introduce anything new architecturally. It’s basically an 8086 with a few more chips bundled on-die. 80286 however introduced protected mode, expanded the address size to 24 bit, hardware enforce memory protection, multitasking, etc.
pkaye 19 hours ago [-]
I believe 80186 was for embedded systems.
librasteve 4 hours ago [-]
nope
Sharlin 17 hours ago [-]
Four because you have to count 8086 itself (486 was one, not zero, geverations before the Pentium and so on).
fulafel 18 hours ago [-]
Transparent scheduling of superscalar execution was a later advance in microprocessors, termed out of order execution. Apart from micros both did come out around the same time in the mid-1960s.
In the x86 microarchitectures superscalar came in Pentium and OoO got introduced in Pentium Pro.
(Superscalar is just having >1 pipelines, which at its introduction meant needing to manually schedule your code very carefully to take advantage of it absent the OoO execution. For example the frequently posted-about Doom optimizations and talk of the u and v pipes are about this. The scheduling didn't happen transparently in early superscalars, at best the cpu automatically stalled, and some archs (eg MIPS, i860, TI C3x) even visibly punted hardware detection of pipeline hazards and required the code to just not go there, see load delay slots and branch delay slots.
)
bell-cot 12 hours ago [-]
> 100x speed improvement ...
Vs. Ken's Blog says "up to 100 times", and Wikipedia gives a lower estimate.
Theory: Your 100X experience compared Intel's "exact emulation" code (noted in the article) with native x87. That emulation would have to cover the myriad x87 oddities and corner cases which Ken describes. Vs. Ken's & Wikipedia's are comparing x87 to various "good enough" 8088 floating point libraries - so naturally much faster than Intel's exact code.
(And yes, speed might have been a low priority for the team writing Intel's emulator.)
cogman10 1 days ago [-]
x87 is such a weird architecture. It was designed the same way you'd design a chip for a scientific calculator. Heck, It's almost a perfect fit for an HP RPN calculator.
But for a compiler to target, it's just so painful. It's so different from almost all other ways CPUs work. There's a reason both CPU and compilers prefer to avoid x87 when possible and use regular SIMD (SSE/AVX) instead.
Also, the arbitrary "Oh, and the registers are 80 bits wide" is also just one of those weird "Where did that number come from?".
jcranmer 1 days ago [-]
> But for a compiler to target, it's just so painful. It's so different from almost all other ways CPUs work. There's a reason both CPU and compilers prefer to avoid x87 when possible and use regular SIMD (SSE/AVX) instead.
The x87 ISA is essentially a one-address stack-based ISA (so unlike a pure stack ISA, you can reference another value on the stack without having to introduce something like a dup instruction). Which honestly isn't particularly painful to work with for a compiler; it's not usual, but there are other ISAs that are also stack-based (the JVM bytecode is the one that most immediately comes to mind).
The actual weirdness of x87, what makes all the compilers run away from it, is that the only values you can have on the stack are 80-bit extended-precision types. But people don't use those types in their code, they use 32-bit and 64-bit single and double precision, and compilers largely implemented these types by pretending that the x87 just used those value sizes in the first type (the only ones to actually get it correct that I'm aware of are Java's strictfp and Intel's icc, although the latter is merely just correctly implementing FLT_EVAL_METHOD==2). The end result is that compilers caused code to have essentially random and largely uncontrollable precision changes, which pissed a lot of users off, and the SSE units having regular scalar proper single and double precision types made it easier for compilers to switch to that rather than introducing the proper sequences to compile for x87.
ack_complete 1 days ago [-]
There is a significant difference between a stack-based ISA and a stack-based bytecode. In bytecode, it's fine or even a requirement to empty the stack between loop iterations. The JIT will then enregister variables across the loop as appropriate.
With x87, however, that causes extra overhead from loads and stores that's best avoided. Unused stack space can be used to cache frequently used variables, but as operations must use ST(0) as one parameter, FXCH instructions must be used to swap around variables. Matching the x87 stack state on entry and exit of the loop is tricky and compilers historically have had trouble doing it. Different FPUs also differed on the efficiency of FXCH so there were often situations where a particular arrangement would double the speed of a routine on one CPU model and halve it on another.
cogman10 1 days ago [-]
Not to mention the size difference as well. The JVM stack is 2^16 in size while x87 has 8.
The java compiler can practically pretend like the stack is infinite in size while a compiler dealing with x87 has to contend with spillage in all but the most trivial of algorithms.
matja 1 days ago [-]
> Also, the arbitrary "Oh, and the registers are 80 bits wide" is also just one of those weird "Where did that number come from?".
One of the features that was advertised (mentioned in the iAPX 86, 88, 186 Microprocessors Part II book (July 1984)) was the ability to do exact arithmetic on integers up to 2^64, which is possible due to the 64-bit mantissa used in the 80-bit format.
mortja 1 days ago [-]
So in a sense the 8087 was the first 64 bit CPU?
matja 1 days ago [-]
I wouldn't say so, because it was a co-processor: e.g., it depended on the 8086 to even generate addresses for memory operations.
I think the IBM 7030 Stretch CPU (from 1964) would be a likely contender for being one of the first 64-bit CPUs.
phire 12 hours ago [-]
Also, even if ignored the fact that it was a co-processor, we don't generally count the width of the floating point and vector registers.
Otherwise most modern CPUs would be labeled as either 256-bit or 512-bit.
These days we generally label CPUs based on the width of the general purpose registers (though, it gets messy with things like the 68000). I personally suspect we won't ever see GPRs wider than 64 bits.
kens 1 days ago [-]
The Cray-1 (1976) was probably the first 64-bit CPU.
I forgot about the IBM 7030 Stretch (1961), which was also 64 bits. The NORC (Naval Ordnance Research Calculator) (1954) had 16 decimal digits, which is sort of 64 bits.
kragen 8 hours ago [-]
Oh, that's a good point. I didn't know about the NORC.
jrdres 5 hours ago [-]
It may have designed as a stack to better decoding math expressions. Or they may have used stacks because Intel liked stacks at the time.
It's known that the 8087 was designed to attach to either the 8087 or Intel's 32-bit chip the 432. The contemporary rumor is it is in fact the floating point unit FROM the 432, a money-is-no-object project to make the "ultimate" 32-bit chips. The 432 itself had only stack-based registers, had all kinds of weird-length instructions, had to be split across multiple chips, and locked those registers away from programmers who were only allowed a slow high-level language to program it. Only when 432 was clearly failing was also paired to the 8086/88--a chip that itself was a crash project because of delays in the 432. Adding the very different 8087 to the 8088 was described as pairing a race car engine into a poky Volksagen bug.
On the other hand, getting floating point right in the 8087 was absolutely worth the effort. You can see the comparisons in a 1983 issue of Hal's newsletter on the Savage benchmark: http://www.easy68k.com/paulrsm/dg/dg26.htm. Go to "Timing Conclusions" and particularly "Error Conclusions" and beyond.
rwmj 10 hours ago [-]
For compilers yes, but if you look at the 8086 demoscene where presumably most things are hand-coded, 8087 instructions allow incredible code compression. eg https://www.pouet.net/prod.php?which=78045
em3rgent0rdr 1 days ago [-]
80-bit wide registers isn't really arbitrary if you consider that the bulk of the floating point number is a 64-bit significand (and the signifiand ALU makes sense as power-of two) and that you don't need as many bits for exponent (it would be wasteful to go to the next power of two up). Memory is stored as 8-bit bytes as the lowest addressable unit, and so the question would be how many extra bytes the number should take, and 80 bits is a nice integer number of 10 bytes.
dzaima 3 hours ago [-]
Except the ALU isn't actually 64-bit, it's 67-bit as per article, extra bits for rounding. I'd imagine it was just taken for "prettiness", with 15 bits for exponent being basically reasonable. (maybe some algorithms which double precision per iteration would like it being a power of two? but any such probably vary significantly on initial estimate precision anyway)
mitxela 14 hours ago [-]
There is no reason that has to be a power of 2.
bonzini 16 hours ago [-]
It only has 2048 opcodes available. A one-operand register or memory operand operation takes 32, while two register operands would take twice as many. Loads and stores have to specify the memory format (three floating point formats, BCD, word, 64-bit integer), so each instruction used 120 encodings or so even with a single operand; loads and stores alone would use almost all the opcode space if they also had to include the destination register.
In other words there simply isn't room in the encoding to specify two operands, so they went for the stack model.
Sharlin 1 days ago [-]
There's also the fact that for all intents and purposes, the real floating-point unit of any x86 in the last 20 years is the SIMD unit, and legacy x87 instructions are emulated on top of that.
cogman10 22 hours ago [-]
I don't believe so.
The issue is that x87 is 80bit floats which is awkward. As such, it still requires dedicated hardware.
Intel has proposed and abandoned pushing a new x86 architecture [1] which tweaks x86 instructions to fit better with the reality that everything is 64bit now. Part of that proposal was to make x87 work with 64bit floats instead of 80bit floats (which would have allowed it to share the same floating point units as the SIMD instructions).
One exception that does come to mind, is .Net Framework on x86. AFAIR that didn't use SSE or SSE2. (In x64 mode it did however, since those were part of the baseline for x64)
ack_complete 18 hours ago [-]
Strange, because .NET was specifically designed to be a JITted environment and taking advantage of SSE2 when available would ordinarily be an advantage of a JIT. But sure enough, .NET 4.0 x86 still uses x87 instructions for math. It's not even good x87, this is surprisingly bad:
I've looked at some early calculators and they are a whole different world of weirdness. They used decimal arithmetic (BCD) because it's a lot easier than converting between binary and decimal. The first calculators were serial, with a 1-bit adder and shift registers and bits constantly in motion. The Sinclair Scientific calculator used TI's strange 4-bit architecture along with terrible algorithms.
bell-cot 14 hours ago [-]
> But for a compiler to target...
To what degree did Intel assume their target market was either using hand-coded assembly, or written-for-x87 code with x87-specific compilers? Memory was not cheap in 1980, ditto 8087 chips, and oddities like the x86's 64K segments would discourage anyone trying to "just recompile" existing programs for x86/x87.
librasteve 3 hours ago [-]
at Weitek, we built the 1167 at about the same time as the 80287, we lined up compiler guys such as Green Hills to support our alternative coprocessor ASM (the instruction bus was memory mapped in the 80286 address space IIRC) … several folks would hand code inner loops eg for LINPACK, but already by this time the compiler tech was important to fully use the pipeline … loop unrolling, dead code elimination and so on.
kens 1 days ago [-]
Author here for your 8087 questions...
bonzini 1 days ago [-]
That's a really vertical microcode. It looks more like a specialized assembly than microcode. I guess it makes sense, since the algorithms are so complex and executing one microinstruction per cycle (is that correct?) already provides almost an order of magnitude performance improvement.
kens 1 days ago [-]
Yes, it's one microinstruction per cycle, except there is a 1-cycle delay for branches, adds, and shifts. And some micro-instructions loop, so they can take a bunch of cycles.
pechay 1 days ago [-]
Great work! I love to read your articles.
I'm curious to know - you say Intel's 8087 emulation code was a bit of a lump at 16KB, do you know if it emulated the 8087 microcode state machine or did it use a different strategy?
kens 1 days ago [-]
I think the emulation code was a rewrite in 8086 assembly language. An 8087 microcode emulator would be slow and difficult. One of the Opcode Collective people is looking at the emulator now, so there may be more details later. Intel claimed that the emulator completely and exactly duplicated the 8087 functionality, so it would be interesting to see if it is 100% accurate or if they missed any corner cases.
agumonkey 1 days ago [-]
no questions, just thanks
1 days ago [-]
finn888 22 hours ago [-]
The jump from a simple scale instruction to 140 micro-instructions makes the special-case machinery much more concrete.
One neat feature of 8087 instruction set is that it can be interspersed with x86 instructions in the code stream, giving you a simultaneous access to two processor chips working in parallel. This combo forms a real asymmetrical multi-processor system with certain opportunities for hardware-assisted code parallelization. If a thoughtful instruction scheduling is used, floating operations executed by 8087 work in parallel with the usual integer x86 code.
It schedules to these transparently for you, that's known as superscalar execution. To maximize occupation, out-of-order execution and simultaneous multithreading are used.
There were a few new instructions, too, mostly closing holes. You could left or right shift with a constant, while the 8086 had only 1 or the CX register. I think mul also gained a constant.
The fact that Intel released a CPU that could not be put in a PC probably indicates how low they estimated the survivability of the PC.
In the x86 microarchitectures superscalar came in Pentium and OoO got introduced in Pentium Pro.
(Superscalar is just having >1 pipelines, which at its introduction meant needing to manually schedule your code very carefully to take advantage of it absent the OoO execution. For example the frequently posted-about Doom optimizations and talk of the u and v pipes are about this. The scheduling didn't happen transparently in early superscalars, at best the cpu automatically stalled, and some archs (eg MIPS, i860, TI C3x) even visibly punted hardware detection of pipeline hazards and required the code to just not go there, see load delay slots and branch delay slots. )
Vs. Ken's Blog says "up to 100 times", and Wikipedia gives a lower estimate.
Theory: Your 100X experience compared Intel's "exact emulation" code (noted in the article) with native x87. That emulation would have to cover the myriad x87 oddities and corner cases which Ken describes. Vs. Ken's & Wikipedia's are comparing x87 to various "good enough" 8088 floating point libraries - so naturally much faster than Intel's exact code.
(And yes, speed might have been a low priority for the team writing Intel's emulator.)
But for a compiler to target, it's just so painful. It's so different from almost all other ways CPUs work. There's a reason both CPU and compilers prefer to avoid x87 when possible and use regular SIMD (SSE/AVX) instead.
Also, the arbitrary "Oh, and the registers are 80 bits wide" is also just one of those weird "Where did that number come from?".
The x87 ISA is essentially a one-address stack-based ISA (so unlike a pure stack ISA, you can reference another value on the stack without having to introduce something like a dup instruction). Which honestly isn't particularly painful to work with for a compiler; it's not usual, but there are other ISAs that are also stack-based (the JVM bytecode is the one that most immediately comes to mind).
The actual weirdness of x87, what makes all the compilers run away from it, is that the only values you can have on the stack are 80-bit extended-precision types. But people don't use those types in their code, they use 32-bit and 64-bit single and double precision, and compilers largely implemented these types by pretending that the x87 just used those value sizes in the first type (the only ones to actually get it correct that I'm aware of are Java's strictfp and Intel's icc, although the latter is merely just correctly implementing FLT_EVAL_METHOD==2). The end result is that compilers caused code to have essentially random and largely uncontrollable precision changes, which pissed a lot of users off, and the SSE units having regular scalar proper single and double precision types made it easier for compilers to switch to that rather than introducing the proper sequences to compile for x87.
With x87, however, that causes extra overhead from loads and stores that's best avoided. Unused stack space can be used to cache frequently used variables, but as operations must use ST(0) as one parameter, FXCH instructions must be used to swap around variables. Matching the x87 stack state on entry and exit of the loop is tricky and compilers historically have had trouble doing it. Different FPUs also differed on the efficiency of FXCH so there were often situations where a particular arrangement would double the speed of a routine on one CPU model and halve it on another.
The java compiler can practically pretend like the stack is infinite in size while a compiler dealing with x87 has to contend with spillage in all but the most trivial of algorithms.
One of the features that was advertised (mentioned in the iAPX 86, 88, 186 Microprocessors Part II book (July 1984)) was the ability to do exact arithmetic on integers up to 2^64, which is possible due to the 64-bit mantissa used in the 80-bit format.
I think the IBM 7030 Stretch CPU (from 1964) would be a likely contender for being one of the first 64-bit CPUs.
Otherwise most modern CPUs would be labeled as either 256-bit or 512-bit.
These days we generally label CPUs based on the width of the general purpose registers (though, it gets messy with things like the 68000). I personally suspect we won't ever see GPRs wider than 64 bits.
It's known that the 8087 was designed to attach to either the 8087 or Intel's 32-bit chip the 432. The contemporary rumor is it is in fact the floating point unit FROM the 432, a money-is-no-object project to make the "ultimate" 32-bit chips. The 432 itself had only stack-based registers, had all kinds of weird-length instructions, had to be split across multiple chips, and locked those registers away from programmers who were only allowed a slow high-level language to program it. Only when 432 was clearly failing was also paired to the 8086/88--a chip that itself was a crash project because of delays in the 432. Adding the very different 8087 to the 8088 was described as pairing a race car engine into a poky Volksagen bug.
Rumors mongered in 1982 by Hal Hardenburgh: http://www.easy68k.com/paulrsm/dg/dg06.htm, "Page 4" section.
On the other hand, getting floating point right in the 8087 was absolutely worth the effort. You can see the comparisons in a 1983 issue of Hal's newsletter on the Savage benchmark: http://www.easy68k.com/paulrsm/dg/dg26.htm. Go to "Timing Conclusions" and particularly "Error Conclusions" and beyond.
In other words there simply isn't room in the encoding to specify two operands, so they went for the stack model.
The issue is that x87 is 80bit floats which is awkward. As such, it still requires dedicated hardware.
Intel has proposed and abandoned pushing a new x86 architecture [1] which tweaks x86 instructions to fit better with the reality that everything is 64bit now. Part of that proposal was to make x87 work with 64bit floats instead of 80bit floats (which would have allowed it to share the same floating point units as the SIMD instructions).
[1] https://www.intel.com/content/www/us/en/developer/articles/t...
But clearly it has some support for using SSE2 when available, because it does use it for zeroing memory:
To what degree did Intel assume their target market was either using hand-coded assembly, or written-for-x87 code with x87-specific compilers? Memory was not cheap in 1980, ditto 8087 chips, and oddities like the x86's 64K segments would discourage anyone trying to "just recompile" existing programs for x86/x87.
I'm curious to know - you say Intel's 8087 emulation code was a bit of a lump at 16KB, do you know if it emulated the 8087 microcode state machine or did it use a different strategy?