欢迎加入开源鸿蒙PC社区: https://harmonypc.csdn.net/

欢迎在PC社区平台申请新建项目:https://atomgit.com/OpenHarmonyPCDeveloper/

1 背景

MLton 是 Standard ML 语言的 whole-program 优化编译器,20241230 是当前最新的正式版本。它是这门语言事实上的性能标杆:官方基准数据显示 10 项测试中 8 项运行时第一,同为 SML 实现的 SML/NJ 平均比它慢 4.16 倍、二进制大 8.45 倍。它也有真实用户——定理证明器 HOL4 的核心用 SML 实现,验证开普勒猜想的 Flyspeck 项目用 MLton 构建官方发布版,这类形式化验证工具链要在鸿蒙上落地,SML 编译器是底座。而 SML 是 Rust、OCaml、F# 类型系统的源头,Rust 从 1.78 起把 OpenHarmony 列为 Tier 2 target 之后生态收益明显,语言工具链接入这条路是有先例的。

MLton 官方支持 12 组 OS 与架构组合,这次适配在它的平台列表里新增了 HarmonyOS 分支——不是只把编译器编过,而是 332 项回归测试在 OpenHarmony arm64 上原样跑通。对应的 PR !5084 已复审通过并合入 OpenHarmonyPCDeveloper/build_in_harmonyos 主线(配方在 archives/m/mlton/20241230),全程在 HUAWEI MateBook Pro(HAD-W32,HarmonyOS,arm64)真机上跑了 19 轮 CI:make check 332/332、六个验证 marker 全部输出、L0/L1/L4/L5 门禁全过,完整构建链耗时 68 分 51 秒。

图1 PR !5084 复审通过并合入主线

图1 PR !5084 复审通过并合入主线(开源鸿蒙PC社区 build_in_harmonyos 仓库)

2 上游假设与鸿蒙PC 环境的差异

MLton 的构建有两点不同于常规 C/C++ 库。一是自举:官方构建要先编译 bootstrap-polyml 作为引导编译器。二是它的回归测试会系统地触碰 libc 数学函数、文件系统、进程和信号,对目标平台的环境完整性要求很高。源码树放进真机跑第一轮构建后,实际遇到的问题集中在五处:

差异点上游假设鸿蒙PC 实际情况
libcglibc 舍入行为musl libc,部分 libm 结果差 1 ULP
文件系统常规 POSIXHMDFS 对 link() 返回 EPERM;/tmp 只读
打包工具GNU gzip 长选项toybox gzip 只支持短选项
签名无签名步骤binary-sign-tool 输出 mode 640,丢可执行位
引导编译器Poly/ML 可用ARM64 后端在 OHOS 上崩溃

这些差异单个都不难处理,麻烦在于它们同时出现在一次构建里,并且会被 332 项回归测试逐个暴露出来。

3 适配流程

适配流程改动自东北大学开源鸿蒙技术俱乐部的研究。王莹教授课题组在"面向OpenHarmony平台的C/C++软件库自动移植技术"课题中提出了知识驱动的自动移植方案 CROSS2OH:通过实证研究把跨平台不兼容问题归纳为八类、对应八种通用适配策略,并构建了覆盖 305 个 API 替代方案与 1599 个头文件路径映射的适配知识库,成果发表在 ASE 2025[1],东北大学团队的课题成果展播报道见[2]。本次适配使用的开发 skill 以这项工作为基础实现,我在其上做了个性化定制:先侦察确认源码地址和校验值,再写 Conan 配方和平台补丁,然后真机跑 CI,失败后诊断、修复、复核。

平台补丁(0001)主要实现:让 MLton 认识 OHOS。Basis Library 的平台类型新增 OHOS 构造,bin/platform 把 HarmonyOS 内核映射为 ohos,运行时复用经过实际编译验证的 Linux-musl 路径。关键改动节选:

--- a/basis-library/mlton/platform.sml
+++ b/basis-library/mlton/platform.sml
@@ -127,6 +127,7 @@
              | HPUX
              | Linux
+             | OHOS
              | MinGW
              | NetBSD
@@ -160,6 +162,7 @@
                 | "hpux" => HPUX
                 | "hurd" => Hurd
                 | "linux" => Linux
+                | "ohos" => OHOS
                 | "mingw" => MinGW

--- a/bin/platform
+++ b/bin/platform
@@ -52,6 +52,9 @@
 Linux)
         HOST_OS='linux'
 ;;
+HarmonyOS)
+        HOST_OS='ohos'
+;;
 MINGW*)
         HOST_OS='mingw'

这次的主要开发工作是在 AtomCode 上完成的。前期侦察和配方初稿在 OpenCode 里起步,之后的主体阶段——五处平台差异的定位、三份补丁的编写、引导编译器 polyml/5.9.2.1 的发布、19 轮 CI 的迭代修复——都由 AtomCode 接续执行到最终合入。用下来的体会是,这类移植任务里 AI 工具真正实现高效高质的地方不是写代码快,而是能把"取证—修复—验证"跑成固定动作:每轮 CI 失败后先产出诊断报告和证据日志,钉死根因才动补丁,然后定向复现旧 FAIL、新 PASS。人工负责的是每轮失败后的决策——修哪里、修到什么程度、什么不能动。

