上一篇,我们决定使用 LLVM 来优化程序,并打算用 C 作为输入语言。现在我们来研究一下,将 6502 指令转换成 C 的可行性。

跳转支持

翻译成 C 语言,可比 JS 容易多了。因为 C 支持 goto,跳转可轻松实现。例如:

$0600  LDA #$01
$0602 STA $02
$0604 JMP $0600

就能翻译成如下 C 代码:

L_0600: A = 0x01; ...
L_0602: write(A, 0x02);
L_0604: goto L_0600;

我们把指令所在的位置,标记成对应的 label 名。例如 $0600 变成 L_0600:

这样 JMP $0600 即可翻译成 goto L_0600。很简单,完全符合 C 语法。

只要跳转是以指令为粒度的,都可以翻译成 goto。(跳到指令中间、指令区外,另当别论)

转换工具

现在,我们需要一个 6502 指令转 C 的工具。

本着不造轮子的原则,我们直接用现成的反汇编工具,生成上述那种的汇编代码。

但是,这不符合 C 语法啊 —— 没事,可以用脚本简单处理一下。例如:

  • 把每行开头的 $nnnn 替换成 L_nnnn:

  • 把 $ 替换成 0x

  • 补上括号、分号

几个正则一上,于是就变成这样:

L_0600: LDA(0x01);
L_0602: STA(0x02);
L_0604: JMP(0600);

这不就符合 C 语法了!

我们把每个指令定义成宏,让编译器自己去展开。调试时,就像调汇编一样,一步一个指令,不会进入逻辑细节。

当然,指令有很多种寻址模式。比如 LDA 指令,就有立即数、零页、零页偏移、绝对地址等模式。

因此,我们再增加一个后缀,以分区它们。例如 LDA $01 翻译成 LDA_ZP(0x01)

类似还有 _ZP_X_ZP_Y_ABS 等等。所以,最终 C 代码是这样的:

L_0600: LDA_ZP(0x01)
L_0602: STA_ZP(0x02)
L_0604: JMP_ABS(0600)

翻译跳转

现在换成 C 语言,跳转又该如何实现?

静态跳转很简单,只是包了一个语法糖:

#define JMP_ABS(addr)  goto L_##addr;

但是动态跳转,就需要斟酌一番了。

因为现在是用 goto 流程控制,没有将指令切割成 block_xxx 函数块,所以不能用之前那种查表的方式。

但是,可以用类似的方案。我们把已知的跳转点,事先都准备好:

jump_map:

switch (pc) {
case 0x0600: goto L_0600;
case 0x0608: goto L_0608;
case 0x0620: goto L_0620;
...
default: enter_interpret_mode()
}

动态跳转时,先 goto jump_map,然后根据 pc 的值,跳到相应的位置;如果没有符合的,则进入解释器模式。

这样,就实现了 C 版的动态跳转!

跳转点优化

当然,已知的跳转点会有很多,其实可以再精简一下。

动态跳转,绝大多数时候都是 RTS 指令,返回之前 JSR 的位置。所以,我们可以只列出 JSR 所在的位置。

相当于只提供 call/return 的跳转。其他小概率情况,反正可以用解释模式。

我们把这个跳转表(或者称作返回表),用宏包装一下,看起来变成这样:

RET_BEGIN
RET_ADDR(0600)
RET_ADDR(0604)
RET_ADDR(0620)
RET_END

这样,非常方便工具输出。

事实上精简这个跳转表,不只是为了减少代码量,更多是为了优化。如果每一行都有被 goto 的可能,那么 inline 就很难做了。

时钟模拟

现在,讨论时钟相关的问题。我们还是用一个变量,模拟剩余的周期数:

int cycle_remain = 20000;

然后每执行一条指令,扣除相应的数量。我们可以在宏里面定义,例如:

#define CYCLE(V)        cycle_remain -= V;

#define _ST(R, M)       write(M, R);
#define STA_ZP(v) CYCLE(3); _ST(A, v);
#define STA_ABS(v) CYCLE(4); _ST(A, v);
#define STA_ABS_X(v) CYCLE(5); _ST(A, v + X);

