Skip to content

Commit 9c91b43

Browse files
bors[bot]vext01
andauthored
Merge ykjit#66
66: Codegen calls. r=ptersilie a=vext01 This makes a start on code-genning calls. See individual commit messages. The second commit is unrelated and should stay separate. Co-authored-by: Edd Barrett <vext01@gmail.com>
2 parents af89bd8 + ce3a019 commit 9c91b43

File tree

1 file changed

+95
-13
lines changed

1 file changed

+95
-13
lines changed

ykcompile/src/lib.rs

Lines changed: 95 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ use yktrace::tir::{
2020

2121
use dynasmrt::DynasmApi;
2222

23+
const X86_86_NUM_REGS: u8 = 16;
24+
2325
#[derive(Debug, Hash, Eq, PartialEq)]
2426
pub enum CompileError {
2527
/// We ran out of registers.
@@ -28,13 +30,16 @@ pub enum CompileError {
2830
/// Compiling this statement is not yet implemented.
2931
/// The string inside is a hint as to what kind of statement needs to be implemented.
3032
Unimplemented(String),
33+
/// The binary symbol could not be found.
34+
UnknownSymbol(String),
3135
}
3236

3337
impl Display for CompileError {
3438
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
3539
match self {
3640
Self::OutOfRegisters => write!(f, "Ran out of registers"),
3741
Self::Unimplemented(s) => write!(f, "Unimplemented compilation: {}", s),
42+
Self::UnknownSymbol(s) => write!(f, "Unknown symbol: {}", s),
3843
}
3944
}
4045
}
@@ -73,7 +78,14 @@ impl CompiledTrace {
7378
// For now a compiled trace always returns whatever has been left in register RAX. We also
7479
// assume for now that this will be a `u64`.
7580
let func: fn() -> u64 = unsafe { mem::transmute(self.mc.ptr(dynasmrt::AssemblyOffset(0))) };
76-
func()
81+
self.exec_trace(func)
82+
}
83+
84+
/// Jump to JITted code. This is a separate unmangled function to make it easy to set a
85+
/// debugger breakpoint right before entering the trace.
86+
#[no_mangle]
87+
fn exec_trace(&self, t_fn: fn() -> u64) -> u64 {
88+
t_fn()
7789
}
7890
}
7991

@@ -222,6 +234,63 @@ impl TraceCompiler {
222234
Ok(())
223235
}
224236

