The time the x86 emulator team found code so bad they fixed it during emulation
188 points by paulmooreparks 4 hours ago | 52 comments

psanchez 2 hours ago
This reminds me of a story from 15 years ago, where I was developing a technology to download games on demand by hooking into the OS calls.

There was a particular game that was superslow when this tech was applied. Original game loading took around 15-20 seconds, whereas once the tech was applied it took easily 3-5 min, even with all data already downloaded.

When I started digging into it, I realized the reason was the game was using something like

   fread(data, 1, 65536, fptr);
instead of

   fread(data, 65536, 1, fptr);
Which basically expanded back in the day to 65k reads of 1 byte for several MB file. Each fread translated to 65k reads of ReadFile Windows API. Since my code was hooking on ReadFile system call, and my call was heavier than ReadFile, the game loading felt really slow. Unusable. It would have not been fun for players.

The easy fix was to swap arguments for certain calls. The long fix required to use an internal cache to account for these cases so that the hooked ReadFile was faster when data was already in disk.

Funny thing is that as we started rolling out the tech and applying it to more and more games we realized lots of games did this. We went for the cache fix and games ended up loading faster than before. Honestly, games could have load all the data in a couple of seconds by just swapping the args. I'm guessing developers did this on purpose so that games seemed like they were loading a lot of stuff, although you never know.

reply
Taniwha 2 hours ago
I used to be a graphics card/chip architect for macs in the early/mid 90s - our chips were the fastest, but some programs were resistant because they did stupid stuff: pagemaker invalidated the font cache every time it went thru its main loop, quark with ATM did an n*2 thing every time it wrote text etc etc. We had special hardware to accelerate text drawing and it did nothing because the software pissed it away. We considered creating a plugin that fixed all these things, it would have been hard to maintain, in the end we travelled around to the people who made these apps and talked them through their problems

To be fair excel would erase places white that it wanted to write up to 9 times before it drew any black pixels, we made that very fast! we didn't tell them :-)

At the time 24-bit framebuffers were so slow that before we built graphics acceleration hardware people would switch back to 8-bit to get stuff done, making 24-bit/true colour your daily driver was a big step forward.

reply
urbandw311er 21 minutes ago
This is a horrible and yet not unexpected insight into the internals of Excel
reply
Someone 9 minutes ago
> Which basically expanded back in the day to 65k reads of 1 byte for several MB file. Each fread translated to 65k reads of ReadFile Windows API

What software did that that badly? If the code asks for (up to) 65,536 single byte items, why would you split that into 65,536 calls?

Also, that change changes behavior. The old call could read anything from zero to 65,536 bytes, the new one only can read zero or 65,536 bytes.

(Reading the source of a few implementations, I think most implementations will fill the output buffer with partial objects if the input doesn’t supply an integral number of them, but the return value of fread cannot signal that to the caller)

reply
somenameforme 39 minutes ago
Doesn't that break anything relying on the return value? fread gives you the number of objects read as a return. So I think a pretty typical thing would be to fread and then parse that number of characters, and that'd just break?
reply
jcul 4 minutes ago
I've seen a lot of code that just assumes fread / fwrite succeeded without bothering to check the return value...

But in this case if the code was calling fread 65535 times in a loop and getting 64KiB each time it wouldn't be good either!

Sounds like the parent comment had to fix this with the internal cache thing to speed up the small freads. I think they meant the easy fix would have been swapping the args in the original / caller code.

reply
lukan 11 minutes ago
"I'm guessing developers did this on purpose so that games seemed like they were loading a lot of stuff"

I really hope that was not the case and rather think incompetence or to deal with obscure legacy problems, but the gamer in me gets enraged at the thought someone would artificially increase loading times.

reply
dlcarrier 3 hours ago
SimCity had a read-after-free bug that Microsoft patched in Windows 95. That was a lot easier for customers than having Maxis fix it, which could have required exchanging copies of the game.
reply
Cthulhu_ 54 minutes ago
It feels like graphics drivers do / did this a lot too. At the very least they make specific optimizations for specific games, probably by tweaking settings and features that the game developers didn't optimize properly themselves.
reply
kalleboo 14 minutes ago
Famously if you renamed Quake 3 to "Quack" 3, it would slow down on the ATI Radeon 8500 https://web.archive.org/web/20091016055550/https://hardocp.c...
reply
easyThrowaway 48 minutes ago
The most interesting part is that IIRC they shipped the entire Windows 3.11 memory allocator to make it work.

