Skip to content

Commit 392e595

Browse files
Fix miscompilation
1 parent 9fa46fe commit 392e595

File tree

3 files changed

+100
-13
lines changed

3 files changed

+100
-13
lines changed

src/librustc_mir/transform/generator.rs

+53-10
Original file line numberDiff line numberDiff line change
@@ -380,28 +380,35 @@ fn make_generator_state_argument_pinned<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body
380380
PinArgVisitor { ref_gen_ty, tcx }.visit_body(body);
381381
}
382382

383-
fn replace_result_variable<'tcx>(
384-
ret_ty: Ty<'tcx>,
383+
/// Allocates a new local and replaces all references of `local` with it. Returns the new local.
384+
///
385+
/// `local` will be changed to a new local decl with type `ty`.
386+
///
387+
/// Note that the new local will be uninitialized. It is the caller's responsibility to assign some
388+
/// valid value to it before its first use.
389+
fn replace_local<'tcx>(
390+
local: Local,
391+
ty: Ty<'tcx>,
385392
body: &mut BodyAndCache<'tcx>,
386393
tcx: TyCtxt<'tcx>,
387394
) -> Local {
388395
let source_info = source_info(body);
389-
let new_ret = LocalDecl {
396+
let new_decl = LocalDecl {
390397
mutability: Mutability::Mut,
391-
ty: ret_ty,
398+
ty,
392399
user_ty: UserTypeProjections::none(),
393400
source_info,
394401
internal: false,
395402
is_block_tail: None,
396403
local_info: LocalInfo::Other,
397404
};
398-
let new_ret_local = Local::new(body.local_decls.len());
399-
body.local_decls.push(new_ret);
400-
body.local_decls.swap(RETURN_PLACE, new_ret_local);
405+
let new_local = Local::new(body.local_decls.len());
406+
body.local_decls.push(new_decl);
407+
body.local_decls.swap(local, new_local);
401408

402-
RenameLocalVisitor { from: RETURN_PLACE, to: new_ret_local, tcx }.visit_body(body);
409+
RenameLocalVisitor { from: local, to: new_local, tcx }.visit_body(body);
403410

404-
new_ret_local
411+
new_local
405412
}
406413

407414
struct StorageIgnored(liveness::LiveVarSet);
@@ -794,6 +801,10 @@ fn compute_layout<'tcx>(
794801
(remap, layout, storage_liveness)
795802
}
796803

804+
/// Replaces the entry point of `body` with a block that switches on the generator discriminant and
805+
/// dispatches to blocks according to `cases`.
806+
///
807+
/// After this function, the former entry point of the function will be bb1.
797808
fn insert_switch<'tcx>(
798809
body: &mut BodyAndCache<'tcx>,
799810
cases: Vec<(usize, BasicBlock)>,
@@ -1093,6 +1104,13 @@ fn create_cases<'tcx>(
10931104

10941105
// Create StorageLive instructions for locals with live storage
10951106
for i in 0..(body.local_decls.len()) {
1107+
if i == 2 {
1108+
// The resume argument is live on function entry. Don't insert a
1109+
// `StorageLive`, or the following `Assign` will read from uninitialized
1110+
// memory.
1111+
continue;
1112+
}
1113+
10961114
let l = Local::new(i);
10971115
if point.storage_liveness.contains(l) && !transform.remap.contains_key(&l) {
10981116
statements
@@ -1110,6 +1128,10 @@ fn create_cases<'tcx>(
11101128
Rvalue::Use(Operand::Move(resume_arg.into())),
11111129
)),
11121130
});
1131+
statements.push(Statement {
1132+
source_info,
1133+
kind: StatementKind::StorageDead(resume_arg),
1134+
});
11131135
}
11141136

11151137
// Then jump to the real target
@@ -1166,7 +1188,28 @@ impl<'tcx> MirPass<'tcx> for StateTransform {
11661188

11671189
// We rename RETURN_PLACE which has type mir.return_ty to new_ret_local
11681190
// RETURN_PLACE then is a fresh unused local with type ret_ty.
1169-
let new_ret_local = replace_result_variable(ret_ty, body, tcx);
1191+
let new_ret_local = replace_local(RETURN_PLACE, ret_ty, body, tcx);
1192+
1193+
// We also replace the resume argument and insert an `Assign`.
1194+
// This is needed because the resume argument might be live across a `yield`, and the
1195+
// transform assumes that any local live across a `yield` is assigned to before that.
1196+
let resume_local = Local::new(2);
1197+
let new_resume_local =
1198+
replace_local(resume_local, body.local_decls[resume_local].ty, body, tcx);
1199+
1200+
// When first entering the generator, move the resume argument into its new local.
1201+
let source_info = source_info(body);
1202+
let stmts = &mut body.basic_blocks_mut()[BasicBlock::new(0)].statements;
1203+
stmts.insert(
1204+
0,
1205+
Statement {
1206+
source_info,
1207+
kind: StatementKind::Assign(box (
1208+
new_resume_local.into(),
1209+
Rvalue::Use(Operand::Move(resume_local.into())),
1210+
)),
1211+
},
1212+
);
11701213

11711214
// Extract locals which are live across suspension point into `layout`
11721215
// `remap` gives a mapping from local indices onto generator struct indices

src/test/mir-opt/generator-drop-cleanup.rs

+2-3
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ fn main() {
1313

1414
// START rustc.main-{{closure}}.generator_drop.0.mir
1515
// bb0: {
16-
// _6 = discriminant((*_1));
17-
// switchInt(move _6) -> [0u32: bb4, 3u32: bb7, otherwise: bb8];
16+
// _7 = discriminant((*_1));
17+
// switchInt(move _7) -> [0u32: bb4, 3u32: bb7, otherwise: bb8];
1818
// }
1919
// bb1: {
2020
// StorageDead(_4);
@@ -37,7 +37,6 @@ fn main() {
3737
// goto -> bb3;
3838
// }
3939
// bb7: {
40-
// StorageLive(_2);
4140
// StorageLive(_3);
4241
// StorageLive(_4);
4342
// goto -> bb1;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// run-pass
2+
3+
#![feature(generators, generator_trait)]
4+
5+
use std::ops::{Generator, GeneratorState};
6+
use std::pin::Pin;
7+
use std::sync::atomic::{AtomicUsize, Ordering};
8+
9+
static DROP: AtomicUsize = AtomicUsize::new(0);
10+
11+
#[derive(PartialEq, Eq, Debug)]
12+
struct Dropper(String);
13+
14+
impl Drop for Dropper {
15+
fn drop(&mut self) {
16+
DROP.fetch_add(1, Ordering::SeqCst);
17+
}
18+
}
19+
20+
fn main() {
21+
let mut g = |mut _d| {
22+
_d = yield;
23+
_d
24+
};
25+
26+
let mut g = Pin::new(&mut g);
27+
28+
assert_eq!(
29+
g.as_mut().resume(Dropper(String::from("Hello world!"))),
30+
GeneratorState::Yielded(())
31+
);
32+
assert_eq!(DROP.load(Ordering::Acquire), 0);
33+
match g.as_mut().resume(Dropper(String::from("Number Two"))) {
34+
GeneratorState::Complete(dropper) => {
35+
assert_eq!(DROP.load(Ordering::Acquire), 1);
36+
assert_eq!(dropper.0, "Number Two");
37+
drop(dropper);
38+
assert_eq!(DROP.load(Ordering::Acquire), 2);
39+
}
40+
_ => unreachable!(),
41+
}
42+
43+
drop(g);
44+
assert_eq!(DROP.load(Ordering::Acquire), 2);
45+
}

0 commit comments

Comments
 (0)