diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 191e3a8988bac8..ce81e15ed8a164 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -158,6 +158,7 @@ jobs: env: RUBY_TESTOPTS: '-q --tty=no' TEST_BUNDLED_GEMS_ALLOW_FAILURES: '' + RUBY_DEBUG_TEST_NO_REMOTE: "1" PRECHECK_BUNDLED_GEMS: 'no' LAUNCHABLE_STDOUT: ${{ steps.launchable.outputs.stdout_report_path }} LAUNCHABLE_STDERR: ${{ steps.launchable.outputs.stderr_report_path }} diff --git a/NEWS.md b/NEWS.md index 26d5157c4aa535..56351a5c2cbcfd 100644 --- a/NEWS.md +++ b/NEWS.md @@ -29,6 +29,9 @@ Note: We're only listing outstanding class updates. * `Array#pack` accepts new formats `R` and `r` for unsigned and signed LEB128 encoded integers. [[Feature #21785]] + * `Array#pack` accepts new formats `x!` and `@!` to align the current + offset to a byte boundary or to the ABI alignment of another + directive. [[Feature #22185]] * ENV @@ -61,6 +64,11 @@ Note: We're only listing outstanding class updates. ineffect inside its body, without affecting the original Proc. [[Feature #22097]] +* Range + + * `Range#clamp` is added. It returns a new `Range` instance whose + begin and end values are clamped to the given bounds. [[Feature #22175]] + * Regexp * All instances of `Regexp` are now frozen, not just literals. @@ -79,6 +87,9 @@ Note: We're only listing outstanding class updates. * `String#unpack` and `String#unpack1` accept a new format `^` that returns the current offset. Useful when combined with variable width formats like LEB128. [[Feature #21796]] + * `String#unpack` and `String#unpack1` accept new formats `x!` and + `@!` to align the current offset to a byte boundary or to the ABI + alignment of another directive. [[Feature #22185]] * Symbol @@ -239,6 +250,8 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [Feature #21981]: https://bugs.ruby-lang.org/issues/21981 [Feature #22137]: https://bugs.ruby-lang.org/issues/22137 [Feature #22139]: https://bugs.ruby-lang.org/issues/22139 +[Feature #22175]: https://bugs.ruby-lang.org/issues/22175 +[Feature #22185]: https://bugs.ruby-lang.org/issues/22185 [PR #17201]: https://github.com/ruby/ruby/pull/17201 [RubyGems-v4.0.4]: https://github.com/rubygems/rubygems/releases/tag/v4.0.4 [RubyGems-v4.0.5]: https://github.com/rubygems/rubygems/releases/tag/v4.0.5 diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb index f8c3573e1f24b5..a288cd1149787d 100644 --- a/bootstraptest/test_ractor.rb +++ b/bootstraptest/test_ractor.rb @@ -656,6 +656,29 @@ def check obj1 end } +# move preserves aliasing inside the moved object graph +assert_equal 'true', %q{ + r = Ractor.new do + Ractor.receive + end + + leaf = +"leaf" + moved = r.send([leaf, leaf], move: true).value + moved[0].equal?(moved[1]) +} + +# move handles cyclic references safely and preserves aliasing +assert_equal 'true', %q{ + r = Ractor.new do + Ractor.receive + end + + a = [] + a << a + moved = r.send(a, move: true).value + moved.equal?(moved[0]) +} + # unshareable frozen objects should still be frozen in new ractor after move assert_equal 'true', %q{ r = Ractor.new do @@ -2668,3 +2691,27 @@ def foo(a:, b:, c:) = super(a: a, b: b, c: c) :ok # platform without fork end } + +# A moved object's source is neutralized into a RactorMovedObject husk that +# stays in its original (possibly larger) slot. It must be given a shape whose +# slot size matches that slot, or a later compaction in the receiver trips the +# slot_size == shape_slot_size invariant (RGENGC_CHECK_MODE) / corrupts the slot. +assert_equal 'ok', %q{ + r = Ractor.new do + while (o = Ractor.receive) + begin + GC.compact + rescue NotImplementedError + # no-op on platforms without GC.compact (e.g. MMTk) + end + end + :ok + end + 500.times do + o = Object.new + 12.times { |i| o.instance_variable_set("@i#{i}", i) } # overflow to a larger slot + r.send(o, move: true) + end + r.send(nil) + r.value +} diff --git a/cygwin/GNUmakefile.in b/cygwin/GNUmakefile.in index 109baa747d9dc9..dd13b5decdc050 100644 --- a/cygwin/GNUmakefile.in +++ b/cygwin/GNUmakefile.in @@ -16,7 +16,6 @@ ifeq ($(target_os),cygwin) DLL_BASE_NAME := $(LIBRUBY_SO:.dll=) else DLL_BASE_NAME := $(RUBY_SO_NAME) - DLLWRAP += -mno-cygwin VPATH := $(VPATH):$(srcdir)/win32 ifneq ($(filter -flto%,$(LDFLAGS)),) miniruby$(EXEEXT): XLDFLAGS += -Wno-maybe-uninitialized diff --git a/debug.c b/debug.c index 730f860e7af988..0ced0df9c7341c 100644 --- a/debug.c +++ b/debug.c @@ -52,7 +52,6 @@ const union { enum ruby_econv_flag_type econv_flag_types; rb_econv_result_t econv_result; enum ruby_preserved_encindex encoding_index; - enum ruby_robject_flags robject_flags; enum ruby_rmodule_flags rmodule_flags; enum ruby_rstring_flags rstring_flags; enum ruby_rarray_flags rarray_flags; diff --git a/doc/language/packed_data.md b/doc/language/packed_data.md index 1b133367d6845d..ce4b85ba0c425f 100644 --- a/doc/language/packed_data.md +++ b/doc/language/packed_data.md @@ -879,6 +879,32 @@ for one byte in the input or output string. "\x00\x00\x02".unpack("x4C") # Raises ArgumentError ``` +- `'x!'` - Align to the given byte boundary or directive alignment; + for packing, null fill if necessary. With `buffer:` or `offset:`, + alignment is relative to the starting position: + + ```ruby + [1, 2].pack("C x!4 C") # => "\x01\x00\x00\x00\x02" + [1, 2].pack("C x!i i") # Aligns to the native int alignment. + ``` + + For unpacking, skip forward to the aligned offset: + + ```ruby + "\x01\x00\x00\x00\x02".unpack("C x!4 C") # => [1, 2] + ``` + +- `'@!'` - Align to the given byte boundary or directive alignment + from the beginning of the output or input string: + + ```ruby + buffer = +"z" + [1, 2].pack("C @!4 C", buffer: buffer) + buffer # => "z\x01\x00\x00\x02" + + "z\x01\x00\x00\x02".unpack("C @!4 C", offset: 1) # => [1, 2] + ``` + - `'^'` - Only for unpacking; the current position: ```ruby diff --git a/ext/objspace/objspace_dump.c b/ext/objspace/objspace_dump.c index 68ada01af3e514..e7778d08dd6214 100644 --- a/ext/objspace/objspace_dump.c +++ b/ext/objspace/objspace_dump.c @@ -591,17 +591,23 @@ dump_object(VALUE obj, struct dump_config *dc) dump_append(dc, "\""); break; - case T_OBJECT: - if (!FL_TEST_RAW(obj, ROBJECT_HEAP)) { + case T_OBJECT: { + shape_id_t shape_id = RBASIC_SHAPE_ID(obj); + + if (rb_shape_embedded_p(shape_id)) { dump_append(dc, ", \"embedded\":true"); } dump_append(dc, ", \"ivars\":"); - dump_append_lu(dc, ROBJECT_FIELDS_COUNT(obj)); - if (rb_obj_shape_complex_p(obj)) { + if (rb_shape_complex_p(shape_id)) { + dump_append_lu(dc, rb_st_table_size(rb_imemo_fields_complex_tbl(ROBJECT_FIELDS_OBJ(obj)))); dump_append(dc, ", \"complex_shape\":true"); } + else { + dump_append_lu(dc, RSHAPE_LEN(shape_id)); + } break; + } case T_FILE: fptr = RFILE(obj)->fptr; diff --git a/gc.c b/gc.c index ef193dc8efacf3..e03d1362b84eb2 100644 --- a/gc.c +++ b/gc.c @@ -1137,8 +1137,11 @@ rb_class_allocate_instance(VALUE klass) #if RUBY_DEBUG VALUE *ptr = ROBJECT_FIELDS(obj); - size_t fields_count = RSHAPE_LEN(RBASIC_SHAPE_ID(obj)); - for (size_t i = fields_count; i < ROBJECT_FIELDS_CAPACITY(obj); i++) { + shape_id_t shape_id = RBASIC_SHAPE_ID(obj); + attr_index_t fields_count = RSHAPE_LEN(shape_id); + attr_index_t capacity = RSHAPE_CAPACITY(shape_id); + + for (attr_index_t i = fields_count; i < capacity; i++) { ptr[i] = Qundef; } #endif @@ -1404,22 +1407,16 @@ rb_gc_obj_needs_cleanup_p(VALUE obj) if (flags & FL_FINALIZE) return true; - switch (flags & RUBY_T_MASK) { - case T_IMEMO: + if ((flags & RUBY_T_MASK) == T_IMEMO) { return rb_gc_imemo_needs_cleanup_p(obj); + } - case T_DATA: - case T_OBJECT: - case T_STRING: - case T_ARRAY: - case T_HASH: - case T_BIGNUM: - case T_STRUCT: + switch (flags & RUBY_T_MASK) { case T_FLOAT: case T_RATIONAL: case T_COMPLEX: - case T_MATCH: - break; + case T_OBJECT: + return false; case T_FILE: case T_SYMBOL: @@ -1428,14 +1425,9 @@ rb_gc_obj_needs_cleanup_p(VALUE obj) case T_MODULE: case T_REGEXP: return true; - } - - shape_id_t shape_id = RBASIC_SHAPE_ID(obj); - switch (flags & RUBY_T_MASK) { - case T_OBJECT: - if (flags & ROBJECT_HEAP) return true; - return false; + case T_IMEMO: + UNREACHABLE_RETURN(true); case T_DATA: { @@ -1450,38 +1442,25 @@ rb_gc_obj_needs_cleanup_p(VALUE obj) return true; case T_STRING: - if (flags & (RSTRING_NOEMBED | RSTRING_FSTR)) return true; - return rb_shape_has_fields(shape_id); + return (flags & (RSTRING_NOEMBED | RSTRING_FSTR)); case T_ARRAY: - if (!(flags & RARRAY_EMBED_FLAG)) return true; - return rb_shape_has_fields(shape_id); + return !(flags & RARRAY_EMBED_FLAG); case T_HASH: - if (flags & RHASH_ST_TABLE_FLAG) return true; - return rb_shape_has_fields(shape_id); + return !(flags & RHASH_ST_TABLE_FLAG); case T_MATCH: - if ((flags & (RMATCH_ONIG | RMATCH_OFFSETS_EXTERNAL)) || USE_DEBUG_COUNTER) return true; - return rb_shape_has_fields(shape_id); + return !((flags & (RMATCH_ONIG | RMATCH_OFFSETS_EXTERNAL)) || USE_DEBUG_COUNTER); case T_BIGNUM: - if (!(flags & BIGNUM_EMBED_FLAG)) return true; - return rb_shape_has_fields(shape_id); + return !(flags & BIGNUM_EMBED_FLAG); case T_STRUCT: - if (!(flags & RSTRUCT_EMBED_LEN_MASK)) return true; - if (flags & RSTRUCT_GEN_FIELDS) return rb_shape_has_fields(shape_id); - return false; - - case T_FLOAT: - case T_RATIONAL: - case T_COMPLEX: - return rb_shape_has_fields(shape_id); - - default: - UNREACHABLE_RETURN(true); + return !(flags & RSTRUCT_EMBED_LEN_MASK); } + + UNREACHABLE_RETURN(true); } static void @@ -2178,10 +2157,6 @@ rb_gc_obj_free_vm_weak_references(VALUE obj) { ASSUME(!RB_SPECIAL_CONST_P(obj)); - if (rb_obj_gen_fields_p(obj)) { - rb_free_generic_ivar(obj); - } - switch (BUILTIN_TYPE(obj)) { case T_STRING: if (FL_TEST_RAW(obj, RSTRING_FSTR)) { @@ -3300,20 +3275,20 @@ rb_gc_mark_children(void *objspace, VALUE obj) } case T_OBJECT: { - uint32_t len; - if (FL_TEST_RAW(obj, ROBJECT_HEAP)) { - if (!rb_gc_checking_shareable()) { - gc_mark_internal(ROBJECT(obj)->as.extended); - } - } - else { + shape_id_t shape_id = RBASIC_SHAPE_ID(obj); + if (rb_shape_embedded_p(shape_id)) { + uint32_t len = RSHAPE_LEN(shape_id); const VALUE * const ptr = ROBJECT(obj)->as.ary; - len = ROBJECT_FIELDS_COUNT_NOT_COMPLEX(obj); for (uint32_t i = 0; i < len; i++) { gc_mark_internal(ptr[i]); } } + else { + if (!rb_gc_checking_shareable()) { + gc_mark_internal(ROBJECT(obj)->as.extended); + } + } break; } @@ -3397,7 +3372,7 @@ rb_gc_obj_optimal_size(VALUE obj) return sizeof(struct RObject); } else { - size_t size = rb_obj_embedded_size(ROBJECT_FIELDS_CAPACITY(obj)); + size_t size = rb_obj_embedded_size(RSHAPE_CAPACITY(RBASIC_SHAPE_ID(obj))); if (rb_gc_size_allocatable_p(size)) { return size; } @@ -3680,15 +3655,15 @@ gc_ref_update_object(void *objspace, VALUE v) RUBY_ASSERT(rb_gc_obj_slot_size(v) == rb_obj_shape_slot_size(v)); shape_id_t shape_id = RBASIC_SHAPE_ID(v); - if (FL_TEST_RAW(v, ROBJECT_HEAP)) { + if (!rb_shape_embedded_p(shape_id)) { UPDATE_IF_MOVED(objspace, ROBJECT(v)->as.extended); if (!rb_shape_complex_p(shape_id) && rb_shape_embedded_capacity(shape_id) >= RSHAPE_LEN(shape_id)) { VALUE *embedded_fields = ROBJECT_EMBEDDED_FIELDS(v); VALUE *extended_fields = ROBJECT_FIELDS(v); MEMCPY(embedded_fields, extended_fields, VALUE, RSHAPE_LEN(shape_id)); - FL_UNSET_RAW(v, ROBJECT_HEAP); - RBASIC_SET_FULL_SHAPE_ID(v, rb_shape_transition_robject(shape_id)); + shape_id = rb_shape_transition_robject(shape_id); + RBASIC_SET_FULL_SHAPE_ID(v, shape_id); rb_gc_writebarrier_remember(v); } else { @@ -3697,7 +3672,8 @@ gc_ref_update_object(void *objspace, VALUE v) } VALUE *ptr = ROBJECT_FIELDS(v); - for (uint32_t i = 0; i < ROBJECT_FIELDS_COUNT(v); i++) { + attr_index_t len = RSHAPE_LEN(shape_id); + for (attr_index_t i = 0; i < len; i++) { UPDATE_IF_MOVED(objspace, ptr[i]); } } @@ -4063,6 +4039,20 @@ vm_weak_table_frozen_strings_foreach(VALUE *str, void *data) } void rb_fstring_foreach_with_replace(int (*callback)(VALUE *str, void *data), void *data); + +// Whether this table must be cleaned every GC after marking. +// Other tables may be skipped cleaned up per-object via rb_gc_obj_free_vm_weak_references. +bool +rb_gc_vm_weak_table_essential_p(enum rb_gc_vm_weak_tables table) +{ + switch (table) { + case RB_GC_VM_GENERIC_FIELDS_TABLE: + return true; + default: + return false; + } +} + void rb_gc_vm_weak_table_foreach(vm_table_foreach_callback_func callback, vm_table_update_callback_func update_callback, @@ -4855,18 +4845,20 @@ rb_raw_obj_info_buitin_type(char *const buff, const size_t buff_size, const VALU } case T_OBJECT: { - if (FL_TEST_RAW(obj, ROBJECT_HEAP)) { - if (rb_obj_shape_complex_p(obj)) { - size_t hash_len = rb_st_table_size(ROBJECT_FIELDS_HASH(obj)); - APPEND_F("(complex) len:%zu", hash_len); + shape_id_t shape_id = RBASIC_SHAPE_ID(obj); + if (rb_shape_embedded_p(shape_id)) { + APPEND_F("(embed) len:%d capa:%d", RSHAPE_LEN(shape_id), RSHAPE_CAPACITY(shape_id)); + } + else { + VALUE fields_obj = ROBJECT_FIELDS_OBJ(obj); + if (rb_shape_complex_p(shape_id)) { + size_t hash_len = rb_st_table_size(rb_imemo_fields_complex_tbl(fields_obj)); + APPEND_F("(complex) len:%zu extended:%p", hash_len, (void *)fields_obj); } else { - APPEND_F("len:%d capa:%d extended:%p", RSHAPE_LEN(RBASIC_SHAPE_ID(obj)), ROBJECT_FIELDS_CAPACITY(obj), (void *)ROBJECT_FIELDS_OBJ(obj)); + APPEND_F("(extended) len:%d capa:%d extended:%p", RSHAPE_LEN(shape_id), RSHAPE_CAPACITY(shape_id), (void *)fields_obj); } } - else { - APPEND_F("(embed) len:%d capa:%d", RSHAPE_LEN(RBASIC_SHAPE_ID(obj)), ROBJECT_FIELDS_CAPACITY(obj)); - } } break; case T_DATA: { @@ -4892,12 +4884,13 @@ rb_raw_obj_info_buitin_type(char *const buff, const size_t buff_size, const VALU switch (imemo_type(obj)) { case imemo_fields: { - if (rb_obj_shape_complex_p(obj)) { + shape_id_t shape_id = RBASIC_SHAPE_ID(obj); + if (rb_shape_complex_p(shape_id)) { size_t hash_len = rb_st_table_size(rb_imemo_fields_complex_tbl(obj)); APPEND_F("(complex) len:%zu", hash_len); } else { - APPEND_F("(embed) len:%d capa:%d", RSHAPE_LEN(RBASIC_SHAPE_ID(obj)), ROBJECT_FIELDS_CAPACITY(obj)); + APPEND_F("(embed) len:%d capa:%d", RSHAPE_LEN(shape_id), RSHAPE_CAPACITY(shape_id)); } APPEND_S("owner -> "); diff --git a/gc/default/default.c b/gc/default/default.c index cee550df6b6c79..e4889a007a19eb 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -4232,6 +4232,15 @@ gc_sweep_freeobj_hooks(rb_objspace_t *objspace) } } +static int +gc_sweep_weak_table_i(VALUE val, void *data) +{ + rb_objspace_t *objspace = data; + if (RB_SPECIAL_CONST_P(val)) return ST_CONTINUE; + if (RVALUE_MARKED(objspace, val)) return ST_CONTINUE; + return ST_DELETE; +} + static void gc_sweep_start(rb_objspace_t *objspace) { @@ -4242,6 +4251,17 @@ gc_sweep_start(rb_objspace_t *objspace) gc_sweep_freeobj_hooks(objspace); } + for (int table = 0; table < RB_GC_VM_WEAK_TABLE_COUNT; table++) { + if (!rb_gc_vm_weak_table_essential_p(table)) continue; + rb_gc_vm_weak_table_foreach( + gc_sweep_weak_table_i, + NULL, + objspace, + true, + table + ); + } + #if GC_CAN_COMPILE_COMPACTION if (objspace->flags.during_compacting) { gc_sort_heap_by_compare_func( diff --git a/gc/gc.h b/gc/gc.h index 3de9b3f165ddb3..7ab14af1a58fc4 100644 --- a/gc/gc.h +++ b/gc/gc.h @@ -92,6 +92,7 @@ MODULAR_GC_FN void rb_gc_vm_unlock_no_barrier(unsigned int lev, const char *file MODULAR_GC_FN void rb_gc_vm_barrier(void); MODULAR_GC_FN size_t rb_gc_obj_optimal_size(VALUE obj); MODULAR_GC_FN void rb_gc_mark_children(void *objspace, VALUE obj); +MODULAR_GC_FN bool rb_gc_vm_weak_table_essential_p(enum rb_gc_vm_weak_tables table); MODULAR_GC_FN void rb_gc_vm_weak_table_foreach(vm_table_foreach_callback_func callback, vm_table_update_callback_func update_callback, void *data, bool weak_only, enum rb_gc_vm_weak_tables table); MODULAR_GC_FN void rb_gc_update_object_references(void *objspace, VALUE obj); MODULAR_GC_FN void rb_gc_update_vm_references(void *objspace); diff --git a/include/ruby/internal/core/robject.h b/include/ruby/internal/core/robject.h index c8917c0f8e2b19..c4d3c0d5cf3429 100644 --- a/include/ruby/internal/core/robject.h +++ b/include/ruby/internal/core/robject.h @@ -43,39 +43,10 @@ #define ROBJECT(obj) RBIMPL_CAST((struct RObject *)(obj)) /** @cond INTERNAL_MACRO */ #define ROBJECT_EMBED_LEN_MAX ROBJECT_EMBED_LEN_MAX -#define ROBJECT_HEAP ROBJECT_HEAP #define ROBJECT_FIELDS_CAPACITY ROBJECT_FIELDS_CAPACITY #define ROBJECT_FIELDS ROBJECT_FIELDS /** @endcond */ -/** - * @private - * - * Bits that you can set to ::RBasic::flags. - */ -enum ruby_robject_flags { - /** - * This flag marks that the object's instance variables are stored in an - * external heap buffer. - * Normally, instance variable references are stored inside the object slot, - * but if it overflow, Ruby may have to allocate a separate buffer and spills - * the instance variables there. - * This flag denotes that situation. - * - * @warning This bit has to be considered read-only. Setting/clearing - * this bit without corresponding fix up must cause immediate - * SEGV. Also, internal structures of an object change - * dynamically and transparently throughout of its lifetime. - * Don't assume it being persistent. - * - * @internal - * - * 3rd parties must not be aware that there even is more than one way to - * store instance variables. Might better be hidden. - */ - ROBJECT_HEAP = RUBY_FL_USER4 -}; - struct st_table; /** diff --git a/internal/imemo.h b/internal/imemo.h index 0bd9fc0bfb50d0..da8196f47167c7 100644 --- a/internal/imemo.h +++ b/internal/imemo.h @@ -252,8 +252,6 @@ struct rb_fields { // IMEMO/fields and T_OBJECT have exactly the same layout. // This is useful for JIT and common codepaths. -#define OBJ_FIELD_HEAP ROBJECT_HEAP -STATIC_ASSERT(imemo_fields_flags, OBJ_FIELD_HEAP == IMEMO_FL_USER0); STATIC_ASSERT(imemo_fields_embed_offset, offsetof(struct RObject, as.ary) == offsetof(struct rb_fields, as.embed.fields)); #define IMEMO_OBJ_FIELDS(fields) ((struct rb_fields *)fields) @@ -292,39 +290,4 @@ rb_imemo_fields_owner(VALUE fields_obj) return CLASS_OF(fields_obj); } -static inline VALUE * -rb_imemo_fields_ptr(VALUE fields_obj) -{ - if (!fields_obj) { - return NULL; - } - - RUBY_ASSERT(IMEMO_TYPE_P(fields_obj, imemo_fields) || RB_TYPE_P(fields_obj, T_OBJECT)); - - if (UNLIKELY(FL_TEST_RAW(fields_obj, OBJ_FIELD_HEAP))) { - RUBY_ASSERT(RB_TYPE_P(fields_obj, T_OBJECT)); - fields_obj = ROBJECT(fields_obj)->as.extended; - RUBY_ASSERT(IMEMO_TYPE_P(fields_obj, imemo_fields)); - } - - return IMEMO_OBJ_FIELDS(fields_obj)->as.embed.fields; -} - -static inline st_table * -rb_imemo_fields_complex_tbl(VALUE fields_obj) -{ - if (!fields_obj) { - return NULL; - } - - RUBY_ASSERT(IMEMO_TYPE_P(fields_obj, imemo_fields)); - RUBY_ASSERT(!FL_TEST_RAW(fields_obj, OBJ_FIELD_HEAP)); - - // Some codepaths unconditionally access the fields_ptr, and assume it can be used as st_table if the - // shape is complex. - RUBY_ASSERT((st_table *)rb_imemo_fields_ptr(fields_obj) == &IMEMO_OBJ_FIELDS(fields_obj)->as.complex.table); - - return &IMEMO_OBJ_FIELDS(fields_obj)->as.complex.table; -} - #endif /* INTERNAL_IMEMO_H */ diff --git a/internal/object.h b/internal/object.h index 2c531186ba3d75..3574f2d89f439c 100644 --- a/internal/object.h +++ b/internal/object.h @@ -68,35 +68,12 @@ RBASIC_SET_CLASS(VALUE obj, VALUE klass) RB_OBJ_WRITTEN(obj, oldv, klass); } -static inline VALUE -ROBJECT_FIELDS_OBJ(VALUE obj) -{ - RBIMPL_ASSERT_TYPE(obj, RUBY_T_OBJECT); - - return FL_TEST_RAW(obj, ROBJECT_HEAP) ? ROBJECT(obj)->as.extended : obj; -} - -static inline VALUE * -ROBJECT_EMBEDDED_FIELDS(VALUE obj) -{ - return ROBJECT(obj)->as.ary; -} - -static inline VALUE * -ROBJECT_FIELDS(VALUE obj) -{ - RBIMPL_ASSERT_TYPE(obj, RUBY_T_OBJECT); - - return ROBJECT_EMBEDDED_FIELDS(ROBJECT_FIELDS_OBJ(obj)); -} - static inline void ROBJECT_SET_EXTENDED(VALUE obj, VALUE fields_obj) { RUBY_ASSERT(RB_TYPE_P(obj, T_OBJECT)); RUBY_ASSERT(RB_TYPE_P(fields_obj, T_IMEMO)); RB_OBJ_WRITE(obj, &ROBJECT(obj)->as.extended, fields_obj); - FL_SET_RAW(obj, ROBJECT_HEAP); } static inline size_t diff --git a/lib/erb.rb b/lib/erb.rb index bde90de84122ce..fbd146ed2ed57f 100644 --- a/lib/erb.rb +++ b/lib/erb.rb @@ -889,7 +889,7 @@ def make_compiler(trim_mode) # # ``` # erb = ERB.new(template, eoutvar: '_foo') - # puts template.src.split('; ') + # puts erb.src.split('; ') # #coding:UTF-8 # _foo = +'' # _foo.<< "The time is ".freeze diff --git a/lib/ipaddr.rb b/lib/ipaddr.rb index 70b804f642f099..a71772e32b05ad 100644 --- a/lib/ipaddr.rb +++ b/lib/ipaddr.rb @@ -328,7 +328,7 @@ def private? end end - # Returns true if the ipaddr is a link-local address. IPv4 + # Returns true if the ipaddr is a link-local unicast address. IPv4 # addresses in 169.254.0.0/16 reserved by RFC 3927 and link-local # IPv6 Unicast Addresses in fe80::/10 reserved by RFC 4291 are # considered link-local. Link-local IPv4 addresses in the @@ -347,6 +347,44 @@ def link_local? end end + alias link_local_unicast? link_local? + + # Returns true if the ipaddr is a multicast address. IPv4 + # addresses in 224.0.0.0/4 and IPv6 multicast addresses + # in ff00::/8 are considered multicast. Multicast IPv4 addresses in the + # IPv4-mapped IPv6 address range are also considered multicast. + def multicast? + case @family + when Socket::AF_INET + @addr & 0xf0000000 == 0xe0000000 # 224.0.0.0/4 + when Socket::AF_INET6 + @addr & 0xff00_0000_0000_0000_0000_0000_0000_0000 == 0xff00_0000_0000_0000_0000_0000_0000_0000 || # ff00::/8 + (@addr >> 32 == 0xffff && + @addr & 0xf0000000 == 0xe0000000 # ::ffff:224.0.0.0/4 + ) + else + raise AddressFamilyError, "unsupported address family" + end + end + + # Returns true if the ipaddr is a link-local multicast address. IPv4 + # addresses in 224.0.0.0/24 and link-local IPv6 Multicast Addresses in ff02::/16 + # are considered link-local multicast. Link-local multicast IPv4 addresses in the + # IPv4-mapped IPv6 address range are also considered link-local multicast. + def link_local_multicast? + case @family + when Socket::AF_INET + @addr & 0xffffff00 == 0xe0000000 # 224.0.0.0/24 Local Network Control Block + when Socket::AF_INET6 + @addr & 0xffff_0000_0000_0000_0000_0000_0000_0000 == 0xff02_0000_0000_0000_0000_0000_0000_0000 || # ff02::/16 + (@addr >> 32 == 0xffff && ( + @addr & 0xffff0000 == 0xe0000000 # ::ffff:224.0.0.0/24 + )) + else + raise AddressFamilyError, "unsupported address family" + end + end + # Returns true if the ipaddr is an IPv4-mapped IPv6 address. def ipv4_mapped? return ipv6? && (@addr >> 32) == 0xffff diff --git a/lib/prism/ffi.rb b/lib/prism/ffi.rb index 6b9bde51eaca49..01c1f4ace4971a 100644 --- a/lib/prism/ffi.rb +++ b/lib/prism/ffi.rb @@ -294,11 +294,23 @@ def parse_file(filepath, **options) # Mirror the Prism.parse_stream API by using the serialization API. def parse_stream(stream, **options) LibRubyParser::PrismBuffer.with do |buffer| + # The largest number of bytes a single character can occupy in any + # encoding Ruby supports. IO#gets(limit) may return up to + # (max_enc_len - 1) bytes more than requested to avoid splitting a + # multi-byte character, so we reserve that much headroom (plus the NUL + # terminator) to guarantee the result fits in the caller's buffer. This + # mirrors MAX_ENC_LEN in ext/prism/extension.c. + max_enc_len = 6 + source = +"" callback = -> (string, size, _) { - raise "Expected size to be >= 0, got: #{size}" if size <= 0 + raise "Expected size to be > #{max_enc_len}, got: #{size}" if size <= max_enc_len - if !(line = stream.gets(size - 1)).nil? + line = String.try_convert(stream.gets(size - max_enc_len)) + if !line.nil? + # A misbehaving `gets` may ignore the limit; never write past the + # buffer (one byte is reserved for the NUL terminator). + line = line.byteslice(0, size - 1) if line.bytesize > size - 1 source << line string.write_string("#{line}\x00", line.bytesize + 1) end diff --git a/lib/prism/translation/ruby_parser.rb b/lib/prism/translation/ruby_parser.rb index 42bc5ee658dec7..edb3f3bf04d6e8 100644 --- a/lib/prism/translation/ruby_parser.rb +++ b/lib/prism/translation/ruby_parser.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true # :markup: markdown +require "timeout" + begin require "sexp" rescue LoadError @@ -1560,7 +1562,7 @@ def attach_comments(sexp, node) # Create a new compiler with the given options. def copy_compiler(in_def: self.in_def, in_pattern: self.in_pattern) - Compiler.new(file, in_def: in_def, in_pattern: in_pattern) + self.class.new(file, in_def: in_def, in_pattern: in_pattern) end # Create a new Sexp object from the given prism node and arguments. @@ -1622,18 +1624,26 @@ def visit_write_value(node) end end - private_constant :Compiler + # Optional scopes to pass to the parser. + attr_reader :scopes #: Array[Array[Symbol]]? + + # :nodoc: + #: (?scopes: Array[Array[Symbol]]?) -> void + def initialize(scopes: nil) + super() + @scopes = scopes + end # Parse the given source and translate it into the seattlerb/ruby_parser # gem's Sexp format. def parse(source, filepath = "(string)") - translate(Prism.parse(source, filepath: filepath, partial_script: true), filepath) + translate(Prism.parse(source, filepath: filepath, partial_script: true, scopes: scopes), filepath) end # Parse the given file and translate it into the seattlerb/ruby_parser # gem's Sexp format. def parse_file(filepath) - translate(Prism.parse_file(filepath, partial_script: true), filepath) + translate(Prism.parse_file(filepath, partial_script: true, scopes: scopes), filepath) end # Parse the give file and translate it into the @@ -1647,14 +1657,14 @@ def process(ruby, file = "(string)", timeout = nil) class << self # Parse the given source and translate it into the seattlerb/ruby_parser # gem's Sexp format. - def parse(source, filepath = "(string)") - new.parse(source, filepath) + def parse(source, filepath = "(string)", scopes: nil) + new(scopes: scopes).parse(source, filepath) end # Parse the given file and translate it into the seattlerb/ruby_parser # gem's Sexp format. - def parse_file(filepath) - new.parse_file(filepath) + def parse_file(filepath, scopes: nil) + new(scopes: scopes).parse_file(filepath) end end @@ -1669,7 +1679,7 @@ def translate(result, filepath) end result.attach_comments! - result.value.accept(Compiler.new(filepath)) + result.value.accept(self.class::Compiler.new(filepath)) end end end diff --git a/object.c b/object.c index 033b3dc35fb5de..2eb958fb151417 100644 --- a/object.c +++ b/object.c @@ -46,9 +46,6 @@ /* Flags of RObject * - * 4: ROBJECT_HEAP - * The object has its instance variables in a separately allocated buffer. - * This can be either a flat buffer of reference, or an st_table for complex objects. */ /*! @@ -355,8 +352,8 @@ rb_obj_copy_ivar(VALUE dest, VALUE obj) RUBY_ASSERT(src_num_ivs <= dest_capa); if (initial_capa < dest_capa) { // We we need to transition the object to an extended layout. - VALUE fields_obj = rb_imemo_fields_new(dest, dest_shape_id, false); - ROBJECT_SET_EXTENDED(dest, fields_obj); + rb_obj_replace_fields(dest, rb_imemo_fields_new(dest, dest_shape_id, false)); + dest_buf = ROBJECT_FIELDS(dest); rb_shape_copy_fields(dest, dest_buf, dest_shape_id, src_buf, src_shape_id); RBASIC_SET_SHAPE_ID_WITH_LAYOUT(dest, dest_shape_id, SHAPE_ID_LAYOUT_EXTENDED); diff --git a/pack.c b/pack.c index 56af4ea099cae6..4b6595b45a9e45 100644 --- a/pack.c +++ b/pack.c @@ -72,8 +72,12 @@ is_bigendian(void) #ifdef NATINT_PACK # define NATINT_LEN(type,len) (natint?(int)sizeof(type):(int)(len)) +# define NATINT_ALIGN(type,len) (natint?(int)RUBY_ALIGNOF(type):(int)(len)) +# define USING_NATINT(expr) expr #else # define NATINT_LEN(type,len) ((int)sizeof(type)) +# define NATINT_ALIGN(type,len) ((int)RUBY_ALIGNOF(type)) +# define USING_NATINT(expr) /* void */ #endif typedef union { @@ -218,9 +222,7 @@ pack_modifiers(const char *p, const char *pend, char type, int *natint, int *exp case '_': case '!': if (strchr(natstr, type)) { -#ifdef NATINT_PACK - *natint = 1; -#endif + USING_NATINT(*natint = 1); p++; } else { @@ -245,6 +247,91 @@ pack_modifiers(const char *p, const char *pend, char type, int *natint, int *exp return p; } +#ifndef NATINT_PACK +# define pack_alignof(t, n) pack_alignof(t) +#endif +static size_t +pack_alignof(char type, int natint) +{ + switch (type) { + case 'c': case 'C': + return RUBY_ALIGNOF(char); + case 's': case 'S': + return NATINT_ALIGN(short, 2); + case 'i': case 'I': + return RUBY_ALIGNOF(int); + case 'l': case 'L': + return NATINT_ALIGN(long, 4); + case 'q': case 'Q': + return RUBY_ALIGNOF(int64_t); + case 'j': + return RUBY_ALIGNOF(intptr_t); + case 'J': + return RUBY_ALIGNOF(uintptr_t); + case 'n': case 'v': + return RUBY_ALIGNOF(uint16_t); + case 'N': case 'V': + return RUBY_ALIGNOF(uint32_t); + case 'f': case 'F': case 'e': case 'g': + return RUBY_ALIGNOF(float); + case 'd': case 'D': case 'E': case 'G': + return RUBY_ALIGNOF(double); + case 'p': case 'P': + return RUBY_ALIGNOF(char *); + default: + return 0; + } +} + +static long +pack_align_pad(long pos, long base, size_t alignment) +{ + long offset, mod; + + if (alignment <= 1) return 0; + if (alignment > LONG_MAX) rb_raise(rb_eRangeError, "alignment too big"); + offset = pos - base; + mod = offset % (long)alignment; + if (mod < 0) mod += alignment; + return mod ? (long)alignment - mod : 0; +} + +static char * +pack_alignment(const char *p, const char *pend, VALUE fmt, size_t *alignment) +{ + char type; + int explicit_endian = 0; + USING_NATINT(int natint = 0); + + if (p >= pend) { + rb_raise(rb_eArgError, "missing alignment"); + } + type = *p++; + p = pack_modifiers(p, pend, type, &natint, &explicit_endian); + if (explicit_endian) { + rb_raise(rb_eArgError, "endian modifier is not allowed for alignment"); + } + *alignment = pack_alignof(type, natint); + if (!*alignment) { + unknown_directive("alignment", type, fmt); + } + return (char *)p; +} + +static char * +pack_alignment_size(const char *p, const char *pend, VALUE fmt, size_t *alignment) +{ + if (p < pend && ISDIGIT(*p)) { + errno = 0; + *alignment = STRTOUL(p, (char**)&p, 10); + if (*alignment <= 0 || errno) { + rb_raise(rb_eRangeError, "invalid alignment"); + } + return (char *)p; + } + return pack_alignment(p, pend, fmt, alignment); +} + static VALUE pack_pack(rb_execution_context_t *ec, VALUE ary, VALUE fmt, VALUE buffer) { @@ -254,6 +341,7 @@ pack_pack(rb_execution_context_t *ec, VALUE ary, VALUE fmt, VALUE buffer) const char *ptr; int enc_info = 1; /* 0 - BINARY, 1 - US-ASCII, 2 - UTF-8 */ int integer_size, bigendian_p; + long align_base; StringValue(fmt); rb_must_asciicompat(fmt); @@ -271,6 +359,7 @@ pack_pack(rb_execution_context_t *ec, VALUE ary, VALUE fmt, VALUE buffer) } idx = 0; + align_base = RSTRING_LEN(res); #define TOO_FEW (rb_raise(rb_eArgError, toofew), 0) #define MORE_ITEM (idx < RARRAY_LEN(ary)) @@ -281,15 +370,25 @@ pack_pack(rb_execution_context_t *ec, VALUE ary, VALUE fmt, VALUE buffer) while (p < pend) { int explicit_endian = 0; + size_t align = 0; if (RSTRING_END(fmt) != pend) { rb_raise(rb_eRuntimeError, "format string modified"); } const char type = *p++; /* get data type */ -#ifdef NATINT_PACK - int natint = 0; /* native integer */ -#endif + USING_NATINT(int natint = 0); /* native integer */ if (skip_blank(p, type)) continue; + + /* Directives that do not take modifiers. */ + if ((type == 'x' || type == '@') && p < pend && *p == '!') { + p++; + p = pack_alignment_size(p, pend, fmt, &align); + len = pack_align_pad(RSTRING_LEN(res), type == '@' ? 0 : align_base, align); + rb_str_modify_expand(res, len); + str_expand_fill(res, '\0', len); + continue; + } + p = pack_modifiers(p, pend, type, &natint, &explicit_endian); if (*p == '*') { /* set data length */ @@ -1005,6 +1104,7 @@ pack_unpack_internal(VALUE str, VALUE fmt, VALUE ofs, enum unpack_mode mode) long len; AVOID_CC_BUG long tmp_len; int signed_p, integer_size, bigendian_p; + long align_base; #define UNPACK_PUSH(item) do {\ VALUE item_val = (item);\ if ((mode) == UNPACK_BLOCK) {\ @@ -1031,6 +1131,7 @@ pack_unpack_internal(VALUE str, VALUE fmt, VALUE ofs, enum unpack_mode mode) s = RSTRING_PTR(str); send = s + len; s += offset; + align_base = offset; p = RSTRING_PTR(fmt); pend = p + RSTRING_LEN(fmt); @@ -1041,12 +1142,24 @@ pack_unpack_internal(VALUE str, VALUE fmt, VALUE ofs, enum unpack_mode mode) while (p < pend) { int explicit_endian = 0; const char type = *p++; -#ifdef NATINT_PACK - int natint = 0; /* native integer */ -#endif + size_t align = 0; int star = 0; + USING_NATINT(int natint = 0); /* native integer */ if (skip_blank(p, type)) continue; + + /* Directives that do not take modifiers. */ + if ((type == 'x' || type == '@') && p < pend && *p == '!') { + p++; + p = pack_alignment_size(p, pend, fmt, &align); + len = pack_align_pad(s - RSTRING_PTR(str), type == '@' ? 0 : align_base, align); + if (len > send - s) { + rb_raise(rb_eArgError, type == '@' ? "@ outside of string" : "x outside of string"); + } + s += len; + continue; + } + p = pack_modifiers(p, pend, type, &natint, &explicit_endian); if (p >= pend) diff --git a/prism/extension.c b/prism/extension.c index 27df8dac50ddff..bd6a7d22c3d08e 100644 --- a/prism/extension.c +++ b/prism/extension.c @@ -1067,13 +1067,40 @@ parse_stream_eof(void *stream) { } /** - * An implementation of fgets that is suitable for use with Ruby IO objects. + * The largest number of bytes a single character can occupy in any encoding + * that Ruby supports (CESU-8). `IO#gets(limit)` treats its argument as a soft + * limit: to avoid splitting a multi-byte character it may return up to + * `MAX_ENC_LEN - 1` more bytes than requested. Reserving `MAX_ENC_LEN` bytes of + * headroom therefore leaves room for both that overshoot and the terminating + * NUL byte. + * + * The value can be re-derived from the Ruby source tree with: + * + * grep -Erh "max (enc|byte) length" enc | grep -Eo '[0-9]+' | sort | tail -n1 + */ +#define MAX_ENC_LEN 6 + +/** + * An implementation of fgets that is suitable for use with Ruby IO objects. As + * required by pm_source_stream_fgets_t, this never writes more than `size` + * bytes into `string` (including the terminating NUL byte). */ static char * parse_stream_fgets(char *string, int size, void *stream) { - RUBY_ASSERT(size > 0); + RUBY_ASSERT(size > MAX_ENC_LEN); + + /* + * Request fewer bytes than the buffer can hold. `gets` may return more + * bytes than requested when the limit falls in the middle of a multi-byte + * character, and the reserved headroom guarantees the result still fits. + */ + VALUE line = rb_funcall((VALUE) stream, rb_intern("gets"), 1, INT2FIX(size - MAX_ENC_LEN)); - VALUE line = rb_funcall((VALUE) stream, rb_intern("gets"), 1, INT2FIX(size - 1)); + /* + * A well-behaved stream returns a String, or nil at EOF. Coerce anything + * else to nil so that we never treat a non-String as a byte buffer. + */ + line = rb_check_string_type(line); if (NIL_P(line)) { return NULL; } @@ -1081,12 +1108,23 @@ parse_stream_fgets(char *string, int size, void *stream) { const char *cstr = RSTRING_PTR(line); long length = RSTRING_LEN(line); - memcpy(string, cstr, length); + /* + * Defensively clamp the copy. A misbehaving `gets` may ignore the limit + * entirely and return an arbitrarily long string; we must never write past + * the caller's buffer. One byte is reserved for the NUL terminator. + */ + if (length > (long) (size - 1)) { + length = (long) (size - 1); + } + + memcpy(string, cstr, (size_t) length); string[length] = '\0'; return string; } +#undef MAX_ENC_LEN + /** * :markup: markdown * call-seq: diff --git a/prism/source.c b/prism/source.c index f61cb19c1bca2b..5a6982c3101405 100644 --- a/prism/source.c +++ b/prism/source.c @@ -368,25 +368,50 @@ pm_source_stream_read(pm_source_t *source) { #define LINE_SIZE 4096 char line[LINE_SIZE]; - while (memset(line, '\n', LINE_SIZE), source->stream.fgets(line, LINE_SIZE, source->stream.stream) != NULL) { + /* + * A chunk read from the stream may legitimately contain embedded NUL bytes, + * so the NUL terminator written by fgets cannot be located with strlen. + * Instead we prefill the buffer with a non-NUL sentinel before each read; + * the terminator is then the only NUL at or after the data, so it can be + * found by scanning from the end. The value only needs to differ from '\0'. + */ +#define LINE_FILL 0xff + + while (memset(line, LINE_FILL, LINE_SIZE), source->stream.fgets(line, LINE_SIZE, source->stream.stream) != NULL) { + /* + * Locate the NUL terminator written by fgets (the last NUL in the + * buffer) to recover the length of the chunk, then drop the terminator. + */ size_t length = LINE_SIZE; - while (length > 0 && line[length - 1] == '\n') length--; - - if (length == LINE_SIZE) { - /* - * If we read a line that is the maximum size and it doesn't end - * with a newline, then we'll just append it to the buffer and - * continue reading. - */ - length--; - pm_buffer_append_string(buffer, line, length); - continue; + while (length > 0 && line[length - 1] != '\0') length--; + if (length > 0) length--; + + /* + * An empty chunk means the stream returned an empty string instead of + * signaling EOF (a well-behaved stream never does this). We can't make + * progress on it, so stop reading rather than spinning forever. + */ + if (length == 0) { + break; } - /* Append the line to the buffer. */ - length--; + /* Append the chunk to the buffer. */ pm_buffer_append_string(buffer, line, length); + bool newline = (line[length - 1] == '\n'); + + /* + * A chunk with no trailing newline is either a line longer than the + * read buffer (keep reading the rest of it) or the final line at EOF. + * This is the only place the stream's EOF state changes what we do + * next, so it is the only place we ask the stream whether it has hit + * EOF. A newline-terminated line needs no such check: if it happens to + * be the last line, the next fgets returns NULL and ends the loop. + */ + if (!newline && !source->stream.feof(source->stream.stream)) { + continue; + } + /* * Check if the line matches the __END__ marker. If it does, then stop * reading and return false. In most circumstances, this means we should @@ -418,14 +443,16 @@ pm_source_stream_read(pm_source_t *source) { } /* - * All data should be read via gets. If the string returned by gets - * _doesn't_ end with a newline, then we assume we hit EOF condition. + * A chunk that reached here without a trailing newline is the final + * line at EOF (the check above would have continued otherwise), so + * there is nothing more to read. */ - if (source->stream.feof(source->stream.stream)) { + if (!newline) { break; } } +#undef LINE_FILL #undef LINE_SIZE source->stream.eof = true; diff --git a/prism/source.h b/prism/source.h index c79987d3fb9c34..115914392f8ec3 100644 --- a/prism/source.h +++ b/prism/source.h @@ -24,6 +24,13 @@ typedef struct pm_source_t pm_source_t; * This function is used to retrieve a line of input from a stream. It closely * mirrors that of fgets so that fgets can be used as the default * implementation. + * + * As with fgets, implementations MUST write at most `size` bytes into + * `string`, including the terminating NUL byte (i.e. at most `size - 1` bytes + * of data followed by a '\0'). The caller relies on this bound for memory + * safety, so an implementation that reads from a source which may return more + * data than requested (for example a multi-byte-aware `gets`) is responsible + * for clamping the amount it copies. Returns `string`, or NULL on EOF. */ typedef char * (pm_source_stream_fgets_t)(char *string, int size, void *stream); diff --git a/ractor.c b/ractor.c index e58a85d100ea7d..fbb5ae8421ecb2 100644 --- a/ractor.c +++ b/ractor.c @@ -1282,6 +1282,20 @@ obj_traverse_reachable_i(VALUE obj, void *ptr) } } +// Traverse obj's children via its GC mark function. Returns 1 to stop. +static int +obj_traverse_reachable(VALUE obj, struct obj_traverse_data *data) +{ + struct obj_traverse_callback_data d = { + .stop = false, + .data = data, + }; + RB_VM_LOCKING_NO_BARRIER() { + rb_objspace_reachable_objects_from(obj, obj_traverse_reachable_i, &d); + } + return d.stop; +} + static struct st_table * obj_traverse_rec(struct obj_traverse_data *data) { @@ -1323,12 +1337,14 @@ obj_traverse_i(VALUE obj, struct obj_traverse_data *data) } RB_OBJ_WRITTEN(data->rec_hash, Qundef, obj); - struct obj_traverse_callback_data d = { - .stop = false, - .data = data, - }; - rb_ivar_foreach(obj, obj_traverse_ivar_foreach_i, (st_data_t)&d); - if (d.stop) return 1; + if (rb_obj_shape_has_ivars(obj)) { + struct obj_traverse_callback_data d = { + .stop = false, + .data = data, + }; + rb_ivar_foreach(obj, obj_traverse_ivar_foreach_i, (st_data_t)&d); + if (d.stop) return 1; + } switch (BUILTIN_TYPE(obj)) { // no child node @@ -1349,7 +1365,7 @@ obj_traverse_i(VALUE obj, struct obj_traverse_data *data) rb_ary_cancel_sharing(obj); for (int i = 0; i < RARRAY_LENINT(obj); i++) { - VALUE e = rb_ary_entry(obj, i); + VALUE e = RARRAY_AREF(obj, i); if (obj_traverse_i(e, data)) return 1; } } @@ -1393,19 +1409,31 @@ obj_traverse_i(VALUE obj, struct obj_traverse_data *data) break; case T_DATA: - case T_IMEMO: { - struct obj_traverse_callback_data d = { - .stop = false, - .data = data, - }; - RB_VM_LOCKING_NO_BARRIER() { - rb_objspace_reachable_objects_from(obj, obj_traverse_reachable_i, &d); + void *const ptr = RTYPEDDATA_GET_DATA(obj); + const rb_data_type_t *type = RTYPEDDATA_TYPE(obj); + + if (!ptr || !type->function.dmark) { + // no references (the class and ivars are handled elsewhere) + } + else if (type->flags & RUBY_TYPED_DECL_MARKING) { + const size_t *offsets = (const size_t *)(uintptr_t)type->function.dmark; + for (; *offsets != RUBY_REF_END; offsets++) { + VALUE ref = *(VALUE *)((char *)ptr + *offsets); + if (obj_traverse_i(ref, data)) return 1; + } + } + else { + if (obj_traverse_reachable(obj, data)) return 1; } - if (d.stop) return 1; } break; + case T_IMEMO: + // TODO: Not sure this can actually happen; traverse rather than crash. + if (obj_traverse_reachable(obj, data)) return 1; + break; + // unreachable case T_CLASS: case T_MODULE: @@ -1575,7 +1603,7 @@ make_shareable_check_shareable(VALUE obj) static enum obj_traverse_iterator_result mark_shareable(VALUE obj) { - if (RB_TYPE_P(obj, T_STRING)) { + if (RB_BUILTIN_TYPE(obj) == T_STRING) { rb_str_make_independent(obj); } @@ -1813,6 +1841,11 @@ obj_traverse_replace_i(VALUE obj, struct obj_traverse_replace_data *data) return 0; } + if (UNLIKELY(data->rec && st_lookup(data->rec, (st_data_t)obj, &replacement))) { + data->replacement = (VALUE)replacement; + return 0; + } + switch (data->enter_func(obj, data)) { case traverse_cont: break; case traverse_skip: return 0; // skip children @@ -1820,16 +1853,9 @@ obj_traverse_replace_i(VALUE obj, struct obj_traverse_replace_data *data) } replacement = (st_data_t)data->replacement; - - if (UNLIKELY(st_lookup(obj_traverse_replace_rec(data), (st_data_t)obj, &replacement))) { - data->replacement = (VALUE)replacement; - return 0; - } - else { - st_insert(obj_traverse_replace_rec(data), (st_data_t)obj, replacement); - RB_OBJ_WRITTEN(data->rec_hash, Qundef, obj); - RB_OBJ_WRITTEN(data->rec_hash, Qundef, replacement); - } + st_insert(obj_traverse_replace_rec(data), (st_data_t)obj, replacement); + RB_OBJ_WRITTEN(data->rec_hash, Qundef, obj); + RB_OBJ_WRITTEN(data->rec_hash, Qundef, replacement); if (!data->move) { obj = replacement; @@ -1881,14 +1907,16 @@ obj_traverse_replace_i(VALUE obj, struct obj_traverse_replace_data *data) case T_OBJECT: { - if (rb_obj_shape_complex_p(obj)) { + VALUE fields_obj = ROBJECT_FIELDS_OBJ(obj); + shape_id_t shape_id = RBASIC_SHAPE_ID(fields_obj); + if (rb_shape_complex_p(shape_id)) { struct obj_traverse_replace_callback_data d = { .stop = false, .data = data, .src = obj, }; rb_st_foreach_with_replace( - ROBJECT_FIELDS_HASH(obj), + rb_imemo_fields_complex_tbl(fields_obj), obj_iv_hash_traverse_replace_foreach_i, obj_iv_hash_traverse_replace_i, (st_data_t)&d @@ -1896,10 +1924,10 @@ obj_traverse_replace_i(VALUE obj, struct obj_traverse_replace_data *data) if (d.stop) return 1; } else { - uint32_t len = ROBJECT_FIELDS_COUNT_NOT_COMPLEX(obj); - VALUE *ptr = ROBJECT_FIELDS(obj); + attr_index_t len = RSHAPE_LEN(shape_id); + VALUE *ptr = rb_imemo_fields_ptr(fields_obj); - for (uint32_t i = 0; i < len; i++) { + for (attr_index_t i = 0; i < len; i++) { CHECK_AND_REPLACE(obj, ptr[i]); } } @@ -1911,7 +1939,7 @@ obj_traverse_replace_i(VALUE obj, struct obj_traverse_replace_data *data) rb_ary_cancel_sharing(obj); for (int i = 0; i < RARRAY_LENINT(obj); i++) { - VALUE e = rb_ary_entry(obj, i); + VALUE e = RARRAY_AREF(obj, i); if (obj_traverse_replace_i(e, data)) { return 1; @@ -2079,11 +2107,17 @@ move_leave(VALUE obj, struct obj_traverse_replace_data *data) } VALUE flags = T_OBJECT | FL_FREEZE | (RBASIC(obj)->flags & FL_PROMOTED); + shape_id_t shape_id = (RBASIC_SHAPE_ID(obj) & SHAPE_ID_CAPACITY_MASK) | ROOT_SHAPE_ID | SHAPE_ID_LAYOUT_ROBJECT | SHAPE_ID_FL_FROZEN; // Avoid mutations using bind_call, etc. MEMZERO((char *)obj, char, sizeof(struct RBasic)); RBASIC(obj)->flags = flags; RBASIC_SET_CLASS_RAW(obj, rb_cRactorMovedObject); + + // The husk keeps its original (larger) slot, so give it a field-less shape + // sized to that slot; otherwise compaction's slot_size == shape_slot_size + // invariant is violated. + RBASIC_SET_FULL_SHAPE_ID(obj, shape_id); return traverse_cont; } diff --git a/range.c b/range.c index 041cd93f73ee4e..fba73f4a39d1c3 100644 --- a/range.c +++ b/range.c @@ -192,6 +192,18 @@ range_eq(VALUE range, VALUE obj) return rb_exec_recursive_paired(recursive_equal, range, obj, obj); } +/* compares _a_ and _b_ and returns: + * < 0: a < b + * = 0: a = b + * > 0: a > b + * raises an ArgumentError if non-comparable + */ +static int +r_cmp(VALUE a, VALUE b) +{ + return OPTIMIZED_CMP(a, b); +} + /* compares _a_ and _b_ and returns: * < 0: a < b * = 0: a = b @@ -2594,6 +2606,111 @@ range_overlap(VALUE range, VALUE other) return Qtrue; } +/* + * call-seq: + * clamp(min, max) -> range + * clamp(range) -> range + * + * Returns a new +Range+ instance whose begin and end values are + * clamped to _min_ and _max_, or to _range.begin_ and _range.end_. + * + * The returned range excludes its end if any of the following is true: + * + * - The returned end value is an excluded end value of +self+ or _range_. + * - Both begin and end values are clamped to the lower bound, or both + * are clamped to the upper bound. Since +self+ is entirely outside + * the clamping bounds, the returned range is made empty by + * excluding its end. + * + * Otherwise, the returned range includes its end. + * + * Examples: + * + * (1..10).clamp(3, 7) # => 3..7 + * (1...10).clamp(3, 7) # => 3..7 + * (1...10).clamp(3, 10) # => 3...10 + * (0...).clamp(0, 10) # => 0..10 + * + * (1..10).clamp(3..7) # => 3..7 + * (1..10).clamp(3...7) # => 3...7 + * (1..5).clamp(3...7) # => 3..5 + * + * (..10).clamp(3, 7) # => 3..7 + * (...10).clamp(3, 7) # => 3..7 + * (..10).clamp(3...7) # => 3...7 + * (..5).clamp(3...7) # => 3..5 + * + * (1..10).clamp(20..30) # => 20...20 + * (1..10).clamp(-10..0) # => 0...0 + * (..10).clamp(20..30) # => 20...20 + * + * (1..10).clamp(..7) # => 1..7 + * (1..10).clamp(...7) # => 1...7 + * (1..5).clamp(...7) # => 1..5 + * + * (1..10).clamp(3..) # => 3..10 + * (1..10).clamp(3...) # => 3..10 + * (1..).clamp(3..) # => 3.. + * (1...).clamp(3...) # => 3... + */ + +static VALUE +range_clamp(int argc, VALUE *argv, VALUE self) +{ + VALUE self_beg = RANGE_BEG(self); + VALUE self_end = RANGE_END(self); + int self_excl = EXCL(self); + VALUE min, max; + int clamp_beg = 0, clamp_end = 0, excl = 0; + + argc = rb_scan_args(argc, argv, "11", &min, &max); + if (argc == 1) { + VALUE range = min; + if (!rb_range_values(range, &min, &max, &excl)) { + rb_raise(rb_eTypeError, "wrong argument type %s (expected Range)", + rb_builtin_class_name(range)); + } + } + if (!NIL_P(min) && !NIL_P(max) && r_cmp(min, max) > 0) { + rb_raise(rb_eArgError, "min argument must be less than or equal to max argument"); + } + + if (!NIL_P(min)) { + if (NIL_P(self_beg) || r_cmp(self_beg, min) < 0) { + clamp_beg = -1; + self_beg = min; + } + if (!NIL_P(self_end) && r_cmp(self_end, min) < 0) { + clamp_end = -1; + self_end = min; + } + } + if (!NIL_P(max)) { + if (clamp_beg == 0) { + if (!NIL_P(self_beg) && r_cmp(self_beg, max) > 0) { + clamp_beg = +1; + self_beg = max; + } + } + if (clamp_end == 0) { + int cmp = NIL_P(self_end) ? +1 : r_cmp(self_end, max); + if (cmp > 0) { + clamp_end = +1; + self_end = max; + self_excl = excl; + } + else if (cmp == 0) { + self_excl |= excl; + } + } + } + if (clamp_beg && clamp_beg == clamp_end) { + /* self is entirely outside the clamping bounds. */ + self_excl = TRUE; + } + return rb_range_new(self_beg, self_end, self_excl); +} + /* A \Range object represents a collection of values * that are between given begin and end values. * @@ -2783,6 +2900,7 @@ range_overlap(VALUE range, VALUE other) * === Methods for Creating a \Range * * - ::new: Returns a new range. + * - #clamp: Returns a new range with clamped begin and end values. * * === Methods for Querying * @@ -2878,4 +2996,5 @@ Init_Range(void) rb_define_method(rb_cRange, "cover?", range_cover, 1); rb_define_method(rb_cRange, "count", range_count, -1); rb_define_method(rb_cRange, "overlap?", range_overlap, 1); + rb_define_method(rb_cRange, "clamp", range_clamp, -1); } diff --git a/shape.c b/shape.c index c54c86b937c923..5c59d2aee22aca 100644 --- a/shape.c +++ b/shape.c @@ -1216,8 +1216,9 @@ static shape_id_t rb_shape_expected_layout(VALUE obj) { switch (BUILTIN_TYPE(obj)) { - case T_OBJECT: - return FL_TEST_RAW(obj, ROBJECT_HEAP) ? SHAPE_ID_LAYOUT_EXTENDED : SHAPE_ID_LAYOUT_ROBJECT; + case T_OBJECT: { + return SHAPE_ID_LAYOUT_ROBJECT; + } case T_CLASS: case T_MODULE: if (FL_TEST_RAW(obj, RCLASS_BOXABLE)) { @@ -1278,8 +1279,10 @@ rb_shape_verify_consistency(VALUE obj, shape_id_t shape_id) shape_id_t actual_layout = rb_shape_layout(rb_obj_shape_id(obj)); shape_id_t expected_layout = rb_shape_expected_layout(obj); if (actual_layout != expected_layout) { - rb_bug("shape_id layout mismatch: expected=%s actual=%s shape_id=%u obj=%s", - shape_layout_name(expected_layout), shape_layout_name(actual_layout), shape_id, rb_obj_info(obj)); + if (!(RB_TYPE_P(obj, T_OBJECT) && actual_layout == SHAPE_ID_LAYOUT_EXTENDED)) { + rb_bug("shape_id layout mismatch: expected=%s actual=%s shape_id=%u obj=%s", + shape_layout_name(expected_layout), shape_layout_name(actual_layout), shape_id, rb_obj_info(obj)); + } } if (shape_id == ROOT_SHAPE_ID) { @@ -1294,10 +1297,10 @@ rb_shape_verify_consistency(VALUE obj, shape_id_t shape_id) // Ensure complex object don't appear as embedded if (RB_TYPE_P(obj, T_OBJECT)) { - RUBY_ASSERT(FL_TEST_RAW(obj, ROBJECT_HEAP)); + RUBY_ASSERT(rb_obj_shape_extended_p(obj)); } else if (IMEMO_TYPE_P(obj, imemo_fields)) { - RUBY_ASSERT(!FL_TEST_RAW(obj, ROBJECT_HEAP)); + RUBY_ASSERT(rb_obj_shape_embedded_p(obj)); } } else { diff --git a/shape.h b/shape.h index 567d488255e4b6..5a7dc76c4d12cd 100644 --- a/shape.h +++ b/shape.h @@ -191,6 +191,30 @@ rb_shape_layout(shape_id_t shape_id) return shape_id & SHAPE_ID_LAYOUT_MASK; } +static inline bool +rb_shape_embedded_p(shape_id_t shape_id) +{ + return rb_shape_layout(shape_id) == SHAPE_ID_LAYOUT_ROBJECT; +} + +static inline bool +rb_shape_extended_p(shape_id_t shape_id) +{ + return rb_shape_layout(shape_id) == SHAPE_ID_LAYOUT_EXTENDED; +} + +static inline bool +rb_obj_shape_embedded_p(VALUE obj) +{ + return rb_shape_embedded_p(RBASIC_SHAPE_ID(obj)); +} + +static inline bool +rb_obj_shape_extended_p(VALUE obj) +{ + return rb_shape_extended_p(RBASIC_SHAPE_ID(obj)); +} + // Assigns the entire shape_id. // shape_id_t is composed of two parts: // - The layout and capacity part, which never changes except on GC compaction. @@ -381,49 +405,15 @@ RSHAPE_EDGE_NAME(shape_id_t shape_id) return RSHAPE(shape_id)->edge_name; } -static inline uint32_t -ROBJECT_FIELDS_CAPACITY(VALUE obj) -{ - RBIMPL_ASSERT_TYPE(obj, RUBY_T_OBJECT); - // Asking for capacity doesn't make sense when the object is using - // a hash table for storing instance variables - RUBY_ASSERT(!rb_obj_shape_complex_p(obj)); - return RSHAPE_CAPACITY(RBASIC_SHAPE_ID(obj)); -} - -static inline st_table * -ROBJECT_FIELDS_HASH(VALUE obj) -{ - RBIMPL_ASSERT_TYPE(obj, RUBY_T_OBJECT); - RUBY_ASSERT(rb_obj_shape_complex_p(obj)); - RUBY_ASSERT(FL_TEST_RAW(obj, ROBJECT_HEAP)); - - return rb_imemo_fields_complex_tbl(ROBJECT(obj)->as.extended); -} - -static inline uint32_t -ROBJECT_FIELDS_COUNT_COMPLEX(VALUE obj) -{ - return (uint32_t)rb_st_table_size(ROBJECT_FIELDS_HASH(obj)); -} - -static inline uint32_t -ROBJECT_FIELDS_COUNT_NOT_COMPLEX(VALUE obj) -{ - RBIMPL_ASSERT_TYPE(obj, RUBY_T_OBJECT); - RUBY_ASSERT(!rb_obj_shape_complex_p(obj)); - return RSHAPE(RBASIC_SHAPE_ID(obj))->next_field_index; -} - -static inline uint32_t -ROBJECT_FIELDS_COUNT(VALUE obj) +static inline VALUE * +rb_imemo_fields_ptr(VALUE fields_obj) { - if (rb_obj_shape_complex_p(obj)) { - return ROBJECT_FIELDS_COUNT_COMPLEX(obj); - } - else { - return ROBJECT_FIELDS_COUNT_NOT_COMPLEX(obj); + if (!fields_obj) { + return NULL; } + + RUBY_ASSERT(rb_obj_shape_embedded_p(fields_obj)); + return IMEMO_OBJ_FIELDS(fields_obj)->as.embed.fields; } static inline uint32_t @@ -688,4 +678,42 @@ rb_setivar_cache_revalidate(shape_id_t shape_id, shape_id_t fields_shape_id, rb_ return rb_shape_transition_offset(shape_id, cache.dest_shape_offset); } +static inline st_table * +rb_imemo_fields_complex_tbl(VALUE fields_obj) +{ + if (!fields_obj) { + return NULL; + } + + RUBY_ASSERT(IMEMO_TYPE_P(fields_obj, imemo_fields)); + + // Some codepaths unconditionally access the fields_ptr, and assume it can be used as st_table if the + // shape is complex. + RUBY_ASSERT((st_table *)rb_imemo_fields_ptr(fields_obj) == &IMEMO_OBJ_FIELDS(fields_obj)->as.complex.table); + + return &IMEMO_OBJ_FIELDS(fields_obj)->as.complex.table; +} + +static inline VALUE +ROBJECT_FIELDS_OBJ(VALUE obj) +{ + RBIMPL_ASSERT_TYPE(obj, RUBY_T_OBJECT); + + return rb_obj_shape_embedded_p(obj) ? obj : ROBJECT(obj)->as.extended; +} + +static inline VALUE * +ROBJECT_EMBEDDED_FIELDS(VALUE obj) +{ + return ROBJECT(obj)->as.ary; +} + +static inline VALUE * +ROBJECT_FIELDS(VALUE obj) +{ + RBIMPL_ASSERT_TYPE(obj, RUBY_T_OBJECT); + + return ROBJECT_EMBEDDED_FIELDS(ROBJECT_FIELDS_OBJ(obj)); +} + #endif diff --git a/spec/ruby/core/array/pack/x_spec.rb b/spec/ruby/core/array/pack/x_spec.rb index 7ff587a01edd8d..1d26de6c51e11c 100644 --- a/spec/ruby/core/array/pack/x_spec.rb +++ b/spec/ruby/core/array/pack/x_spec.rb @@ -32,6 +32,22 @@ [].pack("x*").should == "" [1, 2].pack("Cx*C").should == "\x01\x02" end + + ruby_version_is "4.1" do + it "aligns to the given byte boundary with the '!' modifier" do + [1, 2].pack("C x!4 C").should == "\x01\x00\x00\x00\x02" + end + + it "aligns from the beginning of the output with '@!'" do + buffer = +"z" + [1, 2].pack("C @!4 C", buffer: buffer) + buffer.should == "z\x01\x00\x00\x02" + end + + it "aligns to a directive's alignment with the '!' modifier" do + [1, 2].pack("C x!i i").should == [1, 2].pack("i< i") + end + end end describe "Array#pack with format 'X'" do diff --git a/spec/ruby/core/range/clamp_spec.rb b/spec/ruby/core/range/clamp_spec.rb new file mode 100644 index 00000000000000..e50b4d246ae7f1 --- /dev/null +++ b/spec/ruby/core/range/clamp_spec.rb @@ -0,0 +1,83 @@ +require_relative '../../spec_helper' +require_relative 'fixtures/classes' + +ruby_version_is "4.1" do + describe "Range#clamp" do + it "clamps begin and end to min and max" do + (1..10).clamp(3, 7).should == (3..7) + (..10).clamp(3, 7).should == (3..7) + end + + it "clamps begin and end to a Range" do + (1..10).clamp(3..7).should == (3..7) + (1..5).clamp(3...7).should == (3..5) + (1..10).clamp(RangeSpecs::DuckRange.new(3, 7, true)).should == (3...7) + end + + it "treats min and max like an inclusive Range" do + (1...10).clamp(3, 7).should == (1...10).clamp(3..7) + (1...10).clamp(3, 10).should == (1...10).clamp(3..10) + end + + it "excludes the end when the returned end is excluded by self or the argument Range" do + (1...10).clamp(3, 10).should == (3...10) + (1...10).clamp(3..10).should == (3...10) + (1..10).clamp(3...10).should == (3...10) + (..10).clamp(3...10).should == (3...10) + end + + it "includes the end when the returned end is not an excluded end" do + (1...10).clamp(3, 7).should == (3..7) + (0...).clamp(0, 10).should == (0..10) + (1..5).clamp(3...7).should == (3..5) + (..5).clamp(3...7).should == (3..5) + (1..10).clamp(3...).should == (3..10) + end + + it "handles beginless and endless argument Ranges" do + (1..10).clamp(..7).should == (1..7) + (1..10).clamp(...7).should == (1...7) + (1..5).clamp(...7).should == (1..5) + + (1..10).clamp(3..).should == (3..10) + (1..).clamp(3..).should == (3..) + (1...).clamp(3...).should == (3...) + end + + it "returns an inclusive point Range when begin and end are clamped to different equal bounds" do + (1..10).clamp(3, 3).should == (3..3) + (1..10).clamp(3..3).should == (3..3) + end + + it "returns an empty Range when begin and end are clamped to the same side" do + (1..10).clamp(20, 30).should == (20...20) + (1..10).clamp(-10, 0).should == (0...0) + (..10).clamp(20, 30).should == (20...20) + (1..10).clamp(20..30).should == (20...20) + (1..10).clamp(-10..0).should == (0...0) + (..10).clamp(20..30).should == (20...20) + (1..2).clamp(3, 3).should == (3...3) + (4..10).clamp(3, 3).should == (3...3) + (1..).clamp(nil, 0).should == (0...0) + (1..).clamp(..0).should == (0...0) + end + + it "returns a Range instance, not an instance of a subclass" do + RangeSpecs::MyRange.new(1, 10).clamp(3, 7).should.instance_of?(Range) + end + + it "raises ArgumentError when min and max are not comparable" do + -> { (1..3).clamp(1, "z") }.should.raise(ArgumentError) + -> { (1..3).clamp("a", "z") }.should.raise(ArgumentError) + end + + it "raises ArgumentError when min is greater than max" do + -> { (1..3).clamp(2, 1) }.should.raise(ArgumentError) + -> { (1..3).clamp(2..1) }.should.raise(ArgumentError) + end + + it "raises TypeError when the single argument is not a Range" do + -> { (1..3).clamp(1) }.should.raise(TypeError) + end + end +end diff --git a/spec/ruby/core/range/fixtures/classes.rb b/spec/ruby/core/range/fixtures/classes.rb index bdabfb0d1c8176..5176cd42032226 100644 --- a/spec/ruby/core/range/fixtures/classes.rb +++ b/spec/ruby/core/range/fixtures/classes.rb @@ -156,6 +156,20 @@ def inspect class MyRange < Range end + class DuckRange + def initialize(beg, end_, exclude_end = false) + @begin = beg + @end = end_ + @exclude_end = exclude_end + end + + attr_reader :begin, :end + + def exclude_end? + @exclude_end + end + end + class ComparisonError < RuntimeError end end diff --git a/spec/ruby/core/string/unpack/x_spec.rb b/spec/ruby/core/string/unpack/x_spec.rb index fb2e79fc1f765a..a93a4dba995d50 100644 --- a/spec/ruby/core/string/unpack/x_spec.rb +++ b/spec/ruby/core/string/unpack/x_spec.rb @@ -59,4 +59,18 @@ it "raises an ArgumentError if the count exceeds the size of the String" do -> { "\x01\x02\x03\x04".unpack("C2x3C") }.should.raise(ArgumentError) end + + ruby_version_is "4.1" do + it "aligns to the given byte boundary with the '!' modifier" do + "\x01\x00\x00\x00\x02".unpack("C x!4 C").should == [1, 2] + end + + it "aligns from the beginning of the String with '@!'" do + "z\x01\x00\x00\x02".unpack("C @!4 C", offset: 1).should == [1, 2] + end + + it "aligns to a directive's alignment with the '!' modifier" do + [1, 2].pack("i< i").unpack("C x!i i").should == [1, 2] + end + end end diff --git a/test/prism/api/parse_stream_test.rb b/test/prism/api/parse_stream_test.rb index 3bc86fbd6167b6..ffbedc915d1491 100644 --- a/test/prism/api/parse_stream_test.rb +++ b/test/prism/api/parse_stream_test.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require_relative "../test_helper" +require "timeout" module Prism class ParseStreamTest < TestCase @@ -43,6 +44,18 @@ def test___END__ assert_equal "5 + 6\n", io.read end + # __END__ as the final line with no trailing newline must still be detected + # as the terminator. This exercises the EOF check for a chunk that does not + # end in a newline, distinct from a line longer than the internal read + # buffer (which also has no trailing newline but is not the end of input). + def test___END___without_trailing_newline + io = StringIO.new("1 + 2\n__END__") + result = Prism.parse_stream(io) + + assert result.success? + assert_equal 1, result.value.statements.body.length + end + def test_false___END___in_string io = StringIO.new(<<~RUBY) 1 + 2 @@ -114,5 +127,70 @@ def test_nul_bytes assert result.success? assert_equal 3, result.value.statements.body.length end + + # `IO#gets(limit)` will not split a multi-byte character, so it can return + # more bytes than requested when the limit falls in the middle of one. When + # that character straddles the internal read buffer boundary, the old code + # wrote past the buffer and dropped the overflowing bytes, corrupting the + # content (and overflowing the stack). Sweep offsets around the 4096-byte + # boundary with both 3- and 4-byte characters so the straddle is hit + # regardless of the exact internal read size. + def test_multibyte_on_read_boundary + ["あ", "\u{1F600}"].each do |char| + (4080..4100).each do |prefix| + body = ("a" * prefix) + char + result = Prism.parse_stream(StringIO.new("\"#{body}\"")) + + assert result.success?, "parse failed at prefix=#{prefix} char=#{char.dump}" + assert_equal body, result.value.statements.body[0].content, "content mismatch at prefix=#{prefix} char=#{char.dump}" + end + end + end + + # A misbehaving stream whose `gets` ignores its limit and returns an + # arbitrarily long string must not overflow the internal buffer. The old + # code copied the full returned length into a fixed 4096-byte buffer. + def test_gets_exceeding_limit + stream = Object.new + def stream.gets(limit = nil) + return nil if defined?(@done) && @done + @done = true + ("x" * 100_000) + "\n" + end + def stream.eof?; defined?(@done) && @done; end + + result = Prism.parse_stream(stream) + assert result.success? + end + + # A misbehaving stream whose `gets` returns a non-String must not be read as + # a byte buffer. The old code called RSTRING_PTR on whatever was returned. + def test_gets_returning_non_string + stream = Object.new + def stream.gets(limit = nil) + return nil if defined?(@done) && @done + @done = true + 1234 + end + def stream.eof?; defined?(@done) && @done; end + + assert_nothing_raised do + Prism.parse_stream(stream) + end + end + + # A misbehaving stream whose `gets` returns an empty string instead of nil + # while never reporting EOF must not loop forever. A well-behaved stream + # returns nil at EOF, so an empty chunk is treated as the end of input. + def test_gets_returning_empty_string + stream = Object.new + def stream.gets(limit = nil); ""; end + def stream.eof?; false; end + + result = Timeout.timeout(10) { Prism.parse_stream(stream) } + assert result.success? + rescue Timeout::Error + flunk "Prism.parse_stream looped forever on a stream returning empty strings" + end end end diff --git a/test/prism/newline_test.rb b/test/prism/newline_test.rb index 97e698202df748..ffaadf952c9d0f 100644 --- a/test/prism/newline_test.rb +++ b/test/prism/newline_test.rb @@ -12,6 +12,7 @@ class NewlineTest < TestCase regexp_test.rb test_helper.rb unescape_test.rb + api/parse_stream_test.rb encoding/regular_expression_encoding_test.rb encoding/string_encoding_test.rb result/breadth_first_search_test.rb diff --git a/test/ruby/test_pack.rb b/test/ruby/test_pack.rb index 6e5f0fe7ff948f..e1dd3968378204 100644 --- a/test/ruby/test_pack.rb +++ b/test/ruby/test_pack.rb @@ -939,6 +939,21 @@ def test_unpack_offset assert_equal [nil], "a".unpack("C", offset: 1) end + def test_pack_alignment + assert_equal "\x01\x00\x00\x00\x02".b, [1, 2].pack("C x!4 C") + assert_equal [1, 2], "\x01\x00\x00\x00\x02".b.unpack("C x!4 C") + + buffer = +"z" + [1, 2].pack("C x!4 C", buffer: buffer) + assert_equal "z\x01\x00\x00\x00\x02".b, buffer + assert_equal [1, 2], buffer.unpack("C x!4 C", offset: 1) + + buffer = +"z" + [1, 2].pack("C @!4 C", buffer: buffer) + assert_equal "z\x01\x00\x00\x02".b, buffer + assert_equal [1, 2], buffer.unpack("C @!4 C", offset: 1) + end + def test_monkey_pack assert_separately([], <<-'end;') $-w = false diff --git a/test/ruby/test_range.rb b/test/ruby/test_range.rb index ff17dca69eeb96..e104f4a80bb3d7 100644 --- a/test/ruby/test_range.rb +++ b/test/ruby/test_range.rb @@ -1548,4 +1548,70 @@ def test_overlap? assert_not_operator((1...3), :overlap?, (3..4)) assert_not_operator((...3), :overlap?, (3..)) end + + def test_clamp + # Clamp begin and end to min and max. + assert_equal(3..7, (1..10).clamp(3, 7)) + assert_equal(3..7, (..10).clamp(3, 7)) + + # Clamp begin and end to a range. + assert_equal(3..7, (1..10).clamp(3..7)) + assert_equal(3..5, (1..5).clamp(3...7)) + assert_equal(3...7, (1..10).clamp(DuckRange.new(3, 7, true))) + + # Treat min and max like an inclusive range. + assert_equal((1...10).clamp(3..7), (1...10).clamp(3, 7)) + assert_equal((1...10).clamp(3..10), (1...10).clamp(3, 10)) + + # Exclude the end when the returned end is excluded by self or the + # argument range. + assert_equal(3...10, (1...10).clamp(3, 10)) + assert_equal(3...10, (1...10).clamp(3..10)) + assert_equal(3...10, (1..10).clamp(3...10)) + assert_equal(3...10, (..10).clamp(3...10)) + + # Include the end when the returned end is not an excluded end. + assert_equal(3..7, (1...10).clamp(3, 7)) + assert_equal(0..10, (0...).clamp(0, 10)) + assert_equal(3..5, (1..5).clamp(3...7)) + assert_equal(3..5, (..5).clamp(3...7)) + assert_equal(3..10, (1..10).clamp(3...)) + + # Handle beginless and endless argument ranges. + assert_equal(1..7, (1..10).clamp(..7)) + assert_equal(1...7, (1..10).clamp(...7)) + assert_equal(1..5, (1..5).clamp(...7)) + + assert_equal(3..10, (1..10).clamp(3..)) + assert_equal(3.., (1..).clamp(3..)) + assert_equal(3..., (1...).clamp(3...)) + + # Return an inclusive point range when begin and end are clamped to + # different equal bounds. + assert_equal(3..3, (1..10).clamp(3, 3)) + assert_equal(3..3, (1..10).clamp(3..3)) + + # Return an empty range when begin and end are clamped to the same side. + assert_equal(20...20, (1..10).clamp(20, 30)) + assert_equal(0...0, (1..10).clamp(-10, 0)) + assert_equal(20...20, (..10).clamp(20, 30)) + assert_equal(20...20, (1..10).clamp(20..30)) + assert_equal(0...0, (1..10).clamp(-10..0)) + assert_equal(20...20, (..10).clamp(20..30)) + assert_equal(3...3, (1..2).clamp(3, 3)) + assert_equal(3...3, (4..10).clamp(3, 3)) + assert_equal(0...0, (1..).clamp(nil, 0)) + assert_equal(0...0, (1..).clamp(..0)) + + # Return a Range instance, not an instance of a subclass. + subclass = Class.new(Range) + assert_instance_of(Range, subclass.new(1, 10).clamp(3, 7)) + + # Raise for invalid bounds or argument types. + assert_raise(ArgumentError) {(1..3).clamp(1, "z")} + assert_raise(ArgumentError) {(1..3).clamp("a", "z")} + assert_raise(ArgumentError) {(1..3).clamp(2, 1)} + assert_raise(ArgumentError) {(1..3).clamp(2..1)} + assert_raise(TypeError) {(1..3).clamp(1)} + end end diff --git a/test/socket/test_tcp.rb b/test/socket/test_tcp.rb index d689ab23765c81..cfa1413d3699f8 100644 --- a/test/socket/test_tcp.rb +++ b/test/socket/test_tcp.rb @@ -230,16 +230,22 @@ def test_initialize_v6_hostname_resolved_in_resolution_delay port = server.addr[1] delay_time = 25 # Socket::RESOLUTION_DELAY (private) is 50ms - server_thread = Thread.new { server.accept } + accepted = nil + server_thread = Thread.new { accepted = server.accept } socket = TCPSocket.new( "localhost", port, fast_fallback: true, test_mode_settings: { delay: { ipv6: delay_time } } ) - assert_true(socket.remote_address.ipv6?) + # Another process may be listening on the same port on 127.0.0.1. + omit "connected to an unrelated process on the same port" unless socket.remote_address.ipv6? + server_thread.join(3) + assert_not_nil(accepted, "server did not accept the connection") + assert_equal(socket.local_address.to_sockaddr, accepted.remote_address.to_sockaddr) ensure - server_thread&.value&.close + stop_accept_thread(server_thread, socket) + accepted&.close server&.close socket&.close end @@ -252,16 +258,21 @@ def test_initialize_v6_hostname_resolved_earlier_and_v6_server_is_not_listening server.bind(Socket.pack_sockaddr_in(0, ipv4_address)) port = server.connect_address.ip_port - server_thread = Thread.new { server.listen(1); server.accept } + accepted = nil + server_thread = Thread.new { server.listen(1); accepted, _ = server.accept } socket = TCPSocket.new( "localhost", port, fast_fallback: true, test_mode_settings: { delay: { ipv4: 10 } } ) - assert_equal(ipv4_address, socket.remote_address.ip_address) + # Another process may be listening on the same port on ::1. + omit "connected to an unrelated process on the same port" if socket.remote_address.ipv6? + server_thread.join(3) + assert_not_nil(accepted, "server did not accept the connection") + assert_equal(socket.local_address.to_sockaddr, accepted.remote_address.to_sockaddr) ensure - accepted, _ = server_thread&.value + stop_accept_thread(server_thread, socket) accepted&.close server&.close socket&.close @@ -274,16 +285,21 @@ def test_initialize_v6_hostname_resolved_later_and_v6_server_is_not_listening server.bind(Socket.pack_sockaddr_in(0, "127.0.0.1")) port = server.connect_address.ip_port - server_thread = Thread.new { server.listen(1); server.accept } + accepted = nil + server_thread = Thread.new { server.listen(1); accepted, _ = server.accept } socket = TCPSocket.new( "localhost", port, fast_fallback: true, test_mode_settings: { delay: { ipv6: 25 } } ) - assert_true(socket.remote_address.ipv4?) + # Another process may be listening on the same port on ::1. + omit "connected to an unrelated process on the same port" if socket.remote_address.ipv6? + server_thread.join(3) + assert_not_nil(accepted, "server did not accept the connection") + assert_equal(socket.local_address.to_sockaddr, accepted.remote_address.to_sockaddr) ensure - accepted, _ = server_thread&.value + stop_accept_thread(server_thread, socket) accepted&.close server&.close socket&.close @@ -420,4 +436,15 @@ def test_initialize_fast_fallback_is_false server&.close socket&.close end + + private + + # On success, wait for the accept thread to finish. If the test failed + # before the client connected, `server.accept` never returns, so kill + # the thread instead of waiting for it. + def stop_accept_thread(server_thread, socket) + return unless server_thread + server_thread.join(1) if socket + server_thread.kill.join + end end if defined?(TCPSocket) diff --git a/test/test_ipaddr.rb b/test/test_ipaddr.rb index 9725ab31c173dc..fe940204e4207b 100644 --- a/test/test_ipaddr.rb +++ b/test/test_ipaddr.rb @@ -623,6 +623,51 @@ def test_link_local? assert_equal(false, IPAddr.new('2001:db8:1:1:0:ffff:a9fe:101').link_local?) end + def test_multicast? + assert_equal(false, IPAddr.new('192.168.0.0').multicast?) + assert_equal(false, IPAddr.new('169.254.1.1').multicast?) + assert_equal(false, IPAddr.new('169.254.254.255').multicast?) + + # notable ipv4 multicast addresses + assert_equal(true, IPAddr.new('224.0.0.0').multicast?) + assert_equal(true, IPAddr.new('224.0.0.1').multicast?) + assert_equal(true, IPAddr.new('224.0.0.6').multicast?) + assert_equal(true, IPAddr.new('224.0.1.41').multicast?) + assert_equal(true, IPAddr.new('224.0.1.129').multicast?) + assert_equal(true, IPAddr.new('224.0.23.12').multicast?) + assert_equal(true, IPAddr.new('239.255.255.250').multicast?) + assert_equal(true, IPAddr.new('239.255.255.253').multicast?) + + assert_equal(false, IPAddr.new('::1').multicast?) + assert_equal(false, IPAddr.new('::').multicast?) + assert_equal(false, IPAddr.new('fb84:8bf7:e905::1').multicast?) + + # notable ipv6 multicast addresses + assert_equal(true, IPAddr.new('ff02::1').multicast?) + assert_equal(true, IPAddr.new('ff02::2').multicast?) + assert_equal(true, IPAddr.new('ff02::5').multicast?) + assert_equal(true, IPAddr.new('ff02::6').multicast?) + assert_equal(true, IPAddr.new('ff02::8').multicast?) + assert_equal(true, IPAddr.new('ff02::1:2').multicast?) + assert_equal(true, IPAddr.new('ff02::1:3').multicast?) + assert_equal(true, IPAddr.new('ff05::101').multicast?) + end + + def test_link_local_multicast? + assert_equal(false, IPAddr.new('192.168.0.0').link_local_multicast?) + assert_equal(false, IPAddr.new('169.254.1.1').link_local_multicast?) + assert_equal(false, IPAddr.new('169.254.254.255').link_local_multicast?) + assert_equal(false, IPAddr.new('239.0.0.0').link_local_multicast?) + + assert_equal(true, IPAddr.new('224.0.0.0').link_local_multicast?) + + assert_equal(false, IPAddr.new('::1').link_local_multicast?) + assert_equal(false, IPAddr.new('::').link_local_multicast?) + assert_equal(false, IPAddr.new('ff05::1').link_local_multicast?) + assert_equal(true, IPAddr.new('ff02::1').link_local_multicast?) + assert_equal(true, IPAddr.new('ff02::2').link_local_multicast?) + end + def test_hash a1 = IPAddr.new('192.168.2.0') a2 = IPAddr.new('192.168.2.0') diff --git a/time.c b/time.c index d6826d7aad9ac8..d9afd3fd18ccb0 100644 --- a/time.c +++ b/time.c @@ -2233,6 +2233,10 @@ invalid_utc_offset(VALUE zone) static VALUE utc_offset_arg(VALUE arg) { + if (RB_INTEGER_TYPE_P(arg)) { + return arg; + } + VALUE tmp; if (!NIL_P(tmp = rb_check_string_type(arg))) { int n = 0; @@ -4140,7 +4144,7 @@ static VALUE time_zonelocal(VALUE time, VALUE off) { VALUE zone = off; - if (zone_localtime(zone, time)) return time; + if (maybe_tzobj_p(zone) && zone_localtime(zone, time)) return time; if (NIL_P(off = utc_offset_arg(off))) { off = zone; diff --git a/tool/test-bundled-gems.rb b/tool/test-bundled-gems.rb index 618ce4b909aa0f..a8fbae2b793c9d 100644 --- a/tool/test-bundled-gems.rb +++ b/tool/test-bundled-gems.rb @@ -116,6 +116,9 @@ first_timeout *= 3 when "debug" + # needs pty + next unless /mswin|mingw/ =~ RUBY_PLATFORM + # Since debug gem requires debug.so in child processes without # activating the gem, we preset necessary paths in RUBYLIB # environment variable. @@ -128,6 +131,8 @@ when "csv" first_timeout = 30 + test_command = [ruby, *run_opts, "-C", "#{gem_dir}/src/#{gem}", "run-test.rb"] + test_command << "--ignore-name=/ractor/" if /mswin|mingw/ =~ RUBY_PLATFORM when "win32ole" next unless /mswin|mingw/ =~ RUBY_PLATFORM @@ -152,6 +157,10 @@ end end +heavy_tests = %w[rbs debug reline win32ole irb drb net-imap rdoc typeprof racc rake] +others = heavy_tests.size +jobs.sort_by! {|job| heavy_tests.index(job[:gem]) || (others += 1)} + results = Array.new(jobs.size) queue = Queue.new jobs.each_with_index { |j, i| queue << [j, i] } diff --git a/variable.c b/variable.c index c29054b57652d2..2f4e4a387faa3a 100644 --- a/variable.c +++ b/variable.c @@ -1293,27 +1293,34 @@ obj_use_generic_fields_tbl_p(VALUE obj) VALUE rb_obj_fields(VALUE obj, ID field_name) { - RUBY_ASSERT(!RB_TYPE_P(obj, T_IMEMO)); ivar_ractor_check(obj, field_name); - VALUE fields_obj = 0; - if (rb_obj_shape_has_fields(obj)) { - switch (BUILTIN_TYPE(obj)) { - case T_OBJECT: - fields_obj = ROBJECT_FIELDS_OBJ(obj); - break; - case T_DATA: - fields_obj = RTYPEDDATA(obj)->fields_obj; - break; - case T_STRUCT: - if (LIKELY(!FL_TEST_RAW(obj, RSTRUCT_GEN_FIELDS))) { - fields_obj = RSTRUCT_FIELDS_OBJ(obj); - break; - } - goto generic_fields; - default: - generic_fields: - { + switch (BUILTIN_TYPE(obj)) { + case T_IMEMO: + RUBY_ASSERT(IMEMO_TYPE_P(obj, imemo_fields)); + return obj; + + case T_OBJECT: + return ROBJECT_FIELDS_OBJ(obj); + + case T_CLASS: + case T_MODULE: + return RCLASS_WRITABLE_FIELDS_OBJ(obj); + + case T_DATA: + return RTYPEDDATA(obj)->fields_obj; + + case T_STRUCT: + if (LIKELY(!FL_TEST_RAW(obj, RSTRUCT_GEN_FIELDS))) { + return RSTRUCT_FIELDS_OBJ(obj); + } + goto generic_fields; + default: + generic_fields: + { + VALUE fields_obj = 0; + + if (rb_obj_shape_has_fields(obj)) { rb_execution_context_t *ec = GET_EC(); if (ec->gen_fields_cache.obj == obj && !UNDEF_P(ec->gen_fields_cache.fields_obj) && rb_imemo_fields_owner(ec->gen_fields_cache.fields_obj) == obj) { fields_obj = ec->gen_fields_cache.fields_obj; @@ -1325,9 +1332,10 @@ rb_obj_fields(VALUE obj, ID field_name) ec->gen_fields_cache.obj = obj; } } + + return fields_obj; } } - return fields_obj; } void @@ -1450,24 +1458,7 @@ rb_obj_field_get(VALUE obj, shape_id_t target_shape_id) RUBY_ASSERT(!SPECIAL_CONST_P(obj)); RUBY_ASSERT(RSHAPE_TYPE_P(target_shape_id, SHAPE_IVAR) || RSHAPE_TYPE_P(target_shape_id, SHAPE_OBJ_ID)); - VALUE fields_obj; - - switch (BUILTIN_TYPE(obj)) { - case T_CLASS: - case T_MODULE: - fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj); - break; - case T_OBJECT: - fields_obj = ROBJECT_FIELDS_OBJ(obj); - break; - case T_IMEMO: - RUBY_ASSERT(IMEMO_TYPE_P(obj, imemo_fields)); - fields_obj = obj; - break; - default: - fields_obj = rb_obj_fields(obj, RSHAPE_EDGE_NAME(target_shape_id)); - break; - } + VALUE fields_obj = rb_obj_fields(obj, RSHAPE_EDGE_NAME(target_shape_id)); if (UNLIKELY(rb_shape_complex_p(target_shape_id))) { st_table *fields_hash = rb_imemo_fields_complex_tbl(fields_obj); @@ -1486,35 +1477,9 @@ rb_ivar_lookup(VALUE obj, ID id, VALUE undef) { if (SPECIAL_CONST_P(obj)) return undef; - VALUE fields_obj; - - switch (BUILTIN_TYPE(obj)) { - case T_CLASS: - case T_MODULE: - { - VALUE val = rb_ivar_lookup(RCLASS_WRITABLE_FIELDS_OBJ(obj), id, undef); - if (val != undef && - rb_is_instance_id(id) && - UNLIKELY(!rb_ractor_main_p()) && - !rb_ractor_shareable_p(val)) { - rb_raise(rb_eRactorIsolationError, - "can not get unshareable values from instance variables of classes/modules from non-main Ractors (%"PRIsVALUE" from %"PRIsVALUE")", - rb_id2str(id), obj); - } - return val; - } - case T_IMEMO: - // Handled like T_OBJECT - RUBY_ASSERT(IMEMO_TYPE_P(obj, imemo_fields)); - fields_obj = obj; - break; - case T_OBJECT: - fields_obj = ROBJECT_FIELDS_OBJ(obj); - break; - default: - fields_obj = rb_obj_fields(obj, id); - break; - } + int type = BUILTIN_TYPE(obj); + bool is_class = type == T_CLASS || type == T_MODULE; + VALUE fields_obj = rb_obj_fields(obj, is_class ? 0 : id); if (!fields_obj) { return undef; @@ -1522,21 +1487,34 @@ rb_ivar_lookup(VALUE obj, ID id, VALUE undef) shape_id_t shape_id = RBASIC_SHAPE_ID(fields_obj); + VALUE val = undef; if (UNLIKELY(rb_shape_complex_p(shape_id))) { st_table *iv_table = rb_imemo_fields_complex_tbl(fields_obj); - VALUE val; - if (rb_st_lookup(iv_table, (st_data_t)id, (st_data_t *)&val)) { - return val; + if (!rb_st_lookup(iv_table, (st_data_t)id, (st_data_t *)&val)) { + return undef; } - return undef; + } + else { + attr_index_t index = 0; + if (!rb_shape_get_iv_index(shape_id, id, &index)) { + return undef; + } + val = rb_imemo_fields_ptr(fields_obj)[index]; } - attr_index_t index = 0; - if (rb_shape_get_iv_index(shape_id, id, &index)) { - return rb_imemo_fields_ptr(fields_obj)[index]; + if (is_class && val != undef && rb_is_instance_id(id)) { + if (UNLIKELY(!rb_ractor_main_p()) && !rb_ractor_shareable_p(val)) { + rb_raise( + rb_eRactorIsolationError, + "can not get unshareable values from instance variables of classes/modules from " + "non-main Ractors (%"PRIsVALUE" from %"PRIsVALUE")", + rb_id2str(id), + obj + ); + } } - return undef; + return val; } VALUE @@ -1582,18 +1560,7 @@ rb_ivar_get_at_no_ractor_check(VALUE obj, attr_index_t index) { // Used by JITs, but never for T_OBJECT. - VALUE fields_obj; - switch (BUILTIN_TYPE(obj)) { - case T_OBJECT: - UNREACHABLE_RETURN(Qundef); - case T_CLASS: - case T_MODULE: - fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj); - break; - default: - fields_obj = rb_obj_fields_no_ractor_check(obj); - break; - } + VALUE fields_obj = rb_obj_fields_no_ractor_check(obj); return rb_imemo_fields_ptr(fields_obj)[index]; } @@ -1624,29 +1591,18 @@ rb_ivar_delete(VALUE obj, ID id, VALUE undef) rb_check_frozen(obj); VALUE val = undef; - VALUE fields_obj; bool concurrent = false; int type = BUILTIN_TYPE(obj); - switch(type) { - case T_CLASS: - case T_MODULE: + if (type == T_CLASS || type == T_MODULE) { IVAR_ACCESSOR_SHOULD_BE_MAIN_RACTOR(id); - fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj); if (rb_multi_ractor_p()) { concurrent = true; } - break; - case T_OBJECT: - fields_obj = ROBJECT_FIELDS_OBJ(obj); - break; - default: { - fields_obj = rb_obj_fields(obj, id); - break; - } } + VALUE fields_obj = rb_obj_fields(obj, id); if (!fields_obj) { return undef; } @@ -1710,10 +1666,7 @@ rb_ivar_delete(VALUE obj, ID id, VALUE undef) if (fields_obj != original_fields_obj) { switch (type) { case T_OBJECT: - if (!fields_obj) { - FL_UNSET_RAW(obj, ROBJECT_HEAP); - } - else if (fields_obj != obj) { + if (fields_obj && fields_obj != obj) { ROBJECT_SET_EXTENDED(obj, fields_obj); } break; @@ -2066,32 +2019,12 @@ rb_obj_field_set(VALUE obj, shape_id_t target_shape_id, ID field_name, VALUE val static VALUE ivar_defined0(VALUE obj, ID id) { - attr_index_t index; - if (rb_obj_shape_complex_p(obj)) { - VALUE idx; - st_table *table = NULL; - switch (BUILTIN_TYPE(obj)) { - case T_CLASS: - case T_MODULE: - rb_bug("Unreachable"); - break; - - case T_IMEMO: - RUBY_ASSERT(IMEMO_TYPE_P(obj, imemo_fields)); - table = rb_imemo_fields_complex_tbl(obj); - break; - - case T_OBJECT: - table = ROBJECT_FIELDS_HASH(obj); - break; - - default: { - VALUE fields_obj = rb_obj_fields_no_ractor_check(obj); // defined? doesn't require ractor checks - table = rb_imemo_fields_complex_tbl(fields_obj); - } - } + // defined? doesn't require ractor checks + VALUE fields_obj = rb_obj_fields_no_ractor_check(obj); + st_table *table = rb_imemo_fields_complex_tbl(fields_obj); + VALUE idx; if (!table || !rb_st_lookup(table, id, &idx)) { return Qfalse; } @@ -2099,6 +2032,7 @@ ivar_defined0(VALUE obj, ID id) return Qtrue; } else { + attr_index_t index; return RBOOL(rb_shape_get_iv_index(RBASIC_SHAPE_ID(obj), id, &index)); } } @@ -2201,12 +2135,14 @@ obj_fields_each(VALUE obj, rb_ivar_foreach_callback_func *func, st_data_t arg, b .ivar_only = ivar_only, }; + VALUE fields_obj = ROBJECT_FIELDS_OBJ(obj); shape_id_t shape_id = RBASIC_SHAPE_ID(obj); + if (rb_shape_complex_p(shape_id)) { - st_foreach_safe(ROBJECT_FIELDS_HASH(obj), each_hash_iv, (st_data_t)&itr_data); + st_foreach_safe(rb_imemo_fields_complex_tbl(fields_obj), each_hash_iv, (st_data_t)&itr_data); } else { - itr_data.fields = ROBJECT_FIELDS(obj); + itr_data.fields = rb_imemo_fields_ptr(fields_obj); itr_data.shape_id = shape_id; iterate_over_shapes(shape_id, func, &itr_data); } @@ -2374,51 +2310,15 @@ rb_ivar_count(VALUE obj) if (SPECIAL_CONST_P(obj)) return 0; st_index_t iv_count = 0; - switch (BUILTIN_TYPE(obj)) { - case T_OBJECT: - iv_count = ROBJECT_FIELDS_COUNT(obj); - break; - - case T_CLASS: - case T_MODULE: - { - VALUE fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj); - if (!fields_obj) { - return 0; - } - if (rb_obj_shape_complex_p(fields_obj)) { - iv_count = rb_st_table_size(rb_imemo_fields_complex_tbl(fields_obj)); - } - else { - iv_count = RBASIC_FIELDS_COUNT(fields_obj); - } - } - break; - - case T_IMEMO: - RUBY_ASSERT(IMEMO_TYPE_P(obj, imemo_fields)); + VALUE fields_obj = rb_obj_fields_no_ractor_check(obj); - if (rb_obj_shape_complex_p(obj)) { - iv_count = rb_st_table_size(rb_imemo_fields_complex_tbl(obj)); + if (fields_obj) { + if (rb_obj_shape_complex_p(fields_obj)) { + iv_count = rb_st_table_size(rb_imemo_fields_complex_tbl(fields_obj)); } else { iv_count = RBASIC_FIELDS_COUNT(obj); } - break; - - default: - { - VALUE fields_obj = rb_obj_fields_no_ractor_check(obj); - if (fields_obj) { - if (rb_obj_shape_complex_p(fields_obj)) { - iv_count = rb_st_table_size(rb_imemo_fields_complex_tbl(fields_obj)); - } - else { - iv_count = RBASIC_FIELDS_COUNT(obj); - } - } - } - break; } if (rb_obj_shape_has_id(obj)) { diff --git a/win32/mkexports.rb b/win32/mkexports.rb index 44bda94990d52f..dc628314b94d37 100755 --- a/win32/mkexports.rb +++ b/win32/mkexports.rb @@ -102,7 +102,7 @@ def each_line(objs, &block) end def each_export(objs) - noprefix = ($arch ||= nil and /^(sh|i\d86)/ !~ $arch) + noprefix = ($arch ||= nil and /^i\d86/ !~ $arch) objs = objs.collect {|s| s.tr('/', '\\')} filetype = nil objdump(objs) do |l| diff --git a/win32/setup.mak b/win32/setup.mak index db0445302275f7..43fe24d043f905 100644 --- a/win32/setup.mak +++ b/win32/setup.mak @@ -33,7 +33,6 @@ i386-mswin32: -prologue- -i386- -epilogue- i486-mswin32: -prologue- -i486- -epilogue- i586-mswin32: -prologue- -i586- -epilogue- i686-mswin32: -prologue- -i686- -epilogue- -alpha-mswin32: -prologue- -alpha- -epilogue- x64-mswin64: -prologue- -x64- -epilogue- arm64-mswin64: -prologue- -arm64- -epilogue- @@ -214,7 +213,7 @@ set /a MSC_VER_UPPER = MSC_VER/20*20+19 #elif _MSC_VER >= 1900 set /a MSC_VER_LOWER = MSC_VER/10*10+0 set /a MSC_VER_UPPER = MSC_VER/10*10+9 -#elif _MSC_VER < 1400 +#else # error Unsupported VC++ compiler #endif set MSC_VER @@ -233,14 +232,6 @@ MACHINE = x86 $(CPU) = $(PROCESSOR_LEVEL) !endif #endif - --alpha-: -osname32- - @$(CPP) -Tc <<"checking if compiler is for $(@:-=)" >>$(MAKEFILE) -#ifndef _M_ALPHA -#error Not compiler for $(@:-=) -#else -MACHINE = $(@:-=) -#endif << -x64-: -osname64- diff --git a/win32/win32.c b/win32/win32.c index 00a87ff226d60c..69fc706b2e062a 100644 --- a/win32/win32.c +++ b/win32/win32.c @@ -6708,9 +6708,6 @@ rb_w32_pipe(int fds[2]) static int console_emulator_p(void) { -#ifdef _WIN32_WCE - return FALSE; -#else const void *const func = WriteConsoleW; HMODULE k; MEMORY_BASIC_INFORMATION m; @@ -6722,7 +6719,6 @@ console_emulator_p(void) k = GetModuleHandle("kernel32.dll"); if (!k) return FALSE; return (HMODULE)m.AllocationBase != k; -#endif } /* License: Ruby's */ diff --git a/yjit.c b/yjit.c index 99375a1cdac876..7999c10d302c69 100644 --- a/yjit.c +++ b/yjit.c @@ -439,6 +439,12 @@ rb_yjit_shape_obj_complex_p(VALUE obj) return rb_obj_shape_complex_p(obj); } +bool +rb_yjit_shape_obj_embedded_p(VALUE obj) +{ + return rb_obj_shape_embedded_p(obj); +} + attr_index_t rb_yjit_shape_capacity(shape_id_t shape_id) { diff --git a/yjit/bindgen/src/main.rs b/yjit/bindgen/src/main.rs index 373d5abb42c835..4352625a57c896 100644 --- a/yjit/bindgen/src/main.rs +++ b/yjit/bindgen/src/main.rs @@ -88,6 +88,7 @@ fn main() { .allowlist_function("rb_shape_get_iv_index") .allowlist_function("rb_shape_transition_add_ivar_no_warnings") .allowlist_function("rb_yjit_shape_obj_complex_p") + .allowlist_function("rb_yjit_shape_obj_embedded_p") .allowlist_function("rb_yjit_shape_capacity") .allowlist_function("rb_yjit_shape_index") .allowlist_var("SHAPE_ID_NUM_BITS") diff --git a/yjit/src/cruby.rs b/yjit/src/cruby.rs index 8b99a8154a665f..a5514115f11ad8 100644 --- a/yjit/src/cruby.rs +++ b/yjit/src/cruby.rs @@ -453,9 +453,7 @@ impl VALUE { } pub fn embedded_p(self) -> bool { - unsafe { - FL_TEST_RAW(self, VALUE(ROBJECT_HEAP as usize)) == VALUE(0) - } + unsafe { rb_yjit_shape_obj_embedded_p(self) } } pub fn as_isize(self) -> isize { diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index 48569908056e46..72152ae278e3c7 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -271,8 +271,6 @@ pub const RARRAY_EMBED_LEN_SHIFT: ruby_rarray_consts = 15; pub type ruby_rarray_consts = u32; pub const RMODULE_IS_REFINEMENT: ruby_rmodule_flags = 8192; pub type ruby_rmodule_flags = u32; -pub const ROBJECT_HEAP: ruby_robject_flags = 65536; -pub type ruby_robject_flags = u32; pub type rb_block_call_func = ::std::option::Option< unsafe extern "C" fn( yielded_arg: VALUE, @@ -1219,6 +1217,7 @@ extern "C" { ); pub fn rb_object_shape_count() -> VALUE; pub fn rb_yjit_shape_obj_complex_p(obj: VALUE) -> bool; + pub fn rb_yjit_shape_obj_embedded_p(obj: VALUE) -> bool; pub fn rb_yjit_shape_capacity(shape_id: shape_id_t) -> attr_index_t; pub fn rb_yjit_shape_index(shape_id: shape_id_t) -> attr_index_t; pub fn rb_yjit_sendish_sp_pops(ci: *const rb_callinfo) -> usize; diff --git a/zjit.rb b/zjit.rb index 22eba6e1475260..30ad7f51b75387 100644 --- a/zjit.rb +++ b/zjit.rb @@ -102,7 +102,6 @@ def stats_string # Show fallback counters, ordered by the typical amount of fallbacks for the prefix at the time print_counters_with_prefix(prefix: 'unspecialized_send_def_type_', prompt: 'not optimized method types for send', buf:, stats:, limit: 20) - print_counters_with_prefix(prefix: 'unspecialized_send_without_block_def_type_', prompt: 'not optimized method types for send_without_block', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'unspecialized_super_def_type_', prompt: 'not optimized method types for super', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'uncategorized_fallback_yarv_insn_', prompt: 'instructions with uncategorized fallback reason', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'send_fallback_', prompt: 'send fallback reasons', buf:, stats:, limit: 20) diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index f47fa06bdd5038..3012d602b1e55c 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -3741,11 +3741,18 @@ impl Assembler { } pub fn count_call_to(&mut self, fn_name: &str) { + self.count_call_to_with(|| fn_name.to_string()); + } + + /// Like [`Assembler::count_call_to`], but only builds the name when stats are + /// enabled. Use for callers where computing the name is non-trivial (e.g. + /// resolving a `Class#method` string) so no-stats builds don't pay for it. + pub fn count_call_to_with(&mut self, fn_name: impl FnOnce() -> String) { // We emit ccalls while initializing the JIT. Unfortunately, we skip those because // otherwise we have no counter pointers to read. if crate::state::ZJITState::has_instance() && get_option!(stats) { let ccall_counter_pointers = crate::state::ZJITState::get_ccall_counter_pointers(); - let counter_ptr = ccall_counter_pointers.entry(fn_name.to_string()).or_insert_with(|| Box::new(0)); + let counter_ptr = ccall_counter_pointers.entry(fn_name()).or_insert_with(|| Box::new(0)); let counter_ptr: &mut u64 = counter_ptr.as_mut(); self.incr_counter(Opnd::const_ptr(counter_ptr), 1.into()); } diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 20ff39339b7352..18f6071cb05399 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -18,12 +18,12 @@ use crate::invariants::{ use crate::gc::append_gc_offsets; use crate::payload::{IseqCodePtrs, IseqStatus, IseqVersion, IseqVersionRef, JITFrame, get_or_create_iseq_payload}; use crate::state::ZJITState; -use crate::stats::{CompileError, exit_counter_for_compile_error, exit_counter_for_unhandled_hir_insn, incr_counter, incr_counter_by, send_fallback_counter, send_fallback_counter_for_method_type, send_fallback_counter_for_super_method_type, send_fallback_counter_ptr_for_opcode, send_without_block_fallback_counter_for_method_type, send_without_block_fallback_counter_for_optimized_method_type}; +use crate::stats::{CompileError, exit_counter_for_compile_error, exit_counter_for_unhandled_hir_insn, incr_counter, incr_counter_by, send_fallback_counter, send_fallback_counter_for_method_type, send_fallback_counter_for_super_method_type, send_fallback_counter_ptr_for_opcode, send_fallback_counter_for_optimized_method_type}; use crate::stats::{counter_ptr, with_time_stat, trace_compile_phase, Counter, Counter::{compile_time_ns, exit_compile_error}}; use crate::{asm::CodeBlock, cruby::*, options::debug, virtualmem::CodePtr}; use crate::backend::lir::{self, Assembler, C_ARG_OPNDS, C_RET_OPND, CFP, EC, NATIVE_BASE_PTR, Opnd, SP, SideExit, SideExitRecompile, SideExitTarget, StackMap, StackMapEntry, Target, asm_ccall, asm_comment}; use crate::hir::{iseq_to_hir, BlockId, Invariant, RangeType, SideExitReason::{self, *}, SpecialBackrefSymbol, SpecialObjectType}; -use crate::hir::{BlockHandler, CCallVariadicData, CCallWithFrameData, Const, FieldName, FrameState, Function, Insn, InsnId, Recompile, SendDirectData, SendFallbackReason}; +use crate::hir::{BlockHandler, CCallVariadicData, CCallWithFrameData, Const, FieldName, FrameState, Function, Insn, InsnId, Recompile, SendDirectData, SendFallbackReason, qualified_method_name}; use crate::hir_type::{types, Type}; use crate::options::{get_option, InlineDepth, PerfMap, DEFAULT_MAX_VERSIONS}; use crate::cast::IntoUsize; @@ -726,7 +726,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio &Insn::GuardLess { left, right, ref reason, state } => gen_guard_less(jit, asm, function, opnd!(left), opnd!(right), **reason, &function.frame_state(state)), &Insn::GuardGreaterEq { left, right, state, .. } => gen_guard_greater_eq(jit, asm, function, opnd!(left), opnd!(right), &function.frame_state(state)), Insn::PatchPoint { invariant, state } => no_output!(gen_patch_point(jit, asm, function, invariant, &function.frame_state(*state))), - Insn::CCall { cfunc, recv, args, name, owner: _, return_type: _, elidable: _ } => gen_ccall(asm, *cfunc, *name, opnd!(recv), opnds!(args)), + Insn::CCall { cfunc, recv, args, name, owner, return_type: _, elidable: _ } => gen_ccall(asm, *cfunc, *name, *owner, opnd!(recv), opnds!(args)), Insn::CCallWithFrame(insn) => { let CCallWithFrameData { cfunc, recv, name, args, cme, state, block, .. } = &**insn; gen_ccall_with_frame(jit, asm, function, *cfunc, *name, opnd!(recv), opnds!(args), *cme, *block, &function.frame_state(*state)) @@ -1079,7 +1079,7 @@ fn gen_ccall_with_frame( let mut cfunc_args = vec![recv]; cfunc_args.extend(args); - asm.count_call_to(&name.contents_lossy()); + asm.count_call_to_with(|| qualified_method_name(unsafe { (*cme).owner }, name)); let result = asm.ccall(cfunc, cfunc_args); asm_comment!(asm, "pop C frame"); @@ -1096,10 +1096,10 @@ fn gen_ccall_with_frame( /// Lowering for [`Insn::CCall`]. This is a low-level raw call that doesn't know /// anything about the callee, so handling for e.g. GC safety is dealt with elsewhere. -fn gen_ccall(asm: &mut Assembler, cfunc: *const u8, name: ID, recv: Opnd, args: Vec) -> lir::Opnd { +fn gen_ccall(asm: &mut Assembler, cfunc: *const u8, name: ID, owner: VALUE, recv: Opnd, args: Vec) -> lir::Opnd { let mut cfunc_args = vec![recv]; cfunc_args.extend(args); - asm.count_call_to(&name.contents_lossy()); + asm.count_call_to_with(|| if owner == Qnil { name.contents_lossy().to_string() } else { qualified_method_name(owner, name) }); asm.ccall(cfunc, cfunc_args) } @@ -1168,7 +1168,7 @@ fn gen_ccall_variadic( asm.store(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP), CFP); let argv_ptr = gen_push_opnds(jit, asm, &args); - asm.count_call_to(&name.contents_lossy()); + asm.count_call_to_with(|| qualified_method_name(unsafe { (*cme).owner }, name)); let result = asm.ccall(cfunc, vec![args.len().into(), argv_ptr, recv]); asm_comment!(asm, "pop C frame"); @@ -2068,7 +2068,7 @@ fn gen_new_array( let flags = (RUBY_T_ARRAY as u64) | (RARRAY_EMBED_FLAG as u64); let klass = unsafe { rb_cArray }; - gc_fastpath::gc_fast_path_new_obj(jit, asm, alloc_size, flags, klass, |asm| { + gc_fastpath::gc_fastpath_new_obj(jit, asm, alloc_size, flags, klass, |asm| { asm_ccall!(asm, rb_ec_ary_new_from_values, EC, 0i64.into(), Opnd::UImm(0)) }) } @@ -2361,11 +2361,11 @@ fn gen_new_hash( let flags = RUBY_T_HASH as u64; let klass = unsafe { rb_cHash }; - let hash = gc_fastpath::gc_fast_path_new_obj(jit, asm, alloc_size, flags, klass, |asm| { + let hash = gc_fastpath::gc_fastpath_new_obj(jit, asm, alloc_size, flags, klass, |asm| { asm_ccall!(asm, rb_hash_new,) }); // TODO: this runs on the slow path too, where rb_hash_new already set - // ifnone. A fast-path-only init hook in gc_fast_path_new_obj would avoid + // ifnone. A fast-path-only init hook in gc_fastpath_new_obj would avoid // the redundant store and be reusable for other types. asm.store(Opnd::mem(VALUE_BITS, hash, RUBY_OFFSET_RHASH_IFNONE), Qnil.into()); hash @@ -2381,11 +2381,11 @@ fn gen_new_hash( let flags = RUBY_T_HASH as u64; let klass = unsafe { rb_cHash }; - let hash = gc_fastpath::gc_fast_path_new_obj(jit, asm, alloc_size, flags, klass, |asm| { + let hash = gc_fastpath::gc_fastpath_new_obj(jit, asm, alloc_size, flags, klass, |asm| { asm_ccall!(asm, rb_hash_new_with_size, num_pairs.into()) }); // TODO: this runs on the slow path too, where rb_hash_new already set - // ifnone. A fast-path-only init hook in gc_fast_path_new_obj would avoid + // ifnone. A fast-path-only init hook in gc_fastpath_new_obj would avoid // the redundant store and be reusable for other types. asm.store(Opnd::mem(VALUE_BITS, hash, RUBY_OFFSET_RHASH_IFNONE), Qnil.into()); hash @@ -2450,7 +2450,7 @@ fn gen_object_alloc_class(jit: &mut JITState, asm: &mut Assembler, class: VALUE, }; if has_fastpath { let flags = (RUBY_T_OBJECT as u64) | ((shape_id as u64) << RB_SHAPE_FLAG_SHIFT as u64); - gc_fastpath::gc_fast_path_new_obj(jit, asm, alloc_size, flags, class, |asm| { + gc_fastpath::gc_fastpath_new_obj(jit, asm, alloc_size, flags, class, |asm| { asm_ccall!(asm, rb_class_allocate_instance, class.into()) }) } else { @@ -2996,11 +2996,8 @@ fn gen_incr_send_fallback_counter(asm: &mut Assembler, reason: SendFallbackReaso Uncategorized(opcode) => { gen_incr_counter_ptr(asm, send_fallback_counter_ptr_for_opcode(opcode)); } - SendWithoutBlockNotOptimizedMethodType(method_type) => { - gen_incr_counter(asm, send_without_block_fallback_counter_for_method_type(method_type)); - } - SendWithoutBlockNotOptimizedMethodTypeOptimized(method_type) => { - gen_incr_counter(asm, send_without_block_fallback_counter_for_optimized_method_type(method_type)); + SendNotOptimizedMethodTypeOptimized(method_type) => { + gen_incr_counter(asm, send_fallback_counter_for_optimized_method_type(method_type)); } SendNotOptimizedMethodType(method_type) => { gen_incr_counter(asm, send_fallback_counter_for_method_type(method_type)); diff --git a/zjit/src/codegen/gc_fastpath.rs b/zjit/src/codegen/gc_fastpath.rs index b4ce600e279d5c..1aacf183781fd5 100644 --- a/zjit/src/codegen/gc_fastpath.rs +++ b/zjit/src/codegen/gc_fastpath.rs @@ -68,7 +68,7 @@ enum PreparedNewObjFastpath { Mmtk(RbGcZjitMmtkNewObjFastpath), } -pub(super) fn gc_fast_path_new_obj( +pub(super) fn gc_fastpath_new_obj( jit: &mut JITState, asm: &mut Assembler, alloc_size: usize, diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index 74f8e676a8acd4..ec897ac538f15f 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -648,10 +648,8 @@ impl VALUE { } } - pub fn embedded_p(self) -> bool { - unsafe { - FL_TEST_RAW(self, VALUE(ROBJECT_HEAP as usize)) == VALUE(0) - } + pub fn layout(self) -> ShapeLayout { + self.shape_id_of().layout() } pub fn struct_embedded_p(self) -> bool { diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index 6ebcbd5335d5a6..232478f1220fb1 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -367,8 +367,6 @@ pub struct RArray__bindgen_ty_1__bindgen_ty_1__bindgen_ty_1 { } pub const RMODULE_IS_REFINEMENT: ruby_rmodule_flags = 8192; pub type ruby_rmodule_flags = u32; -pub const ROBJECT_HEAP: ruby_robject_flags = 65536; -pub type ruby_robject_flags = u32; pub type rb_event_flag_t = u32; pub type rb_block_call_func = ::std::option::Option< unsafe extern "C" fn( diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index ace63943053ce4..0a031aa9900c90 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -683,17 +683,11 @@ pub enum ReceiverTypeResolution { /// Reason why a send-ish instruction cannot be optimized from a fallback instruction #[derive(Debug, Clone, Copy)] pub enum SendFallbackReason { - SendWithoutBlockPolymorphic, - SendWithoutBlockMegamorphic, - SendWithoutBlockNoProfiles, - SendWithoutBlockCfuncNotVariadic, - SendWithoutBlockCfuncArrayVariadic, - SendWithoutBlockNotOptimizedMethodType(MethodType), - SendWithoutBlockNotOptimizedMethodTypeOptimized(OptimizedMethodType), - SendWithoutBlockNotOptimizedNeedPermission, - SendWithoutBlockBopRedefined, - SendWithoutBlockOperandsNotFixnum, - SendWithoutBlockPolymorphicFallback, + SendCfuncNotVariadic, + SendNotOptimizedMethodTypeOptimized(OptimizedMethodType), + SendBopRedefined, + SendOperandsNotFixnum, + SendPolymorphicFallback, SendDirectKeywordMismatch, SendDirectKeywordCountMismatch, SendDirectMissingKeyword, @@ -759,18 +753,12 @@ pub enum SendFallbackReason { impl Display for SendFallbackReason { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { - SendWithoutBlockPolymorphic => write!(f, "SendWithoutBlock: polymorphic call site"), - SendWithoutBlockMegamorphic => write!(f, "SendWithoutBlock: megamorphic call site"), - SendWithoutBlockNoProfiles => write!(f, "SendWithoutBlock: no profile data available"), - SendWithoutBlockCfuncNotVariadic => write!(f, "SendWithoutBlock: C function is not variadic"), - SendWithoutBlockCfuncArrayVariadic => write!(f, "SendWithoutBlock: C function expects array variadic"), - SendWithoutBlockNotOptimizedMethodType(method_type) => write!(f, "SendWithoutBlock: unsupported method type {:?}", method_type), - SendWithoutBlockNotOptimizedMethodTypeOptimized(opt_type) => write!(f, "SendWithoutBlock: unsupported optimized method type {:?}", opt_type), - SendWithoutBlockNotOptimizedNeedPermission => write!(f, "SendWithoutBlock: method private or protected and no FCALL"), + SendCfuncNotVariadic => write!(f, "Send: C function is not variadic"), + SendNotOptimizedMethodTypeOptimized(opt_type) => write!(f, "Send: unsupported optimized method type {:?}", opt_type), SendNotOptimizedNeedPermission => write!(f, "Send: method private or protected and no FCALL"), - SendWithoutBlockBopRedefined => write!(f, "SendWithoutBlock: basic operation was redefined"), - SendWithoutBlockOperandsNotFixnum => write!(f, "SendWithoutBlock: operands are not fixnums"), - SendWithoutBlockPolymorphicFallback => write!(f, "SendWithoutBlock: polymorphic fallback"), + SendBopRedefined => write!(f, "Send: basic operation was redefined"), + SendOperandsNotFixnum => write!(f, "Send: operands are not fixnums"), + SendPolymorphicFallback => write!(f, "Send: polymorphic fallback"), SendDirectKeywordMismatch => write!(f, "SendDirect: keyword mismatch"), SendDirectKeywordCountMismatch => write!(f, "SendDirect: keyword count mismatch"), SendDirectMissingKeyword => write!(f, "SendDirect: missing keyword"), @@ -1924,7 +1912,7 @@ fn id_is_empty(id: ID) -> bool { /// Construct a qualified method name for display/debug output. /// Returns strings like "Array#length" for instance methods or "Foo.bar" for singleton methods. -fn qualified_method_name(class: VALUE, method_id: ID) -> String { +pub(crate) fn qualified_method_name(class: VALUE, method_id: ID) -> String { let method_name = method_id.contents_lossy(); // rb_zjit_singleton_class_p also checks if it's a class if unsafe { rb_zjit_singleton_class_p(class) } { @@ -3872,7 +3860,7 @@ impl Function { fn rewrite_if_frozen(&mut self, block: BlockId, orig_insn_id: InsnId, self_val: InsnId, klass: u32, bop: u32, state: InsnId) { if !unsafe { rb_BASIC_OP_UNREDEFINED_P(bop, klass) } { // If the basic operation is already redefined, we cannot optimize it. - self.set_dynamic_send_reason(orig_insn_id, SendWithoutBlockBopRedefined); + self.set_dynamic_send_reason(orig_insn_id, SendBopRedefined); self.push_insn_id(block, orig_insn_id); return; } @@ -4086,23 +4074,20 @@ impl Function { ReceiverTypeResolution::SkewedMegamorphic { .. } | ReceiverTypeResolution::Megamorphic => { if get_option!(stats) { - let reason = if has_block { SendMegamorphic } else { SendWithoutBlockMegamorphic }; - self.set_dynamic_send_reason(insn_id, reason); + self.set_dynamic_send_reason(insn_id, SendMegamorphic); } self.push_insn_id(block, insn_id); continue; } ReceiverTypeResolution::Polymorphic => { if get_option!(stats) { - let reason = if has_block { SendPolymorphic } else { SendWithoutBlockPolymorphic }; - self.set_dynamic_send_reason(insn_id, reason); + self.set_dynamic_send_reason(insn_id, SendPolymorphic); } self.push_insn_id(block, insn_id); continue; } ReceiverTypeResolution::NoProfile => { - let reason = if has_block { SendNoProfiles } else { SendWithoutBlockNoProfiles }; - self.set_dynamic_send_reason(insn_id, reason); + self.set_dynamic_send_reason(insn_id, SendNoProfiles); self.push_insn_id(block, insn_id); continue; } @@ -4115,8 +4100,7 @@ impl Function { // Do method lookup let mut cme = unsafe { rb_callable_method_entry(klass, mid) }; if cme.is_null() { - let reason = if has_block { SendNotOptimizedMethodType(MethodType::Null) } else { SendWithoutBlockNotOptimizedMethodType(MethodType::Null) }; - self.set_dynamic_send_reason(insn_id, reason); + self.set_dynamic_send_reason(insn_id, SendNotOptimizedMethodType(MethodType::Null)); self.push_insn_id(block, insn_id); continue; } // Load an overloaded cme if applicable. See vm_search_cc(). @@ -4128,8 +4112,7 @@ impl Function { (METHOD_VISI_PRIVATE, true) => {} (METHOD_VISI_PROTECTED, true) => {} _ => { - let reason = if has_block { SendNotOptimizedNeedPermission } else { SendWithoutBlockNotOptimizedNeedPermission }; - self.set_dynamic_send_reason(insn_id, reason); + self.set_dynamic_send_reason(insn_id, SendNotOptimizedNeedPermission); self.push_insn_id(block, insn_id); continue; } } @@ -4376,8 +4359,7 @@ impl Function { // Get the profiled type to check if the fields is embedded or heap allocated. let Some(is_embedded) = self.profiled_type_of_at(recv, state).map(|t| t.flags().is_struct_embedded()) else { // No (monomorphic/skewed polymorphic) profile info - let reason = if has_block { SendNoProfiles } else { SendWithoutBlockNoProfiles }; - self.set_dynamic_send_reason(insn_id, reason); + self.set_dynamic_send_reason(insn_id, SendNoProfiles); self.push_insn_id(block, insn_id); continue; }; // Check singleton class assumption first, before emitting other patchpoints @@ -4418,7 +4400,7 @@ impl Function { self.make_equal_to(insn_id, replacement); }, _ => { - self.set_dynamic_send_reason(insn_id, SendWithoutBlockNotOptimizedMethodTypeOptimized(OptimizedMethodType::from(opt_type))); + self.set_dynamic_send_reason(insn_id, SendNotOptimizedMethodTypeOptimized(OptimizedMethodType::from(opt_type))); self.push_insn_id(block, insn_id); continue; }, }; @@ -4639,8 +4621,7 @@ impl Function { self.push_insn_id(block, insn_id); } else { - let reason = if has_block { SendNotOptimizedMethodType(MethodType::from(def_type)) } else { SendWithoutBlockNotOptimizedMethodType(MethodType::from(def_type)) }; - self.set_dynamic_send_reason(insn_id, reason); + self.set_dynamic_send_reason(insn_id, SendNotOptimizedMethodType(MethodType::from(def_type))); self.push_insn_id(block, insn_id); continue; } } @@ -5682,7 +5663,7 @@ impl Function { assert!(self.blocks[block.0].insns.is_empty()); for insn_id in old_insns { match self.find(insn_id) { - Insn::Send { state, reason: SendFallbackReason::SendWithoutBlockNoProfiles | SendFallbackReason::SendNoProfiles, .. } => { + Insn::Send { state, reason: SendFallbackReason::SendNoProfiles, .. } => { self.push_insn(block, Insn::SideExit { state, reason: Box::new(SideExitReason::NoProfileSend), recompile: Some(Recompile) }); // SideExit is a terminator; don't add remaining instructions break; @@ -9170,7 +9151,7 @@ fn add_iseq_to_hir( fun.push_insn(iftrue_block, Insn::Jump(BranchEdge { target: join_block, args: vec![send] })); } // In the fallthrough case, do a generic interpreter send and then join. - let reason = SendWithoutBlockPolymorphicFallback; + let reason = SendPolymorphicFallback; let send = fun.push_insn(block, Insn::Send { recv, cd, block: None, args, state: exit_id, reason }); fun.push_insn(block, Insn::Jump(BranchEdge { target: join_block, args: vec![send] })); state.stack_push(join_param); diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index c374e98c85c9e1..9c8113faeb8ab5 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -1671,7 +1671,7 @@ mod hir_opt_tests { v4:BasicObject = LoadArg :self@0 Jump bb3(v4) bb3(v6:BasicObject): - v11:BasicObject = Send v6, :foo # SendFallbackReason: SendWithoutBlock: unsupported method type Null + v11:BasicObject = Send v6, :foo # SendFallbackReason: Send: unsupported method type Null CheckInterrupts Return v11 "); @@ -3801,7 +3801,7 @@ mod hir_opt_tests { v42:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1048)) Jump bb8(v42, v52) bb8(v32:BasicObject, v33:BasicObject): - v47:BasicObject = Send v32, :call, v11, v13 # SendFallbackReason: SendWithoutBlock: unsupported optimized method type BlockCall + v47:BasicObject = Send v32, :call, v11, v13 # SendFallbackReason: Send: unsupported optimized method type BlockCall CheckInterrupts PopInlineFrame Return v47 @@ -6706,7 +6706,7 @@ mod hir_opt_tests { TEST = ExtendedSetIvar.instance_method(:test) OBJ "#); - assert!(!obj.embedded_p()); + assert!(obj.layout() == ShapeLayout::Extended); assert_snapshot!(hir_string_proc("TEST"), @" fn test@:4: @@ -8442,7 +8442,7 @@ mod hir_opt_tests { v60:TrueClass = Const Value(true) Jump bb7(v60) bb11(): - v48:BasicObject = Send v31, :! # SendFallbackReason: SendWithoutBlock: polymorphic fallback + v48:BasicObject = Send v31, :! # SendFallbackReason: Send: polymorphic fallback Jump bb7(v48) bb7(v35:BasicObject): CheckInterrupts @@ -9761,7 +9761,7 @@ mod hir_opt_tests { v31:BasicObject = GetIvar v19, :@foo Jump bb4(v31) bb6(): - v22:BasicObject = Send v10, :foo # SendFallbackReason: SendWithoutBlock: polymorphic fallback + v22:BasicObject = Send v10, :foo # SendFallbackReason: Send: polymorphic fallback Jump bb4(v22) bb4(v15:BasicObject): CheckInterrupts @@ -10094,7 +10094,7 @@ mod hir_opt_tests { v54:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1048)) Jump bb13(v54, v27) bb13(v44:BasicObject, v45:BasicObject): - v57:BasicObject = Send v44, :call # SendFallbackReason: SendWithoutBlock: no profile data available + v57:BasicObject = Send v44, :call # SendFallbackReason: Send: no profile data available CheckInterrupts Jump bb4(v57) bb6(v62:ObjectSubclass[class_exact*:Object@VALUE(0x1000)], v63:BasicObject): @@ -15764,7 +15764,7 @@ mod hir_opt_tests { PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, Obj) v12:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - v14:BasicObject = Send v12, :secret # SendFallbackReason: SendWithoutBlock: method private or protected and no FCALL + v14:BasicObject = Send v12, :secret # SendFallbackReason: Send: method private or protected and no FCALL CheckInterrupts Return v14 "); @@ -15819,7 +15819,7 @@ mod hir_opt_tests { PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, Obj) v12:BasicObjectExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - v14:BasicObject = Send v12, :initialize # SendFallbackReason: SendWithoutBlock: method private or protected and no FCALL + v14:BasicObject = Send v12, :initialize # SendFallbackReason: Send: method private or protected and no FCALL CheckInterrupts Return v14 "); @@ -15847,7 +15847,7 @@ mod hir_opt_tests { PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, Obj) v12:ObjectExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - v14:BasicObject = Send v12, :toplevel_method # SendFallbackReason: SendWithoutBlock: method private or protected and no FCALL + v14:BasicObject = Send v12, :toplevel_method # SendFallbackReason: Send: method private or protected and no FCALL CheckInterrupts Return v14 "); @@ -15906,7 +15906,7 @@ mod hir_opt_tests { PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, Obj) v12:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - v14:BasicObject = Send v12, :secret # SendFallbackReason: SendWithoutBlock: method private or protected and no FCALL + v14:BasicObject = Send v12, :secret # SendFallbackReason: Send: method private or protected and no FCALL CheckInterrupts Return v14 "); @@ -16719,7 +16719,7 @@ mod hir_opt_tests { v45:Fixnum[4] = Const Value(4) Jump bb4(v45) bb8(): - v28:BasicObject = Send v10, :foo # SendFallbackReason: SendWithoutBlock: polymorphic fallback + v28:BasicObject = Send v10, :foo # SendFallbackReason: Send: polymorphic fallback Jump bb4(v28) bb4(v15:BasicObject): v31:Fixnum[2] = Const Value(2) @@ -16772,7 +16772,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(Integer@0x1040, itself@0x1010, cme:0x1018) Jump bb4(v25) bb8(): - v28:BasicObject = Send v10, :itself # SendFallbackReason: SendWithoutBlock: polymorphic fallback + v28:BasicObject = Send v10, :itself # SendFallbackReason: Send: polymorphic fallback Jump bb4(v28) bb4(v15:BasicObject): CheckInterrupts @@ -16828,7 +16828,7 @@ mod hir_opt_tests { v40:StringExact = CCallVariadic v25, :Integer#to_s@0x1040 Jump bb4(v40) bb8(): - v28:BasicObject = Send v10, :to_s # SendFallbackReason: SendWithoutBlock: polymorphic fallback + v28:BasicObject = Send v10, :to_s # SendFallbackReason: Send: polymorphic fallback Jump bb4(v28) bb4(v15:BasicObject): CheckInterrupts @@ -16881,7 +16881,7 @@ mod hir_opt_tests { v40:BasicObject = CCallWithFrame v25, :Float#to_s@0x1040 Jump bb4(v40) bb8(): - v28:BasicObject = Send v10, :to_s # SendFallbackReason: SendWithoutBlock: polymorphic fallback + v28:BasicObject = Send v10, :to_s # SendFallbackReason: Send: polymorphic fallback Jump bb4(v28) bb4(v15:BasicObject): CheckInterrupts @@ -16934,7 +16934,7 @@ mod hir_opt_tests { v38:StringExact = InvokeBuiltin leaf , v25 Jump bb4(v38) bb8(): - v28:BasicObject = Send v10, :to_s # SendFallbackReason: SendWithoutBlock: polymorphic fallback + v28:BasicObject = Send v10, :to_s # SendFallbackReason: Send: polymorphic fallback Jump bb4(v28) bb4(v15:BasicObject): CheckInterrupts @@ -16983,7 +16983,7 @@ mod hir_opt_tests { v31:Fixnum[3] = Const Value(3) Jump bb4(v31) bb6(): - v22:BasicObject = Send v10, :foo # SendFallbackReason: SendWithoutBlock: polymorphic fallback + v22:BasicObject = Send v10, :foo # SendFallbackReason: Send: polymorphic fallback Jump bb4(v22) bb4(v15:BasicObject): CheckInterrupts @@ -17016,7 +17016,7 @@ mod hir_opt_tests { OBJ "#); // Skip builds where five ivars already force heap-backed storage. - if !obj.embedded_p() { + if obj.layout() != ShapeLayout::RObject { return; } @@ -17027,7 +17027,7 @@ mod hir_opt_tests { probe.instance_variable_set(:@overflow, 1) probe "#); - if probe.embedded_p() { + if probe.layout() == ShapeLayout::RObject { return; } eval("OBJ.test"); @@ -17708,7 +17708,7 @@ mod hir_opt_tests { bb5(): v18:Truthy = RefineType v10, Truthy v22:Fixnum[42] = Const Value(42) - v24:BasicObject = Send v9, :greet_final, v22 # SendFallbackReason: SendWithoutBlock: no profile data available + v24:BasicObject = Send v9, :greet_final, v22 # SendFallbackReason: Send: no profile data available CheckInterrupts Return v24 bb4(v29:BasicObject, v30:Falsy): @@ -18501,7 +18501,7 @@ mod hir_opt_tests { v45:BasicObject = CCallWithFrame v30, :Float#*@0x1040, v13 Jump bb4(v45) bb8(): - v33:BasicObject = Send v12, :*, v13 # SendFallbackReason: SendWithoutBlock: polymorphic fallback + v33:BasicObject = Send v12, :*, v13 # SendFallbackReason: Send: polymorphic fallback Jump bb4(v33) bb4(v20:BasicObject): CheckInterrupts @@ -19995,7 +19995,7 @@ mod hir_opt_tests { v44:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1050)) Jump bb8(v44, v53) bb8(v34:BasicObject, v35:BasicObject): - v48:BasicObject = Send v34, :call, v10 # SendFallbackReason: SendWithoutBlock: unsupported optimized method type BlockCall + v48:BasicObject = Send v34, :call, v10 # SendFallbackReason: Send: unsupported optimized method type BlockCall CheckInterrupts PopInlineFrame PatchPoint NoEPEscape(test) diff --git a/zjit/src/profile.rs b/zjit/src/profile.rs index e3b89c9319ebe7..edf93dffa0b834 100644 --- a/zjit/src/profile.rs +++ b/zjit/src/profile.rs @@ -332,7 +332,7 @@ impl ProfiledType { flags: Flags::immediate() }; } let mut flags = Flags::none(); - if obj.embedded_p() { + if obj.shape_id_of().layout() == ShapeLayout::RObject { flags.0 |= Flags::IS_EMBEDDED; } if obj.struct_embedded_p() { diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 4c95e9f61d2f83..6a05e5f50788f9 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -251,22 +251,16 @@ make_counters! { // Send fallback counters that are summed as dynamic_send_count dynamic_send { // send_fallback_: Fallback reasons for send-ish instructions - send_fallback_send_without_block_polymorphic, - send_fallback_send_without_block_megamorphic, - send_fallback_send_without_block_no_profiles, - send_fallback_send_without_block_cfunc_not_variadic, - send_fallback_send_without_block_cfunc_array_variadic, - send_fallback_send_without_block_not_optimized_method_type, - send_fallback_send_without_block_not_optimized_method_type_optimized, - send_fallback_send_without_block_not_optimized_need_permission, + send_fallback_send_cfunc_not_variadic, + send_fallback_send_not_optimized_method_type_optimized, send_fallback_too_many_args_for_lir, - send_fallback_send_without_block_bop_redefined, - send_fallback_send_without_block_operands_not_fixnum, - send_fallback_send_without_block_polymorphic_fallback, - send_fallback_send_without_block_direct_keyword_mismatch, - send_fallback_send_without_block_direct_keyword_count_mismatch, - send_fallback_send_without_block_direct_missing_keyword, - send_fallback_send_without_block_direct_too_many_keywords, + send_fallback_send_bop_redefined, + send_fallback_send_operands_not_fixnum, + send_fallback_send_polymorphic_fallback, + send_fallback_send_direct_keyword_mismatch, + send_fallback_send_direct_keyword_count_mismatch, + send_fallback_send_direct_missing_keyword, + send_fallback_send_direct_too_many_keywords, send_fallback_send_polymorphic, send_fallback_send_megamorphic, send_fallback_send_no_profiles, @@ -384,27 +378,12 @@ make_counters! { // The number of times YARV instructions are executed on JIT code zjit_insn_count, - // Method call def_type related to send without block fallback to dynamic dispatch - unspecialized_send_without_block_def_type_iseq, - unspecialized_send_without_block_def_type_cfunc, - unspecialized_send_without_block_def_type_attrset, - unspecialized_send_without_block_def_type_ivar, - unspecialized_send_without_block_def_type_bmethod, - unspecialized_send_without_block_def_type_zsuper, - unspecialized_send_without_block_def_type_alias, - unspecialized_send_without_block_def_type_undef, - unspecialized_send_without_block_def_type_not_implemented, - unspecialized_send_without_block_def_type_optimized, - unspecialized_send_without_block_def_type_missing, - unspecialized_send_without_block_def_type_refined, - unspecialized_send_without_block_def_type_null, - // Method call optimized_type related to send without block fallback to dynamic dispatch - unspecialized_send_without_block_def_type_optimized_send, - unspecialized_send_without_block_def_type_optimized_call, - unspecialized_send_without_block_def_type_optimized_block_call, - unspecialized_send_without_block_def_type_optimized_struct_aref, - unspecialized_send_without_block_def_type_optimized_struct_aset, + unspecialized_send_def_type_optimized_send, + unspecialized_send_def_type_optimized_call, + unspecialized_send_def_type_optimized_block_call, + unspecialized_send_def_type_optimized_struct_aref, + unspecialized_send_def_type_optimized_struct_aset, // Method call def_type related to send fallback to dynamic dispatch unspecialized_send_def_type_iseq, @@ -686,24 +665,17 @@ pub fn send_fallback_counter(reason: crate::hir::SendFallbackReason) -> Counter use crate::hir::SendFallbackReason::*; use crate::stats::Counter::*; match reason { - SendWithoutBlockPolymorphic => send_fallback_send_without_block_polymorphic, - SendWithoutBlockMegamorphic => send_fallback_send_without_block_megamorphic, - SendWithoutBlockNoProfiles => send_fallback_send_without_block_no_profiles, - SendWithoutBlockCfuncNotVariadic => send_fallback_send_without_block_cfunc_not_variadic, - SendWithoutBlockCfuncArrayVariadic => send_fallback_send_without_block_cfunc_array_variadic, - SendWithoutBlockNotOptimizedMethodType(_) => send_fallback_send_without_block_not_optimized_method_type, - SendWithoutBlockNotOptimizedMethodTypeOptimized(_) - => send_fallback_send_without_block_not_optimized_method_type_optimized, - SendWithoutBlockNotOptimizedNeedPermission - => send_fallback_send_without_block_not_optimized_need_permission, + SendCfuncNotVariadic => send_fallback_send_cfunc_not_variadic, + SendNotOptimizedMethodTypeOptimized(_) + => send_fallback_send_not_optimized_method_type_optimized, TooManyArgsForLir => send_fallback_too_many_args_for_lir, - SendWithoutBlockBopRedefined => send_fallback_send_without_block_bop_redefined, - SendWithoutBlockOperandsNotFixnum => send_fallback_send_without_block_operands_not_fixnum, - SendWithoutBlockPolymorphicFallback => send_fallback_send_without_block_polymorphic_fallback, - SendDirectKeywordMismatch => send_fallback_send_without_block_direct_keyword_mismatch, - SendDirectKeywordCountMismatch => send_fallback_send_without_block_direct_keyword_count_mismatch, - SendDirectMissingKeyword => send_fallback_send_without_block_direct_missing_keyword, - SendDirectTooManyKeywords => send_fallback_send_without_block_direct_too_many_keywords, + SendBopRedefined => send_fallback_send_bop_redefined, + SendOperandsNotFixnum => send_fallback_send_operands_not_fixnum, + SendPolymorphicFallback => send_fallback_send_polymorphic_fallback, + SendDirectKeywordMismatch => send_fallback_send_direct_keyword_mismatch, + SendDirectKeywordCountMismatch => send_fallback_send_direct_keyword_count_mismatch, + SendDirectMissingKeyword => send_fallback_send_direct_missing_keyword, + SendDirectTooManyKeywords => send_fallback_send_direct_too_many_keywords, SendPolymorphic => send_fallback_send_polymorphic, SendMegamorphic => send_fallback_send_megamorphic, SendNoProfiles => send_fallback_send_no_profiles, @@ -736,37 +708,16 @@ pub fn send_fallback_counter(reason: crate::hir::SendFallbackReason) -> Counter } } -pub fn send_without_block_fallback_counter_for_method_type(method_type: crate::hir::MethodType) -> Counter { - use crate::hir::MethodType::*; - use crate::stats::Counter::*; - - match method_type { - Iseq => unspecialized_send_without_block_def_type_iseq, - Cfunc => unspecialized_send_without_block_def_type_cfunc, - Attrset => unspecialized_send_without_block_def_type_attrset, - Ivar => unspecialized_send_without_block_def_type_ivar, - Bmethod => unspecialized_send_without_block_def_type_bmethod, - Zsuper => unspecialized_send_without_block_def_type_zsuper, - Alias => unspecialized_send_without_block_def_type_alias, - Undefined => unspecialized_send_without_block_def_type_undef, - NotImplemented => unspecialized_send_without_block_def_type_not_implemented, - Optimized => unspecialized_send_without_block_def_type_optimized, - Missing => unspecialized_send_without_block_def_type_missing, - Refined => unspecialized_send_without_block_def_type_refined, - Null => unspecialized_send_without_block_def_type_null, - } -} - -pub fn send_without_block_fallback_counter_for_optimized_method_type(method_type: crate::hir::OptimizedMethodType) -> Counter { +pub fn send_fallback_counter_for_optimized_method_type(method_type: crate::hir::OptimizedMethodType) -> Counter { use crate::hir::OptimizedMethodType::*; use crate::stats::Counter::*; match method_type { - Send => unspecialized_send_without_block_def_type_optimized_send, - Call => unspecialized_send_without_block_def_type_optimized_call, - BlockCall => unspecialized_send_without_block_def_type_optimized_block_call, - StructAref => unspecialized_send_without_block_def_type_optimized_struct_aref, - StructAset => unspecialized_send_without_block_def_type_optimized_struct_aset, + Send => unspecialized_send_def_type_optimized_send, + Call => unspecialized_send_def_type_optimized_call, + BlockCall => unspecialized_send_def_type_optimized_block_call, + StructAref => unspecialized_send_def_type_optimized_struct_aref, + StructAset => unspecialized_send_def_type_optimized_struct_aset, } }