Skip to content

Commit bf75877

Browse files
committed
RJIT: Simplify invokesuper implementation
1 parent 5cc644b commit bf75877

File tree

3 files changed

+106
-93
lines changed

3 files changed

+106
-93
lines changed

lib/ruby_vm/rjit/insn_compiler.rb

Lines changed: 102 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1479,30 +1479,120 @@ def opt_newarray_min(jit, ctx, asm)
14791479
# @param ctx [RubyVM::RJIT::Context]
14801480
# @param asm [RubyVM::RJIT::Assembler]
14811481
def invokesuper(jit, ctx, asm)
1482-
# Specialize on a compile-time receiver, and split a block for chain guards
1482+
cd = C.rb_call_data.new(jit.operand(0))
1483+
block = jit.operand(1)
1484+
1485+
# Defer compilation so we can specialize on class of receiver
14831486
unless jit.at_current_insn?
14841487
defer_compilation(jit, ctx, asm)
14851488
return EndBlock
14861489
end
14871490

1488-
cd = C.rb_call_data.new(jit.operand(0))
1489-
blockiseq = jit.operand(1)
1491+
me = C.rb_vm_frame_method_entry(jit.cfp)
1492+
if me.nil?
1493+
return CantCompile
1494+
end
14901495

1491-
block_handler = jit_caller_setup_arg_block(jit, ctx, asm, cd.ci, blockiseq, true)
1492-
if block_handler == CantCompile
1496+
# FIXME: We should track and invalidate this block when this cme is invalidated
1497+
current_defined_class = me.defined_class
1498+
mid = me.def.original_id
1499+
1500+
if me.to_i != C.rb_callable_method_entry(current_defined_class, me.called_id).to_i
1501+
# Though we likely could generate this call, as we are only concerned
1502+
# with the method entry remaining valid, assume_method_lookup_stable
1503+
# below requires that the method lookup matches as well
14931504
return CantCompile
14941505
end
14951506

1496-
# calling->ci
1497-
mid = C.vm_ci_mid(cd.ci)
1498-
calling = build_calling(ci: cd.ci, block_handler:)
1507+
# vm_search_normal_superclass
1508+
rbasic_klass = C.to_ruby(C.RBasic.new(C.to_value(current_defined_class)).klass)
1509+
if C::BUILTIN_TYPE(current_defined_class) == C::RUBY_T_ICLASS && C::BUILTIN_TYPE(rbasic_klass) == C::RUBY_T_MODULE && \
1510+
C::FL_TEST_RAW(rbasic_klass, C::RMODULE_IS_REFINEMENT) != 0
1511+
return CantCompile
1512+
end
1513+
comptime_superclass = C.rb_class_get_superclass(C.RCLASS_ORIGIN(current_defined_class))
14991514

1500-
# vm_sendish
1501-
cme = jit_search_super_method(jit, ctx, asm, mid, calling)
1502-
if cme == CantCompile
1515+
ci = cd.ci
1516+
argc = C.vm_ci_argc(ci)
1517+
1518+
ci_flags = C.vm_ci_flag(ci)
1519+
1520+
# Don't JIT calls that aren't simple
1521+
# Note, not using VM_CALL_ARGS_SIMPLE because sometimes we pass a block.
1522+
1523+
if ci_flags & C::VM_CALL_KWARG != 0
1524+
asm.incr_counter(:send_keywords)
15031525
return CantCompile
15041526
end
1505-
jit_call_general(jit, ctx, asm, mid, calling, cme, nil)
1527+
if ci_flags & C::VM_CALL_KW_SPLAT != 0
1528+
asm.incr_counter(:send_kw_splat)
1529+
return CantCompile
1530+
end
1531+
if ci_flags & C::VM_CALL_ARGS_BLOCKARG != 0
1532+
asm.incr_counter(:send_block_arg)
1533+
return CantCompile
1534+
end
1535+
1536+
# Ensure we haven't rebound this method onto an incompatible class.
1537+
# In the interpreter we try to avoid making this check by performing some
1538+
# cheaper calculations first, but since we specialize on the method entry
1539+
# and so only have to do this once at compile time this is fine to always
1540+
# check and side exit.
1541+
comptime_recv = jit.peek_at_stack(argc)
1542+
unless C.obj_is_kind_of(comptime_recv, current_defined_class)
1543+
return CantCompile
1544+
end
1545+
1546+
# Do method lookup
1547+
cme = C.rb_callable_method_entry(comptime_superclass, mid)
1548+
1549+
if cme.nil?
1550+
return CantCompile
1551+
end
1552+
1553+
# Check that we'll be able to write this method dispatch before generating checks
1554+
cme_def_type = cme.def.type
1555+
if cme_def_type != C::VM_METHOD_TYPE_ISEQ && cme_def_type != C::VM_METHOD_TYPE_CFUNC
1556+
# others unimplemented
1557+
return CantCompile
1558+
end
1559+
1560+
asm.comment('guard known me')
1561+
lep_opnd = :rax
1562+
jit_get_lep(jit, asm, reg: lep_opnd)
1563+
ep_me_opnd = [lep_opnd, C.VALUE.size * C::VM_ENV_DATA_INDEX_ME_CREF]
1564+
1565+
asm.mov(:rcx, me.to_i)
1566+
asm.cmp(ep_me_opnd, :rcx)
1567+
asm.jne(counted_exit(side_exit(jit, ctx), :invokesuper_me_changed))
1568+
1569+
if block == C::VM_BLOCK_HANDLER_NONE
1570+
# Guard no block passed
1571+
# rb_vm_frame_block_handler(GET_EC()->cfp) == VM_BLOCK_HANDLER_NONE
1572+
# note, we assume VM_ASSERT(VM_ENV_LOCAL_P(ep))
1573+
#
1574+
# TODO: this could properly forward the current block handler, but
1575+
# would require changes to gen_send_*
1576+
asm.comment('guard no block given')
1577+
ep_specval_opnd = [lep_opnd, C.VALUE.size * C::VM_ENV_DATA_INDEX_SPECVAL]
1578+
asm.cmp(ep_specval_opnd, C::VM_BLOCK_HANDLER_NONE)
1579+
asm.jne(counted_exit(side_exit(jit, ctx), :invokesuper_block))
1580+
end
1581+
1582+
# We need to assume that both our current method entry and the super
1583+
# method entry we invoke remain stable
1584+
Invariants.assume_method_lookup_stable(jit, me)
1585+
Invariants.assume_method_lookup_stable(jit, cme)
1586+
1587+
calling = build_calling(ci:, block_handler: block)
1588+
case cme_def_type
1589+
in C::VM_METHOD_TYPE_ISEQ
1590+
iseq = def_iseq_ptr(cme.def)
1591+
frame_type = C::VM_FRAME_MAGIC_METHOD | C::VM_ENV_FLAG_LOCAL
1592+
jit_call_iseq(jit, ctx, asm, cme, calling, iseq, frame_type:)
1593+
in C::VM_METHOD_TYPE_CFUNC
1594+
jit_call_cfunc(jit, ctx, asm, cme, calling, nil)
1595+
end
15061596
end
15071597

