Introduce MIR optimizations for simplifying x? on Results. · rust-lang/rust@2f00e86 (original) (raw)

``

1

`+

//! The general point of the optimizations provided here is to simplify something like:

`

``

2

`+

//!

`

``

3


//! ```rust

``

4

`+

//! match x {

`

``

5

`+

//! Ok(x) => Ok(x),

`

``

6

`+

//! Err(x) => Err(x)

`

``

7

`+

//! }

`

``

8


//! ```

``

9

`+

//!

`

``

10

`` +

//! into just x.

``

``

11

+

``

12

`+

use crate::transform::{MirPass, MirSource, simplify};

`

``

13

`+

use rustc::ty::{TyCtxt, Ty};

`

``

14

`+

use rustc::mir::*;

`

``

15

`+

use rustc_target::abi::VariantIdx;

`

``

16

`+

use itertools::Itertools as _;

`

``

17

+

``

18

`` +

/// Simplifies arms of form Variant(x) => Variant(x) to just a move.

``

``

19

`+

///

`

``

20

`+

/// This is done by transforming basic blocks where the statements match:

`

``

21

`+

///

`

``

22


/// ```rust

``

23

`+

/// _LOCAL_TMP = ((_LOCAL_1 as Variant ).FIELD: TY );

`

``

24

`+

/// ((_LOCAL_0 as Variant).FIELD: TY) = move _LOCAL_TMP;

`

``

25

`+

/// discriminant(_LOCAL_0) = VAR_IDX;

`

``

26


/// ```

``

27

`+

///

`

``

28

`+

/// into:

`

``

29

`+

///

`

``

30


/// ```rust

``

31

`+

/// _LOCAL_0 = move _LOCAL_1

`

``

32


/// ```

``

33

`+

pub struct SimplifyArmIdentity;

`

``

34

+

``

35

`+

impl<'tcx> MirPass<'tcx> for SimplifyArmIdentity {

`

``

36

`+

fn run_pass(&self, _: TyCtxt<'tcx>, _: MirSource<'tcx>, body: &mut Body<'tcx>) {

`

``

37

`+

for bb in body.basic_blocks_mut() {

`

``

38

`+

// Need 3 statements:

`

``

39

`+

let (s0, s1, s2) = match &mut *bb.statements {

`

``

40

`+

[s0, s1, s2] => (s0, s1, s2),

`

``

41

`+

_ => continue,

`

``

42

`+

};

`

``

43

+

``

44

`+

// Pattern match on the form we want:

`

``

45

`+

let (local_tmp_s0, local_1, vf_s0) = match match_get_variant_field(s0) {

`

``

46

`+

None => continue,

`

``

47

`+

Some(x) => x,

`

``

48

`+

};

`

``

49

`+

let (local_tmp_s1, local_0, vf_s1) = match match_set_variant_field(s1) {

`

``

50

`+

None => continue,

`

``

51

`+

Some(x) => x,

`

``

52

`+

};

`

``

53

`+

if local_tmp_s0 != local_tmp_s1

`

``

54

`+

|| vf_s0 != vf_s1

`

``

55

`+

|| Some((local_0, vf_s0.var_idx)) != match_set_discr(s2)

`

``

56

`+

{

`

``

57

`+

continue;

`

``

58

`+

}

`

``

59

+

``

60

`+

// Right shape; transform!

`

``

61

`+

match &mut s0.kind {

`

``

62

`+

StatementKind::Assign(box (place, rvalue)) => {

`

``

63

`+

*place = local_0.into();

`

``

64

`+

*rvalue = Rvalue::Use(Operand::Move(local_1.into()));

`

``

65

`+

}

`

``

66

`+

_ => unreachable!(),

`

``

67

`+

}

`

``

68

`+

s1.make_nop();

`

``

69

`+

s2.make_nop();

`

``

70

`+

}

`

``

71

`+

}

`

``

72

`+

}

`

``

73

+

``

74

`+

/// Match on:

`

``

75


/// ```rust

``

76

`+

/// _LOCAL_INTO = ((_LOCAL_FROM as Variant).FIELD: TY);

`

``

77


/// ```

``

78

`+

fn match_get_variant_field<'tcx>(stmt: &Statement<'tcx>) -> Option<(Local, Local, VarField<'tcx>)> {

`

``

79

`+

match &stmt.kind {

`

``

80

`+

StatementKind::Assign(box (place_into, rvalue_from)) => match rvalue_from {

`

``

81

`+

Rvalue::Use(Operand::Copy(pf)) | Rvalue::Use(Operand::Move(pf)) => {

`

``

82

`+

let local_into = place_into.as_local()?;

`

``

83

`+

let (local_from, vf) = match_variant_field_place(&pf)?;

`

``

84

`+

Some((local_into, local_from, vf))

`

``

85

`+

}

`

``

86

`+

_ => None,

`

``

87

`+

},

`

``

88

`+

_ => None,

`

``

89

`+

}

`

``

90

`+

}

`

``

91

+

``

92

`+

/// Match on:

`

``

93


/// ```rust

``

94

`+

/// ((_LOCAL_FROM as Variant).FIELD: TY) = move _LOCAL_INTO;

`

``

95


/// ```

``

96

`+

fn match_set_variant_field<'tcx>(stmt: &Statement<'tcx>) -> Option<(Local, Local, VarField<'tcx>)> {

`

``

97

`+

match &stmt.kind {

`

``

98

`+

StatementKind::Assign(box (place_from, rvalue_into)) => match rvalue_into {

`

``

99

`+

Rvalue::Use(Operand::Move(place_into)) => {

`

``

100

`+

let local_into = place_into.as_local()?;

`

