Auto merge of #119508 - Zalathar:graph, r=compiler-errors · rust-lang/rust@73252d5 (original) (raw)

1

1

`use rustc_data_structures::captures::Captures;

`

``

2

`+

use rustc_data_structures::fx::FxHashSet;

`

2

3

`use rustc_data_structures::graph::dominators::{self, Dominators};

`

3

4

`use rustc_data_structures::graph::{self, GraphSuccessors, WithNumNodes, WithStartNode};

`

4

5

`use rustc_index::bit_set::BitSet;

`

5

``

`-

use rustc_index::{IndexSlice, IndexVec};

`

6

``

`-

use rustc_middle::mir::{self, BasicBlock, TerminatorKind};

`

``

6

`+

use rustc_index::IndexVec;

`

``

7

`+

use rustc_middle::mir::{self, BasicBlock, Terminator, TerminatorKind};

`

7

8

``

8

9

`use std::cmp::Ordering;

`

9

10

`use std::collections::VecDeque;

`

`@@ -30,23 +31,16 @@ impl CoverageGraph {

`

30

31

`` // SwitchInt to have multiple targets to the same destination BasicBlock, so

``

31

32

`// de-duplication is required. This is done without reordering the successors.

`

32

33

``

33

``

`-

let mut seen = IndexVec::from_elem(false, &bcbs);

`

34

34

`let successors = IndexVec::from_fn_n(

`

35

35

` |bcb| {

`

36

``

`-

for b in seen.iter_mut() {

`

37

``

`-

*b = false;

`

38

``

`-

}

`

39

``

`-

let bcb_data = &bcbs[bcb];

`

40

``

`-

let mut bcb_successors = Vec::new();

`

41

``

`-

for successor in bcb_filtered_successors(mir_body, bcb_data.last_bb())

`

``

36

`+

let mut seen_bcbs = FxHashSet::default();

`

``

37

`+

let terminator = mir_body[bcbs[bcb].last_bb()].terminator();

`

``

38

`+

bcb_filtered_successors(terminator)

`

``

39

`+

.into_iter()

`

42

40

`.filter_map(|successor_bb| bb_to_bcb[successor_bb])

`

43

``

`-

{

`

44

``

`-

if !seen[successor] {

`

45

``

`-

seen[successor] = true;

`

46

``

`-

bcb_successors.push(successor);

`

47

``

`-

}

`

48

``

`-

}

`

49

``

`-

bcb_successors

`

``

41

`+

// Remove duplicate successor BCBs, keeping only the first.

`

``

42

`+

.filter(|&successor_bcb| seen_bcbs.insert(successor_bcb))

`

``

43

`+

.collect::<Vec<_>>()

`

50

44

`},

`

51

45

` bcbs.len(),

`

52

46

`);

`

`@@ -80,112 +74,66 @@ impl CoverageGraph {

`

80

74

`IndexVec<BasicBlock, Option>,

`

81

75

`) {

`

82

76

`let num_basic_blocks = mir_body.basic_blocks.len();

`

83

``

`-

let mut bcbs = IndexVec::with_capacity(num_basic_blocks);

`

``

77

`+

let mut bcbs = IndexVec::<BasicCoverageBlock, _>::with_capacity(num_basic_blocks);

`

84

78

`let mut bb_to_bcb = IndexVec::from_elem_n(None, num_basic_blocks);

`

85

79

``

``

80

`+

let mut add_basic_coverage_block = |basic_blocks: &mut Vec| {

`

``

81

`+

// Take the accumulated list of blocks, leaving the vector empty

`

``

82

`+

// to be used by subsequent BCBs.

`

``

83

`+

let basic_blocks = std::mem::take(basic_blocks);

`

``

84

+

``

85

`+

let bcb = bcbs.next_index();

`

``

86

`+

for &bb in basic_blocks.iter() {

`

``

87

`+

bb_to_bcb[bb] = Some(bcb);

`

