Binary translation is the technique of converting compiled machine code written for one instruction set into equivalent machine code for another, so that a program built for one processor architecture can run on a different one without its source code. In OS compatibility, it is the main way modern operating systems run x86 applications on Arm-based computers, as with Rosetta 2 on Apple silicon Macs and the Prism translator in Windows on Arm.
Unlike classic emulation, which interprets every instruction one at a time, binary translation rewrites whole blocks of code into native instructions and reuses the result. This makes translated programs fast enough for everyday use, often reaching a substantial share of native performance, although the exact figure depends heavily on the workload.
How binary translation converts machine code
Binary translation works by decoding guest instructions, mapping them to an intermediate representation, optimizing that representation and then emitting native host instructions. The translator must also reproduce the guest’s registers, condition flags, memory model and calling conventions, because the program expects the exact behavior of its original processor.
The unit of work is usually a basic block or a larger trace of frequently executed code. Translated blocks are stored in a translation cache, so a loop that runs a million times is translated once and then executed natively. Blocks are linked directly to each other, which avoids returning to the translator after every jump.
The two main strategies differ in when translation happens, and modern systems often combine them. The table compares them.
| Strategy | When code is translated | Strengths | Weaknesses |
|---|---|---|---|
| Static (ahead-of-time) | Before the program starts, often at install | No translation delay at run time | Cannot handle code generated at run time |
| Dynamic (just-in-time) | While the program runs | Handles self-modifying and generated code | Startup delay and memory for the cache |
| Hybrid | Ahead of time, with dynamic fallback | Fast common path, full coverage | More complex to implement |
Where binary translation is used
Binary translation appears wherever a processor architecture transition would otherwise break existing software. Operating system vendors use it to keep legacy software working while developers port their applications to the new architecture.
| Translator | Translates from | Translates to | Approach |
|---|---|---|---|
| Rosetta 2 | x86-64 macOS apps | Arm64 on Apple silicon | Ahead-of-time with just-in-time fallback |
| Prism | x86 and x64 Windows apps | Arm64 Windows | Dynamic with cached translations |
| FEX and Box64 | x86 Linux programs | Arm64 Linux | Dynamic, user-space translation |
| QEMU user mode | Many guest architectures | Many host architectures | Dynamic via an intermediate code |
The same idea is used inside virtual machines and CPU simulators, in some processors that translate their public instruction set into an internal one, and in security tools that rewrite programs to insert monitoring code. In each case, the program never needs to be recompiled.
Binary translation compared with neighboring techniques
Binary translation is easily confused with emulation and compatibility layers, but it solves a narrower problem: a different processor instruction set. A compatibility layer such as Wine or Proton solves a different operating system interface on the same processor, and the two can be stacked when both differ.
| Technique | What differs | How it bridges the gap | Typical performance |
|---|---|---|---|
| Binary translation | Processor instruction set | Rewrites machine code into host code | Moderate to near-native |
| Interpreting emulation | Processor and often hardware | Executes each instruction in software | Much slower than native |
| Compatibility layer | Operating system interfaces | Maps system calls and libraries | Near-native |
| Software porting | Architecture or platform | Recompiles the source code | Native |
A practical rule follows from the table: recompiling to native code is always best when source code and developer support exist, and binary translation is the bridge for everything that cannot be ported yet. Full system emulators often use binary translation internally, so the boundary between the terms is not perfectly sharp.
Performance factors and hard cases
Translated code runs slower than native code mainly because the translator must reproduce guest semantics that the host processor does not have. Some features map cheaply, while others cost several host instructions for each guest instruction.
- Memory ordering: x86 guarantees stronger ordering than Arm, so translators must add barriers. Apple silicon includes a hardware mode for x86-style memory ordering to remove most of that cost.
- Condition flags: x86 updates flags after most arithmetic, and translators compute them lazily, only when later code reads them.
- Self-modifying and generated code: JIT engines inside browsers or games force retranslation and invalidation of cached blocks.
- Vector extensions: instructions such as AVX may be translated slowly or not supported, so some programs refuse to start.
- Kernel-mode code: user-space translators cannot run drivers or kernel extensions, so hardware drivers must be native.
Because of these factors, a startup delay on the first launch, higher memory use and lower performance in vector-heavy work are normal. Anti-cheat systems and copy protection that inspect the processor or kernel are the most common reasons a translated game fails entirely.
Practical guidance for users and developers
For users, the most useful habit is to prefer native builds when they exist and to treat translation as a compatibility safety net. The table summarizes typical decisions.
| Situation | Recommended choice | Reason |
|---|---|---|
| Native Arm build available | Install the native build | Best speed and battery life |
| Only an x86 app exists | Run it through the translator | Usually works without changes |
| App needs a kernel driver | Look for a native version or alternative | Drivers cannot be translated in user space |
| Heavy vector workloads | Benchmark before relying on it | Translation overhead is highest here |
The simple explanation
Binary translation is like a skilled interpreter who translates a whole speech in advance. Each program speaks the language of one kind of processor. The translator rewrites it into the language of another kind. It then keeps the translation, so the work is done only once.
The key takeaway is simple. Old programs keep working on new kinds of computers. Most people never notice the translation at all.
How it is used in practice
An architecture studio replaces its aging x86 laptops with Arm-based laptops that offer longer battery life. Most of its tools, including the office suite and browser, already have native Arm versions, but one specialized structural calculation program exists only as an older x64 build, and its vendor has no plans to port it.
The IT team tests the program under the operating system’s built-in binary translator. The first launch takes noticeably longer while code is translated and cached, and later launches start normally. Calculations run somewhat slower than on the old machines, which the engineers accept because the program is used a few times a day.
One plug-in that installs a kernel driver for a USB license dongle fails to work, so the studio switches to the vendor’s network license server. The result is a successful migration with one native exception list and one workaround, without waiting years for the vendor to port its software.