YJIT: Add jit_prepare_for_gc function (#9775)

* YJIT: Add jit_prepare_for_gc function

* s/jit_prepare_routine_call/jit_prepare_non_leaf_call/

* s/jit_prepare_for_gc/jit_prepare_call_with_gc/

* Use jit_prepare_call_with_gc for leaf builtin
This commit is contained in:
Takashi Kokubun 2024-01-31 09:54:10 -08:00 коммит произвёл GitHub
Родитель 06732d4956
Коммит cc9bbbdd80
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
1 изменённых файлов: 73 добавлений и 61 удалений

Просмотреть файл

@ -341,24 +341,36 @@ fn gen_save_sp_with_offset(asm: &mut Assembler, offset: i8) {
}
}
/// jit_save_pc() + gen_save_sp(). Should be used before calling a routine that
/// could:
/// jit_save_pc() + gen_save_sp(). Should be used before calling a routine that could:
/// - Perform GC allocation
/// - Take the VM lock through RB_VM_LOCK_ENTER()
/// - Perform Ruby method call
fn jit_prepare_routine_call(
///
/// If the routine doesn't call arbitrary methods, use jit_prepare_call_with_gc() instead.
fn jit_prepare_non_leaf_call(
jit: &mut JITState,
asm: &mut Assembler
) {
jit.record_boundary_patch_point = true;
jit_save_pc(jit, asm);
gen_save_sp(asm);
// Prepare for GC. This also sets PC, which prepares for showing a backtrace.
jit_prepare_call_with_gc(jit, asm);
// In case the routine calls Ruby methods, it can set local variables
// through Kernel#binding and other means.
// through Kernel#binding, rb_debug_inspector API, and other means.
asm.clear_local_types();
}
/// jit_save_pc() + gen_save_sp(). Should be used before calling a routine that could:
/// - Perform GC allocation
/// - Take the VM lock through RB_VM_LOCK_ENTER()
fn jit_prepare_call_with_gc(
jit: &mut JITState,
asm: &mut Assembler
) {
jit.record_boundary_patch_point = true; // VM lock could trigger invalidation
jit_save_pc(jit, asm); // for allocation tracing
gen_save_sp(asm); // protect objects from GC
}
/// Record the current codeblock write position for rewriting into a jump into
/// the outlined block later. Used to implement global code invalidation.
fn record_global_inval_patch(asm: &mut Assembler, outline_block_target_pos: CodePtr) {
@ -1338,7 +1350,7 @@ fn gen_newarray(
let n = jit.get_arg(0).as_u32();
// Save the PC and SP because we are allocating
jit_prepare_routine_call(jit, asm);
jit_prepare_call_with_gc(jit, asm);
// If n is 0, then elts is never going to be read, so we can just pass null
let values_ptr = if n == 0 {
@ -1376,7 +1388,7 @@ fn gen_duparray(
let ary = jit.get_arg(0);
// Save the PC and SP because we are allocating
jit_prepare_routine_call(jit, asm);
jit_prepare_call_with_gc(jit, asm);
// call rb_ary_resurrect(VALUE ary);
let new_ary = asm.ccall(
@ -1399,7 +1411,7 @@ fn gen_duphash(
let hash = jit.get_arg(0);
// Save the PC and SP because we are allocating
jit_prepare_routine_call(jit, asm);
jit_prepare_call_with_gc(jit, asm);
// call rb_hash_resurrect(VALUE hash);
let hash = asm.ccall(rb_hash_resurrect as *const u8, vec![hash.into()]);
@ -1418,9 +1430,9 @@ fn gen_splatarray(
) -> Option<CodegenStatus> {
let flag = jit.get_arg(0).as_usize();
// Save the PC and SP because the callee may allocate
// Save the PC and SP because the callee may call #to_a
// Note that this modifies REG_SP, which is why we do it first
jit_prepare_routine_call(jit, asm);
jit_prepare_non_leaf_call(jit, asm);
// Get the operands from the stack
let ary_opnd = asm.stack_opnd(0);
@ -1456,8 +1468,8 @@ fn gen_splatkw(
} else {
// Otherwise, call #to_hash operand to get T_HASH.
// Save the PC and SP because the callee may allocate
jit_prepare_routine_call(jit, asm);
// Save the PC and SP because the callee may call #to_hash
jit_prepare_non_leaf_call(jit, asm);
// Get the operands from the stack
let block_opnd = asm.stack_opnd(0);
@ -1483,9 +1495,9 @@ fn gen_concatarray(
asm: &mut Assembler,
_ocb: &mut OutlinedCb,
) -> Option<CodegenStatus> {
// Save the PC and SP because the callee may allocate
// Save the PC and SP because the callee may call #to_a
// Note that this modifies REG_SP, which is why we do it first
jit_prepare_routine_call(jit, asm);
jit_prepare_non_leaf_call(jit, asm);
// Get the operands from the stack
let ary2st_opnd = asm.stack_opnd(0);
@ -1509,8 +1521,8 @@ fn gen_concattoarray(
asm: &mut Assembler,
_ocb: &mut OutlinedCb,
) -> Option<CodegenStatus> {
// Save the PC and SP because the callee may allocate
jit_prepare_routine_call(jit, asm);
// Save the PC and SP because the callee may call #to_a
jit_prepare_non_leaf_call(jit, asm);
// Get the operands from the stack
let ary2_opnd = asm.stack_opnd(0);
@ -1534,7 +1546,7 @@ fn gen_pushtoarray(
let num = jit.get_arg(0).as_u64();
// Save the PC and SP because the callee may allocate
jit_prepare_routine_call(jit, asm);
jit_prepare_call_with_gc(jit, asm);
// Get the operands from the stack
let ary_opnd = asm.stack_opnd(num as i32);
@ -1558,7 +1570,7 @@ fn gen_newrange(
let flag = jit.get_arg(0).as_usize();
// rb_range_new() allocates and can raise
jit_prepare_routine_call(jit, asm);
jit_prepare_non_leaf_call(jit, asm);
// val = rb_range_new(low, high, (int)flag);
let range_opnd = asm.ccall(
@ -2017,7 +2029,7 @@ fn gen_setlocal_generic(
if asm.ctx.get_chain_depth() > 0
{
// Save the PC and SP because it runs GC
jit_prepare_routine_call(jit, asm);
jit_prepare_call_with_gc(jit, asm);
// Pop the value to write from the stack
let value_opnd = asm.stack_opnd(0);
@ -2113,7 +2125,7 @@ fn gen_newhash(
let num: u64 = jit.get_arg(0).as_u64();
// Save the PC and SP because we are allocating
jit_prepare_routine_call(jit, asm);
jit_prepare_call_with_gc(jit, asm);
if num != 0 {
// val = rb_hash_new_with_size(num / 2);
@ -2163,7 +2175,7 @@ fn gen_putstring(
let put_val = jit.get_arg(0);
// Save the PC and SP because the callee will allocate
jit_prepare_routine_call(jit, asm);
jit_prepare_call_with_gc(jit, asm);
let str_opnd = asm.ccall(
rb_ec_str_resurrect as *const u8,
@ -2186,7 +2198,7 @@ fn gen_checkmatch(
// rb_vm_check_match is not leaf unless flag is VM_CHECKMATCH_TYPE_WHEN.
// See also: leafness_of_checkmatch() and check_match()
if flag != VM_CHECKMATCH_TYPE_WHEN {
jit_prepare_routine_call(jit, asm);
jit_prepare_non_leaf_call(jit, asm);
}
let pattern = asm.stack_opnd(0);
@ -2316,7 +2328,7 @@ fn gen_set_ivar(
// Save the PC and SP because the callee may allocate
// Note that this modifies REG_SP, which is why we do it first
jit_prepare_routine_call(jit, asm);
jit_prepare_non_leaf_call(jit, asm);
// Get the operands from the stack
let val_opnd = asm.stack_opnd(0);
@ -2390,7 +2402,7 @@ fn gen_get_ivar(
asm_comment!(asm, "call rb_ivar_get()");
// The function could raise exceptions.
jit_prepare_routine_call(jit, asm);
jit_prepare_non_leaf_call(jit, asm);
let ivar_val = asm.ccall(rb_ivar_get as *const u8, vec![recv, Opnd::UImm(ivar_name)]);
@ -2648,7 +2660,7 @@ fn gen_setinstancevariable(
// The function could raise exceptions.
// Note that this modifies REG_SP, which is why we do it first
jit_prepare_routine_call(jit, asm);
jit_prepare_non_leaf_call(jit, asm);
// Get the operands from the stack
let val_opnd = asm.stack_opnd(0);
@ -2704,7 +2716,7 @@ fn gen_setinstancevariable(
// It allocates so can trigger GC, which takes the VM lock
// so could yield to a different ractor.
jit_prepare_routine_call(jit, asm);
jit_prepare_non_leaf_call(jit, asm);
asm.ccall(rb_ensure_iv_list_size as *const u8,
vec![
recv,
@ -2787,7 +2799,7 @@ fn gen_defined(
_ => {
// Save the PC and SP because the callee may allocate
// Note that this modifies REG_SP, which is why we do it first
jit_prepare_routine_call(jit, asm);
jit_prepare_non_leaf_call(jit, asm);
// Get the operands from the stack
let v_opnd = asm.stack_opnd(0);
@ -2843,7 +2855,7 @@ fn gen_definedivar(
// Save the PC and SP because the callee may allocate
// Note that this modifies REG_SP, which is why we do it first
jit_prepare_routine_call(jit, asm);
jit_prepare_non_leaf_call(jit, asm);
// Call rb_ivar_defined(recv, ivar_name)
let def_result = asm.ccall(rb_ivar_defined as *const u8, vec![recv, ivar_name.into()]);
@ -2958,7 +2970,7 @@ fn gen_concatstrings(
let n = jit.get_arg(0).as_usize();
// Save the PC and SP because we are allocating
jit_prepare_routine_call(jit, asm);
jit_prepare_call_with_gc(jit, asm);
let values_ptr = asm.lea(asm.ctx.sp_opnd(-((SIZEOF_VALUE as isize) * n as isize)));
@ -3362,7 +3374,7 @@ fn gen_opt_aref(
);
// Prepare to call rb_hash_aref(). It might call #hash on the key.
jit_prepare_routine_call(jit, asm);
jit_prepare_non_leaf_call(jit, asm);
// Call rb_hash_aref
let key_opnd = asm.stack_opnd(0);
@ -3432,7 +3444,7 @@ fn gen_opt_aset(
);
// We might allocate or raise
jit_prepare_routine_call(jit, asm);
jit_prepare_non_leaf_call(jit, asm);
// Call rb_ary_store
let recv = asm.stack_opnd(2);
@ -3467,7 +3479,7 @@ fn gen_opt_aset(
);
// We might allocate or raise
jit_prepare_routine_call(jit, asm);
jit_prepare_non_leaf_call(jit, asm);
// Call rb_hash_aset
let recv = asm.stack_opnd(2);
@ -3492,7 +3504,7 @@ fn gen_opt_aref_with(
asm: &mut Assembler,
_ocb: &mut OutlinedCb,
) -> Option<CodegenStatus>{
jit_prepare_routine_call(jit, asm);
jit_prepare_non_leaf_call(jit, asm);
let key_opnd = Opnd::Value(jit.get_arg(0));
let recv_opnd = asm.stack_opnd(0);
@ -3819,7 +3831,7 @@ fn gen_opt_newarray_max(
let num = jit.get_arg(0).as_u32();
// Save the PC and SP because we may allocate
jit_prepare_routine_call(jit, asm);
jit_prepare_non_leaf_call(jit, asm);
extern "C" {
fn rb_vm_opt_newarray_max(ec: EcPtr, num: u32, elts: *const VALUE) -> VALUE;
@ -3872,7 +3884,7 @@ fn gen_opt_newarray_hash(
let num = jit.get_arg(0).as_u32();
// Save the PC and SP because we may allocate
jit_prepare_routine_call(jit, asm);
jit_prepare_non_leaf_call(jit, asm);
extern "C" {
fn rb_vm_opt_newarray_hash(ec: EcPtr, num: u32, elts: *const VALUE) -> VALUE;
@ -3907,7 +3919,7 @@ fn gen_opt_newarray_min(
let num = jit.get_arg(0).as_u32();
// Save the PC and SP because we may allocate
jit_prepare_routine_call(jit, asm);
jit_prepare_non_leaf_call(jit, asm);
extern "C" {
fn rb_vm_opt_newarray_min(ec: EcPtr, num: u32, elts: *const VALUE) -> VALUE;
@ -4788,7 +4800,7 @@ fn jit_rb_int_div(
guard_two_fixnums(jit, asm, ocb);
// rb_fix_div_fix may GC-allocate for Bignum
jit_prepare_routine_call(jit, asm);
jit_prepare_non_leaf_call(jit, asm);
asm_comment!(asm, "Integer#/");
let obj = asm.stack_opnd(0);
@ -4997,7 +5009,7 @@ fn jit_rb_str_uplus(
}
// We allocate when we dup the string
jit_prepare_routine_call(jit, asm);
jit_prepare_non_leaf_call(jit, asm);
asm.spill_temps(); // For ccall. Unconditionally spill them for RegTemps consistency.
asm_comment!(asm, "Unary plus on string");
@ -5099,7 +5111,7 @@ fn jit_rb_str_getbyte(
fn rb_str_getbyte(str: VALUE, index: VALUE) -> VALUE;
}
// Raises when non-integers are passed in
jit_prepare_routine_call(jit, asm);
jit_prepare_non_leaf_call(jit, asm);
let index = asm.stack_opnd(0);
let recv = asm.stack_opnd(1);
@ -5192,7 +5204,7 @@ fn jit_rb_str_concat(
// Guard buffers from GC since rb_str_buf_append may allocate. During the VM lock on GC,
// other Ractors may trigger global invalidation, so we need ctx.clear_local_types().
// PC is used on errors like Encoding::CompatibilityError raised by rb_str_buf_append.
jit_prepare_routine_call(jit, asm);
jit_prepare_non_leaf_call(jit, asm);
asm.spill_temps(); // For ccall. Unconditionally spill them for RegTemps consistency.
let concat_arg = asm.stack_pop(1);
@ -5299,7 +5311,7 @@ fn jit_rb_ary_push(
asm_comment!(asm, "Array#<<");
// rb_ary_push allocates memory for buffer extension
jit_prepare_routine_call(jit, asm);
jit_prepare_non_leaf_call(jit, asm);
let item_opnd = asm.stack_opnd(0);
let ary_opnd = asm.stack_opnd(1);
@ -6456,7 +6468,7 @@ fn gen_send_iseq(
// The callee may allocate, e.g. Integer#abs on a Bignum.
// Save SP for GC, save PC for allocation tracing, and prepare
// for global invalidation after GC's VM lock contention.
jit_prepare_routine_call(jit, asm);
jit_prepare_call_with_gc(jit, asm);
// Call the builtin func (ec, recv, arg1, arg2, ...)
let mut args = vec![EC];
@ -7348,7 +7360,7 @@ fn gen_send_dynamic<F: Fn(&mut Assembler) -> Opnd>(
}
// Save PC and SP to prepare for dynamic dispatch
jit_prepare_routine_call(jit, asm);
jit_prepare_non_leaf_call(jit, asm);
// Dispatch a method
let ret = vm_sendish(asm);
@ -7765,7 +7777,7 @@ fn gen_send_general(
let sp = asm.lea(asm.ctx.sp_opnd(0));
// Save the PC and SP because the callee can make Ruby calls
jit_prepare_routine_call(jit, asm);
jit_prepare_non_leaf_call(jit, asm);
let kw_splat = flags & VM_CALL_KW_SPLAT;
let stack_argument_pointer = asm.lea(Opnd::mem(64, sp, -(argc) * SIZEOF_VALUE_I32));
@ -8068,7 +8080,7 @@ fn gen_invokeblock_specialized(
);
// The cfunc may not be leaf
jit_prepare_routine_call(jit, asm);
jit_prepare_non_leaf_call(jit, asm);
extern "C" {
fn rb_vm_yield_with_cfunc(ec: EcPtr, captured: *const rb_captured_block, argc: c_int, argv: *const VALUE) -> VALUE;
@ -8304,7 +8316,7 @@ fn gen_getglobal(
let gid = jit.get_arg(0).as_usize();
// Save the PC and SP because we might make a Ruby call for warning
jit_prepare_routine_call(jit, asm);
jit_prepare_non_leaf_call(jit, asm);
let val_opnd = asm.ccall(
rb_gvar_get as *const u8,
@ -8326,7 +8338,7 @@ fn gen_setglobal(
// Save the PC and SP because we might make a Ruby call for
// Kernel#set_trace_var
jit_prepare_routine_call(jit, asm);
jit_prepare_non_leaf_call(jit, asm);
let val = asm.stack_opnd(0);
asm.ccall(
@ -8347,7 +8359,7 @@ fn gen_anytostring(
_ocb: &mut OutlinedCb,
) -> Option<CodegenStatus> {
// Save the PC and SP since we might call #to_s
jit_prepare_routine_call(jit, asm);
jit_prepare_non_leaf_call(jit, asm);
let str = asm.stack_opnd(0);
let val = asm.stack_opnd(1);
@ -8402,7 +8414,7 @@ fn gen_intern(
_ocb: &mut OutlinedCb,
) -> Option<CodegenStatus> {
// Save the PC and SP because we might allocate
jit_prepare_routine_call(jit, asm);
jit_prepare_non_leaf_call(jit, asm);
let str = asm.stack_opnd(0);
let sym = asm.ccall(rb_str_intern as *const u8, vec![str]);
@ -8425,7 +8437,7 @@ fn gen_toregexp(
// Save the PC and SP because this allocates an object and could
// raise an exception.
jit_prepare_routine_call(jit, asm);
jit_prepare_non_leaf_call(jit, asm);
let values_ptr = asm.lea(asm.ctx.sp_opnd(-((SIZEOF_VALUE as isize) * (cnt as isize))));
@ -8484,7 +8496,7 @@ fn gen_getspecial(
// Fetch a "special" backref based on a char encoded by shifting by 1
// Can raise if matchdata uninitialized
jit_prepare_routine_call(jit, asm);
jit_prepare_non_leaf_call(jit, asm);
// call rb_backref_get()
asm_comment!(asm, "rb_backref_get");
@ -8519,7 +8531,7 @@ fn gen_getspecial(
// Fetch the N-th match from the last backref based on type shifted by 1
// Can raise if matchdata uninitialized
jit_prepare_routine_call(jit, asm);
jit_prepare_non_leaf_call(jit, asm);
// call rb_backref_get()
asm_comment!(asm, "rb_backref_get");
@ -8548,7 +8560,7 @@ fn gen_getclassvariable(
_ocb: &mut OutlinedCb,
) -> Option<CodegenStatus> {
// rb_vm_getclassvariable can raise exceptions.
jit_prepare_routine_call(jit, asm);
jit_prepare_non_leaf_call(jit, asm);
let val_opnd = asm.ccall(
rb_vm_getclassvariable as *const u8,
@ -8572,7 +8584,7 @@ fn gen_setclassvariable(
_ocb: &mut OutlinedCb,
) -> Option<CodegenStatus> {
// rb_vm_setclassvariable can raise exceptions.
jit_prepare_routine_call(jit, asm);
jit_prepare_non_leaf_call(jit, asm);
let val = asm.stack_opnd(0);
asm.ccall(
@ -8599,7 +8611,7 @@ fn gen_getconstant(
let id = jit.get_arg(0).as_usize();
// vm_get_ev_const can raise exceptions.
jit_prepare_routine_call(jit, asm);
jit_prepare_non_leaf_call(jit, asm);
let allow_nil_opnd = asm.stack_opnd(0);
let klass_opnd = asm.stack_opnd(1);
@ -8643,7 +8655,7 @@ fn gen_opt_getconstant_path(
let ice = unsafe { (*ic).entry };
if ice.is_null() {
// Prepare for const_missing
jit_prepare_routine_call(jit, asm);
jit_prepare_non_leaf_call(jit, asm);
// If this does not trigger const_missing, vm_ic_update will invalidate this block.
extern "C" {
@ -8852,7 +8864,7 @@ fn gen_getblockparam(
let level = jit.get_arg(1).as_u32();
// Save the PC and SP because we might allocate
jit_prepare_routine_call(jit, asm);
jit_prepare_non_leaf_call(jit, asm);
asm.spill_temps(); // For ccall. Unconditionally spill them for RegTemps consistency.
// A mirror of the interpreter code. Checking for the case
@ -8940,7 +8952,7 @@ fn gen_invokebuiltin(
}
// If the calls don't allocate, do they need up to date PC, SP?
jit_prepare_routine_call(jit, asm);
jit_prepare_non_leaf_call(jit, asm);
// Call the builtin func (ec, recv, arg1, arg2, ...)
let mut args = vec![EC, Opnd::mem(64, CFP, RUBY_OFFSET_CFP_SELF)];
@ -8980,7 +8992,7 @@ fn gen_opt_invokebuiltin_delegate(
}
// If the calls don't allocate, do they need up to date PC, SP?
jit_prepare_routine_call(jit, asm);
jit_prepare_non_leaf_call(jit, asm);
// Call the builtin func (ec, recv, arg1, arg2, ...)
let mut args = vec![EC, Opnd::mem(64, CFP, RUBY_OFFSET_CFP_SELF)];