diff --git a/.github/workflows/check_sast.yml b/.github/workflows/check_sast.yml index 9e8020ca6d7766..af35f0a5a59928 100644 --- a/.github/workflows/check_sast.yml +++ b/.github/workflows/check_sast.yml @@ -78,14 +78,14 @@ jobs: persist-credentials: false - name: Initialize CodeQL - uses: github/codeql-action/init@d1ba80a13dd99fba24a470575428917156a28b43 # v4.37.5 + uses: github/codeql-action/init@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6 with: languages: ${{ matrix.language }} build-mode: none config-file: .github/codeql/codeql-config.yml - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@d1ba80a13dd99fba24a470575428917156a28b43 # v4.37.5 + uses: github/codeql-action/analyze@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6 with: category: '/language:${{ matrix.language }}' upload: False @@ -127,7 +127,7 @@ jobs: continue-on-error: true - name: Upload SARIF - uses: github/codeql-action/upload-sarif@d1ba80a13dd99fba24a470575428917156a28b43 # v4.37.5 + uses: github/codeql-action/upload-sarif@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6 with: sarif_file: sarif-results/${{ matrix.language }}.sarif continue-on-error: true diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index e59ba6bc55c9a7..1f7e23275a0c4e 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -73,6 +73,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@d1ba80a13dd99fba24a470575428917156a28b43 # v4.37.5 + uses: github/codeql-action/upload-sarif@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6 with: sarif_file: results.sarif diff --git a/.github/workflows/zjit-macos.yml b/.github/workflows/zjit-macos.yml index 1e63e8b3115c00..28752a2584a199 100644 --- a/.github/workflows/zjit-macos.yml +++ b/.github/workflows/zjit-macos.yml @@ -98,7 +98,7 @@ jobs: rustup install ${{ matrix.rust_version }} --profile minimal rustup default ${{ matrix.rust_version }} - - uses: taiki-e/install-action@67729d5c413db75907f0ad1e39bb04b9c868ff60 # v2.85.7 + - uses: taiki-e/install-action@cb33e69fad06166ca28a42b2575e4dadabf62ee8 # v2.85.8 with: tool: nextest@0.9 if: ${{ matrix.test_task == 'zjit-check' }} diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml index bac24c26439cbf..e4480f3f355249 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -152,7 +152,7 @@ jobs: ruby-version: '3.1' bundler: none - - uses: taiki-e/install-action@67729d5c413db75907f0ad1e39bb04b9c868ff60 # v2.85.7 + - uses: taiki-e/install-action@cb33e69fad06166ca28a42b2575e4dadabf62ee8 # v2.85.8 with: tool: nextest@0.9 if: ${{ matrix.test_task == 'zjit-check' }} diff --git a/bignum.c b/bignum.c index b72b18ad08ab79..d95444a56378e3 100644 --- a/bignum.c +++ b/bignum.c @@ -45,6 +45,7 @@ #include "ruby/thread.h" #include "ruby/util.h" #include "ruby_assert.h" +#include "vm_core.h" /* for GET_EC() */ #if USE_GMP RBIMPL_WARNING_PUSH() diff --git a/class.c b/class.c index 79817af97d585f..a90a0f43de50ed 100644 --- a/class.c +++ b/class.c @@ -2142,6 +2142,17 @@ module_descendants_add(VALUE klass, struct descendants_traverse_data *data) rb_class_foreach_subclass(klass, module_descendants_recursive, (VALUE)data); } +// an include done in another box is not in the ancestors here if the includer +// has a classext per box, e.g. a module included into a builtin class +static bool +iclass_in_ancestors_p(VALUE iclass, VALUE includer) +{ + for (VALUE p = includer; p; p = RCLASS_SUPER(p)) { + if (p == iclass) return true; + } + return false; +} + static void module_descendants_recursive(VALUE entry, VALUE v) { @@ -2159,6 +2170,7 @@ module_descendants_recursive(VALUE entry, VALUE v) if (!includer || UNDEF_P(includer)) return; if (rb_objspace_garbage_object_p(includer)) return; if (RCLASS_SINGLETON_P(includer)) return; // e.g. Object#extend + if (!iclass_in_ancestors_p(entry, includer)) return; module_descendants_add(includer, data); } else { diff --git a/compile.c b/compile.c index a6a498e9a1f518..adc0037b8dbf79 100644 --- a/compile.c +++ b/compile.c @@ -21,6 +21,7 @@ #include "internal.h" #include "internal/array.h" #include "internal/compile.h" +#include "internal/coverage.h" #include "internal/complex.h" #include "internal/encoding.h" #include "internal/error.h" diff --git a/ext/coverage/coverage.c b/ext/coverage/coverage.c index eae5ddfec8b638..95688c309b0316 100644 --- a/ext/coverage/coverage.c +++ b/ext/coverage/coverage.c @@ -8,12 +8,9 @@ ************************************************/ -#include "internal/gc.h" +#include "internal/coverage.h" #include "internal/hash.h" -#include "internal/thread.h" -#include "internal/sanitizers.h" #include "ruby.h" -#include "vm_core.h" static enum { IDLE, @@ -42,13 +39,13 @@ rb_coverage_supported(VALUE self, VALUE _mode) { ID mode = RB_SYM2ID(_mode); - return RBOOL( + return ( mode == rb_intern("lines") || mode == rb_intern("oneshot_lines") || mode == rb_intern("branches") || mode == rb_intern("methods") || mode == rb_intern("eval") - ); + ) ? Qtrue : Qfalse; } /* @@ -242,60 +239,30 @@ branch_coverage(VALUE branches) return b.result; } -/* - * Fold a single method entry's contribution into the method coverage result. - * `add` is the number of calls to attribute to it (0 is used to make sure a - * defined-but-uncalled method shows up). Entries that resolve elsewhere (e.g. - * aliases) are skipped: their calls are attributed to the original definition - * via the [owner, mid, location] key, exactly like the old heap-walk did. - */ static void -method_coverage_add(const rb_method_entry_t *me, long add, VALUE ncoverages) +method_coverage_i(const struct rb_coverage_method_data *method, void *data) { - VALUE data[5]; - const rb_method_entry_t *me2 = rb_resolve_me_location(me, data); - if (me != me2) return; - - VALUE klass = me->owner; - if (RB_TYPE_P(klass, T_ICLASS)) return; - - VALUE first_lineno = data[1]; - if (FIX2LONG(first_lineno) <= 0) return; - - VALUE path = data[0]; - VALUE ncoverage = rb_hash_aref(ncoverages, path); - if (NIL_P(ncoverage)) return; - - VALUE methods = rb_hash_aref(ncoverage, ID2SYM(rb_intern("methods"))); - VALUE method_id = ID2SYM(me->def->original_id); - VALUE key = rb_ary_new_from_args(6, klass, method_id, data[1], data[2], data[3], data[4]); - - VALUE rcount = rb_hash_aref(methods, key); - long count = NIL_P(rcount) ? 0 : FIX2LONG(rcount); - if (!POSFIXABLE(count + add)) { - count = FIXNUM_MAX; - } - else { - count += add; + VALUE ncoverages = *(VALUE *)data; + VALUE ncoverage = rb_hash_aref(ncoverages, method->path); + + if (!NIL_P(ncoverage)) { + VALUE methods = rb_hash_aref(ncoverage, ID2SYM(rb_intern("methods"))); + VALUE key = rb_ary_new_from_args(6, method->owner, method->method_id, + method->first_lineno, method->first_column, + method->last_lineno, method->last_column); + VALUE rcount = method->count; + VALUE previous = rb_hash_aref(methods, key); + + if (NIL_P(rcount)) rcount = LONG2FIX(0); + if (NIL_P(previous)) previous = LONG2FIX(0); + if (!POSFIXABLE(FIX2LONG(rcount) + FIX2LONG(previous))) { + rcount = LONG2FIX(FIXNUM_MAX); + } + else { + rcount = LONG2FIX(FIX2LONG(rcount) + FIX2LONG(previous)); + } + rb_hash_aset(methods, key, rcount); } - rb_hash_aset(methods, key, LONG2FIX(count)); -} - -/* me_set entries: every defined method, so uncalled ones appear with count 0. */ -static int -method_coverage_me_i(VALUE key, VALUE value, VALUE ncoverages) -{ - method_coverage_add((const rb_method_entry_t *)key, 0, ncoverages); - return ST_CONTINUE; -} - -/* cme2counter entries: call counts, attributed to the definition's key. */ -static int -method_coverage_count_i(VALUE key, VALUE value, VALUE ncoverages) -{ - long add = FIXNUM_P(value) ? FIX2LONG(value) : 0; - method_coverage_add((const rb_method_entry_t *)key, add, ncoverages); - return ST_CONTINUE; } static int @@ -361,8 +328,7 @@ rb_coverage_peek_result(VALUE klass) rb_hash_foreach(coverages, coverage_peek_result_i, ncoverages); if (current_mode & COVERAGE_TARGET_METHODS) { - if (RTEST(me_set)) rb_hash_foreach(me_set, method_coverage_me_i, ncoverages); - if (RTEST(cme2counter)) rb_hash_foreach(cme2counter, method_coverage_count_i, ncoverages); + rb_coverage_each_method(method_coverage_i, &ncoverages); } rb_hash_freeze(ncoverages); diff --git a/ext/objspace/object_tracing.c b/ext/objspace/object_tracing.c index 0497c89210e6f5..93f631ec1ce12a 100644 --- a/ext/objspace/object_tracing.c +++ b/ext/objspace/object_tracing.c @@ -13,7 +13,6 @@ **********************************************************************/ -#include "internal.h" #include "internal/gc.h" #include "ruby/debug.h" #include "objspace.h" diff --git a/ext/objspace/objspace.c b/ext/objspace/objspace.c index 0f0470ed637ecf..35d38bfd87791a 100644 --- a/ext/objspace/objspace.c +++ b/ext/objspace/objspace.c @@ -13,20 +13,16 @@ **********************************************************************/ #include "internal.h" -#include "internal/class.h" -#include "internal/compilers.h" #include "internal/gc.h" #include "internal/hash.h" #include "internal/imemo.h" +#include "internal/objspace.h" #include "internal/sanitizers.h" #include "ruby/io.h" #include "ruby/re.h" #include "ruby/st.h" #include "symbol.h" - -#undef rb_funcall - -#include "ruby/ruby.h" +#include "objspace.h" /* * call-seq: diff --git a/ext/objspace/objspace_dump.c b/ext/objspace/objspace_dump.c index e7778d08dd6214..7bd34864476c47 100644 --- a/ext/objspace/objspace_dump.c +++ b/ext/objspace/objspace_dump.c @@ -14,24 +14,18 @@ #include "id_table.h" #include "internal.h" -#include "internal/array.h" -#include "internal/class.h" -#include "internal/gc.h" #include "internal/hash.h" +#include "internal/imemo.h" #include "internal/io.h" +#include "internal/objspace.h" #include "internal/string.h" #include "internal/sanitizers.h" #include "symbol.h" #include "shape.h" -#include "node.h" #include "objspace.h" #include "ruby/debug.h" #include "ruby/util.h" #include "ruby/io.h" -#include "vm_callinfo.h" -#include "vm_sync.h" - -RUBY_EXTERN const char ruby_hexdigits[]; #define BUFFER_CAPACITY 4096 @@ -443,7 +437,7 @@ dump_object(VALUE obj, struct dump_config *dc) switch (imemo_type(obj)) { case imemo_callinfo: - mid = vm_ci_mid((const struct rb_callinfo *)obj); + mid = rb_imemo_callinfo_mid(obj); if (mid != 0) { dump_append(dc, ", \"mid\":"); dump_append_id(dc, mid); @@ -452,9 +446,10 @@ dump_object(VALUE obj, struct dump_config *dc) case imemo_callcache: { - VALUE klass = ((const struct rb_callcache *)obj)->klass; - if (klass != Qundef) { - mid = vm_cc_cme((const struct rb_callcache *)obj)->called_id; + struct rb_imemo_callcache_data data; + if (rb_imemo_callcache_get_data(obj, &data)) { + VALUE klass = data.klass; + mid = data.called_id; if (mid != 0) { dump_append(dc, ", \"called_id\":"); dump_append_id(dc, mid); diff --git a/ext/socket/raddrinfo.c b/ext/socket/raddrinfo.c index 6a565789952952..1d85835ba531c1 100644 --- a/ext/socket/raddrinfo.c +++ b/ext/socket/raddrinfo.c @@ -9,6 +9,7 @@ ************************************************/ #include "rubysocket.h" +#include // GETADDRINFO_IMPL == 0 : call getaddrinfo/getnameinfo directly // GETADDRINFO_IMPL == 1 : call getaddrinfo/getnameinfo without gvl (but uncancellable) diff --git a/imemo.c b/imemo.c index 62f9e5768dd28d..5fb5766a55eb96 100644 --- a/imemo.c +++ b/imemo.c @@ -11,6 +11,27 @@ size_t rb_iseq_memsize(const rb_iseq_t *iseq); void rb_iseq_mark_and_move(rb_iseq_t *iseq, bool reference_updating); void rb_iseq_free(const rb_iseq_t *iseq); +ID +rb_imemo_callinfo_mid(VALUE obj) +{ + RUBY_ASSERT(imemo_type(obj) == imemo_callinfo); + return vm_ci_mid((const struct rb_callinfo *)obj); +} + +bool +rb_imemo_callcache_get_data(VALUE obj, struct rb_imemo_callcache_data *data) +{ + const struct rb_callcache *cc = (const struct rb_callcache *)obj; + + RUBY_ASSERT(imemo_type(obj) == imemo_callcache); + + if (cc->klass == Qundef) return false; + + data->klass = cc->klass; + data->called_id = vm_cc_cme(cc)->called_id; + return true; +} + const char * rb_imemo_name(enum imemo_type type) { diff --git a/internal/box.h b/internal/box.h index 1958b8dca6b84a..1e7d4fbdef5169 100644 --- a/internal/box.h +++ b/internal/box.h @@ -94,4 +94,8 @@ void rb_box_cleanup_local_extension(VALUE cleanup); void rb_initialize_mandatory_boxes(void); void rb_box_init_done(void); void rb_box_set_gem_flags(rb_box_gem_flags_t *); + +/* variable.c */ +void rb_autoload_copy_table_for_box(st_table *, const rb_box_t *); + #endif /* INTERNAL_BOX_H */ diff --git a/internal/coverage.h b/internal/coverage.h new file mode 100644 index 00000000000000..9c78661671dc23 --- /dev/null +++ b/internal/coverage.h @@ -0,0 +1,50 @@ +#ifndef INTERNAL_COVERAGE_H /*-*-C-*-vi:se ft=c:*/ +#define INTERNAL_COVERAGE_H +/** + * @author Ruby developers + * @copyright This file is a part of the programming language Ruby. + * Permission is hereby granted, to either redistribute and/or + * modify this file, provided that the conditions mentioned in the + * file COPYING are met. Consult the file for details. + * @brief Internal header for Coverage. + */ +#include "ruby/ruby.h" + +#define COVERAGE_INDEX_LINES 0 +#define COVERAGE_INDEX_BRANCHES 1 + +#define COVERAGE_TARGET_LINES 1 +#define COVERAGE_TARGET_BRANCHES 2 +#define COVERAGE_TARGET_METHODS 4 +#define COVERAGE_TARGET_ONESHOT_LINES 8 +#define COVERAGE_TARGET_EVAL 16 + +struct rb_coverage_method_data { + VALUE owner; + VALUE method_id; + VALUE path; + VALUE first_lineno; + VALUE first_column; + VALUE last_lineno; + VALUE last_column; + VALUE count; +}; + +typedef void rb_coverage_method_callback(const struct rb_coverage_method_data *, void *); + +RUBY_SYMBOL_EXPORT_BEGIN + +VALUE rb_get_coverages(void); +void rb_set_coverages(VALUE coverages, int mode, VALUE cme2counter, VALUE me_set); +void rb_clear_coverages(void); +void rb_reset_coverages(void); +void rb_resume_coverages(void); +void rb_suspend_coverages(void); +void rb_coverage_each_method(rb_coverage_method_callback callback, void *data); + +RUBY_SYMBOL_EXPORT_END + +int rb_get_coverage_mode(void); +VALUE rb_default_coverage(int n); + +#endif /* INTERNAL_COVERAGE_H */ diff --git a/internal/gc.h b/internal/gc.h index 9a137d2609554f..706610f35eec4c 100644 --- a/internal/gc.h +++ b/internal/gc.h @@ -14,7 +14,7 @@ #include "internal/compilers.h" /* for __has_attribute */ #include "ruby/ruby.h" /* for rb_event_flag_t */ -#include "vm_core.h" /* for GET_EC() */ +#include "ruby_atomic.h" /* for RUBY_ATOMIC_VALUE_SET */ struct rb_gc_zjit_fastpath; @@ -121,6 +121,7 @@ const char *rb_raw_obj_info(char *const buff, const size_t buff_size, VALUE obj) struct rb_execution_context_struct; /* in vm_core.h */ struct rb_objspace; /* in vm_core.h */ +struct rb_ractor_struct; /* in vm_core.h */ #define EC_NEWOBJ_OF(var, T, c, f, s, ec) \ T *(var) = (T *)rb_ec_newobj_of((ec), (c), (f), s) @@ -200,9 +201,10 @@ void *rb_xrealloc_mul_add(const void *, size_t, size_t, size_t); RUBY_ATTR_MALLOC void *rb_xmalloc_mul_add_mul(size_t, size_t, size_t, size_t); RUBY_ATTR_MALLOC void *rb_xcalloc_mul_add_mul(size_t, size_t, size_t, size_t); void rb_gc_register_pinning_obj(VALUE obj); -rb_execution_context_t *rb_gc_get_ec(void); +struct rb_execution_context_struct *rb_gc_get_ec(void); +VALUE rb_newobj_of_unprotected(VALUE klass, VALUE flags, size_t size); -void *rb_gc_ractor_cache_alloc(rb_ractor_t *ractor); +void *rb_gc_ractor_cache_alloc(struct rb_ractor_struct *ractor); void rb_gc_ractor_cache_free(void *cache); bool rb_gc_zjit_new_obj_fastpath(size_t alloc_size, VALUE flags, VALUE klass, struct rb_gc_zjit_fastpath *fastpath); diff --git a/internal/imemo.h b/internal/imemo.h index da8196f47167c7..fee5c07bc33703 100644 --- a/internal/imemo.h +++ b/internal/imemo.h @@ -160,6 +160,12 @@ void rb_imemo_free(VALUE obj); RUBY_SYMBOL_EXPORT_BEGIN const char *rb_imemo_name(enum imemo_type type); +ID rb_imemo_callinfo_mid(VALUE obj); +struct rb_imemo_callcache_data { + VALUE klass; + ID called_id; +}; +bool rb_imemo_callcache_get_data(VALUE obj, struct rb_imemo_callcache_data *data); RUBY_SYMBOL_EXPORT_END static inline enum imemo_type diff --git a/internal/objspace.h b/internal/objspace.h new file mode 100644 index 00000000000000..db471bda17b3b9 --- /dev/null +++ b/internal/objspace.h @@ -0,0 +1,24 @@ +#ifndef INTERNAL_OBJSPACE_H /*-*-C-*-vi:se ft=c:*/ +#define INTERNAL_OBJSPACE_H +/** + * @author Ruby developers + * @copyright This file is a part of the programming language Ruby. + * Permission is hereby granted, to either redistribute and/or + * modify this file, provided that the conditions mentioned in the + * file COPYING are met. Consult the file for details. + * @brief Internal header for Objspace. + */ + +RUBY_SYMBOL_EXPORT_BEGIN + +/* from intern/class.h */ +RUBY_EXTERN VALUE rb_class_super_of(VALUE klass); +RUBY_EXTERN VALUE rb_class_singleton_p(VALUE klass); +RUBY_EXTERN unsigned char rb_class_variation_count(VALUE klass); + +/* from vm_sync.h */ +RUBY_EXTERN VALUE rb_vm_lock_with_barrier(VALUE (*func)(void *args), void *args); + +RUBY_SYMBOL_EXPORT_END + +#endif /* INTERNAL_OBJSPACE_H */ diff --git a/internal/thread.h b/internal/thread.h index ea891b4372f8f4..77226dafdbebda 100644 --- a/internal/thread.h +++ b/internal/thread.h @@ -12,6 +12,7 @@ #include "ruby/intern.h" /* for rb_blocking_function_t */ #include "ccan/list/list.h" /* for list in rb_io_close_wait_list */ +struct rb_execution_context_struct; /* in vm_core.h */ struct rb_thread_struct; /* in vm_core.h */ struct rb_io; @@ -22,15 +23,6 @@ struct rb_io; SET_MACHINE_STACK_END(&(th)->ec->machine.stack_end); \ } while (0) -/* thread.c */ -#define COVERAGE_INDEX_LINES 0 -#define COVERAGE_INDEX_BRANCHES 1 -#define COVERAGE_TARGET_LINES 1 -#define COVERAGE_TARGET_BRANCHES 2 -#define COVERAGE_TARGET_METHODS 4 -#define COVERAGE_TARGET_ONESHOT_LINES 8 -#define COVERAGE_TARGET_EVAL 16 - #define RUBY_FATAL_THREAD_KILLED INT2FIX(0) #define RUBY_FATAL_THREAD_TERMINATED INT2FIX(1) #define RUBY_FATAL_FIBER_KILLED RB_INT2FIX(2) @@ -38,9 +30,6 @@ struct rb_io; VALUE rb_obj_is_mutex(VALUE obj); VALUE rb_suppress_tracing(VALUE (*func)(VALUE), VALUE arg); void rb_thread_execute_interrupts(VALUE th); -VALUE rb_get_coverages(void); -int rb_get_coverage_mode(void); -VALUE rb_default_coverage(int); VALUE rb_thread_shield_new(void); bool rb_thread_shield_owned(VALUE self); VALUE rb_thread_shield_wait(VALUE self); diff --git a/iseq.c b/iseq.c index 23e9abcea7be9b..a51ad14439233e 100644 --- a/iseq.c +++ b/iseq.c @@ -25,6 +25,7 @@ #include "internal/bits.h" #include "internal/class.h" #include "internal/compile.h" +#include "internal/coverage.h" #include "internal/error.h" #include "internal/file.h" #include "internal/gc.h" diff --git a/iseq.h b/iseq.h index 8308260fedfd68..c9bdfcb484759f 100644 --- a/iseq.h +++ b/iseq.h @@ -10,6 +10,7 @@ Copyright (C) 2004-2008 Koichi Sasada **********************************************************************/ +#include "internal/coverage.h" #include "internal/gc.h" #include "shape.h" #include "vm_core.h" diff --git a/memory_view.c b/memory_view.c index f8d7508f68c24f..2c088e7dab511c 100644 --- a/memory_view.c +++ b/memory_view.c @@ -9,6 +9,7 @@ #include "internal.h" #include "internal/hash.h" #include "internal/variable.h" +#include "internal/vm.h" #include "ruby/memory_view.h" #include "ruby/util.h" #include "vm_sync.h" diff --git a/node.c b/node.c index 5d00823eb67fc7..2521e554e0f724 100644 --- a/node.c +++ b/node.c @@ -11,11 +11,12 @@ #ifdef UNIVERSAL_PARSER #include -#include "node.h" #include "rubyparser.h" #endif +#include "internal.h" #include "internal/variable.h" +#include "node.h" #define NODE_BUF_DEFAULT_SIZE (sizeof(struct RNode) * 16) diff --git a/numeric.c b/numeric.c index cb7b4932e963b9..0760312e0ea9d5 100644 --- a/numeric.c +++ b/numeric.c @@ -39,6 +39,7 @@ #include "internal/string.h" #include "internal/util.h" #include "internal/variable.h" +#include "vm_core.h" #include "ruby/encoding.h" #include "ruby/util.h" #include "builtin.h" diff --git a/parse.y b/parse.y index 2afeb62097471e..f55b6374fef5d8 100644 --- a/parse.y +++ b/parse.y @@ -24,6 +24,7 @@ #endif #include "ruby/internal/config.h" +#include "internal/thread.h" #include @@ -41,11 +42,13 @@ #else #include "internal.h" +#include "internal/array.h" #include "internal/compile.h" #include "internal/compilers.h" #include "internal/complex.h" #include "internal/encoding.h" #include "internal/error.h" +#include "internal/gc.h" #include "internal/hash.h" #include "internal/io.h" #include "internal/numeric.h" @@ -55,7 +58,6 @@ #include "internal/ruby_parser.h" #include "internal/symbol.h" #include "internal/thread.h" -#include "internal/variable.h" #include "node.h" #include "parser_node.h" #include "probes.h" diff --git a/rational.c b/rational.c index f40c412506e273..30c3e6daffcbf6 100644 --- a/rational.c +++ b/rational.c @@ -33,6 +33,7 @@ #include "internal/object.h" #include "internal/rational.h" #include "ruby_assert.h" +#include "vm_core.h" /* for GET_EC() */ #if USE_GMP RBIMPL_WARNING_PUSH() diff --git a/ruby.c b/ruby.c index d83d83cf93380a..3d7ef4ff993968 100644 --- a/ruby.c +++ b/ruby.c @@ -45,6 +45,7 @@ #include "internal.h" #include "internal/cmdlineopt.h" #include "internal/cont.h" +#include "internal/coverage.h" #include "internal/error.h" #include "internal/file.h" #include "internal/inits.h" diff --git a/shape.h b/shape.h index 68fd4a7c9f332c..2a416b1b126e32 100644 --- a/shape.h +++ b/shape.h @@ -2,6 +2,7 @@ #define RUBY_SHAPE_H #include "internal/gc.h" +#include "internal/imemo.h" typedef uint8_t attr_index_t; typedef uint32_t shape_id_t; diff --git a/string.c b/string.c index 18c31812c71623..bc5434ef6ae1f9 100644 --- a/string.c +++ b/string.c @@ -49,6 +49,7 @@ #include "ruby/ractor.h" #include "ruby_assert.h" #include "shape.h" +#include "vm_core.h" #include "vm_sync.h" #include "zjit.h" #include "ruby/internal/attr/nonstring.h" diff --git a/symbol.c b/symbol.c index e8a1a177b0fd56..48e3569c8fbf0a 100644 --- a/symbol.c +++ b/symbol.c @@ -24,6 +24,7 @@ #include "ruby/ractor.h" #include "ruby/st.h" #include "symbol.h" +#include "vm_core.h" #include "vm_sync.h" #include "builtin.h" #include "ruby/internal/attr/nonstring.h" diff --git a/test/ruby/box/descendants.rb b/test/ruby/box/descendants.rb new file mode 100644 index 00000000000000..19db342e9a5269 --- /dev/null +++ b/test/ruby/box/descendants.rb @@ -0,0 +1,25 @@ +module DescendantsExt +end + +class String + include DescendantsExt +end + +class BoxedString < String +end + +MainClass = Ruby::Box.main::TestBoxDescendantsMain + +class MainClass + include DescendantsExt +end + +module Descendants + def self.ext_descendants + DescendantsExt.descendants + end + + def self.string_descendants + String.descendants + end +end diff --git a/test/ruby/test_box.rb b/test/ruby/test_box.rb index 4583db804117a8..c60fc390cef6f7 100644 --- a/test/ruby/test_box.rb +++ b/test/ruby/test_box.rb @@ -352,6 +352,29 @@ def test_methods_and_constants_added_by_include assert_equal "foo 1", @box::OpenClassWithInclude.refer_foo end + + def test_descendants_follow_ancestors_of_the_current_box + setup_box + + @box.require_relative('box/descendants') + + assert_include @box::Descendants.ext_descendants, String + assert_include @box::Descendants.string_descendants, @box::BoxedString + + # a builtin class has its own classext per box, so the include is invisible here + assert_not_include String.ancestors, @box::DescendantsExt + assert_not_include @box::DescendantsExt.descendants, String + + # a class defined outside the box shares its classext, so the include is visible here + assert_include TestBoxDescendantsMain.ancestors, @box::DescendantsExt + assert_include @box::DescendantsExt.descendants, TestBoxDescendantsMain + + assert_include @box::BoxedString.ancestors, String + assert_include String.descendants, @box::BoxedString + end +end + +class TestBoxDescendantsMain end module ProcLookupTestA diff --git a/thread.c b/thread.c index 5f84c0399418da..68a16e4a1f2489 100644 --- a/thread.c +++ b/thread.c @@ -77,6 +77,7 @@ #include "internal.h" #include "internal/class.h" #include "internal/cont.h" +#include "internal/coverage.h" #include "internal/error.h" #include "internal/eval.h" #include "internal/gc.h" @@ -6099,6 +6100,66 @@ rb_resolve_me_location(const rb_method_entry_t *me, VALUE resolved_location[5]) return me; } +struct method_coverage_arg { + rb_coverage_method_callback *callback; + void *data; +}; + +static void +method_coverage_call(const rb_method_entry_t *me, VALUE count, + struct method_coverage_arg *arg) +{ + VALUE location[5]; + const rb_method_entry_t *resolved_me = rb_resolve_me_location(me, location); + + if (me != resolved_me || RB_TYPE_P(me->owner, T_ICLASS) || + FIX2LONG(location[1]) <= 0) return; + + struct rb_coverage_method_data method = { + .owner = me->owner, + .method_id = ID2SYM(me->def->original_id), + .path = location[0], + .first_lineno = location[1], + .first_column = location[2], + .last_lineno = location[3], + .last_column = location[4], + .count = count, + }; + arg->callback(&method, arg->data); +} + +static int +method_coverage_me_i(VALUE me, VALUE value, VALUE data) +{ + method_coverage_call((const rb_method_entry_t *)me, INT2FIX(0), + (struct method_coverage_arg *)data); + return ST_CONTINUE; +} + +static int +method_coverage_count_i(VALUE me, VALUE count, VALUE data) +{ + if (!FIXNUM_P(count)) count = INT2FIX(0); + method_coverage_call((const rb_method_entry_t *)me, count, + (struct method_coverage_arg *)data); + return ST_CONTINUE; +} + +void +rb_coverage_each_method(rb_coverage_method_callback callback, void *data) +{ + struct method_coverage_arg arg = {callback, data}; + VALUE me_set = GET_VM()->me_set; + VALUE cme2counter = GET_VM()->cme2counter; + + if (RTEST(me_set)) { + rb_hash_foreach(me_set, method_coverage_me_i, (VALUE)&arg); + } + if (RTEST(cme2counter)) { + rb_hash_foreach(cme2counter, method_coverage_count_i, (VALUE)&arg); + } +} + static void update_method_coverage(VALUE cme2counter, rb_trace_arg_t *trace_arg) { diff --git a/vm_core.h b/vm_core.h index ce118d5080ccbf..e82227aa6414eb 100644 --- a/vm_core.h +++ b/vm_core.h @@ -2446,13 +2446,6 @@ int rb_thread_check_trap_pending(void); #define RUBY_EVENT_COVERAGE_LINE 0x010000 #define RUBY_EVENT_COVERAGE_BRANCH 0x020000 -extern VALUE rb_get_coverages(void); -extern void rb_set_coverages(VALUE, int, VALUE, VALUE); -extern void rb_clear_coverages(void); -extern void rb_reset_coverages(void); -extern void rb_resume_coverages(void); -extern void rb_suspend_coverages(void); - void rb_postponed_job_flush(rb_vm_t *vm); void rb_postponed_job_trigger_for_ractor(unsigned int h, VALUE running_ractor); diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index 659c0af223bfb7..753955c8affb0a 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -153,6 +153,7 @@ impl ::std::cmp::PartialEq for __BindgenUnionField { } } impl ::std::cmp::Eq for __BindgenUnionField {} +pub const SHAPE_ID_NUM_BITS: u32 = 32; pub const INTEGER_REDEFINED_OP_FLAG: u32 = 1; pub const FLOAT_REDEFINED_OP_FLAG: u32 = 2; pub const STRING_REDEFINED_OP_FLAG: u32 = 4; @@ -171,7 +172,6 @@ pub const VM_ENV_DATA_INDEX_ME_CREF: i32 = -2; pub const VM_ENV_DATA_INDEX_SPECVAL: i32 = -1; pub const VM_ENV_DATA_INDEX_FLAGS: u32 = 0; pub const VM_BLOCK_HANDLER_NONE: u32 = 0; -pub const SHAPE_ID_NUM_BITS: u32 = 32; pub type ID = ::std::os::raw::c_ulong; pub type rb_alloc_func_t = ::std::option::Option VALUE>; pub const RUBY_Qfalse: ruby_special_consts = 0; @@ -300,6 +300,42 @@ pub const RUBY_ENCINDEX_EUC_JP: ruby_preserved_encindex = 10; pub const RUBY_ENCINDEX_Windows_31J: ruby_preserved_encindex = 11; pub const RUBY_ENCINDEX_BUILTIN_MAX: ruby_preserved_encindex = 12; pub type ruby_preserved_encindex = u32; +pub type rb_atomic_t = ::std::os::raw::c_uint; +pub const imemo_env: imemo_type = 0; +pub const imemo_cref: imemo_type = 1; +pub const imemo_svar: imemo_type = 2; +pub const imemo_throw_data: imemo_type = 3; +pub const imemo_ifunc: imemo_type = 4; +pub const imemo_memo: imemo_type = 5; +pub const imemo_ment: imemo_type = 6; +pub const imemo_iseq: imemo_type = 7; +pub const imemo_tmpbuf: imemo_type = 8; +pub const imemo_cvar_entry: imemo_type = 9; +pub const imemo_callinfo: imemo_type = 10; +pub const imemo_callcache: imemo_type = 11; +pub const imemo_constcache: imemo_type = 12; +pub const imemo_fields: imemo_type = 13; +pub const imemo_subclasses: imemo_type = 14; +pub const imemo_cdhash: imemo_type = 15; +pub type imemo_type = u32; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct vm_ifunc_argc { + pub min: ::std::os::raw::c_int, + pub max: ::std::os::raw::c_int, +} +#[repr(C)] +pub struct vm_ifunc { + pub flags: VALUE, + pub svar_lep: *mut VALUE, + pub func: rb_block_call_func_t, + pub data: *const ::std::os::raw::c_void, + pub argc: vm_ifunc_argc, +} +pub type attr_index_t = u8; +pub type shape_id_t = u32; +pub const SHAPE_ID_HAS_IVAR_MASK: shape_id_mask = 67633150; +pub type shape_id_mask = u32; pub const BOP_PLUS: ruby_basic_operators = 0; pub const BOP_MINUS: ruby_basic_operators = 1; pub const BOP_MULT: ruby_basic_operators = 2; @@ -339,38 +375,6 @@ pub const BOP_YIELD: ruby_basic_operators = 35; pub const BOP_LAST_: ruby_basic_operators = 36; pub type ruby_basic_operators = u32; pub type rb_serial_t = ::std::os::raw::c_ulonglong; -pub const imemo_env: imemo_type = 0; -pub const imemo_cref: imemo_type = 1; -pub const imemo_svar: imemo_type = 2; -pub const imemo_throw_data: imemo_type = 3; -pub const imemo_ifunc: imemo_type = 4; -pub const imemo_memo: imemo_type = 5; -pub const imemo_ment: imemo_type = 6; -pub const imemo_iseq: imemo_type = 7; -pub const imemo_tmpbuf: imemo_type = 8; -pub const imemo_cvar_entry: imemo_type = 9; -pub const imemo_callinfo: imemo_type = 10; -pub const imemo_callcache: imemo_type = 11; -pub const imemo_constcache: imemo_type = 12; -pub const imemo_fields: imemo_type = 13; -pub const imemo_subclasses: imemo_type = 14; -pub const imemo_cdhash: imemo_type = 15; -pub type imemo_type = u32; -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct vm_ifunc_argc { - pub min: ::std::os::raw::c_int, - pub max: ::std::os::raw::c_int, -} -#[repr(C)] -pub struct vm_ifunc { - pub flags: VALUE, - pub svar_lep: *mut VALUE, - pub func: rb_block_call_func_t, - pub data: *const ::std::os::raw::c_void, - pub argc: vm_ifunc_argc, -} -pub type rb_atomic_t = ::std::os::raw::c_uint; pub const METHOD_VISI_UNDEF: rb_method_visibility_t = 0; pub const METHOD_VISI_PUBLIC: rb_method_visibility_t = 1; pub const METHOD_VISI_PRIVATE: rb_method_visibility_t = 2; @@ -645,10 +649,6 @@ pub const VM_ENV_FLAG_ESCAPED: vm_frame_env_flags = 4; pub const VM_ENV_FLAG_WB_REQUIRED: vm_frame_env_flags = 8; pub const VM_ENV_FLAG_ISOLATED: vm_frame_env_flags = 16; pub type vm_frame_env_flags = u32; -pub type attr_index_t = u8; -pub type shape_id_t = u32; -pub const SHAPE_ID_HAS_IVAR_MASK: shape_id_mask = 67633150; -pub type shape_id_mask = u32; #[repr(C)] pub struct rb_cvar_class_tbl_entry { pub imemo_flags: VALUE, @@ -1098,6 +1098,8 @@ extern "C" { elements: *const VALUE, opt: ::std::os::raw::c_int, ) -> VALUE; + pub fn rb_obj_info(obj: VALUE) -> *const ::std::os::raw::c_char; + pub fn rb_ec_stack_check(ec: *mut rb_execution_context_struct) -> ::std::os::raw::c_int; pub fn rb_ary_tmp_new_from_values( arg1: VALUE, arg2: ::std::os::raw::c_long, @@ -1108,6 +1110,14 @@ extern "C" { n: ::std::os::raw::c_long, elts: *const VALUE, ) -> VALUE; + pub fn rb_shape_id_offset() -> i32; + pub fn rb_obj_shape_id(obj: VALUE) -> shape_id_t; + pub fn rb_shape_get_iv_index(shape_id: shape_id_t, id: ID, value: *mut attr_index_t) -> bool; + pub fn rb_shape_transition_add_ivar_no_warnings( + shape_id: shape_id_t, + id: ID, + klass: VALUE, + ) -> shape_id_t; pub fn rb_vm_top_self() -> VALUE; pub static mut rb_vm_insn_count: u64; pub fn rb_method_entry_at(obj: VALUE, id: ID) -> *const rb_method_entry_t; @@ -1126,16 +1136,6 @@ extern "C" { pub fn rb_vm_frame_method_entry( cfp: *const rb_control_frame_t, ) -> *const rb_callable_method_entry_t; - pub fn rb_obj_info(obj: VALUE) -> *const ::std::os::raw::c_char; - pub fn rb_ec_stack_check(ec: *mut rb_execution_context_struct) -> ::std::os::raw::c_int; - pub fn rb_shape_id_offset() -> i32; - pub fn rb_obj_shape_id(obj: VALUE) -> shape_id_t; - pub fn rb_shape_get_iv_index(shape_id: shape_id_t, id: ID, value: *mut attr_index_t) -> bool; - pub fn rb_shape_transition_add_ivar_no_warnings( - shape_id: shape_id_t, - id: ID, - klass: VALUE, - ) -> shape_id_t; pub fn rb_ivar_get_at(obj: VALUE, index: attr_index_t, id: ID) -> VALUE; pub fn rb_ivar_get_at_no_ractor_check(obj: VALUE, index: attr_index_t) -> VALUE; pub fn rb_gvar_get(arg1: ID) -> VALUE; diff --git a/zjit/src/codegen_tests.rs b/zjit/src/codegen_tests.rs index 33f82548288901..d5d7d04d2ee11c 100644 --- a/zjit/src/codegen_tests.rs +++ b/zjit/src/codegen_tests.rs @@ -595,6 +595,110 @@ fn test_yield_iseq_guard_miss_recompiles() { assert_snapshot!(assert_compiles_allowing_exits("[invoke { |x| x + 1 }, invoke { |x| x * 2 }]"), @"[42, 82]"); } +#[test] +fn test_yield_polymorphic_blocks_dispatch_directly() { + // A yield site shared by two call sites recompiles with a polymorphic ISEQ dispatch + // chain after the monomorphic guard miss. Once the polymorphic version is installed, + // both blocks must dispatch directly with no side exits. + set_call_threshold(2); + eval(" + def invoke = yield(10) + def add_one = invoke { |x| x + 1 } + def double = invoke { |x| x * 2 } + add_one; double + add_one; double + "); + // Drive the re-profile window so the invalidated monomorphic version is replaced. + let num_profiles = get_option!(num_profiles); + for _ in 0..num_profiles + 2 { + eval("add_one; double"); + } + assert_snapshot!(assert_compiles("[add_one, double]"), @"[11, 20]"); +} + +#[test] +fn test_yield_polymorphic_non_iseq_handler_falls_back() { + // A proc handler at a polymorphic yield site fails the ISEQ tag check and takes the + // generic InvokeBlock fallback in-line, without a side exit or another recompile. + set_call_threshold(2); + eval(" + def invoke = yield(10) + def add_one = invoke { |x| x + 1 } + def double = invoke { |x| x * 2 } + def via_proc(l) = invoke(&l) + add_one; double + add_one; double + "); + let num_profiles = get_option!(num_profiles); + for _ in 0..num_profiles + 2 { + eval("add_one; double; via_proc(proc { |x| x * 3 })"); + } + assert_snapshot!(assert_compiles("[add_one, double, via_proc(proc { |x| x * 3 })]"), @"[11, 20, 30]"); +} + +#[test] +fn test_yield_polymorphic_symbol_handler_falls_back() { + // A symbol handler at a polymorphic yield site fails the ISEQ tag check and takes the + // generic InvokeBlock fallback in-line, without a side exit or another recompile. + set_call_threshold(2); + eval(" + def invoke = yield(10) + def add_one = invoke { |x| x + 1 } + def double = invoke { |x| x * 2 } + def via_sym = invoke(&:to_s) + add_one; double + add_one; double + "); + let num_profiles = get_option!(num_profiles); + for _ in 0..num_profiles + 2 { + eval("add_one; double; via_sym"); + } + assert_snapshot!(assert_compiles("[add_one, double, via_sym]"), @r#"[11, 20, "10"]"#); +} + +#[test] +fn test_yield_polymorphic_ifunc_handler_falls_back() { + // An ifunc handler (Enumerator#each yields to the enumerator's C block) at a polymorphic + // yield site fails the ISEQ tag check and takes the generic InvokeBlock fallback in-line. + // Threshold 4 keeps calls 1-3 in the profile window (num_profiles defaults to 5), so + // invoke's first compile already sees both blocks and installs the polymorphic dispatch; + // the standalone version matters here because the Enumerator calls invoke from C. + set_call_threshold(4); + eval(" + def invoke = yield(10) + def add_one = invoke { |x| x + 1 } + def double = invoke { |x| x * 2 } + def via_enum = to_enum(:invoke).to_a + add_one; double + add_one; double + "); + assert_snapshot!(assert_compiles("[add_one, double, via_enum]"), @"[11, 20, [10]]"); +} + +#[test] +fn test_yield_megamorphic_mixed_block_handlers() { + // A yield site that sees ISEQ, proc, symbol, and ifunc handlers mixed together goes + // megamorphic (each to_enum call profiles a distinct ifunc), so it compiles to the + // generic InvokeBlock and must return the right result for every handler kind. + set_call_threshold(2); + eval(" + def invoke = yield(10) + def add_one = invoke { |x| x + 1 } + def double = invoke { |x| x * 2 } + def via_proc(l) = invoke(&l) + def via_sym = invoke(&:to_s) + def via_enum = to_enum(:invoke).to_a + PR = proc { |x| x * 3 } + add_one; double + add_one; double + "); + let num_profiles = get_option!(num_profiles); + for _ in 0..num_profiles + 2 { + eval("add_one; double; via_proc(PR); via_sym; via_enum"); + } + assert_snapshot!(assert_compiles("[add_one, double, via_proc(PR), via_sym, via_enum]"), @r#"[11, 20, 30, "10", [10]]"#); +} + #[test] fn test_yield_inline_invocation_with_args() { // Plain yield with two args to a matching-arity block inlines and returns correctly. diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index 5bce8574e81e87..3e84f0d4eb5a7f 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -215,6 +215,7 @@ pub const ONIG_OPTION_EXTEND: u32 = 2; pub const ONIG_OPTION_MULTILINE: u32 = 4; pub const ARG_ENCODING_FIXED: u32 = 16; pub const ARG_ENCODING_NONE: u32 = 32; +pub const SHAPE_ID_NUM_BITS: u32 = 32; pub const INTEGER_REDEFINED_OP_FLAG: u32 = 1; pub const FLOAT_REDEFINED_OP_FLAG: u32 = 2; pub const STRING_REDEFINED_OP_FLAG: u32 = 4; @@ -233,7 +234,6 @@ pub const VM_ENV_DATA_INDEX_ME_CREF: i32 = -2; pub const VM_ENV_DATA_INDEX_SPECVAL: i32 = -1; pub const VM_ENV_DATA_INDEX_FLAGS: u32 = 0; pub const VM_BLOCK_HANDLER_NONE: u32 = 0; -pub const SHAPE_ID_NUM_BITS: u32 = 32; pub const ZJIT_STACK_MAP_VREG_TAG: u32 = 8; pub const ZJIT_STACK_MAP_SKIP_TAG: u32 = 16; pub const ZJIT_STACK_MAP_SHIFT: u32 = 8; @@ -403,6 +403,60 @@ pub const RUBY_ENCINDEX_EUC_JP: ruby_preserved_encindex = 10; pub const RUBY_ENCINDEX_Windows_31J: ruby_preserved_encindex = 11; pub const RUBY_ENCINDEX_BUILTIN_MAX: ruby_preserved_encindex = 12; pub type ruby_preserved_encindex = u32; +pub type rb_atomic_t = ::std::os::raw::c_uint; +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Copy, Clone)] +pub struct rb_id_table { + pub _bindgen_opaque_blob: [u64; 3usize], +} +pub const imemo_env: imemo_type = 0; +pub const imemo_cref: imemo_type = 1; +pub const imemo_svar: imemo_type = 2; +pub const imemo_throw_data: imemo_type = 3; +pub const imemo_ifunc: imemo_type = 4; +pub const imemo_memo: imemo_type = 5; +pub const imemo_ment: imemo_type = 6; +pub const imemo_iseq: imemo_type = 7; +pub const imemo_tmpbuf: imemo_type = 8; +pub const imemo_cvar_entry: imemo_type = 9; +pub const imemo_callinfo: imemo_type = 10; +pub const imemo_callcache: imemo_type = 11; +pub const imemo_constcache: imemo_type = 12; +pub const imemo_fields: imemo_type = 13; +pub const imemo_subclasses: imemo_type = 14; +pub const imemo_cdhash: imemo_type = 15; +pub type imemo_type = u32; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct vm_ifunc_argc { + pub min: ::std::os::raw::c_int, + pub max: ::std::os::raw::c_int, +} +#[repr(C)] +pub struct vm_ifunc { + pub flags: VALUE, + pub svar_lep: *mut VALUE, + pub func: rb_block_call_func_t, + pub data: *const ::std::os::raw::c_void, + pub argc: vm_ifunc_argc, +} +pub type attr_index_t = u8; +pub type shape_id_t = u32; +pub const SHAPE_ID_CAPACITY_MASK: shape_id_fl_type = 66584576; +pub const SHAPE_ID_FL_COMPLEX: shape_id_fl_type = 67108864; +pub const SHAPE_ID_FL_FROZEN: shape_id_fl_type = 134217728; +pub const SHAPE_ID_FL_HAS_OBJECT_ID: shape_id_fl_type = 268435456; +pub const SHAPE_ID_LAYOUT_ROBJECT: shape_id_fl_type = 0; +pub const SHAPE_ID_LAYOUT_RCLASS: shape_id_fl_type = 536870912; +pub const SHAPE_ID_LAYOUT_EXTENDED: shape_id_fl_type = 1073741824; +pub const SHAPE_ID_LAYOUT_RDATA: shape_id_fl_type = 1073741824; +pub const SHAPE_ID_LAYOUT_OTHER: shape_id_fl_type = 1610612736; +pub const SHAPE_ID_LAYOUT_MASK: shape_id_fl_type = 1610612736; +pub const SHAPE_ID_FL_NON_CANONICAL_MASK: shape_id_fl_type = 402653184; +pub const SHAPE_ID_FLAGS_MASK: shape_id_fl_type = 2146959360; +pub const SHAPE_ID_FL_PRIVATE_MASK: shape_id_fl_type = 1677197312; +pub type shape_id_fl_type = u32; pub const BOP_PLUS: ruby_basic_operators = 0; pub const BOP_MINUS: ruby_basic_operators = 1; pub const BOP_MULT: ruby_basic_operators = 2; @@ -442,44 +496,6 @@ pub const BOP_YIELD: ruby_basic_operators = 35; pub const BOP_LAST_: ruby_basic_operators = 36; pub type ruby_basic_operators = u32; pub type rb_serial_t = ::std::os::raw::c_ulonglong; -#[repr(C)] -#[repr(align(8))] -#[derive(Debug, Copy, Clone)] -pub struct rb_id_table { - pub _bindgen_opaque_blob: [u64; 3usize], -} -pub const imemo_env: imemo_type = 0; -pub const imemo_cref: imemo_type = 1; -pub const imemo_svar: imemo_type = 2; -pub const imemo_throw_data: imemo_type = 3; -pub const imemo_ifunc: imemo_type = 4; -pub const imemo_memo: imemo_type = 5; -pub const imemo_ment: imemo_type = 6; -pub const imemo_iseq: imemo_type = 7; -pub const imemo_tmpbuf: imemo_type = 8; -pub const imemo_cvar_entry: imemo_type = 9; -pub const imemo_callinfo: imemo_type = 10; -pub const imemo_callcache: imemo_type = 11; -pub const imemo_constcache: imemo_type = 12; -pub const imemo_fields: imemo_type = 13; -pub const imemo_subclasses: imemo_type = 14; -pub const imemo_cdhash: imemo_type = 15; -pub type imemo_type = u32; -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct vm_ifunc_argc { - pub min: ::std::os::raw::c_int, - pub max: ::std::os::raw::c_int, -} -#[repr(C)] -pub struct vm_ifunc { - pub flags: VALUE, - pub svar_lep: *mut VALUE, - pub func: rb_block_call_func_t, - pub data: *const ::std::os::raw::c_void, - pub argc: vm_ifunc_argc, -} -pub type rb_atomic_t = ::std::os::raw::c_uint; pub const METHOD_VISI_UNDEF: rb_method_visibility_t = 0; pub const METHOD_VISI_PUBLIC: rb_method_visibility_t = 1; pub const METHOD_VISI_PRIVATE: rb_method_visibility_t = 2; @@ -1541,22 +1557,6 @@ pub const VM_ENV_FLAG_ESCAPED: vm_frame_env_flags = 4; pub const VM_ENV_FLAG_WB_REQUIRED: vm_frame_env_flags = 8; pub const VM_ENV_FLAG_ISOLATED: vm_frame_env_flags = 16; pub type vm_frame_env_flags = u32; -pub type attr_index_t = u8; -pub type shape_id_t = u32; -pub const SHAPE_ID_CAPACITY_MASK: shape_id_fl_type = 66584576; -pub const SHAPE_ID_FL_COMPLEX: shape_id_fl_type = 67108864; -pub const SHAPE_ID_FL_FROZEN: shape_id_fl_type = 134217728; -pub const SHAPE_ID_FL_HAS_OBJECT_ID: shape_id_fl_type = 268435456; -pub const SHAPE_ID_LAYOUT_ROBJECT: shape_id_fl_type = 0; -pub const SHAPE_ID_LAYOUT_RCLASS: shape_id_fl_type = 536870912; -pub const SHAPE_ID_LAYOUT_EXTENDED: shape_id_fl_type = 1073741824; -pub const SHAPE_ID_LAYOUT_RDATA: shape_id_fl_type = 1073741824; -pub const SHAPE_ID_LAYOUT_OTHER: shape_id_fl_type = 1610612736; -pub const SHAPE_ID_LAYOUT_MASK: shape_id_fl_type = 1610612736; -pub const SHAPE_ID_FL_NON_CANONICAL_MASK: shape_id_fl_type = 402653184; -pub const SHAPE_ID_FLAGS_MASK: shape_id_fl_type = 2146959360; -pub const SHAPE_ID_FL_PRIVATE_MASK: shape_id_fl_type = 1677197312; -pub type shape_id_fl_type = u32; pub const CONST_DEPRECATED: rb_const_flag_t = 256; pub const CONST_VISIBILITY_MASK: rb_const_flag_t = 255; pub const CONST_PUBLIC: rb_const_flag_t = 0; @@ -2176,6 +2176,19 @@ unsafe extern "C" { elements: *const VALUE, opt: ::std::os::raw::c_int, ) -> VALUE; + pub fn rb_obj_info(obj: VALUE) -> *const ::std::os::raw::c_char; + pub fn rb_raw_obj_info( + buff: *mut ::std::os::raw::c_char, + buff_size: usize, + obj: VALUE, + ) -> *const ::std::os::raw::c_char; + pub fn rb_ec_stack_check(ec: *mut rb_execution_context_struct) -> ::std::os::raw::c_int; + pub fn rb_gc_writebarrier_remember(obj: VALUE); + pub fn rb_id_table_lookup( + tbl: *mut rb_id_table, + id: ID, + valp: *mut VALUE, + ) -> ::std::os::raw::c_int; pub fn rb_ary_tmp_new_from_values( arg1: VALUE, arg2: ::std::os::raw::c_long, @@ -2186,13 +2199,16 @@ unsafe extern "C" { n: ::std::os::raw::c_long, elts: *const VALUE, ) -> VALUE; + pub fn rb_shape_id_offset() -> i32; + pub fn rb_obj_shape_id(obj: VALUE) -> shape_id_t; + pub fn rb_shape_get_iv_index(shape_id: shape_id_t, id: ID, value: *mut attr_index_t) -> bool; + pub fn rb_shape_transition_add_ivar_no_warnings( + shape_id: shape_id_t, + id: ID, + klass: VALUE, + ) -> shape_id_t; pub fn rb_vm_top_self() -> VALUE; pub static mut rb_vm_insn_count: u64; - pub fn rb_id_table_lookup( - tbl: *mut rb_id_table, - id: ID, - valp: *mut VALUE, - ) -> ::std::os::raw::c_int; pub fn rb_method_entry_at(obj: VALUE, id: ID) -> *const rb_method_entry_t; pub fn rb_callable_method_entry(klass: VALUE, id: ID) -> *const rb_callable_method_entry_t; pub fn rb_callable_method_entry_or_negative( @@ -2210,22 +2226,6 @@ unsafe extern "C" { pub fn rb_vm_frame_method_entry( cfp: *const rb_control_frame_t, ) -> *const rb_callable_method_entry_t; - pub fn rb_obj_info(obj: VALUE) -> *const ::std::os::raw::c_char; - pub fn rb_raw_obj_info( - buff: *mut ::std::os::raw::c_char, - buff_size: usize, - obj: VALUE, - ) -> *const ::std::os::raw::c_char; - pub fn rb_ec_stack_check(ec: *mut rb_execution_context_struct) -> ::std::os::raw::c_int; - pub fn rb_gc_writebarrier_remember(obj: VALUE); - pub fn rb_shape_id_offset() -> i32; - pub fn rb_obj_shape_id(obj: VALUE) -> shape_id_t; - pub fn rb_shape_get_iv_index(shape_id: shape_id_t, id: ID, value: *mut attr_index_t) -> bool; - pub fn rb_shape_transition_add_ivar_no_warnings( - shape_id: shape_id_t, - id: ID, - klass: VALUE, - ) -> shape_id_t; pub fn rb_const_lookup(klass: VALUE, id: ID) -> *mut rb_const_entry_t; pub fn rb_ivar_get_at_no_ractor_check(obj: VALUE, index: attr_index_t) -> VALUE; pub fn rb_gvar_get(arg1: ID) -> VALUE; diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index ac5cda92de1fdb..56ac49e6f6fad3 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -779,6 +779,9 @@ pub enum SendFallbackReason { SuperTargetComplexArgsPass, /// The `invokeblock` instruction is not yet optimized in `type_specialize`. InvokeBlockNotSpecialized, + /// The runtime block handler at a polymorphic `invokeblock` site did not match any + /// profiled ISEQ candidate, so the site dispatched through the generic fallback. + InvokeBlockPolymorphicMiss, /// The `sendforward` instruction (argument forwarding `...`) is not yet optimized in /// `type_specialize`. SendForwardNotSpecialized, @@ -830,6 +833,7 @@ impl Display for SendFallbackReason { SuperTargetNotFound => write!(f, "super: profiled target method cannot be found"), SuperTargetComplexArgsPass => write!(f, "super: complex argument passing to `super` target call"), InvokeBlockNotSpecialized => write!(f, "InvokeBlock: not yet specialized"), + InvokeBlockPolymorphicMiss => write!(f, "InvokeBlock: polymorphic dispatch miss"), SendForwardNotSpecialized => write!(f, "SendForward: not yet specialized"), InvokeSuperForwardNotSpecialized => write!(f, "InvokeSuperForward: not yet specialized"), SingleRactorModeRequired => write!(f, "Single-ractor mode required"), @@ -2998,6 +3002,12 @@ impl Function { self.load_field(block, str, FieldName::len, RUBY_OFFSET_RSTRING_LEN, types::CInt64) } + /// Load `captured->code.iseq` from a `struct rb_captured_block *`. + fn load_captured_code_iseq(&mut self, block: BlockId, captured: InsnId) -> InsnId { + let offset: i32 = std::mem::offset_of!(rb_captured_block, code).try_into().unwrap(); + self.load_field(block, captured, FieldName::code_iseq, offset, types::CPtr) + } + /// Emit the fast-path `yield` dispatch to a known ISEQ block. /// When `guarded`, the block handler is read from the runtime LEP and guarded (tag + iseq /// identity) because the profiled block can differ per caller. When the enclosing method is @@ -3021,7 +3031,7 @@ impl Function { if guarded { // Guard captured->code.iseq is the comptime block iseq. Compare the raw imemo pointer: // type inference (from_value) can't type an iseq imemo, so guard it as a CPtr identity. - let captured_iseq = self.load_field(block, captured, FieldName::code_iseq, 2 * SIZEOF_VALUE_I32, types::CPtr); + let captured_iseq = self.load_captured_code_iseq(block, captured); self.push_insn(block, Insn::GuardBitEquals { val: captured_iseq, expected: Const::CPtr(block_iseq as *const u8), reason: Box::new(SideExitReason::InvokeBlockIseqChanged), state, recompile: Some(Recompile) }); } @@ -9510,12 +9520,17 @@ fn add_iseq_to_hir( } let args = state.stack_pop_n(crate::profile::num_arguments_on_stack(cd))?; - // The monomorphic block handler class the profile recorded, if any. Both the - // IFUNC and inline-ISEQ specializations below key off this single distribution. - let block_handler_types = payload.profile.get_operand_types(exit_state.insn_idx); - let block_handler_class = block_handler_types.and_then(|types| { - if types.len() != 1 { return None; } - let summary = TypeDistributionSummary::new(&types[0]); + // The profiled block handler distribution. All the specializations below + // (IFUNC, inline-ISEQ, and polymorphic ISEQ dispatch) key off this summary. + let block_handler_summary = payload.profile.get_operand_types(exit_state.insn_idx).and_then(|types| { + if let [block_handler_distribution] = types { + Some(TypeDistributionSummary::new(block_handler_distribution)) + } else { + None + } + }); + // The monomorphic block handler class the profile recorded, if any. + let block_handler_class = block_handler_summary.as_ref().and_then(|summary| { if !summary.is_monomorphic() { return None; } Some(summary.bucket(0).class()) }); @@ -9539,6 +9554,29 @@ fn add_iseq_to_hir( }) } else { None }; + // For polymorphic yield sites, collect the profiled ISEQ blocks that can + // dispatch directly. Iterators like Integer#times are typically called with a + // different block per call site, so requiring a monomorphic profile would + // leave every such shared yield site on the generic fallback. Buckets are + // ordered by frequency, so the hottest block is compared first below. + let mut polymorphic_iseqs: Vec = vec![]; + if let Some(summary) = block_handler_summary.as_ref() { + if block_call_inlinable(flags) && (summary.is_polymorphic() || summary.is_skewed_polymorphic()) { + for &profiled_type in summary.buckets() { + if profiled_type.is_empty() { + break; + } + let obj = profiled_type.class(); + if unsafe { rb_IMEMO_TYPE_P(obj, imemo_iseq) == 1 } { + let iseq = obj.as_iseq(); + if !polymorphic_iseqs.contains(&iseq) && block_call_inlinable_iseq(iseq, args.len()).is_ok() { + polymorphic_iseqs.push(iseq); + } + } + } + } + } + let inlined_known_block = if let AddIseqMode::Inlined { blockiseq: Some(bi), .. } = mode { if block_call_inlinable(flags) // Only methods are inlined today, so exit_state.iseq is always a method iseq and this is @@ -9563,6 +9601,61 @@ fn add_iseq_to_hir( } else if let Some(block_iseq) = inline_iseq { let level = get_lvar_level(exit_state.iseq); fun.push_invoke_block_iseq_direct(block, block_iseq, level, args, exit_id, true) + } else if !polymorphic_iseqs.is_empty() { + // Dispatch on the runtime block ISEQ over the profiled candidates, joining + // on the generic fallback for anything else. Unlike the monomorphic path + // above, a miss must not side-exit: the site is known to see multiple + // blocks, so a guard would keep failing and recompiling. + let level = get_lvar_level(exit_state.iseq); + let ep = fun.get_ep(block, level); + let block_handler = fun.load_ep_env_field(block, ep, FieldName::VM_ENV_DATA_INDEX_SPECVAL, VM_ENV_DATA_INDEX_SPECVAL, types::CInt64); + + let join_block = fun.new_block(insn_idx); + let join_param = fun.push_insn(join_block, Insn::Param); + let dispatch_block = fun.new_block(insn_idx); + let fallback_block = fun.new_block(insn_idx); + + // The handler must be an ISEQ block: VM_BH_ISEQ_BLOCK_P is `& 0x3 == 0x1`. + let tag_mask = fun.push_insn(block, Insn::Const { val: Const::CInt64(0x3) }); + let tag = fun.push_insn(block, Insn::IntAnd { left: block_handler, right: tag_mask }); + let iseq_tag = fun.push_insn(block, Insn::Const { val: Const::CInt64(0x1) }); + let tag_matches = fun.push_insn(block, Insn::IsBitEqual { left: tag, right: iseq_tag }); + fun.push_insn(block, Insn::CondBranch { + val: tag_matches, + if_true: BranchEdge { target: dispatch_block, args: vec![] }, + if_false: BranchEdge { target: fallback_block, args: vec![] }, + }); + + // captured = block_handler & ~0x3 (struct rb_captured_block *) + let untag_mask = fun.push_insn(dispatch_block, Insn::Const { val: Const::CInt64(!0x3) }); + let captured = fun.push_insn(dispatch_block, Insn::IntAnd { left: block_handler, right: untag_mask }); + let captured_iseq = fun.load_captured_code_iseq(dispatch_block, captured); + + let mut compare_block = dispatch_block; + for &block_iseq in &polymorphic_iseqs { + let expected = fun.push_insn(compare_block, Insn::Const { val: Const::CPtr(block_iseq as *const u8) }); + let iseq_matches = fun.push_insn(compare_block, Insn::IsBitEqual { left: captured_iseq, right: expected }); + let direct_block = fun.new_block(insn_idx); + let miss_block = fun.new_block(insn_idx); + fun.push_insn(compare_block, Insn::CondBranch { + val: iseq_matches, + if_true: BranchEdge { target: direct_block, args: vec![] }, + if_false: BranchEdge { target: miss_block, args: vec![] }, + }); + let direct_result = fun.push_insn(direct_block, Insn::InvokeBlockIseqDirect { iseq: block_iseq, captured, args: args.clone(), state: exit_id }); + fun.push_insn(direct_block, Insn::Jump(BranchEdge { target: join_block, args: vec![direct_result] })); + compare_block = miss_block; + } + fun.push_insn(compare_block, Insn::Jump(BranchEdge { target: fallback_block, args: vec![] })); + + let fallback_result = fun.push_insn(fallback_block, Insn::InvokeBlock { + cd, args, state: exit_id, reason: InvokeBlockPolymorphicMiss, + }); + fun.push_insn(fallback_block, Insn::Jump(BranchEdge { target: join_block, args: vec![fallback_result] })); + + // Continue compilation from the join block + block = join_block; + join_param } else if is_ifunc { // Load the block handler from the current frame's LEP. In inlined // code, the function ISEQ is the caller while `exit_state.iseq` is the diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 2cf29df1c7b3b1..471a66d02d2867 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -4053,6 +4053,198 @@ mod hir_opt_tests { "); } + #[test] + fn test_yield_polymorphic_blocks_dispatch_directly() { + // A yield site shared by multiple call sites (like Integer#times) profiles as + // polymorphic. Instead of falling back to the generic InvokeBlock, dispatch on + // the runtime block ISEQ over the profiled candidates, with the generic call + // only as the in-line miss path (no side exit). + let result = eval(" + def invoke = yield(10) + def add_one = invoke { |x| x + 1 } + def double = invoke { |x| x * 2 } + add_one; double + add_one; double + add_one; double + add_one + double + "); + assert_eq!(VALUE::fixnum_from_usize(31), result); + assert_snapshot!(hir_string("invoke"), @" + fn invoke@:2: + bb1(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb3(v1) + bb2(): + EntryPoint JIT(0) + v4:BasicObject = LoadArg :self@0 + Jump bb3(v4) + bb3(v6:BasicObject): + v10:Fixnum[10] = Const Value(10) + v12:CPtr = GetEP 0 + v13:CInt64 = LoadField v12, :VM_ENV_DATA_INDEX_SPECVAL@0x1000 + v15:CInt64[3] = Const CInt64(3) + v16:CInt64 = IntAnd v13, v15 + v17:CInt64[1] = Const CInt64(1) + v18:CBool = IsBitEqual v16, v17 + CondBranch v18, bb5(), bb6() + bb5(): + v20:CInt64[-4] = Const CInt64(-4) + v21:CInt64 = IntAnd v13, v20 + v22:CPtr = LoadField v21, :code_iseq@0x1001 + v23:CPtr[CPtr(0x1002)] = Const CPtr(0x1002) + v24:CBool = IsBitEqual v22, v23 + CondBranch v24, bb7(), bb8() + bb7(): + v26:BasicObject = InvokeBlockIseqDirect (0x1002), v21, v10 + Jump bb4(v26) + bb8(): + v28:CPtr[CPtr(0x1003)] = Const CPtr(0x1003) + v29:CBool = IsBitEqual v22, v28 + CondBranch v29, bb9(), bb10() + bb9(): + v31:BasicObject = InvokeBlockIseqDirect (0x1003), v21, v10 + Jump bb4(v31) + bb10(): + Jump bb6() + bb6(): + v34:BasicObject = InvokeBlock v10 # SendFallbackReason: InvokeBlock: polymorphic dispatch miss + Jump bb4(v34) + bb4(v14:BasicObject): + CheckInterrupts + Return v14 + "); + } + + #[test] + fn test_yield_mixed_iseq_proc_symbol_profile_dispatches_on_iseqs() { + // A yield site whose profile mixes ISEQ blocks with proc and symbol handlers still + // dispatches directly on the ISEQ candidates; the non-ISEQ handlers fail the tag + // check and take the generic fallback in-line. + let result = eval(" + def invoke = yield(10) + def add_one = invoke { |x| x + 1 } + def double = invoke { |x| x * 2 } + def via_proc(l) = invoke(&l) + def via_sym = invoke(&:itself) + pr = proc { |x| x * 3 } + add_one; double; via_proc(pr); via_sym + add_one; double; via_proc(pr); via_sym + add_one; double; via_proc(pr); via_sym + add_one + double + via_proc(pr) + via_sym + "); + assert_eq!(VALUE::fixnum_from_usize(71), result); + assert_snapshot!(hir_string("invoke"), @" + fn invoke@:2: + bb1(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb3(v1) + bb2(): + EntryPoint JIT(0) + v4:BasicObject = LoadArg :self@0 + Jump bb3(v4) + bb3(v6:BasicObject): + v10:Fixnum[10] = Const Value(10) + v12:CPtr = GetEP 0 + v13:CInt64 = LoadField v12, :VM_ENV_DATA_INDEX_SPECVAL@0x1000 + v15:CInt64[3] = Const CInt64(3) + v16:CInt64 = IntAnd v13, v15 + v17:CInt64[1] = Const CInt64(1) + v18:CBool = IsBitEqual v16, v17 + CondBranch v18, bb5(), bb6() + bb5(): + v20:CInt64[-4] = Const CInt64(-4) + v21:CInt64 = IntAnd v13, v20 + v22:CPtr = LoadField v21, :code_iseq@0x1001 + v23:CPtr[CPtr(0x1002)] = Const CPtr(0x1002) + v24:CBool = IsBitEqual v22, v23 + CondBranch v24, bb7(), bb8() + bb7(): + v26:BasicObject = InvokeBlockIseqDirect (0x1002), v21, v10 + Jump bb4(v26) + bb8(): + v28:CPtr[CPtr(0x1003)] = Const CPtr(0x1003) + v29:CBool = IsBitEqual v22, v28 + CondBranch v29, bb9(), bb10() + bb9(): + v31:BasicObject = InvokeBlockIseqDirect (0x1003), v21, v10 + Jump bb4(v31) + bb10(): + Jump bb6() + bb6(): + v34:BasicObject = InvokeBlock v10 # SendFallbackReason: InvokeBlock: polymorphic dispatch miss + Jump bb4(v34) + bb4(v14:BasicObject): + CheckInterrupts + Return v14 + "); + } + + #[test] + fn test_yield_mixed_iseq_ifunc_profile_dispatches_on_iseqs() { + // Like the above, but the non-ISEQ handler in the profile is an ifunc block + // (Enumerator#each yields to the enumerator's C block). The ifunc handler fails + // the ISEQ tag check and takes the generic fallback in-line. + let result = eval(" + def invoke = yield(10) + def add_one = invoke { |x| x + 1 } + def double = invoke { |x| x * 2 } + def via_enum = to_enum(:invoke).to_a + add_one; double + add_one; double + add_one; double + via_enum + add_one + double + "); + assert_eq!(VALUE::fixnum_from_usize(31), result); + assert_snapshot!(hir_string("invoke"), @" + fn invoke@:2: + bb1(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb3(v1) + bb2(): + EntryPoint JIT(0) + v4:BasicObject = LoadArg :self@0 + Jump bb3(v4) + bb3(v6:BasicObject): + v10:Fixnum[10] = Const Value(10) + v12:CPtr = GetEP 0 + v13:CInt64 = LoadField v12, :VM_ENV_DATA_INDEX_SPECVAL@0x1000 + v15:CInt64[3] = Const CInt64(3) + v16:CInt64 = IntAnd v13, v15 + v17:CInt64[1] = Const CInt64(1) + v18:CBool = IsBitEqual v16, v17 + CondBranch v18, bb5(), bb6() + bb5(): + v20:CInt64[-4] = Const CInt64(-4) + v21:CInt64 = IntAnd v13, v20 + v22:CPtr = LoadField v21, :code_iseq@0x1001 + v23:CPtr[CPtr(0x1002)] = Const CPtr(0x1002) + v24:CBool = IsBitEqual v22, v23 + CondBranch v24, bb7(), bb8() + bb7(): + v26:BasicObject = InvokeBlockIseqDirect (0x1002), v21, v10 + Jump bb4(v26) + bb8(): + v28:CPtr[CPtr(0x1003)] = Const CPtr(0x1003) + v29:CBool = IsBitEqual v22, v28 + CondBranch v29, bb9(), bb10() + bb9(): + v31:BasicObject = InvokeBlockIseqDirect (0x1003), v21, v10 + Jump bb4(v31) + bb10(): + Jump bb6() + bb6(): + v34:BasicObject = InvokeBlock v10 # SendFallbackReason: InvokeBlock: polymorphic dispatch miss + Jump bb4(v34) + bb4(v14:BasicObject): + CheckInterrupts + Return v14 + "); + } + #[test] fn reload_local_across_send() { eval(" diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 8347246f2b77af..4323545bd9111b 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -293,6 +293,7 @@ make_counters! { send_fallback_super_target_complex_args_pass, send_fallback_cannot_send_direct, send_fallback_invokeblock_not_specialized, + send_fallback_invokeblock_polymorphic_miss, send_fallback_sendforward_not_specialized, send_fallback_invokesuperforward_not_specialized, send_fallback_single_ractor_mode_required, @@ -705,6 +706,7 @@ pub fn send_fallback_counter(reason: crate::hir::SendFallbackReason) -> Counter SuperTargetNotFound => send_fallback_super_target_not_found, SuperTargetComplexArgsPass => send_fallback_super_target_complex_args_pass, InvokeBlockNotSpecialized => send_fallback_invokeblock_not_specialized, + InvokeBlockPolymorphicMiss => send_fallback_invokeblock_polymorphic_miss, SendForwardNotSpecialized => send_fallback_sendforward_not_specialized, InvokeSuperForwardNotSpecialized => send_fallback_invokesuperforward_not_specialized, SingleRactorModeRequired => send_fallback_single_ractor_mode_required,