profile
viewpoint

google/starthinker 82

Framework for building data workflows provided by Google.

google/json5format 40

JSON5 (a.k.a., "JSON for Humans") formatter that preserves contextual comments

richkadel/cesium-gwt 11

GWT wrappers around some of the Cesium JS library

richkadel/arbor 6

a graph visualization library using web workers and jQuery

richkadel/jsfunction-gwt 1

Extensive support for passing GWT Java functions to JavaScript APIs that require JavaScript functions

richkadel/owf-gwt 1

GWT wrappers around the OZONE Widget Framework (OWF) library

richkadel/cesium-plugins-list 0

The list of Cesium plugins. Open a pull request to add your plugin.

richkadel/com-cotdp-hadoop 0

Small class-library of tools intended to help working with Hadoop

richkadel/flip.tv 0

Appeligo Flip.TV source

richkadel/Gadget-Windowing-Toolkit 0

An LGPL-licensed UI widget tool-kit for Java 1.0.2 (recovered from the archives)

Pull request review commentrust-lang/rust

Working branch-level code coverage

 impl<'a, 'tcx> Instrumentor<'a, 'tcx> {         data.statements.push(statement);     } -    /// Converts the computed `CoverageRegion`s into `SpanViewable`s.-    fn span_viewables(&self, coverage_regions: &Vec<CoverageRegion>) -> Vec<SpanViewable> {-        let mut span_viewables = Vec::new();-        for coverage_region in coverage_regions {-            span_viewables.push(SpanViewable {-                span: coverage_region.span,-                id: format!("{}", coverage_region.blocks[0].index()),-                tooltip: self.make_tooltip_text(coverage_region),-            });+    /// Generate a minimal set of `CoverageSpan`s, each representing a contiguous code region to be+    /// counted.+    ///+    /// The basic steps are:+    ///+    /// 1. Extract an initial set of spans from the `Statement`s and `Terminator`s of each+    ///    `BasicCoverageBlock`.+    /// 2. Sort the spans by span.lo() (starting position). Spans that start at the same position+    ///    are sorted with longer spans before shorter spans; and equal spans are sorted+    ///    (deterministically) based on "dominator" relationship (if any).+    /// 3. Traverse the spans in sorted order to identify spans that can be dropped (for instance,+    ///    if another span or spans are already counting the same code region), or should be merged+    ///    into a broader combined span (because it represents a contiguous, non-branching, and+    ///    uninterrupted region of source code).+    ///+    ///    Closures are exposed in their enclosing functions as `Assign` `Rvalue`s, and since+    ///    closures have their own MIR, their `Span` in their enclosing function should be left+    ///    "uncovered".+    ///+    /// Note the resulting vector of `CoverageSpan`s does may not be fully sorted (and does not need+    /// to be).+    fn coverage_spans(&self) -> Vec<CoverageSpan> {+        let mut initial_spans_by_lo =+            Vec::<CoverageSpan>::with_capacity(self.mir_body.basic_blocks().len() * 2);+        for bcb in self.basic_coverage_blocks().iter() {+            for coverage_span in self.extract_spans(bcb) {+                initial_spans_by_lo.push(coverage_span);+            }         }-        span_viewables-    } -    /// A custom tooltip renderer used in a spanview HTML+CSS document used for coverage analysis.-    fn make_tooltip_text(&self, coverage_region: &CoverageRegion) -> String {-        const INCLUDE_COVERAGE_STATEMENTS: bool = false;-        let tcx = self.tcx;-        let source_map = tcx.sess.source_map();-        let mut text = Vec::new();-        for (i, &bb) in coverage_region.blocks.iter().enumerate() {-            if i > 0 {-                text.push("\n".to_owned());+        if initial_spans_by_lo.is_empty() {+            // This can happen if, for example, the function is unreachable (contains only a+            // `BasicBlock`(s) with an `Unreachable` terminator).+            return initial_spans_by_lo;+        }++        initial_spans_by_lo.sort_unstable_by(|a, b| {+            if a.span.lo() == b.span.lo() {+                if a.span.hi() == b.span.hi() {+                    if a.is_in_same_bcb(b) {+                        Some(Ordering::Equal)+                    } else {+                        // Sort equal spans by dominator relationship, in reverse order (so+                        // dominators always come after the dominated equal spans). When later+                        // comparing two spans in order, the first will either dominate the second,+                        // or they will have no dominator relationship.+                        self.dominators().rank_partial_cmp(b.bcb_leader_bb, a.bcb_leader_bb)+                    }+                } else {+                    // Sort hi() in reverse order so shorter spans are attempted after longer spans.+                    // This guarantees that, if a `prev` span overlaps, and is not equal to, a+                    // `curr` span, the prev span either extends further left of the curr span, or+                    // they start at the same position and the prev span extends further right of+                    // the end of the curr span.+                    b.span.hi().partial_cmp(&a.span.hi())+                }+            } else {+                a.span.lo().partial_cmp(&b.span.lo())             }-            text.push(format!("{:?}: {}:", bb, &source_map.span_to_string(coverage_region.span)));-            let data = &self.mir_body.basic_blocks()[bb];-            for statement in &data.statements {-                let statement_string = match statement.kind {-                    StatementKind::Coverage(box ref coverage) => match coverage.kind {-                        CoverageKind::Counter { id, .. } => {-                            if !INCLUDE_COVERAGE_STATEMENTS {-                                continue;-                            }-                            format!("increment counter #{}", id.index())-                        }-                        CoverageKind::Expression { id, lhs, op, rhs } => {-                            if !INCLUDE_COVERAGE_STATEMENTS {-                                continue;-                            }-                            format!(-                                "expression #{} = {} {} {}",-                                id.index(),-                                lhs.index(),-                                if op == Op::Add { "+" } else { "-" },-                                rhs.index()-                            )+            .unwrap()+        });++        let mut coverage_spans = Vec::with_capacity(initial_spans_by_lo.len());+        let mut pending_dups = Vec::<CoverageSpan>::new();++        // Traverse the spans. Note the `debug!()` statements explain some of the logic here, in+        // lieu of duplicative comments.+        let mut drain = initial_spans_by_lo.drain(..);+        let mut prev = drain.next().expect("at least one span");+        let mut prev_original_span = prev.span;+        for mut curr in drain {+            if let Some(dup) = pending_dups.last() {+                if dup.span != prev.span {+                    // If this is the case, then one of the following two things happened during the+                    // previous iteration:+                    //   * the `span` of prev was modified (by `curr.merge_from(prev)`); or+                    //   * the `span` of prev advanced past the end of the span of pending_dups+                    //     (`prev.span.hi() <= curr.span.lo()`)+                    // In either case, no more spans will match the span of `pending_dups`, so+                    // add them if possible, and clear them.+                    debug!(+                        "    SAME spans, but pending_dups are NOT THE SAME, so BCBs matched on \+                        previous iteration, or prev started a new disjoint span"+                    );+                    if dup.span.hi() <= curr.span.lo() {+                        for dup in pending_dups.drain(..) {+                            debug!("    ...adding at least one pending={:?}", dup);+                            coverage_spans.push(dup);                         }-                        CoverageKind::Unreachable => {-                            if !INCLUDE_COVERAGE_STATEMENTS {-                                continue;-                            }-                            String::from("unreachable")+                    } else {+                        pending_dups.clear();+                    }+                }+            }+            debug!("FOR curr={:?}", curr);+            let curr_span = curr.span;+            if prev.span.lo() > curr.span.lo() {+                // This can only happen if a prior iteration updates `prev` to skip forward, such as+                // skipping past a closure.+                debug!(+                    "  prev.span starts after curr.span, so curr will be dropped (skipping past \+                    closure?); prev={:?}",+                    prev+                );+                continue; // without changing prev+            } else if curr.is_mergeable(&prev) {+                debug!("  same bcb (and neither is a closure), merge with prev={:?}", prev);+                curr.merge_from(prev); // Note that curr.span may now differ from curr_span+            } else if prev.span.hi() <= curr.span.lo() {+                // different and disjoint spans, so keep both+                debug!(+                    "  different bcbs and disjoint spans, so keep curr for next iter, and add \+                    prev={:?}",+                    prev+                );+                coverage_spans.push(prev);+            } else if prev.is_closure {+                // drop any equal or overlapping span (`curr`) and keep `prev` to test again in the+                // next iter+                debug!(+                    "  curr overlaps a closure (prev). Drop curr and keep prev for next iter. \+                    prev={:?}",+                    prev+                );+                continue;+            } else if curr.is_closure {+                // if `prev`s span extends left of the closure (`curr`), carve out the closure's+                // span from `prev`'s span. (The closure's coverage counters will be injected when+                // processing the closure's own MIR.) Add the portion of the span to the left of the+                // closure; and if the span extends to the right of the closure, update `prev` to+                // that portion of the span. For any `pending_dups`, repeat the same process.+                let left_cutoff = curr.span.lo();+                let right_cutoff = curr.span.hi();+                let has_pre_closure_span = prev.span.lo() < right_cutoff;+                let has_post_closure_span = prev.span.hi() > right_cutoff;+                if has_pre_closure_span {

I've been refactoring this today. Update coming shortly. Thanks.

richkadel

comment created time in an hour

PullRequestReviewEvent

pull request commentrust-lang/rust

Working branch-level code coverage

It might be worth profiling with -Zself-profile

Thanks for the reference! I'll look into that.

richkadel

comment created time in an hour

Pull request review commentrust-lang/rust

Working branch-level code coverage

 fn filtered_statement_span(statement: &'a Statement<'tcx>, body_span: Span) -> O         // These statements have spans that are often outside the scope of the executed source code

