资讯动态

Rust 增量编译依赖图(Dep-Graph)调试与测试指南

发布时间:2026/9/12 3:17:25 来源:尧图企业网站定制
Rust 增量编译依赖图Dep-Graph调试与测试指南【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust导读rustc 的增量编译依赖图dep-graph记录了每个编译查询节点之间的依赖边是增量编译正确性与性能的核心数据结构。当你在修改编译器代码后遇到改了foo却错误地重编了bar这类问题或想为新功能补充依赖图测试时需要一套专门的工具来观测、过滤和断言这张图。本文基于 rustc-dev-guide 的incrcomp-debugging章节系统讲解依赖图的三种调试手段——#[rustc_if_this_changed]/#[rustc_then_this_would_need]注解测试、-Z dump-dep-graph图形导出与过滤、RUST_FORBID_DEP_GRAPH_EDGE非法边追踪并结合本仓库源码compiler/rustc_incremental/src/assert_dep_graph.rs、compiler/rustc_middle/src/dep_graph/debug.rs 等深入讲解其底层原理与实战用法。一、测试依赖图注解断言机制1.1 两个核心注解依赖图测试最简单的方式是在tests/ui的测试源码中直接使用两个 rustc 内部属性注解#[rustc_if_this_changed]标注改变源头。它标记某个函数或 trait 项、impl 项、结构体字段作为依赖路径的起点等价于声明如果我改了这里……。#[rustc_then_this_would_need(...)]标注预期受影响的目标参数是目标依赖节点如TypeckTables等价于声明……那么那里的 xxx 就需要重算。一个经典的成对用法如下#[rustc_if_this_changed] fn foo() { } #[rustc_then_this_would_need(TypeckTables)] //~ ERROR OK fn bar() { foo(); }这段代码的语义是如果foo改变了那么bar的TypeckTables就需要重算。测试会检查依赖图中确实存在一条从Hir(foo)到TypeckTables(bar)的路径路径存在时编译器在//~ ERROR OK所在行输出字符串OK到 stderr与 UI 测试框架的预期注释匹配测试通过。反过来可以断言不应该存在依赖路径#[rustc_then_this_would_need(TypeckTables)] //~ ERROR no path fn baz() { }语义是如果foo改变了baz的TypeckTables不需要重算。此时宏必须产生一条错误且错误消息必须包含no path对应源码中的诊断定义见 compiler/rustc_incremental/src/diagnostics.rsno path from foo to TypeckTables注意//~ ERROR OK和//~ ERROR no path从被测试的 Rust 代码角度看是普通注释但对 UI 测试框架而言是必须匹配的预期输出关于 UI 测试的通用机制可参考 tests/ui 章节。1.2 源码级原理断言如何被收集与校验这两个注解的解析与校验逻辑位于 compiler/rustc_incremental/src/assert_dep_graph.rs。核心入口是assert_dep_graph(tcx)函数它通过IfThisChangedvisitor 遍历 crate 内所有 item、trait 项、impl 项与字段定义收集两类注解rustc_if_this_changed的完整形式是#[rustc_if_this_changed(foo)]其中foo用于指定源节点标签省略时默认使用Hir对应DepKind::hir_ownerrustc_then_this_would_need则通过DepNode::from_label_string将字符串解析为具体的DepNode标签。随后check_paths对每个源节点调用query.transitive_predecessors(source_dep_node)求出其传递前驱集合再逐一检查目标节点是否在该集合中在则报告OK不在则报告NoPath错误。也就是说断言校验的是整条路径的存在性传递闭包而不只是单条边。同时源码中有两处前置约束值得注意使用这两个注解必须开启-Z query-dep-graph否则直接报错assert_dep_graph内对应断言rustc_attrsfeature 必须启用注解才会被识别。从 assert_dep_graph.rs 的模块注释可知这些检查在代码生成之后运行观察的是依赖图的最终状态而persist::clean中还有类似的断言用于检查刚从磁盘加载依赖图时的初始状态二者配合可覆盖增量编译的全生命周期。1.3 真实测试用例解读仓库中的 tests/ui/dep-graph/dep-graph-caller-callee.rs 是文档点名的示例它验证了一个非常本质的增量编译性质——改动x只应重算直接调用者y的函数体而不应波及zy的调用者其签名未变// incremental // compile-flags: -Z query-dep-graph #![feature(rustc_attrs)] #![allow(dead_code)] mod x { #[rustc_if_this_changed] pub fn x() { } } mod y { use crate::x; // These dependencies SHOULD exist: #[rustc_then_this_would_need(typeck_root)] //~ ERROR OK pub fn y() { x::x(); } } mod z { use crate::y; // These are expected to yield errors, because changes to x // affect the BODY of y, but not its signature. #[rustc_then_this_would_need(typeck_root)] //~ ERROR no path pub fn z() { y::y(); } }该测试同时使用incremental与-Z query-dep-graph两个编译标志。注意这里的目标标签用的是typeck_root而非TypeckTables说明标签字符串可以由源码中的DepNode定义灵活指定。tests/ui/dep-graph/目录下还有 dep-graph-trait-impl.rs、dep-graph-struct-signature.rs、dep-graph-type-alias.rs、dep-graph-variance-alias.rs、dep-graph-assoc-type-codegen.rs 等大量同类用例分别覆盖 trait 实现、结构体签名、类型别名、variance、关联类型等多个维度的依赖路径断言是编写新测试时的绝佳模板。二、调试依赖图导出与过滤2.1 导出完整图-Z dump-dep-graph编译器支持把依赖图导出到磁盘方便人工检视。使用方法rustc -Z dump-dep-graph your_crate.rs默认会在当前目录生成两个文件dep_graph.txt纯文本边列表每行形如Source - Targetdep_graph.dotGraphViz 格式可交给dot工具渲染成可视化图形。文件名可通过环境变量RUST_DEP_GRAPH覆盖例如RUST_DEP_GRAPHmygraph rustc -Z dump-dep-graph your_crate.rs # 生成 mygraph.txt 与 mygraph.dot对应实现见 assert_dep_graph.rs 的dump_graphtxt文件直接按{source:?} - {target:?}逐行写出所有边dot文件则通过rustc_graphviz渲染节点标签为DepKind的 Debug 输出。两处前置条件同样来自源码-Z dump-dep-graph是[UNTRACKED]的 unstable 选项其官方描述为将依赖图以文本文件和 GraphViz dot 文件两种形式导出到$RUST_DEP_GRAPH默认./dep_graph.{dot,txt}定义于 compiler/rustc_session/src/options.rs实测同样要求-Z query-dep-graph开启dep-graph-dump.rs 测试 即验证了在未开启时输出cant dump dependency graph without -Z query-dep-graph的错误。因此完整命令应为rustc -Z query-dep-graph -Z dump-dep-graph your_crate.rs2.2 过滤图RUST_DEP_GRAPH_FILTER全量依赖图通常非常庞大难以直接阅读。编译器支持通过环境变量RUST_DEP_GRAPH_FILTER过滤过滤模式有三种source_filter // 从 source_filter 出发可达的所有节点出边方向 - target_filter // 能到达 target_filter 的所有节点入边方向 source_filter - target_filter // 位于 source_filter 与 target_filter 之间的节点其中source_filter与target_filter是用分隔的字符串列表。一个节点只要其标签中同时包含这些子串即视为匹配对应 dep_graph/debug.rs 中DepNodeFilter::test的实现对{node:?}的 Debug 字符串按切分后逐一做contains检查。典型用法# 选出所有 TypeckTables 节点的全部前驱 RUST_DEP_GRAPH_FILTER- TypeckTables # 只选名字含 bar 的函数的 TypeckTables 节点的前驱 RUST_DEP_GRAPH_FILTER- TypeckTables bar # 找出从 Hir(foo) 到 TypeckTables(bar) 之间的所有节点 # 用于排查改 foo 却重算 bar的错误边来源 RUST_DEP_GRAPH_FILTERHir foo - TypeckTables bar第三行是最常用的排错姿势当你发现改动foo后编译器不得不重新类型检查bar而你认为这不该发生时用它导出Hir(foo)到TypeckTables(bar)之间的全部中间节点就能顺藤摸瓜找到那条多余的边。从源码看EdgeFilter::new要求格式严格为source - target两个部分否则报错expected a filter like ab - cd过滤逻辑则在assert_dep_graph.rs的filter_nodes中分三种情况处理只有源则沿OUTGOING方向 BFS 遍历、只有目标则沿INCOMING方向遍历、两者都有则执行walk_between双向搜索并借助三态标记Undecided/Deciding/Included/Excluded处理环。2.3 追踪错误边RUST_FORBID_DEP_GRAPH_EDGE有时候你导出的图中存在一条不应存在的路径但不知道它从哪来。在开启 debug 断言的编译器构建中即debug_assertions下可以设置环境变量RUST_FORBID_DEP_GRAPH_EDGE来定位RUST_FORBID_DEP_GRAPH_EDGEHir foo - Collect bar RUST_BACKTRACE1 rustc ...编译器在创建每一条依赖边时都会用该过滤器做匹配一旦命中立即触发bug!并给出崩溃与回溯配合RUST_BACKTRACE1即可看到创建这条边时的完整调用栈。其语法与RUST_DEP_GRAPH_FILTER相同同样基于EdgeFilter但有两点本质差异它作用于每一条边source 端匹配DepNodeFilter、target 端匹配DepNodeFilter不处理图中的长路径见 graph.rs 中CurrentDepGraph::new对forbidden_edge的解析该逻辑被#[cfg(debug_assertions)]包裹release 构建无效必须使用带 debug 断言的编译器。2.4 实战排错流程把上面的工具串起来一个完整的追查错误边流程如下复现现象改动foo后bar被重新类型检查而你不认为需要。导出局部图RUST_DEP_GRAPH_FILTERHir foo - TypeckTables bar \ rustc -Z query-dep-graph -Z dump-dep-graph your_crate.rs阅读dep_graph.txt假设看到Hir(foo) - Collect(bar) Collect(bar) - TypeckTables(bar)锁定可疑边Hir(foo) - Collect(bar)这条边看起来是多余的。设置禁令过滤器并重新编译debug 构建RUST_FORBID_DEP_GRAPH_EDGEHir foo - Collect bar \ RUST_BACKTRACE1 \ rustc -Z query-dep-graph -Z dump-dep-graph your_crate.rs编译器在创建该边时立即bug!并打印回溯根据调用栈定位到创建这条边的查询代码修复后问题消失。三、总结三件套工具的分工工具作用对象关键参数适用场景#[rustc_if_this_changed]/#[rustc_then_this_would_need]依赖图路径传递闭包-Z query-dep-graphrustc_attrsfeature写自动化回归测试断言/否定路径存在-Z dump-dep-graphRUST_DEP_GRAPH_FILTER图导出与节点过滤-Z query-dep-graph必需人工检视局部依赖图定位错误边RUST_FORBID_DEP_GRAPH_EDGE单条边的创建debug 断言构建 RUST_BACKTRACE1确认错误边来源获取回溯三个工具共享同一套DepNode标签与过滤器语法连接子串、-分隔源与目标底层实现在 compiler/rustc_middle/src/dep_graph/debug.rs 与 compiler/rustc_incremental/src/assert_dep_graph.rs。掌握它们无论是为增量编译的新改动编写依赖图回归测试还是排查过度重算的性能与正确性 bug都能事半功倍。更多相关背景可继续阅读仓库中增量编译章节的配套文档rustc-dev-guide 目录下incrcomp-*系列。【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价