Hotdry.
systems

纯CSS实现x86 CPU模拟器:选择器到指令翻译的工程路径

解析纯CSS编写完整8086 CPU模拟器的核心机制,包括选择器状态机映射、时钟驱动与性能约束。

当业界普遍将 CSS 视为样式表语言时,一个令人惊讶的工程挑战正在进行:使用纯 CSS 实现一个可工作的 x86 CPU 模拟器。x86CSS 项目由开发者 Lyra Rebane 创建,它能够在不使用任何 JavaScript 的情况下,在浏览器中运行真实的 8086 机器代码。这一项目的实现路径揭示了 CSS 作为计算框架的深层潜力,以及工程化实现过程中必须面对的性能约束。

选择器状态机:CPU 指令的 CSS 映射基础

x86CSS 的核心创新在于将 CPU 的指令执行模型转化为 CSS 选择器的状态变化。传统 CPU 通过取指、译码、执行三个步骤完成指令循环,而 x86CSS 将这一过程拆解为 CSS 规则的选择器匹配机制。每个 x86 寄存器、内存单元和标志位都被映射为 HTML 元素的不同状态,指令的执行则通过触发这些状态的切换来实现。

具体而言,项目使用 CSS 的兄弟选择器(~)和相邻选择器(+)构建指令解码逻辑。当 CPU 从内存中 “读取” 一个字节的机器码时,实际上是通过特定的选择器规则匹配来确定下一条指令的类型。这种映射方式要求开发者手动构建完整的 8086 指令集查找表,每个 opcode 对应一组 CSS 规则,用于设置目标寄存器或内存单元的状态值。

由于 8086 是复杂指令集(CISC),单条指令可能包含多种寻址模式和操作数长度。x86CSS 通过嵌套的选择器规则来处理这些变体,例如根据 ModR/M 字节的不同位域组合选择不同的寄存器或内存引用方式。这种层层嵌套的选择器结构虽然在逻辑上可行,但导致 CSS 规则的复杂度呈指数级增长,这也是项目采用 Python 脚本自动生成部分重复代码的原因。

时钟驱动:无 JavaScript 的时序实现

CPU 模拟器需要一个时钟信号来驱动指令的顺序执行。传统 Web 模拟器通常依赖 JavaScript 的setIntervalrequestAnimationFrame,但 x86CSS 要求完全排除 JavaScript。该项目实现了两种无脚本时钟方案:基于动画的主时钟和基于用户交互的备用时钟。

主时钟使用 CSS 动画(@keyframes)配合样式容器查询(Style Container Queries)来实现。动画持续运行产生时间基准,容器查询则根据时间变化调整样式规则,从而触发 CPU 状态机的状态转移。这种方式的优势在于不需要用户交互即可自动运行,但代价是性能较低且稳定性受限 —— 动画帧率受浏览器渲染管线制约,无法保证精确的时钟周期。

备用方案借鉴了 Jane Ori 的经典 CPU Hack 技术,通过监听鼠标悬停事件(:hover)来驱动时钟信号。用户将鼠标停留在页面特定区域时,CSS 规则持续匹配该状态并推进 CPU 周期。这种方式速度更快且更加稳定,但需要持续的用户输入。值得注意的是,围绕 “悬停是否满足图灵完备性” 存在争议,因此作者选择了动画方案以确保演示的 “无争议” 性。

内存与 I/O:CSS 环境的工程权衡

x86CSS 默认提供 0x600 字节(1.5KB)的模拟内存,程序加载地址为 0x100。这一内存容量限制并非技术不可逾越,而是工程权衡的结果 —— 更大的内存意味着更多的 CSS 规则和更复杂的样式计算。在实际浏览器环境中,过大的样式表会导致渲染性能急剧下降甚至页面卡死。

项目通过内存映射 I/O 实现与外设的交互。开发者预留了特定的 I/O 地址用于键盘输入和屏幕输出:地址 0x2000-0x2006 用于字符输出函数指针,地址 0x2100 用于控制键盘显示。程序可以通过写入这些特定地址来调用 CSS 实现的输出函数,例如显示自定义字符集或读取用户键盘输入。这种设计将硬件 I/O 寄存器映射为虚拟的内存地址,程序只需按照 8086 的 IN/OUT 指令规范操作这些地址即可。

需要说明的是,该实现并非完整的 8086 模拟。部分标志位(如 CF、OF)的行为与真实硬件存在差异,某些边缘情况也未被完全覆盖。作者采用了一种实用的工程方法:先编写想要运行的 C 程序,再用 GCC 编译,最后仅实现编译结果实际需要的指令。这种需求驱动的方式避免了实现大量未使用的指令,同时确保了目标程序能够正确运行。

编译链路:从 C 源码到 CSS 规则

x86CSS 提供了一套完整的构建工具链,使开发者能够用高级语言编写程序并在 CSS 模拟器中运行。首先使用 gcc-ia16(针对 16 位 8086 的 GCC 交叉编译器)将 C 代码编译为 8086 机器码,输出包含程序二进制(program.bin)和入口点地址(program.start)。随后运行 Python 脚本 build_css.py,该脚本读取机器码并生成对应的 HTML 文件,其中 CSS 规则完整映射了程序所需的指令集。

这套工具链支持 Linux 和 WSL 环境,默认输出仅 1.5KB 内存的模拟器。如需更大内存,可修改 build_css.py 中的配置参数。对于想要深入研究的开发者,项目还支持直接编写 8086 汇编语言 —— 只需将汇编编译后的二进制放入 program.bin 并指定入口地址即可。

性能现实与工程意义

必须坦诚地说,x86CSS 的性能远不及原生硬件甚至常见的 JavaScript 模拟器。CSS 选择器的匹配开销、动画帧率的限制以及浏览器渲染管道的延迟,共同决定了其执行速度处于 “概念验证” 级别。作者在项目文档中明确指出,这并非实用方案 —— 直接用 CSS 编写逻辑会比模拟整个 CPU 架构快得多。

然而,这一项目的价值在于探索 CSS 的计算边界和工程可能性。当我们讨论 CSS 是否属于图灵完备语言时,x86CSS 用实际行动给出了回答。它展示了如何将传统计算机体系结构的核心概念(指令集、寄存器、内存、时钟)映射到 Web 平台的基础设施中,为理解浏览器渲染引擎的工作原理提供了独特的视角,同时也为 “计算机艺术” 这一领域贡献了一个令人惊叹的作品。


资料来源:x86CSS 项目主页(lyra.horse/x86css)及 GitHub 仓库。

查看归档