机器指令翻译成 JavaScript —— No.2 跳转处理

上一篇,我们发现大多数 6502 指令都可以直接 1:1 翻译成 JS 代码,但除了「跳转指令」。

跳转指令,分无条件跳转、条件跳转。从另一个角度,也可分:

  • 静态跳转:目标地址已知

  • 动态跳转:目标地址运行时才知道

为了让问题更简单,本文只讨论「静态跳转」,并且是在固定的指令之间跳转。

我们用 JXX 表示跳转指令,XXX 表示其他指令:

L1:
    XXX
    JXX L1   -
    XXX
    JXX L2   +
    XXX
L2:
    XXX

这里 JXX L1 跳转到之前的位置,暂且称为「负跳转」。相应的,JXX L2 跳到之后的位置,称为「正跳转」。

正跳转

先来讨论正跳转。因为正跳转只会离终点越来越近,所以简单一些。先看个例子:

    XXX 1
    JXX L1 --.
    XXX 2    |
    JXX L2 --|--.
L1:          |  |
    XXX 3  <-|  |
L2:             |
    XXX 4     <-|

这个分支很简单,可轻松翻译成 JS 代码:

XXX 1
if (...) {
    XXX 3
} else {
    XXX 2
}
XXX 4

但是,遇到稍复杂的分支,用 if..else.. 就会显得力不从心,因此需要用 break 来控制流程。

然而 break 只能用在循环里 —— 没事,套个空循环就可以:

do {
    ...
    if (...) break      // JXX L1
    ...
    if (...) break      // JXX L1
    ...
} while (0)
    ...                 // L1:

我们把 do 放在程序顶部,while 放在目标位置之前。这样,用 break 即可实现跳转 —— 退出 while,进入目标位置。

在很多语言里 do..while(0) 都有一些妙用,这是个有趣的语法糖。

当然,如果有多个不同的跳转,就需要嵌套多个 do 了。这时,如何指定 break 对应的层次?

熟悉 JS 的朋友都知道,JS 虽然没有 goto,但 label 的概念还是存在的。因此我们给每个 do 都贴上标签,同时 break 也指明标签:

a1: do {
a2: do {
    ...
    if (...) break a1       // JXX L1
    ...
    if (...) break a2       // JXX L2
    ...
} while (0)                 // L2:
    ...
} while (0)                 // L1:
    ...

我们把 do 放在最开头;每个 while(0) 对应一个目标位置。这样就实现了「正跳转」的翻译!

负跳转

现实中,不可能全是正跳转,否则程序一下子就执行到底了。负跳转,则可跳回先前位置,反复执行指令。

对于负跳转,是否也能用类似的方案?看起来好像不难,把 break 换成 continue,就可以回到 do 的后面。

a1: do {                    // L1
    ...
a2: do {                    // L2
    ...
    ...
    if (...) continue a1    // JXX L1
    ...
} while (0)
    ...
    if (...) continue a2    // JXX L2
    ...
} while (0)
    ...

对于这种简单的分支,貌似还行得通。但是对于稍复杂的情况,例如原本就已存在正跳转:

a1: do {
    ...
    if (...) break  --.
    ...               |
    if (...) break  --|
    ...               |
    DST               | <-.
    ...               |   |
} while (0)         <-!   |
    ...                  ???
    ...                   |
    SRC                 --!

我们打算从 SRC 跳到 DST 位置 —— 这时的负跳转,又该如何实现?

因为 do..while 是单向的,只能从里面跳出来,无法从外面跳进去。

也许你会说,可以在 DST 处放一个 do。很不幸,这个 do 会和 a1 的 while 结合,完全不符合我们的意图。

简单地说,do..while 只能嵌套,无法交叉。

所以,负跳转很难翻译成 JS 代码。


退一步说,就算能翻译出来,最终也未必适用。因为传统程序,很多是这样的逻辑:

BEGIN:
    输入查询
    程序逻辑
    输出结果
    空跑耗时
GOTO BEGIN

这翻译成 JS 就是一个死循环,无法输入,也看不到输出,完全不符合浏览器的线程模型。

因此,还必须对流程进行改造,以一种可控的方式执行。

下一篇,我们讨论如何改造流程。

posted @ 2016-07-05 19:58  EtherDream  阅读(1015)  评论(0编辑  收藏  举报