Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rollup of 7 pull requests #136085

Merged
merged 23 commits into from
Jan 26, 2025
Merged
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
893d81f
on s390x, use `PassMode::Direct` for vector types
folkertdev Jan 20, 2025
4d1c16c
Make the wasm_c_abi future compat warning a hard error
bjorn3 Dec 6, 2024
49c3aaa
Add test
bjorn3 Jan 9, 2025
d0a70d9
Fix testing of the standard library with Emscripten
bjorn3 Jan 23, 2025
88ff147
Remove a bunch of emscripten test ignores
bjorn3 Jan 23, 2025
a20996c
Update a bunch of comments from before wasi support was added
bjorn3 Jan 23, 2025
6b18473
Update target docs to mention MAXIMUM_MEMORY=2GB
bjorn3 Jan 24, 2025
815be93
rustc_hir: replace `debug_fn` with unstable `fmt::from_fn`
yotamofek Jan 23, 2025
8b57fd9
use `fmt::from_fn` in more places, instead of using structs that impl…
yotamofek Jan 23, 2025
2444adf
fix(libtest): Deprecate '--logfile'
epage Dec 13, 2024
1dfc437
Account for mutable borrow in argument suggestion
estebank Jan 24, 2025
492d985
extract principal MIR dump function
lqd Jan 24, 2025
3631c15
use more explicit MIR dumping process
lqd Jan 24, 2025
6baa65e
switch polonius MIR dump to HTML
lqd Jan 24, 2025
82c0168
fix terminator edges comments
lqd Jan 24, 2025
09fb70a
add CFG to polonius MIR dump
lqd Jan 24, 2025
dc202df
Rollup merge of #133951 - bjorn3:wasm_c_abi_lint_hard_error, r=workin…
jhpratt Jan 26, 2025
9ff7ab9
Rollup merge of #134283 - epage:logfile, r=Amanieu
jhpratt Jan 26, 2025
61e572b
Rollup merge of #135785 - folkertdev:s390x-vector-passmode-direct, r=…
jhpratt Jan 26, 2025
b58221e
Rollup merge of #135948 - bjorn3:update_emscripten_std_tests, r=Mark-…
jhpratt Jan 26, 2025
6cf4204
Rollup merge of #135951 - yotamofek:use-debug-helpers, r=SparrowLii
jhpratt Jan 26, 2025
182ccfa
Rollup merge of #136031 - lqd:polonius-debugger-episode-1, r=compiler…
jhpratt Jan 26, 2025
64550d1
Rollup merge of #136032 - estebank:issue-136028, r=SparrowLii
jhpratt Jan 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
181 changes: 170 additions & 11 deletions compiler/rustc_borrowck/src/polonius/dump.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use std::io;

use rustc_middle::mir::pretty::{PrettyPrintMirOptions, dump_mir_with_options};
use rustc_middle::mir::{Body, ClosureRegionRequirements, PassWhere};
use rustc_middle::mir::pretty::{
PassWhere, PrettyPrintMirOptions, create_dump_file, dump_enabled, dump_mir_to_writer,
};
use rustc_middle::mir::{Body, ClosureRegionRequirements};
use rustc_middle::ty::TyCtxt;
use rustc_session::config::MirIncludeSpans;

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

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

if !dump_enabled(tcx, "polonius", body.source.def_id()) {
return;
}

let localized_outlives_constraints = localized_outlives_constraints
.expect("missing localized constraints with `-Zpolonius=next`");

// We want the NLL extra comments printed by default in NLL MIR dumps (they were removed in
// #112346). Specifying `-Z mir-include-spans` on the CLI still has priority: for example,
// they're always disabled in mir-opt tests to make working with blessed dumps easier.
let _: io::Result<()> = try {
let mut file = create_dump_file(tcx, "html", false, "polonius", &0, body)?;
emit_polonius_dump(
tcx,
body,
regioncx,
borrow_set,
localized_outlives_constraints,
closure_region_requirements,
&mut file,
)?;
};
}

