Skip to content

Commit c2270be

Browse files
committed
Auto merge of #136085 - jhpratt:rollup-dxmb2h2, r=jhpratt
Rollup of 7 pull requests Successful merges: - #133951 (Make the wasm_c_abi future compat warning a hard error) - #134283 (fix(libtest): Deprecate '--logfile') - #135785 (use `PassMode::Direct` for vector types on `s390x`) - #135948 (Update emscripten std tests) - #135951 (Use `fmt::from_fn` in more places in the compiler) - #136031 (Expand polonius MIR dump) - #136032 (Account for mutable borrow in argument suggestion) Failed merges: - #135635 (Move `std::io::pipe` code into its own file) r? `@ghost` `@rustbot` modify labels: rollup
2 parents 2f0ad2a + 64550d1 commit c2270be

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+730
-396
lines changed

compiler/rustc_borrowck/src/polonius/dump.rs

+170-11
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
use std::io;
22

3-
use rustc_middle::mir::pretty::{PrettyPrintMirOptions, dump_mir_with_options};
4-
use rustc_middle::mir::{Body, ClosureRegionRequirements, PassWhere};
3+
use rustc_middle::mir::pretty::{
4+
PassWhere, PrettyPrintMirOptions, create_dump_file, dump_enabled, dump_mir_to_writer,
5+
};
6+
use rustc_middle::mir::{Body, ClosureRegionRequirements};
57
use rustc_middle::ty::TyCtxt;
68
use rustc_session::config::MirIncludeSpans;
79

@@ -10,9 +12,6 @@ use crate::polonius::{LocalizedOutlivesConstraint, LocalizedOutlivesConstraintSe
1012
use crate::{BorrowckInferCtxt, RegionInferenceContext};
1113

1214
/// `-Zdump-mir=polonius` dumps MIR annotated with NLL and polonius specific information.
13-
// Note: this currently duplicates most of NLL MIR, with some additions for the localized outlives
14-
// constraints. This is ok for now as this dump will change in the near future to an HTML file to
15-
// become more useful.
1615
pub(crate) fn dump_polonius_mir<'tcx>(
1716
infcx: &BorrowckInferCtxt<'tcx>,
1817
body: &Body<'tcx>,
@@ -26,25 +25,113 @@ pub(crate) fn dump_polonius_mir<'tcx>(
2625
return;
2726
}
2827

28+
if !dump_enabled(tcx, "polonius", body.source.def_id()) {
29+
return;
30+
}
31+
2932
let localized_outlives_constraints = localized_outlives_constraints
3033
.expect("missing localized constraints with `-Zpolonius=next`");
3134

