机器指令翻译成 JavaScript —— No.6 深度优化
第一篇 中我们曾提到,JavaScript 最终还得经过浏览器来解析。因此可以把一些优化工作,交给脚本引擎来完成。
现代浏览器的优化能力确实很强,但是,运行时的优化终归是有限的。如果能在事先实现,则可投入更多资源,优化得更充分。
优化尝试
指令 1:1 翻译成 JS,结果显然会存在一些累赘的逻辑。
例如状态标志,很多时候是不必计算的:
A = read(10) // LDA 10
SR_N = ... // 无意义,可以去除
SR_Z = ...
X = read(20) // LDX 20
SR_N = ... // 值得商榷
SR_Z = ...
if (!SR_Z) { // BNE ..
nextFn = ...
return
}
Y = read(30) // LDY 30
SR_N = ...
SR_Z = ...
第一步的状态标志,会被后面的覆盖,显然是没必要计算的。但第二步,就有一些奥妙了。
如果 !SR_Z 成立,那么进入下一个流程块;否则执行 LDY,状态被重置。因此,要不要计算 SR_N,可以先看 SR_Z 的结果。
于是,可将顺序调整成:
X = read(20) // LDX 20
SR_Z = ...
if (!SR_Z) { // BNE ..
SR_N = ... // 移到这里
...
}
这样,就能避免无意义的计算。
此外,再对表达式进行合并,最终变成:
A = read(10) // LDA 10
X = read(20) // LDX 20
if (X != 0) { // BNE ..
SR_N = (X > 127)
nextFn = ...
return
}
Y = read(30) // LDY 30
SR_N = (Y > 127)
SR_Z = (Y == 0)
看起来就更精简了。
再进一步,若能发现 nextFn 不依赖该流程的状态标记,那么 SR 的计算也可以省略!
其他,还有诸如 inline 优化。例如一些简单的跳转:
JSR L1
XXX 1
XXX 2
L1:
XXX 3
XXX 4
RTS
直接将其展开,于是变成:
XXX 3
XXX 4
XXX 1
XXX 2
这样就能减少指令块的分割,降低流程模拟的开销。
类似的优化方案还有很多。但是,若想亲自实现这些,则需要深厚的理论知识。并且实现起来也很麻烦,完全不适合这种周末时间的小研究。
借用工具
理论知识固然重要,但工程师的宗旨,就是最大程度利用现有资源,将想法快速变成现实。
这几篇文章都只是理论探讨,实现未必就得如此。事实上,我们应该借助现成的工具,快速验证想法。
既然前面提到优化,那就选择一个强大的优化方案 —— LLVM。
我们可以将 6502 指令逻辑,转换成 LLVM 中间表示。这样,即可用现成工具进行优化。
但是这么做有些麻烦。一是对 LLVM 不太熟悉,二是调试起来也不方便。
对于 LLVM 来说,最适合的输入语言当然是 C 了,毕竟 clang 就是为此产生的。
所以,我们不妨将 6502 指令翻译成 C 语言。C 不仅简单优雅,而且借助 IDE 开发调试都超级方便。
下一篇,我们尝试这个方案是否可行。