ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

从零写一个多后端编译器:Java 字节码、JIT、GC 和 GraalVM Polyglot

从零写一个多后端编译器:Java 字节码、JIT、GC 和 GraalVM Polyglot 从零写一个多后端编译器Java 实现字节码、分层 JIT、GC 和 GraalVM Polyglot 跨语言互操作UniVM 是一个编译器实验项目多前端 → 统一字节码 → 解释器 分层 JIT → C / C / Go / JAR 多后端。M1–M6 全部验证通过JIT 加速 2.47xOSR 1.97x并已在 GraalVM Polyglot 上跑通跨语言互操作的第一步。一、为什么做这个项目编译器是计算机科学里最容易被敬而远之的方向之一。教科书讲龙书、讲 LLVM IR、讲 SSA但真正动手写一个能跑、能优化、能多端输出的编译器中间有大量工程细节没人告诉你。UniVM 的出发点是用一套最小的架构把编译器的完整链路走一遍。它的设计模型借鉴 JVM / GraalVM每种语言写一个前端 → 全部落到同一套栈式字节码 → 一个 VM解释器 JIT→ 多个后端输出。不存在一个编译器编译所有语言。JVM 能跑 Java / Kotlin / Scala靠的就是多前端 单字节码 单 VM。目前 M1–M6 已经全部打通并多端验证一致M7 在推进 GraalVM Polyglot 跨语言互操作。二、整体架构text.uni / .py 源码↓ Lexer Parser按扩展名选前端AST↓ TypeCheckerINT / ARRAY 检查Module↓ Codegen栈式指令 结构化控制节点字节码↓解释器 (Vm) ←→ 分层 JIT (Jit) ←→ Heap GC↓C / C / Go / JAR 四种后端核心文件都在 src/ 下模块 文件.uni 前端 Lexer.java Parser.javaPython 前端 LexerPy.java ParserPy.javaAST Ast.java类型检查 TypeChecker.java字节码生成 Codegen.java解释器 Vm.java分层 JIT Jit.java堆 GC Heap.java ArrayTypes.java后端 BackendC.java BackendCpp.java BackendGo.java BackendJava.java整个项目只有 17 个 Java 文件127 KB 源码。不依赖任何第三方库。三、多前端同一套 ASTUniVM 有两种前端.uni 前端类 C 语法递归下降解析支持 let、赋值、print、if/elif/else、while、func、return、表达式语句、数组。Python 前端缩进块、def、elif产出同一套 AST。关键点是前端只负责把源码变成 AST不关心后端。 两种前端落到同一个 Module之后所有阶段完全复用。M5 验证里py_demo.py 经 Python 前端编译后解释器 / jar / Go 三个后端输出完全一致text7 120 3628800 55 3 2 1 5050四、分层 JIT2.47x 加速Jit.java 实现了一个简单的分层 JITTier 0解释器逐条执行字节码。Tier 1当某个函数被调用超过阈值1000 次时编译成一份扁平化的表示跳过解释器循环。OSROn-Stack Replacement对长循环不等到函数被再次调用直接在循环回边达到阈值时替换栈上帧。M4 验证里jit.uni 里的 work 函数被调用 3001 次第 1000 次触发编译模式 耗时解释器 2914 msTier 1 JIT 1179 ms加速比 2.47xM4b 验证 OSRosr.uni 函数只调用 1 次但回边执行 200 万次模式 耗时解释器 325 msOSR 165 ms加速比 1.97x这两个数据不算惊艳——真正的 JVM C2 编译器加速比往往是 10x 以上。但它们的意义在于验证了分层 JIT 的核心机制热点检测、编译触发、扁平化表示、栈上替换。–profile 参数会打印调用计数、C1/OSR 编译、Tier2 状态与堆/GC 统计。在本机普通 OpenJDK无 GraalVM JVMCI时它如实报告 Tier2 不可用。五、类型系统与追踪式 GCM6 加了两个东西类型检查TypeChecker.java作用域符号表只支持 INT 和 ARRAY 两种类型检查未定义变量、实参数量、操作数类型。–no-check 可以关掉。堆 GCHeap.java ArrayTypes.java数组是引用类型[n] 分配、a[i] 读写、len(a) 取长度。GC 是保守追踪式的根 解释器与编译码各帧的局部变量 操作数栈标记保守扫描数据恰好等于句柄只会多留不会误释放回收每 100 次分配触发一次M6 验证里 arrays.uni 输出 30 499 10502 次分配、5 次 GC、每次回收约 100 个对象。jar 和 Go 后端输出一致。六、多后端一套字节码四种输出后端 输出 数组表示 GCC .c long long* 无需手动C .h .cpp vector* 无Go .exe []int64 Go 自带Java .jar long[] JVM 自带每个后端都是独立的类把 Module 翻译成对应语言的源码再调用外部工具链gcc / g / go build / javac编译。M3 验证里funcs.uni 在解释器 / jar / Go 三个后端输出完全一致text7 120 3628800 55多后端一致性的意义它证明字节码语义定义是清晰的。如果同一份字节码在解释器和编译产物上跑出不同结果说明语义有歧义。这个约束逼着我在设计字节码时把每个指令的语义想清楚。七、M7GraalVM Polyglot 跨语言互操作M7 的目标是让 Uni 编译出的函数能被 JavaScript、Python 等 guest language 直接调用。第一步已经在 polyglot/PolyglotDemo.java 里跑通javatry (Context context Context.newBuilder().allowAllAccess(true).build()) {// 1. Java 函数暴露给 JS context.getBindings(js).putMember(addOne, (java.util.function.IntUnaryOperator) x - x 1); int r1 context.eval(js, addOne(41)).asInt(); // 2. 模拟 Uni 堆对象暴露给 JS context.getBindings(js).putMember(uniHeap, new UniHeapStub()); int r2 context.eval(js, uniHeap.load(7) uniHeap.load(35)).asInt(); // 3. 数组跨语言传递 context.getBindings(js).putMember(makeArray, (java.util.function.IntFunctionint[]) n - { int[] a new int[n]; for (int i 0; i n; i) a[i] i * i; return a; }); Value arr context.eval(js, makeArray(5));}输出text UniVM Polyglot Demo [JS - Java] addOne(41) 42[JS - UniHeap] load(7) load(35) 44[JS - Java array] squares 0 1 4 9 16 Demo 完成 下一步是把真实的 Codegen 输出注册为 Value让 JS 直接调用 .uni 文件里定义的函数。八、踩过的坑坑一GraalVM JDK 不自带 Polyglot API从 GraalVM for JDK 21 (23.1) 开始Polyglot API 不再捆绑在 JDK 里而是通过 Maven Central 分发。需要显式加依赖xmlorg.graalvm.polyglotpolyglot23.1.12org.graalvm.polyglotjs23.1.12pom坑二Windows 路径 Bug用 23.1.1 版本时在 Windows 上报textjava.nio.file.InvalidPathException:Illegal char : at index 2:/D:/UniVM/UniVM/polyglot/target/classes/23.1.2 版本移除了引发问题的 classpath 隔离代码Bug 消失。坑三版本必须与 JDK 严格对齐用 23.1.2 时又报textPolyglot version compatibility check failed.Your Java runtime ‘21.0.127-LTS-jvmci-23.1-b96’with compiler version ‘23.1.12’is incompatible with polyglot version ‘23.1.2’.JDK 补丁版本和 Polyglot artifact 版本必须同步升级。 改成 23.1.12 后一切正常。坑四PowerShell 写文件的 BOM 问题Set-Content -Encoding UTF8 会写入 BOMjavac 报 非法字符: ‘\ufeff’。解法powershell[System.IO.File]::WriteAllText($path,content,(New−ObjectSystem.Text.UTF8Encoding(content, (New-Object System.Text.UTF8Encoding(content,(New−ObjectSystem.Text.UTF8Encoding(false)))坑五网络本地 git push 到 GitHub 一直 Connection was reset。最后的方案是先把代码推到 Gitee再从 Gitee 导入到 GitHub。九、现状与路线图已完成✅ M1–M3变量 / 控制流 / 函数栈帧 多后端✅ M4分层 JIT2.47x✅ M4bOSR1.97x Tier2 能力探测✅ M5多语言前端Python 风格✅ M6类型系统 追踪式 GC✅ M7 第一步Polyglot 互操作最小验证待完成⬜ M7 完整把 Uni 编译产物注册为 Value⬜ 字符串统一到 TruffleString⬜ 真实 Heap 实现 Polyglot 数组协议⬜ Tier2换 GraalVM 后启用 JVMCI 机器码十、项目信息GitHubhttps://github.com/liyinqi1917/Uni_vmGitee 镜像https://gitee.com/liyinqi1971/univm许可证MIT环境JDK 17主项目GraalVM JDK 21 Maven 3.9Polyglot 子项目如果你对编译器、JIT、GC、GraalVM 感兴趣欢迎来看代码。CONTRIBUTING.md 里列了几个具体任务从简单的到较难的都有。
返回列表