eliminate cross-TU calls to rb_vm_empty_cc()

rb_vm_empty_cc(), rb_vm_empty_cc_for_super() and rb_vm_uncached_cc()
shouldn't be real function calls. rb_iseq_mark_and_move was calling
these as real functions in a loop.
This commit is contained in:
Luke Gruber 2026-09-04 11:47:37 -04:00 committed by Luke Gruber
parent ae2c6841c4
commit 25eb737f03
Notes: git 2026-10-27 13:49:56 +00:00
5 changed files with 20 additions and 42 deletions

6
iseq.c
View File

@ -433,8 +433,8 @@ rb_iseq_mark_and_move(const struct rb_gc_mark_ctx *ctx, rb_iseq_t *iseq, bool re
if (cds[i].ci) rb_gc_mark_and_move_ptr_ctx(ctx, &cds[i].ci);
const struct rb_callcache *cc = cds[i].cc;
if (!cc || cc == rb_vm_empty_cc() || cc == rb_vm_empty_cc_for_super() ||
cc == rb_vm_uncached_cc()) {
if (!cc || cc == &rb_vm_empty_cc || cc == &rb_vm_empty_cc_for_super ||
cc == &rb_vm_uncached_cc) {
// No need for marking, reference updating, or clearing
// We also want to avoid reassigning to improve CoW
continue;
@ -450,7 +450,7 @@ rb_iseq_mark_and_move(const struct rb_gc_mark_ctx *ctx, rb_iseq_t *iseq, bool re
else {
// Either the CC or CME has been invalidated. Replace
// it with the empty CC so that it can be GC'd.
cds[i].cc = rb_vm_empty_cc();
cds[i].cc = &rb_vm_empty_cc;
}
}
}

24
vm.c
View File

@ -771,7 +771,7 @@ rb_serial_t ruby_vm_constant_cache_invalidations = 0;
rb_serial_t ruby_vm_constant_cache_misses = 0;
rb_serial_t ruby_vm_global_cvar_state = 1;
static const struct rb_callcache vm_empty_cc = {
const struct rb_callcache rb_vm_empty_cc = {
.flags = T_IMEMO | (imemo_callcache << FL_USHIFT) | VM_CALLCACHE_UNMARKABLE,
.klass = Qundef,
.cme_ = NULL,
@ -784,7 +784,7 @@ static const struct rb_callcache vm_empty_cc = {
/* Installed instead of a real cc at call sites of methods that opted out of
* call caching: dispatch resolves the cme on every call rather than leaving a
* cc behind on the receiver's class. */
static const struct rb_callcache vm_uncached_cc = {
const struct rb_callcache rb_vm_uncached_cc = {
.flags = T_IMEMO | (imemo_callcache << FL_USHIFT) | VM_CALLCACHE_UNMARKABLE,
.klass = Qundef,
.cme_ = NULL,
@ -794,7 +794,7 @@ static const struct rb_callcache vm_uncached_cc = {
}
};
static const struct rb_callcache vm_empty_cc_for_super = {
const struct rb_callcache rb_vm_empty_cc_for_super = {
.flags = T_IMEMO | (imemo_callcache << FL_USHIFT) | VM_CALLCACHE_UNMARKABLE,
.klass = Qundef,
.cme_ = NULL,
@ -5464,22 +5464,4 @@ vm_collect_usage_register(int reg, int isset)
}
#endif
const struct rb_callcache *
rb_vm_empty_cc(void)
{
return &vm_empty_cc;
}
const struct rb_callcache *
rb_vm_empty_cc_for_super(void)
{
return &vm_empty_cc_for_super;
}
const struct rb_callcache *
rb_vm_uncached_cc(void)
{
return &vm_uncached_cc;
}
#include "vm_call_iseq_optimized.inc" /* required from vm_insnhelper.c */

View File

@ -324,11 +324,11 @@ enum vm_cc_type {
cc_type_refinement,
};
extern const struct rb_callcache *rb_vm_empty_cc(void);
extern const struct rb_callcache *rb_vm_empty_cc_for_super(void);
extern const struct rb_callcache *rb_vm_uncached_cc(void);
extern const struct rb_callcache rb_vm_empty_cc;
extern const struct rb_callcache rb_vm_empty_cc_for_super;
extern const struct rb_callcache rb_vm_uncached_cc;
#define vm_cc_empty() rb_vm_empty_cc()
#define vm_cc_empty() (&rb_vm_empty_cc)
static inline VALUE
cc_check_class(VALUE klass)

View File

@ -420,7 +420,7 @@ gccct_method_search_slowpath(rb_vm_t *vm, VALUE klass, unsigned int index, const
vm_search_method_slowpath0(vm->self, &cd, klass);
if (UNLIKELY(cd.cc == rb_vm_uncached_cc())) {
if (UNLIKELY(cd.cc == &rb_vm_uncached_cc)) {
// Carries no cme, so it would only ever miss here. Don't evict a usable entry for it.
return cd.cc;
}
@ -571,7 +571,7 @@ rb_call0(rb_execution_context_t *ec,
const struct rb_callcache *cc = gccct_method_search(ec, recv, mid, &ci);
if (UNLIKELY(cc == rb_vm_uncached_cc())) {
if (UNLIKELY(cc == &rb_vm_uncached_cc)) {
return vm_call0_uncached(ec, recv, mid, argc, argv, kw_splat, scope, self);
}

View File

@ -42,10 +42,6 @@ extern int rb_method_definition_eq(const rb_method_definition_t *d1, const rb_me
extern VALUE rb_make_no_method_exception(VALUE exc, VALUE format, VALUE obj,
int argc, const VALUE *argv, int priv);
static const struct rb_callcache vm_empty_cc;
static const struct rb_callcache vm_empty_cc_for_super;
static const struct rb_callcache vm_uncached_cc;
/* control stack frame */
static rb_control_frame_t *vm_get_ruby_level_caller_cfp(const rb_execution_context_t *ec, const rb_control_frame_t *cfp);
@ -2110,8 +2106,8 @@ vm_populate_cc(VALUE klass, const struct rb_callinfo * const ci, ID mid)
if (cme == NULL) {
// undef or not found: can't cache the information
VM_ASSERT(vm_cc_cme(&vm_empty_cc) == NULL);
return &vm_empty_cc;
VM_ASSERT(vm_cc_cme(&rb_vm_empty_cc) == NULL);
return &rb_vm_empty_cc;
}
if (UNLIKELY(cme->def->uncached && !(vm_ci_flag(ci) & VM_CALL_SUPER))) {
@ -2119,7 +2115,7 @@ vm_populate_cc(VALUE klass, const struct rb_callinfo * const ci, ID mid)
// is usually a metaclass, so the cost would scale with the number of
// classes. invokesuper is excluded because vm_search_super_method needs a
// cme in the cc it gets back.
return &vm_uncached_cc;
return &rb_vm_uncached_cc;
}
VALUE cc_tbl = RCLASS_WRITABLE_CC_TBL(klass);
@ -2259,7 +2255,7 @@ vm_search_cc(const VALUE klass, const struct rb_callinfo * const ci)
static bool
vm_cc_resolved_p(const struct rb_callcache *cc)
{
return cc != &vm_empty_cc && cc != &vm_uncached_cc;
return cc != &rb_vm_empty_cc && cc != &rb_vm_uncached_cc;
}
#endif
@ -2326,7 +2322,7 @@ vm_search_method_slowpath0(VALUE cd_owner, struct rb_call_data *cd, VALUE klass)
}
#if USE_DEBUG_COUNTER
const struct rb_callcache *empty_cc = &vm_empty_cc;
const struct rb_callcache *empty_cc = &rb_vm_empty_cc;
if (!old_cc || old_cc == empty_cc) {
// empty
RB_DEBUG_COUNTER_INC(mc_inline_miss_empty);
@ -2386,7 +2382,7 @@ vm_search_method_fastpath(const struct rb_control_frame_struct *reg_cfp, struct
if (vm_cc_hit_p(cc, cd, klass)) {
return cc;
}
if (UNLIKELY(cc == &vm_uncached_cc)) {
if (UNLIKELY(cc == &rb_vm_uncached_cc)) {
return cc;
}
@ -2435,7 +2431,7 @@ rb_zjit_vm_search_method(VALUE cd_owner, struct rb_call_data *cd, VALUE recv)
VALUE klass = CLASS_OF(recv);
const struct rb_callcache *cc = cd->cc;
if (!vm_cc_hit_p(cc, cd, klass)) {
if (UNLIKELY(cc == &vm_uncached_cc)) {
if (UNLIKELY(cc == &rb_vm_uncached_cc)) {
return rb_callable_method_entry(klass, vm_ci_mid(cd->ci));
}
cc = vm_search_method_slowpath0(cd_owner, cd, klass);
@ -5234,7 +5230,7 @@ vm_super_outside(void)
static const struct rb_callcache *
empty_cc_for_super(void)
{
return &vm_empty_cc_for_super;
return &rb_vm_empty_cc_for_super;
}
static const struct rb_callcache *