Skip to content

Commit 9036c82

Browse files
committed
Rollup merge of rust-lang#30823 - pnkfelix:put-back-alloca-zeroing-for-issue-30530, r=dotdash
Put back alloca zeroing for issues rust-lang#29092, rust-lang#30018, rust-lang#30530; inject zeroing for rust-lang#30822. ---- Background context: `fn alloca_zeroed` was removed in PR rust-lang#22969, so we haven't been "zero'ing" (\*) the alloca's since at least that point, but the logic behind that PR seems sound, so its not entirely obvious how *long* the underlying bug has actually been present. In other words, I have not yet done a survey to see when the new `alloc_ty` and `lvalue_scratch_datum` calls were introduced that should have had "zero'ing" the alloca's. ---- I first fixed rust-lang#30018, then decided to do a survey of `alloc_ty` calls to see if they needed similar treatment, which quickly led to a rediscovery of rust-lang#30530. While making the regression test for the latter, I discovered rust-lang#30822, which is a slightly different bug (in terms of where the "zero'ing" needs to go), but still relevant. I haven't finished the aforementioned survey of `fn alloc_ty` calls, but I decided I wanted to get this up for review in its current state (namely to see if my attempt to force developers to include a justification for passing `Uninit` can possibly fly, or if I should abandon that path of action). ---- (*): I am putting quotation marks around "zero'ing" because we no longer use zero as our "dropped" marker value. Fix rust-lang#29092 Fix rust-lang#30018 Fix rust-lang#30530 Fix rust-lang#30822
2 parents 3e248aa + decc286 commit 9036c82

File tree

10 files changed

+392
-44
lines changed

10 files changed

+392
-44
lines changed

src/librustc_trans/trans/_match.rs

