VML 上游项目:22 语言前端 × 17 目标后端
你手上这份都来码里随包附带的 VML,是上游工程 VML Toolchain 的精简发行版。 这一页讲的是上游那一份的完整样子 —— 它本身是一套「多前端 → 统一 IR → 多后端」的编译器工具链, 可以脱离都来码,在桌面上独立使用。
它和都来码里那份是什么关系
一句话:同一个项目,两个发行范围。上游是完整工程(含桌面 IDE 与硬件模拟器), 都来码里那份是按「在手机与桌面进程内跑得起来」这个目标裁过的子集。
| 都来码随包内置的那份 | 上游 VML Toolchain | |
|---|---|---|
| 引入方式 | 源码复制(vendoring)进本仓库,进程内调用 | 独立仓库,自己构建、自己发行 |
| 汇编器 / 虚拟机 / 语言前端 | 有(27 门) | 有(22 门) |
| 17 个后端翻译器 | 有(编译依赖,不是可选装饰) | 有 |
| 标准库与示例 | 有(标准库完整;示例留精选子集) | 有(示例是完整目录) |
| 桌面 IDE | 没有 | 有(VMLIde) |
| 硬件模拟器 | 没有 | 有(ConsoleEmulator / FullDevicesEmulator) |
| 文档站与网页 Demo | 没有 | 有(本页内容即整理自它) |
裁掉的都不影响「写一段代码、编出来、在手机上跑起来」这条主线 —— 它们是桌面上的调试与演示设施。 反过来说,上游能做的事比随包那份多:想跑老机器模拟器、想烧到单片机上、想用桌面 IDE, 就去上游。
口径说明:上游首页写的是「22 种语言 × 18 种架构」,但枚举出来是 17 个后端
(本仓库里 third_party/vml 下也是 17 个:8 位 5 个、16 位 2 个、32 位 7 个、虚拟机 3 个)。
本页按枚举列,写 17 个。
它是什么
一套编译器的总线:前端把各种语言编到同一个中间表示,后端再把中间表示翻到各种架构。 写 C / Python / Rust / Go / Java … 任意一门,编译成 VML 汇编,再翻译到 6502 / ARM / RISC-V / x86 … 任意一个目标。
源代码 .c .bas .pas .py .lua .fth .rs .go .ld .java .js .cs
.swift .cpp .kt .scm .rb .dart .m .r .d .f90
↓ 22 个前端编译器
统一 IR VML 汇编 (.vml) → VMB 二进制 (.vmb)
↓ 17 个后端翻译器
目标平台 6502 / Z80 / 8051 / AVR / PIC / MSP430 / PIC24 / ARM-CM
x86 / MIPS / RISC-V / 68000 / PowerPC / SPARC / JVM / .NET / Wasm
一个贯穿全项目的约定:所有语言的编译命令是同一条,编译器按文件扩展名自动选择。 学一次命令行,22 门语言通用。
vmltool hello.c -o hello.vml
vmltool -r hello.vml
22 门语言前端
每门语言一个独立的前端编译器,都走同一条四阶段流水线: 词法分析 → 语法分析 → 代码生成 → VML 汇编输出。 其中 16 个达到生产可用(完成度 90% 以上),6 个为新增的教学可用级别。
| 语言 | 扩展名 | 完成度 | 调用约定 |
|---|---|---|---|
| C | .c | ~96% | cdecl |
| Forth | .fth | ~94% | 栈式 |
| JavaScript | .js | ~94% | stdcall |
| C++ | .cpp | ~93% | cdecl |
| Rust | .rs | ~93% | stdcall |
| Kotlin | .kt | ~93% | stdcall |
| Scheme | .scm | ~93% | stdcall |
| Ladder | .ld | ~93% | — |
| BASIC | .bas | ~92% | CCv2 |
| Pascal | .pas | ~92% | pascal |
| Lua | .lua | ~91% | stdcall |
| Go | .go | ~90% | stdcall |
| Python | .py | ~90% | stdcall |
| C# | .cs | ~90% | stdcall |
| Java | .java | ~90% | stdcall |
| Swift | .swift | ~90% | stdcall |
| Ruby | .rb | ~85% | stdcall |
| Dart | .dart | ~80% | stdcall |
| Objective-C | .m | ~80% | cdecl |
| R | .r | ~80% | stdcall |
| D | .d | ~80% | stdcall |
| Fortran | .f90 | ~80% | stdcall |
前端之间共享代码:所有语言共用词法分析基类与代码生成基类;C 系语言(C / Go / Rust / JavaScript) 共用表达式与类型系统那一层;面向对象语言(C++ / Java / C# / Swift / Kotlin)再共用一个类、 继承与虚函数那一层。
四种调用约定(cdecl / CCv2 / pascal / stdcall)决定了参数怎么传、栈由谁清 —— 22 门语言彼此可以互相调用,靠的就是这一层约定对齐。
17 个目标后端
翻译是四段:解析 VML 指令 → 指令与寄存器映射 → 生成目标汇编 → 格式化产物(HEX / BIN / ELF / S19)。 8 位架构用零页内存模拟寄存器,ARM Cortex-M 与 RISC-V 可以做到 1:1 直接映射。
| 字长 | 架构 | 典型去处 |
|---|---|---|
| 8 位 | 6502 | Apple II / C64 / NES |
| 8 位 | Z80 | CP/M / ZX Spectrum / MSX |
| 8 位 | 8051 | 嵌入式 MCU 经典 |
| 8 位 | AVR | Arduino / ATmega328P |
| 8 位 | PIC | Microchip PIC16F84A |
| 16 位 | MSP430 | TI 超低功耗 |
| 16 位 | PIC24 | Microchip 16 位 |
| 32 位 | ARM Cortex-M | STM32 / Cortex-M |
| 32 位 | x86 | IBM PC / 通用计算 |
| 32 位 | MIPS | PIC32 / 网络设备 |
| 32 位 | RISC-V | SiFive / 开源 ISA |
| 32 位 | 68000 | Amiga / Atari ST |
| 32 位 | PowerPC | Xilinx PPC405 / 游戏机 |
| 32 位 | SPARC | LEON3 / 航天级 |
| 虚拟机 | JVM | Java 字节码 |
| 虚拟机 | .NET | CLR / CIL |
| 虚拟机 | Wasm | 浏览器 / WebAssembly |
烧录用的是一个一站式命令,从源码一路到固件:
vml2hex blink.c -arch arm-cm -f hex -o blink.hex
三个优化档:-O0 不优化(调试)、-O1 死代码消除 + 常量折叠(默认)、
-O2 再加复制传播与循环优化(发布)。
六种输出格式:Intel HEX(默认)、Binary、Motorola S19、ELF、DOS EXE、Text Dump。
运行时与模拟器
同一份 VMB 字节码,有多个运行时可以执行它 —— 从「跑得快」到「跑在老机器上」都覆盖:
| 运行时 | 语言 | 特点 |
|---|---|---|
| VMLRuntime | C# (.NET) | 核心执行引擎:92 条指令、16 个通用寄存器 R0–R15、8 个浮点 F0–F7、4 个双精度 D0–D3、30 多个系统调用、中断与异常(CLI / STI / IRET、TRY / CATCH)、MMIO 设备模拟、VMB 加载与保存 |
| VMLRuntimeC | C99 | 跨平台原生运行时,零依赖、约 100 KB:92 / 92 个操作码与 C# 运行时逐号对齐,VGA 80×25 文本渲染、PC 扬声器模拟与 WAV 录音,可静态链接成单文件 |
| ConsoleEmulator | C# (.NET) | 终端模拟器:VGA 文本模式、按键脚本自动化,可模拟六种老机器(Apple II / Commodore 64 / ZX Spectrum / MSX / Amstrad CPC / Commodore PET) |
| FullDevicesEmulator | C# (Avalonia) | 图形界面的全设备模拟器:设备可视化、实时状态面板 |
| VMLIde | C# (Avalonia) | 跨平台桌面 IDE(约 70% 完成):代码编辑、断点单步与寄存器查看、设备可视化、AI 辅助面板 |
内存布局是固定的一段 32 位地址空间:代码段从 0x000000 起、数据段从 0x000400 起、
堆从 0x008000 向上增长、栈从顶部向下增长;0xE000 是 FFI 函数指针表、
0xF000 是 MMIO 设备寄存器。
CLI 工具集
| 工具 | 做什么 |
|---|---|
| VMLTool | 主工具,GCC 风格命令行,按扩展名自动选编译器;一条命令完成「编译 → 运行」,也能只生成汇编、只做预处理 |
| VMLAssembler | 汇编器与反汇编器:VML 汇编文本 ↔ VMB 二进制,两个方向都通;标签解析、伪指令、表达式求值、库链接都由它做 |
| VMLToHex | 编译 + 链接 + 翻译 + 产物格式化:17 个目标架构、6 种输出格式,-O0/-O1/-O2 三档优化 |
| VMLPacker | 打包成独立可执行文件,可跨平台交叉打包;也有 C 语言版本,静态编译后体积更小 |
| GenLib | 从 C 源码自动编译 VML 静态共享库,并为 22 种语言生成模块包装与绑定代码 |
| GenDyn | 扫描 .dylib / .so / .dll 的导出符号,生成 FFI 包装器与 22 种语言调用绑定 |
| GenDev | 从 XML 设备描述文件自动生成 22 种语言的设备驱动代码(寄存器地址、外设初始化函数) |
生成器这一组有个共同的设计:一次生成,22 种语言同时到手。 写一个 C 库,GenLib 会替你把别的 21 种语言的调用方式也生成出来。
FFI 外部函数接口
VML 程序能调原生动态库 —— 而且22 门语言都用自己那门语言的原生语法写,不用手写汇编。 调用链是四个系统调用接起来的:
| 系统调用 | 编号 | 做什么 |
|---|---|---|
dl_open | #370 | 加载动态库,返回句柄 |
dl_sym | #371 | 按名字查找函数,返回函数指针编号 |
dl_close | #372 | 卸载动态库 |
native_call | #373 | 调用原生函数(整数 / 指针参数,最多 8 个) |
native_call_0 | #374 | 调用无参原生函数 |
native_call_f | #375 | 调用原生函数(浮点参数,为 OpenGL 那一类接口准备) |
上游的完整示例是同一个 OpenGL 旋转立方体,用 22 种语言各写一遍, 全部调用同一个 VML 封装库、同一个原生动态库。每种语言的主循环都长这样:
fn main() {
gl_setup();
while gl_should_close() == 0 {
gl_frame();
}
gl_teardown();
}
跨语言调用有一个细节值得一提:不同语言的符号命名习惯不一样(Lua 加前缀、Forth 加前缀、 Pascal 与 C# 需要先声明),封装库把各种别名一并导出,所以每种语言都能用最自然的那种写法调用。
OS 模式与 MCU 模式
同一个编译器有两副面孔,由模式开关决定生成什么:
| OS 模式 | MCU 模式 | |
|---|---|---|
| 产物 | 桌面应用 | 零依赖裸机固件,一路到 HEX 烧录 |
| 动态库加载 | 有(FFI) | 没有 |
| 图形 | OpenGL 那一套 | 没有 |
| C 标准库 | 完整 | BIOS 保留 malloc 与文件 I/O |
| 跳过的部分 | — | 异步、线程、垃圾回收 |
这不是"两个项目共用一套代码",而是同一套前端、同一份标准库,只换后端与运行时 —— 程序写得对,两个模式都能编过。
从哪开始
上游构建与第一个程序(在上游仓库里执行):
git clone https://gitee.com/aleckstygit/vml.git
dotnet build VMLToolchain.sln
vmltool hello.c -o hello.vml
vmltool -r hello.vml
本页内容整理自上游项目自己的介绍页(web/ 目录),
数字与表格均照其原文;「17 个后端」是按枚举口径的更正,未经上游确认。
上游许可:MIT License。都来码随包内置的那份以源码复制方式并入本仓库的
third_party/vml/,本地适配说明见该目录下的 README。
都来码 Dolaima