I have very little understanding on how allocation works at OS level, but I'm surprised there are no wrappers like dgVoodoo or dxWrapper specifically for this kind of issues. There are quite a bunch of old Windows games (Need for Speed 1-4 for a start) that refuse to run on modern OSes due to rather...bold memory management strategies.

reply
rincebrain 15 minutes ago
Apparently the recollection of the fix was that they deferred actually freeing memory for a while if they detected it was SimCity running. [1]

[1] - https://www.joelonsoftware.com/2000/05/24/strategy-letter-ii...

reply
hodgehog11 4 hours ago
I think we're starting to see more of this sort of thing happening now with Proton and Wine gaining prominence in the Linux community. Some games (Elden Ring comes to mind) have bad enough PC ports when they come out that the compatibility layer can incorporate a hotfix to improve performance, while users of the software on the original platform still had to suffer.
reply
Gigachad 3 hours ago
Fairly sure GPU drivers do the same thing where they include a ton of per game tweaks to make them run faster. It does feel like a fragile way of doing things where an external component that should be agnostic to the software running ends up including a handful of junk trying to fix stuff that should have been fixed by the consumer of the driver.
reply
zoenolan 42 minutes ago
The big one I remember was many applications, not just games assuming the buffer swap was performed by a blit into the display buffer, not an framebuffer pointer update. They relied on the previous frames data still being in the back buffer. For those applications you were forced to blit the buffer, not swap the pointer and take a performance hit.

I also remember a media player being called out by name in the code for doing invalid operations, needing a work around and code to detect it was running just to function.

reply
Guvante 2 hours ago
It goes the other way too, sometimes you trigger some optimization silliness in the driver and the game needs to adapt to avoid it.
reply
rickdeckard 54 minutes ago
then the driver gets updated and the game either continues to optimize (wrong) or branches out into code that was written before that driver came out and generally wasn't that well tested, and the circle continues...

It's the life of a (game) developer...

reply
anilakar 3 hours ago
GPU driver packages are already a huge collection of workarounds for bad game engine coding.

An Nvidia employee once told me that one of the easiest ways to squeeze out a few extra frames on your old machine is to rename the game executable to hl2.exe.

reply
st_goliath 2 hours ago
> GPU driver packages are already a huge collection of workarounds for bad game engine coding.

And of course, browser engines also do the same things for certain websites:

https://github.com/WebKit/WebKit/blob/main/Source/WebCore/pa...

https://github.com/WebKit/WebKit/blob/main/Source/WebCore/pa...

reply
necovek 2 hours ago
I can see how it can modify GPU driver behavior, but I cannot see how it would get you better performance with everything else the same?

What it should do is ensure some things not relevant to Half-Life 2 were not done, thus getting better performance for this game in particular, but there is no guarantee that same optimizations work for other applications or games, so one should not expect an overall improvement.

Unless they are doing some silly things like dropping quality, but that's the "everything else the same" point.

If not, why not have this enabled as default behavior instead?

reply
dlcarrier 15 minutes ago
I wouldn't be surprised if it made other games on the Source engine faster, but everything else slower.
reply
limflick 2 hours ago
> to rename the game executable to hl2.exe

This seems genuinely unbelievable. Does anyone have a technical explanation for this?

reply
hurtigioll 2 hours ago
gpu drivers detect games, among other thing by looking at executable names

then driver "optimizes" behavior, sometimes dishonestly (reducing precision), sometimes honestly (working around game engine stupidity)

reply
limflick 2 hours ago
Couldn't that also cause glitches since optimizations meant for HL2 might not work for, say San Andreas? I understand some optimizations might be universal but I can't help but think about unexpected behavior.
reply
ChocolateGod 54 minutes ago
Yes.

A lot of people use Nvidia profile inspector to enable reBar on all games and claim that Nvidia is purposely holding back performance, but doing this causes many games to crash.

reply
tester756 2 hours ago
Who's problem is this?

Nvidia probably doesnt officially say anything about this and 99.9% of people do not rename process name