/// The polonius dump consists of:
/// - the NLL MIR
/// - the list of polonius localized constraints
/// - a mermaid graph of the CFG
fn emit_polonius_dump<'tcx>(
tcx: TyCtxt<'tcx>,
body: &Body<'tcx>,
regioncx: &RegionInferenceContext<'tcx>,
borrow_set: &BorrowSet<'tcx>,
localized_outlives_constraints: LocalizedOutlivesConstraintSet,
closure_region_requirements: &Option<ClosureRegionRequirements<'tcx>>,
out: &mut dyn io::Write,
) -> io::Result<()> {
// Prepare the HTML dump file prologue.
writeln!(out, "<!DOCTYPE html>")?;
writeln!(out, "<html>")?;
writeln!(out, "<head><title>Polonius MIR dump</title></head>")?;
writeln!(out, "<body>")?;

// Section 1: the NLL + Polonius MIR.
writeln!(out, "<div>")?;
writeln!(out, "Raw MIR dump")?;
writeln!(out, "<code><pre>")?;
emit_html_mir(
tcx,
body,
regioncx,
borrow_set,
localized_outlives_constraints,
closure_region_requirements,
out,
)?;
writeln!(out, "</pre></code>")?;
writeln!(out, "</div>")?;

// Section 2: mermaid visualization of the CFG.
writeln!(out, "<div>")?;
writeln!(out, "Control-flow graph")?;
writeln!(out, "<code><pre class='mermaid'>")?;
emit_mermaid_cfg(body, out)?;
writeln!(out, "</pre></code>")?;
writeln!(out, "</div>")?;

// Finalize the dump with the HTML epilogue.
writeln!(
out,
"<script src='https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js'></script>"
)?;
writeln!(out, "<script>")?;
writeln!(out, "mermaid.initialize({{ startOnLoad: false, maxEdges: 100 }});")?;
writeln!(out, "mermaid.run({{ querySelector: '.mermaid' }})")?;
writeln!(out, "</script>")?;
writeln!(out, "</body>")?;
writeln!(out, "</html>")?;

Ok(())
}

/// Emits the polonius MIR, as escaped HTML.
fn emit_html_mir<'tcx>(
tcx: TyCtxt<'tcx>,
body: &Body<'tcx>,
regioncx: &RegionInferenceContext<'tcx>,
borrow_set: &BorrowSet<'tcx>,
localized_outlives_constraints: LocalizedOutlivesConstraintSet,
closure_region_requirements: &Option<ClosureRegionRequirements<'tcx>>,
out: &mut dyn io::Write,
) -> io::Result<()> {
// Buffer the regular MIR dump to be able to escape it.
let mut buffer = Vec::new();

// We want the NLL extra comments printed by default in NLL MIR dumps. Specifying `-Z
// mir-include-spans` on the CLI still has priority.
let options = PrettyPrintMirOptions {
include_extra_comments: matches!(
tcx.sess.opts.unstable_opts.mir_include_spans,
MirIncludeSpans::On | MirIncludeSpans::Nll
),
};

dump_mir_with_options(
dump_mir_to_writer(
tcx,
false,
"polonius",
&0,
body,
&mut buffer,
|pass_where, out| {
emit_polonius_mir(
tcx,
@@ -57,7 +144,27 @@ pub(crate) fn dump_polonius_mir<'tcx>(
)
},
options,
);
)?;

// Escape the handful of characters that need it. We don't need to be particularly efficient:
// we're actually writing into a buffered writer already. Note that MIR dumps are valid UTF-8.
let buffer = String::from_utf8_lossy(&buffer);
for ch in buffer.chars() {
let escaped = match ch {
'>' => "&gt;",
'<' => "&lt;",
'&' => "&amp;",
'\'' => "&#39;",
'"' => "&quot;",
_ => {
// The common case, no escaping needed.
write!(out, "{}", ch)?;
continue;
}
};
write!(out, "{}", escaped)?;
}
Ok(())
}

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

Ok(())
}

