diff --git a/.github/workflows/zjit-macos.yml b/.github/workflows/zjit-macos.yml index c80dec1b7eaa50..2e736673991aea 100644 --- a/.github/workflows/zjit-macos.yml +++ b/.github/workflows/zjit-macos.yml @@ -93,7 +93,7 @@ jobs: rustup install ${{ matrix.rust_version }} --profile minimal rustup default ${{ matrix.rust_version }} - - uses: taiki-e/install-action@cf39a74df4a72510be4e5b63348d61067f11e64a # v2.75.0 + - uses: taiki-e/install-action@80e6af7a2ec7f280fffe2d0a9d3a12a9d11d86e9 # v2.75.1 with: tool: nextest@0.9 if: ${{ matrix.test_task == 'zjit-check' }} diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml index 5c269774179c95..a0adec6652ac1a 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -119,7 +119,7 @@ jobs: ruby-version: '3.1' bundler: none - - uses: taiki-e/install-action@cf39a74df4a72510be4e5b63348d61067f11e64a # v2.75.0 + - uses: taiki-e/install-action@80e6af7a2ec7f280fffe2d0a9d3a12a9d11d86e9 # v2.75.1 with: tool: nextest@0.9 if: ${{ matrix.test_task == 'zjit-check' }} diff --git a/compile.c b/compile.c index 5decd5e59e742d..e6748d38b309da 100644 --- a/compile.c +++ b/compile.c @@ -2731,7 +2731,6 @@ iseq_set_sequence(rb_iseq_t *iseq, LINK_ANCHOR *const anchor) data.len = len; rb_hash_foreach(map, cdhash_set_label_i, (VALUE)&data); - rb_hash_rehash(map); freeze_hide_obj(map); rb_ractor_make_shareable(map); generated_iseq[code_index + 1 + j] = map; @@ -5373,10 +5372,10 @@ compile_hash(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *node, int meth if (!RB_SPECIAL_CONST_P(elem[1])) RB_OBJ_SET_FROZEN_SHAREABLE(elem[1]); rb_ary_cat(ary, elem, 2); } - VALUE hash = rb_hash_new_with_size(RARRAY_LEN(ary) / 2); + VALUE hash = rb_hash_alloc_fixed_size(Qfalse, RARRAY_LEN(ary) / 2); rb_hash_bulk_insert(RARRAY_LEN(ary), RARRAY_CONST_PTR(ary), hash); RB_GC_GUARD(ary); - hash = RB_OBJ_SET_FROZEN_SHAREABLE(rb_obj_hide(hash)); + hash = RB_OBJ_SET_FROZEN_SHAREABLE(hash); /* Emit optimized code */ FLUSH_CHUNK(); @@ -12168,7 +12167,7 @@ iseq_build_from_ary_body(rb_iseq_t *iseq, LINK_ANCHOR *const anchor, case TS_CDHASH: { int i; - VALUE map = rb_hash_new_with_size(RARRAY_LEN(op)/2); + VALUE map = rb_hash_alloc_fixed_size(Qfalse, RARRAY_LEN(op)/2); RHASH_TBL_RAW(map)->type = &cdhash_type; op = rb_to_array_type(op); @@ -12180,7 +12179,7 @@ iseq_build_from_ary_body(rb_iseq_t *iseq, LINK_ANCHOR *const anchor, rb_hash_aset(map, key, (VALUE)label | 1); } RB_GC_GUARD(op); - RB_OBJ_SET_SHAREABLE(rb_obj_hide(map)); // allow mutation while compiling + RB_OBJ_SET_SHAREABLE(map); // allow mutation while compiling argv[j] = map; RB_OBJ_WRITTEN(iseq, Qundef, map); } @@ -14335,7 +14334,7 @@ static VALUE ibf_load_object_hash(const struct ibf_load *load, const struct ibf_object_header *header, ibf_offset_t offset) { long len = (long)ibf_load_small_value(load, &offset); - VALUE obj = rb_hash_new_with_size(len); + VALUE obj = header->frozen ? rb_hash_alloc_fixed_size(rb_cHash, len) : rb_hash_new_with_size(len); int i; for (i = 0; i < len; i++) { @@ -14346,7 +14345,6 @@ ibf_load_object_hash(const struct ibf_load *load, const struct ibf_object_header VALUE val = ibf_load_object(load, val_index); rb_hash_aset(obj, key, val); } - rb_hash_rehash(obj); if (header->internal) rb_obj_hide(obj); if (header->frozen) { diff --git a/defs/gmake.mk b/defs/gmake.mk index 718131e937a4ae..fd1d467a46b14a 100644 --- a/defs/gmake.mk +++ b/defs/gmake.mk @@ -434,7 +434,7 @@ ifneq ($(DOT_WAIT),) endif ifeq ($(HAVE_GIT),yes) -REVISION_LATEST := $(shell $(GIT_IN_SRC) rev-parse HEAD) +REVISION_LATEST := $(shell $(GIT_IN_SRC) rev-parse HEAD 2>/dev/null) else REVISION_LATEST := update endif diff --git a/ext/socket/lib/socket.rb b/ext/socket/lib/socket.rb index 36fcceaee96300..465b74964f9b22 100644 --- a/ext/socket/lib/socket.rb +++ b/ext/socket/lib/socket.rb @@ -924,15 +924,11 @@ def self.tcp_with_fast_fallback(host, port, local_host = nil, local_port = nil, end end ensure - hostname_resolution_threads.each do |thread| - thread.exit - end + hostname_resolution_threads.each(&:exit).each(&:join) hostname_resolution_result&.close - connecting_sockets.each_key do |connecting_socket| - connecting_socket.close - end + connecting_sockets.each_key(&:close) end private_class_method :tcp_with_fast_fallback diff --git a/gc.c b/gc.c index f05b1a691375ca..3cf3bdebce2626 100644 --- a/gc.c +++ b/gc.c @@ -1291,6 +1291,34 @@ rb_gc_handle_weak_references(VALUE obj) } } +static inline bool +rb_gc_imemo_needs_cleanup_p(VALUE obj) +{ + switch (imemo_type(obj)) { + case imemo_constcache: + case imemo_cref: + case imemo_ifunc: + case imemo_memo: + case imemo_svar: + case imemo_callcache: + case imemo_throw_data: + return false; + + case imemo_env: + case imemo_ment: + case imemo_iseq: + case imemo_callinfo: + return true; + + case imemo_tmpbuf: + return ((rb_imemo_tmpbuf_t *)obj)->ptr != NULL; + + case imemo_fields: + return FL_TEST_RAW(obj, OBJ_FIELD_HEAP) || (id2ref_tbl && rb_shape_obj_has_id(obj)); + } + UNREACHABLE_RETURN(true); +} + /* * Returns true if the object requires a full rb_gc_obj_free() call during sweep, * false if it can be freed quickly without calling destructors or cleanup. @@ -1313,7 +1341,7 @@ rb_gc_obj_needs_cleanup_p(VALUE obj) switch (flags & RUBY_T_MASK) { case T_IMEMO: - return rb_imemo_needs_cleanup_p(obj); + return rb_gc_imemo_needs_cleanup_p(obj); case T_DATA: case T_OBJECT: diff --git a/gc.rb b/gc.rb index 01d798addb1596..48bed27880f3ae 100644 --- a/gc.rb +++ b/gc.rb @@ -269,7 +269,16 @@ def self.stat hash_or_key = nil # GC.stat_heap # # => # {0 => - # {slot_size: 40, + # {slot_size: 32, + # heap_eden_pages: 24, + # heap_eden_slots: 12288, + # total_allocated_pages: 24, + # force_major_gc_count: 0, + # force_incremental_marking_finish_count: 0, + # total_allocated_objects: 8450, + # total_freed_objects: 3120}, + # 1 => + # {slot_size: 64, # heap_eden_pages: 246, # heap_eden_slots: 402802, # total_allocated_pages: 246, @@ -277,8 +286,8 @@ def self.stat hash_or_key = nil # force_incremental_marking_finish_count: 1, # total_allocated_objects: 33867152, # total_freed_objects: 33520523}, - # 1 => - # {slot_size: 80, + # 2 => + # {slot_size: 128, # heap_eden_pages: 84, # heap_eden_slots: 68746, # total_allocated_pages: 84, @@ -286,8 +295,8 @@ def self.stat hash_or_key = nil # force_incremental_marking_finish_count: 4, # total_allocated_objects: 147491, # total_freed_objects: 90699}, - # 2 => - # {slot_size: 160, + # 3 => + # {slot_size: 256, # heap_eden_pages: 157, # heap_eden_slots: 64182, # total_allocated_pages: 157, @@ -295,8 +304,8 @@ def self.stat hash_or_key = nil # force_incremental_marking_finish_count: 0, # total_allocated_objects: 211460, # total_freed_objects: 190075}, - # 3 => - # {slot_size: 320, + # 4 => + # {slot_size: 512, # heap_eden_pages: 8, # heap_eden_slots: 1631, # total_allocated_pages: 8, @@ -304,8 +313,8 @@ def self.stat hash_or_key = nil # force_incremental_marking_finish_count: 0, # total_allocated_objects: 1422, # total_freed_objects: 700}, - # 4 => - # {slot_size: 640, + # 5 => + # {slot_size: 1024, # heap_eden_pages: 16, # heap_eden_slots: 1628, # total_allocated_pages: 16, @@ -316,7 +325,7 @@ def self.stat hash_or_key = nil # # In the example above, the keys in the outer hash are the heap identifiers: # - # GC.stat_heap.keys # => [0, 1, 2, 3, 4] + # GC.stat_heap.keys # => [0, 1, 2, 3, 4, 5] # # On CRuby, each heap identifier is an integer; # on other implementations, a heap identifier may be a string. @@ -324,9 +333,9 @@ def self.stat hash_or_key = nil # With only argument +heap_id+ given, # returns statistics for the given heap identifier: # - # GC.stat_heap(2) + # GC.stat_heap(3) # # => - # {slot_size: 160, + # {slot_size: 256, # heap_eden_pages: 157, # heap_eden_slots: 64182, # total_allocated_pages: 157, @@ -338,7 +347,7 @@ def self.stat hash_or_key = nil # With arguments +heap_id+ and +key+ given, # returns the value for the given key in the given heap: # - # GC.stat_heap(2, :slot_size) # => 160 + # GC.stat_heap(3, :slot_size) # => 256 # # With arguments +nil+ and +hash+ given, # merges the statistics for all heaps into the given hash: diff --git a/gc/default/default.c b/gc/default/default.c index 295a51d5d6e0cf..309f47ac4f4de9 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -187,9 +187,36 @@ static RB_THREAD_LOCAL_SPECIFIER int malloc_increase_local; #define USE_TICK_T (PRINT_ENTER_EXIT_TICK || PRINT_ROOT_TICKS) #ifndef HEAP_COUNT -# define HEAP_COUNT 5 +# if SIZEOF_VALUE >= 8 +# define HEAP_COUNT 12 +# else +# define HEAP_COUNT 5 +# endif +#endif + +/* The reciprocal table and pool_slot_sizes array are both generated from this + * single definition, so they can never get out of sync. */ +#if SIZEOF_VALUE >= 8 +# define EACH_POOL_SLOT_SIZE(SLOT) \ + SLOT(32) SLOT(40) SLOT(64) SLOT(80) SLOT(96) SLOT(128) \ + SLOT(160) SLOT(256) SLOT(512) SLOT(640) SLOT(768) SLOT(1024) +#else +# define EACH_POOL_SLOT_SIZE(SLOT) \ + SLOT(32) SLOT(64) SLOT(128) SLOT(256) SLOT(512) #endif +/* Precomputed reciprocals for fast slot index calculation. + * For slot size d: reciprocal = ceil(2^48 / d). + * Then offset / d == (uint32_t)((offset * reciprocal) >> 48) + * for all offset < HEAP_PAGE_SIZE. */ +#define SLOT_RECIPROCAL_SHIFT 48 +#define SLOT_RECIPROCAL(size) (((1ULL << SLOT_RECIPROCAL_SHIFT) + (size) - 1) / (size)) + +static const uint64_t heap_slot_reciprocal_table[HEAP_COUNT] = { +#define SLOT(size) SLOT_RECIPROCAL(size), + EACH_POOL_SLOT_SIZE(SLOT) +#undef SLOT +}; typedef struct ractor_newobj_heap_cache { struct free_slot *freelist; struct heap_page *using_page; @@ -653,6 +680,8 @@ typedef struct rb_objspace { unsigned long live_ractor_cache_count; + int sweeping_heap_count; + int fork_vm_lock_lev; } rb_objspace_t; @@ -690,14 +719,17 @@ size_t rb_gc_impl_obj_slot_size(VALUE obj); #define RVALUE_SLOT_SIZE (sizeof(struct RBasic) + sizeof(VALUE[RBIMPL_RVALUE_EMBED_LEN_MAX]) + RVALUE_OVERHEAD) static const size_t pool_slot_sizes[HEAP_COUNT] = { - RVALUE_SLOT_SIZE, - RVALUE_SLOT_SIZE * 2, - RVALUE_SLOT_SIZE * 4, - RVALUE_SLOT_SIZE * 8, - RVALUE_SLOT_SIZE * 16, +#define SLOT(size) size, + EACH_POOL_SLOT_SIZE(SLOT) +#undef SLOT }; -static uint8_t size_to_heap_idx[RVALUE_SLOT_SIZE * (1 << (HEAP_COUNT - 1)) / 8 + 1]; + +#if SIZEOF_VALUE >= 8 +static uint8_t size_to_heap_idx[1024 / 8 + 1]; +#else +static uint8_t size_to_heap_idx[512 / 8 + 1]; +#endif #ifndef MAX # define MAX(a, b) (((a) > (b)) ? (a) : (b)) @@ -707,11 +739,12 @@ static uint8_t size_to_heap_idx[RVALUE_SLOT_SIZE * (1 << (HEAP_COUNT - 1)) / 8 + #endif #define roomof(x, y) (((x) + (y) - 1) / (y)) #define CEILDIV(i, mod) roomof(i, mod) +#define MIN_POOL_SLOT_SIZE 32 enum { HEAP_PAGE_ALIGN = (1UL << HEAP_PAGE_ALIGN_LOG), HEAP_PAGE_ALIGN_MASK = (~(~0UL << HEAP_PAGE_ALIGN_LOG)), HEAP_PAGE_SIZE = HEAP_PAGE_ALIGN, - HEAP_PAGE_BITMAP_LIMIT = CEILDIV(CEILDIV(HEAP_PAGE_SIZE, RVALUE_SLOT_SIZE), BITS_BITLENGTH), + HEAP_PAGE_BITMAP_LIMIT = CEILDIV(CEILDIV(HEAP_PAGE_SIZE, MIN_POOL_SLOT_SIZE), BITS_BITLENGTH), HEAP_PAGE_BITMAP_SIZE = (BITS_SIZE * HEAP_PAGE_BITMAP_LIMIT), }; #define HEAP_PAGE_ALIGN (1 << HEAP_PAGE_ALIGN_LOG) @@ -773,8 +806,11 @@ struct free_slot { }; struct heap_page { + /* Cache line 0: allocation fast path + SLOT_INDEX */ + struct free_slot *freelist; + uintptr_t start; + uint64_t slot_size_reciprocal; unsigned short slot_size; - uint32_t slot_div_magic; unsigned short total_slots; unsigned short free_slots; unsigned short final_slots; @@ -789,8 +825,6 @@ struct heap_page { struct heap_page *free_next; struct heap_page_body *body; - uintptr_t start; - struct free_slot *freelist; struct ccan_list_node page_node; bits_t wb_unprotected_bits[HEAP_PAGE_BITMAP_LIMIT]; @@ -851,15 +885,13 @@ heap_page_in_global_empty_pages_pool(rb_objspace_t *objspace, struct heap_page * #define GET_PAGE_HEADER(x) (&GET_PAGE_BODY(x)->header) #define GET_HEAP_PAGE(x) (GET_PAGE_HEADER(x)->page) -static uint32_t slot_div_magics[HEAP_COUNT]; - static inline size_t -slot_index_for_offset(size_t offset, uint32_t div_magic) +slot_index_for_offset(size_t offset, uint64_t reciprocal) { - return (size_t)(((uint64_t)offset * div_magic) >> 32); + return (uint32_t)(((uint64_t)offset * reciprocal) >> SLOT_RECIPROCAL_SHIFT); } -#define SLOT_INDEX(page, p) slot_index_for_offset((uintptr_t)(p) - (page)->start, (page)->slot_div_magic) +#define SLOT_INDEX(page, p) slot_index_for_offset((uintptr_t)(p) - (page)->start, (page)->slot_size_reciprocal) #define SLOT_BITMAP_INDEX(page, p) (SLOT_INDEX(page, p) / BITS_BITLENGTH) #define SLOT_BITMAP_OFFSET(page, p) (SLOT_INDEX(page, p) & (BITS_BITLENGTH - 1)) #define SLOT_BITMAP_BIT(page, p) ((bits_t)1 << SLOT_BITMAP_OFFSET(page, p)) @@ -986,12 +1018,7 @@ gc_mode_verify(enum gc_mode mode) static inline bool has_sweeping_pages(rb_objspace_t *objspace) { - for (int i = 0; i < HEAP_COUNT; i++) { - if ((&heaps[i])->sweeping_page) { - return TRUE; - } - } - return FALSE; + return objspace->sweeping_heap_count != 0; } static inline size_t @@ -1649,7 +1676,7 @@ heap_page_add_freeobj(rb_objspace_t *objspace, struct heap_page *page, VALUE obj /* obj should belong to page */ !(page->start <= (uintptr_t)obj && (uintptr_t)obj < ((uintptr_t)page->start + (page->total_slots * page->slot_size)) && - obj % pool_slot_sizes[0] == 0)) { + obj % sizeof(VALUE) == 0)) { rb_bug("heap_page_add_freeobj: %p is not rvalue.", (void *)obj); } @@ -1990,19 +2017,17 @@ heap_add_page(rb_objspace_t *objspace, rb_heap_t *heap, struct heap_page *page) GC_ASSERT(!heap->sweeping_page); GC_ASSERT(heap_page_in_global_empty_pages_pool(objspace, page)); - /* Align start to the first slot_size boundary after the page header */ + /* Align start to slot_size boundary */ uintptr_t start = (uintptr_t)page->body + sizeof(struct heap_page_header); - size_t remainder = start % heap->slot_size; - if (remainder != 0) { - start += heap->slot_size - remainder; - } + uintptr_t rem = start % heap->slot_size; + if (rem) start += heap->slot_size - rem; int slot_count = (int)((HEAP_PAGE_SIZE - (start - (uintptr_t)page->body))/heap->slot_size); page->start = start; page->total_slots = slot_count; page->slot_size = heap->slot_size; - page->slot_div_magic = slot_div_magics[heap - heaps]; + page->slot_size_reciprocal = heap_slot_reciprocal_table[heap - heaps]; page->heap = heap; memset(&page->wb_unprotected_bits[0], 0, HEAP_PAGE_BITMAP_SIZE); @@ -2595,7 +2620,7 @@ is_pointer_to_heap(rb_objspace_t *objspace, const void *ptr) if (p < heap_pages_lomem || p > heap_pages_himem) return FALSE; RB_DEBUG_COUNTER_INC(gc_isptr_range); - if (p % pool_slot_sizes[0] != 0) return FALSE; + if (p % sizeof(VALUE) != 0) return FALSE; RB_DEBUG_COUNTER_INC(gc_isptr_align); page = heap_page_for_ptr(objspace, (uintptr_t)ptr); @@ -2992,6 +3017,7 @@ gc_abort(void *objspace_ptr) } if (is_lazy_sweeping(objspace)) { + objspace->sweeping_heap_count = 0; for (int i = 0; i < HEAP_COUNT; i++) { rb_heap_t *heap = &heaps[i]; @@ -3501,7 +3527,7 @@ gc_sweep_plane(rb_objspace_t *objspace, rb_heap_t *heap, uintptr_t p, bits_t bit do { VALUE vp = (VALUE)p; - GC_ASSERT(vp % pool_slot_sizes[0] == 0); + GC_ASSERT(vp % sizeof(VALUE) == 0); rb_asan_unpoison_object(vp, false); if (bitset & 1) { @@ -3726,6 +3752,9 @@ static void gc_sweep_start_heap(rb_objspace_t *objspace, rb_heap_t *heap) { heap->sweeping_page = ccan_list_top(&heap->pages, struct heap_page, page_node); + if (heap->sweeping_page) { + objspace->sweeping_heap_count++; + } heap->free_pages = NULL; heap->pooled_pages = NULL; if (!objspace->flags.immediate_sweep) { @@ -3837,7 +3866,8 @@ gc_sweep_finish_heap(rb_objspace_t *objspace, rb_heap_t *heap) heap_allocatable_bytes_expand(objspace, heap, swept_slots, heap->total_slots, heap->slot_size); } } - else if (objspace->heap_pages.allocatable_bytes < (min_free_slots - swept_slots) * heap->slot_size) { + else if (swept_slots < min_free_slots * 7 / 8 && + objspace->heap_pages.allocatable_bytes < (min_free_slots * 7 / 8 - swept_slots) * heap->slot_size) { gc_needs_major_flags |= GPR_FLAG_MAJOR_BY_NOFREE; heap->force_major_gc_count++; } @@ -3952,6 +3982,8 @@ gc_sweep_step(rb_objspace_t *objspace, rb_heap_t *heap) } while ((sweep_page = heap->sweeping_page)); if (!heap->sweeping_page) { + objspace->sweeping_heap_count--; + GC_ASSERT(objspace->sweeping_heap_count >= 0); gc_sweep_finish_heap(objspace, heap); if (!has_sweeping_pages(objspace)) { @@ -5466,7 +5498,7 @@ gc_marks_finish(rb_objspace_t *objspace) } if (objspace->heap_pages.allocatable_bytes == 0 && sweep_slots < min_free_slots) { - if (!full_marking) { + if (!full_marking && sweep_slots < min_free_slots * 7 / 8) { if (objspace->profile.count - objspace->rgengc.last_major_gc < RVALUE_OLD_AGE) { full_marking = TRUE; } @@ -5602,7 +5634,7 @@ gc_compact_plane(rb_objspace_t *objspace, rb_heap_t *heap, uintptr_t p, bits_t b do { VALUE vp = (VALUE)p; - GC_ASSERT(vp % pool_slot_sizes[0] == 0); + GC_ASSERT(vp % sizeof(VALUE) == 0); if (bitset & 1) { objspace->rcompactor.considered_count_table[BUILTIN_TYPE(vp)]++; @@ -9521,11 +9553,15 @@ rb_gc_impl_objspace_init(void *objspace_ptr) rb_bug("Could not preregister postponed job for GC"); } + /* A standard RVALUE (RBasic + embedded VALUEs + debug overhead) must fit + * in at least one pool. In debug builds RVALUE_OVERHEAD can push this + * beyond the 48-byte pool into the 64-byte pool, which is fine. */ + GC_ASSERT(rb_gc_impl_size_allocatable_p(sizeof(struct RBasic) + sizeof(VALUE[RBIMPL_RVALUE_EMBED_LEN_MAX]))); + for (int i = 0; i < HEAP_COUNT; i++) { rb_heap_t *heap = &heaps[i]; heap->slot_size = pool_slot_sizes[i]; - slot_div_magics[i] = (uint32_t)((uint64_t)UINT32_MAX / heap->slot_size + 1); ccan_list_head_init(&heap->pages); } @@ -9555,6 +9591,12 @@ rb_gc_impl_init(void) { VALUE gc_constants = rb_hash_new(); rb_hash_aset(gc_constants, ID2SYM(rb_intern("DEBUG")), GC_DEBUG ? Qtrue : Qfalse); + /* Minimum slot size that fits a standard RVALUE */ + size_t rvalue_pool = 0; + for (size_t i = 0; i < HEAP_COUNT; i++) { + if (pool_slot_sizes[i] >= RVALUE_SLOT_SIZE) { rvalue_pool = pool_slot_sizes[i]; break; } + } + rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVALUE_SIZE")), SIZET2NUM(rvalue_pool - RVALUE_OVERHEAD)); rb_hash_aset(gc_constants, ID2SYM(rb_intern("RBASIC_SIZE")), SIZET2NUM(sizeof(struct RBasic))); rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVALUE_OVERHEAD")), SIZET2NUM(RVALUE_OVERHEAD)); rb_hash_aset(gc_constants, ID2SYM(rb_intern("HEAP_PAGE_BITMAP_SIZE")), SIZET2NUM(HEAP_PAGE_BITMAP_SIZE)); diff --git a/gc/mmtk/mmtk.c b/gc/mmtk/mmtk.c index 1dacd95ab5dedc..3f680e76f4cd9f 100644 --- a/gc/mmtk/mmtk.c +++ b/gc/mmtk/mmtk.c @@ -635,22 +635,29 @@ void rb_gc_impl_set_params(void *objspace_ptr) { } static VALUE gc_verify_internal_consistency(VALUE self) { return Qnil; } -#define MMTK_HEAP_COUNT 6 -#define MMTK_MAX_OBJ_SIZE 640 - +#if SIZEOF_VALUE >= 8 +#define MMTK_HEAP_COUNT 12 +#define MMTK_MAX_OBJ_SIZE 1024 static size_t heap_sizes[MMTK_HEAP_COUNT + 1] = { - 32, 40, 80, 160, 320, MMTK_MAX_OBJ_SIZE, 0 + 32, 40, 64, 80, 96, 128, 160, 256, 512, 640, 768, MMTK_MAX_OBJ_SIZE, 0 }; +#else +#define MMTK_HEAP_COUNT 5 +#define MMTK_MAX_OBJ_SIZE 512 +static size_t heap_sizes[MMTK_HEAP_COUNT + 1] = { + 32, 64, 128, 256, MMTK_MAX_OBJ_SIZE, 0 +}; +#endif void rb_gc_impl_init(void) { VALUE gc_constants = rb_hash_new(); + rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVALUE_SIZE")), SIZET2NUM(SIZEOF_VALUE >= 8 ? 64 : 32)); rb_hash_aset(gc_constants, ID2SYM(rb_intern("RBASIC_SIZE")), SIZET2NUM(sizeof(struct RBasic))); rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVALUE_OVERHEAD")), INT2NUM(0)); rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVARGC_MAX_ALLOCATE_SIZE")), LONG2FIX(MMTK_MAX_OBJ_SIZE)); - // Pretend we have 5 size pools - rb_hash_aset(gc_constants, ID2SYM(rb_intern("SIZE_POOL_COUNT")), LONG2FIX(MMTK_HEAP_COUNT)); + rb_hash_aset(gc_constants, ID2SYM(rb_intern("HEAP_COUNT")), LONG2FIX(MMTK_HEAP_COUNT)); // TODO: correctly set RVALUE_OLD_AGE when we have generational GC support rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVALUE_OLD_AGE")), INT2FIX(0)); OBJ_FREEZE(gc_constants); diff --git a/hash.c b/hash.c index 79dbd5d8e90f0a..700c429d2aeb49 100644 --- a/hash.c +++ b/hash.c @@ -1142,12 +1142,15 @@ ar_values(VALUE hash, st_data_t *values, st_index_t size) static ar_table* ar_copy(VALUE hash1, VALUE hash2) { + RUBY_ASSERT(rb_gc_obj_slot_size(hash1) >= sizeof(struct RHash) + sizeof(ar_table)); ar_table *old_tab = RHASH_AR_TABLE(hash2); ar_table *new_tab = RHASH_AR_TABLE(hash1); - *new_tab = *old_tab; + unsigned int bound = RHASH_AR_TABLE_BOUND(hash2); + new_tab->ar_hint.word = old_tab->ar_hint.word; + MEMCPY(&new_tab->pairs, &old_tab->pairs, ar_table_pair, bound); RHASH_AR_TABLE(hash1)->ar_hint.word = RHASH_AR_TABLE(hash2)->ar_hint.word; - RHASH_AR_TABLE_BOUND_SET(hash1, RHASH_AR_TABLE_BOUND(hash2)); + RHASH_AR_TABLE_BOUND_SET(hash1, bound); RHASH_AR_TABLE_SIZE_SET(hash1, RHASH_AR_TABLE_SIZE(hash2)); rb_gc_writebarrier_remember(hash1); @@ -1490,6 +1493,23 @@ rb_hash_new_capa(long capa) return rb_hash_new_with_size((st_index_t)capa); } +VALUE +rb_hash_alloc_fixed_size(VALUE klass, st_index_t size) +{ + VALUE ret; + if (size > RHASH_AR_TABLE_MAX_SIZE) { + ret = hash_alloc_flags(klass, 0, Qnil, true); + hash_st_table_init(ret, &objhash, size); + } + else { + size_t slot_size = sizeof(struct RHash) + offsetof(ar_table, pairs) + size * sizeof(ar_table_pair); + ret = rb_wb_protected_newobj_of(GET_EC(), klass, T_HASH, 0, slot_size); + } + + RHASH_SET_IFNONE(ret, Qnil); + return ret; +} + static VALUE hash_copy(VALUE ret, VALUE hash) { @@ -7475,7 +7495,7 @@ Init_Hash(void) rb_define_singleton_method(rb_cHash, "ruby2_keywords_hash?", rb_hash_s_ruby2_keywords_hash_p, 1); rb_define_singleton_method(rb_cHash, "ruby2_keywords_hash", rb_hash_s_ruby2_keywords_hash, 1); - rb_cHash_empty_frozen = rb_hash_freeze(rb_hash_new()); + rb_cHash_empty_frozen = rb_hash_freeze(rb_hash_alloc_fixed_size(rb_cHash, 0)); RB_OBJ_SET_SHAREABLE(rb_cHash_empty_frozen); rb_vm_register_global_object(rb_cHash_empty_frozen); diff --git a/internal/class.h b/internal/class.h index 80bb8cb4f73ceb..c08aa0375524c7 100644 --- a/internal/class.h +++ b/internal/class.h @@ -111,9 +111,9 @@ struct RClass_and_rb_classext_t { }; #if SIZEOF_VALUE >= SIZEOF_LONG_LONG -// Assert that classes can be embedded in heaps[2] (which has 160B slot size) +// Assert that classes can be embedded in heaps[3] (256B slot size on 64-bit). // On 32bit platforms there is no variable width allocation so it doesn't matter. -STATIC_ASSERT(sizeof_rb_classext_t, sizeof(struct RClass_and_rb_classext_t) <= 4 * RVALUE_SIZE); +STATIC_ASSERT(sizeof_rb_classext_t, sizeof(struct RClass_and_rb_classext_t) <= 256); #endif struct RClass_boxable { diff --git a/internal/hash.h b/internal/hash.h index 03cd830506a6d6..9688478d1ee661 100644 --- a/internal/hash.h +++ b/internal/hash.h @@ -88,6 +88,7 @@ int rb_hash_stlike_delete(VALUE hash, st_data_t *pkey, st_data_t *pval); int rb_hash_stlike_foreach_with_replace(VALUE hash, st_foreach_check_callback_func *func, st_update_callback_func *replace, st_data_t arg); int rb_hash_stlike_update(VALUE hash, st_data_t key, st_update_callback_func *func, st_data_t arg); bool rb_hash_default_unredefined(VALUE hash); +VALUE rb_hash_alloc_fixed_size(VALUE klass, st_index_t size); VALUE rb_ident_hash_new_with_size(st_index_t size); void rb_hash_free(VALUE hash); RUBY_EXTERN VALUE rb_cHash_empty_frozen; diff --git a/internal/imemo.h b/internal/imemo.h index 4f2c4ebfbf6e98..e48832b4e5aaa0 100644 --- a/internal/imemo.h +++ b/internal/imemo.h @@ -292,30 +292,4 @@ rb_imemo_fields_complex_tbl(VALUE fields_obj) return IMEMO_OBJ_FIELDS(fields_obj)->as.complex.table; } -static inline bool -rb_imemo_needs_cleanup_p(VALUE obj) -{ - switch (imemo_type(obj)) { - case imemo_constcache: - case imemo_cref: - case imemo_ifunc: - case imemo_memo: - case imemo_svar: - case imemo_callcache: - case imemo_throw_data: - return false; - - case imemo_env: - case imemo_ment: - case imemo_iseq: - case imemo_callinfo: - case imemo_fields: - return true; - - case imemo_tmpbuf: - return ((rb_imemo_tmpbuf_t *)obj)->ptr != NULL; - } - UNREACHABLE_RETURN(true); -} - #endif /* INTERNAL_IMEMO_H */ diff --git a/internal/object.h b/internal/object.h index 22da9ddb5e26fc..3cf58d55d940e3 100644 --- a/internal/object.h +++ b/internal/object.h @@ -64,6 +64,9 @@ RBASIC_SET_CLASS(VALUE obj, VALUE klass) static inline size_t rb_obj_embedded_size(uint32_t fields_count) { +#if (defined(RACTOR_CHECK_MODE) && RACTOR_CHECK_MODE) || (defined(GC_DEBUG) && GC_DEBUG) + if (fields_count < 1) fields_count = 1; +#endif return offsetof(struct RObject, as.ary) + (sizeof(VALUE) * fields_count); } #endif /* INTERNAL_OBJECT_H */ diff --git a/lib/mkmf.rb b/lib/mkmf.rb index 38a5a15fb5fdd0..37ee4a70d9bbbb 100644 --- a/lib/mkmf.rb +++ b/lib/mkmf.rb @@ -573,11 +573,16 @@ def cc_command(opt="") conf) end - def cpp_command(outfile, opt="") + def cpp_config(opt) conf = cc_config(opt) if $universal and (arch_flag = conf['ARCH_FLAG']) and !arch_flag.empty? conf['ARCH_FLAG'] = arch_flag.gsub(/(?:\G|\s)-arch\s+\S+/, '') end + conf + end + + def cpp_command(outfile, opt="") + conf = cpp_config(opt) RbConfig::expand("$(CPP) #$INCFLAGS #$CPPFLAGS #$CFLAGS #{opt} #{CONFTEST_C} #{outfile}", conf) end @@ -926,20 +931,12 @@ def egrep_cpp(pat, src, opt = "", &b) xpopen(cpp_command('', opt)) do |f| if Regexp === pat puts(" ruby -ne 'print if #{pat.inspect}'") - f.grep(pat) {|l| + !f.grep(pat) {|l| puts "#{f.lineno}: #{l}" - return true - } - false + }.empty? else puts(" egrep '#{pat}'") - begin - stdin = $stdin.dup - $stdin.reopen(f) - system("egrep", pat) - ensure - $stdin.reopen(stdin) - end + system("egrep", pat, in: f) end end ensure @@ -3033,15 +3030,32 @@ def conftest_source def cc_command(opt="") conf = cc_config(opt) + cxx_command(opt, conf) RbConfig::expand("$(CXX) #$INCFLAGS #$CPPFLAGS #$CXXFLAGS #$ARCH_FLAG #{opt} -c #{CONFTEST_CXX}", conf) end + def cpp_command(outfile, opt="") + conf = cpp_config(opt) + cxx = cxx_command(opt, conf) + cpp = conf['CPP'].sub(/(\A|\s)#{Regexp.quote(conf['CC'])}(?=\z|\s)/) { + "#$1#{cxx}" + } + RbConfig::expand("#{cpp} #$INCFLAGS #$CPPFLAGS #$CXXFLAGS #{opt} #{CONFTEST_CXX} #{outfile}", + conf) + end + def link_command(ldflags, *opts) conf = link_config(ldflags, *opts) RbConfig::expand(TRY_LINK_CXX.dup, conf) end + def cxx_command(opt="", conf = cc_config(opt)) + cxx = conf['CXX'] + raise Errno::ENOENT, "C++ compiler not found" if !cxx or cxx == 'false' + cxx + end + # :startdoc: end diff --git a/parse.y b/parse.y index 2c8be06373263e..a92faab27fad51 100644 --- a/parse.y +++ b/parse.y @@ -8927,7 +8927,6 @@ number_literal_suffix(struct parser_params *p, int mask) } if (!ISASCII(c) || ISALPHA(c) || c == '_') { p->lex.pcur = lastp; - literal_flush(p, p->lex.pcur); return 0; } pushback(p, c); diff --git a/prism/prism.c b/prism/prism.c index 72c49da6f29499..3ae6ca3d7bf78e 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -19685,6 +19685,15 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, u PM_PARSER_ERR_TOKEN_FORMAT(parser, &next, PM_ERR_EXPECT_EOL_AFTER_STATEMENT, pm_token_str(next.type)); } } + + // It's possible that we've parsed a block argument through our + // call to parse_arguments. If we found one, we should mark it + // as invalid and destroy it, as we don't have a place for it. + if (arguments.block != NULL) { + pm_parser_err_node(parser, arguments.block, PM_ERR_UNEXPECTED_BLOCK_ARGUMENT); + pm_node_unreference(parser, arguments.block); + arguments.block = NULL; + } } switch (keyword.type) { diff --git a/prism_compile.c b/prism_compile.c index b693f2e05a806a..8f3f027f1898eb 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -876,11 +876,10 @@ pm_static_literal_value(rb_iseq_t *iseq, const pm_node_t *node, pm_scope_node_t rb_ary_cat(array, pair, 2); } - VALUE value = rb_hash_new_with_size(elements->size); + VALUE value = rb_hash_alloc_fixed_size(Qfalse, elements->size); rb_hash_bulk_insert(RARRAY_LEN(array), RARRAY_CONST_PTR(array), value); RB_GC_GUARD(array); - value = rb_obj_hide(value); RB_OBJ_SET_FROZEN_SHAREABLE(value); return value; } @@ -1560,10 +1559,9 @@ pm_compile_hash_elements(rb_iseq_t *iseq, const pm_node_t *node, const pm_node_l } index --; - VALUE hash = rb_hash_new_with_size(RARRAY_LEN(ary) / 2); + VALUE hash = rb_hash_alloc_fixed_size(Qfalse, RARRAY_LEN(ary) / 2); rb_hash_bulk_insert(RARRAY_LEN(ary), RARRAY_CONST_PTR(ary), hash); RB_GC_GUARD(ary); - hash = rb_obj_hide(hash); RB_OBJ_SET_FROZEN_SHAREABLE(hash); // Emit optimized code. @@ -5772,7 +5770,7 @@ pm_compile_shareable_constant_literal(rb_iseq_t *iseq, const pm_node_t *node, pm } case PM_HASH_NODE: { const pm_hash_node_t *cast = (const pm_hash_node_t *) node; - VALUE result = rb_hash_new_capa(cast->elements.size); + VALUE result = rb_hash_alloc_fixed_size(rb_cHash, cast->elements.size); for (size_t index = 0; index < cast->elements.size; index++) { const pm_node_t *element = cast->elements.nodes[index]; diff --git a/shape.c b/shape.c index 90036722f10026..c9169313cffe96 100644 --- a/shape.c +++ b/shape.c @@ -477,14 +477,14 @@ static attr_index_t shape_grow_capa(attr_index_t current_capa) { const attr_index_t *capacities = rb_shape_tree.capacities; + size_t heaps_count = rb_shape_tree.heaps_count; // First try to use the next size that will be embeddable in a larger object slot. - attr_index_t capa; - while ((capa = *capacities)) { + for (size_t i = 0; i < heaps_count; i++) { + attr_index_t capa = capacities[i]; if (capa > current_capa) { return capa; } - capacities++; } return (attr_index_t)rb_malloc_grow_capa(current_capa, sizeof(VALUE)); @@ -1539,12 +1539,17 @@ Init_default_shapes(void) while (heap_sizes[heaps_count]) { heaps_count++; } - attr_index_t *capacities = ALLOC_N(attr_index_t, heaps_count + 1); - capacities[heaps_count] = 0; + attr_index_t *capacities = ALLOC_N(attr_index_t, heaps_count); size_t index; for (index = 0; index < heaps_count; index++) { - capacities[index] = (heap_sizes[index] - sizeof(struct RBasic)) / sizeof(VALUE); + if (heap_sizes[index] > sizeof(struct RBasic)) { + capacities[index] = (heap_sizes[index] - sizeof(struct RBasic)) / sizeof(VALUE); + } + else { + capacities[index] = 0; + } } + rb_shape_tree.heaps_count = heaps_count; rb_shape_tree.capacities = capacities; #ifdef HAVE_MMAP diff --git a/shape.h b/shape.h index ad9b148247b32e..09487006bc912b 100644 --- a/shape.h +++ b/shape.h @@ -115,6 +115,7 @@ typedef struct { rb_shape_t *shape_list; rb_shape_t *root_shape; const attr_index_t *capacities; + size_t heaps_count; rb_atomic_t next_shape_id; redblack_node_t *shape_cache; diff --git a/test/.excludes-mmtk/TestObjSpace.rb b/test/.excludes-mmtk/TestObjSpace.rb index 82858b256fdcc9..94eb2c436d4435 100644 --- a/test/.excludes-mmtk/TestObjSpace.rb +++ b/test/.excludes-mmtk/TestObjSpace.rb @@ -1,5 +1,4 @@ exclude(:test_dump_all_full, "testing behaviour specific to default GC") exclude(:test_dump_flag_age, "testing behaviour specific to default GC") exclude(:test_dump_flags, "testing behaviour specific to default GC") -exclude(:test_dump_includes_slot_size, "can be removed when pool 0 slot size is 32 bytes") exclude(:test_dump_objects_dumps_page_slot_sizes, "testing behaviour specific to default GC") diff --git a/test/mkmf/test_egrep_cpp.rb b/test/mkmf/test_egrep_cpp.rb index 7ac0e6001001fa..112632496551a5 100644 --- a/test/mkmf/test_egrep_cpp.rb +++ b/test/mkmf/test_egrep_cpp.rb @@ -10,4 +10,18 @@ def test_egrep_cpp def test_not_have_func assert_equal(false, egrep_cpp(/never match/, ""), MKMFLOG) end + + class TestMkmfEgrepCxx < self + def test_cxx_egrep_cpp + assert_equal(true, MakeMakefile["C++"].egrep_cpp(/^ok/, <<~SRC), MKMFLOG) + #ifdef __cplusplus + ok + #else + #error not C++ + #endif + SRC + rescue Errno::ENOENT + omit "C++ compiler not available: #{$!.message}" + end + end end diff --git a/test/objspace/test_objspace.rb b/test/objspace/test_objspace.rb index c1208cf4242e84..8d019e587a63e2 100644 --- a/test/objspace/test_objspace.rb +++ b/test/objspace/test_objspace.rb @@ -473,12 +473,12 @@ def test_dump_object assert_include(info, '"embedded":true') assert_include(info, '"ivars":0') - # Non-embed object + # Non-embed object (needs > 6 ivars to exceed pool 0 embed capacity) obj = klass.new - 5.times { |i| obj.instance_variable_set("@ivar#{i}", 0) } + 7.times { |i| obj.instance_variable_set("@ivar#{i}", 0) } info = ObjectSpace.dump(obj) assert_not_include(info, '"embedded":true') - assert_include(info, '"ivars":5') + assert_include(info, '"ivars":7') end def test_dump_control_char @@ -648,7 +648,8 @@ def dump_my_heap_please next if obj["type"] == "SHAPE" assert_not_nil obj["slot_size"] - assert_equal 0, obj["slot_size"] % GC.stat_heap(0, :slot_size) + slot_sizes = GC::INTERNAL_CONSTANTS[:HEAP_COUNT].times.map { |i| GC.stat_heap(i, :slot_size) } + assert_include slot_sizes, obj["slot_size"] } end end diff --git a/test/prism/errors/block_pass_return_value.txt b/test/prism/errors/block_pass_return_value.txt new file mode 100644 index 00000000000000..c9d12281d93040 --- /dev/null +++ b/test/prism/errors/block_pass_return_value.txt @@ -0,0 +1,33 @@ +return &b + ^ unexpected '&', expecting end-of-input + ^ unexpected '&', ignoring it + +return(&b) + ^ unexpected '&', ignoring it + ^ unexpected '&', expecting end-of-input + ^ unexpected '&', ignoring it + ^ expected a matching `)` + ^ unexpected '&', expecting end-of-input + ^ unexpected '&', ignoring it + ^ unexpected ')', expecting end-of-input + ^ unexpected ')', ignoring it + +return a, &b + ^~ block argument should not be given + +return(a, &b) + ^~ unexpected write target + ^ unexpected '&', expecting end-of-input + ^ unexpected '&', ignoring it + ^ expected a matching `)` + ^ unexpected '&', expecting end-of-input + ^ unexpected '&', ignoring it + ^ unexpected ')', expecting end-of-input + ^ unexpected ')', ignoring it + +tap { break a, &b } + ^~ block argument should not be given + +tap { next a, &b } + ^~ block argument should not be given + diff --git a/test/ruby/test_ast.rb b/test/ruby/test_ast.rb index 2ba87c60802f75..5d95dcd46f4225 100644 --- a/test/ruby/test_ast.rb +++ b/test/ruby/test_ast.rb @@ -1751,6 +1751,14 @@ def test_negative_numeric_locations assert_locations(node.children.last.locations, [[1, 0, 1, 2]]) end + def test_numeric_location_with_nonsuffix + node = ast_parse("1if true") + assert_locations(node.children.last.children[1].locations, [[1, 0, 1, 1]]) + + node = ast_parse("1q", error_tolerant: true) + assert_locations(node.children.last.locations, [[1, 0, 1, 1]]) + end + private def ast_parse(src, **options) begin diff --git a/test/ruby/test_gc.rb b/test/ruby/test_gc.rb index 43540d4412af21..21448294c2073c 100644 --- a/test/ruby/test_gc.rb +++ b/test/ruby/test_gc.rb @@ -230,7 +230,7 @@ def test_stat_heap GC.stat(stat) end - assert_equal GC.stat_heap(0, :slot_size) * (2**i), stat_heap[:slot_size] + assert_equal GC.stat_heap(i, :slot_size), stat_heap[:slot_size] assert_operator stat_heap[:heap_live_slots], :<=, stat[:heap_live_slots] assert_operator stat_heap[:heap_free_slots], :<=, stat[:heap_free_slots] assert_operator stat_heap[:heap_final_slots], :<=, stat[:heap_final_slots] @@ -773,7 +773,7 @@ def initialize end def test_gc_stress_at_startup - assert_in_out_err([{"RUBY_DEBUG"=>"gc_stress"}], '', [], [], '[Bug #15784]', success: true, timeout: 60) + assert_in_out_err([{"RUBY_DEBUG"=>"gc_stress"}], '', [], [], '[Bug #15784]', success: true, timeout: 120) end def test_gc_disabled_start diff --git a/test/ruby/test_gc_compact.rb b/test/ruby/test_gc_compact.rb index 46b4a0605e35b4..b8d53d71973acc 100644 --- a/test/ruby/test_gc_compact.rb +++ b/test/ruby/test_gc_compact.rb @@ -207,7 +207,7 @@ def test_updating_references_for_heap_allocated_shared_arrays end def test_updating_references_for_embed_shared_arrays - omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1 + omit if GC::INTERNAL_CONSTANTS[:HEAP_COUNT] == 1 assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10) begin; @@ -256,7 +256,7 @@ def test_updating_references_for_heap_allocated_frozen_shared_arrays end def test_updating_references_for_embed_frozen_shared_arrays - omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1 + omit if GC::INTERNAL_CONSTANTS[:HEAP_COUNT] == 1 assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10) begin; @@ -284,7 +284,7 @@ def test_updating_references_for_embed_frozen_shared_arrays end def test_moving_arrays_down_heaps - omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1 + omit if GC::INTERNAL_CONSTANTS[:HEAP_COUNT] == 1 assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10) begin; @@ -306,7 +306,7 @@ def test_moving_arrays_down_heaps end def test_moving_arrays_up_heaps - omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1 + omit if GC::INTERNAL_CONSTANTS[:HEAP_COUNT] == 1 assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10) begin; @@ -315,7 +315,7 @@ def test_moving_arrays_up_heaps GC.verify_compaction_references(expand_heap: true, toward: :empty) Fiber.new { - ary = "hello".chars + ary = "hello world".chars # > 6 elements to exceed pool 0 embed capacity $arys = ARY_COUNT.times.map do x = [] ary.each { |e| x << e } @@ -330,7 +330,7 @@ def test_moving_arrays_up_heaps end def test_moving_objects_between_heaps - omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1 + omit if GC::INTERNAL_CONSTANTS[:HEAP_COUNT] == 1 assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 60) begin; @@ -362,7 +362,7 @@ def add_ivars end def test_compact_objects_of_varying_sizes - omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1 + omit if GC::INTERNAL_CONSTANTS[:HEAP_COUNT] == 1 assert_ruby_status([], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10) begin; @@ -378,7 +378,7 @@ def test_compact_objects_of_varying_sizes end def test_moving_strings_up_heaps - omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1 + omit if GC::INTERNAL_CONSTANTS[:HEAP_COUNT] == 1 assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 30) begin; @@ -399,7 +399,7 @@ def test_moving_strings_up_heaps end def test_moving_strings_down_heaps - omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1 + omit if GC::INTERNAL_CONSTANTS[:HEAP_COUNT] == 1 assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 30) begin; @@ -419,7 +419,7 @@ def test_moving_strings_down_heaps end def test_moving_hashes_down_heaps - omit if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1 + omit if GC::INTERNAL_CONSTANTS[:HEAP_COUNT] == 1 # AR and ST hashes are in the same size pool on 32 bit omit unless RbConfig::SIZEOF["uint64_t"] <= RbConfig::SIZEOF["void*"] diff --git a/test/ruby/test_object.rb b/test/ruby/test_object.rb index 7342b3f933e753..646244a43ae6e0 100644 --- a/test/ruby/test_object.rb +++ b/test/ruby/test_object.rb @@ -358,38 +358,40 @@ def test_remove_instance_variable def test_remove_instance_variable_re_embed assert_separately(%w[-robjspace], "#{<<~"begin;"}\n#{<<~'end;'}") begin; - c = Class.new do - attr_reader :a, :b, :c + # Determine the RVALUE pool's embed capacity from GC constants. + rvalue_size = GC::INTERNAL_CONSTANTS[:RVALUE_SIZE] + rbasic_size = GC::INTERNAL_CONSTANTS[:RBASIC_SIZE] + embed_cap = (rvalue_size - rbasic_size) / RbConfig::SIZEOF["void*"] - def initialize - @a = nil - @b = nil - @c = nil - end - end + # Build a class whose initialize sets embed_cap ivars so objects + # are allocated in the RVALUE pool with embedded storage. + init_body = embed_cap.times.map { |i| "@v#{i} = nil" }.join("; ") + c = Class.new { class_eval("def initialize; #{init_body}; end") } o1 = c.new o2 = c.new - o1.instance_variable_set(:@foo, 5) - o1.instance_variable_set(:@a, 0) - o1.instance_variable_set(:@b, 1) - o1.instance_variable_set(:@c, 2) + # All embed_cap ivars fit - should be embedded + embed_cap.times { |i| o1.instance_variable_set(:"@v#{i}", i) } + assert_includes ObjectSpace.dump(o1), '"embedded":true' + + # One more ivar overflows embed capacity + o1.instance_variable_set(:@overflow, 99) refute_includes ObjectSpace.dump(o1), '"embedded":true' - o1.remove_instance_variable(:@foo) + + # Remove the overflow ivar - should re-embed + o1.remove_instance_variable(:@overflow) assert_includes ObjectSpace.dump(o1), '"embedded":true' - o2.instance_variable_set(:@a, 0) - o2.instance_variable_set(:@b, 1) - o2.instance_variable_set(:@c, 2) + # An object that never overflowed is also embedded + embed_cap.times { |i| o2.instance_variable_set(:"@v#{i}", i) } assert_includes ObjectSpace.dump(o2), '"embedded":true' - assert_equal(0, o1.a) - assert_equal(1, o1.b) - assert_equal(2, o1.c) - assert_equal(0, o2.a) - assert_equal(1, o2.b) - assert_equal(2, o2.c) + # Verify values survived re-embedding + embed_cap.times do |i| + assert_equal(i, o1.instance_variable_get(:"@v#{i}")) + assert_equal(i, o2.instance_variable_get(:"@v#{i}")) + end end; end diff --git a/test/ruby/test_time.rb b/test/ruby/test_time.rb index 595e183f6c44ca..b2cbd06a9f3ab5 100644 --- a/test/ruby/test_time.rb +++ b/test/ruby/test_time.rb @@ -1421,7 +1421,7 @@ def test_memsize # Time objects are common in some code, try to keep them small omit "Time object size test" if /^(?:i.?86|x86_64)-linux/ !~ RUBY_PLATFORM omit "GC is in debug" if GC::INTERNAL_CONSTANTS[:RVALUE_OVERHEAD] > 0 - omit "memsize is not accurate due to using malloc_usable_size" if GC::INTERNAL_CONSTANTS[:SIZE_POOL_COUNT] == 1 + omit "memsize is not accurate due to using malloc_usable_size" if GC::INTERNAL_CONSTANTS[:HEAP_COUNT] == 1 omit "Only run this test on 64-bit" if RbConfig::SIZEOF["void*"] != 8 require 'objspace' @@ -1433,7 +1433,10 @@ def test_memsize RbConfig::SIZEOF["void*"] # Same size as VALUE end sizeof_vtm = RbConfig::SIZEOF["void*"] * 4 + 8 - expect = GC.stat_heap(0, :slot_size) - GC::INTERNAL_CONSTANTS[:RVALUE_OVERHEAD] + sizeof_timew + sizeof_vtm + data_size = GC::INTERNAL_CONSTANTS[:RVALUE_SIZE] + sizeof_timew + sizeof_vtm + # Round up to the smallest slot size that fits + slot_sizes = GC::INTERNAL_CONSTANTS[:HEAP_COUNT].times.map { |i| GC.stat_heap(i, :slot_size) } + expect = slot_sizes.find { |s| s >= data_size } || slot_sizes.last assert_operator ObjectSpace.memsize_of(t), :<=, expect rescue LoadError => e omit "failed to load objspace: #{e.message}" diff --git a/test/ruby/test_transcode.rb b/test/ruby/test_transcode.rb index 99b5ee8d43da94..2c4462eb717237 100644 --- a/test/ruby/test_transcode.rb +++ b/test/ruby/test_transcode.rb @@ -2344,7 +2344,7 @@ def test_ractor_lazy_load_encoding def test_ractor_lazy_load_encoding_random omit 'unstable on s390x and windows' if RUBY_PLATFORM =~ /s390x|mswin/ - assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}") + assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}", timeout: 30) begin; rs = [] 100.times do diff --git a/tool/zjit_bisect.rb b/tool/zjit_bisect.rb index 9fa98ca0f3e162..a265a3c01f9a9b 100755 --- a/tool/zjit_bisect.rb +++ b/tool/zjit_bisect.rb @@ -5,6 +5,9 @@ require 'tempfile' require 'timeout' +required_ruby_version = Gem::Version.new("3.4.0") +raise "Ruby version #{required_ruby_version} or higher is required" if Gem::Version.new(RUBY_VERSION) < required_ruby_version + ARGS = {timeout: 5} OptionParser.new do |opts| opts.banner += " -- " @@ -79,6 +82,9 @@ def add_zjit_options cmd zjit_opts = cmd.select { |arg| arg.start_with?("--zjit") } run_opts_index = cmd.find_index { |arg| arg.start_with?("RUN_OPTS=") } specopts_index = cmd.find_index { |arg| arg.start_with?("SPECOPTS=") } + if run_opts_index && specopts_index + raise "Expected only one of RUN_OPTS or SPECOPTS to be present in make command, but both were found" + end if run_opts_index run_opts = Shellwords.split(cmd[run_opts_index].delete_prefix("RUN_OPTS=")) run_opts.concat(zjit_opts) @@ -129,7 +135,7 @@ def run_with_jit_list(ruby, options, jit_list) # Try running with no JIT list to get a stable baseline unless run_with_jit_list(RUBY, OPTIONS, []).success? - cmd = [RUBY, "--zjit-allowed-iseqs=/dev/null", *OPTIONS].shelljoin + cmd = add_zjit_options([RUBY, "--zjit-allowed-iseqs=/dev/null", *OPTIONS]).shelljoin raise "The command failed unexpectedly with an empty JIT list. To reproduce, try running the following: `#{cmd}`" end # Collect the JIT list from the failing Ruby process diff --git a/zjit.rb b/zjit.rb index ffee0006962fc9..89a4a15cfd5d95 100644 --- a/zjit.rb +++ b/zjit.rb @@ -58,8 +58,6 @@ def induce_side_exit! = nil # A directive for the compiler to emit a breakpoint instruction at the call site of this method. # To show this to ZJIT, say `::RubyVM::ZJIT.induce_breakpoint!` verbatim. # Other forms are too dynamic to detect during compilation. - # - # Actually running this method does nothing, whether ZJIT sees the call or not. def induce_breakpoint! = nil # Check if `--zjit-stats` is used diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 5df020f4f56bc6..da15d30d03c741 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -2458,8 +2458,9 @@ fn gen_has_type(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, ty: Typ asm.csel_e(Opnd::Imm(1), Opnd::Imm(0)) } else if ty.is_subtype(types::StaticSymbol) { // Static symbols have (val & 0xff) == RUBY_SYMBOL_FLAG - // Use 8-bit comparison like YJIT does. GuardType should not be used - // for a known VALUE, which with_num_bits() does not support. + // Use 8-bit comparison like YJIT does. + // If `val` is a constant (rare but possible), put it in a register to allow masking. + let val = asm.load_imm(val); asm.cmp(val.with_num_bits(8), Opnd::UImm(RUBY_SYMBOL_FLAG as u64)); asm.csel_e(Opnd::Imm(1), Opnd::Imm(0)) } else if ty.is_subtype(types::NilClass) { @@ -2528,8 +2529,9 @@ fn gen_guard_type(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, guard asm.jne(jit, side_exit(jit, state, GuardType(guard_type))); } else if guard_type.is_subtype(types::StaticSymbol) { // Static symbols have (val & 0xff) == RUBY_SYMBOL_FLAG - // Use 8-bit comparison like YJIT does. GuardType should not be used - // for a known VALUE, which with_num_bits() does not support. + // Use 8-bit comparison like YJIT does. + // If `val` is a constant (rare but possible), put it in a register to allow masking. + let val = asm.load_imm(val); asm.cmp(val.with_num_bits(8), Opnd::UImm(RUBY_SYMBOL_FLAG as u64)); asm.jne(jit, side_exit(jit, state, GuardType(guard_type))); } else if guard_type.is_subtype(types::NilClass) { @@ -3543,6 +3545,15 @@ impl Assembler { } } + /// Emits a load for constant based operands and returns a vreg, + /// otherwise returns recv. + fn load_imm(&mut self, recv: Opnd) -> Opnd { + match recv { + Opnd::Value { .. } | Opnd::UImm(_) | Opnd::Imm(_) => self.load(recv), + _ => recv, + } + } + /// Make a C call while marking the start and end positions for IseqCall fn ccall_with_iseq_call(&mut self, fptr: *const u8, opnds: Vec, iseq_call: &IseqCallRef) -> Opnd { // We need to create our own branch rc objects so that we can move the closure below diff --git a/zjit/src/codegen_tests.rs b/zjit/src/codegen_tests.rs index d57efdc698c031..e19d365057b87b 100644 --- a/zjit/src/codegen_tests.rs +++ b/zjit/src/codegen_tests.rs @@ -5586,3 +5586,27 @@ fn test_send_block_unused_warning_emitted_from_jit() { test "#), @"true"); } + +#[test] +fn test_load_immediates_into_registers_before_masking() { + // See https://github.com/ruby/ruby/pull/16669 -- this is a reduced reproduction from a Ruby + // spec. + set_call_threshold(2); + assert_snapshot!(inspect(r#" + def test + klass = Class.new do + def ===(o) + true + end + end + + case 1 + when klass.new + :called + end == :called + end + + test + test + "#), @"true"); +} diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 767f6499e80606..e104a0f320a505 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -397,6 +397,7 @@ fn inline_array_push(fun: &mut hir::Function, block: hir::BlockId, recv: hir::In if let &[val] = args { if !fun.likely_a(recv, types::Array, state) { return None; } let recv = fun.coerce_to(block, recv, types::Array, state); + fun.guard_not_frozen(block, recv, state); let _ = fun.push_insn(block, hir::Insn::ArrayPush { array: recv, val, state }); return Some(recv); } @@ -408,6 +409,7 @@ fn inline_array_pop(fun: &mut hir::Function, block: hir::BlockId, recv: hir::Ins let &[] = args else { return None; }; if !fun.likely_a(recv, types::Array, state) { return None; } let recv = fun.coerce_to(block, recv, types::Array, state); + fun.guard_not_frozen(block, recv, state); fun.guard_not_shared(block, recv, state); Some(fun.push_insn(block, hir::Insn::ArrayPop { array: recv, state })) } diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 9a09e8789cea97..dced5ebe4a117c 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -7111,6 +7111,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { let count = get_arg(pc, 0).as_usize(); let vals = state.stack_pop_n(count)?; let array = state.stack_pop()?; + fun.guard_not_frozen(block, array, exit_id); for val in vals.into_iter() { fun.push_insn(block, Insn::ArrayPush { array, val, state: exit_id }); } diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 88ab8d30711147..96543daf7f9b56 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -5517,10 +5517,7 @@ mod hir_opt_tests { v14:HeapBasicObject = RefineType v6, HeapBasicObject v17:Fixnum[2] = Const Value(2) PatchPoint SingleRactorMode - StoreField v14, :@bar@0x1004, v17 - WriteBarrier v14, v17 - v40:CShape[0x1005] = Const CShape(0x1005) - StoreField v14, :_shape_id@0x1000, v40 + SetIvar v14, :@bar, v17 CheckInterrupts Return v17 "); @@ -7794,22 +7791,23 @@ mod hir_opt_tests { v26:CInt64 = IntAnd v12, v23 v27:CBool = IsBitEqual v26, v25 IfTrue v27, bb6() - v31:BasicObject = GetIvar v11, :@foo - Jump bb4(v31) + v32:BasicObject = GetIvar v11, :@foo + Jump bb4(v32) bb5(): v20:CPtr = LoadField v11, :_as_heap@0x1003 v21:BasicObject = LoadField v20, :@foo@0x1004 Jump bb4(v21) bb6(): - v29:BasicObject = LoadField v11, :@foo@0x1003 - Jump bb4(v29) + v29:CPtr = LoadField v11, :_as_heap@0x1003 + v30:BasicObject = LoadField v29, :@foo@0x1000 + Jump bb4(v30) bb4(v13:BasicObject): - v34:Fixnum[1] = Const Value(1) + v35:Fixnum[1] = Const Value(1) PatchPoint MethodRedefined(Integer@0x1008, +@0x1010, cme:0x1018) - v45:Fixnum = GuardType v13, Fixnum - v46:Fixnum = FixnumAdd v45, v34 + v46:Fixnum = GuardType v13, Fixnum + v47:Fixnum = FixnumAdd v46, v35 CheckInterrupts - Return v46 + Return v47 "); } @@ -7864,30 +7862,32 @@ mod hir_opt_tests { v17:CInt64 = IntAnd v12, v14 v18:CBool = IsBitEqual v17, v16 IfTrue v18, bb5() - v22:CUInt64[0xffffffff0000001f] = Const CUInt64(0xffffffff0000001f) - v23:CPtr[CPtr(0x1002)] = Const CPtr(0x1002) - v24 = RefineType v23, CUInt64 - v25:CInt64 = IntAnd v12, v22 - v26:CBool = IsBitEqual v25, v24 - IfTrue v26, bb6() - v44:CShape = LoadField v11, :_shape_id@0x1003 - v45:CShape[0x1004] = GuardBitEquals v44, CShape(0x1004) - v46:BasicObject = LoadField v11, :@foo@0x1005 - Jump bb4(v46) + v23:CUInt64[0xffffffff0000001f] = Const CUInt64(0xffffffff0000001f) + v24:CPtr[CPtr(0x1002)] = Const CPtr(0x1002) + v25 = RefineType v24, CUInt64 + v26:CInt64 = IntAnd v12, v23 + v27:CBool = IsBitEqual v26, v25 + IfTrue v27, bb6() + v45:CShape = LoadField v11, :_shape_id@0x1003 + v46:CShape[0x1004] = GuardBitEquals v45, CShape(0x1004) + v47:CPtr = LoadField v11, :_as_heap@0x1005 + v48:BasicObject = LoadField v47, :@foo@0x1000 + Jump bb4(v48) bb5(): - v20:BasicObject = LoadField v11, :@foo@0x1005 - Jump bb4(v20) + v20:CPtr = LoadField v11, :_as_heap@0x1005 + v21:BasicObject = LoadField v20, :@foo@0x1000 + Jump bb4(v21) bb6(): - v28:CPtr = LoadField v11, :_as_heap@0x1005 - v29:BasicObject = LoadField v28, :@foo@0x1006 - Jump bb4(v29) + v29:CPtr = LoadField v11, :_as_heap@0x1005 + v30:BasicObject = LoadField v29, :@foo@0x1006 + Jump bb4(v30) bb4(v13:BasicObject): - v34:Fixnum[1] = Const Value(1) + v35:Fixnum[1] = Const Value(1) PatchPoint MethodRedefined(Integer@0x1008, +@0x1010, cme:0x1018) - v49:Fixnum = GuardType v13, Fixnum - v50:Fixnum = FixnumAdd v49, v34 + v51:Fixnum = GuardType v13, Fixnum + v52:Fixnum = FixnumAdd v51, v35 CheckInterrupts - Return v50 + Return v52 "); } @@ -9564,6 +9564,8 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(Array@0x1008) PatchPoint MethodRedefined(Array@0x1008, <<@0x1010, cme:0x1018) v27:ArrayExact = GuardType v10, ArrayExact + v28:CUInt64 = LoadField v27, :_rbasic_flags@0x1040 + v29:CUInt64 = GuardNoBitsSet v28, RUBY_FL_FREEZE=CUInt64(2048) ArrayPush v27, v15 CheckInterrupts Return v27 @@ -9596,6 +9598,8 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(Array@0x1008) PatchPoint MethodRedefined(Array@0x1008, push@0x1010, cme:0x1018) v26:ArrayExact = GuardType v10, ArrayExact + v27:CUInt64 = LoadField v26, :_rbasic_flags@0x1040 + v28:CUInt64 = GuardNoBitsSet v27, RUBY_FL_FREEZE=CUInt64(2048) ArrayPush v26, v15 CheckInterrupts Return v26 @@ -9666,6 +9670,8 @@ mod hir_opt_tests { v26:RubyValue = LoadField v23, :_ep_specval@0x1050 v27:FalseClass = GuardBitEquals v26, Value(false) v28:Array = GuardType v9, Array + v29:CUInt64 = LoadField v28, :_rbasic_flags@0x1051 + v30:CUInt64 = GuardNoBitsSet v29, RUBY_FL_FREEZE=CUInt64(2048) ArrayPush v28, v10 CheckInterrupts Return v28 @@ -9698,17 +9704,18 @@ mod hir_opt_tests { v20:CallableMethodEntry[VALUE(0x1040)] = GuardBitEquals v19, Value(VALUE(0x1040)) v21:RubyValue = LoadField v18, :_ep_specval@0x1048 v22:FalseClass = GuardBitEquals v21, Value(false) - v28:CPtr = GetEP 0 - v29:RubyValue = LoadField v28, :_ep_method_entry@0x1038 - v30:CallableMethodEntry[VALUE(0x1040)] = GuardBitEquals v29, Value(VALUE(0x1040)) - v31:RubyValue = LoadField v28, :_ep_specval@0x1048 - v32:FalseClass = GuardBitEquals v31, Value(false) + v30:CPtr = GetEP 0 + v31:RubyValue = LoadField v30, :_ep_method_entry@0x1038 + v32:CallableMethodEntry[VALUE(0x1040)] = GuardBitEquals v31, Value(VALUE(0x1040)) + v33:RubyValue = LoadField v30, :_ep_specval@0x1048 + v34:FalseClass = GuardBitEquals v33, Value(false) v23:Array = GuardType v6, Array v24:CUInt64 = LoadField v23, :_rbasic_flags@0x1049 - v25:CUInt64 = GuardNoBitsSet v24, RUBY_ELTS_SHARED=CUInt64(4096) - v26:BasicObject = ArrayPop v23 + v25:CUInt64 = GuardNoBitsSet v24, RUBY_FL_FREEZE=CUInt64(2048) + v27:CUInt64 = GuardNoBitsSet v24, RUBY_ELTS_SHARED=CUInt64(4096) + v28:BasicObject = ArrayPop v23 CheckInterrupts - Return v26 + Return v28 "); } @@ -14968,21 +14975,21 @@ mod hir_opt_tests { v14:HeapBasicObject = RefineType v6, HeapBasicObject v17:Fixnum[2] = Const Value(2) PatchPoint SingleRactorMode - StoreField v14, :@b@0x1004, v17 - WriteBarrier v14, v17 - v54:CShape[0x1005] = Const CShape(0x1005) - StoreField v14, :_shape_id@0x1000, v54 + SetIvar v14, :@b, v17 v21:HeapBasicObject = RefineType v14, HeapBasicObject v24:Fixnum[3] = Const Value(3) PatchPoint SingleRactorMode - StoreField v21, :@c@0x1006, v24 - WriteBarrier v21, v24 - v61:CShape[0x1007] = Const CShape(0x1007) - StoreField v21, :_shape_id@0x1000, v61 + SetIvar v21, :@c, v24 v28:HeapBasicObject = RefineType v21, HeapBasicObject v31:Fixnum[4] = Const Value(4) PatchPoint SingleRactorMode - SetIvar v28, :@d, v31 + v50:CShape = LoadField v28, :_shape_id@0x1000 + v51:CShape[0x1004] = GuardBitEquals v50, CShape(0x1004) + v52:CPtr = LoadField v28, :_as_heap@0x1002 + StoreField v52, :@d@0x1005, v31 + WriteBarrier v28, v31 + v55:CShape[0x1006] = Const CShape(0x1006) + StoreField v28, :_shape_id@0x1000, v55 CheckInterrupts Return v31 "); diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs index 3358d7573593b7..4cb01d02c89635 100644 --- a/zjit/src/hir/tests.rs +++ b/zjit/src/hir/tests.rs @@ -2405,10 +2405,12 @@ pub(crate) mod hir_build_tests { bb3(v9:BasicObject, v10:BasicObject): v16:ArrayExact = ToNewArray v10 v18:Fixnum[1] = Const Value(1) + v20:CUInt64 = LoadField v16, :_rbasic_flags@0x1001 + v21:CUInt64 = GuardNoBitsSet v20, RUBY_FL_FREEZE=CUInt64(2048) ArrayPush v16, v18 - v22:BasicObject = Send v9, :foo, v16 # SendFallbackReason: Uncategorized(opt_send_without_block) + v24:BasicObject = Send v9, :foo, v16 # SendFallbackReason: Uncategorized(opt_send_without_block) CheckInterrupts - Return v22 + Return v24 "); } @@ -4077,6 +4079,8 @@ pub(crate) mod hir_build_tests { bb3(v9:BasicObject, v10:BasicObject): v15:ArrayExact = ToNewArray v10 v17:Fixnum[1] = Const Value(1) + v19:CUInt64 = LoadField v15, :_rbasic_flags@0x1001 + v20:CUInt64 = GuardNoBitsSet v19, RUBY_FL_FREEZE=CUInt64(2048) ArrayPush v15, v17 CheckInterrupts Return v15 @@ -4107,6 +4111,8 @@ pub(crate) mod hir_build_tests { v17:Fixnum[1] = Const Value(1) v19:Fixnum[2] = Const Value(2) v21:Fixnum[3] = Const Value(3) + v23:CUInt64 = LoadField v15, :_rbasic_flags@0x1001 + v24:CUInt64 = GuardNoBitsSet v23, RUBY_FL_FREEZE=CUInt64(2048) ArrayPush v15, v17 ArrayPush v15, v19 ArrayPush v15, v21