reply
limflick 28 minutes ago
Phrasing, I wasn't blaming anyone, just curious about the technicalities.
reply
hurtigioll 44 minutes ago
of course they do.

nvidia even has an official api for a game to identify itself so they dont need to look at executable name

reply
proton_9 2 hours ago
This sounds like a really interesting story, would like to read more on why half life 2 specifically? the game itself was pretty well optimized and ran on really low end hardware even back in the day.
reply
db48x 2 hours ago
Because everyone reported performance metrics using it as a benchmark. Higher number = more sales.
reply
murderfs 2 hours ago
If you go back 5 years, everyone was using Quake 3 Arena as the benchmark. ATI got in some hot water because if you renamed quake3.exe to quack3.exe, your FPS would drop by 15%, because they were silently reducing quality to juice their benchmark numbers.
reply
jkrejcha 58 minutes ago
Apparently people did this with the DirectX "3D Tunnel" demo as well[1] back over 20 years ago.

Also there was one "that checked if you were printing a specific string used by a popular benchmark program. If so, then it only drew the string a quarter of the time and merely returned without doing anything the other three quarters of the time".

[1]: https://devblogs.microsoft.com/oldnewthing/20040305-00/?p=40...

reply
tester756 2 hours ago
5 or 50? I'd say 5 years ago it was already Witcher 3, Cyberpunk 2077, GTA 5, etc.
reply
Klayy 49 minutes ago
5 years before hl2
reply
AHTERIX5000 51 minutes ago
Yep, someone needs to do the same workarounds Windows drivers do but on Linux and the translation layer is a good spot for them, look at https://github.com/HansKristian-Work/vkd3d-proton/blob/938d7... for example
reply
harrall 2 hours ago
A big portion of GPU driver updates are actually just that, same with Windows updates.

Windows 95 patched a bug in SimCity just to get it to work.

reply
selcuka 3 hours ago
To be fair it is possible that the developer enabled a special "unroll all loops, no matter what" optimisation flag during compilation.

I agree it would be stupid for a compiler to even support such a flag, but those were the 1980s/90s.

reply
cyberax 3 hours ago
reply
PhilipRoman 55 minutes ago
Right up there with fun, safe math optimizations
reply
kazinator 2 hours ago
> Anyway, my colleague found that there was one program that needed to allocate around 64KB of memory on the stack and initialize it. The standard way of doing this is to perform a stack probe to ensure that 64KB of memory is available, then subtracting 65536 from the stack pointer, and then initializing the memory in a small, tight loop.

Actually, the standard way of allocating 64 kB of memory on the stack is to just assume you can do it, subtract 64k from the stack pointer, and hope for the best.

Most stack allocations in the wild are not checked.

reply
classichasclass 3 hours ago
Betting Alpha was the native architecture in question. It seemed to have the best support.
reply
jeffbee 3 hours ago
People from Transmeta told me stories about how their translators were full of special case optimizations to fix horrors they discovered in Microsoft Windows itself.
reply
wolfi1 50 minutes ago
speaking of which, what became of it?
reply
notorandit 3 hours ago
> they fixed it during emulation

It means the fix was applied to run during the emulation loop execution, not that the fix was found and applied while the emulation loop was running.

Which would have made it an emulation code escape.

reply
electroglyph 3 hours ago
heh, when Raymond Chen dunks on the MSVC team =)
reply
m1r 3 hours ago
Couldn't they just turn the optimization off for this loop?
reply
MadnessASAP 3 hours ago
They didn't have the code for the offensive program, they were creating the emulator to run it on a different architecture.
reply
McGlockenshire 3 hours ago
> offensive program

Agreed.

reply
notorandit 3 hours ago
Which optimizer replaces a 64k loop with 64k instructions?

Ah, yes. Microsoft's!

reply
selcuka 3 hours ago
There is no indication that the compiler that produced the code was Microsoft's. Actually the article hints otherwise ("[...] whatever compiler was used to compile this code").
reply
ant6n 2 hours ago
Arguably more of an optimization, rather than a fix. Looks like un-unrolling a loop, or better, rolling a loop. Or rolling straight line code?
reply
yieldcrv 3 hours ago
> All in all, it took this program 256 kilobytes of code to initialize 64 kilobytes of data.

solidity sweating profusely

reply
rohitsriram 2 hours ago
[flagged]
reply