``

88

`+

}

`

``

89

`+

let bcb_data = BasicCoverageBlockData::from(basic_blocks);

`

``

90

`+

debug!("adding bcb{}: {:?}", bcb.index(), bcb_data);

`

``

91

`+

bcbs.push(bcb_data);

`

``

92

`+

};

`

``

93

+

86

94

`` // Walk the MIR CFG using a Preorder traversal, which starts from START_BLOCK and follows

``

87

95

`` // each block terminator's successors(). Coverage spans must map to actual source code,

``

88

96

`// so compiler generated blocks and paths can be ignored. To that end, the CFG traversal

`

89

97

`// intentionally omits unwind paths.

`

90

98

`` // FIXME(#78544): MIR InstrumentCoverage: Improve coverage of #[should_panic] tests and

``

91

99

`` // catch_unwind() handlers.

``

92

100

``

``

101

`+

// Accumulates a chain of blocks that will be combined into one BCB.

`

93

102

`let mut basic_blocks = Vec::new();

`

94

``

`-

for bb in short_circuit_preorder(mir_body, bcb_filtered_successors) {

`

95

``

`-

if let Some(last) = basic_blocks.last() {

`

96

``

`-

let predecessors = &mir_body.basic_blocks.predecessors()[bb];

`

97

``

`-

if predecessors.len() > 1 || !predecessors.contains(last) {

`

98

``

`` -

// The bb has more than one incoming edge, and should start its own

``

99

``

`` -

// BasicCoverageBlockData. (Note, the basic_blocks vector does not yet

``

100

``

`` -

// include bb; it contains a sequence of one or more sequential basic_blocks

``

101

``

`-

// with no intermediate branches in or out. Save these as a new

`

102

``

`` -

// BasicCoverageBlockData before starting the new one.)

``

103

``

`-

Self::add_basic_coverage_block(

`

104

``

`-

&mut bcbs,

`

105

``

`-

&mut bb_to_bcb,

`

106

``

`-

basic_blocks.split_off(0),

`

107

``

`-

);

`

108

``

`-

debug!(

`

109

``

`-

" because {}",

`

110

``

`-

if predecessors.len() > 1 {

`

111

``

`-

"predecessors.len() > 1".to_owned()

`

112

``

`-

} else {

`

113

``

`-

format!("bb {} is not in predecessors: {:?}", bb.index(), predecessors)

`

114

``

`-

}

`

115

``

`-

);

`

116

``

`-

}

`

117

``

`-

}

`

118

``

`-

basic_blocks.push(bb);

`

119

103

``

120

``

`-

let term = mir_body[bb].terminator();

`

121

``

-

122

``

`-

match term.kind {

`

123

``

`-

TerminatorKind::Return { .. }

`

124

``

`-

| TerminatorKind::UnwindTerminate(_)

`

125

``

`-

| TerminatorKind::Yield { .. }

`

126

``

`-

| TerminatorKind::SwitchInt { .. } => {

`

127

``

`` -

// The bb has more than one outgoing edge, or exits the function. Save the

``

128

``

`` -

// current sequence of basic_blocks gathered to this point, as a new

``

129

``

`` -

// BasicCoverageBlockData.

``

130

``

`-

Self::add_basic_coverage_block(

`

131

``

`-

&mut bcbs,

`

132

``

`-

&mut bb_to_bcb,

`

133

``

`-

basic_blocks.split_off(0),

`

134

``

`-

);

`

135

``

`-

debug!(" because term.kind = {:?}", term.kind);

`

136

``

`` -

// Note that this condition is based on TerminatorKind, even though it

``

137

``

`` -

// theoretically boils down to successors().len() != 1; that is, either zero

``

138

``

`` -

// (e.g., Return, Terminate) or multiple successors (e.g., SwitchInt), but

``

139

``

`-

// since the BCB CFG ignores things like unwind branches (which exist in the

`

140

``

