* Port gen_send_cfunc to the new backend

* Remove an obsoleted test

* Add more cfunc tests

* Use csel_e instead and more into()

Co-authored-by: Maxime Chevalier-Boisvert <maximechevalierb@gmail.com>

* Add a missing lea for build_kwargs

* Split cfunc test cases

Co-authored-by: Maxime Chevalier-Boisvert <maximechevalierb@gmail.com>
This commit is contained in:
Takashi Kokubun 2022-08-04 11:47:53 -07:00
Родитель c91a44cba4
Коммит 4539c21367
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 6FFC433B12EE23DD
2 изменённых файлов: 103 добавлений и 91 удалений

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

@ -3111,3 +3111,35 @@ assert_equal '9001', %q{
end
foo()
}
# opt_send_without_block (VM_METHOD_TYPE_CFUNC)
assert_equal 'nil', %q{
def foo
nil.inspect # argc: 0
end
foo
}
assert_equal '4', %q{
def foo
2.pow(2) # argc: 1
end
foo
}
assert_equal 'aba', %q{
def foo
"abc".tr("c", "a") # argc: 2
end
foo
}
assert_equal 'true', %q{
def foo
respond_to?(:inspect) # argc: -1
end
foo
}
assert_equal '["a", "b"]', %q{
def foo
"a\nb".lines(chomp: true) # kwargs
end
foo
}

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