32-
// We want the NLL extra comments printed by default in NLL MIR dumps (they were removed in
33-
// #112346). Specifying `-Z mir-include-spans` on the CLI still has priority: for example,
34-
// they're always disabled in mir-opt tests to make working with blessed dumps easier.
35+
let _: io::Result<()> = try {
36+
let mut file = create_dump_file(tcx, "html", false, "polonius", &0, body)?;
37+
emit_polonius_dump(
38+
tcx,
39+
body,
40+
regioncx,
41+
borrow_set,
42+
localized_outlives_constraints,
43+
closure_region_requirements,
44+
&mut file,
45+
)?;
46+
};
47+
}
48+
49+
/// The polonius dump consists of:
50+
/// - the NLL MIR
51+
/// - the list of polonius localized constraints
52+
/// - a mermaid graph of the CFG
53+
fn emit_polonius_dump<'tcx>(
54+
tcx: TyCtxt<'tcx>,
55+
body: &Body<'tcx>,
56+
regioncx: &RegionInferenceContext<'tcx>,
57+
borrow_set: &BorrowSet<'tcx>,
58+
localized_outlives_constraints: LocalizedOutlivesConstraintSet,
59+
closure_region_requirements: &Option<ClosureRegionRequirements<'tcx>>,
60+
out: &mut dyn io::Write,
61+
) -> io::Result<()> {
62+
// Prepare the HTML dump file prologue.
63+
writeln!(out, "<!DOCTYPE html>")?;
64+
writeln!(out, "<html>")?;
65+
writeln!(out, "<head><title>Polonius MIR dump</title></head>")?;
66+
writeln!(out, "<body>")?;
67+
68+
// Section 1: the NLL + Polonius MIR.
69+
writeln!(out, "<div>")?;
70+
writeln!(out, "Raw MIR dump")?;
71+
writeln!(out, "<code><pre>")?;
72+
emit_html_mir(
73+
tcx,
74+
body,
75+
regioncx,
76+
borrow_set,
77+
localized_outlives_constraints,
78+
closure_region_requirements,
79+
out,
80+
)?;
81+
writeln!(out, "</pre></code>")?;
82+
writeln!(out, "</div>")?;
83+
84+
// Section 2: mermaid visualization of the CFG.
85+
writeln!(out, "<div>")?;
86+
writeln!(out, "Control-flow graph")?;
87+
writeln!(out, "<code><pre class='mermaid'>")?;
88+
emit_mermaid_cfg(body, out)?;
89+
writeln!(out, "</pre></code>")?;
90+
writeln!(out, "</div>")?;
91+
92+
// Finalize the dump with the HTML epilogue.
93+
writeln!(
94+
out,
95+
"<script src='https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js'></script>"
96+
)?;
97+
writeln!(out, "<script>")?;
98+
writeln!(out, "mermaid.initialize({{ startOnLoad: false, maxEdges: 100 }});")?;
99+
writeln!(out, "mermaid.run({{ querySelector: '.mermaid' }})")?;
100+
writeln!(out, "</script>")?;
101+
writeln!(out, "</body>")?;
102+
writeln!(out, "</html>")?;
103+
104+
Ok(())
105+
}
106+
107+
/// Emits the polonius MIR, as escaped HTML.
108+
fn emit_html_mir<'tcx>(
109+
tcx: TyCtxt<'tcx>,
110+
body: &Body<'tcx>,
111+
regioncx: &RegionInferenceContext<'tcx>,
112+
borrow_set: &BorrowSet<'tcx>,
113+
localized_outlives_constraints: LocalizedOutlivesConstraintSet,
114+
closure_region_requirements: &Option<ClosureRegionRequirements<'tcx>>,
115+
out: &mut dyn io::Write,
116+
) -> io::Result<()> {
117+
// Buffer the regular MIR dump to be able to escape it.
118+
let mut buffer = Vec::new();
119+
120+
// We want the NLL extra comments printed by default in NLL MIR dumps. Specifying `-Z
121+
// mir-include-spans` on the CLI still has priority.
35122
let options = PrettyPrintMirOptions {
36123
include_extra_comments: matches!(
37124
tcx.sess.opts.unstable_opts.mir_include_spans,
38125
MirIncludeSpans::On | MirIncludeSpans::Nll
39126
),
40127
};
41128

42-
dump_mir_with_options(
129+
dump_mir_to_writer(
43130
tcx,
44-
false,
45131
"polonius",
46132
&0,
47133
body,
134+
&mut buffer,
48135
|pass_where, out| {
49136
emit_polonius_mir(
50137
tcx,
@@ -57,7 +144,27 @@ pub(crate) fn dump_polonius_mir<'tcx>(
57144
)
58145
},
59146
options,
60-
);
147+
)?;
148+
149+
// Escape the handful of characters that need it. We don't need to be particularly efficient:
150+
// we're actually writing into a buffered writer already. Note that MIR dumps are valid UTF-8.
151+
let buffer = String::from_utf8_lossy(&buffer);
152+
for ch in buffer.chars() {
153+
let escaped = match ch {
154+
'>' => "&gt;",
155+
'<' => "&lt;",
156+
'&' => "&amp;",
157+
'\'' => "&#39;",
158+
'"' => "&quot;",
159+
_ => {
160+
// The common case, no escaping needed.
161+
write!(out, "{}", ch)?;
162+
continue;
163+
}
164+
};
165+
write!(out, "{}", escaped)?;
166+
}
167+
Ok(())
61168
}
62169

63170
/// Produces the actual NLL + Polonius MIR sections to emit during the dumping process.
@@ -102,3 +209,55 @@ fn emit_polonius_mir<'tcx>(
102209

