10 ms·
This code smells of desperation
- marcosdumay 3y agoThe only way to have more fun than abstracting broken software is abstracting broken hardware. I can imagine somebody spent months on those few lines of assembly.
- benj111 3y agoI suppose it depends how the hardware was broken. If you just needed a delay, this is bad code thats just been randomly iterated until it 'works'. On the other hand, if the hardware does require such an incantation then it's impressive that someone managed to wade through the brokenness. I'm inclined to believe it's the former though.
- twoodfin 3y agoThe delays introduced by the repeated PUSH/POPs would be quite short, even on the 8088. How would you propose such making high-precision waits for the external x87 chip? (Assuming they were needed at all.)
- benj111 3y agoif it was just 100 push pops, that would be fine for a delay. if its 20 push pops foo 10 push pops bar foo 20 push pops foo foo 10 push pops. to achieve the same delay. its like: a=1 b=2 result=0 print 1+2 return it isnt wrong, but its indicative of someone who doesnt understand whats going on. you wouldnt describe it as good code.
- peterfirefly 3y agoA non-obvious reason for using PUSH/POP instead of a loop could be to generate as many different bus cycles as possible. We get instruction fetch, memory read, memory write, and probably idle -- and an I/O write (0F0h). We should get the idle cycles because PUSH/POP instructions are single-byte and require a little time for decoding and the 286 BIU fetched 2 bytes at a time so the instruction buffer should get full. Maybe they wanted that on top of the delay (which is obviously there because they didn't want to use the FWAIT instruction) -- maybe some 80287 chips had internal state machines that could get confused and needed some help?
- benj111 3y agoIt /could/ be that. But I think that's an overly charitable interpretation based on the evidence. If the source code turns up with detailed comments about why it's like it is. Fine. In the absence of that, I'm not buying it.
- peterfirefly 3y agoI also don't think it is. I think it is just a short and simple delay -- each push/pop pair is only two bytes AND generates 2 word transfers AND doesn't disturb any registers. They probably had a macro or repm loop for it.
- benj111 3y agoBut why the redundant FNCLEX and io writes then? As I said, if you just had 100 push pops, fine. But it isnt, it's push pops mixed in with other seemingly redundant code.
- Palomides 3y agoit looks suspicious to me, like the kind of thing you find on page 30 of a processor errata document, something like "single writes to external device fail 0.5% of the time due to a register clearing bug on mask revisions prior to version 23. recommended workaround: write twice."
- wruza 3y agoOtoh, these operations are too specific to come up with by random iteration. I believe it was some hardware nonsense that was both arcane and avoided by random iteration.
- benj111 3y agoIs it too specific or is it Adams' sentient puddle? I've done similar where you misunderstand something and what you write doesn't work, you strip it down to a minimum working version add on bits which break it, fiddle about with iterating the new bits and afterwards you have an accretion that 'works' but you don't know why.
- brmgb 3y agoI can tell from this post that you probably have had the chance of never having to work with or emulate broken hardware (which is to say every pieces of hardware ever). At some point you just stop trying to be sane and just go with what works.
- michaelcampbell 3y agoThe removal of "This..." from the title here really confuses it. With "This", it's obvious the title is "(This code) smells of desperation". The submitted title is ambiguous; it could mean "(Code smells) of desperation".
- h2odragon 3y agothose opening "wtf" sequences might be there as filler space; harmless instructions with a known pattern where you can come back later and insert different instructions. Most people use NOPs for that but perhaps they wanted a different signature or needed 3 separate, differentiated patch points at entry. Or maybe they wanted to help sell more 8087 chips. Anybody recall if there was a notable performance difference between Borland's FP emulation lib and M$, then? My habit at the time was to religiously avoid all floats, to the point of shipping a home made arbitrary precision BCD math library. It was no faster than anything else but it gave the same results for the same inputs, every time on every machine.
- deleted 3y ago[deleted]
- outside1234 3y agoThis is what you’d run across in codebases before the internet, let alone Stack Overflow. People didn’t have code to copy and paste — so they randomly wrote it like monkeys until it worked based their understanding of one page of a manual, which was literally the only documentation or description anywhere of how the system they were working with worked. Source: I was there :)
- jethkl 3y agoAdd to the mix bug reports like, "This worked on the Gateway when the Epson was freshly plugged into the LPR port but crashed after the Epson had printed 5 pages. If we remove our sound card, then no more problems..." Microsoft's strategy was to support legacy and buggy hardware -- this reduced friction for OEMs and helped expand the market, but it also caused a lot of trouble.
- xbar 3y agoSomewhere there is a production codebase containing a particular sequence of check-ins that reflect the peak of my similar flailings. I am not proud of my desperation, but I can acknowledge it now.
- quercusa 3y ago"This time for sure!"
- peterfirefly 3y agoIt 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 ago
- crabbone 3y agoI've inherited a similar bit of code that kicks in right after pivot (of Linux boot) and tries to disassemble and clean up whatever storage was concocted by the previous steps during boot, and then proceeds to assemble it using some user-supplied layout. The code is awful, but, really, if anyone's to blame, it's the Linux people who never cared to systematize and unify system's understanding and representation of storage.
- layer8 3y agoThis sounds like the kind of thing where Raymond Chen would write up a historically completely sensible rationale for why that code is the way it is.
- cratermoon 3y agoIf there was one, he would have written it by now.
- veave 3y agoIt's been a few years since it seems he ran out of interesting historic things to tell.
- MBCook 3y agoNot knowing anything about x87 programming, but assuming the code is rational, I would guess this causes the code to work with a handful of _really_ broken or flakey FPUs. Or perhaps just one popular one.
- smitty1e 3y agoIf git had existed, one would anticipate the commit log for the code would read like a Lovecraftian descent into madness as the coder makes increasingly unhinged pleas to the Great Old Ones to accept the unit tests.
- Sesse__ 3y agoHaving read plenty of version control system logs from around the turn of the millennium (i.e. when things were _far_ less crazy than 1987), I'd wager most of it would come in the form of commits marked “implemented EGA driver, faster AI in Reversi, improve FPU exception code”, aka “I'm done for today, committing”.
- kelnos 3y agoWhich makes sense, in a way. Most (all?) of the popular version control systems back then were centralized, and required the central server to even make a commit. Even Subversion, a "better CVS", required this. When you have to wait several seconds (or more) to make a commit, and you couldn't edit things to tidy up history after the fact, you tended to make commits much less often. I kinda take git for granted now, where commits happen in a fraction of a second. Sometimes it's hard to remember what a pain it was using CVS, and even SVN.
- Izkata 3y ago> But the code in WIN87EM.DLL looks very much like the result of changes made in desperation until it worked somehow, even though the changes made little or no sense. This is how the characters in Coding Machines realized something was up, assembly instructions involving carry bits that made no sense, that they later realized was how an AI writes code: https://www.teamten.com/lawrence/writings/coding-machines/ https://www.teamten.com/lawrence/writings/coding-machines/ > It took us the rest of the afternoon to pick through the convoluted jump targets and decode four consecutive instructions. That snippet, it turns out, was finding the sign of an integer. Anyone else would have done a simple comparison and a jump to set the output register to -1, 0, or 1, but the four instructions were a mess of instructions that all either set the carry bit as a side-effect, or used it in an unorthodox way.
- Terr_ 3y agoThat reminds me of a case where an evolutionary-algorithm was being applied to FPGA circuits ("programmable" circuit layouts) with the goal of detecting the presence or absence of a particular tone. [0] One of the results was a bizarre circuit that wasn't really digital anymore, because the pieces were arranged to exploit ways in which the digital circuit was imperfect, forming a system that was actually analog and idiosyncratic to the test environment. [0] https://www.damninteresting.com/on-the-origin-of-circuits/ https://www.damninteresting.com/on-the-origin-of-circuits/
- Vecr 3y agoSomewhat off topic, but your network switches don't still come with metal cases? I get the cheapest stuff that's likely to be reasonably good quality and they all have metal cases.
- charonn0 3y agoI'm not nearly expert enough to judge, but to me it smells like heavy wizardry.
- whoopdedo 3y agoFPUs in the early x86 family are weird. They were typically on separate chips so you could have an 8088+8087, 80286+287, 80286+287XL (which was actually a 80387), 80386+387 (SX and DX models for 24 or 32 bit bus), 80386+287[1], 80386 or 486+Weitek[2], 80386+Weitek+387, 80486SX+80487 where the co-processor was a full CPU that disabled the main chip. And then there were the clones doing creative things such as the Nx586+587[3] which because of it's lack of on-board FPU was often confused for a 386 by software and lost the advantage of its Pentium ops. So I'm not surprised the exception handler is a mess. It's a domain built entirely out of corner-cases. [1] https://old.reddit.com/r/retrobattlestations/comments/hj12ck/a_combination_you_dont_see_that_often_an_80386/ https://old.reddit.com/r/retrobattlestations/comments/hj12ck... [2] https://micro.magnet.fsu.edu/optics/olympusmicd/galleries/chips/weitekmathlow.html https://micro.magnet.fsu.edu/optics/olympusmicd/galleries/ch... [3] https://en.wikipedia.org/wiki/NexGen https://en.wikipedia.org/wiki/NexGen
- jftuga 3y agoA friend and I each bought 387's (which was physically, a separate chip) for our 386's circa 1992. IIRC, I had a 80386/25 MHz with 4 MB of ram. I remember a tank game called Scorched Earth where you would have to set angle & power to try to hit the other person's tank. Some ordinances took a 10-15 seconds to fire & complete because it was running FP ops on the CPU. Once the 387 was installed, this calculation was done almost instantly. That's about all I remember my FPU being good for. LOL good times!
- EdwardDiego 3y agoThe funky bomb or death's head? Especially when it was a large map with lots of ground to destroy. Me and my siblings had a house rule to not use either when playing on our 286 because it took a minute or so to complete...
- readyplayernull 3y ago"Desperation" or random iterations until it passed every test. It doesn't seem to have a lot of opcodes. How much time did it take to find the algorithm with the processing speed of their time?
- quickthrower2 3y agoGot rabbit holed... I love this ad - https://www.os2museum.com/wp/os2-history/os2-beginnings/1987-05-11-infoworld-p13/ https://www.os2museum.com/wp/os2-history/os2-beginnings/1987... - it is a sort of weird mixture of Steve Job's Apple smooth talking and desperate street seller at the same time.
- sghiassy 3y agoThis triggered my PTSD haha
- emoemwin-asm 3y agoThe Microsoft code leak mentioned by one of the comments has been out there for years so might as well paste it here so cut down on some of the speculation? Fair use - commercial value is zero, historical value for analysis and criticism is high. The relevant code comments seems to be "Fix timing problem??" and "486 bug - must wait till after last "out f0" to clear fp exceptions or IGNNE# will be permanently active." public __fpIRQ13 __fpIRQ13: cli WASTE_TIME 70 push ax xor al, al NULL_JMP out 0f0h, al ; reset busy line. NULL_JMP mov al, 65h NULL_JMP out 0a0h, al ; EOI slave irq 5 NULL_JMP mov al, 62h NULL_JMP out 20h, al ; EOI master irq 2 NULL_JMP pop ax sub sp, 2 push bp mov bp, sp fnstsw [bp+2] WASTE_TIME push ax xor al, al NULL_JMP out 0f0h, al ; reset busy line. NULL_JMP pop ax pop bp ; fnclex ; 486 bug - must wait till after last ; "out f0" to clear fp exceptions ; or IGNNE# will be permanently active. WASTE_TIME push ax xor al, al NULL_JMP out 0f0h, al ; reset busy line. NULL_JMP pop ax ; fnclex ; 486 bug - must wait till after last ; "out f0" to clear fp exceptions ; or IGNNE# will be permanently active. WASTE_TIME push ax xor al, al NULL_JMP out 0f0h, al ; reset busy line. NULL_JMP pop ax fnclex ;Now this is safe. WASTE_TIME 70 ;Fix timing problem?? jmp __FPEXCEPTION87P
- projektfu 3y agoReally interesting that it was a 486 bug, given the provenance listed in the article. Windows 3.0 was, indeed, released after the 80486 was. I am not sure why the reset busy code was repeated 3 times, I assume the bit must have been somewhat sticky. "If an unmasked exception occurs when the numeric exception bit in CR0 is clear and the IGNNE# pin is active, the performance of the FPU will be retarded as long as the exception remains pending." https://www.cs.earlham.edu/~dusko/cs63/prepentium.html https://www.cs.earlham.edu/~dusko/cs63/prepentium.html I wonder if that has anything to do with it all.