Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 9974149

Browse files
committed
ZJIT: Split Send into Lookup+Call
1 parent 347e581 commit 9974149

File tree

10 files changed

+677
-495
lines changed

10 files changed

+677
-495
lines changed

jit.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,3 +421,16 @@ rb_assert_cme_handle(VALUE handle)
421421
RUBY_ASSERT_ALWAYS(!rb_objspace_garbage_object_p(handle));
422422
RUBY_ASSERT_ALWAYS(IMEMO_TYPE_P(handle, imemo_ment));
423423
}
424+
425+
extern
426+
VALUE
427+
rb_vm_call0(rb_execution_context_t *ec, VALUE recv, ID id, int argc, const VALUE *argv, const rb_callable_method_entry_t *cme, int kw_splat);
428+
429+
VALUE
430+
rb_zjit_vm_call0_no_splat(rb_execution_context_t *ec, VALUE recv, ID id, int argc, const VALUE *argv, const rb_callable_method_entry_t *cme) {
431+
return rb_vm_call0(ec, recv, id, argc, argv, cme, /*kw_splat=*/0);
432+
}
433+
434+
extern
435+
VALUE
436+
rb_vm_send(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, CALL_DATA cd, ISEQ blockiseq);

zjit/bindgen/src/main.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,8 @@ fn main() {
347347
.allowlist_function("rb_optimized_call")
348348
.allowlist_function("rb_zjit_icache_invalidate")
349349
.allowlist_function("rb_zjit_print_exception")
350+
.allowlist_function("rb_zjit_vm_call0_no_splat")
351+
.allowlist_function("rb_vm_send")
350352
.allowlist_type("robject_offsets")
351353
.allowlist_type("rstring_offsets")
352354

zjit/src/backend/arm64/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ pub const _C_ARG_OPNDS: [Opnd; 6] = [
2828
// C return value register on this platform
2929
pub const C_RET_REG: Reg = X0_REG;
3030
pub const _C_RET_OPND: Opnd = Opnd::Reg(X0_REG);
31+
pub const _NATIVE_STACK_PTR: Opnd = Opnd::Reg(X13_REG);
3132

3233
// These constants define the way we work with Arm64's stack pointer. The stack
3334
// pointer always needs to be aligned to a 16-byte boundary.
@@ -679,6 +680,10 @@ impl Assembler
679680
*opnd = opnd0;
680681
asm.push_insn(insn);
681682
},
683+
Insn::CPush(opnd) => {
684+
let opnd = split_load_operand(asm, *opnd);
685+
asm.push_insn(Insn::CPush(opnd));
686+
}
682687
Insn::Store { dest, src } => {
683688
// The value being stored must be in a register, so if it's
684689
// not already one we'll load it first.

zjit/src/backend/lir.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ pub const SP: Opnd = _SP;
1616

1717
pub const C_ARG_OPNDS: [Opnd; 6] = _C_ARG_OPNDS;
1818
pub const C_RET_OPND: Opnd = _C_RET_OPND;
19+
pub const NATIVE_STACK_PTR: Opnd = _NATIVE_STACK_PTR;
1920
pub use crate::backend::current::{Reg, C_RET_REG};
2021

2122
// Memory operand base
@@ -2225,7 +2226,7 @@ impl Assembler {
22252226
/// when not dumping disassembly.
22262227
macro_rules! asm_comment {
22272228
($asm:expr, $($fmt:tt)*) => {
2228-
if $crate::options::get_option!(dump_disasm) {
2229+
if $crate::options::get_option!(dump_disasm) || $crate::options::get_option!(dump_lir) {
22292230
$asm.push_insn(crate::backend::lir::Insn::Comment(format!($($fmt)*)));
22302231
}
22312232
};

zjit/src/backend/x86_64/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ pub const _C_ARG_OPNDS: [Opnd; 6] = [
2828
// C return value register on this platform
2929
pub const C_RET_REG: Reg = RAX_REG;
3030
pub const _C_RET_OPND: Opnd = Opnd::Reg(RAX_REG);
31+
pub const _NATIVE_STACK_PTR: Opnd = Opnd::Reg(RSP_REG);
3132

3233
impl CodeBlock {
3334
// The number of bytes that are generated by jmp_ptr

zjit/src/codegen.rs

Lines changed: 104 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ use crate::profile::get_or_create_iseq_payload;
66
use crate::state::ZJITState;
77
use crate::{asm::CodeBlock, cruby::*, options::debug, virtualmem::CodePtr};
88
use crate::invariants::{iseq_escapes_ep, track_no_ep_escape_assumption};
9-
use crate::backend::lir::{self, asm_comment, Assembler, Opnd, Target, CFP, C_ARG_OPNDS, C_RET_OPND, EC, SP};
10-
use crate::hir::{iseq_to_hir, Block, BlockId, BranchEdge, CallInfo, RangeType, SELF_PARAM_IDX};
9+
use crate::backend::lir::{self, asm_comment, Assembler, Opnd, Target, CFP, C_ARG_OPNDS, C_RET_OPND, EC, SP, NATIVE_STACK_PTR};
10+
use crate::hir::{iseq_to_hir, Block, BlockId, BranchEdge, RangeType, SELF_PARAM_IDX};
1111
use crate::hir::{Const, FrameState, Function, Insn, InsnId};
1212
use crate::hir_type::{types::Fixnum, Type};
1313
use crate::options::get_option;
@@ -257,8 +257,10 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
257257
Insn::Jump(branch) => return gen_jump(jit, asm, branch),
258258
Insn::IfTrue { val, target } => return gen_if_true(jit, asm, opnd!(val), target),
259259
Insn::IfFalse { val, target } => return gen_if_false(jit, asm, opnd!(val), target),
260-
Insn::SendWithoutBlock { call_info, cd, state, self_val, args, .. } => gen_send_without_block(jit, asm, call_info, *cd, &function.frame_state(*state), self_val, args)?,
261-
Insn::SendWithoutBlockDirect { iseq, self_val, args, .. } => gen_send_without_block_direct(cb, jit, asm, *iseq, opnd!(self_val), args)?,
260+
Insn::LookupMethod { self_val, method_id, state } => gen_lookup_method(jit, asm, opnd!(self_val), *method_id, &function.frame_state(*state))?,
261+
Insn::CallMethod { callable, cd, self_val, args, state } => gen_call_method(jit, asm, opnd!(callable), *cd, opnd!(self_val), args, &function.frame_state(*state))?,
262+
Insn::CallCFunc { cfunc, self_val, args, .. } => gen_call_cfunc(jit, asm, *cfunc, opnd!(self_val), args)?,
263+
Insn::CallIseq { iseq, self_val, args, .. } => gen_send_without_block_direct(cb, jit, asm, *iseq, opnd!(self_val), args)?,
262264
Insn::Return { val } => return Some(gen_return(asm, opnd!(val))?),
263265
Insn::FixnumAdd { left, right, state } => gen_fixnum_add(jit, asm, opnd!(left), opnd!(right), &function.frame_state(*state))?,
264266
Insn::FixnumSub { left, right, state } => gen_fixnum_sub(jit, asm, opnd!(left), opnd!(right), &function.frame_state(*state))?,
@@ -455,38 +457,109 @@ fn gen_if_false(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, branch:
455457
}
456458

457459
/// Compile a dynamic dispatch without block
458-
fn gen_send_without_block(
460+
fn gen_lookup_method(
459461
jit: &mut JITState,
460462
asm: &mut Assembler,
461-
call_info: &CallInfo,
462-
cd: *const rb_call_data,
463+
recv: Opnd,
464+
method_id: ID,
465+
state: &FrameState,
466+
) -> Option<lir::Opnd> {
467+
asm_comment!(asm, "get the class of the receiver with rb_obj_class");
468+
let class = asm.ccall(rb_obj_class as *const u8, vec![recv]);
469+
// TODO(max): Figure out if we need to do anything here to save state to CFP
470+
let method_opnd = Opnd::UImm(method_id.0.into());
471+
asm_comment!(asm, "call rb_callable_method_entry");
472+
let result = asm.ccall(
473+
rb_callable_method_entry as *const u8,
474+
vec![class, method_opnd],
475+
);
476+
asm.test(result, result);
477+
asm.jz(side_exit(jit, state)?);
478+
Some(result)
479+
}
480+
481+
fn gen_call_method(
482+
jit: &mut JITState,
483+
asm: &mut Assembler,
484+
callable: Opnd,
485+
cd: CallDataPtr,
486+
recv: Opnd,
487+
args: &Vec<InsnId>,
463488
state: &FrameState,
464-
self_val: &InsnId,
489+
) -> Option<Opnd> {
490+
// Don't push recv; it is passed in separately.
491+
asm_comment!(asm, "make stack-allocated array of {} args", args.len());
492+
for &arg in args.iter().rev() {
493+
asm.cpush(jit.get_opnd(arg)?);
494+
}
495+
// Save PC for GC
496+
gen_save_pc(asm, state);
497+
// Call rb_zjit_vm_call0_no_splat, which will push a frame
498+
// TODO(max): Figure out if we need to manually handle stack alignment and how to do it
499+
let call_info = unsafe { rb_get_call_data_ci(cd) };
500+
let method_id = unsafe { rb_vm_ci_mid(call_info) };
501+
asm_comment!(asm, "get stack pointer");
502+
let sp = asm.lea(Opnd::mem(VALUE_BITS, NATIVE_STACK_PTR, 0));
503+
asm_comment!(asm, "call rb_zjit_vm_call0_no_splat");
504+
let result = asm.ccall(
505+
rb_zjit_vm_call0_no_splat as *const u8,
506+
vec![EC, recv, Opnd::UImm(method_id.0), Opnd::UImm(args.len().try_into().unwrap()), sp, callable],
507+
);
508+
// Pop all the args off the stack
509+
asm_comment!(asm, "clear stack-allocated array of {} args", args.len());
510+
let new_sp = asm.add(NATIVE_STACK_PTR, (args.len()*SIZEOF_VALUE).into());
511+
asm.mov(NATIVE_STACK_PTR, new_sp);
512+
Some(result)
513+
}
514+
515+
/// Compile an interpreter frame
516+
fn gen_push_frame(asm: &mut Assembler, recv: Opnd) {
517+
// Write to a callee CFP
518+
fn cfp_opnd(offset: i32) -> Opnd {
519+
Opnd::mem(64, CFP, offset - (RUBY_SIZEOF_CONTROL_FRAME as i32))
520+
}
521+
522+
asm_comment!(asm, "push callee control frame");
523+
asm.mov(cfp_opnd(RUBY_OFFSET_CFP_SELF), recv);
524+
// TODO: Write more fields as needed
525+
}
526+
527+
fn gen_call_cfunc(
528+
jit: &mut JITState,
529+
asm: &mut Assembler,
530+
cfunc: CFuncPtr,
531+
recv: Opnd,
465532
args: &Vec<InsnId>,
466533
) -> Option<lir::Opnd> {
467-
// Spill the receiver and the arguments onto the stack. They need to be marked by GC and may be caller-saved registers.
468-
// TODO: Avoid spilling operands that have been spilled before.
469-
for (idx, &insn_id) in [*self_val].iter().chain(args.iter()).enumerate() {
470-
// Currently, we don't move the SP register. So it's equal to the base pointer.
471-
let stack_opnd = Opnd::mem(64, SP, idx as i32 * SIZEOF_VALUE_I32);
472-
asm.mov(stack_opnd, jit.get_opnd(insn_id)?);
534+
let cfunc_argc = unsafe { get_mct_argc(cfunc) };
535+
// NB: The presence of self is assumed (no need for +1).
536+
if args.len() != cfunc_argc as usize {
537+
// TODO(max): We should check this at compile-time. If we have an arity mismatch at this
538+
// point, we should side-exit (we're definitely going to raise) and if we don't, we should
539+
// not check anything.
540+
todo!("Arity mismatch");
473541
}
474542

475-
// Save PC and SP
476-
gen_save_pc(asm, state);
477-
gen_save_sp(asm, 1 + args.len()); // +1 for receiver
543+
// Set up the new frame
544+
gen_push_frame(asm, recv);
545+
546+
asm_comment!(asm, "switch to new CFP");
547+
let new_cfp = asm.sub(CFP, RUBY_SIZEOF_CONTROL_FRAME.into());
548+
asm.mov(CFP, new_cfp);
549+
asm.store(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP), CFP);
478550

479-
asm_comment!(asm, "call #{} with dynamic dispatch", call_info.method_name);
480-
unsafe extern "C" {
481-
fn rb_vm_opt_send_without_block(ec: EcPtr, cfp: CfpPtr, cd: VALUE) -> VALUE;
551+
// Set up arguments
552+
let mut c_args: Vec<Opnd> = vec![recv];
553+
for &arg in args.iter() {
554+
c_args.push(jit.get_opnd(arg)?);
482555
}
483-
let ret = asm.ccall(
484-
rb_vm_opt_send_without_block as *const u8,
485-
vec![EC, CFP, (cd as usize).into()],
486-
);
556+
557+
// Make a method call. The target address will be rewritten once compiled.
558+
let cfun = unsafe { get_mct_func(cfunc) }.cast();
487559
// TODO(max): Add a PatchPoint here that can side-exit the function if the callee messed with
488560
// the frame's locals
489-
561+
let ret = asm.ccall(cfun, c_args);
562+
gen_pop_frame(asm);
490563
Some(ret)
491564
}
492565

@@ -590,14 +663,18 @@ fn gen_new_range(
590663
new_range
591664
}
592665

593-
/// Compile code that exits from JIT code with a return value
594-
fn gen_return(asm: &mut Assembler, val: lir::Opnd) -> Option<()> {
666+
fn gen_pop_frame(asm: &mut Assembler) {
595667
// Pop the current frame (ec->cfp++)
596668
// Note: the return PC is already in the previous CFP
597669
asm_comment!(asm, "pop stack frame");
598670
let incr_cfp = asm.add(CFP, RUBY_SIZEOF_CONTROL_FRAME.into());
599671
asm.mov(CFP, incr_cfp);
600672
asm.mov(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP), CFP);
673+
}
674+
675+
/// Compile code that exits from JIT code with a return value
676+
fn gen_return(asm: &mut Assembler, val: lir::Opnd) -> Option<()> {
677+
gen_pop_frame(asm);
601678

602679
asm.frame_teardown();
603680

zjit/src/cruby.rs

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,9 @@ pub use autogened::*;
120120
#[cfg_attr(test, allow(unused))] // We don't link against C code when testing
121121
unsafe extern "C" {
122122
pub fn rb_check_overloaded_cme(
123-
me: *const rb_callable_method_entry_t,
123+
me: CmePtr,
124124
ci: *const rb_callinfo,
125-
) -> *const rb_callable_method_entry_t;
125+
) -> CmePtr;
126126

127127
// Floats within range will be encoded without creating objects in the heap.
128128
// (Range is 0x3000000000000001 to 0x4fffffffffffffff (1.7272337110188893E-77 to 2.3158417847463237E+77).
@@ -144,8 +144,8 @@ unsafe extern "C" {
144144
pub fn rb_vm_set_ivar_id(obj: VALUE, idx: u32, val: VALUE) -> VALUE;
145145
pub fn rb_vm_setinstancevariable(iseq: IseqPtr, obj: VALUE, id: ID, val: VALUE, ic: IVC);
146146
pub fn rb_aliased_callable_method_entry(
147-
me: *const rb_callable_method_entry_t,
148-
) -> *const rb_callable_method_entry_t;
147+
me: CmePtr,
148+
) -> CmePtr;
149149
pub fn rb_vm_getclassvariable(iseq: IseqPtr, cfp: CfpPtr, id: ID, ic: ICVARC) -> VALUE;
150150
pub fn rb_vm_setclassvariable(
151151
iseq: IseqPtr,
@@ -156,7 +156,7 @@ unsafe extern "C" {
156156
) -> VALUE;
157157
pub fn rb_vm_ic_hit_p(ic: IC, reg_ep: *const VALUE) -> bool;
158158
pub fn rb_vm_stack_canary() -> VALUE;
159-
pub fn rb_vm_push_cfunc_frame(cme: *const rb_callable_method_entry_t, recv_idx: c_int);
159+
pub fn rb_vm_push_cfunc_frame(cme: CmePtr, recv_idx: c_int);
160160
}
161161

162162
// Renames
@@ -259,6 +259,12 @@ pub struct ID(pub ::std::os::raw::c_ulong);
259259
/// Pointer to an ISEQ
260260
pub type IseqPtr = *const rb_iseq_t;
261261

262+
pub type CallDataPtr = *const rb_call_data;
263+
264+
pub type CmePtr = *const rb_callable_method_entry_t;
265+
266+
pub type CFuncPtr = *const rb_method_cfunc_t;
267+
262268
// Given an ISEQ pointer, convert PC to insn_idx
263269
pub fn iseq_pc_to_insn_idx(iseq: IseqPtr, pc: *mut VALUE) -> Option<u16> {
264270
let pc_zero = unsafe { rb_iseq_pc_at_idx(iseq, 0) };
@@ -579,8 +585,8 @@ impl VALUE {
579585
}
580586

581587
/// Assert that `self` is a method entry in debug builds
582-
pub fn as_cme(self) -> *const rb_callable_method_entry_t {
583-
let ptr: *const rb_callable_method_entry_t = self.as_ptr();
588+
pub fn as_cme(self) -> CmePtr {
589+
let ptr: CmePtr = self.as_ptr();
584590

585591
#[cfg(debug_assertions)]
586592
if !ptr.is_null() {
@@ -611,9 +617,9 @@ impl From<IseqPtr> for VALUE {
611617
}
612618
}
613619

614-
impl From<*const rb_callable_method_entry_t> for VALUE {
620+
impl From<CmePtr> for VALUE {
615621
/// For `.into()` convenience
616-
fn from(cme: *const rb_callable_method_entry_t) -> Self {
622+
fn from(cme: CmePtr) -> Self {
617623
VALUE(cme as usize)
618624
}
619625
}

zjit/src/cruby_bindings.inc.rs

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

zjit/src/cruby_methods.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ impl Annotations {
3737
if VM_METHOD_TYPE_CFUNC != get_cme_def_type(method) {
3838
return None;
3939
}
40-
get_mct_func(get_cme_def_body_cfunc(method.cast()))
40+
rb_get_mct_func(rb_get_cme_def_body_cfunc(method.cast()))
4141
};
4242
self.cfuncs.get(&fn_ptr).copied()
4343
}
@@ -65,11 +65,12 @@ pub fn init() -> Annotations {
6565
let cfuncs = &mut HashMap::new();
6666

6767
macro_rules! annotate {
68-
($module:ident, $method_name:literal, $return_type:expr, $($properties:ident),+) => {
68+
($module:ident, $method_name:literal, $return_type:expr, $($properties:ident),*) => {
69+
#[allow(unused_mut)]
6970
let mut props = FnProperties { no_gc: false, leaf: false, elidable: false, return_type: $return_type };
7071
$(
7172
props.$properties = true;
72-
)+
73+
)*
7374
annotate_c_method(cfuncs, unsafe { $module }, $method_name, props);
7475
}
7576
}
@@ -80,6 +81,7 @@ pub fn init() -> Annotations {
8081
annotate!(rb_cModule, "===", types::BoolExact, no_gc, leaf);
8182
annotate!(rb_cArray, "length", types::Fixnum, no_gc, leaf, elidable);
8283
annotate!(rb_cArray, "size", types::Fixnum, no_gc, leaf, elidable);
84+
annotate!(rb_cInteger, "+", types::IntegerExact,);
8385

8486
Annotations {
8587
cfuncs: std::mem::take(cfuncs)

0 commit comments

Comments
 (0)