之前我们讨论的,是在跳转时统一扣除;现在,每个指令都要减一次,会不会浪费性能?

其实不用担心,这些都是常量计算,编译器会优化合并的。

接着就是频率控制。因为这是 C 语言,我们先尝试编译成本地程序,所以暂时无需考虑线程问题。如果 cycle_remain 用尽了,sleep 一下就可以。

演示

最后,就是将所有 6502 指令实现成宏。好在数量不算太多,而且很多都有规律。

上周末花了大半天时间,把这唯一的体力活完成了,大致是这样的:6502.h

现在,我们尝试一个案例 —— 贪吃蛇。

这是它的原始机器码:

0600: 20 06 06 20 38 06 20 0d 06 20 2a 06 60 a9 02 85
0610: 02 a9 04 85 03 a9 11 85 10 a9 10 85 12 a9 0f 85
0620: 14 a9 04 85 11 85 13 85 15 60 a5 fe 85 00 a5 fe
....
0730: ea ca d0 fb 60

用现有的工具反汇编:

$0600    20 06 06  JSR $0606
$0603 20 38 06 JSR $0638
$0606 20 0d 06 JSR $060d
$0609 20 2a 06 JSR $062a
$060c 60 RTS
$060d a9 02 LDA #$02
....
$0731 ca DEX
$0732 d0 fb BNE $072f
$0734 60 RTS

然后,经过我们的脚本加工,变成 C 代码:

L_0600: JSR(0606, 0600)
L_0603: JSR(0638, 0603)
L_0606: JSR(060d, 0606)
L_0609: JSR(062a, 0609)
L_060c: RTS()
L_060d: LDA_IMM(0x02)
....
L_0731: DEX()
L_0732: BNE(072f)
L_0734: RTS()

以及一个用来支持动态跳转的表:

RET_BEGIN
RET_ADDR(0603)
RET_ADDR(0606)
RET_ADDR(0609)
RET_ADDR(060c)
....
RET_END

此外,还约定了几个特殊地址:

  • [0x0200, 0x0600) 屏幕输出 - 32*32 像素,8 位

  • 0xfe 随机数

  • 0xff 最后按下的键

考虑到屏幕很小,不如直接 print 出来吧。为了简单演示,先不实现颜色。

我们将其编译成本地程序,运行:

(由于字体原因,看起来变成了长方形,所以把图片高度缩小了一半)

虽然效果很差,但至少逻辑上是没问题的!

结尾

将 6502 指令翻译成 C 代码,其实还是非常容易的。不过,我们目标可是 JavaScript。

下一篇,我们将尝试终极挑战。

机器指令翻译成 JavaScript —— No.7 过渡语言的更多相关文章

  1. 【探索】机器指令翻译成 JavaScript

    前言 前些时候研究脚本混淆时,打算先学一些「程序流程」相关的概念.为了不因太枯燥而放弃,决定想一个有趣的案例,可以边探索边学. 于是想了一个话题:尝试将机器指令 1:1 翻译 成 JavaScript ...

  2. 机器指令翻译成 JavaScript —— No.5 指令变化

    上一篇,我们通过内置解释器的方案,解决任意跳转的问题.同时,也提到另一个问题:如果指令发生变化,又该如何应对. 指令自改 如果指令加载到 RAM 中,那就和普通数据一样,也是可以随意修改的.然而,对应 ...

  3. 机器指令翻译成 JavaScript —— No.6 深度优化

    第一篇 中我们曾提到,JavaScript 最终还得经过浏览器来解析.因此可以把一些优化工作,交给脚本引擎来完成. 现代浏览器的优化能力确实很强,但是,运行时的优化终归是有限的.如果能在事先实现,则可 ...

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

    上一篇,我们发现大多数 6502 指令都可以直接 1:1 翻译成 JS 代码,但除了「跳转指令」. 跳转指令,分无条件跳转.条件跳转.从另一个角度,也可分: 静态跳转:目标地址已知 动态跳转:目标地址 ...

  5. 机器指令翻译成 JavaScript —— 终极目标

    上一篇,我们顺利将 6502 指令翻译成 C 代码,并演示了一个案例. 现在,我们来完成最后的目标 -- 转换成 JavaScript. 中间码输出 我们之所以选择 C,就是为了使用 LLVM.现在来 ...

  6. 机器指令翻译成 JavaScript —— No.3 流程分割

    上一篇 我们讨论了跳转指令,并实现「正跳转」的翻译,但最终困在「负跳转」上.而且,由于线程模型的差异,我们不能 1:1 的翻译,必须对流程进行一些改造. 当初之所以选择翻译,而不是模拟,就是出于性能考 ...

  7. 机器指令翻译成 JavaScript —— No.4 动态跳转

    上一篇,我们用模拟流程的方式,解决了跳转问题. 不过静态跳转,好歹事先是知道来龙去脉的.而动态跳转,只有运行时才知道要去哪.既然流程都是未知的,翻译从何谈起? 动态跳转,平时出现的多吗?非常多!除了 ...

  8. quartz定时任务cron表达式讲解及翻译成现实语言的插件的使用详解

    cron表达式讲解 参见该网址: https://www.cnblogs.com/GarfieldTom/p/3746290.html cron表达式只有专业技术人员才看得懂,普通人不知道表达式是什么 ...

  9. 利用Google翻译成多国语言的见解

    1.首先注意,英语句子中的 第一个单词的首字母要大写, 2.句子结尾了,要用句号. 3.英语中单词和前面的标点符号要留一个空格,如:  you.Are   应该是 you. Are you..... ...