/// Emits a mermaid flowchart of the CFG blocks and edges, similar to the graphviz version.
fn emit_mermaid_cfg(body: &Body<'_>, out: &mut dyn io::Write) -> io::Result<()> {
use rustc_middle::mir::{TerminatorEdges, TerminatorKind};

// The mermaid chart type: a top-down flowchart.
writeln!(out, "flowchart TD")?;

// Emit the block nodes.
for (block_idx, block) in body.basic_blocks.iter_enumerated() {
let block_idx = block_idx.as_usize();
let cleanup = if block.is_cleanup { " (cleanup)" } else { "" };
writeln!(out, "{block_idx}[\"bb{block_idx}{cleanup}\"]")?;
}

// Emit the edges between blocks, from the terminator edges.
for (block_idx, block) in body.basic_blocks.iter_enumerated() {
let block_idx = block_idx.as_usize();
let terminator = block.terminator();
match terminator.edges() {
TerminatorEdges::None => {}
TerminatorEdges::Single(bb) => {
writeln!(out, "{block_idx} --> {}", bb.as_usize())?;
}
TerminatorEdges::Double(bb1, bb2) => {
if matches!(terminator.kind, TerminatorKind::FalseEdge { .. }) {
writeln!(out, "{block_idx} --> {}", bb1.as_usize())?;
writeln!(out, "{block_idx} -- imaginary --> {}", bb2.as_usize())?;
} else {
writeln!(out, "{block_idx} --> {}", bb1.as_usize())?;
writeln!(out, "{block_idx} -- unwind --> {}", bb2.as_usize())?;
}
}
TerminatorEdges::AssignOnReturn { return_, cleanup, .. } => {
for to_idx in return_ {
writeln!(out, "{block_idx} --> {}", to_idx.as_usize())?;
}

if let Some(to_idx) = cleanup {
writeln!(out, "{block_idx} -- unwind --> {}", to_idx.as_usize())?;
}
}
TerminatorEdges::SwitchInt { targets, .. } => {
for to_idx in targets.all_targets() {
writeln!(out, "{block_idx} --> {}", to_idx.as_usize())?;
}
}
}
}

Ok(())
}
24 changes: 7 additions & 17 deletions compiler/rustc_hir/src/hir.rs
Original file line number Diff line number Diff line change
@@ -1285,13 +1285,13 @@ impl fmt::Debug for OwnerNodes<'_> {
.field("node", &self.nodes[ItemLocalId::ZERO])
.field(
"parents",
&self
.nodes
.iter_enumerated()
.map(|(id, parented_node)| {
debug_fn(move |f| write!(f, "({id:?}, {:?})", parented_node.parent))
})
.collect::<Vec<_>>(),
&fmt::from_fn(|f| {
f.debug_list()
.entries(self.nodes.iter_enumerated().map(|(id, parented_node)| {
fmt::from_fn(move |f| write!(f, "({id:?}, {:?})", parented_node.parent))
}))
.finish()
}),
)
.field("bodies", &self.bodies)
.field("opt_hash_including_bodies", &self.opt_hash_including_bodies)
@@ -4638,15 +4638,5 @@ mod size_asserts {
// tidy-alphabetical-end
}

fn debug_fn(f: impl Fn(&mut fmt::Formatter<'_>) -> fmt::Result) -> impl fmt::Debug {
struct DebugFn<F>(F);
impl<F: Fn(&mut fmt::Formatter<'_>) -> fmt::Result> fmt::Debug for DebugFn<F> {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
(self.0)(fmt)
}
}
DebugFn(f)
}

#[cfg(test)]
mod tests;
1 change: 1 addition & 0 deletions compiler/rustc_hir/src/lib.rs
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@
#![allow(internal_features)]
#![feature(associated_type_defaults)]
#![feature(closure_track_caller)]
#![feature(debug_closure_helpers)]
#![feature(exhaustive_patterns)]
#![feature(let_chains)]
#![feature(never_type)]
62 changes: 30 additions & 32 deletions compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs
Original file line number Diff line number Diff line change
@@ -184,68 +184,66 @@ enum Scope<'a> {
},
}

#[derive(Copy, Clone, Debug)]
enum BinderScopeType {
/// Any non-concatenating binder scopes.
Normal,
/// Within a syntactic trait ref, there may be multiple poly trait refs that
/// are nested (under the `associated_type_bounds` feature). The binders of
/// the inner poly trait refs are extended from the outer poly trait refs
/// and don't increase the late bound depth. If you had
/// `T: for<'a> Foo<Bar: for<'b> Baz<'a, 'b>>`, then the `for<'b>` scope
/// would be `Concatenating`. This also used in trait refs in where clauses
/// where we have two binders `for<> T: for<> Foo` (I've intentionally left
/// out any lifetimes because they aren't needed to show the two scopes).
/// The inner `for<>` has a scope of `Concatenating`.
Concatenating,
}

