说个前段时间被反复问到的场景:有人想看中文能不能写底层一点的东西,比如一个驱动、一段启动代码,而不是整天在业务层做中文变量名。这句话点醒我一个一直没落地的想法——与其纠结“怎么把Java关键字翻译成中文”,不如直接下沉到指令集这一层,把助记符本身做成中文。我花了大概三个月的业余时间,搞出来一套叫“伏羲-128”的全中文字义指令集:128条指令,每条指令由一个或两个有明确动作含义的汉字承担,配套一个汇编器、一个虚拟机,以及一套能映射到常见CPU指令的翻译模板。这东西不是什么玩具,它能跑斐波那契、能写一小段排序、能给脚本语言做后端,也可以拿来教学生理解寄存器、栈、程序计数器这些概念。
这篇文章我会把这套指令集从立项到落地的完整过程拆开讲,包括128这个数字怎么定的、字表怎么建、汇编器怎么处理中文分词、为什么先做VM而不是直接怼机器码、踩过哪些全角符号和输入法相关的大坑。适合对编译器、汇编、指令集设计感兴趣的开发者,也适合一直在思考“中文到底能不能做底层技术”的朋友参考。
1. 立项前纠结了很久的问题:助记符中文化的本质是什么
1.1 中文编程做了几十年,为什么没人把汇编中文化到底
很多中文编程项目做的事情其实是用中文关键字替换英文关键字,比如if变成“如果”,else变成“否则”,class变成“类”。这种方案本质上是给现有语言的语法树披了一层中文皮,底层仍然是英文关键字驱动的解析逻辑。它当然有它的价值,降低了部分初学者的入门门槛,但说实话,换皮替换对理解“程序到底怎么跑”帮助不大。
真正让我觉得有空间的地方在汇编层。你看指令集设计里有一件很有趣的事:像MOV、ADD、JMP、PUSH这些助记符,它们本质上只是操作码给人看的一个名字。操作码0x01到底代表“把数据搬进寄存器”还是“把数据存回内存”,完全是芯片设计者说了算。既然助记符只是一个“给操作码起的可读名字”,那它换成“取”“存”“跳”“入”这些汉字,从原理上完全说得通。
但“把助记符翻译成汉字”和“设计一套字义指令集”是两码事。前者只是做了一张英文到中文的映射表,后者要求整个编码体系、查表逻辑、操作数规则都围绕汉字来组织。这也是我给伏羲-128定下的基本原则:不是MOV加个注释叫“移动”,而是从根上让“取”这个字负责取数语义,“入”这个字负责压栈语义,字和指令之间是真正的绑定关系。
1.2 从“句法翻译”到“字义编码”:我对指令集的理解
中文编程社区里有个争论一直没停过:如果中文名可以出现在代码里,那算不算中文编程?我的看法是,普通应用层代码里出现中文名当然方便,但那只是标识符层面的本地化。而“字义指令集”是把指令系统中的每一个符号都当成有意义的中文单元来参与编码,包括操作码、寄存器编号、甚至标志位,这些在传统体系里往往都是英文缩写。
举个具体例子。传统汇编里你会写:
asm复制MOV AX, 5
ADD AX, 1
如果只是中文化助记符,会变成:
asm复制移动 寄存器甲, 5
增加 寄存器甲, 1
但“字义指令集”的思路不一样。我不需要有一个英文助记符作为源头,指令表本身就是一套中文体系。例如伏羲-128里同样的操作写作:
text复制取 甲, 五
加 甲, 一
“取”这个字既承担了记忆作用,也承担了查表作用。汇编器看到“取”就知道操作码是0x01,看到“加”就知道操作码是0x0A,不再经过任何英文中转。这就是字义编码和翻译替换之间的本质区别。
1.3 128这个数字是怎么定下来的,以及项目名来源
很多人看到“伏羲-128”第一反应是问:128是不是代表128位?其实不是,128代表的是指令条数。我初版整理需求的时候,只统计最常用的数据传送、算术逻辑、栈操作和控制流指令,大约六七十条就够用了。但接着我意识到一个问题:如果这套指令集将来要承接函数调用、中断处理、浮点运算,甚至给一个简单脚本语言做后端,那六七十条会非常紧张。
后来我按三个层次做了词表规划。第一层是核心指令,包括常见的载入、存储、算术、逻辑、比较、跳转,大约五十条;第二层是栈帧与过程调用相关的指令,包括压栈、出栈、调用、返回等,约三十条;第三层是输入输出、系统控制、调试辅助这类“环境相关”指令,约四十条。加起来一百二左右,再加上若干预留空位组成了128。选择128还有一个工程上的理由:7位二进制就能编下,最高位留作
