Skip to content

Commit fe107b3

Browse files
committed
Auto merge of #25959 - pnkfelix:fsk-hack-move-val-init, r=nikomatsakis
Hack the move_val_init intrinsic to trans directly into the destination address. This is to remove an intermediate (and unnecessary) alloca on the stack that one otherwise suffers when using this intrinsic. This is part of the `box` protocol work; in particular, this is meant to address the `ptr::write` codegen issues alluded to at this comment: #22086 (comment) cc #22181
2 parents b70f49b + 0b74800 commit fe107b3

File tree

2 files changed

+236
-16
lines changed

2 files changed

+236
-16
lines changed

src/librustc_trans/trans/intrinsic.rs

+42-16
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,48 @@ pub fn trans_intrinsic_call<'a, 'blk, 'tcx>(mut bcx: Block<'blk, 'tcx>,
265265
}
266266
}
267267

268+
// For `move_val_init` we can evaluate the destination address
269+
// (the first argument) and then trans the source value (the
270+
// second argument) directly into the resulting destination
271+
// address.
272+
if &name[..] == "move_val_init" {
273+
if let callee::ArgExprs(ref exprs) = args {
274+
let (dest_expr, source_expr) = if exprs.len() != 2 {
275+
ccx.sess().bug("expected two exprs as arguments for `move_val_init` intrinsic");
276+
} else {
277+
(&exprs[0], &exprs[1])
278+
};
279+
let arg_tys = ty::erase_late_bound_regions(bcx.tcx(), &ty::ty_fn_args(callee_ty));
280+
281+
// evaluate destination address
282+
let lldest_addr = unpack_result!(bcx, {
283+
let dest_datum = unpack_datum!(bcx, expr::trans(bcx, dest_expr));
284+
callee::trans_arg_datum(bcx,
285+
arg_tys[0],
286+
dest_datum,
287+
cleanup::CustomScope(cleanup_scope),
288+
callee::DontAutorefArg)
289+
});
290+
291+
// `expr::trans_into(bcx, expr, dest)` is equiv to
292+
//
293+
// `trans(bcx, expr).store_to_dest(dest)`,
294+
//
295+
// which for `dest == expr::SaveIn(addr)`, is equivalent to:
296+
//
297+
// `trans(bcx, expr).store_to(bcx, addr)`.
298+
let lldest = expr::Dest::SaveIn(lldest_addr);
299+
bcx = expr::trans_into(bcx, source_expr, lldest);
300+
301+
let llresult = C_nil(ccx);
302+
fcx.pop_and_trans_custom_cleanup_scope(bcx, cleanup_scope);
303+
304+
return Result::new(bcx, llresult);
305+
} else {
306+
ccx.sess().bug("expected two exprs as arguments for `move_val_init` intrinsic");
307+
}
308+
}
309+
268310
// Push the arguments.
269311
let mut llargs = Vec::new();
270312
bcx = callee::trans_args(bcx,
@@ -356,22 +398,6 @@ pub fn trans_intrinsic_call<'a, 'blk, 'tcx>(mut bcx: Block<'blk, 'tcx>,
356398
let lltp_ty = type_of::type_of(ccx, tp_ty);
357399
C_uint(ccx, machine::llalign_of_pref(ccx, lltp_ty))
358400
}
359-
(_, "move_val_init") => {
360-
// Create a datum reflecting the value being moved.
361-
// Use `appropriate_mode` so that the datum is by ref
362-
// if the value is non-immediate. Note that, with
363-
// intrinsics, there are no argument cleanups to
364-
// concern ourselves with, so we can use an rvalue datum.
365-
let tp_ty = *substs.types.get(FnSpace, 0);
366-
let mode = appropriate_rvalue_mode(ccx, tp_ty);
367-
let src = Datum {
368-
val: llargs[1],
369-
ty: tp_ty,
370-
kind: Rvalue::new(mode)
371-
};
372-
bcx = src.store_to(bcx, llargs[0]);
373-
C_nil(ccx)
374-
}
375401
(_, "drop_in_place") => {
376402
let tp_ty = *substs.types.get(FnSpace, 0);
377403
glue::drop_ty(bcx, llargs[0], tp_ty, call_debug_location);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
// Copyright 2015 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+
// This test is checking that the move_val_init intrinsic is
12+
// respecting cleanups for both of its argument expressions.
13+
//
14+
// In other words, if either DEST or SOURCE in
15+
//
16+
// `intrinsics::move_val_init(DEST, SOURCE)
17+
//
18+
// introduce temporaries that require cleanup, and SOURCE panics, then
19+
// make sure the cleanups still occur.
20+
21+
#![feature(core, std_misc)]
22+
23+
use std::cell::RefCell;
24+
use std::intrinsics;
25+
use std::sync::{Arc, LockResult, Mutex, MutexGuard};
26+
use std::thread;
27+
28+
type LogEntry = (&'static str, i32);
29+
type Guarded = RefCell<Vec<LogEntry>>;
30+
#[derive(Clone)]
31+
struct Log(Arc<Mutex<Guarded>>);
32+
struct Acquired<'a>(MutexGuard<'a, Guarded>);
33+
type LogState = (MutexWas, &'static [LogEntry]);
34+
35+
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
36+
enum MutexWas { Poisoned, NotPoisoned }
37+
38+
impl Log {
39+
fn lock(&self) -> LockResult<MutexGuard<RefCell<Vec<LogEntry>>>> { self.0.lock() }
40+
fn acquire(&self) -> Acquired { Acquired(self.0.lock().unwrap()) }
41+
}
42+
43+
impl<'a> Acquired<'a> {
44+
fn log(&self, s: &'static str, i: i32) { self.0.borrow_mut().push((s, i)); }
45+
}
46+
47+
const TEST1_EXPECT: LogState = (MutexWas::NotPoisoned,
48+
&[("double-check non-poisoning path", 1)
49+
]);
50+
51+
fn test1(log: Log) {
52+
{
53+
let acq = log.acquire();
54+
acq.log("double-check non-poisoning path", 1);
55+
}
56+
panic!("every test ends in a panic");
57+
}
58+
59+
const TEST2_EXPECT: LogState = (MutexWas::Poisoned,
60+
&[("double-check poisoning path", 1),
61+
("and multiple log entries", 2),
62+
]);
63+
fn test2(log: Log) {
64+
let acq = log.acquire();
65+
acq.log("double-check poisoning path", 1);
66+
acq.log("and multiple log entries", 2);
67+
panic!("every test ends in a panic");
68+
}
69+
70+
struct LogOnDrop<'a>(&'a Acquired<'a>, &'static str, i32);
71+
impl<'a> Drop for LogOnDrop<'a> {
72+
fn drop(&mut self) {
73+
self.0.log(self.1, self.2);
74+
}
75+
}
76+
77+
const TEST3_EXPECT: LogState = (MutexWas::Poisoned,
78+
&[("double-check destructors can log", 1),
79+
("drop d2", 2),
80+
("drop d1", 3),
81+
]);
82+
fn test3(log: Log) {
83+
let acq = log.acquire();
84+
acq.log("double-check destructors can log", 1);
85+
let _d1 = LogOnDrop(&acq, "drop d1", 3);
86+
let _d2 = LogOnDrop(&acq, "drop d2", 2);
87+
panic!("every test ends in a panic");
88+
}
89+
90+
// The *real* tests of panic-handling for move_val_init intrinsic
91+
// start here.
92+
93+
const TEST4_EXPECT: LogState = (MutexWas::Poisoned,
94+
&[("neither arg panics", 1),
95+
("drop temp LOD", 2),
96+
("drop temp LOD", 3),
97+
("drop dest_b", 4),
98+
("drop dest_a", 5),
99+
]);
100+
fn test4(log: Log) {
101+
let acq = log.acquire();
102+
acq.log("neither arg panics", 1);
103+
let mut dest_a = LogOnDrop(&acq, "a will be overwritten, not dropped", 0);
104+
let mut dest_b = LogOnDrop(&acq, "b will be overwritten, not dropped", 0);
105+
unsafe {
106+
intrinsics::move_val_init({ LogOnDrop(&acq, "drop temp LOD", 2); &mut dest_a },
107+
LogOnDrop(&acq, "drop dest_a", 5));
108+
intrinsics::move_val_init(&mut dest_b, { LogOnDrop(&acq, "drop temp LOD", 3);
109+
LogOnDrop(&acq, "drop dest_b", 4) });
110+
}
111+
panic!("every test ends in a panic");
112+
}
113+
114+
115+
// Check that move_val_init(PANIC, SOURCE_EXPR) never evaluates SOURCE_EXPR
116+
const TEST5_EXPECT: LogState = (MutexWas::Poisoned,
117+
&[("first arg panics", 1),
118+
("drop orig dest_a", 2),
119+
]);
120+
fn test5(log: Log) {
121+
let acq = log.acquire();
122+
acq.log("first arg panics", 1);
123+
let mut _dest_a = LogOnDrop(&acq, "drop orig dest_a", 2);
124+
unsafe {
125+
intrinsics::move_val_init({ panic!("every test ends in a panic") },
126+
LogOnDrop(&acq, "we never get here", 0));
127+
}
128+
}
129+
130+
// Check that move_val_init(DEST_EXPR, PANIC) cleans up temps from DEST_EXPR.
131+
const TEST6_EXPECT: LogState = (MutexWas::Poisoned,
132+
&[("second arg panics", 1),
133+
("drop temp LOD", 2),
134+
("drop orig dest_a", 3),
135+
]);
136+
fn test6(log: Log) {
137+
let acq = log.acquire();
138+
acq.log("second arg panics", 1);
139+
let mut dest_a = LogOnDrop(&acq, "drop orig dest_a", 3);
140+
unsafe {
141+
intrinsics::move_val_init({ LogOnDrop(&acq, "drop temp LOD", 2); &mut dest_a },
142+
{ panic!("every test ends in a panic"); });
143+
}
144+
}
145+
146+
// Check that move_val_init(DEST_EXPR, COMPLEX_PANIC) cleans up temps from COMPLEX_PANIC.
147+
const TEST7_EXPECT: LogState = (MutexWas::Poisoned,
148+
&[("second arg panics", 1),
149+
("drop temp LOD", 2),
150+
("drop temp LOD", 3),
151+
("drop orig dest_a", 4),
152+
]);
153+
fn test7(log: Log) {
154+
let acq = log.acquire();
155+
acq.log("second arg panics", 1);
156+
let mut dest_a = LogOnDrop(&acq, "drop orig dest_a", 4);
157+
unsafe {
158+
intrinsics::move_val_init({ LogOnDrop(&acq, "drop temp LOD", 2); &mut dest_a },
159+
{ LogOnDrop(&acq, "drop temp LOD", 3);
160+
panic!("every test ends in a panic"); });
161+
}
162+
}
163+
164+
const TEST_SUITE: &'static [(&'static str, fn (Log), LogState)] =
165+
&[("test1", test1, TEST1_EXPECT),
166+
("test2", test2, TEST2_EXPECT),
167+
("test3", test3, TEST3_EXPECT),
168+
("test4", test4, TEST4_EXPECT),
169+
("test5", test5, TEST5_EXPECT),
170+
("test6", test6, TEST6_EXPECT),
171+
("test7", test7, TEST7_EXPECT),
172+
];
173+
174+
fn main() {
175+
for &(name, test, expect) in TEST_SUITE {
176+
let log = Log(Arc::new(Mutex::new(RefCell::new(Vec::new()))));
177+
let ret = { let log = log.clone(); thread::spawn(move || test(log)).join() };
178+
assert!(ret.is_err(), "{} must end with panic", name);
179+
{
180+
let l = log.lock();
181+
match l {
182+
Ok(acq) => {
183+
assert_eq!((MutexWas::NotPoisoned, &acq.borrow()[..]), expect);
184+
println!("{} (unpoisoned) log: {:?}", name, *acq);
185+
}
186+
Err(e) => {
187+
let acq = e.into_inner();
188+
assert_eq!((MutexWas::Poisoned, &acq.borrow()[..]), expect);
189+
println!("{} (poisoned) log: {:?}", name, *acq);
190+
}
191+
}
192+
}
193+
}
194+
}

0 commit comments

Comments
 (0)