机器指令翻译成 JavaScript —— No.7 过渡语言
上一篇,我们决定使用 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 过渡语言的更多相关文章
- 【探索】机器指令翻译成 JavaScript
前言 前些时候研究脚本混淆时,打算先学一些「程序流程」相关的概念.为了不因太枯燥而放弃,决定想一个有趣的案例,可以边探索边学. 于是想了一个话题:尝试将机器指令 1:1 翻译 成 JavaScript ...
- 机器指令翻译成 JavaScript —— No.5 指令变化
上一篇,我们通过内置解释器的方案,解决任意跳转的问题.同时,也提到另一个问题:如果指令发生变化,又该如何应对. 指令自改 如果指令加载到 RAM 中,那就和普通数据一样,也是可以随意修改的.然而,对应 ...
- 机器指令翻译成 JavaScript —— No.6 深度优化
第一篇 中我们曾提到,JavaScript 最终还得经过浏览器来解析.因此可以把一些优化工作,交给脚本引擎来完成. 现代浏览器的优化能力确实很强,但是,运行时的优化终归是有限的.如果能在事先实现,则可 ...
- 机器指令翻译成 JavaScript —— No.2 跳转处理
上一篇,我们发现大多数 6502 指令都可以直接 1:1 翻译成 JS 代码,但除了「跳转指令」. 跳转指令,分无条件跳转.条件跳转.从另一个角度,也可分: 静态跳转:目标地址已知 动态跳转:目标地址 ...
- 机器指令翻译成 JavaScript —— 终极目标
上一篇,我们顺利将 6502 指令翻译成 C 代码,并演示了一个案例. 现在,我们来完成最后的目标 -- 转换成 JavaScript. 中间码输出 我们之所以选择 C,就是为了使用 LLVM.现在来 ...
- 机器指令翻译成 JavaScript —— No.3 流程分割
上一篇 我们讨论了跳转指令,并实现「正跳转」的翻译,但最终困在「负跳转」上.而且,由于线程模型的差异,我们不能 1:1 的翻译,必须对流程进行一些改造. 当初之所以选择翻译,而不是模拟,就是出于性能考 ...
- 机器指令翻译成 JavaScript —— No.4 动态跳转
上一篇,我们用模拟流程的方式,解决了跳转问题. 不过静态跳转,好歹事先是知道来龙去脉的.而动态跳转,只有运行时才知道要去哪.既然流程都是未知的,翻译从何谈起? 动态跳转,平时出现的多吗?非常多!除了 ...
- quartz定时任务cron表达式讲解及翻译成现实语言的插件的使用详解
cron表达式讲解 参见该网址: https://www.cnblogs.com/GarfieldTom/p/3746290.html cron表达式只有专业技术人员才看得懂,普通人不知道表达式是什么 ...
- 利用Google翻译成多国语言的见解
1.首先注意,英语句子中的 第一个单词的首字母要大写, 2.句子结尾了,要用句号. 3.英语中单词和前面的标点符号要留一个空格,如: you.Are 应该是 you. Are you..... ...
随机推荐
- 从直播编程到直播教育:LiveEdu.tv开启多元化的在线学习直播时代
2015年9月,一个叫Livecoding.tv的网站在互联网上引起了编程界的注意.缘于Pingwest品玩的一位编辑在上网时无意中发现了这个网站,并写了一篇文章<一个比直播睡觉更奇怪的网站:直 ...
- SQL Server 大数据搬迁之文件组备份还原实战
一.本文所涉及的内容(Contents) 本文所涉及的内容(Contents) 背景(Contexts) 解决方案(Solution) 搬迁步骤(Procedure) 搬迁脚本(SQL Codes) ...
- 在离线环境中发布.NET Core至Windows Server 2008
在离线环境中发布.NET Core至Windows Server 2008 0x00 写在开始 之前一篇博客中写了在离线环境中使用.NET Core,之后一边学习一边写了一些页面作为测试,现在打算发布 ...
- .net 大型分布式电子商务架构说明
.net大型分布式电子商务架构说明 背景 构建具备高可用,高扩展性,高性能,能承载高并发,大流量的分布式电子商务平台,支持用户,订单,采购,物流,配送,财务等多个项目的协作,便于后续运营报表,分析,便 ...
- 【用户交互】APP没有退出前台但改变系统属性如何实时更新UI?监听系统广播,让用户交互更舒心~
前日,一小伙伴问我一个问题,说它解决了半天都没解决这个问题,截图如下: 大概楼主理解如下: 如果在应用中有一个判断wifi的开关和一个当前音量大小的seekbar以及一个获取当前电量多少的按钮,想知道 ...
- background例子
- BPM配置故事之案例12-触发另外流程
还记得阿海么,对就是之前的那个采购员,他又有了些意见. 阿海:小明,你看现在的流程让大家都这么方便,能不能帮个忙让我也轻松点啊-- 小明:--你有什么麻烦,现在不是已经各个部门自己提交申请了嘛? 阿海 ...
- iOS之开发中一些相关的路径以及获取路径的方法
模拟器的位置: /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs ...
- Oracle 11g必须开启的服务及服务详细介绍
转自:http://www.educity.cn/shujuku/404120.html 成功安装Oracle 11g数据库后,你会发现自己电脑运行速度会变慢,配置较低的电脑甚至出现非常卡的状况,通 ...
- .Net 初步学习笔记之一——.Net 平台与.Net FrameWork框架的关系
.Net 包含两部分 .Net平台 和.Net FrameWork 框架 1..Net FrameWork框架包含于.Net平台. .Net FrameWork提供环境和支撑保证.Net平台运行. 2 ...