+3
Original file line numberDiff line numberDiff line change
@@ -1760,6 +1760,9 @@ fn mk_binding_alloca<'blk, 'tcx, A, F>(bcx: Block<'blk, 'tcx>,
17601760
let lvalue = Lvalue::new_with_hint(caller_name, bcx, p_id, HintKind::DontZeroJustUse);
17611761
let datum = Datum::new(llval, var_ty, lvalue);
17621762

1763+
debug!("mk_binding_alloca cleanup_scope={:?} llval={} var_ty={:?}",
1764+
cleanup_scope, bcx.ccx().tn().val_to_string(llval), var_ty);
1765+
17631766
// Subtle: be sure that we *populate* the memory *before*
17641767
// we schedule the cleanup.
17651768
call_lifetime_start(bcx, llval);

src/librustc_trans/trans/adt.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ use syntax::ast;
5555
use syntax::attr;
5656
use syntax::attr::IntType;
5757
use trans::_match;
58+
use trans::base::InitAlloca;
5859
use trans::build::*;
5960
use trans::cleanup;
6061
use trans::cleanup::CleanupMethods;
@@ -1279,7 +1280,12 @@ pub fn trans_drop_flag_ptr<'blk, 'tcx>(mut bcx: Block<'blk, 'tcx>,
12791280
let custom_cleanup_scope = fcx.push_custom_cleanup_scope();
12801281
let scratch = unpack_datum!(bcx, datum::lvalue_scratch_datum(
12811282
bcx, tcx.dtor_type(), "drop_flag",
1282-
cleanup::CustomScope(custom_cleanup_scope), (), |_, bcx, _| bcx
1283+
InitAlloca::Uninit("drop flag itself has no dtor"),
1284+
cleanup::CustomScope(custom_cleanup_scope), (), |_, bcx, _| {
1285+
debug!("no-op populate call for trans_drop_flag_ptr on dtor_type={:?}",
1286+
tcx.dtor_type());
1287+
bcx
1288+
}
12831289
));
12841290
bcx = fold_variants(bcx, r, val, |variant_cx, st, value| {
12851291
let ptr = struct_field_ptr(variant_cx, st, MaybeSizedValue::sized(value),

src/librustc_trans/trans/base.rs

+135-35
Original file line numberDiff line numberDiff line change
@@ -1147,48 +1147,63 @@ pub fn with_cond<'blk, 'tcx, F>(bcx: Block<'blk, 'tcx>, val: ValueRef, f: F) ->
11471147
next_cx
11481148
}
11491149

1150-
pub fn call_lifetime_start(cx: Block, ptr: ValueRef) {
1151-
if cx.sess().opts.optimize == config::No {
1150+
enum Lifetime { Start, End }
1151+
1152+
// If LLVM lifetime intrinsic support is enabled (i.e. optimizations
1153+
// on), and `ptr` is nonzero-sized, then extracts the size of `ptr`
1154+
// and the intrinsic for `lt` and passes them to `emit`, which is in
1155+
// charge of generating code to call the passed intrinsic on whatever
1156+
// block of generated code is targetted for the intrinsic.
1157+
//
1158+
// If LLVM lifetime intrinsic support is disabled (i.e. optimizations
1159+
// off) or `ptr` is zero-sized, then no-op (does not call `emit`).
1160+
fn core_lifetime_emit<'blk, 'tcx, F>(ccx: &'blk CrateContext<'blk, 'tcx>,
1161+
ptr: ValueRef,
1162+
lt: Lifetime,
1163+
emit: F)
1164+
where F: FnOnce(&'blk CrateContext<'blk, 'tcx>, machine::llsize, ValueRef)
1165+
{
1166+
if ccx.sess().opts.optimize == config::No {
11521167
return;
11531168
}
11541169

1155-
let _icx = push_ctxt("lifetime_start");
1156-
let ccx = cx.ccx();
1170+
let _icx = push_ctxt(match lt {
1171+
Lifetime::Start => "lifetime_start",
1172+
Lifetime::End => "lifetime_end"
1173+
});
11571174

11581175
let size = machine::llsize_of_alloc(ccx, val_ty(ptr).element_type());
11591176
if size == 0 {
11601177
return;
11611178
}
11621179

1163-
let ptr = PointerCast(cx, ptr, Type::i8p(ccx));
1164-
let lifetime_start = ccx.get_intrinsic(&"llvm.lifetime.start");
1165-
Call(cx,
1166-
lifetime_start,
1167-
&[C_u64(ccx, size), ptr],
1168-
None,
1169-
DebugLoc::None);
1180+
let lifetime_intrinsic = ccx.get_intrinsic(match lt {
1181+
Lifetime::Start => "llvm.lifetime.start",
1182+
Lifetime::End => "llvm.lifetime.end"
1183+
});
1184+
emit(ccx, size, lifetime_intrinsic)
11701185
}
11711186

1172-
pub fn call_lifetime_end(cx: Block, ptr: ValueRef) {
1173-
if cx.sess().opts.optimize == config::No {
1174-
return;
1175-
}
1176-
1177-
let _icx = push_ctxt("lifetime_end");
1178-
let ccx = cx.ccx();
1179-
1180-
let size = machine::llsize_of_alloc(ccx, val_ty(ptr).element_type());
1181-
if size == 0 {
1182-
return;
1183-
}
1187+
pub fn call_lifetime_start(cx: Block, ptr: ValueRef) {
1188+
core_lifetime_emit(cx.ccx(), ptr, Lifetime::Start, |ccx, size, lifetime_start| {
1189+
let ptr = PointerCast(cx, ptr, Type::i8p(ccx));
1190+
Call(cx,
1191+
lifetime_start,
1192+
&[C_u64(ccx, size), ptr],
1193+
None,
1194+
DebugLoc::None);
1195+
})
1196+
}
11841197

1185-
let ptr = PointerCast(cx, ptr, Type::i8p(ccx));
1186-
let lifetime_end = ccx.get_intrinsic(&"llvm.lifetime.end");
1187-
Call(cx,
1188-
lifetime_end,
1189-
&[C_u64(ccx, size), ptr],
1190-
None,
1191-
DebugLoc::None);
1198+
pub fn call_lifetime_end(cx: Block, ptr: ValueRef) {
1199+
core_lifetime_emit(cx.ccx(), ptr, Lifetime::End, |ccx, size, lifetime_end| {
1200+
let ptr = PointerCast(cx, ptr, Type::i8p(ccx));
1201+
Call(cx,
1202+
lifetime_end,
1203+
&[C_u64(ccx, size), ptr],
1204+
None,
1205+
DebugLoc::None);
1206+
})
11921207
}
11931208

11941209
// Generates code for resumption of unwind at the end of a landing pad.
@@ -1285,12 +1300,81 @@ fn memfill<'a, 'tcx>(b: &Builder<'a, 'tcx>, llptr: ValueRef, ty: Ty<'tcx>, byte:
12851300
None);
12861301
}
12871302

1288-
pub fn alloc_ty<'blk, 'tcx>(bcx: Block<'blk, 'tcx>, t: Ty<'tcx>, name: &str) -> ValueRef {
1303+
/// In general, when we create an scratch value in an alloca, the
1304+
/// creator may not know if the block (that initializes the scratch
1305+
/// with the desired value) actually dominates the cleanup associated
1306+
/// with the scratch value.
1307+
///
1308+
/// To deal with this, when we do an alloca (at the *start* of whole
1309+
/// function body), we optionally can also set the associated
1310+
/// dropped-flag state of the alloca to "dropped."
1311+
#[derive(Copy, Clone, Debug)]
1312+
pub enum InitAlloca {
1313+
/// Indicates that the state should have its associated drop flag
1314+
/// set to "dropped" at the point of allocation.
1315+
Dropped,
1316+
/// Indicates the value of the associated drop flag is irrelevant.
1317+
/// The embedded string literal is a programmer provided argument
1318+
/// for why. This is a safeguard forcing compiler devs to
1319+
/// document; it might be a good idea to also emit this as a
1320+
/// comment with the alloca itself when emitting LLVM output.ll.
1321+
Uninit(&'static str),
1322+
}
1323+
1324+
1325+
pub fn alloc_ty<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
1326+
t: Ty<'tcx>,
1327+
name: &str) -> ValueRef {
1328+
// pnkfelix: I do not know why alloc_ty meets the assumptions for
1329+
// passing Uninit, but it was never needed (even back when we had
1330+
// the original boolean `zero` flag on `lvalue_scratch_datum`).
1331+
alloc_ty_init(bcx, t, InitAlloca::Uninit("all alloc_ty are uninit"), name)
1332+
}
1333+
1334+
/// This variant of `fn alloc_ty` does not necessarily assume that the
1335+
/// alloca should be created with no initial value. Instead the caller
1336+
/// controls that assumption via the `init` flag.
1337+
///
1338+
/// Note that if the alloca *is* initialized via `init`, then we will
1339+
/// also inject an `llvm.lifetime.start` before that initialization
1340+
/// occurs, and thus callers should not call_lifetime_start
1341+
/// themselves. But if `init` says "uninitialized", then callers are
1342+
/// in charge of choosing where to call_lifetime_start and
1343+
/// subsequently populate the alloca.
1344+
///
1345+
/// (See related discussion on PR #30823.)
1346+
pub fn alloc_ty_init<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
1347+
t: Ty<'tcx>,
1348+
init: InitAlloca,
1349+
name: &str) -> ValueRef {
12891350
let _icx = push_ctxt("alloc_ty");
12901351
let ccx = bcx.ccx();
12911352
let ty = type_of::type_of(ccx, t);
12921353
assert!(!t.has_param_types());
1293-
alloca(bcx, ty, name)
1354+
match init {
1355+
InitAlloca::Dropped => alloca_dropped(bcx, t, name),
1356+
InitAlloca::Uninit(_) => alloca(bcx, ty, name),
1357+
}
1358+
}
1359+
1360+
pub fn alloca_dropped<'blk, 'tcx>(cx: Block<'blk, 'tcx>, ty: Ty<'tcx>, name: &str) -> ValueRef {
1361+
let _icx = push_ctxt("alloca_dropped");
1362+
let llty = type_of::type_of(cx.ccx(), ty);
1363+
if cx.unreachable.get() {
1364+
unsafe { return llvm::LLVMGetUndef(llty.ptr_to().to_ref()); }
1365+
}
1366+
let p = alloca(cx, llty, name);
1367+
let b = cx.fcx.ccx.builder();
1368+
b.position_before(cx.fcx.alloca_insert_pt.get().unwrap());
1369+
1370+
// This is just like `call_lifetime_start` (but latter expects a
1371+
// Block, which we do not have for `alloca_insert_pt`).
1372+
core_lifetime_emit(cx.ccx(), p, Lifetime::Start, |ccx, size, lifetime_start| {
1373+
let ptr = b.pointercast(p, Type::i8p(ccx));
1374+
b.call(lifetime_start, &[C_u64(ccx, size), ptr], None);
1375+
});
1376+
memfill(&b, p, ty, adt::DTOR_DONE);
1377+
p
12941378
}
12951379

12961380
pub fn alloca(cx: Block, ty: Type, name: &str) -> ValueRef {
@@ -1639,6 +1723,8 @@ pub fn create_datums_for_fn_args<'a, 'tcx>(mut bcx: Block<'a, 'tcx>,
16391723
let fcx = bcx.fcx;
16401724
let arg_scope_id = cleanup::CustomScope(arg_scope);
16411725

1726+
debug!("create_datums_for_fn_args");
1727+
16421728
// Return an array wrapping the ValueRefs that we get from `get_param` for
16431729
// each argument into datums.
16441730
//
@@ -1650,6 +1736,7 @@ pub fn create_datums_for_fn_args<'a, 'tcx>(mut bcx: Block<'a, 'tcx>,
16501736
// This alloca should be optimized away by LLVM's mem-to-reg pass in
16511737
// the event it's not truly needed.
16521738
let mut idx = fcx.arg_offset() as c_uint;
1739+
let uninit_reason = InitAlloca::Uninit("fn_arg populate dominates dtor");
16531740
for (i, &arg_ty) in arg_tys.iter().enumerate() {
16541741
let arg_datum = if !has_tupled_arg || i < arg_tys.len() - 1 {
16551742
if type_of::arg_is_indirect(bcx.ccx(), arg_ty) &&
@@ -1669,9 +1756,12 @@ pub fn create_datums_for_fn_args<'a, 'tcx>(mut bcx: Block<'a, 'tcx>,
16691756
let data = get_param(fcx.llfn, idx);
16701757
let extra = get_param(fcx.llfn, idx + 1);
16711758
idx += 2;
1672-
unpack_datum!(bcx, datum::lvalue_scratch_datum(bcx, arg_ty, "",
1759+
unpack_datum!(bcx, datum::lvalue_scratch_datum(bcx, arg_ty, "", uninit_reason,
16731760
arg_scope_id, (data, extra),
16741761
|(data, extra), bcx, dst| {
1762+
debug!("populate call for create_datum_for_fn_args \
1763+
early fat arg, on arg[{}] ty={:?}", i, arg_ty);
1764+
16751765
Store(bcx, data, expr::get_dataptr(bcx, dst));
16761766
Store(bcx, extra, expr::get_meta(bcx, dst));
16771767
bcx
@@ -1684,9 +1774,16 @@ pub fn create_datums_for_fn_args<'a, 'tcx>(mut bcx: Block<'a, 'tcx>,
16841774
datum::lvalue_scratch_datum(bcx,
16851775
arg_ty,
16861776
"",
1777+
uninit_reason,
16871778
arg_scope_id,
16881779
tmp,
1689-
|tmp, bcx, dst| tmp.store_to(bcx, dst)))
1780+
|tmp, bcx, dst| {
1781+
1782+
debug!("populate call for create_datum_for_fn_args \
1783+
early thin arg, on arg[{}] ty={:?}", i, arg_ty);
1784+
1785+
tmp.store_to(bcx, dst)
1786+
}))
16901787
}
16911788
} else {
16921789
// FIXME(pcwalton): Reduce the amount of code bloat this is responsible for.
@@ -1696,11 +1793,14 @@ pub fn create_datums_for_fn_args<'a, 'tcx>(mut bcx: Block<'a, 'tcx>,
16961793
datum::lvalue_scratch_datum(bcx,
16971794
arg_ty,
16981795
"tupled_args",
1796+
uninit_reason,
16991797
arg_scope_id,
17001798
(),
17011799
|(),
17021800
mut bcx,
1703-
llval| {
1801+
llval| {
1802+
debug!("populate call for create_datum_for_fn_args \
1803+
tupled_args, on arg[{}] ty={:?}", i, arg_ty);
17041804
for (j, &tupled_arg_ty) in
17051805
tupled_arg_tys.iter().enumerate() {
17061806
let lldest = StructGEP(bcx, llval, j);

src/librustc_trans/trans/datum.rs

+21-4
Original file line numberDiff line numberDiff line change
@@ -288,20 +288,31 @@ pub fn immediate_rvalue_bcx<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
288288
return DatumBlock::new(bcx, immediate_rvalue(val, ty))
289289
}
290290

291-
292291
/// Allocates temporary space on the stack using alloca() and returns a by-ref Datum pointing to
293292
/// it. The memory will be dropped upon exit from `scope`. The callback `populate` should
294293
/// initialize the memory.
294+
///
295+
/// The flag `zero` indicates how the temporary space itself should be
296+
/// initialized at the outset of the function; the only time that
297+
/// `InitAlloca::Uninit` is a valid value for `zero` is when the
298+
/// caller can prove that either (1.) the code injected by `populate`
299+
/// onto `bcx` always dominates the end of `scope`, or (2.) the data
300+
/// being allocated has no associated destructor.
295301
pub fn lvalue_scratch_datum<'blk, 'tcx, A, F>(bcx: Block<'blk, 'tcx>,
296302
ty: Ty<'tcx>,
297303
name: &str,
304+
zero: InitAlloca,
298305
scope: cleanup::ScopeId,
299306
arg: A,
300307
populate: F)
301308
-> DatumBlock<'blk, 'tcx, Lvalue> where
302309
F: FnOnce(A, Block<'blk, 'tcx>, ValueRef) -> Block<'blk, 'tcx>,
303310
{
304-
let scratch = alloc_ty(bcx, ty, name);
311+
// Very subtle: potentially initialize the scratch memory at point where it is alloca'ed.
312+
// (See discussion at Issue 30530.)
313+
let scratch = alloc_ty_init(bcx, ty, zero, name);
314+
debug!("lvalue_scratch_datum scope={:?} scratch={} ty={:?}",
315+
scope, bcx.ccx().tn().val_to_string(scratch), ty);
305316

306317
// Subtle. Populate the scratch memory *before* scheduling cleanup.
307318
let bcx = populate(arg, bcx, scratch);
@@ -340,6 +351,8 @@ fn add_rvalue_clean<'a, 'tcx>(mode: RvalueMode,
340351
scope: cleanup::ScopeId,
341352
val: ValueRef,
342353
ty: Ty<'tcx>) {
354+
debug!("add_rvalue_clean scope={:?} val={} ty={:?}",
355+
scope, fcx.ccx.tn().val_to_string(val), ty);
343356
match mode {
344357
ByValue => { fcx.schedule_drop_immediate(scope, val, ty); }
345358
ByRef => {
@@ -496,9 +509,13 @@ impl<'tcx> Datum<'tcx, Rvalue> {
496509

497510
ByValue => {
498511
lvalue_scratch_datum(
499-
bcx, self.ty, name, scope, self,
512+
bcx, self.ty, name, InitAlloca::Dropped, scope, self,
500513
|this, bcx, llval| {
501-
call_lifetime_start(bcx, llval);
514+
debug!("populate call for Datum::to_lvalue_datum_in_scope \
515+
self.ty={:?}", this.ty);
516+
// do not call_lifetime_start here; the
517+
// `InitAlloc::Dropped` will start scratch
518+
// value's lifetime at open of function body.
502519
let bcx = this.store_to(bcx, llval);
503520
bcx.fcx.schedule_lifetime_end(scope, llval);
504521
bcx

src/librustc_trans/trans/expr.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1487,6 +1487,8 @@ pub fn trans_adt<'a, 'blk, 'tcx>(mut bcx: Block<'blk, 'tcx>,
14871487
}
14881488
};
14891489

1490+
debug!("trans_adt");
1491+
14901492
// This scope holds intermediates that must be cleaned should
14911493
// panic occur before the ADT as a whole is ready.
14921494
let custom_cleanup_scope = fcx.push_custom_cleanup_scope();

src/librustc_trans/trans/tvec.rs

+11-4
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,15 @@ pub fn trans_slice_vec<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
111111

112112
// Always create an alloca even if zero-sized, to preserve
113113
// the non-null invariant of the inner slice ptr
114-
let llfixed = base::alloc_ty(bcx, fixed_ty, "");
115-
call_lifetime_start(bcx, llfixed);
114+
let llfixed;
115+
// Issue 30018: ensure state is initialized as dropped if necessary.
116+
if fcx.type_needs_drop(vt.unit_ty) {
117+
llfixed = base::alloc_ty_init(bcx, fixed_ty, InitAlloca::Dropped, "");
118+
} else {
119+
let uninit = InitAlloca::Uninit("fcx says vt.unit_ty is non-drop");
120+
llfixed = base::alloc_ty_init(bcx, fixed_ty, uninit, "");
121+
call_lifetime_start(bcx, llfixed);
122+
};
116123

117124
if count > 0 {
118125
// Arrange for the backing array to be cleaned up.
@@ -212,8 +219,8 @@ fn write_content<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
212219
bcx = expr::trans_into(bcx, &**element,
213220
SaveIn(lleltptr));
214221
let scope = cleanup::CustomScope(temp_scope);
215-
fcx.schedule_lifetime_end(scope, lleltptr);
216-
fcx.schedule_drop_mem(scope, lleltptr, vt.unit_ty, None);
222+
// Issue #30822: mark memory as dropped after running destructor
223+
fcx.schedule_drop_and_fill_mem(scope, lleltptr, vt.unit_ty, None);
217224
}
218225
fcx.pop_custom_cleanup_scope(temp_scope);
219226
}

0 commit comments

Comments
 (0)