Skip to content

Commit 92d1f15

Browse files
committed
Emit LLVM lifetime intrinsics to improve stack usage and codegen in general
Lifetime intrinsics help to reduce stack usage, because LLVM can apply stack coloring to reuse the stack slots of dead allocas for new ones. For example these functions now both use the same amount of stack, while previous `bar()` used five times as much as `foo()`: ````rust fn foo() { println("{}", 5); } fn bar() { println("{}", 5); println("{}", 5); println("{}", 5); println("{}", 5); println("{}", 5); } ```` On top of that, LLVM can also optimize out certain operations when it knows that memory is dead after a certain point. For example, it can sometimes remove the zeroing used to cancel the drop glue. This is possible when the glue drop itself was already removed because the zeroing dominated the drop glue call. For example in: ````rust pub fn bar(x: (Box<int>, int)) -> (Box<int>, int) { x } ```` With optimizations, this currently results in: ````llvm define void @_ZN3bar20h330fa42547df8179niaE({ i64*, i64 }* noalias nocapture nonnull sret, { i64*, i64 }* noalias nocapture nonnull) unnamed_addr #0 { "_ZN29_$LP$Box$LT$int$GT$$C$int$RP$39glue_drop.$x22glue_drop$x22$LP$1347$RP$17h88cf42702e5a322aE.exit": %2 = bitcast { i64*, i64 }* %1 to i8* %3 = bitcast { i64*, i64 }* %0 to i8* tail call void @llvm.memcpy.p0i8.p0i8.i64(i8* %3, i8* %2, i64 16, i32 8, i1 false) tail call void @llvm.memset.p0i8.i64(i8* %2, i8 0, i64 16, i32 8, i1 false) ret void } ```` But with lifetime intrinsics we get: ````llvm define void @_ZN3bar20h330fa42547df8179niaE({ i64*, i64 }* noalias nocapture nonnull sret, { i64*, i64 }* noalias nocapture nonnull) unnamed_addr #0 { "_ZN29_$LP$Box$LT$int$GT$$C$int$RP$39glue_drop.$x22glue_drop$x22$LP$1347$RP$17h88cf42702e5a322aE.exit": %2 = bitcast { i64*, i64 }* %1 to i8* %3 = bitcast { i64*, i64 }* %0 to i8* tail call void @llvm.memcpy.p0i8.p0i8.i64(i8* %3, i8* %2, i64 16, i32 8, i1 false) tail call void @llvm.lifetime.end(i64 16, i8* %2) ret void } ```` Fixes #15665
1 parent 8748a69 commit 92d1f15

File tree

7 files changed

+81
-8
lines changed

7 files changed

+81
-8
lines changed

src/librustc/middle/trans/_match.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1603,6 +1603,7 @@ fn mk_binding_alloca<'a,A>(bcx: &'a Block<'a>,
16031603
// Subtle: be sure that we *populate* the memory *before*
16041604
// we schedule the cleanup.
16051605
let bcx = populate(arg, bcx, llval, var_ty);
1606+
bcx.fcx.schedule_lifetime_end(cleanup_scope, llval);
16061607
bcx.fcx.schedule_drop_mem(cleanup_scope, llval, var_ty);
16071608

16081609
// Now that memory is initialized and has cleanup scheduled,

src/librustc/middle/trans/base.rs

+33-1
Original file line numberDiff line numberDiff line change
@@ -1070,6 +1070,34 @@ pub fn with_cond<'a>(
10701070
next_cx
10711071
}
10721072