15081598
# @param jit [RubyVM::RJIT::JITState]
@@ -3906,87 +3996,6 @@ def jit_search_method(jit, ctx, asm, mid, calling)
39063996
return cme, comptime_recv_klass
39073997
end
39083998

3909-
def jit_search_super_method(jit, ctx, asm, mid, calling)
3910-
assert_equal(true, jit.at_current_insn?)
3911-
3912-
me = C.rb_vm_frame_method_entry(jit.cfp)
3913-
if me.nil?
3914-
return CantCompile
3915-
end
3916-
3917-
# FIXME: We should track and invalidate this block when this cme is invalidated
3918-
current_defined_class = me.defined_class
3919-
mid = me.def.original_id
3920-
3921-
if me.to_i != C.rb_callable_method_entry(current_defined_class, me.called_id).to_i
3922-
# Though we likely could generate this call, as we are only concerned
3923-
# with the method entry remaining valid, assume_method_lookup_stable
3924-
# below requires that the method lookup matches as well
3925-
return CantCompile
3926-
end
3927-
3928-
# vm_search_normal_superclass
3929-
rbasic_klass = C.to_ruby(C.RBasic.new(C.to_value(current_defined_class)).klass)
3930-
if C::BUILTIN_TYPE(current_defined_class) == C::RUBY_T_ICLASS && C::BUILTIN_TYPE(rbasic_klass) == C::RUBY_T_MODULE && \
3931-
C::FL_TEST_RAW(rbasic_klass, C::RMODULE_IS_REFINEMENT) != 0
3932-
return CantCompile
3933-
end
3934-
comptime_superclass = C.rb_class_get_superclass(C.RCLASS_ORIGIN(current_defined_class))
3935-
3936-
# Don't JIT calls that aren't simple
3937-
# Note, not using VM_CALL_ARGS_SIMPLE because sometimes we pass a block.
3938-
3939-
if calling.flags & C::VM_CALL_KWARG != 0
3940-
asm.incr_counter(:send_kwarg)
3941-
return CantCompile
3942-
end
3943-
if calling.flags & C::VM_CALL_KW_SPLAT != 0
3944-
asm.incr_counter(:send_kw_splat)
3945-
return CantCompile
3946-
end
3947-
3948-
# Ensure we haven't rebound this method onto an incompatible class.
3949-
# In the interpreter we try to avoid making this check by performing some
3950-
# cheaper calculations first, but since we specialize on the method entry
3951-
# and so only have to do this once at compile time this is fine to always
3952-
# check and side exit.
3953-
comptime_recv = jit.peek_at_stack(calling.argc)
3954-
unless C.obj_is_kind_of(comptime_recv, current_defined_class)
3955-
return CantCompile
3956-
end
3957-
3958-
# Do method lookup
3959-
cme = C.rb_callable_method_entry(comptime_superclass, mid)
3960-
3961-
if cme.nil?
3962-
return CantCompile
3963-
end
3964-
3965-
# Check that we'll be able to write this method dispatch before generating checks
3966-
cme_def_type = cme.def.type
3967-
if cme_def_type != C::VM_METHOD_TYPE_ISEQ && cme_def_type != C::VM_METHOD_TYPE_CFUNC
3968-
# others unimplemented
3969-
return CantCompile
3970-
end
3971-
3972-
# Guard that the receiver has the same class as the one from compile time
3973-
side_exit = side_exit(jit, ctx)
3974-
3975-
asm.comment('guard known me')
3976-
jit_get_lep(jit, asm, reg: :rax)
3977-
3978-
asm.mov(:rcx, me.to_i)
3979-
asm.cmp([:rax, C.VALUE.size * C::VM_ENV_DATA_INDEX_ME_CREF], :rcx)
3980-
asm.jne(counted_exit(side_exit, :invokesuper_me_changed))
3981-
3982-
# We need to assume that both our current method entry and the super
3983-
# method entry we invoke remain stable
3984-
Invariants.assume_method_lookup_stable(jit, me)
3985-
Invariants.assume_method_lookup_stable(jit, cme)
3986-
3987-
return cme
3988-
end
3989-
39903999
# vm_call_general
39914000
# @param jit [RubyVM::RJIT::JITState]
39924001
# @param ctx [RubyVM::RJIT::Context]

