7 ms·
It is clearly written to not use the (F)WAIT instruction -- the "dumb" code is there to make sure the previous 80287 instruction has completed. The first time
by peterfirefly 3y ago
It is clearly written to not use the (F)WAIT instruction -- the "dumb" code is there to make sure the previous 80287 instruction has completed.
The first time wasting code is long because it has to be slower than the slowest 287 instruction takes to complete after signaling an error. The other time wasters are shorter because they come after known instructions that are faster (FNSTSW just stores 2 bytes to memory, FNCLEX clears some bits inside the 287). Note also that they are the FNSTSW and FNCLEX -- that means there is no implicit (F)WAIT instruction before the real 287 instruction.
Why two FNCLEX? I don't know.
Why 4 writes to port F0? Probably in case the FNSTSW and FNCLEX instructions lead to errors.
- anyfoo 3y agoYou should write that answer as a comment to the blog post. The author of the blog is very thorough and likely to take an interest to it, if there’s anything to it. (As an aside, why are we assuming 80287 and not 8087? I know nothing about both, so it’s well likely that I missed obvious hints. EDIT: Ah, I guess because it’s the int 13 handler specifically.)
- peterfirefly 3y agoI did. Stuck in moderation. Correct, int 13h.
- anyfoo 3y agoWasn’t 13h disk services? Guess they got shared? Or is it hardware interrupt 13h mapped somewhere else through the interrupt controller?
- projektfu 3y agoShould be int 16h I think.
- peterfirefly 3y agoInt 16h was the BIOS keyboard services.
- projektfu 3y agoRight, thanks.
- peterfirefly 3y agoIt was actually IRQ13 -- one of the input pins on the slave interrupt controller. It was typically mapped to INT 75h. I don't know if Windows remapped it.
- 13of40 3y ago> Why two FNCLEX? There is a behavior on some CPUs where "out 0xf0" can leave IGNNE# active, but you can clear it after the "out" by running "fnclex". Why are there two of them? Either the "out 0xf0" is affected by IGNNE# being active, or maybe the original draft had one "spin, out, fnclex" and that whole block of code was just copy+pasted when they added the second one.
- userbinator 3y agoThat was my first thought too - they were trying to synchronise the CPU and FPU. The mention of not using the wait instruction reminded me of this other post on the same site: https://www.os2museum.com/wp/learn-something-old-every-day-part-vii-8087-intricacies/ https://www.os2museum.com/wp/learn-something-old-every-day-p...
- Varriount 3y agoHow does an FPU get out of sync with a CPU? Wouldn't the CPU automatically wait for the FPU logic to complete (just like with every other instruction)?
- winternewt 3y agoNope. That's why the FWAIT instruction exists.
- projektfu 3y agoThis is the Intel recommended exception handler: • Store the NPX environment (control, status, and tag words, operand and instruction pointers) as it existed at the time of the exception. • Clear the exception bits in the status word. • Enable interrupts on the CPU. • Identify the exception by examining the status and control words in the save environment. • Take some system-dependent action to rectify the exception. • Return to the interrupted program and resume normal execution. and one of the examples for writing the handler is this: SAVE_ENVIRONMENT PROC ; ; SAVE CPU REGISTERS, ALLOCATE STACK SPACE FOR 80287 ENVIRONMENT PUSH BP MOV BP,SP SUB SP, 14 ; SAVE ENVIRONMENT, WAIT FOR COMPLETION, ; ENABLE CPU INTERRUPTS FNSTENV [BP-14] FWAIT STI ; ; APPLICATION EXCEPTION-HANDLING CODE GOES HERE ; ; CLEAR EXCEPTION FLAGS IN STATUS WORD ; RESTORE MODIFIED ; ENVIRONMENT IMAGE MOV BYTE PTR [BP-12] , 0H FLDENV [BP-14] ; DE-ALLOCATE STACK SPACE, RESTORE CPU REGISTERS MOV SP,BP POP BP ; ; RETURN TO INTERRUPTED CALCULATION IRET SAVE_ENVIRONMENT ENDP Make of that as you like. I am somewhat curious about what happened here.
- peterfirefly 3y agoThe 8087 and 8086/8088 (and 80287/80286) perform a complicated dance to cooperate on the execution of FPU instructions. The (2)87 has an output pin that is active when the FPU is busy. It is connected to an input pin on the CPU and the WAIT/FWAIT instruction waits until that pin isn't active anymore. The 80386 with the 80287 or 80387 did the same thing. The dance also involves the FPU snooping on the address and data buses (multiplexed on the same pins on the 8086/8088). The FPU can see which memory reads are instruction fetches + the CPU helpfully tells the world what it does with the instruction prefetch queue (it gets flushed sometimes). The FPU maintains its own copy of the prefetch queue so it sees the same instruction bytes as the CPU and in the same order. The CPU also helpfully says when it gets the first byte from the queue. This is how the FPU knows when to look for an ESC opcode (all the 80(2)87 opcodes are ESC opcodes -- the CPU doesn't know what they are). If the ESC opcode has a memory operand, the CPU performs a dummy read. The FPU snoops on that -- and sees what address the CPU uses. If the FPU instruction involves reading from memory, it also stores whatever data the memory returns. If the FPU needs to write to memory or it needs to read more memory, it will request bus access from the CPU. When it gets it, it will issue the memory requests it needs and then relinquish the bus again. The 486 had the FPU integrated and no longer had to do any of that. This also opened up the possibility of fp comparison instructions that set the CPU flags directly -- prior to that, you would execute an fp comparison instruction on the FPU, write the FPU status flags to memory with another fp instruction, read them back to the AX register with a CPU instruction, use the SAHF instruction to write them to the CPU flags, and only then do a conditional branch. The Pentium Pro introduced the FCOMI/FCOMIP/FUCOMI/FUCOMIP instructions that short-circuited that: it's just the FCOMI/... instruction directly followed by the conditional branch. Much better.