这条流程里我认为最关键且提高效率的点是:先取证,再动手。每个问题必须看到旧版本 FAIL、新版本 PASS 的对照才算闭环;并且要求AI通过联网取证对改动有充足的理由和证据;禁止吞错误,禁止放宽容差,禁止跳过用例。CI 共跑了 19 轮:前 13 轮解决平台识别、补丁格式、/tmp 只读、HMDFS 硬链接这些问题;第 14 到 16 轮逐个清掉浮点、gzip、签名三个点;第 17 轮首次全绿;第 19 轮复核通过。

4 自举链:Poly/ML 的 ARM64 崩溃

4.1 崩溃定位

第 6 轮 CI,引导编译器 Poly/ML 在编译 MLton 的过程中抛出 getAllocatedGenReg 内部错误,构建中断。定位后确认是 Poly/ML 的 ARM64 GC save-set 寄存器分配器的问题:跨 GC 存活的寄存器需要物化,但相应的寄存器槽从未被分配,运行时读到了空值。

4.2 官方无修复的验证

动手修之前先确认了一件事:上游是否已有修复。把官方针对 issue #277 的修复提交 5437d321 干净应用到 v5.9.2,固定输入下崩溃指纹与打补丁前完全一致;再检出官方 master 最新端点 d615dad7,同样输入依然复现。也就是说,上游历史无修复。这一步不能省——省了,本地补丁的正当性就说不清楚。

4.3 补丁与发布

补丁的内容很克制:已分配的 GenReg 复用,未分配的走原有冲突感知的 findRegister 分配,spill 失败路径保持原样报错。不默认寄存器,不删活跃值,不关优化。同时自建了 arm64_save_set_regression.ML 回归用例防止回退。按仓库规则,已发布的版本不能原地改配方,所以修复以 polyml/5.9.2.1 为独立版本号发布到社区 ohpcd 制品仓,MLton 配方里只声明依赖,不携带 Poly/ML 的文件:

def build_requirements(self):
    # Poly/ML is an explicit, audited source-built bootstrap compiler.
    self.tool_requires("polyml/5.9.2.1")

同输入旧 FAIL、新 PASS,产物哈希可复核。这种"独立补丁 + 回归用例 + 独立版本号"的做法,后续其他库遇到上游无修复的引导依赖问题时可以照搬。

5 浮点差异:atan2f 差 1 ULP

5.1 差异现象

第 13 轮 CI 后只剩一个失败用例,real 回归中的 atan2:

期望 atan2 (0.34028235E39, ~0.123E4) = 1.570796251
实际 atan2 (0.34028235E39, ~0.123E4) = 1.570796371

差异从第 8 位有效数字开始,是典型的 1 ULP 问题。这时候最忌讳直接改期望值——差异可能出在 MLton 代码生成、SML Basis 库、系统 libm 任何一层,没定位清楚就改期望,等于把可能的编译器 bug 藏起来。

5.2 三路交叉归因

验证方法是三路交叉:同一输入分别用 C 直接调用 atan2f、用 SML Basis 的 Real32.Math.atan2、用 SML 经 FFI 调用 atan2f,把结果按原始比特输出对比。三路逐位一致,都是 0x3fc90fdb。编译器无罪,这是 musl 与 glibc 在该输入下的舍入差异,而且 musl 返回的才是正确舍入值——上游 x86-linux 和 darwin 的 oracle 记录的正是这个值,只有默认的 amd64-linux oracle 记录了偏差值。

5.3 平台 oracle 变体

解法顺势而为:MLton 回归框架本来就支持按平台选择期望文件(测试名.平台.ok),新增一个 real.arm64-ohos.ok 变体会被自动拾取。公共 golden 不动,容差不放宽,用例不跳过。这条经验后来沉淀为社区知识库的 E332 条目:平台差异用平台 oracle 表达,不修改公共基线。

6 其余三处差异

这三处相对常规。HMDFS 不支持硬链接且 /tmp 只读,测试临时文件整体迁到 TMPDIR,硬链接用例改在支持硬链接的 /dev/shm 上用唯一文件前缀执行(实测 /dev/shm 不能建子目录,mkdtemp 用不了);toybox gzip 不认 GNU 长选项,一个 11 行的补丁把 --force --best 改成 -f -9;签名工具输出的文件 mode 是 640,替换后可执行位丢失、打包出的编译器无法运行,在 _sign_tree 里替换前记录原 mode、替换后恢复。

签名那处修复的关键片段(conanfile.py_sign_tree):

orig_mode = stat.S_IMODE(os.stat(path).st_mode)
subprocess.run([objcopy, "--remove-section", ".codesign", path, clean], check=True)
subprocess.run([signer, "sign", "-inFile", clean, "-outFile", signed,
                "-selfSign", "1"], check=True)
os.chmod(signed, orig_mode)     # 签名输出默认 mode=640,恢复原可执行位
os.replace(signed, path)