237+
/// Compile a call to a native symbol. This is used for occasions where you don't want the
238+
/// callee inlined, or cannot inline it (e.g. it's a foreign function).
239+
fn c_call(
240+
&mut self,
241+
opnd: &CallOperand,
242+
args: &Vec<Operand>,
243+
dest: &Option<Place>,
244+
) -> Result<(), CompileError> {
245+
let sym = if let CallOperand::Fn(sym) = opnd {
246+
sym
247+
} else {
248+
return Err(CompileError::Unimplemented(
249+
"unknown call target".to_owned(),
250+
));
251+
};
252+
253+
// FIXME implement support for arguments.
254+
if !args.is_empty() {
255+
return Err(CompileError::Unimplemented("call with args".to_owned()));
256+
}
257+
258+
// Generally speaking, we don't know what the callee will clobber, so we save our registers
259+
// before doing the call. For now we assume that the caller is OK with RAX being clobbered
260+
// though.
261+
for reg in 1..X86_86_NUM_REGS {
262+
dynasm!(self.asm
263+
; push Rq(reg)
264+
);
265+
}
266+
267+
let sym_addr = TraceCompiler::find_symbol(sym)? as i64;
268+
dynasm!(self.asm
269+
// In Sys-V ABI, `al` is a hidden argument used to specify the number of vector args
270+
// for a vararg call. We don't support this right now, so set it to zero. FIXME.
271+
; xor rax, rax
272+
; mov r11, QWORD sym_addr
273+
; call r11
274+
);
275+
276+
// Restore registers.
277+
for reg in (1..X86_86_NUM_REGS).rev() {
278+
dynasm!(self.asm
279+
; pop Rq(reg)
280+
);
281+
}
282+
283+
// Put return value in place.
284+
if let Some(dest) = dest {
285+
let dest_reg = self.local_to_reg(dest.local)?;
286+
dynasm!(self.asm
287+
; mov Rq(dest_reg), rax
288+
);
289+
}
290+
291+
Ok(())
292+
}
293+
225294
fn statement(&mut self, stmt: &Statement) -> Result<(), CompileError> {
226295
match stmt {
227296
Statement::Assign(l, r) => {
@@ -247,7 +316,7 @@ impl TraceCompiler {
247316
Statement::Leave => self.c_leave()?,
248317
Statement::StorageLive(_) => {}
249318
Statement::StorageDead(l) => self.free_register(l),
250-
c @ Statement::Call(..) => return Err(CompileError::Unimplemented(format!("{:?}", c))),
319+
Statement::Call(target, args, dest) => self.c_call(target, args, dest)?,
251320
Statement::Nop => {}
252321
Statement::Unimplemented(s) => {
253322
return Err(CompileError::Unimplemented(format!("{:?}", s)))
@@ -337,25 +406,24 @@ impl TraceCompiler {
337406
CompiledTrace { mc: tc.finish() }
338407
}
339408

340-
#[allow(dead_code)] // Not used just yet.
341-
fn find_symbol(sym: &str) -> Option<*mut c_void> {
409+
fn find_symbol(sym: &str) -> Result<*mut c_void, CompileError> {
342410
use std::ffi::CString;
343411

344412
let sym_arg = CString::new(sym).unwrap();
345413
let addr = unsafe { dlsym(RTLD_DEFAULT, sym_arg.into_raw()) };
346414

347415
if addr == 0 as *mut c_void {
348-
None
416+
Err(CompileError::UnknownSymbol(sym.to_owned()))
349417
} else {
350-
Some(addr)
418+
Ok(addr)
351419
}
352420
}
353421
}
354422

355423
#[cfg(test)]
356424
mod tests {
357-
use super::{HashMap, Local, TraceCompiler};
358-
use libc::c_void;
425+
use super::{CompileError, HashMap, Local, TraceCompiler};
426+
use libc::{c_void, getuid};
359427
use std::collections::HashSet;
360428
use yktrace::tir::{CallOperand, Statement, TirOp, TirTrace};
361429
use yktrace::{start_tracing, TracingKind};
@@ -435,7 +503,7 @@ mod tests {
435503
assert_eq!(ct.execute(), 13);
436504
}
437505

438-
fn fnested3(i: u8, j: u8) -> u8 {
506+
fn fnested3(i: u8, _j: u8) -> u8 {
439507
let c = i;
440508
c
441509
}
@@ -462,7 +530,7 @@ mod tests {
462530
// Test finding a symbol in a shared object.
463531
#[test]
464532
fn find_symbol_shared() {
465-
assert!(TraceCompiler::find_symbol("printf") == Some(libc::printf as *mut c_void));
533+
assert!(TraceCompiler::find_symbol("printf") == Ok(libc::printf as *mut c_void));
466534
}
467535

468536
// Test finding a symbol in the main binary.
@@ -472,22 +540,25 @@ mod tests {
472540
#[no_mangle]
473541
fn find_symbol_main() {
474542
assert!(
475-
TraceCompiler::find_symbol("find_symbol_main") == Some(find_symbol_main as *mut c_void)
543+
TraceCompiler::find_symbol("find_symbol_main") == Ok(find_symbol_main as *mut c_void)
476544
);
477545
}
478546

479547
// Check that a non-existent symbol cannot be found.
480548
#[test]
481549
fn find_nonexistent_symbol() {
482-
assert!(TraceCompiler::find_symbol("__xxxyyyzzz__").is_none());
550+
assert_eq!(
551+
TraceCompiler::find_symbol("__xxxyyyzzz__"),
552+
Err(CompileError::UnknownSymbol("__xxxyyyzzz__".to_owned()))
553+
);
483554
}
484555

485556
// A trace which contains a call to something which we don't have SIR for should emit a TIR
486557
// call operation.
487558
#[test]
488559
pub fn call_symbol() {
489560
let th = start_tracing(Some(TracingKind::HardwareTracing));
490-
let g = core::intrinsics::wrapping_add(10u64, 40u64);
561+
let _ = core::intrinsics::wrapping_add(10u64, 40u64);
491562
let sir_trace = th.stop_tracing().unwrap();
492563
let tir_trace = TirTrace::new(&*sir_trace).unwrap();
493564

@@ -502,4 +573,15 @@ mod tests {
502573
}
503574
assert!(found_call);
504575
}
576+
577+
/// Execute a trace which calls a symbol accepting no arguments, but which does return a value.
578+
#[test]
579+
pub fn exec_call_symbol_no_args_with_rv() {
580+
let th = start_tracing(Some(TracingKind::HardwareTracing));
581+
let expect = unsafe { getuid() };
582+
let sir_trace = th.stop_tracing().unwrap();
583+
let tir_trace = TirTrace::new(&*sir_trace).unwrap();
584+
let got = TraceCompiler::compile(tir_trace).execute();
585+
assert_eq!(expect as u64, got);
586+
}
505587
}

0 commit comments

Comments
 (0)