`` -

// Terminators successors() list) checking the number of successors won't

``

141

``

`-

// work.

`

142

``

`-

}

`

143

``

-

144

``

`` -

// The following TerminatorKinds are either not expected outside an unwind branch,

``

145

``

`-

// or they should not (under normal circumstances) branch. Coverage graphs are

`

146

``

`-

// simplified by assuring coverage results are accurate for program executions that

`

147

``

`-

// don't panic.

`

148

``

`-

//

`

149

``

`-

// Programs that panic and unwind may record slightly inaccurate coverage results

`

150

``

`` -

// for a coverage region containing the Terminator that began the panic. This

``

151

``

`-

// is as intended. (See Issue #78544 for a possible future option to support

`

152

``

`-

// coverage in test programs that panic.)

`

153

``

`-

TerminatorKind::Goto { .. }

`

154

``

`-

| TerminatorKind::UnwindResume

`

155

``

`-

| TerminatorKind::Unreachable

`

156

``

`-

| TerminatorKind::Drop { .. }

`

157

``

`-

| TerminatorKind::Call { .. }

`

158

``

`-

| TerminatorKind::CoroutineDrop

`

159

``

`-

| TerminatorKind::Assert { .. }

`

160

``

`-

| TerminatorKind::FalseEdge { .. }

`

161

``

`-

| TerminatorKind::FalseUnwind { .. }

`

162

``

`-

| TerminatorKind::InlineAsm { .. } => {}

`

``

104

`+

let filtered_successors = |bb| bcb_filtered_successors(mir_body[bb].terminator());

`

``

105

`+

for bb in short_circuit_preorder(mir_body, filtered_successors)

`

``

106

`+

.filter(|&bb| mir_body[bb].terminator().kind != TerminatorKind::Unreachable)

`

``

107

`+

{

`

``

108

`` +

// If the previous block can't be chained into bb, flush the accumulated

``

``

109

`+

// blocks into a new BCB, then start building the next chain.

`

``

110

`+

if let Some(&prev) = basic_blocks.last()

`

``

111

`+

&& (!filtered_successors(prev).is_chainable() || {

`

``

112

`` +

// If bb has multiple predecessor blocks, or prev isn't

``

``

113

`+

// one of its predecessors, we can't chain and must flush.

`

``

114

`+

let predecessors = &mir_body.basic_blocks.predecessors()[bb];

`

``

115

`+

predecessors.len() > 1 || !predecessors.contains(&prev)

`

``

116

`+

})

`

``

117

`+

{

`

``

118

`+

debug!(

`

``

119

`+

terminator_kind = ?mir_body[prev].terminator().kind,

`

``

120

`+

predecessors = ?&mir_body.basic_blocks.predecessors()[bb],

`

``

121

`+

"can't chain from {prev:?} to {bb:?}"

`

``

122

`+

);

`

``

123

`+

add_basic_coverage_block(&mut basic_blocks);

`

163

124

`}

`

``

125

+

``

126

`+

basic_blocks.push(bb);

`

164

127

`}

`

165

128

``

166

129

`if !basic_blocks.is_empty() {

`

167

``

`` -

// process any remaining basic_blocks into a final BasicCoverageBlockData

``

168

``

`-

Self::add_basic_coverage_block(&mut bcbs, &mut bb_to_bcb, basic_blocks.split_off(0));

`

169

``

`-

debug!(" because the end of the MIR CFG was reached while traversing");

`

``

130

`+

debug!("flushing accumulated blocks into one last BCB");

`

``

131

`+

add_basic_coverage_block(&mut basic_blocks);

`

170

132

`}

`

171

133

``

172

134

`(bcbs, bb_to_bcb)

`

173

135

`}

`

174

136

``

175

``

`-

fn add_basic_coverage_block(

`

176

``

`-

bcbs: &mut IndexVec<BasicCoverageBlock, BasicCoverageBlockData>,

`

177