1073+
pub fn call_lifetime_start(cx: &Block, ptr: ValueRef) {
1074+
if cx.sess().opts.optimize == config::No {
1075+
return;
1076+
}
1077+
1078+
let _icx = push_ctxt("lifetime_start");
1079+
let ccx = cx.ccx();
1080+
1081+
let llsize = C_u64(ccx, machine::llsize_of_alloc(ccx, val_ty(ptr).element_type()));
1082+
let ptr = PointerCast(cx, ptr, Type::i8p(ccx));
1083+
let lifetime_start = ccx.get_intrinsic(&"llvm.lifetime.start");
1084+
Call(cx, lifetime_start, [llsize, ptr], []);
1085+
}
1086+
1087+
pub fn call_lifetime_end(cx: &Block, ptr: ValueRef) {
1088+
if cx.sess().opts.optimize == config::No {
1089+
return;
1090+
}
1091+
1092+
let _icx = push_ctxt("lifetime_end");
1093+
let ccx = cx.ccx();
1094+
1095+
let llsize = C_u64(ccx, machine::llsize_of_alloc(ccx, val_ty(ptr).element_type()));
1096+
let ptr = PointerCast(cx, ptr, Type::i8p(ccx));
1097+
let lifetime_end = ccx.get_intrinsic(&"llvm.lifetime.end");
1098+
Call(cx, lifetime_end, [llsize, ptr], []);
1099+
}
1100+
10731101
pub fn call_memcpy(cx: &Block, dst: ValueRef, src: ValueRef, n_bytes: ValueRef, align: u32) {
10741102
let _icx = push_ctxt("call_memcpy");
10751103
let ccx = cx.ccx();
@@ -1157,6 +1185,8 @@ pub fn alloca_maybe_zeroed(cx: &Block, ty: Type, name: &str, zero: bool) -> Valu
11571185
let b = cx.fcx.ccx.builder();
11581186
b.position_before(cx.fcx.alloca_insert_pt.get().unwrap());
11591187
memzero(&b, p, ty);
1188+
} else {
1189+
call_lifetime_start(cx, p);
11601190
}
11611191
p
11621192
}
@@ -1169,7 +1199,9 @@ pub fn arrayalloca(cx: &Block, ty: Type, v: ValueRef) -> ValueRef {
11691199
}
11701200
}
11711201
debuginfo::clear_source_location(cx.fcx);
1172-
return ArrayAlloca(cx, ty, v);
1202+
let p = ArrayAlloca(cx, ty, v);
1203+
call_lifetime_start(cx, p);
1204+
p
11731205
}
11741206

11751207
// Creates and returns space for, or returns the argument representing, the

src/librustc/middle/trans/cleanup.rs

+32
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,20 @@ impl<'a> CleanupMethods<'a> for FunctionContext<'a> {
226226
self.trans_cleanups_to_exit_scope(ReturnExit)
227227
}
228228

229+
fn schedule_lifetime_end(&self,
230+
cleanup_scope: ScopeId,
231+
val: ValueRef) {
232+
let drop = box LifetimeEnd {
233+
ptr: val,
234+
};
235+
236+
debug!("schedule_lifetime_end({:?}, val={})",
237+
cleanup_scope,
238+
self.ccx.tn.val_to_string(val));
239+
240+
self.schedule_clean(cleanup_scope, drop as Box<Cleanup>);
241+
}
242+
229243
fn schedule_drop_mem(&self,
230244
cleanup_scope: ScopeId,
231245
val: ValueRef,
@@ -902,6 +916,21 @@ impl Cleanup for FreeValue {
902916
}
903917
}
904918

