机器指令翻译成 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..... ...
随机推荐
- CSS的未来
仅供参考 前言 完成<CSS核心技术与实战>这本书,已有一个多月了,而这篇文章原本是打算写在那本书里面的,但本章讲解的内容,毕竟属于CSS未来的范畴,而这一切都还不能够确定下来,所以这一章 ...
- premere cs4绿色版 安装 并且 视频导出 讲解
最近室友,开始在玩视频剪辑,用的是 premere cs4 绿色版.让他遇到的最大问题也是我之前遇到的最大问题,就是视频导出. 所以我在这里上传一套自己的一点点经验吧. 接下来,我就总结一下 我是怎么 ...
- 数据库的快照隔离级别(Snapshot Isolation)
隔离级别定义事务处理数据读取操作的隔离程度,在SQL Server中,隔离级别只会影响读操作申请的共享锁(Shared Lock),而不会影响写操作申请的互斥锁(Exclusive Lock),隔离级 ...
- 在Ubuntu 16.10 安装 git 并上传代码至 git.oschina.net
1. 注册一个账号和创建项目 先在git.oschina.net上注册一个账号和新建一个project ,如project name 是"myTest". 2.安装git sudo ...
- shiro权限管理框架与springmvc整合
shiro是apache下的一个项目,和spring security类似,用于用户权限的管理‘ 但从易用性和学习成本上考虑,shiro更具优势,同时shiro支持和很多接口集成 用户及权限管理是众多 ...
- webstorm下载&&安装过程&&打开项目
一.webstorm下载 WebStorm 是jetbrains公司旗下一款JavaScript 开发工具.被广大中国JS开发者誉为"Web前端开发神器"."最强大的HT ...
- VS2015使用scanf报错的解决方案
1.在程序最前面加: #define _CRT_SECURE_NO_DEPRECATE 2.在程序最前面加: #pragma warning(disable:4996) 3.把scanf改为scanf ...
- Ajax部分
Ajax的概念 AJAX即"Asynchronous Javascript And XML"(异步JavaScript和XML),是一种用于创建快速动态网页的技术. 动态网页:是指 ...
- 编译器开发系列--Ocelot语言7.中间代码
Ocelot的中间代码是仿照国外编译器相关图书Modern Compiler Implementation 中所使用的名为Tree 的中间代码设计的.顾名思义,Tree 是一种树形结构,其特征是简单, ...
- [AlwaysOn Availability Groups]健康模型 Part 1——概述
健康模型概述 在成功部署AG之后,跟踪和维护健康状况是很重要的. 1.AG健康模型概述 AG的健康模型是基于策略管理(Policy Based Management PBM)的.如果不熟悉这个特性,可 ...