资讯详情

ReScript reanalyze 死代码分析“纯管道“重构指南:从全局可变状态到不可变数据流

📅 2026/9/28 12:33:53 | 华诺云谱 👁 阅读
ReScript reanalyze 死代码分析“纯管道“重构指南:从全局可变状态到不可变数据流
编译器编程语言开发工具【免费下载链接】rescript-compilerReScript is a robustly typed language that compiles to efficient and human-readable JavaScript.项目地址https://gitcode.com/gh_mirrors/re/rescript-compiler点击查看免费下载本篇指南以 ReScript 编译器仓库中 reanalyze 死代码分析Dead Code Elimination, DCE的纯管道重构计划为核心系统讲解如何把一项全局可变状态驱动的分析器改造成输入→输出透明、副作用只在边缘、处理顺序无关、可增量更新的纯函数式管道。读者将掌握其三大设计原则局部可变→不可变、清晰阶段边界、增量更新、map → list → merge可复用模式以及 Task 1–11 的完整重构拆解、执行顺序与验证方法可直接用于理解和改进 reanalyze 乃至其他同类静态分析器的架构。为什么需要这次重构全局可变状态的四宗罪在重构之前reanalyze 的死代码分析依赖大量全局可变状态全局 ref、全局 Hashtbl、延迟队列这带来了四个难以逾越的架构缺陷增量/响应式分析不可能——无法只重处理一个文件因为每个文件的分析结果都被写入共享全局状态无法单独替换测试困难——全局状态在多个测试用例之间残留导致用例相互污染必须靠模块重载来清理并行化不可能——所有分析函数共享可变状态无法并发执行推理困难——存在大量依赖处理顺序的隐藏副作用同一批文件换一种顺序处理结果可能不同。重构计划的目标非常明确让分析成为输入 → 结果的纯函数消除全局可变状态把日志、文件 I/O 等副作用限制在管道边缘从而同时解锁增量分析、并行化和可测试性。三大核心设计原则原则一AST 处理期使用局部可变状态处理完立即冻结为不可变重构计划并不排斥可变状态本身而是划定明确的边界AST 处理阶段逐文件允许使用局部可变状态Hashtbl 等换取性能但函数返回时冻结为不可变file_data该阶段天然是逐文件顺序执行的分析阶段项目级只操作不可变数据结构必须是可并行、可重排的从这一节点开始获得静态保证。计划的参考代码把这一边界表达得非常清晰(* AST processing: local mutable state OK, returns immutable *) let process_file config cmt_infos : file_data let local_state Hashtbl.create 256 in (* local mutable *) ... traverse AST, mutate local_state ... freeze_to_file_data local_state (* return immutable *) (* Analysis: immutable in, immutable out - parallelizable *) let solve_deadness config (files : file_data list) : analysis_result ... pure computation on immutable data ...原则二清晰的阶段边界整个分析被拆成四个阶段每个阶段对输入、可变性、输出、可并行性都有明确约束阶段输入可变性输出可并行?AST 处理cmt 文件局部可变 OK不可变file_data逐文件可并行合并 Mergefile_data list无不可变合并视图可并行分析 Solve合并视图无不可变result可并行报告 ReportresultI/O 副作用无N/A原则三不可变数据结构让增量更新成为可能当文件 F 发生变化时只对 F 重新执行 AST 处理得到新的file_data在以文件名为 key 的file_data映射中替换 F 的条目重新执行合并与分析都基于不可变数据。关键在于——不可变数据结构支持安全的增量更新你可以替换一个文件的数据而不影响其他文件这正是 reanalyze 后续响应式管道analysis/reactive/得以建立的基础。被修复的六大问题P1–P6重构计划把要解决的问题编号为 P1–P6全部来自真实代码中的全局状态每一项都有具体的消除路径与完成状态。P1全局当前文件上下文Common.currentSrc、currentModule、currentModuleName是全局 ref在处理每个文件之前被设置。每个函数都隐式依赖当前正在处理哪个文件这使并发或增量处理多个文件成为不可能。使用方DeadCommon.addDeclaration_、DeadType.addTypeDependenciesAcrossFiles、DeadValue的路径构造。状态✅ 已在 Task 1 修复——显式file_context被贯穿所有分析函数。在现行源码中DeadCommon.File_context.t以记录形式携带source_path、module_name、is_interface三个字段见 dead_common.ml而Dce_file_processing也维护了同构的file_context类型见 dce_file_processing.mli。P2全局分析表所有分析结果累积在全局 Hashtbl 中DeadCommon.decls—— 全部声明ValueReferences.table—— 全部值引用TypeReferences.table—— 全部类型引用FileReferences.table—— 跨文件依赖影响无法只分析文件子集而不重分析全部无法在测试用例之间清理状态除非重载模块。这些全局表现在均已被删除——源码中的注释明确写道 Global decls removed - now using Declarations.builder/t pattern、Global ValueReferences removed - now using References.builder/t pattern见 dead_common.ml。P3跨文件处理队列多种分析依赖全局队列、稍后统一 flushDeadOptionalArgs.delayedItems—— 跨文件可选参数分析 → 已删除改为CrossFileItemsDeadException.delayedItems—— 跨文件异常检查 → 已删除改为CrossFileItemsDeadType.TypeDependencies.delayedItems—— 逐文件类型依赖原本就是逐文件处理的ProcessDeadAnnotations.positionsAnnotated—— 注解跟踪其中positionsAnnotated还有一个额外问题它把输入来自 AST 的源码注解和输出求解器判定为 dead 的位置混在同一个表中求解器在分析过程中直接改写它破坏了纯度。影响结果依赖顺序——队列在任意时刻被处理换一种文件处理顺序可能得到不同结果输入输出混用则使增量分析无法进行。P4全局配置读取分析代码散落着!Common.Cli.debug、RunConfig.runConfig.transitive等直接读取无法在不改写全局的前提下用不同配置运行分析。状态✅ 已在 Task 2 修复——显式config贯穿所有分析函数。现行实现中 dce_config.ml 的DceConfig.current ()一次性从全局Clirefs 与RunConfig捕获出一个不可变的Dce_config.t值之后分析代码只接受显式参数type cli_config { debug: bool; ci: bool; json: bool; live_names: string list; live_paths: string list; exclude_paths: string list; } type t {run: Run_config.t; cli: cli_config} let current () ... (* 从全局捕获生成单个不可变配置值 *)P5副作用与分析混杂分析函数直接调用Log_.warning日志、EmitJsonJSON 输出、WriteDeadAnnotations文件 I/O后已删除并直接改写结果数据结构。影响无法把分析结果当数据取回测试必须捕获 I/O分析逻辑无法复用到不同输出格式。这正是 Task 8 系列要解决的核心问题。P6绑定/报告状态DeadCommon.Current.bindings、lastBinding、maxValuePosEnd是存储在全局的逐文件状态。状态✅ 在更早的工作中已修复——现在通过显式状态贯穿遍历。目标终态四层不可变数据类型与编排重构计划的终态定义了一整套不可变数据类型以及一个把四个阶段串起来的编排函数(* IMMUTABLE DATA TYPES *) (* Configuration: immutable *) type config { ... } (* Per-file data - IMMUTABLE, returned by AST processing *) type file_data { source_path : string; module_name : Name.t; is_interface : bool; source_annotations : AnnotationMap.t; (* immutable map *) decls : DeclMap.t; (* immutable map *) value_refs : RefMap.t; (* immutable map *) type_refs : RefMap.t; file_deps : StringSet.t; (* files this depends on *) } (* Project-wide merged view - IMMUTABLE *) type merged_view { all_annotations : AnnotationMap.t; all_decls : DeclMap.t; all_value_refs : RefMap.t; all_type_refs : RefMap.t; file_graph : FileGraph.t; } (* Analysis results - IMMUTABLE *) type analysis_result { dead_decls : decl list; issues : issue list; annotations_to_write : (string * line_annotation list) list; }对应的编排参考实现含增量更新版本let run_analysis ~config ~cmt_files (* Phase 1: Process files (can parallelize per-file) *) let files cmt_files | List.map (fun path - (path, process_file config (load_cmt path))) | StringMap.of_list in (* Phase 2: Merge *) let merged merge_files files in (* Phase 3: Analyze *) let result solve_deadness config merged in (* Phase 4: Report (side effects) *) report result (* Incremental: only re-process changed file *) let update_file ~config ~files ~changed_file let new_data process_file config (load_cmt changed_file) in let files StringMap.add changed_file new_data files in let merged merge_files files in solve_deadness config merged这一终态在现行源码中已经基本落地。以 dce_file_processing.mli 为例真实的file_data记录包含五个可合并的 buildertype file_data { annotations: File_annotations.builder; decls: Declarations.builder; refs: References.builder; cross_file: Cross_file_items.builder; file_deps: File_deps.builder; }而 analysis_result.mli 提供了不可变结果类型及其构造器empty、add_issue、add_issues、get_issues、make_dead_issue、make_dead_module_issue。贯穿始终的map → list → merge可复用模式Task 3–7 反复使用同一个模式计划将其抽象为 reanalyze 的核心范式map逐文件→ list有序无关→ merge → 不可变结果。builder 与 t 的双类型设计每个数据域都提供两个类型(* Two types: mutable builder, immutable result *) type builder (* mutable - for AST processing *) type t (* immutable - for solver *) (* Builder API *) val create_builder : unit - builder val annotate_* : builder - ... - unit (* Merge: list of builders → immutable result *) val merge_all : builder list - t (* Read-only API for t *) val is_annotated_* : t - ... - bool源码中的实际 API 印证源注解file_annotations.mli 定义annotated_as GenType | Dead | Livebuilder 提供annotate_gentype/annotate_dead/annotate_livemerge_all : builder list - t之后求解器只能通过is_annotated_dead、is_annotated_gentype_or_live等只读查询接口访问声明declarations.mli 的 builder 提供add/find_opt_builder/replace_builder不可变t只暴露find_opt/fold/iter/length引用references.mli 以refs_from方向存储posFrom - 目标集合恰好适配前向活性liveness算法并提供freeze_builder把 builder 冻结为t文件依赖file_deps.mli 提供add_file/add_dep与iter_files_from_roots_to_leaves按拓扑序从根到叶遍历跨文件项cross_file_items.mli 把跨文件的exception_ref、optional_arg_call、function_ref、optional_arg_value_escape统一收进 builder合并后由纯函数process_exception_refs处理。关键性质顺序无关builder 按任意顺序收集结果一致、可并行map 阶段可并发、可增量替换列表中的单个 builder 后重新 merge、类型安全t的 API 中根本没有可变函数。该模式在Reanalyze.runAnalysis的合并段中得到完整落地——所有file_data的 builder 分别经过FileAnnotations.merge_all、Declarations.merge_all、CrossFileItems.merge_all、References.freeze_builder后交给求解器见 reanalyze.ml。重构任务拆解Task 1–11每个任务都遵循三条验收标准✅ 修复上面列出的一个真实问题✅ 让代码进入可度量的更好状态✅ 行为保持可测试行为不变架构改进❌ 不添加立即可用的脚手架。Task 1移除全局当前文件上下文P1— ✅ 完成创建DeadCommon.FileContext.t类型含source_path、module_name、is_interface字段贯穿DeadCode.processCmt、DeadValue、DeadType、DeadCommon.addDeclaration_贯穿Exception.processCmt、Arnold.processCmt从 DCE 代码中移除所有Common.currentSrc、currentModule、currentModuleName读取从Common.ml删除这三个全局量。价值使文件可以并发或乱序处理。测试对同一批文件改变处理顺序结果应完全一致。工作量中等约触及 10 个函数多为机械性改动。Task 2把配置提取为显式值P4— ✅ 完成使用已创建的DceConfig.t贯穿 DCE 分析函数把所有!Common.Cli.debug、runConfig.transitive等读取替换为config.debug、config.run.transitive所有配置参数必填任何地方都不再有config option配置贯穿 Exception 与 Arnold 分析分析代码中不再出现DceConfig.current()单一入口只有 CLI/入口包装层runAnalysisAndReport、DceCommand调用一次DceConfig.current()然后把显式 config 传遍各处。测试构造两个不同配置分别运行分析应各自遵循自己的配置而不是读取全局。工作量中等已完成。Task 3源注解采用 map → list → merge 模式P3— ✅ 完成创建FileAnnotations模块含builder可变供 AST 处理与t不可变供求解器只读DceFileProcessing.process_cmt_file返回builderprocessCmtFiles把 builders 收集成列表顺序无关FileAnnotations.merge_all : builder list - t合并为不可变结果求解器只拿到tAPI 中没有任何可变函数移除求解器改写resolveRecursiveRefs不再调用annotate_dead直接使用decl.resolvedDead已解析的声明直接使用其存储结果。价值为一种数据类型示范局部可变 → 不可变架构确立 Task 4–7 可复用的模式。Task 4声明采用 map → list → merge 模式P2— ✅ 完成创建Declarations模块builder/tprocess_cmt_file返回同时含 annotations 与 decls builder 的file_data删除全局DeadCommon.declsDeadOptionalArgs.forceDelayedItems改为接收~decls:Declarations.t。价值声明在 AST 处理后不可变分析可并行。工作量中等核心数据结构调用点多。Task 5引用采用 map → list → merge 模式P2— ✅ 完成创建References模块builder/t通过~refs:References.builder贯穿addValueReference、addTypeReference合并 refs 进 builder、处理延迟项、再冻结求解器通过find_value_refs、find_type_refs访问References.t删除全局ValueReferences.table与TypeReferences.table。Task 6跨文件项采用 map → list → merge 模式P3— ✅ 完成创建CrossFileItems模块builder/tprocess_exception_refs与process_optional_args变成作用于合并后t的纯函数删除DeadException与DeadOptionalArgs中的全局delayedItemsrefs。关键洞察跨文件项本质上是跨越文件边界的引用应当与其他数据遵循完全相同的模式。DeadType.TypeDependencies原本就是逐文件处理无需纳入。Task 7文件依赖采用 map → list → merge 模式P2P3— ✅ 完成创建FileDeps模块builder/titer_files_from_roots_to_leaves : t - (string - unit) - unit纯函数删除Common.ml中的全局FileReferences。Task 8分析阶段纯化P5— ✅ 完成目标架构merged_view不可变→ solve_deadness纯函数→ analysis_result不可变→ report副作用仅在此处。方法拆成一系列小而行为保持的步骤每步验证后再推进关键手法是先改返回类型然后在调用点立刻打日志保证行为逐字节不变。Task 8.1创建AnalysisResult模块type t { issues: Common.issue list }构造器empty/add_issue/get_issues及 issue 构造器make_dead_issue/make_dead_module_issue。✅Task 8.2emitWarning改为内部使用makeDeadIssue纯函数。验证make test-analysis输出一致。✅Task 8.3Decl.report签名改为返回issue option在reportDead调用点立即记录返回的 issue。✅Task 8.4DeadOptionalArgs.check改为返回issue list新增foldUnused、foldAlwaysUsed调用点立即记录。✅Task 8.5不正确的dead注解 issue 改用makeDeadIssue调用点立即记录删除已无用的emitWarning。✅Task 8.6DeadModules.checkModuleDead改为返回issue option调用点立即记录。✅Task 8.7Decl.report改为返回issue list含 dead module issues用List.concat_map汇总在reportDead末尾统一记录。✅Task 8.8reportDead改为返回AnalysisResult.t把日志从reportDead移到调用方Reanalyze.ml。✅关键保证分析阶段reportDead现在返回包含全部死代码 issue 的不可变AnalysisResult.t副作用日志只发生在调用方Reanalyze.runAnalysis。Task 8b把所有 issue 收进AnalysisResult.t— ✅ 完成解决 Task 8 遗留的内联日志问题resolveRecursiveRefs曾直接通过Log_.warning记录两类 issue可选参数 issue 与不正确的dead注解 issue绕过了AnalysisResult.t。修复方式通过~issues:(Common.issue list ref)贯穿resolveRecursiveRefs收集这两类 issue加入reportDead的AnalysisResult.t并移除其中所有Log_.warning调用。最终保证resolveRecursiveRefs中不存在任何Log_.warning。Task 9把注解计算与文件写入分离P5— 已移除WriteDeadAnnotations功能被整体删除-write标志自动向源文件插入dead注解因引入显著复杂度全局状态、分析期文件 I/O、额外类型而移除而该功能使用率很低。需要抑制死代码警告的用户可以手动添加dead注解。Task 10验证分析代码中零DceConfig.current()调用 — ✅ 完成验证DceConfig.current()只在入口包装层CLI /runAnalysisAndReport被调用验证Dead*.ml、Exception.ml、Arnold.ml中无DceConfig.current()所有分析函数都接受显式~config参数。验证命令文档给出的 grep 检查grep -r DceConfig.current analysis/reanalyze/src/{Dead,Exception,Arnold}.ml返回零结果。✅ 工作量琐碎已完成。Task 11集成与顺序无关性验证 — ✅ 完成编写属性测试随机打乱文件处理顺序验证结果一致新增-test-shuffleCLI 标志随机化文件处理顺序新增test-order-independence.sh脚本运行 3 轮打乱迭代并比较输出通过make test-reanalyze-order-independence运行不属于默认测试集求解器接受显式输入无全局状态——由架构验证在架构文档中新增架构图章节。测试即任务本身。工作量小主要是写测试。执行策略先隐性依赖、后数据平面、再输出平面计划给出的完成状态与剩余顺序为已完成Task 1 ✅、Task 2 ✅、Task 3 ✅、Task 10 ✅、Task 11 ✅剩余顺序4 → 5 → 6 → 7 → 8 → 9 → 11测试为什么是这个顺序Task 1–2 先移除隐性依赖文件上下文、配置——✅ DONETask 3 让源注解只读求解器不再改写——✅ DONETask 4–7 让状态逐文件化支撑增量更新Task 8 让报告纯化返回不可变结果Task 9 分离注解计算与文件写入Task 10 验证不再有全局配置读取——✅ DONETask 11 全面验证包括增量更新。关键架构里程碑Task 7 之后所有状态逐文件化以文件名为 keyTask 8 之后求解器纯化返回不可变结果Task 11 之后增量更新验证通过。时间估算文档原估顺利 2–3 天现实含 bug/复杂情况1 周最坏重大架构问题2 周。可选后续任务OptionalArgs 跟踪不可变化 — ✅ 完成OptionalArgs.t完全不可变无可变字段新增纯函数apply_call、combine_pair在Common.ml创建OptionalArgsState模块承载状态映射compute_optional_args_state返回不可变状态映射DeadOptionalArgs.check从映射中查状态。架构声明的optionalArgs字段 初始状态有哪些参数OptionalArgsState.t 计算后状态所有调用/组合之后求解器用OptionalArgsState.find_opt取得最终状态。验证手段顺序无关性测试与架构文档顺序无关性是整个重构的核心验收指标仓库为其提供了双保险测试专用 CLI 标志-test-shuffle在 cli.ml 声明let test_shuffle ref false在 reanalyze.ml 注册为-test-shuffle标志Test flag: shuffle file processing order to verify order-independence。启用后run_analysis会在合并前用 Fisher–Yates 洗牌算法打乱dce_data_list见 reanalyze.mlmake test-reanalyze-order-independence驱动test-order-independence.sh脚本运行 3 轮打乱迭代比较输出是否一致。各阶段还可独立做单元测试Phase 1 处理单个.cmt验证file_dataPhase 2 合并已知 builders 验证合并结果Phase 3 用已知输入调用求解器验证 issues——由于数据不可变测试无需任何 mock只需传入不可变数据。这些内容在 ARCHITECTURE.md 的 Testing 章节有完整描述。下图展示了重构后管道的整体架构来源batch-pipeline.svg源文件 batch-pipeline.mmd重构后的能力兑现增量更新与响应式管道纯管道架构的直接成果是增量更新的可行性与落地标准路径Reanalyze.runAnalysis依次执行收集 cmt 路径 → 逐文件处理 → 合并 → 求解 → 报告见 reanalyze.mlTiming.time_phase分别统计FileLoading/Merging/Solving/Reporting各阶段耗时响应式路径-reactiveanalysis/reactive/目录基于同一份不可变架构实现了增量管道。当文件变化时只有受影响条目被重算未触及的条目保持稳定没有任何文件变化时所有响应式集合稳定唯一的工作是遍历预计算好的 issues 集合。据 ARCHITECTURE.md 记录的基准数据冷启动约 4900 个文件时 Solving 约 2ms、Reporting 约 3ms缓存命中0 文件变化时 Solving 约 1–5ms、Reporting 约 3–8ms单个文件变化时求解复杂度为 O(受影响声明数)、报告为 O(issues)。README 中给出的反应式模式实测示例为标准模式 CMT 处理 0.78s / 总计 1.01s而反应式暖运行 CMT 处理 0.01s / 总计 0.20s见 README.md。响应式管道还通过-mermaid标志输出完整的 Mermaid 管线图-timing标志输出每个响应式节点的增量统计d_recv、e_recv、d_emit、runs、time_ms等。成功标准清单重构完成后计划定义了六项可验证的成功标准✅局部可变 → 不可变边界——AST 处理用局部可变状态性能考量返回不可变file_data分析阶段只操作不可变数据。✅纯分析阶段——solve_deadness : merged_view - analysis_result是纯函数分析中无副作用日志、I/O可并行、可记忆化、可重排。✅增量更新——替换一个文件的file_data不影响其他文件重新合并与重新分析都是不可变数据上的纯函数。✅顺序无关——任意顺序处理文件 → 相同的file_data任意顺序合并 → 相同的merged_view由属性测试验证。✅静态保证——类型系统在 AST 处理之后强制执行不可变性分析阶段 API 中看不到ref或可变Hashtbl编译器捕获违规。✅可测试——AST 处理、合并、分析均可隔离测试无需 mock直接传入不可变数据即可。这六项标准不仅是这次重构的验收指标也为 reanalyze 后续的响应式服务reanalyze-server 的透明代理、-churn增量正确性测试、-runs缓存有效性基准等奠定了架构基础。读者若要深入源码可从 reanalyze.ml编排入口、dce_file_processing.mliPhase 1、analysis_result.mliPhase 3 输出与 ARCHITECTURE.md完整架构文档入手。赞分享编译器编程语言开发工具【免费下载链接】rescript-compilerReScript is a robustly typed language that compiles to efficient and human-readable JavaScript.项目地址https://gitcode.com/gh_mirrors/re/rescript-compiler点击查看免费下载相关推荐ReScript reanalyze 死代码分析架构深度解析从四阶段纯管道到响应式增量流水线ReScript reanalyze 死代码分析架构深度解析从四阶段纯管道到响应式增量流水线 本文以 analysis/reanalyze/ARCHITECT编译器编程语言开发工具Eve状态管理不可变性Immer与不可变数据Eve状态管理不可变性Immer与不可变数据 你是否还在为JavaScript状态管理中的数据突变问题头疼尝试过手动复制对象却依然出现意外副作用本文将带你编程语言SolidJS状态管理进阶不可变数据与不可变更新SolidJS状态管理进阶不可变数据与不可变更新 在现代前端框架中状态管理是构建复杂应用的核心挑战。SolidJS作为一款高性能的声明式JavaScript前端上一篇终极mimalloc集成指南3种方法彻底解决C内存性能瓶颈下一篇3 分钟上手 Zod 校验零依赖的 TypeScript 数据验证工具创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