随机推荐

  1. 从直播编程到直播教育:LiveEdu.tv开启多元化的在线学习直播时代

    2015年9月,一个叫Livecoding.tv的网站在互联网上引起了编程界的注意.缘于Pingwest品玩的一位编辑在上网时无意中发现了这个网站,并写了一篇文章<一个比直播睡觉更奇怪的网站:直 ...

  2. SQL Server 大数据搬迁之文件组备份还原实战

    一.本文所涉及的内容(Contents) 本文所涉及的内容(Contents) 背景(Contexts) 解决方案(Solution) 搬迁步骤(Procedure) 搬迁脚本(SQL Codes) ...

  3. 在离线环境中发布.NET Core至Windows Server 2008

    在离线环境中发布.NET Core至Windows Server 2008 0x00 写在开始 之前一篇博客中写了在离线环境中使用.NET Core,之后一边学习一边写了一些页面作为测试,现在打算发布 ...

  4. .net 大型分布式电子商务架构说明

    .net大型分布式电子商务架构说明 背景 构建具备高可用,高扩展性,高性能,能承载高并发,大流量的分布式电子商务平台,支持用户,订单,采购,物流,配送,财务等多个项目的协作,便于后续运营报表,分析,便 ...

  5. 【用户交互】APP没有退出前台但改变系统属性如何实时更新UI?监听系统广播,让用户交互更舒心~

    前日,一小伙伴问我一个问题,说它解决了半天都没解决这个问题,截图如下: 大概楼主理解如下: 如果在应用中有一个判断wifi的开关和一个当前音量大小的seekbar以及一个获取当前电量多少的按钮,想知道 ...

  6. background例子

  7. BPM配置故事之案例12-触发另外流程

    还记得阿海么,对就是之前的那个采购员,他又有了些意见. 阿海:小明,你看现在的流程让大家都这么方便,能不能帮个忙让我也轻松点啊-- 小明:--你有什么麻烦,现在不是已经各个部门自己提交申请了嘛? 阿海 ...

  8. iOS之开发中一些相关的路径以及获取路径的方法

    模拟器的位置: /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs ...

  9. Oracle 11g必须开启的服务及服务详细介绍

    转自:http://www.educity.cn/shujuku/404120.html 成功安装Oracle  11g数据库后,你会发现自己电脑运行速度会变慢,配置较低的电脑甚至出现非常卡的状况,通 ...

  10. .Net 初步学习笔记之一——.Net 平台与.Net FrameWork框架的关系

    .Net 包含两部分 .Net平台 和.Net FrameWork 框架 1..Net FrameWork框架包含于.Net平台. .Net FrameWork提供环境和支撑保证.Net平台运行. 2 ...