103210
Ok(())
104211
}
212+
213+
/// Emits a mermaid flowchart of the CFG blocks and edges, similar to the graphviz version.
214+
fn emit_mermaid_cfg(body: &Body<'_>, out: &mut dyn io::Write) -> io::Result<()> {
215+
use rustc_middle::mir::{TerminatorEdges, TerminatorKind};
216+
217+
// The mermaid chart type: a top-down flowchart.
218+
writeln!(out, "flowchart TD")?;
219+
220+
// Emit the block nodes.
221+
for (block_idx, block) in body.basic_blocks.iter_enumerated() {
222+
let block_idx = block_idx.as_usize();
223+
let cleanup = if block.is_cleanup { " (cleanup)" } else { "" };
224+
writeln!(out, "{block_idx}[\"bb{block_idx}{cleanup}\"]")?;
225+
}
226+
227+
// Emit the edges between blocks, from the terminator edges.
228+
for (block_idx, block) in body.basic_blocks.iter_enumerated() {
229+
let block_idx = block_idx.as_usize();
230+
let terminator = block.terminator();
231+
match terminator.edges() {
232+
TerminatorEdges::None => {}
233+
TerminatorEdges::Single(bb) => {
234+
writeln!(out, "{block_idx} --> {}", bb.as_usize())?;
235+
}
236+
TerminatorEdges::Double(bb1, bb2) => {
237+
if matches!(terminator.kind, TerminatorKind::FalseEdge { .. }) {
238+
writeln!(out, "{block_idx} --> {}", bb1.as_usize())?;
239+
writeln!(out, "{block_idx} -- imaginary --> {}", bb2.as_usize())?;
240+
} else {
241+
writeln!(out, "{block_idx} --> {}", bb1.as_usize())?;
242+
writeln!(out, "{block_idx} -- unwind --> {}", bb2.as_usize())?;
243+
}
244+
}
245+
TerminatorEdges::AssignOnReturn { return_, cleanup, .. } => {
246+
for to_idx in return_ {
247+
writeln!(out, "{block_idx} --> {}", to_idx.as_usize())?;
248+
}
249+
250+
if let Some(to_idx) = cleanup {
251+
writeln!(out, "{block_idx} -- unwind --> {}", to_idx.as_usize())?;
252+
}
253+
}
254+
TerminatorEdges::SwitchInt { targets, .. } => {
255+
for to_idx in targets.all_targets() {
256+
writeln!(out, "{block_idx} --> {}", to_idx.as_usize())?;
257+
}
258+
}
259+
}
260+
}
261+
262+
Ok(())
263+
}

compiler/rustc_hir/src/hir.rs

+7-17
Original file line numberDiff line numberDiff line change
@@ -1285,13 +1285,13 @@ impl fmt::Debug for OwnerNodes<'_> {
12851285
.field("node", &self.nodes[ItemLocalId::ZERO])
12861286
.field(
12871287
"parents",
1288-
&self
1289-
.nodes
1290-
.iter_enumerated()
1291-
.map(|(id, parented_node)| {
1292-
debug_fn(move |f| write!(f, "({id:?}, {:?})", parented_node.parent))
1293-
})
1294-
.collect::<Vec<_>>(),
1288+
&fmt::from_fn(|f| {
1289+
f.debug_list()
1290+
.entries(self.nodes.iter_enumerated().map(|(id, parented_node)| {
1291+
fmt::from_fn(move |f| write!(f, "({id:?}, {:?})", parented_node.parent))
1292+
}))
1293+
.finish()
1294+
}),
12951295
)
12961296
.field("bodies", &self.bodies)
12971297
.field("opt_hash_including_bodies", &self.opt_hash_including_bodies)
@@ -4638,15 +4638,5 @@ mod size_asserts {
46384638
// tidy-alphabetical-end
46394639
}
46404640

4641-
fn debug_fn(f: impl Fn(&mut fmt::Formatter<'_>) -> fmt::Result) -> impl fmt::Debug {
4642-
struct DebugFn<F>(F);
4643-
impl<F: Fn(&mut fmt::Formatter<'_>) -> fmt::Result> fmt::Debug for DebugFn<F> {
4644-
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
4645-
(self.0)(fmt)
4646-
}
4647-
}
4648-
DebugFn(f)
4649-
}
4650-
46514641
#[cfg(test)]
46524642
mod tests;

compiler/rustc_hir/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#![allow(internal_features)]
77
#![feature(associated_type_defaults)]
88
#![feature(closure_track_caller)]
9+
#![feature(debug_closure_helpers)]
910
#![feature(exhaustive_patterns)]
1011
#![feature(let_chains)]
1112
#![feature(never_type)]

compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs

+30-32
Original file line numberDiff line numberDiff line change
@@ -184,68 +184,66 @@ enum Scope<'a> {
184184
},
185185
}
186186