gzip 那处的补丁最短,全文只有 11 行,直接贴出来感受一下这类修复的量级:

--- a/Makefile
+++ b/Makefile
@@ -425,7 +425,7 @@
 	$(MKDIR) "$(TMAN)"
 	cd "$(SRC)/man" && $(CP) $(MAN_PAGES) "$(TMAN)/"
 ifeq (true, $(GZIP_MAN))
-	cd "$(TMAN)" && $(GZIP) --force --best $(MAN_PAGES);
+	cd "$(TMAN)" && $(GZIP) -f -9 $(MAN_PAGES);
 endif

 STRIP_PROGS := "$(TLIB)/$(MLTON_OUTPUT)$(EXE)"

其余 diff 都在仓库补丁文件里,这里不再展开。

7 真机运行与复现

PR 合入后,我在同一台 MateBook Pro 上做了一次端到端复现:从 ohpcd 制品仓下载 CI 构建的官方产物(与第 19 轮 CI 的 package 引用一致),编译并运行 test_package 的消费者测试。

(* MLton on HarmonyOS PC: consumer test from PR !5084 test_package *)
val answer = List.foldl op+ 0 [1, 2, 3, 4];
val _ = if answer = 10 then print "MLTON_CONSUMER_OK\n" else raise Fail "bad sum";
$ mlton -codegen c -output hello hello.sml   # 整程序编译约 3 秒
$ ./hello
MLTON_CONSUMER_OK

故意写错的语法样例同样被正确拒绝(Syntax error found at SEMICOLON)。负向测试在 test_package 里的写法很直白——编译必须失败,成功反而报错:

result = subprocess.run(
    [mlton, "-codegen", "c", "-output", output + "-bad", bad],
    stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
if result.returncode == 0:
    raise ConanException("negative syntax test unexpectedly succeeded")
self.output.info("NEGATIVE_TEST_MARKER: invalid SML rejected PASS")

图2 HUAWEI MateBook Pro 真机运行 MLton 20241230

图2 HUAWEI MateBook Pro(鸿蒙PC)真机运行 MLton 20241230:编译并执行 hello.sml

如果你手上有配置了社区适配环境的鸿蒙PC 设备,可以直接验证:

conan remote add ohpcd https://conan.cnb.cool/OpenHarmonyPCDeveloper/Conan/-/packages/
conan list "mlton/*" -r=ohpcd          # 查看可用版本与官方构建产物

图3 ohpcd 制品仓查询 mlton/20241230(鸿蒙PC 真机实测)

图3 ohpcd 制品仓查询 mlton/20241230(鸿蒙PC 真机实测输出)

配方、三个补丁和 test_package 的完整源码都在上面 AtomGit 仓库里,文中每个问题都能对到具体的 diff。适配中遇到同类报错的话,这些补丁可以直接拿来参考。

8 小结

回头看,这次适配可复用的东西主要是几条方法:平台能力缺口先做能力探测再定修复方式;浮点差异先归因到层,再决定是平台 oracle 还是代码问题;上游无修复时用独立补丁加回归用例形成可审计的闭环;所有修复保持最小改动,失败路径不吞错误。这些经验已经作为 E332–E335 条目进入社区知识库。这套把踩坑经验结构化成可复用资产的做法,和东北大学团队 CROSS2OH 构建适配知识库[1] 的思路是相通的。

图4 第 19 轮 CI 复核摘要(round-19-ci-summary.log 关键行)

图4 第 19 轮 CI 复核摘要(设备端 round-19-ci-summary.log 关键行)

编译器是语言生态的地基。MLton 在鸿蒙PC 上跑通 332 项回归,意味着 Standard ML 的工具链在 OpenHarmony arm64 上完整可用了。如果你想参与开源鸿蒙PC 的三方库适配,路径是现成的:

  1. 加入开源鸿蒙PC社区,查看进行中的适配方向和积分赛信息;
  2. 在社区平台申请新建项目,选一个还没人做的库;
  3. 拿本篇的 PR !5084 当模板,跑通你的第一个库。

参考文献

[1] Qian Zhang, Tsz On Li, Ying Wang, Li Li, Shing-Chi Cheung. CROSS2OH: Enabling Seamless Porting of C/C++ Software Libraries to OpenHarmony[C]// Proceedings of the 40th IEEE/ACM International Conference on Automated Software Engineering (ASE 2025). Seoul, Republic of Korea: IEEE, 2025: 1744-1755. DOI: 10.1109/ASE63991.2025.00146

[2] 知识驱动、自动修复:东北大学团队为开源鸿蒙 C/C++ 软件库移植打造自动化工具链——开源鸿蒙技术课题成果展播(第 5 期)[EB/OL]. 微信公众平台, 2026-08. https://mp.weixin.qq.com/s/0pddnK1bxTAUNf9PnTuebQ

欢迎加入开源鸿蒙PC社区: https://harmonypc.csdn.net/

欢迎在PC社区平台申请新建项目:https://atomgit.com/OpenHarmonyPCDeveloper/

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