// A helper struct for debugging scopes without printing parent scopes
struct TruncatedScopeDebug<'a>(&'a Scope<'a>);

impl<'a> fmt::Debug for TruncatedScopeDebug<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0 {
Scope::Binder { bound_vars, scope_type, hir_id, where_bound_origin, s: _ } => f
impl<'a> Scope<'a> {
// A helper for debugging scopes without printing parent scopes
fn debug_truncated(&'a self) -> impl fmt::Debug + 'a {
fmt::from_fn(move |f| match self {
Self::Binder { bound_vars, scope_type, hir_id, where_bound_origin, s: _ } => f
.debug_struct("Binder")
.field("bound_vars", bound_vars)
.field("scope_type", scope_type)
.field("hir_id", hir_id)
.field("where_bound_origin", where_bound_origin)
.field("s", &"..")
.finish(),
Scope::Opaque { captures, def_id, s: _ } => f
Self::Opaque { captures, def_id, s: _ } => f
.debug_struct("Opaque")
.field("def_id", def_id)
.field("captures", &captures.borrow())
.field("s", &"..")
.finish(),
Scope::Body { id, s: _ } => {
Self::Body { id, s: _ } => {
f.debug_struct("Body").field("id", id).field("s", &"..").finish()
}
Scope::ObjectLifetimeDefault { lifetime, s: _ } => f
Self::ObjectLifetimeDefault { lifetime, s: _ } => f
.debug_struct("ObjectLifetimeDefault")
.field("lifetime", lifetime)
.field("s", &"..")
.finish(),
Scope::Supertrait { bound_vars, s: _ } => f
Self::Supertrait { bound_vars, s: _ } => f
.debug_struct("Supertrait")
.field("bound_vars", bound_vars)
.field("s", &"..")
.finish(),
Scope::TraitRefBoundary { s: _ } => f.debug_struct("TraitRefBoundary").finish(),
Scope::LateBoundary { s: _, what, deny_late_regions } => f
Self::TraitRefBoundary { s: _ } => f.debug_struct("TraitRefBoundary").finish(),
Self::LateBoundary { s: _, what, deny_late_regions } => f
.debug_struct("LateBoundary")
.field("what", what)
.field("deny_late_regions", deny_late_regions)
.finish(),
Scope::Root { opt_parent_item } => {
Self::Root { opt_parent_item } => {
f.debug_struct("Root").field("opt_parent_item", &opt_parent_item).finish()
}
}
})
}
}

#[derive(Copy, Clone, Debug)]
enum BinderScopeType {
/// Any non-concatenating binder scopes.
Normal,
/// Within a syntactic trait ref, there may be multiple poly trait refs that
/// are nested (under the `associated_type_bounds` feature). The binders of
/// the inner poly trait refs are extended from the outer poly trait refs
/// and don't increase the late bound depth. If you had
/// `T: for<'a> Foo<Bar: for<'b> Baz<'a, 'b>>`, then the `for<'b>` scope
/// would be `Concatenating`. This also used in trait refs in where clauses
/// where we have two binders `for<> T: for<> Foo` (I've intentionally left
/// out any lifetimes because they aren't needed to show the two scopes).
/// The inner `for<>` has a scope of `Concatenating`.
Concatenating,
}

type ScopeRef<'a> = &'a Scope<'a>;

pub(crate) fn provide(providers: &mut Providers) {
@@ -1144,7 +1142,7 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> {
{
let BoundVarContext { tcx, map, .. } = self;
let mut this = BoundVarContext { tcx: *tcx, map, scope: &wrap_scope };
let span = debug_span!("scope", scope = ?TruncatedScopeDebug(this.scope));
let span = debug_span!("scope", scope = ?this.scope.debug_truncated());
{
let _enter = span.enter();
f(&mut this);
1 change: 1 addition & 0 deletions compiler/rustc_hir_analysis/src/lib.rs
Original file line number Diff line number Diff line change
@@ -63,6 +63,7 @@ This API is completely unstable and subject to change.
#![doc(rust_logo)]
#![feature(assert_matches)]
#![feature(coroutines)]
#![feature(debug_closure_helpers)]
#![feature(if_let_guard)]
#![feature(iter_from_coroutine)]
#![feature(iter_intersperse)]
Loading
Loading