187-
#[derive(Copy, Clone, Debug)]
188-
enum BinderScopeType {
189-
/// Any non-concatenating binder scopes.
190-
Normal,
191-
/// Within a syntactic trait ref, there may be multiple poly trait refs that
192-
/// are nested (under the `associated_type_bounds` feature). The binders of
193-
/// the inner poly trait refs are extended from the outer poly trait refs
194-
/// and don't increase the late bound depth. If you had
195-
/// `T: for<'a> Foo<Bar: for<'b> Baz<'a, 'b>>`, then the `for<'b>` scope
196-
/// would be `Concatenating`. This also used in trait refs in where clauses
197-
/// where we have two binders `for<> T: for<> Foo` (I've intentionally left
198-
/// out any lifetimes because they aren't needed to show the two scopes).
199-
/// The inner `for<>` has a scope of `Concatenating`.
200-
Concatenating,
201-
}
202-
203-
// A helper struct for debugging scopes without printing parent scopes
204-
struct TruncatedScopeDebug<'a>(&'a Scope<'a>);
205-
206-
impl<'a> fmt::Debug for TruncatedScopeDebug<'a> {
207-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
208-
match self.0 {
209-
Scope::Binder { bound_vars, scope_type, hir_id, where_bound_origin, s: _ } => f
187+
impl<'a> Scope<'a> {
188+
// A helper for debugging scopes without printing parent scopes
189+
fn debug_truncated(&'a self) -> impl fmt::Debug + 'a {
190+
fmt::from_fn(move |f| match self {
191+
Self::Binder { bound_vars, scope_type, hir_id, where_bound_origin, s: _ } => f
210192
.debug_struct("Binder")
211193
.field("bound_vars", bound_vars)
212194
.field("scope_type", scope_type)
213195
.field("hir_id", hir_id)
214196
.field("where_bound_origin", where_bound_origin)
215197
.field("s", &"..")
216198
.finish(),
217-
Scope::Opaque { captures, def_id, s: _ } => f
199+
Self::Opaque { captures, def_id, s: _ } => f
218200
.debug_struct("Opaque")
219201
.field("def_id", def_id)
220202
.field("captures", &captures.borrow())
221203
.field("s", &"..")
222204
.finish(),
223-
Scope::Body { id, s: _ } => {
205+
Self::Body { id, s: _ } => {
224206
f.debug_struct("Body").field("id", id).field("s", &"..").finish()
225207
}
226-
Scope::ObjectLifetimeDefault { lifetime, s: _ } => f
208+
Self::ObjectLifetimeDefault { lifetime, s: _ } => f
227209
.debug_struct("ObjectLifetimeDefault")
228210
.field("lifetime", lifetime)
229211
.field("s", &"..")
230212
.finish(),
231-
Scope::Supertrait { bound_vars, s: _ } => f
213+
Self::Supertrait { bound_vars, s: _ } => f
232214
.debug_struct("Supertrait")
233215
.field("bound_vars", bound_vars)
234216
.field("s", &"..")
235217
.finish(),
236-
Scope::TraitRefBoundary { s: _ } => f.debug_struct("TraitRefBoundary").finish(),
237-
Scope::LateBoundary { s: _, what, deny_late_regions } => f
218+
Self::TraitRefBoundary { s: _ } => f.debug_struct("TraitRefBoundary").finish(),
219+
Self::LateBoundary { s: _, what, deny_late_regions } => f
238220
.debug_struct("LateBoundary")
239221
.field("what", what)
240222
.field("deny_late_regions", deny_late_regions)
241223
.finish(),
242-
Scope::Root { opt_parent_item } => {
224+
Self::Root { opt_parent_item } => {
243225
f.debug_struct("Root").field("opt_parent_item", &opt_parent_item).finish()
244226
}
245-
}
227+
})
246228
}
247229
}
248230

231+
#[derive(Copy, Clone, Debug)]
232+
enum BinderScopeType {
233+
/// Any non-concatenating binder scopes.
234+
Normal,
235+
/// Within a syntactic trait ref, there may be multiple poly trait refs that
236+
/// are nested (under the `associated_type_bounds` feature). The binders of
237+
/// the inner poly trait refs are extended from the outer poly trait refs
238+
/// and don't increase the late bound depth. If you had
239+
/// `T: for<'a> Foo<Bar: for<'b> Baz<'a, 'b>>`, then the `for<'b>` scope
240+
/// would be `Concatenating`. This also used in trait refs in where clauses
241+
/// where we have two binders `for<> T: for<> Foo` (I've intentionally left
242+
/// out any lifetimes because they aren't needed to show the two scopes).
243+
/// The inner `for<>` has a scope of `Concatenating`.
244+
Concatenating,
245+
}
246+
249247
type ScopeRef<'a> = &'a Scope<'a>;
250248

251249
pub(crate) fn provide(providers: &mut Providers) {
@@ -1144,7 +1142,7 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> {
11441142
{
11451143
let BoundVarContext { tcx, map, .. } = self;
11461144
let mut this = BoundVarContext { tcx: *tcx, map, scope: &wrap_scope };
1147-
let span = debug_span!("scope", scope = ?TruncatedScopeDebug(this.scope));
1145+
let span = debug_span!("scope", scope = ?this.scope.debug_truncated());
11481146
{
11491147
let _enter = span.enter();
11501148
f(&mut this);

compiler/rustc_hir_analysis/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ This API is completely unstable and subject to change.
6363
#![doc(rust_logo)]
6464
#![feature(assert_matches)]
6565
#![feature(coroutines)]
66+
#![feature(debug_closure_helpers)]
6667
#![feature(if_let_guard)]
6768
#![feature(iter_from_coroutine)]
6869
#![feature(iter_intersperse)]

0 commit comments

Comments
 (0)