919+
pub struct LifetimeEnd {
920+
ptr: ValueRef,
921+
}
922+
923+
impl Cleanup for LifetimeEnd {
924+
fn clean_on_unwind(&self) -> bool {
925+
false
926+
}
927+
928+
fn trans<'a>(&self, bcx: &'a Block<'a>) -> &'a Block<'a> {
929+
base::call_lifetime_end(bcx, self.ptr);
930+
bcx
931+
}
932+
}
933+
905934
pub fn temporary_scope(tcx: &ty::ctxt,
906935
id: ast::NodeId)
907936
-> ScopeId {
@@ -957,6 +986,9 @@ pub trait CleanupMethods<'a> {
957986
cleanup_scope: ast::NodeId,
958987
exit: uint) -> BasicBlockRef;
959988
fn return_exit_block(&'a self) -> BasicBlockRef;
989+
fn schedule_lifetime_end(&self,
990+
cleanup_scope: ScopeId,
991+
val: ValueRef);
960992
fn schedule_drop_mem(&self,
961993
cleanup_scope: ScopeId,
962994
val: ValueRef,

src/librustc/middle/trans/context.rs

+3
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,9 @@ fn declare_intrinsic(ccx: &CrateContext, key: & &'static str) -> Option<ValueRef
425425
ifn!("llvm.umul.with.overflow.i32" fn(t_i32, t_i32) -> mk_struct!{t_i32, i1});
426426
ifn!("llvm.umul.with.overflow.i64" fn(t_i64, t_i64) -> mk_struct!{t_i64, i1});
427427

428+
ifn!("llvm.lifetime.start" fn(t_i64,i8p) -> void);
429+
ifn!("llvm.lifetime.end" fn(t_i64, i8p) -> void);
430+
428431
ifn!("llvm.expect.i1" fn(i1, i1) -> i1);
429432

430433
// Some intrinsics were introduced in later versions of LLVM, but they have

src/librustc/middle/trans/datum.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ pub fn lvalue_scratch_datum<'a, A>(bcx: &'a Block<'a>,
124124

125125
// Subtle. Populate the scratch memory *before* scheduling cleanup.
126126
let bcx = populate(arg, bcx, scratch);
127+
bcx.fcx.schedule_lifetime_end(scope, scratch);
127128
bcx.fcx.schedule_drop_mem(scope, scratch, ty);
128129

129130
DatumBlock::new(bcx, Datum::new(scratch, ty, Lvalue))
@@ -169,7 +170,10 @@ fn add_rvalue_clean(mode: RvalueMode,
169170
ty: ty::t) {
170171
match mode {
171172
ByValue => { fcx.schedule_drop_immediate(scope, val, ty); }
172-
ByRef => { fcx.schedule_drop_mem(scope, val, ty); }
173+
ByRef => {
174+
fcx.schedule_lifetime_end(scope, val);
175+
fcx.schedule_drop_mem(scope, val, ty);
176+
}
173177
}
174178
}
175179

src/librustc/middle/trans/expr.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -1145,8 +1145,9 @@ pub fn trans_adt<'a>(bcx: &'a Block<'a>,
11451145
let dest = adt::trans_field_ptr(bcx, repr, addr, discr, i);
11461146
let e_ty = expr_ty_adjusted(bcx, &**e);
11471147
bcx = trans_into(bcx, &**e, SaveIn(dest));
1148-
fcx.schedule_drop_mem(cleanup::CustomScope(custom_cleanup_scope),
1149-
dest, e_ty);
1148+
let scope = cleanup::CustomScope(custom_cleanup_scope);
1149+
fcx.schedule_lifetime_end(scope, dest);
1150+
fcx.schedule_drop_mem(scope, dest, e_ty);
11501151
}
11511152

11521153
for base in optbase.iter() {

src/librustc/middle/trans/tvec.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ pub fn trans_slice_vstore<'a>(
170170
let llfixed_ty = type_of::type_of(bcx.ccx(), fixed_ty).ptr_to();
171171
let llfixed_casted = BitCast(bcx, llfixed, llfixed_ty);
172172
let cleanup_scope = cleanup::temporary_scope(bcx.tcx(), content_expr.id);
173+
fcx.schedule_lifetime_end(cleanup_scope, llfixed_casted);
173174
fcx.schedule_drop_mem(cleanup_scope, llfixed_casted, fixed_ty);
174175

175176
// Generate the content into the backing array.
@@ -364,10 +365,9 @@ pub fn write_content<'a>(
364365
i, bcx.val_to_string(lleltptr));
365366
bcx = expr::trans_into(bcx, &**element,
366367
SaveIn(lleltptr));
367-
fcx.schedule_drop_mem(
368-
cleanup::CustomScope(temp_scope),
369-
lleltptr,
370-
vt.unit_ty);
368+
let scope = cleanup::CustomScope(temp_scope);
369+
fcx.schedule_lifetime_end(scope, lleltptr);
370+
fcx.schedule_drop_mem(scope, lleltptr, vt.unit_ty);
371371
}
372372
fcx.pop_custom_cleanup_scope(temp_scope);
373373
}

0 commit comments

Comments
 (0)