GitLab will be upgraded to the 12.10.14-ce.0 on 28 Sept 2020 at 2.00pm (AEDT) to 2.30pm (AEDT). During the update, GitLab and Mattermost services will not be available. If you have any concerns with this, please talk to us at N110 (b) CSIT building.

Commit a49d8ab6 authored by qinsoon's avatar qinsoon

patch unresolved callsites after jit compiled the callee

parent fa77a6d8
......@@ -3238,6 +3238,7 @@ impl CodeGenerator for ASMCodeGen {
&mut self,
callsite: String,
func: MuName,
func_id: MuID,
addr: Address,
pe: Option<MuName>,
is_native: bool
......
......@@ -365,6 +365,7 @@ impl BinaryCodeGen {
inst: XedInstruction,
callsite_name: MuName,
dest: Address,
func_id: MuID,
potentially_exception: Option<MuName>
) {
self.add_xed_inst_internal(
......@@ -379,8 +380,9 @@ impl BinaryCodeGen {
} else {
BranchTarget::None
},
Some(MyCallsite {
Some(JITCallsite {
name: callsite_name,
callee_id: func_id,
target: CallTarget::Direct(dest)
})
)
......@@ -395,7 +397,7 @@ impl BinaryCodeGen {
is_move: bool,
spill_info: Option<SpillMemInfo>,
branch: BranchTarget,
call: Option<MyCallsite>
call: Option<JITCallsite>
) {
let ctx = self.cur_mut();
trace!("xed: {:?}", inst);
......@@ -1030,6 +1032,7 @@ impl CodeGenerator for BinaryCodeGen {
&mut self,
callsite: String,
func: MuName,
func_id: MuID,
addr: Address,
pe: Option<MuName>,
is_native: bool
......@@ -1046,7 +1049,7 @@ impl CodeGenerator for BinaryCodeGen {
target
);
self.add_xed_call_addr(inst, callsite, addr, pe);
self.add_xed_call_addr(inst, callsite, addr, func_id, pe);
unsafe { ValueLocation::null() }
}
......@@ -1287,7 +1290,7 @@ pub fn emit_code(fv: &mut MuFunctionVersion, vm: &VM) {
// generate code (back patching included)
let code_start = mm::allocate_code(JIT_CODE_SIZE);
let code_size = xed_ctx.generate_code(code_start, JIT_CODE_SIZE);
let code_size = xed_ctx.generate_code(code_start, JIT_CODE_SIZE, vm);
(code_start, code_start + code_size)
};
......@@ -1320,3 +1323,36 @@ pub fn emit_code(fv: &mut MuFunctionVersion, vm: &VM) {
info!("code emission done");
}
pub fn jit_patch_call_addr(dest: Address, inst_start: Address, length: ByteSize) {
use std::i32;
let call_offset = dest.diff_offset(inst_start + length);
assert!(
call_offset >= i32::MIN as isize && call_offset <= i32::MAX as isize,
"call offset is not suitable for a CALL NEAR"
);
let inst = xed_inst1(
xed_state_64(),
xed_iclass_enum_t::XED_ICLASS_CALL_NEAR,
64,
xed_relbr(call_offset as i32, 32)
);
let new_len = encode_inst(
&inst,
inst_start.to_ptr_mut(),
XED_MAX_INSTRUCTION_BYTES as usize
);
if new_len < length {
warn!("need to pad with a few nops");
unimplemented!();
} else if new_len > length {
panic!("patching used more space than we have");
} else {
// do nothing
}
}
\ No newline at end of file
......@@ -13,6 +13,8 @@ use compiler::machine_code::MachineCode;
use compiler::machine_code::CompiledCallsite;
use runtime::ValueLocation;
use runtime::mm;
use runtime::patchpoint::RuntimePatchpoint;
use vm::VM;
use utils::mem::memsec;
use utils::vec_utils;
use utils::Address;
......@@ -243,38 +245,57 @@ impl XedContext {
ret
}
pub fn generate_code(&mut self, code_ptr: Address, len: ByteSize) -> ByteSize {
pub fn generate_code(&mut self, code_ptr: Address, len: ByteSize, vm: &VM) -> ByteSize {
info!("code mission");
// generate code and recording branches and block start address for back patching
let mut cur_ptr = code_ptr;
let mut used = 0;
let code_length = {
let mut cur_ptr = code_ptr;
let mut used = 0;
for i in 0..self.insts.len() {
trace!("encode inst: {}", self.pretty_print_inst(i));
for i in 0..self.insts.len() {
trace!("encode inst: {}", self.pretty_print_inst(i));
let my_len = encode_inst(
&self.insts[i],
cur_ptr.to_ptr_mut(),
XED_MAX_INSTRUCTION_BYTES as usize
);
let my_len = encode_inst(
&self.insts[i],
cur_ptr.to_ptr_mut(),
XED_MAX_INSTRUCTION_BYTES as usize
);
trace!("emitted");
trace!(
"0x{:x}: {}",
cur_ptr,
XedContext::inspect_code(cur_ptr, my_len)
);
trace!("");
trace!("emitted");
trace!(
"0x{:x}: {}",
cur_ptr,
XedContext::inspect_code(cur_ptr, my_len)
);
trace!("");
self.insts_addr.push(cur_ptr);
self.insts_len.push(my_len as ByteSize);
cur_ptr = cur_ptr + my_len;
used += my_len;
}
self.insts_addr.push(cur_ptr);
self.insts_len.push(my_len as ByteSize);
cur_ptr = cur_ptr + my_len;
used += my_len;
}
trace!("generated code (before backpatching): ");
trace!("{}", XedContext::inspect_code(code_ptr, used));
trace!("generated code (before backpatching): ");
trace!("{}", XedContext::inspect_code(code_ptr, used));
used
};
// another pass through the code, record patchpoints
info!("record patch point...");
for i in 0..self.insts.len() {
if let Some(ref callsite) = self.insts_info[i].call {
let inst_addr = self.insts_addr[i];
let patchpoint = RuntimePatchpoint::inst(inst_addr, self.insts_len[i]);
trace!(
"added callsite for func {} at {}",
callsite.callee_id,
inst_addr
);
vm.add_callsite_patchpoint(callsite.callee_id, patchpoint);
}
}
info!("back patching...");
for i in 0..self.insts.len() {
......@@ -291,7 +312,7 @@ impl XedContext {
// we may need to patch offsets
let new_inst = match info.call {
// if we are calling an address, we need to patch offset
Some(MyCallsite {
Some(JITCallsite {
target: CallTarget::Direct(callee_addr),
..
}) => {
......@@ -353,10 +374,10 @@ impl XedContext {
}
trace!("generated code (after backpatching): ");
trace!("{}", XedContext::inspect_code(code_ptr, used));
trace!("{}", XedContext::inspect_code(code_ptr, code_length));
trace!("back patching done");
used
code_length
}
/// builds a callsite table for runtime query
......@@ -592,7 +613,7 @@ pub struct InstInfo {
/// branch target of this instruction (intra-procedural)
pub branch: BranchTarget,
/// call target of this instruction (inter-procedural)
pub call: Option<MyCallsite>
pub call: Option<JITCallsite>
}
#[derive(Copy, Clone, Debug)]
......@@ -980,8 +1001,9 @@ pub enum BranchTarget {
}
#[derive(Clone, Debug)]
pub struct MyCallsite {
pub struct JITCallsite {
pub name: MuName,
pub callee_id: MuID,
pub target: CallTarget
}
......
......@@ -19,6 +19,7 @@ mod binary_codegen;
pub use self::binary_codegen::BinaryCodeGen;
pub use self::binary_codegen::init_jit;
pub use self::binary_codegen::emit_code;
pub use self::binary_codegen::jit_patch_call_addr;
mod code_context;
......
......@@ -226,6 +226,7 @@ pub trait CodeGenerator {
&mut self,
callsite: String,
func: MuName,
func_id: MuID,
addr: Address,
pe: Option<MuName>,
is_native: bool
......
......@@ -3730,6 +3730,7 @@ impl<'a> InstructionSelection {
self.backend.emit_call_near_rel32(
callsite.clone(),
func_name,
0,
unsafe { Address::zero() },
None,
true
......@@ -3906,6 +3907,7 @@ impl<'a> InstructionSelection {
self.backend.emit_call_near_rel32(
callsite,
target.name(),
target_id,
unsafe { Address::zero() },
potentially_excepting,
false
......@@ -3917,6 +3919,7 @@ impl<'a> InstructionSelection {
self.backend.emit_call_near_rel32(
callsite,
target.name(),
target_id,
addr,
potentially_excepting,
false
......@@ -3927,6 +3930,7 @@ impl<'a> InstructionSelection {
self.backend.emit_call_near_rel32(
callsite,
target.name(),
target_id,
entrypoints::LAZY_RESOLVE_FUNC.jit,
potentially_excepting,
false
......
......@@ -130,6 +130,9 @@ pub use compiler::backend::x86_64::asm_backend::spill_rewrite as asm_spill_rewri
#[cfg(feature = "binary-codegen")]
pub use compiler::backend::x86_64::binary_backend::spill_rewrite as binary_spill_rewrite;
#[cfg(target_arch = "x86_64")]
#[cfg(feature = "binary-codegen")]
pub use compiler::backend::x86_64::binary_backend::jit_patch_call_addr;
#[cfg(target_arch = "x86_64")]
pub use compiler::backend::x86_64::ARGUMENT_GPRS;
#[cfg(target_arch = "x86_64")]
pub use compiler::backend::x86_64::ARGUMENT_FPRS;
......
......@@ -183,7 +183,20 @@ pub extern "C" fn lazy_resolve_function_internal(callsite: Address) -> Address {
};
trace!("lazy compiling function version {}", func_id);
vm.jit_function(func_id)
let func_start = vm.jit_function(func_id);
// patch all callsites of this function
{
use runtime::patchpoint::*;
let patch_table = vm.callsite_patch_table().read().unwrap();
for patchpoint in patch_table.get(&func_id).unwrap().iter() {
assert!(patchpoint.v == RuntimePatchpoint_::Instruction);
backend::jit_patch_call_addr(func_start, patchpoint.start, patchpoint.length);
}
}
func_start
}
// rewrite parts of the hprof crates to print via log (instead of print!())
......
......@@ -39,6 +39,8 @@ pub mod math;
pub mod entrypoints;
/// exception handling
pub mod exception;
/// code patching point
pub mod patchpoint;
/// returns name for a function address
......
use utils::Address;
use utils::ByteSize;
#[derive(Clone, Copy, Debug)]
pub struct RuntimePatchpoint {
pub start: Address,
pub length: ByteSize,
pub v: RuntimePatchpoint_
}
impl RuntimePatchpoint {
pub fn inst(start: Address, length: ByteSize) -> RuntimePatchpoint {
RuntimePatchpoint {
start: start,
length: length,
v: RuntimePatchpoint_::Instruction
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum RuntimePatchpoint_ {
Instruction,
Offset(ByteSize)
}
\ No newline at end of file
......@@ -25,12 +25,13 @@ use compiler::backend;
use compiler::backend::BackendType;
use compiler::machine_code::{CompiledCallsite, CompiledFunction};
use runtime::thread::*;
use runtime::*;
use utils::ByteSize;
use utils::BitSize;
use utils::Address;
use runtime::thread::*;
use runtime::*;
use runtime::mm as gc;
use runtime::patchpoint::RuntimePatchpoint;
use vm::handle::*;
use vm::vm_options::VMOptions;
use vm::vm_options::MuLogLevel;
......@@ -119,6 +120,13 @@ pub struct VM {
/// a map from callsite address to CompiledCallsite
compiled_callsite_table: RwLock<HashMap<Address, CompiledCallsite>>, // 896
/// patchpoints for callsites. This is used for two scenarios:
/// * for unresolved functions, we insert a call to trampoline, but later after we JIT compiled
/// the callee, we need to patch the callsite
/// * for function redefinition, we need to patch the callsite as well
/// We keep this as a separate table because we want to index it with function ID
callsite_patchpoints: RwLock<HashMap<MuID, Vec<RuntimePatchpoint>>>,
/// Nnmber of callsites in the callsite tables
callsite_count: AtomicUsize
}
......@@ -162,6 +170,12 @@ unsafe impl rodal::Dump for VM {
dumper.dump_object_here(&RwLock::new(
rodal::EmptyHashMap::<Address, CompiledCallsite>::new()
));
dumper.dump_padding(&self.callsite_patchpoints);
dumper.dump_object_here(&RwLock::new(
rodal::EmptyHashMap::<MuID, Vec<RuntimePatchpoint>>::new()
));
dumper.dump_object(&self.callsite_count);
}
}
......@@ -225,6 +239,7 @@ impl<'a> VM {
primordial: RwLock::new(None),
aot_pending_funcref_store: RwLock::new(HashMap::new()),
compiled_callsite_table: RwLock::new(HashMap::new()),
callsite_patchpoints: RwLock::new(HashMap::new()),
callsite_count: ATOMIC_USIZE_INIT
};
......@@ -341,6 +356,17 @@ impl<'a> VM {
self.callsite_count.fetch_add(1, Ordering::Relaxed);
}
/// adds a callsite patchpoint
pub fn add_callsite_patchpoint(&self, function_id: MuID, patchpoint: RuntimePatchpoint) {
let mut table = self.callsite_patchpoints.write().unwrap();
if table.contains_key(&function_id) {
table.get_mut(&function_id).unwrap().push(patchpoint);
} else {
table.insert(function_id, vec![patchpoint]);
}
}
/// resumes persisted VM. Ideally the VM should be back to the status when we start
/// persisting it except a few fields that we do not want to persist.
pub fn resume_vm(dumped_vm: *mut Arc<VM>) -> Arc<VM> {
......@@ -953,6 +979,11 @@ impl<'a> VM {
&self.callsite_table
}
/// returns the lock for callsite patch table
pub fn callsite_patch_table(&self) -> &RwLock<HashMap<MuID, Vec<RuntimePatchpoint>>> {
&self.callsite_patchpoints
}
/// set info (entry function, arguments) for primordial thread for boot image
pub fn set_primordial_thread(&self, func_id: MuID, has_const_args: bool, args: Vec<Constant>) {
let mut guard = self.primordial.write().unwrap();
......
......@@ -70,6 +70,42 @@ fn test_call_add_unresolved() {
}
}
#[test]
fn test_call_add_callsite_patching() {
VM::start_logging_trace();
let mut vm = add();
call_add(&vm);
// do not inline functions otherwise add() will get inlined
vm.vm_options.flag_disable_inline = true;
let vm = Arc::new(vm);
// compile call_add() - at this time, add() is NOT compiled (we will lazily compile add())
let call_add_id = vm.id_of("call_add");
let code_start = vm.jit_function(call_add_id);
info!("call_add() is at 0x{:x}", code_start);
info!("add() fvid = {}", vm.id_of("add"));
unsafe {
let need_dealloc = MuThread::current_thread_as_mu_thread(Address::zero(), vm.clone());
let call_add: unsafe extern "C" fn(u64, u64) -> u64 = mem::transmute(code_start);
let res = call_add(1, 1);
println!("add(1, 1) = {}", res);
assert!(res == 2);
let res = call_add(1, 2);
println!("add(1, 2) = {}", res);
assert!(res == 3);
if need_dealloc {
MuThread::cleanup_current_mu_thread();
}
}
}
fn call_add(vm: &VM) {
let int64 = vm.get_type(vm.id_of("int64"));
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment