x86エミュレータチームが、エミュレーション中に修正せざるを得ないほどひどいコードを見つけた話
原題: The time the x86 emulator team found code so bad they fixed it during emulation
日本語訳
# タイトル
x86エミュレータのチームが、あまりにひどいコードを見つけてエミュレーション中に修正してしまった時の話
# 本身
苦労話の交換中、ある同僚が、Windowsが他のプロセッサをネイティブで動作させるシステム向けに、x86-32用のプロセッサエミュレータを搭載していた時代の話を披露してくれました。(こうしたことは何度もありました。ちなみに、この特定の物語がどのプロセッサに関するものかは分かりません。)
このエミュレータはバイナリ変換を採用しており、元のx86-32コードと同等の操作を実行するためのネイティブコードを生成していました。これにより、インタプリタによるエミュレーションに比べて大幅なパフォーマンス向上が実現していました。x86-32を単なるバイトコード、エミュレータをJITコンパイラとしてイメージすると分かりやすいでしょう。
さて、私の同僚はあるプログラムが、スタック上に約64KBのメモリを割り当てて初期化する必要があることを見つけました。これを行う標準的な方法は、まず64KBのメモリが利用可能であることを確認するためにスタックプローブを行い、次にスタックポインタから65,536を減らし、それから小さなタイトなループでメモリを初期化するというものです。
しかし、このコードをコンパイルしたコンパイラにとって、メモリを初期化するためにループを使用するのはあまりに単調すぎたようです。バッファの各バイトを初期化するためのループを生成する代わりに、コンパイラはループを65,536個の個別の「メモリへのバイト書き込み」命令(各4バイト)へと展開することで、コードを「最適化」してしまったのです。
結果として、このプログラムは64KBのデータを初期化するために、合計256KBものコードを必要としていました。
開発チームはこのあまりのひどさに憤慨し、この忌々しい関数を検出し、同等のタイトなループに置き換えるための特別なコードをトランスレータに追加したほどです。
原文(英語)を表示
During an exchange of war stories, a colleague of mine told one from back in the days when Windows included a processor emulator for x86-32 on systems that natively ran some other processor. (This has happened many times. And no, I don’t know which processor this particular story applied to.)
This particular emulator employed binary translation, generating native code to perform the equivalent operations of the original x86-32 code. This offered a significant performance improvement over emulation via interpreter. You can imagine that x86-32 is just a bytecode, and the emulator is a JIT compiler.
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.
But using a loop to initialize the memory was too mundane for whatever compiler was used to compile this code. Instead of generating a loop to initialize each byte of the buffer, the compiler “optimized” the code by unrolling the loop into 65,536 individual “write byte to memory” instructions, each 4 bytes long.
All in all, it took this program 256 kilobytes of code to initialize 64 kilobytes of data.
This offended the team so much that they added special code to the translator to detect this horrible function and replace it with the equivalent tight loop.