``

`-

bb_to_bcb: &mut IndexSlice<BasicBlock, Option>,

`

178

``

`-

basic_blocks: Vec,

`

179

``

`-

) {

`

180

``

`-

let bcb = bcbs.next_index();

`

181

``

`-

for &bb in basic_blocks.iter() {

`

182

``

`-

bb_to_bcb[bb] = Some(bcb);

`

183

``

`-

}

`

184

``

`-

let bcb_data = BasicCoverageBlockData::from(basic_blocks);

`

185

``

`-

debug!("adding bcb{}: {:?}", bcb.index(), bcb_data);

`

186

``

`-

bcbs.push(bcb_data);

`

187

``

`-

}

`

188

``

-

189

137

`#[inline(always)]

`

190

138

`pub fn iter_enumerated(

`

191

139

`&self,

`

`@@ -346,28 +294,76 @@ impl BasicCoverageBlockData {

`

346

294

`}

`

347

295

`}

`

348

296

``

``

297

`+

/// Holds the coverage-relevant successors of a basic block's terminator, and

`

``

298

`+

/// indicates whether that block can potentially be combined into the same BCB

`

``

299

`+

/// as its sole successor.

`

``

300

`+

#[derive(Clone, Copy, Debug)]

`

``

301

`+

enum CoverageSuccessors<'a> {

`

``

302

`+

/// The terminator has exactly one straight-line successor, so its block can

`

``

303

`+

/// potentially be combined into the same BCB as that successor.

`

``

304

`+

Chainable(BasicBlock),

`

``

305

`+

/// The block cannot be combined into the same BCB as its successor(s).

`

``

306

`+

NotChainable(&'a [BasicBlock]),

`

``

307

`+

}

`

``

308

+

``

309

`+

impl CoverageSuccessors<'_> {

`

``

310

`+

fn is_chainable(&self) -> bool {

`

``

311

`+

match self {

`

``

312

`+

Self::Chainable(_) => true,

`

``

313

`+

Self::NotChainable(_) => false,

`

``

314

`+

}

`

``

315

`+

}

`

``

316

`+

}

`

``

317

+

``

318

`+

impl IntoIterator for CoverageSuccessors<'_> {

`

``

319

`+

type Item = BasicBlock;

`

``

320

`+

type IntoIter = impl DoubleEndedIterator<Item = Self::Item>;

`

``

321

+

``

322

`+

fn into_iter(self) -> Self::IntoIter {

`

``

323

`+

match self {

`

``

324

`+

Self::Chainable(bb) => Some(bb).into_iter().chain((&[]).iter().copied()),

`

``

325

`+

Self::NotChainable(bbs) => None.into_iter().chain(bbs.iter().copied()),

`

``

326

`+

}

`

``

327

`+

}

`

``

328

`+

}

`

``

329

+

349

330

`// Returns the subset of a block's successors that are relevant to the coverage

`

350

``

`-

// graph, i.e. those that do not represent unwinds or unreachable branches.

`

``

331

`+

// graph, i.e. those that do not represent unwinds or false edges.

`

351

332

`` // FIXME(#78544): MIR InstrumentCoverage: Improve coverage of #[should_panic] tests and

``

352

333

`` // catch_unwind() handlers.

``

353

``

`-

fn bcb_filtered_successors<'a, 'tcx>(

`

354

``

`-

body: &'a mir::Body<'tcx>,

`

355

``

`-

bb: BasicBlock,

`

356

``

`-

) -> impl Iterator<Item = BasicBlock> + Captures<'a> + Captures<'tcx> {

`

357

``

`-

let terminator = body[bb].terminator();

`

358

``

-

359

``

`-

let take_n_successors = match terminator.kind {

`

360

``

`-

// SwitchInt successors are never unwinds, so all of them should be traversed.

`

361

``

`-

TerminatorKind::SwitchInt { .. } => usize::MAX,

`

362

``

