Skip to content

Commit 4a022d9

Browse files
committed
Inline function return variables.
This change replaces the default return variable `$0` with the variable in the outer context where the return value will end up after leaving the function. This saves us an instruction when we compile the trace. More importantly however, this guards us against a future optimisation in rustc that allows SIR to assign to $0 multiple times and at the beginning of a block, which could lead to another function overwriting its value (see rust-lang/rust#72205).
1 parent 9c91b43 commit 4a022d9

File tree

2 files changed

+39
-29
lines changed

2 files changed

+39
-29
lines changed

ykcompile/src/lib.rs

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,9 @@ pub struct TraceCompiler {
9696
available_regs: Vec<u8>,
9797
/// Maps locals to their assigned registers.
9898
assigned_regs: HashMap<Local, u8>,
99-
/// Stores the destination locals to which we copy RAX to after leaving an inlined call.
100-
leaves: Vec<Option<Place>>,
99+
/// Stores the destination local of the outermost function and moves its content into RAX at
100+
/// the end of the trace.
101+
rtn_var: Option<Place>,
101102
}
102103

103104
impl TraceCompiler {
@@ -127,6 +128,15 @@ impl TraceCompiler {
127128

128129
fn free_register(&mut self, local: &Local) {
129130
if let Some(reg) = self.assigned_regs.remove(local) {
131+
if local == &self.rtn_var.as_ref().unwrap().local {
132+
// We currently assume that we only trace a single function which leaves its return
133+
// value in RAX. Since we now inline a function's return variable this won't happen
134+
// automatically anymore. To keep things working, we thus copy the return value of
135+
// the most outer function into RAX at the end of the trace.
136+
dynasm!(self.asm
137+
; mov rax, Rq(reg)
138+
);
139+
}
130140
self.available_regs.push(reg);
131141
}
132142
}
@@ -217,19 +227,9 @@ impl TraceCompiler {
217227
},
218228
}
219229
}
220-
// Remember the return destination.
221-
self.leaves.push(dest.as_ref().cloned());
222-
Ok(())
223-
}
224-
225-
fn c_leave(&mut self) -> Result<(), CompileError> {
226-
let dest = self.leaves.pop();
227-
if let Some(d) = dest {
228-
if let Some(d) = d {
229-
// When we see a leave statement move whatever's left in RAX into the destination
230-
// local.
231-
self.mov_local_local(d.local, Local(0))?;
232-
}
230+
if self.rtn_var.is_none() {
231+
// Remember the return variable of the most outer function.
232+
self.rtn_var = dest.as_ref().cloned();
233233
}
234234
Ok(())
235235
}
@@ -313,7 +313,7 @@ impl TraceCompiler {
313313
};
314314
}
315315
Statement::Enter(op, args, dest, off) => self.c_enter(op, args, dest, *off)?,
316-
Statement::Leave => self.c_leave()?,
316+
Statement::Leave => {}
317317
Statement::StorageLive(_) => {}
318318
Statement::StorageDead(l) => self.free_register(l),
319319
Statement::Call(target, args, dest) => self.c_call(target, args, dest)?,
@@ -388,7 +388,7 @@ impl TraceCompiler {
388388
// Use all the 64-bit registers we can (R15-R8, RDX, RCX).
389389
available_regs: vec![15, 14, 13, 12, 11, 10, 9, 8, 2, 1],
390390
assigned_regs: HashMap::new(),
391-
leaves: Vec::new(),
391+
rtn_var: None,
392392
};
393393

394394
for i in 0..tt.len() {
@@ -452,7 +452,7 @@ mod tests {
452452
asm: dynasmrt::x64::Assembler::new().unwrap(),
453453
available_regs: vec![15, 14, 13, 12, 11, 10, 9, 8, 2, 1],
454454
assigned_regs: HashMap::new(),
455-
leaves: Vec::new(),
455+
rtn_var: None,
456456
};
457457

458458
for _ in 0..32 {
@@ -470,7 +470,7 @@ mod tests {
470470
asm: dynasmrt::x64::Assembler::new().unwrap(),
471471
available_regs: vec![15, 14, 13, 12, 11, 10, 9, 8, 2, 1],
472472
assigned_regs: HashMap::new(),
473-
leaves: Vec::new(),
473+
rtn_var: None,
474474
};
475475

476476
let mut seen = HashSet::new();

yktrace/src/tir.rs

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -166,16 +166,16 @@ impl TirTrace {
166166
let newdest = dest
167167
.as_ref()
168168
.map(|(ret_val, _ret_bb)| rnm.rename_place(&ret_val));
169-
// Rename all `Local`s within the arguments.
170-
let newargs = rnm.rename_args(&args);
171-
// Inform VarRenamer about this function's offset, which is equal to the
172-
// number of variables assigned in the outer body.
173-
rnm.enter(body.num_locals);
174169
// FIXME It seems that calls always have a destination despite it being
175170
// an `Option`. If this is not always the case, we may want add the
176171
// `Local` offset (`var_len`) to this statement so we can assign the
177172
// arguments to the correct `Local`s during trace compilation.
178173
assert!(newdest.is_some());
174+
// Rename all `Local`s within the arguments.
175+
let newargs = rnm.rename_args(&args);
176+
// Inform VarRenamer about this function's offset, which is equal to the
177+
// number of variables assigned in the outer body.
178+
rnm.enter(body.num_locals, newdest.as_ref().unwrap().clone());
179179
TirOp::Statement(Statement::Enter(
180180
op.clone(),
181181
newargs,
@@ -313,33 +313,37 @@ struct VarRenamer {
313313
/// restored again after leaving that call.
314314
stack: Vec<u32>,
315315
/// Current offset used to rename variables.
316-
offset: u32
316+
offset: u32,
317+
returns: Vec<Place>
317318
}
318319

319320
impl VarRenamer {
320321
fn new() -> Self {
321322
VarRenamer {
322323
stack: vec![0],
323-
offset: 0
324+
offset: 0,
325+
returns: Vec::new()
324326
}
325327
}
326328

327329
fn offset(&self) -> u32 {
328330
self.offset
329331
}
330332

331-
fn enter(&mut self, num_locals: usize) {
333+
fn enter(&mut self, num_locals: usize, dest: Place) {
332334
// When entering an inlined function call update the current offset by adding the number of
333335
// assigned variables in the outer context. Also add this offset to the stack, so we can
334336
// restore it once we leave the inlined function call again.
335337
self.offset += num_locals as u32;
336338
self.stack.push(self.offset);
339+
self.returns.push(dest);
337340
}
338341

339342
fn leave(&mut self) {
340343
// When we leave an inlined function call, we pop the previous offset from the stack,
341344
// reverting the offset to what it was before the function was entered.
342345
self.stack.pop();
346+
self.returns.pop();
343347
if let Some(v) = self.stack.last() {
344348
self.offset = *v;
345349
} else {
@@ -380,8 +384,14 @@ impl VarRenamer {
380384

381385
fn rename_place(&self, place: &Place) -> Place {
382386
if &place.local == &Local(0) {
383-
// Local(0) is used for returning values from calls, so it doesn't need to be renamed.
384-
place.clone()
387+
// Replace the default return variable $0 with the variable in the outer context where
388+
// the return value will end up after leaving the function. This saves us an
389+
// instruction when we compile the trace.
390+
if let Some(v) = self.returns.last() {
391+
v.clone()
392+
} else {
393+
panic!("Expected return value!")
394+
}
385395
} else {
386396
let mut p = place.clone();
387397
p.local = self.rename_local(&p.local);

0 commit comments

Comments
 (0)