Skip to content

Commit 7be7e94

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 7be7e94

File tree

2 files changed

+54
-29
lines changed

2 files changed

+54
-29
lines changed

ykcompile/src/lib.rs

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ pub enum CompileError {
2727
/// We ran out of registers.
2828
/// In the long-run, when we have a proper register allocator, this won't be needed.
2929
OutOfRegisters,
30+
/// We tried to retrieve the register for a local which doesn't have a register assigned to it.
31+
UnknownLocal,
3032
/// Compiling this statement is not yet implemented.
3133
/// The string inside is a hint as to what kind of statement needs to be implemented.
3234
Unimplemented(String),
@@ -38,6 +40,7 @@ impl Display for CompileError {
3840
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
3941
match self {
4042
Self::OutOfRegisters => write!(f, "Ran out of registers"),
43+
Self::UnknownLocal => write!(f, "Couldn't find assigned register for local."),
4144
Self::Unimplemented(s) => write!(f, "Unimplemented compilation: {}", s),
4245
Self::UnknownSymbol(s) => write!(f, "Unknown symbol: {}", s),
4346
}
@@ -96,8 +99,9 @@ pub struct TraceCompiler {
9699
available_regs: Vec<u8>,
97100
/// Maps locals to their assigned registers.
98101
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>>,
102+
/// Stores the destination local of the outer most function and moves its content into RAX at
103+
/// the end of the trace.
104+
rtn_var: Option<Place>,
101105
}
102106

103107
impl TraceCompiler {
@@ -125,8 +129,26 @@ impl TraceCompiler {
125129
}
126130
}
127131

132+
/// Returns the currently assigned register for a given `Local`. Similar to `local_to_reg` but
133+
/// read-only, i.e. this function doesn't assign `Local`'s to registers.
134+
fn get_reg(&self, local: &Local) -> Result<u8, CompileError> {
135+
match self.assigned_regs.get(local) {
136+
Some(u) => Ok(*u),
137+
None => Err(CompileError::UnknownLocal),
138+
}
139+
}
140+
128141
fn free_register(&mut self, local: &Local) {
129142
if let Some(reg) = self.assigned_regs.remove(local) {
143+
if local == &self.rtn_var.as_ref().unwrap().local {
144+
// We currently assume that we only trace a single function which leaves its return
145+
// value in RAX. Since we now inline a function's return variable this won't happen
146+
// automatically anymore. To keep things working, we thus copy the return value of
147+
// the most outer function into RAX at the end of the trace.
148+
dynasm!(self.asm
149+
; mov rax, Rq(reg)
150+
);
151+
}
130152
self.available_regs.push(reg);
131153
}
132154
}
@@ -217,19 +239,9 @@ impl TraceCompiler {
217239
},
218240
}
219241
}
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-
}
242+
if self.rtn_var.is_none() {
243+
// Remember the return variable of the most outer function.
244+
self.rtn_var = dest.as_ref().cloned();
233245
}
234246
Ok(())
235247
}
@@ -313,7 +325,7 @@ impl TraceCompiler {
313325
};
314326
}
315327
Statement::Enter(op, args, dest, off) => self.c_enter(op, args, dest, *off)?,
316-
Statement::Leave => self.c_leave()?,
328+
Statement::Leave => {}
317329
Statement::StorageLive(_) => {}
318330
Statement::StorageDead(l) => self.free_register(l),
319331
Statement::Call(target, args, dest) => self.c_call(target, args, dest)?,
@@ -388,7 +400,7 @@ impl TraceCompiler {
388400
// Use all the 64-bit registers we can (R15-R8, RDX, RCX).
389401
available_regs: vec![15, 14, 13, 12, 11, 10, 9, 8, 2, 1],
390402
assigned_regs: HashMap::new(),
391-
leaves: Vec::new(),
403+
rtn_var: None,
392404
};
393405

394406
for i in 0..tt.len() {
@@ -452,7 +464,7 @@ mod tests {
452464
asm: dynasmrt::x64::Assembler::new().unwrap(),
453465
available_regs: vec![15, 14, 13, 12, 11, 10, 9, 8, 2, 1],
454466
assigned_regs: HashMap::new(),
455-
leaves: Vec::new(),
467+
rtn_var: None,
456468
};
457469

458470
for _ in 0..32 {
@@ -470,7 +482,7 @@ mod tests {
470482
asm: dynasmrt::x64::Assembler::new().unwrap(),
471483
available_regs: vec![15, 14, 13, 12, 11, 10, 9, 8, 2, 1],
472484
assigned_regs: HashMap::new(),
473-
leaves: Vec::new(),
485+
rtn_var: None,
474486
};
475487

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

yktrace/src/tir.rs

Lines changed: 23 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,17 @@ 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. More importantly however, this guards us
390+
// against a future optimisation in rustc that allows SIR to assign to $0 multiple
391+
// times and at the beginning of a block, which could lead to another function
392+
// overwriting its value (see https://github.com/rust-lang/rust/pull/72205).
393+
if let Some(v) = self.returns.last() {
394+
v.clone()
395+
} else {
396+
panic!("Expected return value!")
397+
}
385398
} else {
386399
let mut p = place.clone();
387400
p.local = self.rename_local(&p.local);

0 commit comments

Comments
 (0)