@ -1881,10 +1881,10 @@ pub const GET_IVAR_MAX_DEPTH: i32 = 10;
/*
// hashes and arrays
pub const OPT_AREF_MAX_CHAIN_DEPTH: i32 = 2;
*/
// up to 5 different classes
pub const SEND_MAX_DEPTH: i32 = 5;
*/
// Codegen for setting an instance variable.
// Preconditions:
@ -2221,7 +2221,7 @@ fn gen_checktype(
// Only three types are emitted by compile.c at the moment
if let RUBY_T_STRING | RUBY_T_ARRAY | RUBY_T_HASH = type_val {
let val_type = ctx.get_opnd_type(StackOpnd(0));
let val = ctx.stack_pop(1);
let val = asm.load(ctx.stack_pop(1));
// Check if we know from type information
match val_type.known_value_type() {
@ -2253,7 +2253,7 @@ fn gen_checktype(
Opnd::mem(64, val, RUBY_OFFSET_RBASIC_FLAGS),
Opnd::UImm(RUBY_T_MASK.into()));
asm.cmp(object_type, Opnd::UImm(type_val.into()));
let ret_opnd = asm.csel_e(Opnd::UImm(Qfalse.into()), Opnd::UImm(Qtrue.into()));
let ret_opnd = asm.csel_e(Qtrue.into(), Qfalse.into());
asm.write_label(ret);
let stack_ret = ctx.stack_push(Type::UnknownImm);
@ -3851,6 +3851,7 @@ fn jit_thread_s_current(
mov(cb, stack_ret, REG0);
true
}
*/
// Check if we know how to codegen for a particular cfunc method
fn lookup_cfunc_codegen(def: *const rb_method_definition_t) -> Option<MethodGenFn> {
@ -3858,7 +3859,6 @@ fn lookup_cfunc_codegen(def: *const rb_method_definition_t) -> Option<MethodGenF
CodegenGlobals::look_up_codegen_method(method_serial)
}
*/
// Is anyone listening for :c_call and :c_return event currently?
fn c_method_tracing_currently_enabled(jit: &JITState) -> bool {
@ -3868,7 +3868,6 @@ fn c_method_tracing_currently_enabled(jit: &JITState) -> bool {
}
}
/*
// Similar to args_kw_argv_to_hash. It is called at runtime from within the
// generated assembly to build a Ruby hash of the passed keyword arguments. The
// keys are the Symbol objects associated with the keywords and the values are
@ -3889,7 +3888,7 @@ unsafe extern "C" fn build_kwhash(ci: *const rb_callinfo, sp: *const VALUE) -> V
fn gen_send_cfunc(
jit: &mut JITState,
ctx: &mut Context,
cb: &mut CodeBlock,
asm: &mut Assembler,
ocb: &mut OutlinedCb,
ci: *const rb_callinfo,
cme: *const rb_callable_method_entry_t,
@ -3902,7 +3901,7 @@ fn gen_send_cfunc(
// If the function expects a Ruby array of arguments
if cfunc_argc < 0 && cfunc_argc != -1 {
gen_counter_incr!(cb, send_cfunc_ruby_array_varg);
gen_counter_incr!(asm, send_cfunc_ruby_array_varg);
return CantCompile;
}
@ -3923,19 +3922,19 @@ fn gen_send_cfunc(
// If the argument count doesn't match
if cfunc_argc >= 0 && cfunc_argc != passed_argc {
gen_counter_incr!(cb, send_cfunc_argc_mismatch);
gen_counter_incr!(asm, send_cfunc_argc_mismatch);
return CantCompile;
}
// Don't JIT functions that need C stack arguments for now
if cfunc_argc >= 0 && passed_argc + 1 > (C_ARG_REGS.len() as i32) {
gen_counter_incr!(cb, send_cfunc_toomany_args);
if cfunc_argc >= 0 && passed_argc + 1 > (C_ARG_OPNDS.len() as i32) {
gen_counter_incr!(asm, send_cfunc_toomany_args);
return CantCompile;
}
if c_method_tracing_currently_enabled(jit) {
// Don't JIT if tracing c_call or c_return
gen_counter_incr!(cb, send_cfunc_tracing);
gen_counter_incr!(asm, send_cfunc_tracing);
return CantCompile;
}
@ -3943,6 +3942,7 @@ fn gen_send_cfunc(
if kw_arg.is_null() {
let codegen_p = lookup_cfunc_codegen(unsafe { (*cme).def });
if let Some(known_cfunc_codegen) = codegen_p {
return CantCompile; /*
let start_pos = cb.get_write_ptr().raw_ptr() as usize;
if known_cfunc_codegen(jit, ctx, cb, ocb, ci, cme, block, argc, recv_known_klass) {
let written_bytes = cb.get_write_ptr().raw_ptr() as usize - start_pos;
@ -3955,6 +3955,7 @@ fn gen_send_cfunc(
jump_to_next_insn(jit, ctx, cb, ocb);
return EndBlock;
}
*/
}
}
@ -3962,57 +3963,49 @@ fn gen_send_cfunc(
let side_exit = get_side_exit(jit, ocb, ctx);
// Check for interrupts
gen_check_ints(cb, side_exit);
gen_check_ints(asm, side_exit);
// Stack overflow check
// #define CHECK_VM_STACK_OVERFLOW0(cfp, sp, margin)
// REG_CFP <= REG_SP + 4 * SIZEOF_VALUE + sizeof(rb_control_frame_t)
add_comment(cb, "stack overflow check");
lea(
cb,
REG0,
ctx.sp_opnd((SIZEOF_VALUE * 4 + 2 * RUBY_SIZEOF_CONTROL_FRAME) as isize),
);
cmp(cb, REG_CFP, REG0);
jle_ptr(cb, counted_exit!(ocb, side_exit, send_se_cf_overflow));
asm.comment("stack overflow check");
let stack_limit = asm.lea(ctx.sp_opnd((SIZEOF_VALUE * 4 + 2 * RUBY_SIZEOF_CONTROL_FRAME) as isize));
asm.cmp(CFP, stack_limit);
asm.jbe(counted_exit!(ocb, side_exit, send_se_cf_overflow).into());
// Points to the receiver operand on the stack
let recv = ctx.stack_opnd(argc);
// Store incremented PC into current control frame in case callee raises.
jit_save_pc(jit, cb, REG0);
jit_save_pc(jit, asm);
if let Some(block_iseq) = block {
// Change cfp->block_code in the current frame. See vm_caller_setup_arg_block().
// VM_CFP_TO_CAPTURED_BLOCK does &cfp->self, rb_captured_block->code.iseq aliases
// with cfp->block_code.
jit_mov_gc_ptr(jit, cb, REG0, VALUE(block_iseq as usize));
let block_code_opnd = mem_opnd(64, REG_CFP, RUBY_OFFSET_CFP_BLOCK_CODE);
mov(cb, block_code_opnd, REG0);
asm.mov(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_BLOCK_CODE), Opnd::UImm(block_iseq as u64));
}
// Increment the stack pointer by 3 (in the callee)
// sp += 3
lea(cb, REG0, ctx.sp_opnd((SIZEOF_VALUE as isize) * 3));
let sp = asm.lea(ctx.sp_opnd((SIZEOF_VALUE as isize) * 3));
// Write method entry at sp[-3]
// sp[-3] = me;
// Put compile time cme into REG1. It's assumed to be valid because we are notified when
// any cme we depend on become outdated. See yjit_method_lookup_change().
jit_mov_gc_ptr(jit, cb, REG1, VALUE(cme as usize));
mov(cb, mem_opnd(64, REG0, 8 * -3), REG1);
asm.mov(Opnd::mem(64, sp, 8 * -3), Opnd::UImm(cme as u64));
// Write block handler at sp[-2]
// sp[-2] = block_handler;
if let Some(_block_iseq) = block {
// reg1 = VM_BH_FROM_ISEQ_BLOCK(VM_CFP_TO_CAPTURED_BLOCK(reg_cfp));
let cfp_self = mem_opnd(64, REG_CFP, RUBY_OFFSET_CFP_SELF);
lea(cb, REG1, cfp_self);
or(cb, REG1, imm_opnd(1));
mov(cb, mem_opnd(64, REG0, 8 * -2), REG1);
let cfp_self = asm.lea(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_SELF));
let block_handler = asm.or(cfp_self, Opnd::Imm(1));
asm.mov(Opnd::mem(64, sp, 8 * -2), block_handler);
} else {
let dst_opnd = mem_opnd(64, REG0, 8 * -2);
mov(cb, dst_opnd, uimm_opnd(VM_BLOCK_HANDLER_NONE.into()));
let dst_opnd = Opnd::mem(64, sp, 8 * -2);
asm.mov(dst_opnd, Opnd::UImm(VM_BLOCK_HANDLER_NONE.into()));
}
// Write env flags at sp[-1]
@ -4021,11 +4014,12 @@ fn gen_send_cfunc(
if !kw_arg.is_null() {
frame_type |= VM_FRAME_FLAG_CFRAME_KW
}
mov(cb, mem_opnd(64, REG0, 8 * -1), uimm_opnd(frame_type.into()));
asm.mov(Opnd::mem(64, sp, 8 * -1), Opnd::UImm(frame_type.into()));
// Allocate a new CFP (ec->cfp--)
let ec_cfp_opnd = mem_opnd(64, REG_EC, RUBY_OFFSET_EC_CFP);
sub(cb, ec_cfp_opnd, uimm_opnd(RUBY_SIZEOF_CONTROL_FRAME as u64));
let ec_cfp_opnd = Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP);
let new_cfp = asm.sub(ec_cfp_opnd, Opnd::UImm(RUBY_SIZEOF_CONTROL_FRAME as u64));
asm.store(ec_cfp_opnd, new_cfp);
// Setup the new frame
// *cfp = (const struct rb_control_frame_struct) {
@ -4039,22 +4033,15 @@ fn gen_send_cfunc(
// };
// Can we re-use ec_cfp_opnd from above?
let ec_cfp_opnd = mem_opnd(64, REG_EC, RUBY_OFFSET_EC_CFP);
mov(cb, REG1, ec_cfp_opnd);
mov(cb, mem_opnd(64, REG1, RUBY_OFFSET_CFP_PC), imm_opnd(0));
mov(cb, mem_opnd(64, REG1, RUBY_OFFSET_CFP_SP), REG0);
mov(cb, mem_opnd(64, REG1, RUBY_OFFSET_CFP_ISEQ), imm_opnd(0));
mov(
cb,
mem_opnd(64, REG1, RUBY_OFFSET_CFP_BLOCK_CODE),
imm_opnd(0),
);
mov(cb, mem_opnd(64, REG1, RUBY_OFFSET_CFP_BP), REG0);
sub(cb, REG0, uimm_opnd(SIZEOF_VALUE as u64));
mov(cb, mem_opnd(64, REG1, RUBY_OFFSET_CFP_EP), REG0);
mov(cb, REG0, recv);
mov(cb, mem_opnd(64, REG1, RUBY_OFFSET_CFP_SELF), REG0);
let ec_cfp_opnd = asm.load(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP));
asm.mov(Opnd::mem(64, ec_cfp_opnd, RUBY_OFFSET_CFP_PC), Opnd::Imm(0));
asm.mov(Opnd::mem(64, ec_cfp_opnd, RUBY_OFFSET_CFP_SP), sp);
asm.mov(Opnd::mem(64, ec_cfp_opnd, RUBY_OFFSET_CFP_ISEQ), Opnd::Imm(0));
asm.mov(Opnd::mem(64, ec_cfp_opnd, RUBY_OFFSET_CFP_BLOCK_CODE), Opnd::Imm(0));
asm.mov(Opnd::mem(64, ec_cfp_opnd, RUBY_OFFSET_CFP_BP), sp);
let ep = asm.sub(sp, Opnd::UImm(SIZEOF_VALUE as u64));
asm.mov(Opnd::mem(64, ec_cfp_opnd, RUBY_OFFSET_CFP_EP), ep);
asm.mov(Opnd::mem(64, ec_cfp_opnd, RUBY_OFFSET_CFP_SELF), recv);
/*
// Verify that we are calling the right function
@ -4070,71 +4057,66 @@ fn gen_send_cfunc(
if !kw_arg.is_null() {
// Build a hash from all kwargs passed
jit_mov_gc_ptr(jit, cb, C_ARG_REGS[0], VALUE(ci as usize));
lea(cb, C_ARG_REGS[1], ctx.sp_opnd(0));
call_ptr(cb, REG0, build_kwhash as *const u8);
asm.comment("build_kwhash");
let sp = asm.lea(ctx.sp_opnd(0));
let kwargs = asm.ccall(build_kwhash as *const u8, vec![Opnd::UImm(ci as u64), sp]);
// Replace the stack location at the start of kwargs with the new hash
let stack_opnd = ctx.stack_opnd(argc - passed_argc);
mov(cb, stack_opnd, RAX);
asm.mov(stack_opnd, kwargs);
}
// Copy SP into RAX because REG_SP will get overwritten
lea(cb, RAX, ctx.sp_opnd(0));
// Copy SP because REG_SP will get overwritten
let sp = asm.lea(ctx.sp_opnd(0));
// Pop the C function arguments from the stack (in the caller)
ctx.stack_pop((argc + 1).try_into().unwrap());
// Write interpreter SP into CFP.
// Needed in case the callee yields to the block.
gen_save_sp(cb, ctx);
gen_save_sp(jit, asm, ctx);
// Non-variadic method
if cfunc_argc >= 0 {
let args = if cfunc_argc >= 0 {
// Copy the arguments from the stack to the C argument registers
// self is the 0th argument and is at index argc from the stack top
for i in 0..=passed_argc as usize {
let stack_opnd = mem_opnd(64, RAX, -(argc + 1 - (i as i32)) * SIZEOF_VALUE_I32);
let c_arg_reg = C_ARG_REGS[i];
mov(cb, c_arg_reg, stack_opnd);
}
(0..=passed_argc).map(|i|
Opnd::mem(64, sp, -(argc + 1 - (i as i32)) * SIZEOF_VALUE_I32)
).collect()
}
// Variadic method
if cfunc_argc == -1 {
else if cfunc_argc == -1 {
// The method gets a pointer to the first argument
// rb_f_puts(int argc, VALUE *argv, VALUE recv)
mov(cb, C_ARG_REGS[0], imm_opnd(passed_argc.into()));
lea(
cb,
C_ARG_REGS[1],
mem_opnd(64, RAX, -(argc) * SIZEOF_VALUE_I32),
);
mov(
cb,
C_ARG_REGS[2],
mem_opnd(64, RAX, -(argc + 1) * SIZEOF_VALUE_I32),
);
vec![
Opnd::Imm(passed_argc.into()),
asm.lea(Opnd::mem(64, sp, -(argc) * SIZEOF_VALUE_I32)),
Opnd::mem(64, sp, -(argc + 1) * SIZEOF_VALUE_I32),
]
}
else {
panic!("unexpected cfunc_args: {}", cfunc_argc)
};
// Call the C function
// VALUE ret = (cfunc->func)(recv, argv[0], argv[1]);
// cfunc comes from compile-time cme->def, which we assume to be stable.
// Invalidation logic is in yjit_method_lookup_change()
add_comment(cb, "call C function");
call_ptr(cb, REG0, unsafe { get_mct_func(cfunc) });
asm.comment("call C function");
let ret = asm.ccall(unsafe { get_mct_func(cfunc) }, args);
// Record code position for TracePoint patching. See full_cfunc_return().
record_global_inval_patch(cb, CodegenGlobals::get_outline_full_cfunc_return_pos());
record_global_inval_patch(asm, CodegenGlobals::get_outline_full_cfunc_return_pos());
// Push the return value on the Ruby stack
let stack_ret = ctx.stack_push(Type::Unknown);
mov(cb, stack_ret, RAX);
asm.mov(stack_ret, ret);
// Pop the stack frame (ec->cfp++)
// Can we reuse ec_cfp_opnd from above?
let ec_cfp_opnd = mem_opnd(64, REG_EC, RUBY_OFFSET_EC_CFP);
add(cb, ec_cfp_opnd, uimm_opnd(RUBY_SIZEOF_CONTROL_FRAME as u64));
let ec_cfp_opnd = Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP);
let new_cfp = asm.add(ec_cfp_opnd, Opnd::UImm(RUBY_SIZEOF_CONTROL_FRAME as u64));
asm.store(ec_cfp_opnd, new_cfp);
// cfunc calls may corrupt types
ctx.clear_local_types();
@ -4144,10 +4126,11 @@ fn gen_send_cfunc(
// Jump (fall through) to the call continuation block
// We do this to end the current block after the call
jump_to_next_insn(jit, ctx, cb, ocb);
jump_to_next_insn(jit, ctx, asm, ocb);
EndBlock
}
/*
fn gen_return_branch(
cb: &mut CodeBlock,
target0: CodePtr,
@ -4852,12 +4835,10 @@ fn gen_send_general(
// Points to the receiver operand on the stack
let recv = ctx.stack_opnd(argc);
let recv_opnd = StackOpnd(argc.try_into().unwrap());
// TODO: Resurrect this once jit_guard_known_klass is implemented for getivar
/*
jit_guard_known_klass(
jit,
ctx,
cb,
asm,
ocb,
comptime_recv_klass,
recv,
@ -4865,7 +4846,7 @@ fn gen_send_general(
comptime_recv,
SEND_MAX_DEPTH,
side_exit,
); */
);
// Do method lookup
let mut cme = unsafe { rb_callable_method_entry(comptime_recv_klass, mid) };
@ -4911,7 +4892,6 @@ fn gen_send_general(
return CantCompile; // return gen_send_iseq(jit, ctx, cb, ocb, ci, cme, block, argc);
}
VM_METHOD_TYPE_CFUNC => {
return CantCompile; /*
return gen_send_cfunc(
jit,
ctx,
@ -4922,7 +4902,7 @@ fn gen_send_general(
block,
argc,
&comptime_recv_klass,
); */
);
}
VM_METHOD_TYPE_IVAR => {
if argc != 0 {