rjit_c.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ RJIT_RUNTIME_COUNTERS(
4646
send_c_tracing,
4747
send_is_a_class_mismatch,
4848
send_instance_of_class_mismatch,
49+
send_keywords,
4950

5051
send_blockiseq,
5152
send_block_handler,
@@ -105,6 +106,7 @@ RJIT_RUNTIME_COUNTERS(
105106
send_bmethod_blockarg,
106107

107108
invokesuper_me_changed,
109+
invokesuper_block,
108110

109111
invokeblock_none,
110112
invokeblock_symbol,

rjit_c.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1334,6 +1334,7 @@ def C.rb_rjit_runtime_counters
13341334
send_c_tracing: [CType::Immediate.parse("size_t"), Primitive.cexpr!("OFFSETOF((*((struct rb_rjit_runtime_counters *)NULL)), send_c_tracing)")],
13351335
send_is_a_class_mismatch: [CType::Immediate.parse("size_t"), Primitive.cexpr!("OFFSETOF((*((struct rb_rjit_runtime_counters *)NULL)), send_is_a_class_mismatch)")],
13361336
send_instance_of_class_mismatch: [CType::Immediate.parse("size_t"), Primitive.cexpr!("OFFSETOF((*((struct rb_rjit_runtime_counters *)NULL)), send_instance_of_class_mismatch)")],
1337+
send_keywords: [CType::Immediate.parse("size_t"), Primitive.cexpr!("OFFSETOF((*((struct rb_rjit_runtime_counters *)NULL)), send_keywords)")],
13371338
send_blockiseq: [CType::Immediate.parse("size_t"), Primitive.cexpr!("OFFSETOF((*((struct rb_rjit_runtime_counters *)NULL)), send_blockiseq)")],
13381339
send_block_handler: [CType::Immediate.parse("size_t"), Primitive.cexpr!("OFFSETOF((*((struct rb_rjit_runtime_counters *)NULL)), send_block_handler)")],
13391340
send_block_setup: [CType::Immediate.parse("size_t"), Primitive.cexpr!("OFFSETOF((*((struct rb_rjit_runtime_counters *)NULL)), send_block_setup)")],
@@ -1384,6 +1385,7 @@ def C.rb_rjit_runtime_counters
13841385
send_bmethod_not_iseq: [CType::Immediate.parse("size_t"), Primitive.cexpr!("OFFSETOF((*((struct rb_rjit_runtime_counters *)NULL)), send_bmethod_not_iseq)")],
13851386
send_bmethod_blockarg: [CType::Immediate.parse("size_t"), Primitive.cexpr!("OFFSETOF((*((struct rb_rjit_runtime_counters *)NULL)), send_bmethod_blockarg)")],
13861387
invokesuper_me_changed: [CType::Immediate.parse("size_t"), Primitive.cexpr!("OFFSETOF((*((struct rb_rjit_runtime_counters *)NULL)), invokesuper_me_changed)")],
1388+
invokesuper_block: [CType::Immediate.parse("size_t"), Primitive.cexpr!("OFFSETOF((*((struct rb_rjit_runtime_counters *)NULL)), invokesuper_block)")],
13871389
invokeblock_none: [CType::Immediate.parse("size_t"), Primitive.cexpr!("OFFSETOF((*((struct rb_rjit_runtime_counters *)NULL)), invokeblock_none)")],
13881390
invokeblock_symbol: [CType::Immediate.parse("size_t"), Primitive.cexpr!("OFFSETOF((*((struct rb_rjit_runtime_counters *)NULL)), invokeblock_symbol)")],
13891391
invokeblock_proc: [CType::Immediate.parse("size_t"), Primitive.cexpr!("OFFSETOF((*((struct rb_rjit_runtime_counters *)NULL)), invokeblock_proc)")],

0 commit comments

Comments
 (0)