Theseus, a Static Windows Emulator

Theseus is a new Windows/x86 emulator that utilizes static binary translation to convert programs into native code, offering a unique alternative to traditional JIT-based emulation.
This post is likely the end of my series on retrowin32.
I bring you: Theseus, a new Windows/x86 emulator that translates programs statically, solving a bunch of emulation problems while surely introducing new ones.
What happened to retrowin32?
I haven't been working on retrowin32, my win32 emulator, in part due to life stuff and in part because I haven't been sure where I wanted to go with it. And then someone who had contributed to it in the past posted retrotick, their own web-based Windows emulator that looks better than my years of work, and commented on HN that it took them an hour with Claude.
This is not a post about AI, both because there are too many of those already and because I'm not yet sure of my own feelings on it. But one small thing I have been thinking about is that (1) AI has been slowly but surely climbing the junior to senior engineer ladder; and (2) one of the main pieces of being a senior engineer is better understanding what you ought to be building, as distinct from how to build it.
So, Mr. Senior Engineer, what ought we build? What problem are we even solving with emulators, and how do our approaches meet that? I came to a kind of unorthodox solution that I'd like to tell you about!
Emulators and JITs
The simplest CPU emulator is very similar to an interpreter. An input program, after parsing, becomes x86 instructions like:
mov eax, 3
add eax, 4
call ... ; some Windows system API
An interpreting emulator is a big loop that steps through the instructions. Like an interpreter, this approach is slow because it does a bunch of dynamic work for each instruction. x86 memory references are extra painful because they are very flexible.
Further, on x86 the add instruction not only adds the numbers but also computes six derived values, including things like the parity flag. A correct emulator needs to either compute all of these as well, or perform some sort of side analysis of the code to decide how to run it efficiently.
If you want to go fast what you really need is some combination of analyzing the code and generating native machine code from it — a JIT. JITs are famously hard to write! They are effectively optimizing compilers where the runtime of the compilation itself is in the critical performance path.
Static binary translation
So suppose you want to generate efficient machine code, but you don't want to write a JIT. You know what's really good at analyzing code and generating efficient machine code from it? A compiler!
So here's the main idea. Given code like the above input x86 snippet, we can process it into source code that looks like:
regs.eax = 3;
regs.eax = add(regs.eax, 4);
windows_api(); // some native implementation of the API that was called
We then feed this code back in to an optimizing compiler to get a program native to your current architecture, x86 no longer needed. Instead of handing an .exe file directly to an emulator, we have a sort of compiler that statically translates the .exe directly into a "native" executable.
This approach is known as static binary translation. It has some nice properties, and also some big problems.
Decompilation
Have you heard of decompilation? These people are manually recreating the source code to old video games, one function at a time. Decompilation highlighted something important for me: I don't so much care about having an emulator that can run any random program, I care about running a few very specific programs and I'm willing to go to even some manual lengths to help out.
In practice, if you look at a person building a Windows emulator, they end up as surgeons needing to kind of manually reach in and pump the heart of the target program themselves anyway.
An old idea
Statically translating machine code is not a new idea. Why isn't it more popular? I think there are two main problems, a technical one and a more cultural one.
The technical part is that the simple idea has complex details. Any program that generates code at runtime (e.g. itself containing a JIT) won't work. There are also challenges around how control flow works. It's in the limit impossible to statically find all of the code that might be executed because of dynamic control flow from vtables or jump tables. But if I'm willing to manually help out a bit on a specific program, then this problem might be fine.
The main cultural reason...
Source: Hacker News