`-

// For all other kinds, return only the first successor (if any), ignoring any

`

363

``

`-

// unwind successors.

`

364

``

`-

_ => 1,

`

365

``

`-

};

`

366

``

-

367

``

`-

terminator

`

368

``

`-

.successors()

`

369

``

`-

.take(take_n_successors)

`

370

``

`-

.filter(move |&successor| body[successor].terminator().kind != TerminatorKind::Unreachable)

`

``

334

`+

fn bcb_filtered_successors<'a, 'tcx>(terminator: &'a Terminator<'tcx>) -> CoverageSuccessors<'a> {

`

``

335

`+

use TerminatorKind::*;

`

``

336

`+

match terminator.kind {

`

``

337

`+

// A switch terminator can have many coverage-relevant successors.

`

``

338

`+

// (If there is exactly one successor, we still treat it as not chainable.)

`

``

339

`+

SwitchInt { ref targets, .. } => CoverageSuccessors::NotChainable(targets.all_targets()),

`

``

340

+

``

341

`+

// A yield terminator has exactly 1 successor, but should not be chained,

`

``

342

`+

// because its resume edge has a different execution count.

`

``

343

`+

Yield { ref resume, .. } => CoverageSuccessors::NotChainable(std::slice::from_ref(resume)),

`

``

344

+

``

345

`+

// These terminators have exactly one coverage-relevant successor,

`

``

346

`+

// and can be chained into it.

`

``

347

`+

Assert { target, .. }

`

``

348

`+

| Drop { target, .. }

`

``

349

`+

| FalseEdge { real_target: target, .. }

`

``

350

`+

| FalseUnwind { real_target: target, .. }

`

``

351

`+

| Goto { target } => CoverageSuccessors::Chainable(target),

`

``

352

+

``

353

`+

// These terminators can normally be chained, except when they have no

`

``

354

`+

// successor because they are known to diverge.

`

``

355

`+

Call { target: maybe_target, .. } | InlineAsm { destination: maybe_target, .. } => {

`

``

356

`+

match maybe_target {

`

``

357

`+

Some(target) => CoverageSuccessors::Chainable(target),

`

``

358

`+

None => CoverageSuccessors::NotChainable(&[]),

`

``

359

`+

}

`

``

360

`+

}

`

``

361

+

``

362

`+

// These terminators have no coverage-relevant successors.

`

``

363

`+

CoroutineDrop | Return | Unreachable | UnwindResume | UnwindTerminate(_) => {

`

``

364

`+

CoverageSuccessors::NotChainable(&[])

`

``

365

`+

}

`

``

366

`+

}

`

371

367

`}

`

372

368

``

373

369

`/// Maintains separate worklists for each loop in the BasicCoverageBlock CFG, plus one for the

`

`@@ -544,8 +540,8 @@ fn short_circuit_preorder<'a, 'tcx, F, Iter>(

`

544

540

`filtered_successors: F,

`

545

541

`) -> impl Iterator<Item = BasicBlock> + Captures<'a> + Captures<'tcx>

`

546

542

`where

`

547

``

`-

F: Fn(&'a mir::Body<'tcx>, BasicBlock) -> Iter,

`

548

``

`-

Iter: Iterator<Item = BasicBlock>,

`

``

543

`+

F: Fn(BasicBlock) -> Iter,

`

``

544

`+

Iter: IntoIterator<Item = BasicBlock>,

`

549

545

`{

`

550

546

`let mut visited = BitSet::new_empty(body.basic_blocks.len());

`

551

547

`let mut worklist = vec![mir::START_BLOCK];

`

`@@ -556,7 +552,7 @@ where

`

556

552

`continue;

`

557

553

`}

`

558

554

``

559

``

`-

worklist.extend(filtered_successors(body, bb));

`

``

555

`+

worklist.extend(filtered_successors(bb));

`

560

556

``

561

557

`return Some(bb);

`

562

558

`}

`