网站首页 > 技术文章 正文
上一篇,我们顺利将 6502 指令翻译成 C 代码,并演示了一个案例。
现在,我们来完成最后的目标 —— 转换成 JavaScript。
中间码输出
我们之所以选择 C,就是为了使用 LLVM。现在来看看,生成的 LLVM 中间表示:
不难看出,顺序执行的逻辑都在一个 label 中,跳转则用 br 符号。
这种风格,和我们之前讨论的指令切割非常相似。一个 label 块,正好翻译成一个 block_xxx 的 JS 函数。
所以,理论上翻译成 JS 并不困难,写一个 LLVM backend 插件即可。
现有工具
不过,实际操作起来还是挺麻烦的。
LLVM 中间码和汇编差不多,一步一个操作。如果直接翻译,生成的 JS 会很累赘,类似这样:
$301 = MEM[16];
$302 = $301 + 10;
$303 = ($302 == 0);
if ($303) {
...
}
如果能将多步操作合并成一行 JS,则会简洁得多。另外,变量名分配和重用,也是比较麻烦的。
事实上 LLVM 输出成 JS,前辈们早就尝试过了。例如 emscripten,目前已非常成熟。所以,我们不如就用现成的工具吧。
emscripten 是如何处理流程及跳转的?其实和我们之前讨论的切割类似,也是用额外的变量模拟流程。只不过它是用数字变量,而不是函数变量。
例如这样的流程:
a: xx 1
goto c
b: xx 2
goto a
c: xx 3
goto b
生成的 JS 类似这样:
while (1) {
switch (label) {
case 1:
xx 1
label = 3; continue;
case 2:
xx 2
label = 1; continue;
case 3:
xx 3
label = 2; continue;
}
}
使用数字,就可以在同个 function 里控制流程,因此更合理一些。
但如果流程复杂,则会陷入众多的判断。哪个方案更好,还得看浏览器的实际优化能力。
线程模型
既然要在浏览器中运行,当然就不能用 sleep 了,取而代之的是 yield。
但 emscripten 生成的代码,显然是不会有 yield 的。因此,我们得手动实现上下文的切换。
我们在切出前,记住当前的流程位置,然后 return;下次调用时,根据上一次的流程位置,跳转到相应的地方继续执行(用上一篇提到的动态表)。
这样,就符合浏览器的线程模型了。
接口交互
之前为了演示,使用新线程 + getchar 来接受输入,这显然不符合浏览器的模型。
我们得监听键盘事件,在回调中更新相应的内存数据。
不过,emscripten 内置了 SDL框架,它封装了各种事件处理、图形渲染、音频播放等,非常实用。
SDL 会把消息记录在自己的队列里。任何时候,我们可以通过 Poll 的方式去拉取。这样就避免了回调,也不会有阻塞。
因此最终的模型,就类似这样:
void render {
cycle_remain = N;
input; // 获取输入
update; // 指令逻辑(执行到 cycle_remain <= 0)
output; // 屏幕输出
}
// 通过浏览器的 rAF 接口实现
emscripten_set_main_loop(render);
具体可以参考:这个文件。
最终结果
我们将上一篇的「贪吃蛇」编译成 JavaScript,在浏览器中运行:
在线演示(ASDW 控制方向)
由于 emscripten 打包了一些 C 运行时、辅助函数、SDL 框架等各种程序,所以生成的脚本很大,超过 200 KB。事实上 6502 指令对应的 JS 并不多,可以参考下面链接。
回顾下整个翻译过程:
机器码--> (现有工具) -->汇编码--> 小脚本 -->C 代码--> emscripten -->JS 代码
虽然这种方式不是最完美的,但实现起来很简单。
当然,我们的目标并非为了实现 6502 指令,只是借此学习一下「程序流程」相关的知识,以及探索一些开脑洞的想法。
猜你喜欢
- 2025-07-24 Web 服务器基准测试:Go vs. Node.js vs. Nim vs. Bun
- 2025-07-24 腾讯元宝上线AI编程模式:支持边修改代码边实时预览
- 2025-05-02 Node.js+Puppeteer:新一代动态爬虫利器,高效抓取不再难!
- 2025-05-02 现在可以部署到Web Assembly的6种语言
- 2025-05-02 七爪源码:这是在 Go 中进行实时重新加载的好方法
- 2025-05-02 哪种编程语言又快又省电?有人对比了27种语言
- 2025-05-02 阿里云迎来史上最特殊的员工:工号AI001,精通200 多种编程语言!
- 2025-05-02 使用 WebAssembly 和 Go 编写前端 Web 框架
- 2025-05-02 完全用 Go 编写的 JS 引擎(常用的编写javascript的工具有哪些)
- 2025-05-02 使用Docker和Codeship来装运Node.js应用
- 1517℃桌面软件开发新体验!用 Blazor Hybrid 打造简洁高效的视频处理工具
- 594℃Dify工具使用全场景:dify-sandbox沙盒的原理(源码篇·第2期)
- 521℃MySQL service启动脚本浅析(r12笔记第59天)
- 489℃服务器异常重启,导致mysql启动失败,问题解决过程记录
- 488℃启用MySQL查询缓存(mysql8.0查询缓存)
- 476℃「赵强老师」MySQL的闪回(赵强iso是哪个大学毕业的)
- 456℃mysql服务怎么启动和关闭?(mysql服务怎么启动和关闭)
- 453℃MySQL server PID file could not be found!失败
- 最近发表
-
- PS所有滤镜的说明(六)(ps滤镜详解)
- 5款小白也能用的在线图片编辑器!电商效率飙升就靠它!
- Java变量(java变量有什么作用)
- Java面试常见问题:Java注解(java中的面试题)
- Java编程入门第一课:HelloWorld(java编程从入门到实践)
- Java基础教程:Java继承概述(java里继承的概述)
- java基础之——访问修饰符(private/default/protected/public)
- 如何规划一个合理的JAVA项目工程结构
- 将机器指令翻译成 JavaScript -- 终极目标
- Web 服务器基准测试:Go vs. Node.js vs. Nim vs. Bun
- 标签列表
-
- cmd/c (90)
- c++中::是什么意思 (84)
- 标签用于 (71)
- 主键只能有一个吗 (77)
- c#console.writeline不显示 (95)
- pythoncase语句 (81)
- es6includes (73)
- windowsscripthost (67)
- apt-getinstall-y (86)
- node_modules怎么生成 (76)
- c++int转char (75)
- static函数和普通函数 (76)
- el-date-picker开始日期早于结束日期 (70)
- js判断是否是json字符串 (67)
- checkout-b (67)
- c语言min函数头文件 (68)
- asynccallback (71)
- localstorage.removeitem (74)
- vector线程安全吗 (70)
- java (73)
- js数组插入 (83)
- mac安装java (72)
- eacces (67)
- 查看mysql是否启动 (70)
- 无效的列索引 (74)