Skip to content

Commit f242fe3

Browse files
committed
Various improvements to MIR and LLVM IR Construction
Primarily affects the MIR construction, which indirectly improves LLVM IR generation, but some LLVM IR changes have been made too. * Handle "statement expressions" more intelligently. These are expressions that always evaluate to `()`. Previously a temporary would be generated as a destination to translate into, which is unnecessary. This affects assignment, augmented assignment, `return`, `break` and `continue`. * Avoid inserting drops for non-drop types in more places. Scheduled drops were already skipped for types that we knew wouldn't need dropping at construction time. However manually-inserted drops like those for `x` in `x = y;` were still generated. `build_drop` now takes a type parameter like its `schedule_drop` counterpart and checks to see if the type needs dropping. * Avoid generating an extra temporary for an assignment where the types involved don't need dropping. Previously an expression like `a = b + 1;` would result in a temporary for `b + 1`. This is so the RHS can be evaluated, then the LHS evaluated and dropped and have everything work correctly. However, this isn't necessary if the `LHS` doesn't need a drop, as we can just overwrite the existing value. * Improves lvalue analysis to allow treating an `Rvalue::Use` as an operand in certain conditions. The reason for it never being an operand is so it can be zeroed/drop-filled, but this is only true for types that need dropping. The first two changes result in significantly fewer MIR blocks being generated, as previously almost every statement would end up generating a new block due to the drop of the `()` temporary being generated.
1 parent cda7c1c commit f242fe3

File tree

8 files changed

+195
-107
lines changed

8 files changed

+195
-107
lines changed

src/librustc_mir/build/block.rs

Lines changed: 118 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,12 @@
99
// except according to those terms.
1010

1111
use build::{BlockAnd, BlockAndExtension, Builder};
12+
use build::scope::LoopScope;
1213
use hair::*;
14+
use rustc::middle::region::CodeExtent;
1315
use rustc::mir::repr::*;
1416
use rustc::hir;
17+
use syntax::codemap::Span;
1518