(I'm actually amazed that these are the only special cases. It's way better than I expected, and I'm impressed that the MIR generally stays pretty true to the original source code.)

richkadel

comment created time in 9 hours

PullRequestReviewEvent

Pull request review commentrust-lang/rust

Working branch-level code coverage

 impl<'a, 'tcx> Instrumentor<'a, 'tcx> {     ///    into a broader combined span (because it represents a contiguous, non-branching, and     ///    uninterrupted region of source code).     ///+    ///    Closures are exposed in their enclosing functions as `Assign` `Rvalue`s, and since

I don't think this can change, but if for some reason it did, it would be a significant change in Rust and the MIR, and whoever changes it would have to fix this as well. (Tests would catch it.)

I did consider several alternatives, but I think this is the cleanest approach.

One alternative (that I rejected) would be to query the module for all other functions, get their spans, and see if any of those spans are inside the span of the function being instrumented. That is much more complex (both conceptually and from a performance perspective).

I'm quite happy with the current implementation actually.

richkadel

comment created time in 9 hours

PullRequestReviewEvent

Pull request review commentrust-lang/rust

Working branch-level code coverage

 fn filtered_statement_span(statement: &'a Statement<'tcx>, body_span: Span) -> O         // These statements have spans that are often outside the scope of the executed source code

@tmandry @wesleywiser - I don't think this is practical at the current time. There are very few special cases now. (There used to be more, but I was able to remove most of them.) The special cases that are left are (for the most part) necessary right now.

I think the only way to avoid this in the long term would require removing the spans from StorageLive and StorageDead (which I doubt is feasible). But that's just moving the special case handling outside of the InstrumentCoverage pass.

From a coverage perspective, these statements are problematic because their spans refer to the variable declarations, which are quite often in a completely different area in the source from where they actually need to come alive or die.

(Control Flow vs. Data Flow)

As for the other "special cases", I could remove Coverage and Nop. They are just included for completeness, but should not crop up here anyway.

The ForGuardBinding special case is definitely something I would like to remove, but there's already a FIXME for that. That's there because there is a bug somewhere else in the MIR code (I guess) producing an invalid Span.

richkadel

comment created time in 10 hours

PullRequestReviewEvent

pull request commentrust-lang/rust

Working branch-level code coverage

Update: I believe I've addressed all feedback (to date) from @tmandry. @wesleywiser has a review in progress, so we want to wait for that before merging.

richkadel

comment created time in a day

Pull request review commentrust-lang/rust

Working branch-level code coverage

 impl<'tcx> MirPass<'tcx> for InstrumentCoverage {     ) {         // If the InstrumentCoverage pass is called on promoted MIRs, skip them.         // See: https://github.com/rust-lang/rust/pull/73011#discussion_r438317601-        if mir_source.promoted.is_none() {-            Instrumentor::new(&self.name(), tcx, mir_source, mir_body).inject_counters();+        if mir_source.promoted.is_some() {+            trace!(+                "InstrumentCoverage skipped for {:?} (already promoted for Miri evaluation)",+                mir_source.def_id()+            );+            return;         }++        let hir_id = tcx.hir().local_def_id_to_hir_id(mir_source.def_id().expect_local());+        let is_fn_like = FnLikeNode::from_node(tcx.hir().get(hir_id)).is_some();++        // Only instrument functions, methods, and closures (not constants since they are evaluated+        // at compile time by Miri).+        if !is_fn_like {+            trace!("InstrumentCoverage skipped for {:?} (not an FnLikeNode)", mir_source.def_id());+            return;+        }+        // FIXME(richkadel): By comparison, the MIR pass `ConstProp` includes associated constants,+        // with functions, methods, and closures. I assume Miri is used for associated constants as+        // well. If not, we may need to include them here too.++        trace!("InstrumentCoverage starting for {:?}", mir_source.def_id());+        Instrumentor::new(&self.name(), tcx, mir_source, mir_body).inject_counters();+        trace!("InstrumentCoverage starting for {:?}", mir_source.def_id());     } } -#[derive(Clone)]-struct CoverageRegion {-    pub span: Span,+/// A BasicCoverageBlock (BCB) represents the maximal-length sequence of CFG (MIR) BasicBlocks+/// without conditional branches.+#[derive(Debug, Clone)]+struct BasicCoverageBlock {     pub blocks: Vec<BasicBlock>, } +impl BasicCoverageBlock {+    pub fn leader_bb(&self) -> BasicBlock {+        self.blocks[0]+    }++    pub fn id(&self) -> String {+        format!(+            "@{}",+            self.blocks+                .iter()+                .map(|bb| bb.index().to_string())+                .collect::<Vec<_>>()+                .join(ID_SEPARATOR)+        )+    }+}++struct BasicCoverageBlocks {+    vec: IndexVec<BasicBlock, Option<BasicCoverageBlock>>,+}++impl BasicCoverageBlocks {+    pub fn from_mir(mir_body: &mir::Body<'tcx>) -> Self {+        let mut basic_coverage_blocks =+            BasicCoverageBlocks { vec: IndexVec::from_elem_n(None, mir_body.basic_blocks().len()) };+        basic_coverage_blocks.extract_from_mir(mir_body);+        basic_coverage_blocks+    }++    pub fn iter(&self) -> impl Iterator<Item = &BasicCoverageBlock> {+        self.vec.iter().filter_map(|option| option.as_ref())+    }++    fn extract_from_mir(&mut self, mir_body: &mir::Body<'tcx>) {+        // Traverse the CFG but ignore anything following an `unwind`+        let cfg_without_unwind = ShortCircuitPreorder::new(mir_body, |term_kind| {+            let mut successors = term_kind.successors();+            match &term_kind {+                // SwitchInt successors are never unwind, and all of them should be traversed+                TerminatorKind::SwitchInt { .. } => successors,+                // For all other kinds, return only the first successor, if any, and ignore unwinds+                _ => successors.next().into_iter().chain(&[]),+            }+        });++        let mut blocks = Vec::new();+        for (bb, data) in cfg_without_unwind+            .filter(|(_, data)| data.terminator().kind != TerminatorKind::Unreachable)+        {+            if let Some(last) = blocks.last() {+                let predecessors = &mir_body.predecessors()[bb];+                if predecessors.len() > 1 || !predecessors.contains(last) {+                    self.add_basic_coverage_block(blocks.split_off(0));+                    debug!(+                        "  because {}",+                        if predecessors.len() > 1 {+                            "predecessors.len() > 1".to_owned()+                        } else {+                            format!("bb {} is not in precessors: {:?}", bb.index(), predecessors)+                        }+                    );+                }+            }+            blocks.push(bb);++            let term = data.terminator();++            // inject counters at branch blocks only

I also expanded the wildcard into the individual variants.

richkadel

comment created time in a day

PullRequestReviewEvent

Pull request review commentrust-lang/rust

Working branch-level code coverage

 impl<'tcx> MirPass<'tcx> for InstrumentCoverage {     ) {         // If the InstrumentCoverage pass is called on promoted MIRs, skip them.         // See: https://github.com/rust-lang/rust/pull/73011#discussion_r438317601-        if mir_source.promoted.is_none() {-            Instrumentor::new(&self.name(), tcx, mir_source, mir_body).inject_counters();+        if mir_source.promoted.is_some() {+            trace!(+                "InstrumentCoverage skipped for {:?} (already promoted for Miri evaluation)",+                mir_source.def_id()+            );+            return;         }++        let hir_id = tcx.hir().local_def_id_to_hir_id(mir_source.def_id().expect_local());+        let is_fn_like = FnLikeNode::from_node(tcx.hir().get(hir_id)).is_some();++        // Only instrument functions, methods, and closures (not constants since they are evaluated+        // at compile time by Miri).+        if !is_fn_like {+            trace!("InstrumentCoverage skipped for {:?} (not an FnLikeNode)", mir_source.def_id());+            return;+        }+        // FIXME(richkadel): By comparison, the MIR pass `ConstProp` includes associated constants,+        // with functions, methods, and closures. I assume Miri is used for associated constants as+        // well. If not, we may need to include them here too.++        trace!("InstrumentCoverage starting for {:?}", mir_source.def_id());+        Instrumentor::new(&self.name(), tcx, mir_source, mir_body).inject_counters();+        trace!("InstrumentCoverage starting for {:?}", mir_source.def_id());     } } -#[derive(Clone)]-struct CoverageRegion {-    pub span: Span,+/// A BasicCoverageBlock (BCB) represents the maximal-length sequence of CFG (MIR) BasicBlocks+/// without conditional branches.+#[derive(Debug, Clone)]+struct BasicCoverageBlock {     pub blocks: Vec<BasicBlock>, } +impl BasicCoverageBlock {+    pub fn leader_bb(&self) -> BasicBlock {+        self.blocks[0]+    }++    pub fn id(&self) -> String {+        format!(+            "@{}",+            self.blocks+                .iter()+                .map(|bb| bb.index().to_string())+                .collect::<Vec<_>>()+                .join(ID_SEPARATOR)+        )+    }+}++struct BasicCoverageBlocks {+    vec: IndexVec<BasicBlock, Option<BasicCoverageBlock>>,+}++impl BasicCoverageBlocks {+    pub fn from_mir(mir_body: &mir::Body<'tcx>) -> Self {+        let mut basic_coverage_blocks =+            BasicCoverageBlocks { vec: IndexVec::from_elem_n(None, mir_body.basic_blocks().len()) };+        basic_coverage_blocks.extract_from_mir(mir_body);+        basic_coverage_blocks+    }++    pub fn iter(&self) -> impl Iterator<Item = &BasicCoverageBlock> {+        self.vec.iter().filter_map(|option| option.as_ref())+    }++    fn extract_from_mir(&mut self, mir_body: &mir::Body<'tcx>) {+        // Traverse the CFG but ignore anything following an `unwind`+        let cfg_without_unwind = ShortCircuitPreorder::new(mir_body, |term_kind| {+            let mut successors = term_kind.successors();+            match &term_kind {+                // SwitchInt successors are never unwind, and all of them should be traversed+                TerminatorKind::SwitchInt { .. } => successors,+                // For all other kinds, return only the first successor, if any, and ignore unwinds+                _ => successors.next().into_iter().chain(&[]),+            }+        });++        let mut blocks = Vec::new();+        for (bb, data) in cfg_without_unwind+            .filter(|(_, data)| data.terminator().kind != TerminatorKind::Unreachable)

Done.

richkadel

comment created time in a day

PullRequestReviewEvent

Pull request review commentrust-lang/rust

Working branch-level code coverage

 impl<'tcx> MirPass<'tcx> for InstrumentCoverage {     ) {         // If the InstrumentCoverage pass is called on promoted MIRs, skip them.         // See: https://github.com/rust-lang/rust/pull/73011#discussion_r438317601-        if mir_source.promoted.is_none() {-            Instrumentor::new(&self.name(), tcx, mir_source, mir_body).inject_counters();+        if mir_source.promoted.is_some() {+            trace!(+                "InstrumentCoverage skipped for {:?} (already promoted for Miri evaluation)",+                mir_source.def_id()+            );+            return;         }++        let hir_id = tcx.hir().local_def_id_to_hir_id(mir_source.def_id().expect_local());+        let is_fn_like = FnLikeNode::from_node(tcx.hir().get(hir_id)).is_some();++        // Only instrument functions, methods, and closures (not constants since they are evaluated+        // at compile time by Miri).+        if !is_fn_like {+            trace!("InstrumentCoverage skipped for {:?} (not an FnLikeNode)", mir_source.def_id());+            return;+        }+        // FIXME(richkadel): By comparison, the MIR pass `ConstProp` includes associated constants,+        // with functions, methods, and closures. I assume Miri is used for associated constants as+        // well. If not, we may need to include them here too.++        trace!("InstrumentCoverage starting for {:?}", mir_source.def_id());+        Instrumentor::new(&self.name(), tcx, mir_source, mir_body).inject_counters();+        trace!("InstrumentCoverage starting for {:?}", mir_source.def_id());     } } -#[derive(Clone)]-struct CoverageRegion {-    pub span: Span,+/// A BasicCoverageBlock (BCB) represents the maximal-length sequence of CFG (MIR) BasicBlocks+/// without conditional branches.+#[derive(Debug, Clone)]+struct BasicCoverageBlock {     pub blocks: Vec<BasicBlock>, } +impl BasicCoverageBlock {+    pub fn leader_bb(&self) -> BasicBlock {+        self.blocks[0]+    }++    pub fn id(&self) -> String {+        format!(+            "@{}",+            self.blocks+                .iter()+                .map(|bb| bb.index().to_string())+                .collect::<Vec<_>>()+                .join(ID_SEPARATOR)+        )+    }+}++struct BasicCoverageBlocks {+    vec: IndexVec<BasicBlock, Option<BasicCoverageBlock>>,+}++impl BasicCoverageBlocks {+    pub fn from_mir(mir_body: &mir::Body<'tcx>) -> Self {+        let mut basic_coverage_blocks =+            BasicCoverageBlocks { vec: IndexVec::from_elem_n(None, mir_body.basic_blocks().len()) };+        basic_coverage_blocks.extract_from_mir(mir_body);+        basic_coverage_blocks+    }++    pub fn iter(&self) -> impl Iterator<Item = &BasicCoverageBlock> {+        self.vec.iter().filter_map(|option| option.as_ref())+    }++    fn extract_from_mir(&mut self, mir_body: &mir::Body<'tcx>) {+        // Traverse the CFG but ignore anything following an `unwind`+        let cfg_without_unwind = ShortCircuitPreorder::new(mir_body, |term_kind| {+            let mut successors = term_kind.successors();+            match &term_kind {+                // SwitchInt successors are never unwind, and all of them should be traversed+                TerminatorKind::SwitchInt { .. } => successors,+                // For all other kinds, return only the first successor, if any, and ignore unwinds+                _ => successors.next().into_iter().chain(&[]),+            }+        });++        let mut blocks = Vec::new();+        for (bb, data) in cfg_without_unwind+            .filter(|(_, data)| data.terminator().kind != TerminatorKind::Unreachable)+        {+            if let Some(last) = blocks.last() {+                let predecessors = &mir_body.predecessors()[bb];+                if predecessors.len() > 1 || !predecessors.contains(last) {+                    self.add_basic_coverage_block(blocks.split_off(0));+                    debug!(+                        "  because {}",+                        if predecessors.len() > 1 {+                            "predecessors.len() > 1".to_owned()+                        } else {+                            format!("bb {} is not in precessors: {:?}", bb.index(), predecessors)+                        }+                    );+                }+            }+            blocks.push(bb);++            let term = data.terminator();++            // inject counters at branch blocks only

Yes, I've added comments above the CFG traversal loop to clarify this, hoping that helps. The CFG traversal uses successors() to create the preorder traversal iterator. The unwind filter (above the loop) is where successors() is actually called, so unwind paths can be filtered out.

richkadel

comment created time in a day

PullRequestReviewEvent

push eventrichkadel/rust

Rich Kadel

commit sha 00f0bca24ab832e35defaa2639397974715d20d5

Addressed review feedback * Removed unnecessary Unreachable filter. * Replaced a match wildcard with remining variants. * Added more comments to help clarify the role of successors() in the CFG traversal

view details

push time in a day

Pull request review commentrust-lang/rust

Working branch-level code coverage

 impl<'tcx> MirPass<'tcx> for InstrumentCoverage {     ) {         // If the InstrumentCoverage pass is called on promoted MIRs, skip them.         // See: https://github.com/rust-lang/rust/pull/73011#discussion_r438317601-        if mir_source.promoted.is_none() {-            Instrumentor::new(&self.name(), tcx, mir_source, mir_body).inject_counters();+        if mir_source.promoted.is_some() {+            trace!(+                "InstrumentCoverage skipped for {:?} (already promoted for Miri evaluation)",+                mir_source.def_id()+            );+            return;         }++        let hir_id = tcx.hir().local_def_id_to_hir_id(mir_source.def_id().expect_local());+        let is_fn_like = FnLikeNode::from_node(tcx.hir().get(hir_id)).is_some();++        // Only instrument functions, methods, and closures (not constants since they are evaluated+        // at compile time by Miri).+        if !is_fn_like {+            trace!("InstrumentCoverage skipped for {:?} (not an FnLikeNode)", mir_source.def_id());+            return;+        }+        // FIXME(richkadel): By comparison, the MIR pass `ConstProp` includes associated constants,+        // with functions, methods, and closures. I assume Miri is used for associated constants as+        // well. If not, we may need to include them here too.++        trace!("InstrumentCoverage starting for {:?}", mir_source.def_id());+        Instrumentor::new(&self.name(), tcx, mir_source, mir_body).inject_counters();+        trace!("InstrumentCoverage starting for {:?}", mir_source.def_id());     } } -#[derive(Clone)]-struct CoverageRegion {-    pub span: Span,+/// A BasicCoverageBlock (BCB) represents the maximal-length sequence of CFG (MIR) BasicBlocks+/// without conditional branches.+#[derive(Debug, Clone)]+struct BasicCoverageBlock {     pub blocks: Vec<BasicBlock>, } +impl BasicCoverageBlock {+    pub fn leader_bb(&self) -> BasicBlock {+        self.blocks[0]+    }++    pub fn id(&self) -> String {+        format!(+            "@{}",+            self.blocks+                .iter()+                .map(|bb| bb.index().to_string())+                .collect::<Vec<_>>()+                .join(ID_SEPARATOR)+        )+    }+}++struct BasicCoverageBlocks {+    vec: IndexVec<BasicBlock, Option<BasicCoverageBlock>>,+}++impl BasicCoverageBlocks {+    pub fn from_mir(mir_body: &mir::Body<'tcx>) -> Self {+        let mut basic_coverage_blocks =+            BasicCoverageBlocks { vec: IndexVec::from_elem_n(None, mir_body.basic_blocks().len()) };+        basic_coverage_blocks.extract_from_mir(mir_body);+        basic_coverage_blocks+    }++    pub fn iter(&self) -> impl Iterator<Item = &BasicCoverageBlock> {+        self.vec.iter().filter_map(|option| option.as_ref())+    }++    fn extract_from_mir(&mut self, mir_body: &mir::Body<'tcx>) {+        // Traverse the CFG but ignore anything following an `unwind`+        let cfg_without_unwind = ShortCircuitPreorder::new(mir_body, |term_kind| {

Maybe is just moved. I don't see the duplication. I'll mark this resolved for now, but if you do see the duplication, let me know.

richkadel

comment created time in a day

PullRequestReviewEvent

Pull request review commentrust-lang/rust

Working branch-level code coverage

 impl<'tcx> MirPass<'tcx> for InstrumentCoverage {     ) {         // If the InstrumentCoverage pass is called on promoted MIRs, skip them.         // See: https://github.com/rust-lang/rust/pull/73011#discussion_r438317601-        if mir_source.promoted.is_none() {-            Instrumentor::new(&self.name(), tcx, mir_source, mir_body).inject_counters();+        if mir_source.promoted.is_some() {+            trace!(+                "InstrumentCoverage skipped for {:?} (already promoted for Miri evaluation)",+                mir_source.def_id()+            );+            return;         }++        let hir_id = tcx.hir().local_def_id_to_hir_id(mir_source.def_id().expect_local());+        let is_fn_like = FnLikeNode::from_node(tcx.hir().get(hir_id)).is_some();++        // Only instrument functions, methods, and closures (not constants since they are evaluated+        // at compile time by Miri).+        if !is_fn_like {+            trace!("InstrumentCoverage skipped for {:?} (not an FnLikeNode)", mir_source.def_id());+            return;+        }+        // FIXME(richkadel): By comparison, the MIR pass `ConstProp` includes associated constants,+        // with functions, methods, and closures. I assume Miri is used for associated constants as+        // well. If not, we may need to include them here too.++        trace!("InstrumentCoverage starting for {:?}", mir_source.def_id());+        Instrumentor::new(&self.name(), tcx, mir_source, mir_body).inject_counters();+        trace!("InstrumentCoverage starting for {:?}", mir_source.def_id());     } } -#[derive(Clone)]-struct CoverageRegion {-    pub span: Span,+/// A BasicCoverageBlock (BCB) represents the maximal-length sequence of CFG (MIR) BasicBlocks+/// without conditional branches.+#[derive(Debug, Clone)]+struct BasicCoverageBlock {     pub blocks: Vec<BasicBlock>, } +impl BasicCoverageBlock {+    pub fn leader_bb(&self) -> BasicBlock {+        self.blocks[0]+    }++    pub fn id(&self) -> String {+        format!(+            "@{}",+            self.blocks+                .iter()+                .map(|bb| bb.index().to_string())+                .collect::<Vec<_>>()+                .join(ID_SEPARATOR)+        )+    }+}++struct BasicCoverageBlocks {+    vec: IndexVec<BasicBlock, Option<BasicCoverageBlock>>,+}++impl BasicCoverageBlocks {+    pub fn from_mir(mir_body: &mir::Body<'tcx>) -> Self {+        let mut basic_coverage_blocks =+            BasicCoverageBlocks { vec: IndexVec::from_elem_n(None, mir_body.basic_blocks().len()) };+        basic_coverage_blocks.extract_from_mir(mir_body);+        basic_coverage_blocks+    }++    pub fn iter(&self) -> impl Iterator<Item = &BasicCoverageBlock> {+        self.vec.iter().filter_map(|option| option.as_ref())+    }++    fn extract_from_mir(&mut self, mir_body: &mir::Body<'tcx>) {+        // Traverse the CFG but ignore anything following an `unwind`+        let cfg_without_unwind = ShortCircuitPreorder::new(mir_body, |term_kind| {+            let mut successors = term_kind.successors();+            match &term_kind {+                // SwitchInt successors are never unwind, and all of them should be traversed+                TerminatorKind::SwitchInt { .. } => successors,+                // For all other kinds, return only the first successor, if any, and ignore unwinds+                _ => successors.next().into_iter().chain(&[]),+            }+        });++        let mut blocks = Vec::new();+        for (bb, data) in cfg_without_unwind+            .filter(|(_, data)| data.terminator().kind != TerminatorKind::Unreachable)

I believe you're right. Maybe I added the filter because I wasn't sure at the time, rather than having observed otherwise. I've seen MIR's with Unreachable terminators in recent test programs, but I'm having a hard time reproducing that situation, so I can't confirm this for sure. But looking at the Preorder code, it must be true.

I'll remove the filter and push the change as soon as I re-run the tests.

richkadel

comment created time in a day

PullRequestReviewEvent

pull request commentrust-lang/rust

Working branch-level code coverage

There should be some reduction in binary size after I replace some counters with expressions, but that will also add some time to the compiling. I think the bigger impact of expressions will be binary execution time (which I haven't performance-tested here).

I'd love to find ways to reduce the compile-time impact. I don't know of any low-hanging fruit to impact that, off the top of my head. Maybe reviewers will see something. Performance profiling of the instrumentation MIR pass and coverage map generation, will be good to do eventually. I'm also not sure how much LLVM's codegen implementation of this is adding. (I'm curious how these numbers stack up against Clang.

richkadel

comment created time in a day

pull request commentrust-lang/rust

Working branch-level code coverage

Note: Using this PR's rustc build, I compiled the json5format crate (including all of its dev dependencies) with and without -Z instrument-coverage.

Overall cargo build time (after clean) took around 74% more time to compile, and the target directory increased in size by 92%.

This is hardly scientific, and there may be outliers in this particular test. (The proptest compile stage seemed to take a very long portion of the compile time, for instance, but it's not clear if that's due to unusual complexities in proptest, or how it's used, or where it falls in the sequence of build stages.)

And the size increase is worth exploring at a more granular level as well.

But at least this gives us some idea of the order of magnitude of impact.

richkadel

comment created time in a day

pull request commentrust-lang/rust

Working branch-level code coverage

Sorry, but one more new test. I was wondering if the latest changes would still work for structs/functions with parameterized types, and it appears they do still work:

https://github.com/richkadel/rust/blob/llvm-coverage-counters-2/src/test/run-make-fulldeps/instrument-coverage-cov-reports-base/expected_show_coverage.coverage_of_generics.txt

richkadel

comment created time in a day

push eventrichkadel/rust

Rich Kadel

commit sha 3d14a530aa2cbf7fa9aeacce7e3977baf9b41e83

test coverage of inline generic struct function

view details

push time in a day

pull request commentrust-lang/rust

Working branch-level code coverage

@tmandry @wesleywiser -

I had an idea for improving test coverage (for a future PR). Let me know your thoughts.

The tests in https://github.com/richkadel/rust/tree/llvm-coverage-counters-2/src/test/run-make-fulldeps/ for instrument-coverage-mir-cov-html-* and instrument-coverage-cov-reports-* are good for validating the coverage spans are where we expect them, and the resulting coverage numbers are what we expect, and those tests share the same single set of Rust test program sources, at:

https://github.com/richkadel/rust/tree/llvm-coverage-counters-2/src/test/run-make-fulldeps/instrument-coverage/

By contrast, the mir-opt test is still quite primitive. It doesn't even have Counter Expressions.

I think it's a good idea to validate the MIR results from the InstrumentCoverage pass for the same kinds of Rust code situations tested by the run-make-fulldeps tests, but I don't want to copy and maintain two sets of test programs for the same test code.

It should be easy for me to change the run-make-fulldeps tests to use test sources from src/test/mir-opt/instrument-coverage/ (moving the existing tests from src/test/run-make-fulldeps/instrument-coverage/) so they can be used for both test suites.

The Makefiles already pull the sources from a different source directory anyway.

Thoughts?

richkadel

comment created time in 2 days

pull request commentrust-lang/rust

Working branch-level code coverage

closure span handling is fixed! (See changes in https://github.com/rust-lang/rust/pull/77080/commits/6ec5b4a4b16f0c7d7e27c8a84898b38ff5c1a57b)

Screen Shot 2020-09-27 at 5 07 47 PM

I added two new test cases, one for closures (to debug and resolve that problem), and one for inner items (including inner functions). The inner items worked fine without the new changes, but I hadn't tested that and didn't know until I did.

richkadel

comment created time in 2 days

push eventrichkadel/rust

Rich Kadel

commit sha 6ec5b4a4b16f0c7d7e27c8a84898b38ff5c1a57b

Fix closure handling, add tests for closures and inner items And cleaned up other tests for consistency, and to make it more clear where spans start/end by breaking up lines.

view details

Rich Kadel

commit sha 32e7fca07f7f195138146e9b4d71df38e2e79605

renamed "typical" test results "expected" Now that the `llvm-cov show` tests are improved to normally expect matching actuals, and to allow individual tests to override that expectation.

view details

push time in 2 days

pull request commentrust-lang/rust

Working branch-level code coverage

Update on the closure issue. I believe I have a fairly sensible solution. I'm testing it now and will update this PR.

richkadel

comment created time in 2 days

pull request commentrust-lang/rust

Working branch-level code coverage

(It is odd that some lines have 0 for coverage. That has to do with how llvm-cov is deciding to count lines, based on the overlapping spans it has.)

richkadel

comment created time in 3 days

pull request commentrust-lang/rust

Working branch-level code coverage

I wanted to highlight one problem that I did notice when compiling the json5format crate with coverage. I thought it was wrong, but actually it turns at to be "right" (from a MIR-coverage perspective). It's just problematic when trying to interpret the coverage analysis with llvm-cov show:

Screen Shot 2020-09-25 at 12 52 06 PM

If you look at the closure alone, the coverage seems to be wrong. It's showing 1 for line coverage, but all of the coverage spans show ^0 (and are highlighted in red.

This closure was not executed.

But the method unwrap_or_else(...) was called, and that function has a span that starts at self.get_matches_from_safe_borrow(iter).unwrap_or_else( and ends at the ) after the closure body!

So the line coverage of 1 is counting the execution of that outer function call, not the execution of the closure.

The coverage algorithm processes one MIR (function/closure) at a time, so even though the coverage algorithm deconflicts spans within a MIR, it doesn't have the context (I guess) to know to deconflict spans across different MIRs. (Or maybe it does, ...still thinking about that.)

Another possible solution, if not at the MIR coverage generation level, would be to resolve this at the module level during coverage map generation.

I think this is OK for this PR, and it is still technically correct, but I'll think about how to fix it and try to do something better in a follow-up PR, if the reviewers are OK with that.

richkadel

comment created time in 3 days

push eventrichkadel/rust

Rich Kadel

commit sha 9f153d16629c40382dab189f303ff1cdaee57370

Addressed PR feedback, and removed temporary -Zexperimental-coverage -Zinstrument-coverage once again supports the latest capabilities of LLVM instrprof coverage instrumentation. Also fixed a bug in spanview.

view details

push time in 4 days

push eventrichkadel/rust

Rich Kadel

commit sha 5d87d4f68be763e877f3afc101a90f5366499ad8

Addressed PR feedback, and removed temporary -Zexperimental-coverage -Zinstrument-coverage once again supports the latest capabilities of LLVM instrprof coverage instrumentation. Also fixed a bug in spanview.

view details

push time in 4 days

pull request commentrust-lang/rust

Working branch-level code coverage

@tmandry - I made updates based on your feedback, or in some cases, I replied to your comments here in the PR. Please take a look and see if this answers your questions.

Let me know if these answers resolve your feedback/questions.

Thanks!

richkadel

comment created time in 4 days

Pull request review commentrust-lang/rust

Working branch-level code coverage

 impl<'a, 'tcx> Instrumentor<'a, 'tcx> {         data.statements.push(statement);     } -    /// Converts the computed `CoverageRegion`s into `SpanViewable`s.-    fn span_viewables(&self, coverage_regions: &Vec<CoverageRegion>) -> Vec<SpanViewable> {-        let mut span_viewables = Vec::new();-        for coverage_region in coverage_regions {-            span_viewables.push(SpanViewable {-                span: coverage_region.span,-                id: format!("{}", coverage_region.blocks[0].index()),-                tooltip: self.make_tooltip_text(coverage_region),-            });+    fn coverage_spans(&self) -> Vec<CoverageSpan> {+        let mut initial_spans_by_lo =+            Vec::<CoverageSpan>::with_capacity(self.mir_body.basic_blocks().len() * 2);+        for bcb in self.basic_coverage_blocks().iter() {+            for coverage_span in self.extract_spans(bcb) {+                initial_spans_by_lo.push(coverage_span);+            }         }-        span_viewables-    } -    /// A custom tooltip renderer used in a spanview HTML+CSS document used for coverage analysis.-    fn make_tooltip_text(&self, coverage_region: &CoverageRegion) -> String {-        const INCLUDE_COVERAGE_STATEMENTS: bool = false;-        let tcx = self.tcx;-        let source_map = tcx.sess.source_map();-        let mut text = Vec::new();-        for (i, &bb) in coverage_region.blocks.iter().enumerate() {-            if i > 0 {-                text.push("\n".to_owned());+        initial_spans_by_lo.sort_unstable_by(|a, b| {+            if a.span.lo() == b.span.lo() {+                if a.span.hi() == b.span.hi() {+                    if a.is_in_same_bcb(b) {+                        Some(Ordering::Equal)+                    } else {+                        // Sort equal spans by dominator relationship, in reverse order (so+                        // dominators always come after the dominated equal spans). When later+                        // comparing two spans in order, the first will either dominate the second,+                        // or they will have no dominator relationship.+                        self.dominators().rank_partial_cmp(b.bcb_leader_bb, a.bcb_leader_bb)+                    }+                } else {+                    // Sort hi() in reverse order so shorter spans are attempted after longer spans.+                    // This guarantees that, if a `prev` span overlaps, and is not equal to, a+                    // `curr` span, the prev span either extends further left of the curr span, or+                    // they start at the same position and the prev span extends further right of+                    // the end of the curr span.+                    b.span.hi().partial_cmp(&a.span.hi())+                }+            } else {+                a.span.lo().partial_cmp(&b.span.lo())             }-            text.push(format!("{:?}: {}:", bb, &source_map.span_to_string(coverage_region.span)));-            let data = &self.mir_body.basic_blocks()[bb];-            for statement in &data.statements {-                let statement_string = match statement.kind {-                    StatementKind::Coverage(box ref coverage) => match coverage.kind {-                        CoverageKind::Counter { id, .. } => {-                            if !INCLUDE_COVERAGE_STATEMENTS {-                                continue;-                            }-                            format!("increment counter #{}", id.index())-                        }-                        CoverageKind::Expression { id, lhs, op, rhs } => {-                            if !INCLUDE_COVERAGE_STATEMENTS {-                                continue;-                            }-                            format!(-                                "expression #{} = {} {} {}",-                                id.index(),-                                lhs.index(),-                                if op == Op::Add { "+" } else { "-" },-                                rhs.index()-                            )-                        }-                        CoverageKind::Unreachable => {-                            if !INCLUDE_COVERAGE_STATEMENTS {-                                continue;-                            }-                            String::from("unreachable")+            .unwrap()+        });++        let mut coverage_spans = Vec::with_capacity(initial_spans_by_lo.len());+        let mut pending_dup_spans = Vec::<CoverageSpan>::new();++        let mut drain = initial_spans_by_lo.drain(..);+        let mut prev = drain.next().expect("at least one span");+        for mut curr in drain {+            if let Some(pending) = pending_dup_spans.last() {+                debug!(+                    "    SAME spans, but pending_dup_spans are NOT THE SAME, so BCBs matched on \+                    previous iteration"+                );+                if pending.span != prev.span {+                    if pending.span.hi() <= curr.span.lo() {+                        for pending in pending_dup_spans.drain(..) {+                            debug!("    ...adding at least one pending={:?}", pending);+                            coverage_spans.push(pending);                         }-                    },-                    _ => format!("{:?}", statement),-                };-                let source_range = source_range_no_file(tcx, &statement.source_info.span);-                text.push(format!(-                    "\n{}{}: {}: {}",-                    TOOLTIP_INDENT,-                    source_range,-                    statement_kind_name(statement),-                    statement_string-                ));+                    } else {+                        pending_dup_spans.clear();+                    }+                }             }-            let term = data.terminator();-            let source_range = source_range_no_file(tcx, &term.source_info.span);-            text.push(format!(-                "\n{}{}: {}: {:?}",-                TOOLTIP_INDENT,-                source_range,-                terminator_kind_name(term),-                term.kind-            ));+            debug!("FOR curr={:?}", curr);+            if curr.is_in_same_bcb(&prev) {+                debug!("  same bcb, merge with prev={:?}", prev);+                curr.merge_from(prev);+            } else if prev.span.hi() <= curr.span.lo() {+                // different and disjoint spans, so keep both+                debug!(+                    "  different bcbs and disjoint spans, so keep curr for next iter, and add \+                    prev={:?}",+                    prev+                );+                coverage_spans.push(prev);+            } else if prev.span == curr.span {+                // equal coverage spans are ordered by dominators before dominated (if any)+                debug_assert!(!prev.is_dominated_by(&curr, self.dominators()));+                if curr.is_dominated_by(&prev, self.dominators()) {+                    debug!(+                        "  different bcbs but SAME spans, and prev dominates curr, so only count \+                        curr, by merging prev={:?}",+                        prev+                    );+                    curr.merge_from(prev);+                } else {+                    debug!(+                        "  different bcbs but SAME spans, and neither dominates, so keep curr for \+                        next iter, and add prev={:?}",+                        prev+                    );+                    pending_dup_spans.push(prev);+                }+            } else {+                debug!(+                    "  different bcbs, overlapping spans, so ignore/drop pending and only add prev \+                    if it has statements that end before curr={:?}",+                    prev+                );+                pending_dup_spans.clear();+                prev.cutoff_statements_at(curr.span.lo());+                if !prev.coverage_statements.is_empty() {+                    debug!("  ... adding modified prev={:?}", prev);+                    coverage_spans.push(prev);+                } else {+                    debug!("  ... no non-overlapping statements to add");+                }+            }++            prev = curr;+        }+        debug!("    AT END, adding last prev={:?}", prev);+        for pending in pending_dup_spans.drain(..) {+            debug!("    ...adding at least one pending={:?}", pending);+            coverage_spans.push(pending);+        }+        coverage_spans.push(prev);++        // FIXME(richkadel): Replace some counters with expressions if they can be calculated based+        // on branching. (For example, one branch of a SwitchInt can be computed from the counter+        // for the CoverageSpan just prior to the SwitchInt minus the sum of the counters of all+        // other branches).+        coverage_spans+    }++    /// Converts the computed `BasicCoverageBlock`s into `SpanViewable`s.+    fn span_viewables(&self, coverage_spans: &Vec<CoverageSpan>) -> Vec<SpanViewable> {+        let tcx = self.tcx;+        let mir_body = &self.mir_body;+        let mut span_viewables = Vec::new();+        for coverage_span in coverage_spans {+            let bcb = self.bcb_from_coverage_span(coverage_span);+            let CoverageSpan { span, bcb_leader_bb: bb, coverage_statements } = coverage_span;+            let id = bcb.id();+            let tooltip = coverage_statements+                .iter()+                .map(|covstmt| covstmt.format(tcx, mir_body))+                .collect::<Vec<_>>()+                .join("\n");+            span_viewables.push(SpanViewable { bb: *bb, span: *span, id, tooltip });+        }+        span_viewables+    }++    #[inline(always)]+    fn bcb_from_coverage_span(&self, coverage_span: &CoverageSpan) -> &BasicCoverageBlock {+        &self.basic_coverage_blocks()[coverage_span.bcb_leader_bb]+    }++    #[inline(always)]+    fn body_span(&self) -> Span {+        self.hir_body.value.span+    }++    // Return the de-duplicated spans of a block or blocks. Multiple blocks represent a segment of+    // the MIR control-flow-graph that have no branches. For example: A; A -> B; A -> B -> C; and+    // and so on.

Comments updated

richkadel

comment created time in 4 days

PullRequestReviewEvent

Pull request review commentrust-lang/rust

Working branch-level code coverage

 impl<'a, 'tcx> Instrumentor<'a, 'tcx> {         data.statements.push(statement);     } -    /// Converts the computed `CoverageRegion`s into `SpanViewable`s.-    fn span_viewables(&self, coverage_regions: &Vec<CoverageRegion>) -> Vec<SpanViewable> {-        let mut span_viewables = Vec::new();-        for coverage_region in coverage_regions {-            span_viewables.push(SpanViewable {-                span: coverage_region.span,-                id: format!("{}", coverage_region.blocks[0].index()),-                tooltip: self.make_tooltip_text(coverage_region),-            });+    fn coverage_spans(&self) -> Vec<CoverageSpan> {+        let mut initial_spans_by_lo =+            Vec::<CoverageSpan>::with_capacity(self.mir_body.basic_blocks().len() * 2);+        for bcb in self.basic_coverage_blocks().iter() {+            for coverage_span in self.extract_spans(bcb) {+                initial_spans_by_lo.push(coverage_span);+            }         }-        span_viewables-    } -    /// A custom tooltip renderer used in a spanview HTML+CSS document used for coverage analysis.-    fn make_tooltip_text(&self, coverage_region: &CoverageRegion) -> String {-        const INCLUDE_COVERAGE_STATEMENTS: bool = false;-        let tcx = self.tcx;-        let source_map = tcx.sess.source_map();-        let mut text = Vec::new();-        for (i, &bb) in coverage_region.blocks.iter().enumerate() {-            if i > 0 {-                text.push("\n".to_owned());+        initial_spans_by_lo.sort_unstable_by(|a, b| {+            if a.span.lo() == b.span.lo() {+                if a.span.hi() == b.span.hi() {+                    if a.is_in_same_bcb(b) {+                        Some(Ordering::Equal)+                    } else {+                        // Sort equal spans by dominator relationship, in reverse order (so+                        // dominators always come after the dominated equal spans). When later+                        // comparing two spans in order, the first will either dominate the second,+                        // or they will have no dominator relationship.+                        self.dominators().rank_partial_cmp(b.bcb_leader_bb, a.bcb_leader_bb)+                    }+                } else {+                    // Sort hi() in reverse order so shorter spans are attempted after longer spans.+                    // This guarantees that, if a `prev` span overlaps, and is not equal to, a+                    // `curr` span, the prev span either extends further left of the curr span, or+                    // they start at the same position and the prev span extends further right of+                    // the end of the curr span.+                    b.span.hi().partial_cmp(&a.span.hi())+                }+            } else {+                a.span.lo().partial_cmp(&b.span.lo())             }-            text.push(format!("{:?}: {}:", bb, &source_map.span_to_string(coverage_region.span)));-            let data = &self.mir_body.basic_blocks()[bb];-            for statement in &data.statements {-                let statement_string = match statement.kind {-                    StatementKind::Coverage(box ref coverage) => match coverage.kind {-                        CoverageKind::Counter { id, .. } => {-                            if !INCLUDE_COVERAGE_STATEMENTS {-                                continue;-                            }-                            format!("increment counter #{}", id.index())-                        }-                        CoverageKind::Expression { id, lhs, op, rhs } => {-                            if !INCLUDE_COVERAGE_STATEMENTS {-                                continue;-                            }-                            format!(-                                "expression #{} = {} {} {}",-                                id.index(),-                                lhs.index(),-                                if op == Op::Add { "+" } else { "-" },-                                rhs.index()-                            )-                        }-                        CoverageKind::Unreachable => {-                            if !INCLUDE_COVERAGE_STATEMENTS {-                                continue;-                            }-                            String::from("unreachable")+            .unwrap()+        });++        let mut coverage_spans = Vec::with_capacity(initial_spans_by_lo.len());+        let mut pending_dup_spans = Vec::<CoverageSpan>::new();++        let mut drain = initial_spans_by_lo.drain(..);+        let mut prev = drain.next().expect("at least one span");+        for mut curr in drain {

Comments added

richkadel

comment created time in 4 days

PullRequestReviewEvent

Pull request review commentrust-lang/rust

Working branch-level code coverage

 impl<'a, 'tcx> Instrumentor<'a, 'tcx> {         // progress to identify the correct code region spans and associated counters to generate         // accurate Rust coverage reports. -        let block_span = |data: &BasicBlockData<'tcx>| {-            // The default span will be the `Terminator` span; but until we have a smarter solution,-            // the coverage region also incorporates at least the statements in this BasicBlock as-            // well. Extend the span to encompass all, if possible.-            // FIXME(richkadel): Assuming the terminator's span is already known to be contained in `body_span`.-            let mut span = data.terminator().source_info.span;-            // FIXME(richkadel): It's looking unlikely that we should compute a span from MIR-            // spans, but if we do keep something like this logic, we will need a smarter way-            // to combine `Statement`s and/or `Terminator`s with `Span`s from different-            // files.-            for statement_span in data.statements.iter().map(|statement| statement.source_info.span)-            {-                // Only combine Spans from the function's body_span.-                if body_span.contains(statement_span) {-                    span = span.to(statement_span);-                }-            }-            span-        };+        self.dominators.replace(mir_body.dominators());+        self.basic_coverage_blocks.replace(BasicCoverageBlocks::from_mir(mir_body)); -        // Traverse the CFG but ignore anything following an `unwind`-        let cfg_without_unwind = ShortCircuitPreorder::new(mir_body, |term_kind| {-            let mut successors = term_kind.successors();-            match &term_kind {-                // SwitchInt successors are never unwind, and all of them should be traversed-                TerminatorKind::SwitchInt { .. } => successors,-                // For all other kinds, return only the first successor, if any, and ignore unwinds-                _ => successors.next().into_iter().chain(&[]),-            }-        });--        let mut coverage_regions = Vec::with_capacity(cfg_without_unwind.size_hint().0);-        for (bb, data) in cfg_without_unwind {-            if !body_span.contains(data.terminator().source_info.span) {-                continue;-            }--            // FIXME(richkadel): Regions will soon contain multiple blocks.-            let mut blocks = Vec::new();-            blocks.push(bb);-            let span = block_span(data);-            coverage_regions.push(CoverageRegion { span, blocks });-        }+        let coverage_spans = self.coverage_spans();          let span_viewables = if pretty::dump_enabled(tcx, self.pass_name, def_id) {-            Some(self.span_viewables(&coverage_regions))+            Some(self.span_viewables(&coverage_spans))         } else {             None         }; -        // Inject counters for the selected spans-        for CoverageRegion { span, blocks } in coverage_regions {-            debug!(-                "Injecting counter at: {:?}:\n{}\n==========",-                span,-                tcx.sess.source_map().span_to_snippet(span).expect("Error getting source for span"),-            );-            let counter = self.make_counter();-            self.inject_statement(counter, span, blocks[0]);+        let mut bb_counters = IndexVec::from_elem_n(None, mir_body.basic_blocks().len());+        for CoverageSpan { span, bcb_leader_bb: bb, .. } in coverage_spans {+            if let Some(&counter_operand) = bb_counters[bb].as_ref() {+                let expression =+                    self.make_expression(counter_operand, Op::Add, ExpressionOperandId::ZERO);+                debug!(+                    "Injecting counter expression {:?} at: {:?}:\n{}\n==========",+                    expression,+                    span,+                    source_map.span_to_snippet(span).expect("Error getting source for span"),+                );+                self.inject_statement(expression, span, bb);+            } else {+                let counter = self.make_counter();+                debug!(+                    "Injecting counter {:?} at: {:?}:\n{}\n==========",+                    counter,+                    span,+                    source_map.span_to_snippet(span).expect("Error getting source for span"),+                );+                let counter_operand = counter.as_operand_id();+                bb_counters[bb] = Some(counter_operand);+                self.inject_statement(counter, span, bb);+            }

Comments added

richkadel

comment created time in 4 days

PullRequestReviewEvent

Pull request review commentrust-lang/rust

Working branch-level code coverage

 impl<'tcx> MirPass<'tcx> for InstrumentCoverage {     ) {         // If the InstrumentCoverage pass is called on promoted MIRs, skip them.         // See: https://github.com/rust-lang/rust/pull/73011#discussion_r438317601-        if mir_source.promoted.is_none() {-            Instrumentor::new(&self.name(), tcx, mir_source, mir_body).inject_counters();+        if mir_source.promoted.is_some() {+            trace!(+                "InstrumentCoverage skipped for {:?} (already promoted for Miri evaluation)",+                mir_source.def_id()+            );+            return;         }++        let hir_id = tcx.hir().local_def_id_to_hir_id(mir_source.def_id().expect_local());+        let is_fn_like = FnLikeNode::from_node(tcx.hir().get(hir_id)).is_some();++        // Only instrument functions, methods, and closures (not constants since they are evaluated+        // at compile time by Miri).+        if !is_fn_like {+            trace!("InstrumentCoverage skipped for {:?} (not an FnLikeNode)", mir_source.def_id());+            return;+        }+        // FIXME(richkadel): By comparison, the MIR pass `ConstProp` includes associated constants,+        // with functions, methods, and closures. I assume Miri is used for associated constants as+        // well. If not, we may need to include them here too.++        trace!("InstrumentCoverage starting for {:?}", mir_source.def_id());+        Instrumentor::new(&self.name(), tcx, mir_source, mir_body).inject_counters();+        trace!("InstrumentCoverage starting for {:?}", mir_source.def_id());     } } -#[derive(Clone)]-struct CoverageRegion {-    pub span: Span,+/// A BasicCoverageBlock (BCB) represents the maximal-length sequence of CFG (MIR) BasicBlocks+/// without conditional branches.+#[derive(Debug, Clone)]+struct BasicCoverageBlock {     pub blocks: Vec<BasicBlock>, } +impl BasicCoverageBlock {+    pub fn leader_bb(&self) -> BasicBlock {+        self.blocks[0]+    }++    pub fn id(&self) -> String {+        format!(+            "@{}",+            self.blocks+                .iter()+                .map(|bb| bb.index().to_string())+                .collect::<Vec<_>>()+                .join(ID_SEPARATOR)+        )+    }+}++struct BasicCoverageBlocks {+    vec: IndexVec<BasicBlock, Option<BasicCoverageBlock>>,+}++impl BasicCoverageBlocks {+    pub fn from_mir(mir_body: &mir::Body<'tcx>) -> Self {+        let mut basic_coverage_blocks =+            BasicCoverageBlocks { vec: IndexVec::from_elem_n(None, mir_body.basic_blocks().len()) };+        basic_coverage_blocks.extract_from_mir(mir_body);+        basic_coverage_blocks+    }++    pub fn iter(&self) -> impl Iterator<Item = &BasicCoverageBlock> {+        self.vec.iter().filter_map(|option| option.as_ref())+    }++    fn extract_from_mir(&mut self, mir_body: &mir::Body<'tcx>) {+        // Traverse the CFG but ignore anything following an `unwind`+        let cfg_without_unwind = ShortCircuitPreorder::new(mir_body, |term_kind| {+            let mut successors = term_kind.successors();+            match &term_kind {+                // SwitchInt successors are never unwind, and all of them should be traversed+                TerminatorKind::SwitchInt { .. } => successors,+                // For all other kinds, return only the first successor, if any, and ignore unwinds+                _ => successors.next().into_iter().chain(&[]),+            }+        });++        let mut blocks = Vec::new();+        for (bb, data) in cfg_without_unwind+            .filter(|(_, data)| data.terminator().kind != TerminatorKind::Unreachable)+        {+            if let Some(last) = blocks.last() {+                let predecessors = &mir_body.predecessors()[bb];+                if predecessors.len() > 1 || !predecessors.contains(last) {+                    self.add_basic_coverage_block(blocks.split_off(0));+                    debug!(+                        "  because {}",+                        if predecessors.len() > 1 {+                            "predecessors.len() > 1".to_owned()+                        } else {+                            format!("bb {} is not in precessors: {:?}", bb.index(), predecessors)+                        }+                    );+                }+            }+            blocks.push(bb);++            let term = data.terminator();++            // inject counters at branch blocks only

Comments added

richkadel

comment created time in 4 days

PullRequestReviewEvent

Pull request review commentrust-lang/rust

Working branch-level code coverage

 impl<'tcx> MirPass<'tcx> for InstrumentCoverage {     ) {         // If the InstrumentCoverage pass is called on promoted MIRs, skip them.         // See: https://github.com/rust-lang/rust/pull/73011#discussion_r438317601-        if mir_source.promoted.is_none() {-            Instrumentor::new(&self.name(), tcx, mir_source, mir_body).inject_counters();+        if mir_source.promoted.is_some() {+            trace!(+                "InstrumentCoverage skipped for {:?} (already promoted for Miri evaluation)",+                mir_source.def_id()+            );+            return;         }++        let hir_id = tcx.hir().local_def_id_to_hir_id(mir_source.def_id().expect_local());+        let is_fn_like = FnLikeNode::from_node(tcx.hir().get(hir_id)).is_some();++        // Only instrument functions, methods, and closures (not constants since they are evaluated+        // at compile time by Miri).+        if !is_fn_like {+            trace!("InstrumentCoverage skipped for {:?} (not an FnLikeNode)", mir_source.def_id());+            return;+        }+        // FIXME(richkadel): By comparison, the MIR pass `ConstProp` includes associated constants,+        // with functions, methods, and closures. I assume Miri is used for associated constants as+        // well. If not, we may need to include them here too.++        trace!("InstrumentCoverage starting for {:?}", mir_source.def_id());+        Instrumentor::new(&self.name(), tcx, mir_source, mir_body).inject_counters();+        trace!("InstrumentCoverage starting for {:?}", mir_source.def_id());     } } -#[derive(Clone)]-struct CoverageRegion {-    pub span: Span,+/// A BasicCoverageBlock (BCB) represents the maximal-length sequence of CFG (MIR) BasicBlocks+/// without conditional branches.

Answered in updated comments.

richkadel

comment created time in 4 days

PullRequestReviewEvent

Pull request review commentrust-lang/rust

Working branch-level code coverage

 impl<'tcx> MirPass<'tcx> for InstrumentCoverage {     ) {         // If the InstrumentCoverage pass is called on promoted MIRs, skip them.         // See: https://github.com/rust-lang/rust/pull/73011#discussion_r438317601-        if mir_source.promoted.is_none() {-            Instrumentor::new(&self.name(), tcx, mir_source, mir_body).inject_counters();+        if mir_source.promoted.is_some() {+            trace!(+                "InstrumentCoverage skipped for {:?} (already promoted for Miri evaluation)",+                mir_source.def_id()+            );+            return;         }++        let hir_id = tcx.hir().local_def_id_to_hir_id(mir_source.def_id().expect_local());+        let is_fn_like = FnLikeNode::from_node(tcx.hir().get(hir_id)).is_some();++        // Only instrument functions, methods, and closures (not constants since they are evaluated+        // at compile time by Miri).

done

richkadel

comment created time in 4 days

PullRequestReviewEvent

Pull request review commentrust-lang/rust

Working branch-level code coverage

 rustc_index::newtype_index! {     } } +impl ExpressionOperandId {+    pub const ZERO: Self = Self::from_u32(0);

done

richkadel

comment created time in 4 days

PullRequestReviewEvent

push eventrichkadel/rust

Rich Kadel

commit sha 3933202d6ad7551b97bf5e63b0077f411ecbf662

Addressed PR feedback, and removed temporary -Zexperimental-coverage -Zinstrument-coverage once again supports the latest capabilities of LLVM instrprof coverage instrumentation.

view details

push time in 4 days

Pull request review commentrust-lang/rust

Working branch-level code coverage

 impl<'a, 'tcx> Instrumentor<'a, 'tcx> {         data.statements.push(statement);     } -    /// Converts the computed `CoverageRegion`s into `SpanViewable`s.-    fn span_viewables(&self, coverage_regions: &Vec<CoverageRegion>) -> Vec<SpanViewable> {-        let mut span_viewables = Vec::new();-        for coverage_region in coverage_regions {-            span_viewables.push(SpanViewable {-                span: coverage_region.span,-                id: format!("{}", coverage_region.blocks[0].index()),-                tooltip: self.make_tooltip_text(coverage_region),-            });+    fn coverage_spans(&self) -> Vec<CoverageSpan> {+        let mut initial_spans_by_lo =+            Vec::<CoverageSpan>::with_capacity(self.mir_body.basic_blocks().len() * 2);+        for bcb in self.basic_coverage_blocks().iter() {+            for coverage_span in self.extract_spans(bcb) {+                initial_spans_by_lo.push(coverage_span);+            }         }-        span_viewables-    } -    /// A custom tooltip renderer used in a spanview HTML+CSS document used for coverage analysis.-    fn make_tooltip_text(&self, coverage_region: &CoverageRegion) -> String {-        const INCLUDE_COVERAGE_STATEMENTS: bool = false;-        let tcx = self.tcx;-        let source_map = tcx.sess.source_map();-        let mut text = Vec::new();-        for (i, &bb) in coverage_region.blocks.iter().enumerate() {-            if i > 0 {-                text.push("\n".to_owned());+        initial_spans_by_lo.sort_unstable_by(|a, b| {+            if a.span.lo() == b.span.lo() {+                if a.span.hi() == b.span.hi() {+                    if a.is_in_same_bcb(b) {+                        Some(Ordering::Equal)+                    } else {+                        // Sort equal spans by dominator relationship, in reverse order (so+                        // dominators always come after the dominated equal spans). When later+                        // comparing two spans in order, the first will either dominate the second,+                        // or they will have no dominator relationship.+                        self.dominators().rank_partial_cmp(b.bcb_leader_bb, a.bcb_leader_bb)+                    }+                } else {+                    // Sort hi() in reverse order so shorter spans are attempted after longer spans.+                    // This guarantees that, if a `prev` span overlaps, and is not equal to, a+                    // `curr` span, the prev span either extends further left of the curr span, or+                    // they start at the same position and the prev span extends further right of+                    // the end of the curr span.+                    b.span.hi().partial_cmp(&a.span.hi())+                }+            } else {+                a.span.lo().partial_cmp(&b.span.lo())             }-            text.push(format!("{:?}: {}:", bb, &source_map.span_to_string(coverage_region.span)));-            let data = &self.mir_body.basic_blocks()[bb];-            for statement in &data.statements {-                let statement_string = match statement.kind {-                    StatementKind::Coverage(box ref coverage) => match coverage.kind {-                        CoverageKind::Counter { id, .. } => {-                            if !INCLUDE_COVERAGE_STATEMENTS {-                                continue;-                            }-                            format!("increment counter #{}", id.index())-                        }-                        CoverageKind::Expression { id, lhs, op, rhs } => {-                            if !INCLUDE_COVERAGE_STATEMENTS {-                                continue;-                            }-                            format!(-                                "expression #{} = {} {} {}",-                                id.index(),-                                lhs.index(),-                                if op == Op::Add { "+" } else { "-" },-                                rhs.index()-                            )-                        }-                        CoverageKind::Unreachable => {-                            if !INCLUDE_COVERAGE_STATEMENTS {-                                continue;-                            }-                            String::from("unreachable")+            .unwrap()+        });++        let mut coverage_spans = Vec::with_capacity(initial_spans_by_lo.len());+        let mut pending_dup_spans = Vec::<CoverageSpan>::new();++        let mut drain = initial_spans_by_lo.drain(..);+        let mut prev = drain.next().expect("at least one span");+        for mut curr in drain {+            if let Some(pending) = pending_dup_spans.last() {+                debug!(+                    "    SAME spans, but pending_dup_spans are NOT THE SAME, so BCBs matched on \+                    previous iteration"+                );+                if pending.span != prev.span {+                    if pending.span.hi() <= curr.span.lo() {+                        for pending in pending_dup_spans.drain(..) {+                            debug!("    ...adding at least one pending={:?}", pending);+                            coverage_spans.push(pending);                         }-                    },-                    _ => format!("{:?}", statement),-                };-                let source_range = source_range_no_file(tcx, &statement.source_info.span);-                text.push(format!(-                    "\n{}{}: {}: {}",-                    TOOLTIP_INDENT,-                    source_range,-                    statement_kind_name(statement),-                    statement_string-                ));+                    } else {+                        pending_dup_spans.clear();+                    }+                }             }-            let term = data.terminator();-            let source_range = source_range_no_file(tcx, &term.source_info.span);-            text.push(format!(-                "\n{}{}: {}: {:?}",-                TOOLTIP_INDENT,-                source_range,-                terminator_kind_name(term),-                term.kind-            ));+            debug!("FOR curr={:?}", curr);+            if curr.is_in_same_bcb(&prev) {+                debug!("  same bcb, merge with prev={:?}", prev);+                curr.merge_from(prev);+            } else if prev.span.hi() <= curr.span.lo() {+                // different and disjoint spans, so keep both+                debug!(+                    "  different bcbs and disjoint spans, so keep curr for next iter, and add \+                    prev={:?}",+                    prev+                );+                coverage_spans.push(prev);+            } else if prev.span == curr.span {+                // equal coverage spans are ordered by dominators before dominated (if any)+                debug_assert!(!prev.is_dominated_by(&curr, self.dominators()));+                if curr.is_dominated_by(&prev, self.dominators()) {+                    debug!(+                        "  different bcbs but SAME spans, and prev dominates curr, so only count \+                        curr, by merging prev={:?}",+                        prev+                    );+                    curr.merge_from(prev);+                } else {+                    debug!(+                        "  different bcbs but SAME spans, and neither dominates, so keep curr for \+                        next iter, and add prev={:?}",+                        prev+                    );+                    pending_dup_spans.push(prev);+                }+            } else {+                debug!(+                    "  different bcbs, overlapping spans, so ignore/drop pending and only add prev \+                    if it has statements that end before curr={:?}",+                    prev+                );+                pending_dup_spans.clear();+                prev.cutoff_statements_at(curr.span.lo());+                if !prev.coverage_statements.is_empty() {+                    debug!("  ... adding modified prev={:?}", prev);+                    coverage_spans.push(prev);+                } else {+                    debug!("  ... no non-overlapping statements to add");+                }+            }++            prev = curr;+        }+        debug!("    AT END, adding last prev={:?}", prev);+        for pending in pending_dup_spans.drain(..) {+            debug!("    ...adding at least one pending={:?}", pending);+            coverage_spans.push(pending);+        }+        coverage_spans.push(prev);++        // FIXME(richkadel): Replace some counters with expressions if they can be calculated based+        // on branching. (For example, one branch of a SwitchInt can be computed from the counter+        // for the CoverageSpan just prior to the SwitchInt minus the sum of the counters of all+        // other branches).+        coverage_spans+    }++    /// Converts the computed `BasicCoverageBlock`s into `SpanViewable`s.+    fn span_viewables(&self, coverage_spans: &Vec<CoverageSpan>) -> Vec<SpanViewable> {+        let tcx = self.tcx;+        let mir_body = &self.mir_body;+        let mut span_viewables = Vec::new();+        for coverage_span in coverage_spans {+            let bcb = self.bcb_from_coverage_span(coverage_span);+            let CoverageSpan { span, bcb_leader_bb: bb, coverage_statements } = coverage_span;+            let id = bcb.id();+            let tooltip = coverage_statements+                .iter()+                .map(|covstmt| covstmt.format(tcx, mir_body))+                .collect::<Vec<_>>()+                .join("\n");+            span_viewables.push(SpanViewable { bb: *bb, span: *span, id, tooltip });+        }+        span_viewables+    }++    #[inline(always)]+    fn bcb_from_coverage_span(&self, coverage_span: &CoverageSpan) -> &BasicCoverageBlock {+        &self.basic_coverage_blocks()[coverage_span.bcb_leader_bb]+    }++    #[inline(always)]+    fn body_span(&self) -> Span {+        self.hir_body.value.span+    }++    // Return the de-duplicated spans of a block or blocks. Multiple blocks represent a segment of+    // the MIR control-flow-graph that have no branches. For example: A; A -> B; A -> B -> C; and+    // and so on.

Great catch. Yep. The comment is outdated. I'm updating it.

richkadel

comment created time in 4 days

PullRequestReviewEvent

Pull request review commentrust-lang/rust

Working branch-level code coverage

 impl<'tcx> MirPass<'tcx> for InstrumentCoverage {     ) {         // If the InstrumentCoverage pass is called on promoted MIRs, skip them.         // See: https://github.com/rust-lang/rust/pull/73011#discussion_r438317601-        if mir_source.promoted.is_none() {-            Instrumentor::new(&self.name(), tcx, mir_source, mir_body).inject_counters();+        if mir_source.promoted.is_some() {+            trace!(+                "InstrumentCoverage skipped for {:?} (already promoted for Miri evaluation)",+                mir_source.def_id()+            );+            return;         }++        let hir_id = tcx.hir().local_def_id_to_hir_id(mir_source.def_id().expect_local());+        let is_fn_like = FnLikeNode::from_node(tcx.hir().get(hir_id)).is_some();++        // Only instrument functions, methods, and closures (not constants since they are evaluated+        // at compile time by Miri).+        if !is_fn_like {+            trace!("InstrumentCoverage skipped for {:?} (not an FnLikeNode)", mir_source.def_id());+            return;+        }+        // FIXME(richkadel): By comparison, the MIR pass `ConstProp` includes associated constants,+        // with functions, methods, and closures. I assume Miri is used for associated constants as+        // well. If not, we may need to include them here too.++        trace!("InstrumentCoverage starting for {:?}", mir_source.def_id());+        Instrumentor::new(&self.name(), tcx, mir_source, mir_body).inject_counters();+        trace!("InstrumentCoverage starting for {:?}", mir_source.def_id());     } } -#[derive(Clone)]-struct CoverageRegion {-    pub span: Span,+/// A BasicCoverageBlock (BCB) represents the maximal-length sequence of CFG (MIR) BasicBlocks+/// without conditional branches.+#[derive(Debug, Clone)]+struct BasicCoverageBlock {     pub blocks: Vec<BasicBlock>, } +impl BasicCoverageBlock {+    pub fn leader_bb(&self) -> BasicBlock {+        self.blocks[0]+    }++    pub fn id(&self) -> String {+        format!(+            "@{}",+            self.blocks+                .iter()+                .map(|bb| bb.index().to_string())+                .collect::<Vec<_>>()+                .join(ID_SEPARATOR)+        )+    }+}++struct BasicCoverageBlocks {+    vec: IndexVec<BasicBlock, Option<BasicCoverageBlock>>,+}++impl BasicCoverageBlocks {+    pub fn from_mir(mir_body: &mir::Body<'tcx>) -> Self {+        let mut basic_coverage_blocks =+            BasicCoverageBlocks { vec: IndexVec::from_elem_n(None, mir_body.basic_blocks().len()) };+        basic_coverage_blocks.extract_from_mir(mir_body);+        basic_coverage_blocks+    }++    pub fn iter(&self) -> impl Iterator<Item = &BasicCoverageBlock> {+        self.vec.iter().filter_map(|option| option.as_ref())+    }++    fn extract_from_mir(&mut self, mir_body: &mir::Body<'tcx>) {+        // Traverse the CFG but ignore anything following an `unwind`+        let cfg_without_unwind = ShortCircuitPreorder::new(mir_body, |term_kind| {+            let mut successors = term_kind.successors();+            match &term_kind {+                // SwitchInt successors are never unwind, and all of them should be traversed+                TerminatorKind::SwitchInt { .. } => successors,+                // For all other kinds, return only the first successor, if any, and ignore unwinds+                _ => successors.next().into_iter().chain(&[]),+            }+        });++        let mut blocks = Vec::new();+        for (bb, data) in cfg_without_unwind+            .filter(|(_, data)| data.terminator().kind != TerminatorKind::Unreachable)+        {+            if let Some(last) = blocks.last() {+                let predecessors = &mir_body.predecessors()[bb];+                if predecessors.len() > 1 || !predecessors.contains(last) {+                    self.add_basic_coverage_block(blocks.split_off(0));+                    debug!(+                        "  because {}",+                        if predecessors.len() > 1 {+                            "predecessors.len() > 1".to_owned()+                        } else {+                            format!("bb {} is not in precessors: {:?}", bb.index(), predecessors)+                        }+                    );+                }+            }+            blocks.push(bb);++            let term = data.terminator();++            // inject counters at branch blocks only

It's a good question, but yes. (In fact, I tested the theory by removing the terminator check, just in case I was wrong.)

I'm adding some additional comments on this.

The predecessors rule starts a new BasicCoverageBlock (and closes the non-branching BasicBlock sequence captured in the blocks vector as a separate BasicCoverageBlock because the current BasicBlock has multiple incoming edges.

The terminator check closes a sequence of BasicBlocks with a BasicBlock that has more than one outgoing edge OR a BasicBlock that exits the function.

I'm pretty sure both checks are required.

For example:

fn somefun() {
    println!("entered of function");
    if some_condition {
        // one incoming edge, but these statements have to be in their own BasicCoverageBlock
        println!("some_condition is true");
    }
    // multiple incoming edges here, start a new BasicCoverageBlock
}

The SwitchInt terminator has to close the current BasicCoverageBlock, which includes the first println!() and the SwitchInt (if condition check), because the next block in the traversal (in this case, the content of the true branch) must start a new BasicCoverage block.

richkadel

comment created time in 4 days

PullRequestReviewEvent

Pull request review commentrust-lang/rust

Working branch-level code coverage

 impl<'tcx> MirPass<'tcx> for InstrumentCoverage {     ) {         // If the InstrumentCoverage pass is called on promoted MIRs, skip them.         // See: https://github.com/rust-lang/rust/pull/73011#discussion_r438317601-        if mir_source.promoted.is_none() {-            Instrumentor::new(&self.name(), tcx, mir_source, mir_body).inject_counters();+        if mir_source.promoted.is_some() {+            trace!(+                "InstrumentCoverage skipped for {:?} (already promoted for Miri evaluation)",+                mir_source.def_id()+            );+            return;         }++        let hir_id = tcx.hir().local_def_id_to_hir_id(mir_source.def_id().expect_local());+        let is_fn_like = FnLikeNode::from_node(tcx.hir().get(hir_id)).is_some();++        // Only instrument functions, methods, and closures (not constants since they are evaluated+        // at compile time by Miri).+        if !is_fn_like {+            trace!("InstrumentCoverage skipped for {:?} (not an FnLikeNode)", mir_source.def_id());+            return;+        }+        // FIXME(richkadel): By comparison, the MIR pass `ConstProp` includes associated constants,+        // with functions, methods, and closures. I assume Miri is used for associated constants as+        // well. If not, we may need to include them here too.++        trace!("InstrumentCoverage starting for {:?}", mir_source.def_id());+        Instrumentor::new(&self.name(), tcx, mir_source, mir_body).inject_counters();+        trace!("InstrumentCoverage starting for {:?}", mir_source.def_id());     } } -#[derive(Clone)]-struct CoverageRegion {-    pub span: Span,+/// A BasicCoverageBlock (BCB) represents the maximal-length sequence of CFG (MIR) BasicBlocks+/// without conditional branches.+#[derive(Debug, Clone)]+struct BasicCoverageBlock {     pub blocks: Vec<BasicBlock>, } +impl BasicCoverageBlock {+    pub fn leader_bb(&self) -> BasicBlock {+        self.blocks[0]+    }++    pub fn id(&self) -> String {+        format!(+            "@{}",+            self.blocks+                .iter()+                .map(|bb| bb.index().to_string())+                .collect::<Vec<_>>()+                .join(ID_SEPARATOR)+        )+    }+}++struct BasicCoverageBlocks {+    vec: IndexVec<BasicBlock, Option<BasicCoverageBlock>>,+}++impl BasicCoverageBlocks {+    pub fn from_mir(mir_body: &mir::Body<'tcx>) -> Self {+        let mut basic_coverage_blocks =+            BasicCoverageBlocks { vec: IndexVec::from_elem_n(None, mir_body.basic_blocks().len()) };+        basic_coverage_blocks.extract_from_mir(mir_body);+        basic_coverage_blocks+    }++    pub fn iter(&self) -> impl Iterator<Item = &BasicCoverageBlock> {+        self.vec.iter().filter_map(|option| option.as_ref())+    }++    fn extract_from_mir(&mut self, mir_body: &mir::Body<'tcx>) {+        // Traverse the CFG but ignore anything following an `unwind`+        let cfg_without_unwind = ShortCircuitPreorder::new(mir_body, |term_kind| {+            let mut successors = term_kind.successors();+            match &term_kind {+                // SwitchInt successors are never unwind, and all of them should be traversed+                TerminatorKind::SwitchInt { .. } => successors,+                // For all other kinds, return only the first successor, if any, and ignore unwinds+                _ => successors.next().into_iter().chain(&[]),+            }+        });++        let mut blocks = Vec::new();+        for (bb, data) in cfg_without_unwind+            .filter(|(_, data)| data.terminator().kind != TerminatorKind::Unreachable)

If we were to insert counters in Unreachable blocks, they would never be reached, so there should be no difference in the output, but we might end up inserting LLVM instrprof counters in the code for no reason. Generally this is an easy performance optimization.

From what I've seen, Unreachable blocks exist outside (disconnected from) the main CFG.

richkadel

comment created time in 4 days

PullRequestReviewEvent

Pull request review commentrust-lang/rust

Working branch-level code coverage

 impl<'tcx> MirPass<'tcx> for InstrumentCoverage {     ) {         // If the InstrumentCoverage pass is called on promoted MIRs, skip them.         // See: https://github.com/rust-lang/rust/pull/73011#discussion_r438317601-        if mir_source.promoted.is_none() {-            Instrumentor::new(&self.name(), tcx, mir_source, mir_body).inject_counters();+        if mir_source.promoted.is_some() {+            trace!(+                "InstrumentCoverage skipped for {:?} (already promoted for Miri evaluation)",+                mir_source.def_id()+            );+            return;         }++        let hir_id = tcx.hir().local_def_id_to_hir_id(mir_source.def_id().expect_local());+        let is_fn_like = FnLikeNode::from_node(tcx.hir().get(hir_id)).is_some();++        // Only instrument functions, methods, and closures (not constants since they are evaluated+        // at compile time by Miri).+        if !is_fn_like {+            trace!("InstrumentCoverage skipped for {:?} (not an FnLikeNode)", mir_source.def_id());+            return;+        }+        // FIXME(richkadel): By comparison, the MIR pass `ConstProp` includes associated constants,+        // with functions, methods, and closures. I assume Miri is used for associated constants as+        // well. If not, we may need to include them here too.++        trace!("InstrumentCoverage starting for {:?}", mir_source.def_id());+        Instrumentor::new(&self.name(), tcx, mir_source, mir_body).inject_counters();+        trace!("InstrumentCoverage starting for {:?}", mir_source.def_id());     } } -#[derive(Clone)]-struct CoverageRegion {-    pub span: Span,+/// A BasicCoverageBlock (BCB) represents the maximal-length sequence of CFG (MIR) BasicBlocks+/// without conditional branches.+#[derive(Debug, Clone)]+struct BasicCoverageBlock {     pub blocks: Vec<BasicBlock>, } +impl BasicCoverageBlock {+    pub fn leader_bb(&self) -> BasicBlock {+        self.blocks[0]+    }++    pub fn id(&self) -> String {+        format!(+            "@{}",+            self.blocks+                .iter()+                .map(|bb| bb.index().to_string())+                .collect::<Vec<_>>()+                .join(ID_SEPARATOR)+        )+    }+}++struct BasicCoverageBlocks {+    vec: IndexVec<BasicBlock, Option<BasicCoverageBlock>>,+}++impl BasicCoverageBlocks {+    pub fn from_mir(mir_body: &mir::Body<'tcx>) -> Self {+        let mut basic_coverage_blocks =+            BasicCoverageBlocks { vec: IndexVec::from_elem_n(None, mir_body.basic_blocks().len()) };+        basic_coverage_blocks.extract_from_mir(mir_body);+        basic_coverage_blocks+    }++    pub fn iter(&self) -> impl Iterator<Item = &BasicCoverageBlock> {+        self.vec.iter().filter_map(|option| option.as_ref())+    }++    fn extract_from_mir(&mut self, mir_body: &mir::Body<'tcx>) {+        // Traverse the CFG but ignore anything following an `unwind`+        let cfg_without_unwind = ShortCircuitPreorder::new(mir_body, |term_kind| {

That line reference ended up wrong, for the current commit. Here is what I meant to point at. Scroll up from there to see relevant info in the same comment:

https://github.com/richkadel/rust/blob/60a480dcd21c7daebea1d54c33d5f1d4ebc9180a/compiler/rustc_mir/src/transform/instrument_coverage.rs#L212

richkadel

comment created time in 4 days

PullRequestReviewEvent

Pull request review commentrust-lang/rust

Working branch-level code coverage

 impl<'tcx> MirPass<'tcx> for InstrumentCoverage {     ) {         // If the InstrumentCoverage pass is called on promoted MIRs, skip them.         // See: https://github.com/rust-lang/rust/pull/73011#discussion_r438317601-        if mir_source.promoted.is_none() {-            Instrumentor::new(&self.name(), tcx, mir_source, mir_body).inject_counters();+        if mir_source.promoted.is_some() {+            trace!(+                "InstrumentCoverage skipped for {:?} (already promoted for Miri evaluation)",+                mir_source.def_id()+            );+            return;         }++        let hir_id = tcx.hir().local_def_id_to_hir_id(mir_source.def_id().expect_local());+        let is_fn_like = FnLikeNode::from_node(tcx.hir().get(hir_id)).is_some();++        // Only instrument functions, methods, and closures (not constants since they are evaluated+        // at compile time by Miri).+        if !is_fn_like {+            trace!("InstrumentCoverage skipped for {:?} (not an FnLikeNode)", mir_source.def_id());+            return;+        }+        // FIXME(richkadel): By comparison, the MIR pass `ConstProp` includes associated constants,+        // with functions, methods, and closures. I assume Miri is used for associated constants as+        // well. If not, we may need to include them here too.++        trace!("InstrumentCoverage starting for {:?}", mir_source.def_id());+        Instrumentor::new(&self.name(), tcx, mir_source, mir_body).inject_counters();+        trace!("InstrumentCoverage starting for {:?}", mir_source.def_id());     } } -#[derive(Clone)]-struct CoverageRegion {-    pub span: Span,+/// A BasicCoverageBlock (BCB) represents the maximal-length sequence of CFG (MIR) BasicBlocks+/// without conditional branches.+#[derive(Debug, Clone)]+struct BasicCoverageBlock {     pub blocks: Vec<BasicBlock>, } +impl BasicCoverageBlock {+    pub fn leader_bb(&self) -> BasicBlock {+        self.blocks[0]+    }++    pub fn id(&self) -> String {+        format!(+            "@{}",+            self.blocks+                .iter()+                .map(|bb| bb.index().to_string())+                .collect::<Vec<_>>()+                .join(ID_SEPARATOR)+        )+    }+}++struct BasicCoverageBlocks {+    vec: IndexVec<BasicBlock, Option<BasicCoverageBlock>>,+}++impl BasicCoverageBlocks {+    pub fn from_mir(mir_body: &mir::Body<'tcx>) -> Self {+        let mut basic_coverage_blocks =+            BasicCoverageBlocks { vec: IndexVec::from_elem_n(None, mir_body.basic_blocks().len()) };+        basic_coverage_blocks.extract_from_mir(mir_body);+        basic_coverage_blocks+    }++    pub fn iter(&self) -> impl Iterator<Item = &BasicCoverageBlock> {+        self.vec.iter().filter_map(|option| option.as_ref())+    }++    fn extract_from_mir(&mut self, mir_body: &mir::Body<'tcx>) {+        // Traverse the CFG but ignore anything following an `unwind`+        let cfg_without_unwind = ShortCircuitPreorder::new(mir_body, |term_kind| {

I considered that, but I'm concerned that the closure definition is not general purpose enough.

See, for example the comment block at/above:

https://github.com/richkadel/rust/blob/60a480dcd21c7daebea1d54c33d5f1d4ebc9180a/compiler/rustc_mir/src/transform/instrument_coverage.rs#L240

(This is what I was trying to improve, when I asked for your help figuring out how to build a conditional expression that can result in different Iterator types (Filter, Chain, option::Iter, etc.). I never could figure out a good way to do this without Boxing, and I didn't want to add the Box if I didn't need to. As it turned out, I don't need this yet, and didn't have to make any change for now, but if someone needs to do more with this later, the ShortCircuitPreorder type likely won't be sufficient, and changing it (like adding Boxing) cold also have performance implications. (I don't want to generalize it here, for that reason.)

richkadel

comment created time in 4 days

PullRequestReviewEvent

Pull request review commentrust-lang/rust

Working branch-level code coverage

 impl<'tcx> MirPass<'tcx> for InstrumentCoverage {     ) {         // If the InstrumentCoverage pass is called on promoted MIRs, skip them.         // See: https://github.com/rust-lang/rust/pull/73011#discussion_r438317601-        if mir_source.promoted.is_none() {-            Instrumentor::new(&self.name(), tcx, mir_source, mir_body).inject_counters();+        if mir_source.promoted.is_some() {+            trace!(+                "InstrumentCoverage skipped for {:?} (already promoted for Miri evaluation)",+                mir_source.def_id()+            );+            return;         }++        let hir_id = tcx.hir().local_def_id_to_hir_id(mir_source.def_id().expect_local());+        let is_fn_like = FnLikeNode::from_node(tcx.hir().get(hir_id)).is_some();++        // Only instrument functions, methods, and closures (not constants since they are evaluated+        // at compile time by Miri).

#73156 Handle source code coverage in const eval

richkadel

comment created time in 4 days

PullRequestReviewEvent

Pull request review commentrust-lang/rust

Working branch-level code coverage

 impl<'tcx> MirPass<'tcx> for InstrumentCoverage {     ) {         // If the InstrumentCoverage pass is called on promoted MIRs, skip them.         // See: https://github.com/rust-lang/rust/pull/73011#discussion_r438317601-        if mir_source.promoted.is_none() {-            Instrumentor::new(&self.name(), tcx, mir_source, mir_body).inject_counters();+        if mir_source.promoted.is_some() {+            trace!(+                "InstrumentCoverage skipped for {:?} (already promoted for Miri evaluation)",+                mir_source.def_id()+            );+            return;         }++        let hir_id = tcx.hir().local_def_id_to_hir_id(mir_source.def_id().expect_local());+        let is_fn_like = FnLikeNode::from_node(tcx.hir().get(hir_id)).is_some();++        // Only instrument functions, methods, and closures (not constants since they are evaluated+        // at compile time by Miri).

Yes, we've discussed it and you created an Issue for it. I'll reference that issue here in the code with a FIXME comment:

        // FIXME(#73156): Handle source code coverage in const eval
richkadel

comment created time in 4 days

PullRequestReviewEvent

Pull request review commentrust-lang/rust

Working branch-level code coverage

 impl<Node: Idx> Dominators<Node> {         // FIXME -- could be optimized by using post-order-rank         self.dominators(node).any(|n| n == dom)     }++    /// Provide deterministic ordering of nodes such that, if any two nodes have a dominator+    /// relationship, the dominator will always precede the dominated. (The relative ordering+    /// of two unrelated nodes will also be consistent, but otherwise the order has no+    /// meaning.) This method cannot be used to determine if either Node dominates the other.+    pub fn rank_partial_cmp(&self, lhs: Node, rhs: Node) -> Option<Ordering> {

Comparing or sorting blocks based on rank_partial_cmp has no meaning without also checking that two blocks have a dominator-dominated relationship.

I believe this function has no value by itself.

Let me know if there's a clearer way for me to describe this in the doc comment.

richkadel

comment created time in 4 days

PullRequestReviewEvent

pull request commentrust-lang/rust

Updates to experimental coverage counter injection

Note to those following:

Rust compiler MCP rust-lang/compiler-team#278 Relevant issue: #34701 - Implement support for LLVMs code coverage instrumentation

This PR implements branch-level coverage. Support for LLVM code coverage is nearly complete.

See some examples in the main PR comment.

The one known remaining feature I'll be implementing next is to replace some counters with counter expressions, where possible.

richkadel

comment created time in 4 days

pull request commentrust-lang/rust

Updates to experimental coverage counter injection

I compiled json5format and its dependencies with branch-level coverage. There were some unhandled edge cases, which I fixed, and I was able to successfully compile, run, and show the coverage. I'm going to drop some example output into the PR comment.

richkadel

comment created time in 4 days

push eventrichkadel/rust

Rich Kadel

commit sha 4a2004f151cc428e55a0dea3fe2432af1c9b7fdb

added more test examples also improved Makefiles with support for non-zero exit status and to force validation of tests unless a specific test overrides it with a specific comment.

view details

Rich Kadel

commit sha 60a480dcd21c7daebea1d54c33d5f1d4ebc9180a

Fixed rare issues after testing on real-world crate

view details

push time in 4 days

pull request commentrust-lang/rust

WIP - Updates to experimental coverage counter injection

Ready for review (no longer "draft/WIP").

Last commit:

added more test examples

also improved Makefiles with support for non-zero exit status and to force validation of tests unless a specific test overrides it with a specific comment.

@tmandry @wesleywiser - I added a few more examples. The results look really good. Take a look at the coverage results in

src/test/run-make-fulldeps/instrument-coverage-cov-reports-link-dead-code/typical_*

And the coverage spanview files for reference.

Hopefully this gives you confidence as well.

I know we can add more tests, and I'll work on those.

Other than that, I believe the next (perhaps last) thing needed is to replace some counters with expressions, where they can be computed.

Thanks! Rich

richkadel

comment created time in 5 days

push eventrichkadel/rust

Rich Kadel

commit sha 7c1a04057e1fa935e42cf3af8477c8e92e6c3151

added more test examples also improved Makefiles with support for non-zero exit status and to force validation of tests unless a specific test overrides it with a specific comment.

view details

push time in 5 days

pull request commentrust-lang/rust

WIP - Updates to experimental coverage counter injection

New commit corrects for a couple of problems I just noticed in the spanview test output:

Corrected a fairly recent assumption in runtest.rs that all MIR dump files end in .mir. (It was appending .mir to the graphviz .dot and spanview .html file names when generating blessed output files. That also left outdated files in the baseline alongside the files with the incorrect names, which I've now removed.)

Updated spanview HTML title elements to match their content, replacing a hardcoded and incorrect name that was left in accidentally when originally submitted.

richkadel

comment created time in 5 days

push eventrichkadel/rust

Rich Kadel

commit sha b8136cba9c10f089d7e7b9a8538cf8c63cd77da7

fixed mir-opt non-mir extensions and spanview title elements Corrected a fairly recent assumption in runtest.rs that all MIR dump files end in .mir. (It was appending .mir to the graphviz .dot and spanview .html file names when generating blessed output files. That also left outdated files in the baseline alongside the files with the incorrect names, which I've now removed.) Updated spanview HTML title elements to match their content, replacing a hardcoded and incorrect name that was left in accidentally when originally submitted.

view details

push time in 5 days

pull request commentrust-lang/rust

WIP - Updates to experimental coverage counter injection

@tmandry @wesleywiser -

Most of the changed files are auto-generated test results (using --bless).

You may be interested in seeing the actual coverage results, and a good place to look is at the .txt files produced by llvm-cov show:

https://github.com/rust-lang/rust/pull/77080/files#diff-6502ae3b67c4afbb171d2c9525127e69

These results look right to me.

You can also browse, hover, and click on the rendered spanview files by opening the github location with the additional prefix (in front of the entire URL) https://htmlpreview.github.io/?.

For example:

https://htmlpreview.github.io/?https://github.com/rust-lang/rust/blob/93fb99282c5aa2805451b0400c667b91bec4cb78/src/test/run-make-fulldeps/instrument-coverage-mir-cov-html-base/expected_mir_dump.coverage_of_simple_loop/coverage_of_simple_loop.main.-------.InstrumentCoverage.0.html

richkadel

comment created time in 6 days

push eventrichkadel/rust

Rich Kadel

commit sha 93fb99282c5aa2805451b0400c667b91bec4cb78

Additional examples and some small improvements.

view details

push time in 6 days

pull request commentrust-lang/rust

WIP - Updates to experimental coverage counter injection

🦈 we're gonna need a bigger tooltip 🦈

Screen Shot 2020-09-23 at 10 01 49 AM

Using scanview is great for understanding how the coverage algorithm is determined and the content of the BasicCoverageBlocks, but in some cases they can get a bit long. Native tooltips have limits (perhaps browser dependent, a maximum number of lines or characters or something), so unless there's a workaround for those limits, I may have to convert to using some JavaScript and use a DOM-based tooltip.

I liked having an implementation that avoided JavaScript, but considering the DOM features are fairly advanced already, some JavaScript is probably not a major ask.

The downside is, given I usually view these inside VSCode panels, the tooltips would often extend beyond the frame of the HTML preview, and native tooltips have no problem with overlapping the frame (displaying above the VSCode editor window).

JavaScript tooltips are going to get cutoff (require scrolling or growing the pane).

Tradeoffs.

richkadel

comment created time in 6 days

Pull request review commentrust-lang/rust

WIP - Updates to experimental coverage counter injection

 impl<Node: Idx> Dominators<Node> {         // FIXME -- could be optimized by using post-order-rank         self.dominators(node).any(|n| n == dom)     }++    /// Provide deterministic ordering of nodes such that, if any two nodes have a dominator+    /// relationship, the dominator will always precede the dominated. (The relative ordering+    /// of two unrelated nodes will also be consistent, but otherwise the order has no+    /// meaning.) This method cannot be used to determine if either Node dominates the other.+    pub fn rank_partial_cmp(&self, lhs: Node, rhs: Node) -> Option<Ordering> {

Note: I validated this with the following temporarily inserted assertions:

    for &node in rpo.iter() {
        for dominator in dominators.dominators(node) {
            if node == dominator {
                debug_assert_eq!(dominators.rank_partial_cmp(node, dominator), Some(Ordering::Equal));
            } else {
                debug_assert_eq!(dominators.rank_partial_cmp(node, dominator), Some(Ordering::Less));
            }
        }
    }
richkadel

comment created time in 7 days

PullRequestReviewEvent

PR opened rust-lang/rust

WIP - Updates to experimental coverage counter injection

Add a generalized implementation for computing branch-level coverage spans.

Note to reviewers: I'm marking this a Draft / WIP for now while I expand on the test cases.

This iteration resolves some of the challenges I had identified a few weeks ago.

I wouldn't be surprised if there are other cases that don't yet work, but I've tried to implement a solution that is general enough to work for a lot of different graphs/patterns. I'm hopeful.

In the mean time, I think it's worth taking a look so you can give me some initial feedback and or see the direction I'm going. Feel free to ask questions of course!

r? @tmandry FYI: @wesleywiser

+2179 -1836

0 comment

36 changed files

pr created time in 7 days

create barnchrichkadel/rust

branch : llvm-coverage-counters-2

created branch time in 7 days

push eventrichkadel/rust

Ivan Tham

commit sha 2b7f87b5fa43336ed1237747f60fd9095a41ea3d

Liballoc tweak use *const T instead of *const i8 *const T is also used in the same parts and also used for arith_offset.

view details

Ivan Tham

commit sha cc0d6345500932e8118ba65e98944a6a3bac3277

Liballoc IntoIter limit unsafe to pointer arithmethic

view details

Ivan Tham

commit sha 50315238aa8ffae08f29b260aa36511e03b5e070

Liballoc DoubleEndedIterator limit unsafe to pointer arithmethic

view details

Marcel Hellwig

commit sha 73e27b3e18dcbbef3a36620c4a44306e2bbdcd13

deny(unsafe_op_in_unsafe_fn) in libstd/process.rs

view details

Marcel Hellwig

commit sha 00d537dcd03f9ff5ebdf8b86e039dbdb0a7f850c

deny(unsafe_op_in_unsafe_fn) in libstd/path.rs

view details

Tim Nielens

commit sha 2ecc2ac864739cff6aed2609021e2467dedb117a

unit-arg - improve suggestion

view details

Ivan Tham

commit sha c5975e9b6c5781b3b7300b7921c14b060086e1c1

Reduce duplicate in liballoc reserve error handling

view details

Ivan Tham

commit sha a7468705cbf0fb551b8b1d8b420123262f7d92b2

Use translated variable for test string Test should be educative, added english translation and pronounciation.

view details

Yoshua Wuyts

commit sha 688f4471fd553c83ae3ff0306956d89b7d7c2d28

Stabilize future readiness fns

view details

Eduardo Broto

commit sha baf62e7a38854ff6a0039ddccb124ff329a32143

Update changelog to beta 1.47

view details

Tim Nielens

commit sha f3ccbef2af24d5d83f82f1fb50bd97a9b75e609f

unit-arg - pr comments

view details

Hirochika Matsumoto

commit sha 5574182b4d2d08c848a88a1ac5633fc194e0465e

Add a new lint to prevent `create_dir` from being used

view details

Hirochika Matsumoto

commit sha 607905d126c55422668007737c22d7a7a89c0d57

Add STD_FS_CREATE_DIR into paths

view details

Hirochika Matsumoto

commit sha 34e302e67c08c9b97d58d062ea83cc1fd860c56e

Fix clippy error

view details

Hirochika Matsumoto

commit sha eebd2483654456e332d7cf53218b56b9cbd6f2f5

Fix errors

view details

bors

commit sha c88c6149415dd47b5f05e69d7307e0a1967c33f2

Auto merge of #5970 - ebroto:changelog_beta_1_47, r=flip1995 Update changelog to beta 1.47 [Rendered](https://github.com/ebroto/rust-clippy/blob/changelog_beta_1_47/CHANGELOG.md) changelog: none

view details

flip1995

commit sha 282c59820b8e1d8c76f440484b81a190c576f91b

Merge commit '3d0b0e66afdfaa519d8855b338b35b4605775945' into clippyup

view details

Eduardo Broto

commit sha 7a66e6502dc3c7085b3f4078c01d4957d96175ed

or_fn_call: ignore nullary associated const fns

view details

Hirochika Matsumoto

commit sha 5b7590f841974255f74c64d573189aecc7a30b2e

Downgrade applicability of `create_dir`

view details

Ivan Tham

commit sha ba4c4988161abbe58e973b792c7e271785b4bc4d

Add more info for Vec Drain doc See its documentation for more

view details

push time in 7 days

PR merged richkadel/rust

Update from origin 2020-09-22
+40308 -24110

0 comment

1057 changed files

richkadel

pr closed time in 7 days

PR opened richkadel/rust

Update from origin 2020-09-22
+40308 -24110

0 comment

1057 changed files

pr created time in 7 days

pull request commentrust-lang/rust

Don't force graphviz diagrams to render in Courier

@ecstatic-morse - #76794 was successfully merged, so this PR can probably be closed, if you agree.

Thanks!

ecstatic-morse

comment created time in 13 days

pull request commentrust-lang/rust

Make graphviz font configurable

@bors r=ecstatic-morse rollup

richkadel

comment created time in 13 days

Pull request review commentrust-lang/rust

Make graphviz font configurable

 pub fn build_session_options(matches: &getopts::Matches) -> Options {         debugging_opts.symbol_mangling_version = SymbolManglingVersion::V0;     } +    if let Some(env) = std::env::var_os("RUSTC_GRAPHVIZ_FONT") {+        if let Ok(graphviz_font) = env.into_string() {+            debugging_opts.graphviz_font = graphviz_font;

(Verified .dot file generates the expected font with and without the environment variable set... Using env::var now.)

richkadel

comment created time in 13 days

PullRequestReviewEvent

Pull request review commentrust-lang/rust

Make graphviz font configurable

 pub fn build_session_options(matches: &getopts::Matches) -> Options {         debugging_opts.symbol_mangling_version = SymbolManglingVersion::V0;     } +    if let Some(env) = std::env::var_os("RUSTC_GRAPHVIZ_FONT") {+        if let Ok(graphviz_font) = env.into_string() {+            debugging_opts.graphviz_font = graphviz_font;

I'll want for checks to compile, and also test locally one more time with and without the env setting.

Then I'll push it.

richkadel

comment created time in 13 days

PullRequestReviewEvent

push eventrichkadel/rust

Rich Kadel

commit sha 3875abe32f6b98449bf2ec99ed210b7b77953703

Added RUSTC_GRAPHVIZ_FONT environment variable Overrides the debugging_opts.graphviz_font setting.

view details

push time in 13 days

Pull request review commentrust-lang/rust

Make graphviz font configurable

 pub fn build_session_options(matches: &getopts::Matches) -> Options {         debugging_opts.symbol_mangling_version = SymbolManglingVersion::V0;     } +    if let Some(env) = std::env::var_os("RUSTC_GRAPHVIZ_FONT") {+        if let Ok(graphviz_font) = env.into_string() {+            debugging_opts.graphviz_font = graphviz_font;

Oh, since you delegated this, I'm going to make the change.

richkadel

comment created time in 13 days

PullRequestReviewEvent

Pull request review commentrust-lang/rust

Make graphviz font configurable

 pub fn build_session_options(matches: &getopts::Matches) -> Options {         debugging_opts.symbol_mangling_version = SymbolManglingVersion::V0;     } +    if let Some(env) = std::env::var_os("RUSTC_GRAPHVIZ_FONT") {+        if let Ok(graphviz_font) = env.into_string() {+            debugging_opts.graphviz_font = graphviz_font;

lol, yeah, that makes sense now. Good to know for the future. Thanks!

richkadel

comment created time in 13 days

PullRequestReviewEvent

Pull request review commentrust-lang/rust

Make graphviz font configurable

 pub fn build_session_options(matches: &getopts::Matches) -> Options {         debugging_opts.symbol_mangling_version = SymbolManglingVersion::V0;     } +    if let Some(graphviz_font) = option_env!("RUSTC_GRAPHVIZ_FONT") {

Done. I waited for it to compile this time ;-) ... tested locally with the environment variable set and unset and it works.

richkadel

comment created time in 13 days

PullRequestReviewEvent

push eventrichkadel/rust

Rich Kadel

commit sha 5b713ad8658882495d68f83e9609edfc06aab41a

Added RUSTC_GRAPHVIZ_FONT environment variable Overrides the debugging_opts.graphviz_font setting.

view details

push time in 13 days

PullRequestReviewEvent

Pull request review commentrust-lang/rust

Make graphviz font configurable

 pub fn build_session_options(matches: &getopts::Matches) -> Options {         debugging_opts.symbol_mangling_version = SymbolManglingVersion::V0;     } +    if let Some(graphviz_font) = option_env!("RUSTC_GRAPHVIZ_FONT") {

Oh, right. OK.

(I wanted to push this before waiting for my test to compile.)

But generally you're OK with the env var name, etc.?

I'll fix how it's evaluated.

richkadel

comment created time in 13 days

push eventrichkadel/rust

Rich Kadel

commit sha b567edf5248a40c279ce36b6f1a84e41032f00b7

Added RUSTC_GRAPHVIZ_FONT environment variable Overrides the debugging_opts.graphviz_font setting.

view details

push time in 13 days

pull request commentrust-lang/rust

Make graphviz font configurable

Sure, I'm not trying to break your workflow. Just exploring the options. Most of the compiler configs seem to be through flags, not as much through environment variables, but I can add support for an environment variable to this PR.

richkadel

comment created time in 13 days

more