
Emscripten 的 git subtree 外部库管理设计从 mimalloc 试点到 musl 迁移的完整方案【免费下载链接】emscriptenEmscripten: An LLVM-to-WebAssembly Compiler项目地址: https://gitcode.com/gh_mirrors/em/emscripten本文基于 Emscripten 仓库中的设计文档 04-git-subtrees.md状态为 Draft系统讲解 Emscripten 如何采用git subtree统一管理与版本化system/lib/下的外部库包括现状痛点、候选库分析、--squash与 tag 管理等关键设计决策、以mimalloc为例的四步迁移实操、日常 diff/升级/回补操作以及面向musl与 LLVM 运行时库的三阶段路线图。读完本文你可以理解 Emscripten 供应链管理的演进逻辑并能独立复现 subtree 初始化与上游版本升级的完整流程。1. 背景Emscripten 外部库管理的现状与痛点Emscripten 在 system/lib/ 下 vendor内置了多个外部库来提供核心运行时能力包括 C/C 标准库、内存分配器和编译器运行时。在设计文档提出之前这些库通过四种彼此割裂的机制维护外部 Fork 同步脚本muslsystem/lib/libc/musl通过emscripten-core/musl外部 fork 仓库维护并依赖两个同步脚本 push_musl_changes.py 与 update_musl.pyLLVM 运行时库compiler-rt、libcxx、libcxxabi、libunwind、llvm-libc、openmp从emscripten-core/llvm-project的分支同步使用 push_llvm_changes.py 以及各自的update_*.py脚本如 update_compiler_rt.py。手动文件拷贝mimallocsystem/lib/mimalloc树内跟踪了 Emscripten 专属的原语实现但升级新上游版本时只是把文件直接拷贝进目录没有任何自动化工具。静态单文件 vendordlmallocsystem/lib/dlmalloc.c与stb_imagesystem/lib/stb_image.c低变更频率的单个源文件。这一现状带来的四个具体痛点设计文档逐条列出失同步Desynchronization直接在 Emscripten PR 中修改 vendor 文件尤其是system/lib/libc/musl时作者经常忘记把改动回推到下游 fork 仓库丢失 Git 历史与署名用shutil.copytree或手动替换文件拷贝整棵目录树会丢弃提交历史、作者署名以及两个仓库之间的中间变更上下文更新流程复杂升级一个外部库需要同时摆弄多个本地 checkout、在外部仓库里做合并、跑自定义拷贝脚本、再跨仓库协调开 PRDiff 与审计困难想核对 Emscripten 的 vendor 代码与上游版本之间的精确差异必须在不同的 clone 之间做人工比对。Prior Art通过git subtree管理 vendor 系统库的做法此前已在wasi-libcWebAssembly 官方的 wasi-libc 项目见其 PR #797中被成功采用为 Emscripten 提供了直接的先例。当前仓库中的实际状态与文档描述一致上述同步脚本push_musl_changes.py、update_musl.py、push_llvm_changes.py、update_*.py系列仍然全部存在于 system/lib/ 下说明 subtree 迁移在该仓库当前快照中尚未落地。与此同时mimalloc目录里有一份版本说明文件 README.emscripten明确记录当前树内版本This contains mimalloc 34fbd7e7cd4627424490afe19b20f8066bfc537d (v3.5.1) with Emscripten-specific changes. Origin: https://github.com/microsoft/mimalloc For the Emscripten port design see src/prim/emscripten/prim.c这正是设计文档选择mimalloc作为试点的直接依据——文档中提到的“当前 Emscripten 中的版本 v3.5.1”与树内文件记录完全吻合。2. 目标与非目标目标Goals使用git subtree标准化树内外部库的管理让日常对 vendor 库的编辑完全自包含在 Emscripten 仓库内标准 commit 和 PR 直接改动 vendor 目录不再依赖树外脚本升级到上游新版本或挑选cherry-pick修复时支持单命令三方合并git subtree pull --squash简化与上游版本、tag 的 diff 与补丁审计git diff tag: HEAD:prefix建立分阶段推进策略先以低风险、自包含的试点库mimalloc起步再做musl最后评估 LLVM 运行时库弃用并删除自定义同步脚本如push_musl_changes.py和update_musl.py。非目标Non-Goals不在一次大变更中同时迁移所有外部库不使用git submodule子模块要求递归 clone、要管理 detachedHEAD会复杂化贡献者工作流;不替换单文件、高度定制或已休眠的 vendor 代码如dlmalloc.c这类库上游变更极少subtree 收益很低。3. 候选库分析哪些适合 subtree 模型设计文档用一张完整表格对各候选库逐一评估上游仓库以名称指代库树内路径上游仓库当前工作流复杂度 / 建议mimallocsystem/lib/mimallocmicrosoft/mimalloc手动拷贝粘贴推荐试点。独立仓库、干净的 release tag、体量小、自包含muslsystem/lib/libc/muslgit.musl-libc.org/muslFork update_musl.py高优先级。有wasi-libc先例可消除同步脚本和 fork 漂移compiler-rtsystem/lib/compiler-rtllvm/llvm-projectFork update_compiler_rt.py中优先级。上游是 monorepo需要 subtree split 或跟踪分支/forklibcxx/libcxxabisystem/lib/libcxx{,abi}llvm/llvm-projectFork update_libcxx*.py中优先级。同上 monorepo 考量libunwindsystem/lib/libunwindllvm/llvm-projectFork update_libunwind.py中优先级。同上 monorepo 考量llvm-libcsystem/lib/llvm-libcllvm/llvm-projectFork update_llvm_libc.py中优先级。同上 monorepo 考量openmpsystem/lib/openmpllvm/llvm-projectFork update_openmp.py中优先级。同上 monorepo 考量dlmallocsystem/lib/dlmalloc.cDoug Lea (v2.8.6)单文件 vendor低优先级 / 跳过。上游不活跃Emscripten 定制很深stb_imagesystem/lib/stb_image.cnothings/stb单文件 vendor低优先级。更新频率低单文件为什么从mimalloc起步文档给出五条理由隔离且低风险与 libc 不同mimalloc出问题不会危及基础运行时或核心工具链启动上游独立、干净上游是独立的 GitHub 仓库microsoft/mimalloc有标准的 release tagv3.5.1、v3.4.1等远程管理 straightforward没有遗留 fork 包袱mimalloc不存在emscripten-core外部 fork 仓库或既有同步脚本需要拆除一直靠手动拷贝更新定制补丁集小且集中Emscripten 专属支持集中在system/lib/mimalloc/src/prim/emscripten/prim.c其中相当部分已回馈上游以及tools/system_libs.py中的编译参数调整上游变更活跃mimalloc发版频繁近期数月内 v3.3.1 - v3.4.1 - v3.5.1subtree 对后续版本升级能立即产生价值。当前仓库的源码可以从三个方向佐证这一判断移植设计集中在单一文件system/lib/mimalloc/src/prim/emscripten/prim.c 开头有一段完整的设计注释说明 mimalloc 构建在 emmalloc 之上、emmalloc 构建在 sbrk 之上的三层结构因为 sbrk 只能单向伸缩、不能“跳过”区域无法像 POSIXmmap那样正确归还系统内存。该文件版权头为 “Copyright (c) 2018-2026, Microsoft Research, Daan Leijen, Alon Zakai”印证了文档所说“Emscripten 的移植工作相当部分已上游化”构建侧的 Emscripten 定制就是文档所说的“编译参数调整”tools/system_libs.py 中的libmimalloc类集中定义了一组-D编译开关例如-DMI_ENABLE_LARGE_PAGES0默认禁用大页、-DMI_ARENA_SLICE_SHIFT(12MI_SIZE_SHIFT)wasm64 下把页大小减半为 32KiB、wasm32 下为 16KiB、-DMI_MAX_ALIGN_SIZE8、-DMI_DEFAULT_ARENA_RESERVE65536以 64 MiB 为单位预留内存并注明需与-sINITIAL_HEAP默认值保持同步、-DMI_LIBC_MUSL、-DMI_USE_BUILTIN_THREAD_POINTER等该类还设置了force_object_files Truemalloc/free/calloc 是运行时函数可在 LTO 阶段生成因此自身不能参与 LTO测试侧已有守护用例test/test_other.py 的test_mimalloc_no_asan断言mimalloc与-fsanitizeaddress组合会报错与 tools/system_libs.py 中 “mimalloc is not compatible with -fsanitizeaddress” 的守卫逻辑对应test/test_other.py 的test_mimalloc_headers则以-sMALLOCmimalloc编译并运行一段#include mimalloc.h的程序验证头文件可用。MALLOCmimalloc选项本身也在 src/settings.js 中被文档化为 “a powerful multithreaded allocator”。这些证据共同说明mimalloc在树内已经是“上游目录结构 集中式构建定制 独立测试”的形态迁移到 subtree 的改动面确实很小。4. Subtree 架构总览以mimalloc为例方案使用git subtree并固定配合--squash标志把外部库 vendor 到指定目录前缀之下。对mimalloc而言上游仓库microsoft/mimalloc树内前缀路径system/lib/mimalloc文档给出的数据流示意Upstream (microsoft/mimalloc) │ git fetch / git subtree pull ▼ ┌─────────────────────────────────────────────────────────────┐ │ emscripten-core/emscripten │ │ │ │ ├── system/lib/mimalloc/ ◄── Managed via subtree │ │ │ ├── include/ ◄── Tracked in-tree │ │ │ └── src/ │ │ │ └── prim/emscripten/ ◄── Emscripten primitives │ │ └── tools/system_libs.py ◄── Compiles libmimalloc │ └─────────────────────────────────────────────────────────────┘ │ git subtree split / push (Optional) ▼ Upstream PR / Fork (e.g. microsoft/mimalloc)即上游通过git fetch/git subtree pull流入 Emscripten 的system/lib/mimalloc前缀本地改动日常直接提交在 Emscripten 仓库中只有当补丁成熟准备回馈时才用git subtree split抽取成分支推给上游。5. 关键设计决策5.1 始终使用--squash上游仓库动辄数万提交。不加--squash时完整上游历史会直接灌进 Emscripten 的 git log加--squash后git 会合成一个代表合并点上游快照的单提交并在提交信息中嵌入 subtree 元数据git-subtree-dir与git-subtree-split从而保留将来三方合并所需的正确祖先关系。5.2 跟踪完整的上游目录结构通过 subtree 导入上游仓库时保留其上游目录结构不做裁剪mimalloc、musl这类库本身紧凑几 MB 量级裁剪或排除未使用的目录会在未来git subtree pull时制造人为的合并冲突tools/system_libs.py 中的构建逻辑显式挑选为 WebAssembly 编译哪些源文件——以libmimalloc为例它对system/lib/mimalloc/src下的*.c做 glob 并排除alloc-override.c、free.c、page-queue.c、static.c这些文件在源码层面被其他文件#include再显式追加prim/prim.c、prim/prim-tls.c、树外的emmalloc.c和 libc 的sbrk.c。未被编译的文件既不影响二进制产物也不影响缓存体积。从源码结构看这正好验证了设计文档“多导入文件无害、构建侧负责筛选”的判断——目录完整性服务于 merge 正确性编译范围由MTLibrary类单独控制。5.3 Emscripten 仓库作为唯一事实来源Single Source of Truth本地改动、bug 修复、移植适配直接提交进 Emscripten 仓库。独立 fork 仓库如emscripten-core/musl不再充当中间同步枢纽。当本地补丁准备回馈上游时用git subtree split把前缀内的历史干净地抽取成一个适合开上游 PR 的分支。5.4 远程与 tag 管理防止 tag 冲突默认情况下git fetch加--tags会把上游所有 tag如v1.2.6、v3.5.1导入本地refs/tags/*命名空间这带来两个问题Tag 冲突与噪音不同上游的通用版本号 tag如v1.0、v2.0可能互相冲突或 clutter 掉 Emscripten 的 tag 列表和 shell 自动补全污染git describe自动化工具和构建脚本用git describe --tags判定编译器版本时可能命中可达的上游 tag 而非 Emscripten 的 release tag。为此文档约定三条措施1) 配置 remote 时使用--no-tags或设置remote.name.tagOpt --no-tagsgit remote add --no-tags mimalloc-upstream \ https://github.com/microsoft/mimallocgit subtree pull并不依赖本地已有 tagGit 可以把指定 tag 或 commit 按需取到FETCH_HEAD而不必落进refs/tags/*。2) 命名空间 refspec可选供本地访问上游 tag 的维护者使用# 隔离到 refs/remotes/ 下对 git tag 和 git describe 不可见 git config --add remote.mimalloc-upstream.fetch \ refs/tags/*:refs/remotes/mimalloc-upstream/tags/*或者用子目录前缀放进refs/tags/git config --add remote.mimalloc-upstream.fetch \ refs/tags/*:refs/tags/mimalloc/*3) 基于 URL 的临时拉取wasi-libc 模式普通贡献者的日常工作区完全不需要配置上游 remote执行版本升级的维护者可以直接从仓库 URL 拉取git subtree pull --prefixsystem/lib/mimalloc \ https://github.com/microsoft/mimalloc v3.5.2 --squash \ -m Update mimalloc to v3.5.2这样导入压缩后的提交而不在仓库中留下任何外部 remote 或 tag。6. 迁移实操把mimalloc初始化为 subtree不丢失现有改动以下是文档给出的完整四步流程核心思路是先在上游 v3.5.1 之上叠一份当前树内文件作为“初始化分支”再删除树内散落的文件、用git subtree add重新挂上该分支从而把既有补丁无损地纳入 subtree 管理。Step 1配置上游 remotegit remote add --no-tags mimalloc-upstream https://github.com/microsoft/mimalloc git fetch mimalloc-upstream tag v3.5.1Step 2准备带本地补丁的初始分支基于上游v3.5.1当前 Emscripten 中的版本创建临时分支并覆盖当前树内文件以保留本地补丁# Check out base upstream release tag git checkout -b temp-mimalloc-init FETCH_HEAD # Overlay current Emscripten mimalloc directory to preserve local patches cp -r /path/to/emscripten/system/lib/mimalloc/* . git add -A git commit -m Apply Emscripten modifications on top of mimalloc v3.5.1Step 3在 Emscripten 中初始化 subtree在 Emscripten 工作分支上git checkout -b init-mimalloc-subtree # 1. Remove previously un-tracked mimalloc directory git rm -rf system/lib/mimalloc git commit -m [lib] Remove loose mimalloc files for subtree initialization # 2. Add the subtree using the prepared branch git subtree add --prefixsystem/lib/mimalloc temp-mimalloc-init --squash \ -m Initialize system/lib/mimalloc as git subtree tracking mimalloc v3.5.1 # 3. Clean up the temporary branch git branch -D temp-mimalloc-initStep 4用测试验证./test/runner test_other.test_mimalloc_headers ./test/runner test_other.test_mimalloc_no_asan ./test/runner test_hello_world -sMALLOCmimalloc这三条验证命令在当前仓库中都有对应实现前两个用例位于 test/test_other.py第三个则用-sMALLOCmimalloc走 src/settings.js 中声明的 mimalloc 分配器路径完成 hello world 级冒烟验证。7. 日常操作手册以mimalloc为例7.1 查看与上游的 diff核对 Emscripten 树与某个上游 tag 之间的精确差异git fetch --no-tags mimalloc-upstream tag v3.5.1 # Diff entire subtree against upstream v3.5.1 git diff FETCH_HEAD: HEAD:system/lib/mimalloc # Diff a specific file git diff v3.5.1:src/prim/emscripten/prim.c \ HEAD:system/lib/mimalloc/src/prim/emscripten/prim.c若要以独立提交日志的形式查看 Emscripten 的本地改动git subtree split --prefixsystem/lib/mimalloc -b mimalloc-local-changes git log v3.5.1..mimalloc-local-changes这直接解决了痛点 #4diff 与审计困难原来要在多个 clone 之间人工比对现在一条git diff或git log即可完成。7.2 升级到新的上游版本如v3.5.2单条命令完成# Option A: Pull by remote name (with remote configured with --no-tags) git subtree pull --prefixsystem/lib/mimalloc \ mimalloc-upstream v3.5.2 --squash \ -m Update mimalloc to v3.5.2 # Option B: Pull directly by repository URL (no local remote or tags stored) git subtree pull --prefixsystem/lib/mimalloc \ https://github.com/microsoft/mimalloc v3.5.2 --squash \ -m Update mimalloc to v3.5.2 # Resolve any merge conflicts in-place if necessary # Run tests ./test/runner test_other.test_mimalloc_headers7.3 Cherry-pick 上游修复在新版本发布之前先回补某个上游 commitgit fetch mimalloc-upstream git subtree pull --prefixsystem/lib/mimalloc \ mimalloc-upstream commit-sha --squash \ -m Backport upstream mimalloc commit commit-sha7.4 把本地修复回馈上游把 Emscripten 前缀内的本地提交抽取成独立分支向microsoft/mimalloc开 PRgit subtree split --prefixsystem/lib/mimalloc -b mimalloc-upstream-pr # Push to personal GitHub fork and open upstream PR git push gitgithub.com:user/mimalloc.git mimalloc-upstream-pr:my-fix8. 第二阶段应用到musl并退役同步脚本mimalloc验证成功后同一套流程适用于system/lib/libc/musl配置 remotegit remote add --no-tags musl-upstream git://git.musl-libc.org/musl git fetch musl-upstream tag v1.2.6基于v1.2.6准备初始分支覆盖system/lib/libc/musl的当前内容用下面的命令替换system/lib/libc/muslgit subtree add --prefixsystem/lib/libc/musl temp-musl-init --squash \ -m Initialize system/lib/libc/musl as git subtree tracking musl v1.2.6跑 libc 测试套件./test/runner test_libc*删除过时的同步脚本git rm system/lib/push_musl_changes.py system/lib/update_musl.py这一步完成后痛点 #1失同步与 #3跨仓库协调在 libc 上即被消除——改动直接落在 Emscripten 仓库里fork 漂移不复存在。9. LLVM 运行时库评估monorepo 带来的特殊挑战迁移 LLVM 运行时compiler-rt、libcxx、libcxxabi、libunwind、llvm-libc、openmp面临上游是多 GB 级 monorepollvm/llvm-project的独特问题对着 monorepo 子目录做 subtree 的可行性直接对 monorepo 根跑git subtree pull不现实——每个库都嵌套在子目录runtimes/、libcxx/等中拉根仓库会把整个 LLVM 历史拖进来中间 split 分支较干净的思路是在emscripten-core/llvm-project维护轻量级 split 分支或用 GitHub Actions 按上游 release tag 自动生成“每运行时一个 subtree 分支”例如llvmorg-22.1.8-libcxx这类命名分阶段推进鉴于上述考量LLVM 运行时应放到 Phase 3待mimalloc与musl积累经验后再评估。在那之前现有的update_*.py与push_llvm_changes.py脚本可继续运行——当前仓库中这些脚本也确实在位。10. 新旧工作流对比设计文档用下表总结了迁移前后的差异操作旧工作流新的git subtree工作流日常补丁在 Emscripten 里改跑 push 脚本推到 fork 仓库直接在 Emscripten 里改文件并提交上游升级在 fork 仓库合并、解冲突再跑拷贝脚本在 Emscripten 里直接跑git subtree pull --squash冲突解决在外部 clone 中解决再用脚本盲目拷贝在 Emscripten 中就地解决使用标准 git 工具审计 diff人工 diff 两个独立 checkoutgit diff tag: HEAD:prefix唯一事实来源分裂在 fork 仓库与 Emscripten 仓库之间Emscripten 仓库即唯一事实来源11. 推进路线图Rollout RoadmapPhase 1试点把mimalloc初始化为 git subtree验证日常开发、本地补丁与一次上游 release 升级的完整闭环Phase 2musl按已验证的模式把system/lib/libc/musl转为 git subtree并退役push_musl_changes.py与update_musl.pyPhase 3LLVM 运行时评估调研迁移compiler-rt、libcxx等其他 LLVM 运行时重点是解决 monorepo 的 subtree 抽取策略。12. 小结这份 Draft 状态的设计文档目录约定见 docs/design/README.md各文档需标注 Draft/Accepted/Completed 状态给出了一个完整的供应链治理方案以git subtree --squash为统一机制以“Emscripten 仓库为唯一事实来源”为核心原则以mimalloc低风险、独立上游、补丁集小为先导、musl高价值、有 wasi-libc 先例为主力、LLVM 运行时monorepo 难题为后续评估对象。配合树内已有的构建定制tools/system_libs.py 的libmimalloc类与守护测试test/test_other.py 的test_mimalloc_headers/test_mimalloc_no_asan该方案在工程上是可复现、可验证的从git subtree add初始化到git subtree pull升级每一步都有对应的仓库内代码与测试作为落点。【免费下载链接】emscriptenEmscripten: An LLVM-to-WebAssembly Compiler项目地址: https://gitcode.com/gh_mirrors/em/emscripten创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考