1619
impl<'a,'tcx> Builder<'a,'tcx> {
1720
pub fn ast_block(&mut self,
@@ -44,11 +47,7 @@ impl<'a,'tcx> Builder<'a,'tcx> {
4447
StmtKind::Expr { scope, expr } => {
4548
unpack!(block = this.in_scope(scope, block, |this, _| {
4649
let expr = this.hir.mirror(expr);
47-
let expr_span = expr.span;
48-
let temp = this.temp(expr.ty.clone());
49-
unpack!(block = this.into(&temp, block, expr));
50-
unpack!(block = this.build_drop(block, expr_span, temp));
51-
block.unit()
50+
this.stmt_expr(block, expr)
5251
}));
5352
}
5453
StmtKind::Let { remainder_scope, init_scope, pattern, initializer } => {
@@ -83,4 +82,118 @@ impl<'a,'tcx> Builder<'a,'tcx> {
8382
block.unit()
8483
})
8584
}
85+
86+
pub fn stmt_expr(&mut self, mut block: BasicBlock, expr: Expr<'tcx>) -> BlockAnd<()> {
87+
let this = self;
88+
let expr_span = expr.span;
89+
let scope_id = this.innermost_scope_id();
90+
// Handle a number of expressions that don't need a destination at all. This
91+
// avoids needing a mountain of temporary `()` variables.
92+
match expr.kind {
93+
ExprKind::Scope { extent, value } => {
94+
let value = this.hir.mirror(value);
95+
this.in_scope(extent, block, |this, _| this.stmt_expr(block, value))
96+
}
97+
ExprKind::Assign { lhs, rhs } => {
98+
let lhs = this.hir.mirror(lhs);
99+
let scope_id = this.innermost_scope_id();
100+
let lhs_span = lhs.span;
101+
let lhs_ty = lhs.ty;
102+
103+
let lhs_needs_drop = this.hir.needs_drop(lhs_ty);
104+
105+
// Note: we evaluate assignments right-to-left. This
106+
// is better for borrowck interaction with overloaded
107+
// operators like x[j] = x[i].
108+
109+
// Generate better code for things that don't need to be
110+
// dropped. We need the temporary as_operand generates
111+
// so we can clean up the data if evaluating the LHS unwinds,
112+
// but if the LHS (and therefore the RHS) doesn't need
113+
// unwinding, we just translate directly to an rvalue instead.
114+
let rhs = if lhs_needs_drop {
115+
let op = unpack!(block = this.as_operand(block, rhs));
116+
Rvalue::Use(op)
117+
} else {
118+
unpack!(block = this.as_rvalue(block, rhs))
119+
};
120+
121+
let lhs = unpack!(block = this.as_lvalue(block, lhs));
122+
unpack!(block = this.build_drop(block, lhs_span, lhs.clone(), lhs_ty));
123+
this.cfg.push_assign(block, scope_id, expr_span, &lhs, rhs);
124+
block.unit()
125+
}
126+
ExprKind::AssignOp { op, lhs, rhs } => {
127+
// FIXME(#28160) there is an interesting semantics
128+
// question raised here -- should we "freeze" the
129+
// value of the lhs here? I'm inclined to think not,
130+
// since it seems closer to the semantics of the
131+
// overloaded version, which takes `&mut self`. This
132+
// only affects weird things like `x += {x += 1; x}`
133+
// -- is that equal to `x + (x + 1)` or `2*(x+1)`?
134+
135+
// As above, RTL.
136+
let rhs = unpack!(block = this.as_operand(block, rhs));
137+
let lhs = unpack!(block = this.as_lvalue(block, lhs));
138+
139+
// we don't have to drop prior contents or anything
140+
// because AssignOp is only legal for Copy types
141+
// (overloaded ops should be desugared into a call).
142+
this.cfg.push_assign(block, scope_id, expr_span, &lhs,
143+
Rvalue::BinaryOp(op,
144+
Operand::Consume(lhs.clone()),
145+
rhs));
146+
147+
block.unit()
148+
}
149+
ExprKind::Continue { label } => {
150+
this.break_or_continue(expr_span, label, block,
151+
|loop_scope| loop_scope.continue_block)
152+
}
153+
ExprKind::Break { label } => {
154+
this.break_or_continue(expr_span, label, block, |loop_scope| {
155+
loop_scope.might_break = true;
156+
loop_scope.break_block
157+
})
158+
}
159+
ExprKind::Return { value } => {
160+
block = match value {
161+
Some(value) => unpack!(this.into(&Lvalue::ReturnPointer, block, value)),
162+
None => {
163+
this.cfg.push_assign_unit(block, scope_id,
164+
expr_span, &Lvalue::ReturnPointer);
165+
block
166+
}
167+
};
168+
let extent = this.extent_of_return_scope();
169+
let return_block = this.return_block();
170+
this.exit_scope(expr_span, extent, block, return_block);
171+
this.cfg.start_new_block().unit()
172+
}
173+
_ => {
174+
let expr_span = expr.span;
175+
let expr_ty = expr.ty;
176+
let temp = this.temp(expr.ty.clone());
177+
unpack!(block = this.into(&temp, block, expr));
178+
unpack!(block = this.build_drop(block, expr_span, temp, expr_ty));
179+
block.unit()
180+
}
181+
}
182+
}
183+
184+
fn break_or_continue<F>(&mut self,
185+
span: Span,
186+
label: Option<CodeExtent>,
187+
block: BasicBlock,
188+
exit_selector: F)
189+
-> BlockAnd<()>
190+
where F: FnOnce(&mut LoopScope) -> BasicBlock
191+
{
192+
let (exit_block, extent) = {
193+
let loop_scope = self.find_loop_scope(span, label);
194+
(exit_selector(loop_scope), loop_scope.extent)
195+
};
196+
self.exit_scope(span, extent, block, exit_block);
197+
self.cfg.start_new_block().unit()
198+
}
86199
}

src/librustc_mir/build/expr/into.rs

Lines changed: 9 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,9 @@
1212
1313
use build::{BlockAnd, BlockAndExtension, Builder};
1414
use build::expr::category::{Category, RvalueFunc};
15-
use build::scope::LoopScope;
1615
use hair::*;
17-
use rustc::middle::region::CodeExtent;
1816
use rustc::ty;
1917
use rustc::mir::repr::*;
20-
use syntax::codemap::Span;
2118

2219
impl<'a,'tcx> Builder<'a,'tcx> {
2320
/// Compile `expr`, storing the result into `destination`, which
@@ -207,65 +204,6 @@ impl<'a,'tcx> Builder<'a,'tcx> {
207204
}
208205
exit_block.unit()
209206
}
210-
ExprKind::Assign { lhs, rhs } => {
211-
// Note: we evaluate assignments right-to-left. This
212-
// is better for borrowck interaction with overloaded
213-
// operators like x[j] = x[i].
214-
let lhs = this.hir.mirror(lhs);
215-
let lhs_span = lhs.span;
216-
let rhs = unpack!(block = this.as_operand(block, rhs));
217-
let lhs = unpack!(block = this.as_lvalue(block, lhs));
218-
unpack!(block = this.build_drop(block, lhs_span, lhs.clone()));
219-
this.cfg.push_assign(block, scope_id, expr_span, &lhs, Rvalue::Use(rhs));
220-
block.unit()
221-
}
222-
ExprKind::AssignOp { op, lhs, rhs } => {
223-
// FIXME(#28160) there is an interesting semantics
224-
// question raised here -- should we "freeze" the
225-
// value of the lhs here? I'm inclined to think not,
226-
// since it seems closer to the semantics of the
227-
// overloaded version, which takes `&mut self`. This
228-
// only affects weird things like `x += {x += 1; x}`
229-
// -- is that equal to `x + (x + 1)` or `2*(x+1)`?
230-
231-
// As above, RTL.
232-
let rhs = unpack!(block = this.as_operand(block, rhs));
233-
let lhs = unpack!(block = this.as_lvalue(block, lhs));
234-
235-
// we don't have to drop prior contents or anything
236-
// because AssignOp is only legal for Copy types
237-
// (overloaded ops should be desugared into a call).
238-
this.cfg.push_assign(block, scope_id, expr_span, &lhs,
239-
Rvalue::BinaryOp(op,
240-
Operand::Consume(lhs.clone()),
241-
rhs));
242-
243-
block.unit()
244-
}
245-
ExprKind::Continue { label } => {
246-
this.break_or_continue(expr_span, label, block,
247-
|loop_scope| loop_scope.continue_block)
248-
}
249-
ExprKind::Break { label } => {
250-
this.break_or_continue(expr_span, label, block, |loop_scope| {
251-
loop_scope.might_break = true;
252-
loop_scope.break_block
253-
})
254-
}
255-
ExprKind::Return { value } => {
256-
block = match value {
257-
Some(value) => unpack!(this.into(&Lvalue::ReturnPointer, block, value)),
258-
None => {
259-
this.cfg.push_assign_unit(block, scope_id,
260-
expr_span, &Lvalue::ReturnPointer);
261-
block
262-
}
263-
};
264-
let extent = this.extent_of_return_scope();
265-
let return_block = this.return_block();
266-
this.exit_scope(expr_span, extent, block, return_block);
267-
this.cfg.start_new_block().unit()
268-
}
269207
ExprKind::Call { ty, fun, args } => {
270208
let diverges = match ty.sty {
271209
ty::TyFnDef(_, _, ref f) | ty::TyFnPtr(ref f) => {
@@ -294,6 +232,15 @@ impl<'a,'tcx> Builder<'a,'tcx> {
294232
success.unit()
295233
}
296234

235+
// These cases don't actually need a destination
236+
ExprKind::Assign { .. } |
237+
ExprKind::AssignOp { .. } |
238+
ExprKind::Continue { .. } |
239+
ExprKind::Break { .. } |
240+
ExprKind::Return {.. } => {
241+
this.stmt_expr(block, expr)
242+
}
243+
297244
// these are the cases that are more naturally handled by some other mode
298245
ExprKind::Unary { .. } |
299246
ExprKind::Binary { .. } |
@@ -327,20 +274,4 @@ impl<'a,'tcx> Builder<'a,'tcx> {
327274
}
328275
}
329276
}
330-
331-
fn break_or_continue<F>(&mut self,
332-
span: Span,
333-
label: Option<CodeExtent>,
334-
block: BasicBlock,
335-
exit_selector: F)
336-
-> BlockAnd<()>
337-
where F: FnOnce(&mut LoopScope) -> BasicBlock
338-
{
339-
let (exit_block, extent) = {
340-
let loop_scope = self.find_loop_scope(span, label);
341-
(exit_selector(loop_scope), loop_scope.extent)
342-
};
343-
self.exit_scope(span, extent, block, exit_block);
344-
self.cfg.start_new_block().unit()
345-
}
346277
}

src/librustc_mir/build/expr/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,5 +75,5 @@ mod as_lvalue;
7575
mod as_rvalue;
7676
mod as_operand;
7777
mod as_temp;
78-
mod category;
78+
pub mod category;
7979
mod into;

src/librustc_mir/build/scope.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -497,8 +497,11 @@ impl<'a,'tcx> Builder<'a,'tcx> {
497497
pub fn build_drop(&mut self,
498498
block: BasicBlock,
499499
span: Span,
500-
value: Lvalue<'tcx>)
501-
-> BlockAnd<()> {
500+
value: Lvalue<'tcx>,
501+
ty: Ty<'tcx>) -> BlockAnd<()> {
502+
if !self.hir.needs_drop(ty) {
503+
return block.unit();
504+
}
502505
let scope_id = self.innermost_scope_id();
503506
let next_target = self.cfg.start_new_block();
504507
let diverge_target = self.diverge_cleanup();

src/librustc_trans/mir/analyze.rs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use super::rvalue;
2020
pub fn lvalue_temps<'bcx,'tcx>(bcx: Block<'bcx,'tcx>,
2121
mir: &mir::Mir<'tcx>)
2222
-> BitVector {
23-
let mut analyzer = TempAnalyzer::new(mir.temp_decls.len());
23+
let mut analyzer = TempAnalyzer::new(mir, bcx, mir.temp_decls.len());
2424

2525
analyzer.visit_mir(mir);
2626

@@ -30,7 +30,8 @@ pub fn lvalue_temps<'bcx,'tcx>(bcx: Block<'bcx,'tcx>,
3030
if ty.is_scalar() ||
3131
ty.is_unique() ||
3232
ty.is_region_ptr() ||
33-
ty.is_simd()
33+
ty.is_simd() ||
34+
common::type_is_zero_size(bcx.ccx(), ty)
3435
{
3536
// These sorts of types are immediates that we can store
3637
// in an ValueRef without an alloca.
@@ -50,14 +51,20 @@ pub fn lvalue_temps<'bcx,'tcx>(bcx: Block<'bcx,'tcx>,
5051
analyzer.lvalue_temps
5152
}
5253

53-
struct TempAnalyzer {
54+
struct TempAnalyzer<'mir, 'bcx, 'tcx: 'mir + 'bcx> {
55+
mir: &'mir mir::Mir<'tcx>,
56+
bcx: Block<'bcx, 'tcx>,
5457
lvalue_temps: BitVector,
5558
seen_assigned: BitVector
5659
}
5760

58-
impl TempAnalyzer {
59-
fn new(temp_count: usize) -> TempAnalyzer {
61+
impl<'mir, 'bcx, 'tcx> TempAnalyzer<'mir, 'bcx, 'tcx> {
62+
fn new(mir: &'mir mir::Mir<'tcx>,
63+
bcx: Block<'bcx, 'tcx>,
64+
temp_count: usize) -> TempAnalyzer<'mir, 'bcx, 'tcx> {
6065
TempAnalyzer {
66+
mir: mir,
67+
bcx: bcx,
6168
lvalue_temps: BitVector::new(temp_count),
6269
seen_assigned: BitVector::new(temp_count)
6370
}
@@ -75,7 +82,7 @@ impl TempAnalyzer {
7582
}
7683
}
7784

78-
impl<'tcx> Visitor<'tcx> for TempAnalyzer {
85+
impl<'mir, 'bcx, 'tcx> Visitor<'tcx> for TempAnalyzer<'mir, 'bcx, 'tcx> {
7986
fn visit_assign(&mut self,
8087
block: mir::BasicBlock,
8188
lvalue: &mir::Lvalue<'tcx>,
@@ -85,7 +92,7 @@ impl<'tcx> Visitor<'tcx> for TempAnalyzer {
8592
match *lvalue {
8693
mir::Lvalue::Temp(index) => {
8794
self.mark_assigned(index as usize);
88-
if !rvalue::rvalue_creates_operand(rvalue) {
95+
if !rvalue::rvalue_creates_operand(self.mir, self.bcx, rvalue) {
8996
self.mark_as_lvalue(index as usize);
9097
}
9198
}

src/librustc_trans/mir/mod.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ use rustc_data_structures::bitvec::BitVector;
3434
use self::lvalue::{LvalueRef, get_dataptr, get_meta};
3535
use rustc_mir::traversal;
3636

37-
use self::operand::OperandRef;
37+
use self::operand::{OperandRef, OperandValue};
3838

3939
#[derive(Clone)]
4040
pub enum CachedMir<'mir, 'tcx: 'mir> {
@@ -150,6 +150,15 @@ pub fn trans_mir<'blk, 'tcx: 'blk>(fcx: &'blk FunctionContext<'blk, 'tcx>) {
150150
TempRef::Lvalue(LvalueRef::alloca(&bcx,
151151
mty,
152152
&format!("temp{:?}", i)))
153+
} else if common::type_is_zero_size(bcx.ccx(), mty) {
154+
// Zero-size temporaries aren't always initialized, which
155+
// doesn't matter because they don't contain data, but
156+
// we need something in the operand.
157+
let op = OperandRef {
158+
val: OperandValue::Immediate(common::C_nil(bcx.ccx())),
159+
ty: mty
160+
};
161+
TempRef::Operand(Some(op))
153162
} else {
154163
// If this is an immediate temp, we do not create an
155164
// alloca in advance. Instead we wait until we see the

0 commit comments

Comments
 (0)