Skip to content

Commit df6fdbc

Browse files
committed
fix closure inlining by spilling arguments to a temporary
1 parent 83f5a96 commit df6fdbc

File tree

3 files changed

+93
-10
lines changed

3 files changed

+93
-10
lines changed

src/librustc_mir/transform/inline.rs

+39-8
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use rustc::ty::{self, Instance, Ty, TyCtxt, TypeFoldable};
2222
use rustc::ty::subst::{Subst,Substs};
2323

2424
use std::collections::VecDeque;
25+
use std::iter;
2526
use transform::{MirPass, MirSource};
2627
use super::simplify::{remove_dead_blocks, CfgSimplifier};
2728

@@ -558,8 +559,29 @@ impl<'a, 'tcx> Inliner<'a, 'tcx> {
558559
) -> Vec<Operand<'tcx>> {
559560
let tcx = self.tcx;
560561

561-
// A closure is passed its self-type and a tuple like `(arg1, arg2, ...)`,
562-
// hence mappings to tuple fields are needed.
562+
// There is a bit of a mismatch between the *caller* of a closure and the *callee*.
563+
// The caller provides the arguments wrapped up in a tuple:
564+
//
565+
// tuple_tmp = (a, b, c)
566+
// Fn::call(closure_ref, tuple_tmp)
567+
//
568+
// meanwhile the closure body expects the arguments (here, `a`, `b`, and `c`)
569+
// as distinct arguments. (This is the "rust-call" ABI hack.) Normally, trans has
570+
// the job of unpacking this tuple. But here, we are trans. =) So we want to create
571+
// a vector like
572+
//
573+
// [closure_ref, tuple_tmp.0, tuple_tmp.1, tuple_tmp.2]
574+
//
575+
// Except for one tiny wrinkle: we don't actually want `tuple_tmp.0`. It's more convenient
576+
// if we "spill" that into *another* temporary, so that we can map the argument
577+
// variable in the callee MIR directly to an argument variable on our side.
578+
// So we introduce temporaries like:
579+
//
580+
// tmp0 = tuple_tmp.0
581+
// tmp1 = tuple_tmp.1
582+
// tmp2 = tuple_tmp.2
583+
//
584+
// and the vector is `[closure_ref, tmp0, tmp1, tmp2]`.
563585
if tcx.is_closure(callsite.callee) {
564586
let mut args = args.into_iter();
565587
let self_ = self.create_temp_if_necessary(args.next().unwrap(), callsite, caller_mir);
@@ -572,12 +594,21 @@ impl<'a, 'tcx> Inliner<'a, 'tcx> {
572594
bug!("Closure arguments are not passed as a tuple");
573595
};
574596

575-
let mut res = Vec::with_capacity(1 + tuple_tys.len());
576-
res.push(Operand::Consume(self_));
577-
res.extend(tuple_tys.iter().enumerate().map(|(i, ty)| {
578-
Operand::Consume(tuple.clone().field(Field::new(i), ty))
579-
}));
580-
res
597+
// The `closure_ref` in our example above.
598+
let closure_ref_arg = iter::once(Operand::Consume(self_));
599+
600+
// The `tmp0`, `tmp1`, and `tmp2` in our example abonve.
601+
let tuple_tmp_args =
602+
tuple_tys.iter().enumerate().map(|(i, ty)| {
603+
// This is e.g. `tuple_tmp.0` in our example above.
604+
let tuple_field = Operand::Consume(tuple.clone().field(Field::new(i), ty));
605+
606+
// Spill to a local to make e.g. `tmp0`.
607+
let tmp = self.create_temp_if_necessary(tuple_field, callsite, caller_mir);
608+
Operand::Consume(tmp)
609+
});
610+
611+
closure_ref_arg.chain(tuple_tmp_args).collect()
581612
} else {
582613
args.into_iter()
583614
.map(|a| Operand::Consume(self.create_temp_if_necessary(a, callsite, caller_mir)))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
// compile-flags: -Z span_free_formats
12+
13+
// Tests that MIR inliner can handle closure arguments,
14+
// even when (#45894)
15+
16+
fn main() {
17+
println!("{}", foo(0, &14));
18+
}
19+
20+
fn foo<T: Copy>(_t: T, q: &i32) -> i32 {
21+
let x = |r: &i32, _s: &i32| {
22+
let variable = &*r;
23+
*variable
24+
};
25+
x(q, q)
26+
}
27+
28+
// END RUST SOURCE
29+
// START rustc.foo.Inline.after.mir
30+
// ...
31+
// bb0: {
32+
// ...
33+
// _3 = [closure@NodeId(39)];
34+
// ...
35+
// _4 = &_3;
36+
// ...
37+
// _6 = &(*_2);
38+
// ...
39+
// _7 = &(*_2);
40+
// _5 = (_6, _7);
41+
// _9 = (_5.0: &i32);
42+
// _10 = (_5.1: &i32);
43+
// StorageLive(_8);
44+
// _8 = (*_9);
45+
// _0 = _8;
46+
// ...
47+
// return;
48+
// }
49+
// ...
50+
// END rustc.foo.Inline.after.mir

src/test/mir-opt/inline-closure.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,11 @@ fn foo<T: Copy>(_t: T, q: i32) -> i32 {
3434
// ...
3535
// _7 = _2;
3636
// _5 = (_6, _7);
37-
// _0 = (_5.0: i32);
37+
// _8 = (_5.0: i32);
38+
// _9 = (_5.1: i32);
39+
// _0 = _8;
3840
// ...
3941
// return;
4042
// }
4143
// ...
42-
// END rustc.foo.Inline.after.mir
44+
// END rustc.foo.Inline.after.mir

0 commit comments

Comments
 (0)