``

101

`+

let (local_from, vf) = match_variant_field_place(&place_from)?;

`

``

102

`+

Some((local_into, local_from, vf))

`

``

103

`+

}

`

``

104

`+

_ => None,

`

``

105

`+

},

`

``

106

`+

_ => None,

`

``

107

`+

}

`

``

108

`+

}

`

``

109

+

``

110

`+

/// Match on:

`

``

111


/// ```rust

``

112

`+

/// discriminant(_LOCAL_TO_SET) = VAR_IDX;

`

``

113


/// ```

``

114

`+

fn match_set_discr<'tcx>(stmt: &Statement<'tcx>) -> Option<(Local, VariantIdx)> {

`

``

115

`+

match &stmt.kind {

`

``

116

`+

StatementKind::SetDiscriminant { place, variant_index } => Some((

`

``

117

`+

place.as_local()?,

`

``

118

`+

*variant_index

`

``

119

`+

)),

`

``

120

`+

_ => None,

`

``

121

`+

}

`

``

122

`+

}

`

``

123

+

``

124

`+

#[derive(PartialEq)]

`

``

125

`+

struct VarField<'tcx> {

`

``

126

`+

field: Field,

`

``

127

`+

field_ty: Ty<'tcx>,

`

``

128

`+

var_idx: VariantIdx,

`

``

129

`+

}

`

``

130

+

``

131

`` +

/// Match on ((_LOCAL as Variant).FIELD: TY).

``

``

132

`+

fn match_variant_field_place<'tcx>(place: &Place<'tcx>) -> Option<(Local, VarField<'tcx>)> {

`

``

133

`+

match place.as_ref() {

`

``

134

`+

PlaceRef {

`

``

135

`+

base: &PlaceBase::Local(local),

`

``

136

`+

projection: &[ProjectionElem::Downcast(_, var_idx), ProjectionElem::Field(field, ty)],

`

``

137

`+

} => Some((local, VarField { field, field_ty: ty, var_idx })),

`

``

138

`+

_ => None,

`

``

139

`+

}

`

``

140

`+

}

`

``

141

+

``

142

`` +

/// Simplifies SwitchInt(_) -> [targets],

``

``

143

`` +

/// where all the targets have the same form,

``

``

144

`` +

/// into goto -> target_first.

``

``

145

`+

pub struct SimplifyBranchSame;

`

``

146

+

``

147

`+

impl<'tcx> MirPass<'tcx> for SimplifyBranchSame {

`

``

148

`+

fn run_pass(&self, _: TyCtxt<'tcx>, _: MirSource<'tcx>, body: &mut Body<'tcx>) {

`

``

149

`+

let mut did_remove_blocks = false;

`

``

150

`+

let bbs = body.basic_blocks_mut();

`

``

151

`+

for bb_idx in bbs.indices() {

`

``

152

`+

let targets = match &bbs[bb_idx].terminator().kind {

`

``

153

`+

TerminatorKind::SwitchInt { targets, .. } => targets,

`

``

154

`+

_ => continue,

`

``

155

`+

};

`

``

156

+

``

157

`+

let mut iter_bbs_reachable = targets

`

``

158

`+

.iter()

`

``

159

`+

.map(|idx| (*idx, &bbs[*idx]))

`

``

160

`+

.filter(|(_, bb)| {

`

``

161

`` +

// Reaching unreachable is UB so assume it doesn't happen.

``

``

162

`+

bb.terminator().kind != TerminatorKind::Unreachable

`

``

163

`` +

// But asm!(...) could abort the program,

``

``

164

`` +

// so we cannot assume that the unreachable terminator itself is reachable.

``

``

165

`+

// FIXME(Centril): use a normalization pass instead of a check.

`

``

166

`+

|| bb.statements.iter().any(|stmt| match stmt.kind {

`

``

167

`+

StatementKind::InlineAsm(..) => true,

`

``

168

`+

_ => false,

`

``

169

`+

})

`

``

170

`+

})

`

``

171

`+

.peekable();

`

``

172

+

``

173

`` +

// We want to goto -> bb_first.

``

``

174

`+

let bb_first = iter_bbs_reachable

`

``

175

`+

.peek()

`

``

176

`+

.map(|(idx, _)| *idx)

`

``

177

`+

.unwrap_or(targets[0]);

`

``

178

+

``

179

`+

// All successor basic blocks should have the exact same form.

`

``

180

`+

let all_successors_equivalent = iter_bbs_reachable

`

``

181

`+

.map(|(_, bb)| bb)

`

``

182

`+

.tuple_windows()

`

``

183

`+

.all(|(bb_l, bb_r)| {

`

``

184

`+

bb_l.is_cleanup == bb_r.is_cleanup

`

``

185

`+

&& bb_l.terminator().kind == bb_r.terminator().kind

`

``

186

`+

&& bb_l.statements.iter().eq_by(&bb_r.statements, |x, y| x.kind == y.kind)

`

``

187

`+

});

`

``

188

+

``

189

`+

if all_successors_equivalent {

`

``

190

`` +

// Replace SwitchInt(..) -> [bb_first, ..]; with a goto -> bb_first;.

``

``

191

`+

bbs[bb_idx].terminator_mut().kind = TerminatorKind::Goto { target: bb_first };

`

``

192

`+

did_remove_blocks = true;

`

``

193

`+

}

`

``

194

`+

}

`

``

195

+

``

196

`+

if did_remove_blocks {

`

``

197

`+

// We have dead blocks now, so remove those.

`

``

198

`+

simplify::remove_dead_blocks(body);

`

``

199

`+

}

`

``

200

`+

}

`

``

201

`+

}

`