
Emscripten 设计文档体系docs/design 目录的编写规范、状态生命周期与四份真实设计案例【免费下载链接】emscriptenEmscripten: An LLVM-to-WebAssembly Compiler项目地址: https://gitcode.com/gh_mirrors/em/emscriptenEmscripten 将重要功能与大型重构的设计文档统一存放在docs/design/目录下并用版本控制管理其演进过程。该目录的 README 定义了设计文档的格式约定与Status状态生命周期Draft / Accepted / Completed而目录内现存的 4 份设计文档精确 futex 唤醒、Wasm Worker pthread 兼容、原生 Clang 前端、git subtree 管理外部库则完整示范了这套规范的落地方式。读完本文你将掌握 Emscripten 设计文档的写作与审阅规范并能通过 4 份真实案例快速定位各线程/构建机制的底层设计依据。为什么把设计文档放进仓库docs/design/README.md 开篇说明了该目录的定位它收集 Emscripten 各功能及重大变更/重构的设计文档design documents。维护团队当时是在试验一种新做法——将设计文档纳入源代码控制source control统一管理而不是全部放在 Google Docs 或 GitHub issue 里讨论。README 给出的理由有两条都是工程上可验证的收益可追踪设计的历史演进文档随代码一起提交git log能看到一份设计从草案到定稿、再到落地的完整时间线可被标准工具检索文档就是仓库里的普通文本直接用git grep就能按关键词定位例如想找所有提到emscripten_futex_wait的设计讨论一条命令即可命中 docs/design/01-precise-futex-wakeups.md。这一点对贡献者尤其重要当你在system/lib/pthread/下看到某段实现为什么这样做可以直接反查对应的设计文档而不是去翻 issue 线程里被淹没的讨论。文档格式与 Status 状态生命周期README 对每份设计文档提出了两条硬性格式要求这部分是理解整个目录的钥匙Markdown 格式该目录下的每份文档都应是一个 markdown 文件顶部标注 Status每份文档开头必须声明一个Status字段取值只有三种Draft草案方案仍在讨论Accepted已被接受准备或正在实施Completed工作已完成。此外 README 还规定了一条容易忽视但很重要的改写规则当文档被标记为Completed时正文必须随之改写为过去时视角让读者清楚这项工作已经做完。原文给出的具体做法是把 The current behavior 这类表述替换为 The previous behavior。这条规则的目的是防止读者把已经改掉的行为误读为当前行为——对正在阅读源码理解现状的开发者而言这是致命的歧义来源。当前文档清单与状态分布截至当前仓库状态docs/design/目录下共有 5 个文件1 份 README 加 4 份设计文档。从各文档头部实际标注的Status字段看可以精确验证上述规范是否被遵守文档主题Status备注01-precise-futex-wakeups.md精确的 futex 唤醒机制Completed已实现02-wasm-worker-pthread-compat.mdWasm Worker 的 pthread API 兼容Completed已实现03-native-clang-frontend.md原生 C 启动器 / Clang 前端Draft方案评估阶段04-git-subtrees.md用 git subtree 管理外部库Draft分阶段推进阶段一个清晰的规律是4 份文档头部都带有- **Status**: ...与- **Bug**: ...两行元数据Bug 行指向对应的上游 issue 编号正文随后按 Context / Goals / Non-Goals / Design 等小节展开——这正是 README 格式约定在实际文档中的具象化。案例剖析一Completed 文档的规范样板精确 futex 唤醒01-precise-futex-wakeups.md 是一份状态为Completed的文档可作为一份合格设计文档长什么样的范本。它的结构完整覆盖了规范隐含的所有要素Context背景emscripten_futex_wait实现位于 system/lib/pthread/emscripten_futex_wait.c历史上依赖周期性唤醒循环——主运行时线程 1ms、可取消 pthread 100ms 一次。目的是检查线程取消和主运行时线程的 mailbox 事件但代价是频繁的 CPU 唤醒与事件延迟。Goals / Non-Goals目标是从emscripten_futex_wait中移除周期唤醒、实现事件驱动的精确唤醒同时保持 API 签名不变非目标则明确划出边界——主浏览器线程的忙等循环、直接调用atomic.wait的线程、没有struct pthread结构的 Wasm Worker 均不在范围内。Design设计核心思想是侧信道唤醒——取消或 mailbox 事件发生时唤醒者直接对等待者当前阻塞的那个 futex 地址调用atomic.wake。文档给出了三个关键代码片段并都标注了落地的源文件struct pthread新增原子字段wait_addr位于 musl 的pthread_impl.h用最低位编码状态// 低位作状态位futex 地址必须 4 字节对齐低 1 位安全 // NULL: 未等待NOTIFY_BIT(0x1): 未等待但有通知 // addr: 正等待 addraddr | NOTIFY_BIT: 等待且已通知 _Atomic uintptr_t wait_addr; #define NOTIFY_BIT (1 0)等待方逻辑先用 CAS 把wait_addr从 NULL 换成目标地址若 CAS 失败说明别的线程刚置了NOTIFY_BIT则直接按假唤醒返回根本不进入atomic.wait等待结束后把wait_addr清零。文档特别强调即使怀疑本次唤醒来自侧信道也不在内部循环必须返回用户层以免吞掉一个并发的真实应用唤醒。唤醒方逻辑_emscripten_thread_notify用atomic_fetch_or置位NOTIFY_BIT只有抢到置位权的那一方负责循环emscripten_futex_wake((void*)addr, INT_MAX)直到等待者清零wait_addr循环中带sched_yield()防止忙等死锁。文档随后以 Benefits / Alternatives Considered / Security Safety 收尾说明为何不用基于信号的唤醒Wasm 中信号无法打断atomic.wait、为何不用每线程单一唤醒地址atomic.wait不支持同时等两个地址等取舍。这份文档同时满足 README 的Completed改写规则——它通篇用过去/现在完成时描述已实现的行为读者不会误以为周期唤醒仍然存在。案例剖析二Wasm Worker 的 pthread 兼容同样 Completed02-wasm-worker-pthread-compat.md 解决的是混合程序pthreads 与 Wasm Worker 并存中 pthread API 在 Worker 内失效的问题Wasm Worker 没有完整的struct pthreadpthread_self()等在纯 Worker 程序里无所谓但在混合模式下会以未定义方式失败。文档给出的方案要点均可在仓库中对照实现内存布局调整普通 Wasm Worker 只分配[TLS data] [Stack]混合模式改为[struct pthread] [TSD pointers] [TLS data] [Stack]struct pthread位于每个 Worker 内存块的最前端初始化分工由创建者在emscripten_create_wasm_worker/emscripten_malloc_wasm_worker中零初始化结构、设置self指针与tidWorker 侧通过 src/lib/libwasm_worker.js 中的___set_thread_state调用__set_thread_state完成线程指针设置__get_tp支持修改汇编 system/lib/pthread/emscripten_thread_state.S该文件确实存在于 pthread 线程原语目录中使 Wasm Worker 的__get_tp返回struct pthread地址从而让__pthread_self()正常工作API 支持子集pthread_self/pthread_equal/pthread_getspecific/pthread_setspecific/pthread_mutex_*/pthread_cond_*均可用而pthread_create/pthread_join/pthread_detach/pthread_cancel/pthread_kill明确不支持因为 Worker 有自己的生命周期管理。文档末尾的 Verification 一节要求验证非混合的普通 Wasm Worker 构建没有额外开销体现了设计文档中性能边界也是必须写明的内容。案例剖析三Draft 文档如何记录未定稿的设计两份Draft文档展示了草案阶段的写作方式——它们不做已实现陈述而是摆出问题、约束与候选方案的对比。原生 C 启动器Native Clang Frontend03-native-clang-frontend.md 针对的问题很具体用 CMake/Make/Ninja 构建时emcc/em会被启动成千上万次而当前的 emcc.py 是 Python 脚本每次调用都要付出解释器启动开销——文档给出的量级是 Linux 上约 50–100ms、Windows 上约 1.5–2.4s 每次对单翻译单元的增量编译实际 Clang 编译可能只要约 150msWindows 上 Python 包装层占了总耗时的 80–90%。文档同时指出这套设计将吸收并取代现有的 tools/pylauncher/pylauncher.c——Windows 上目前emcc.exe就是一个只做找到 python.exe 并拉起 emcc.py的极简 Win32 程序POSIX 上则是 shell 脚本承担同样的包装角色。新原生二进制的职责边界写得很清楚原生处理纯编译命令-c、-S、-E遇到链接、--js-library、--embed-file等需要 Python 后处理的场景立即通过execvp/CreateProcess回退到emcc.py。文档对比了两种架构指标Design 1独立 exec 启动器Design 2链接 libclang/LLVM 的原生程序启动开销极小约 2ms 启动器 原生 clang exec零进程生成开销代码复杂度低约 1500 行标准 C高需集成 LLVM driver依赖仅标准 C 库libclang / LLVM C 库EMSDK 打包影响极小独立小二进制大库体积大LLVM 版本稳定性不受 LLVM API 变化影响必须跟随 LLVM API 更新最终建议是分阶段Phase 1 先做 Design 1能拿到 90–95% 的收益且零额外依赖Phase 2 再视大规模构建或 IDE 集成的需求评估 Design 2 / 常驻编译守护进程。git subtree 管理外部库04-git-subtrees.md 处理的是system/lib/下外部库的同步问题。文档盘点了现状的三种机制外部 fork 同步脚本system/lib/libc/musl依赖外部 fork 加 push_musl_changes.py 与 update_musl.pycompiler-rt、libcxx、libcxxabi、libunwind、llvm-libc、openmp则由 push_llvm_changes.py 和各自的update_*.py脚本从 llvm-project fork 同步这些脚本在 system/lib 下均真实存在手工文件拷贝mimalloc每次上游发版靠人工把新代码拷进目录单文件 vendoringdlmalloc.c 与 stb_image.c 低变动量单文件维护即可。文档指出的痛点包括直接改 vendored 文件后忘记回传 fork 仓库、拷贝丢失 git 历史与作者归属、升级流程要在多个仓库间协调、以及跨克隆手工 diff 审计困难。方案是用git subtree --squash标准化外部库管理并把 Emscripten 仓库本身作为唯一事实来源。文档包含一张候选库分析表mimalloc 推荐作试点、musl 高优先级、LLVM 运行时中等优先级因上游是巨型 monorepo、dlmalloc/stb_image 建议跳过并以 mimalloc 为样例给出完整的迁移步骤配置--no-tags远端 → 在临时分支上叠加本地补丁 →git subtree add --prefixsystem/lib/mimalloc ... --squash→ 用./test/runner跑test_mimalloc_headers等用例验证和日常操作指南对比上游 tag 的 diff、单命令升级、cherry-pick 上游修复、用git subtree split把本地修复提取成上游 PR 分支。标签命名空间污染上游 tag 覆盖git describe --tags的结果问题也给出了--no-tags远端 命名空间 refspec 的解法。这两份 Draft 文档说明了一个约定草案可以保留大量待评估内容对比表、路线图、候选方案这正是Draft与Completed状态区分的价值所在——读者能从 Status 一行判断哪些是既定事实、哪些仍是探索。如何使用这份设计文档体系查行为依据在system/lib/、src/lib/、tools/中读到难以解释的实现时先在docs/design/里git grep相关符号。例如 futex 唤醒逻辑对应 docs/design/01-precise-futex-wakeups.md 与 system/lib/pthread/emscripten_futex_wait.c、_emscripten_thread_notify所在的线程原语文件。读状态再看结论Completed文档描述的是已落地行为注意 README 要求的过去时改写可以直接当作源码注释读Draft文档只描述计划与候选方案不能作为当前实现的依据——例如原生 emcc 前端仍是 Draft当前 emcc.py tools/pylauncher/pylauncher.c 仍是实际入口。为新设计投稿按 README 的格式约定新文档应放在docs/design/下使用 markdown、顶部声明Status: Draft并遵循现存文档的 Context / Goals / Non-Goals / Design 小节结构落地完成后将 Status 改为Completed并把正文改写为过去时视角。小结docs/design/README.md篇幅不长但定义了一套可执行的过程规范设计文档随代码入库、git grep可检索、Status 三态生命周期、Completed 时强制过去时改写。目录内的 4 份文档是对这套规范的完整实践——两份 Completed 文档精确记录了 futex 精确唤醒与 Wasm Worker pthread 兼容的落地细节两份 Draft 文档则展示了原生 Clang 前端与 git subtree 迁移在草案阶段的方案对比与路线图。对理解 Emscripten 的线程模型、Wasm Worker 架构和构建/依赖管理演进而言这个目录是最权威的一手资料。【免费下载链接】emscriptenEmscripten: An LLVM-to-WebAssembly Compiler项目地址: https://gitcode.com/gh_mirrors/em/emscripten创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考