diff --git a/ext/json/vendor/fast_float_parser.h b/ext/json/vendor/fast_float_parser.h index 9e4a6691360d43..6e43b254c814ec 100644 --- a/ext/json/vendor/fast_float_parser.h +++ b/ext/json/vendor/fast_float_parser.h @@ -720,7 +720,10 @@ static inline double ffp_bits2double(uint64_t bits) { static inline unsigned int ffc_nlz_int64(uint64_t x) { -#if defined(_MSC_VER) && defined(__AVX2__) && defined(HAVE___LZCNT64) +#if defined(HAVE_BUILTIN___BUILTIN_CLZLL) && !(defined(__sun) && defined(__sparc)) + return (unsigned int)__builtin_clzll((unsigned long long)x); + +#elif defined(_MSC_VER) && defined(__AVX2__) && defined(HAVE___LZCNT64) return (unsigned int)__lzcnt64(x); #elif defined(__x86_64__) && defined(__LZCNT__) && defined(HAVE__LZCNT_U64) @@ -730,21 +733,6 @@ ffc_nlz_int64(uint64_t x) unsigned long r; return _BitScanReverse64(&r, x) ? (63u - (unsigned int)r) : 64; -#elif __has_builtin(__builtin_clzl) && __has_builtin(__builtin_clzll) && !(defined(__sun) && defined(__sparc)) - if (x == 0) { - return 64; - } - else if (sizeof(long) * CHAR_BIT == 64) { - return (unsigned int)__builtin_clzl((unsigned long)x); - } - else if (sizeof(long long) * CHAR_BIT == 64) { - return (unsigned int)__builtin_clzll((unsigned long long)x); - } - else { - /* :FIXME: Is there a way to make this branch a compile-time error? */ - __builtin_unreachable(); - } - #else uint64_t y; unsigned int n = 64; diff --git a/gc.c b/gc.c index b385ed31332916..7407fa66420a30 100644 --- a/gc.c +++ b/gc.c @@ -671,7 +671,7 @@ typedef struct gc_function_map { const char *(*active_gc_name)(void); // Miscellaneous struct rb_gc_object_metadata_entry *(*object_metadata)(void *objspace_ptr, VALUE obj); - bool (*pointer_to_heap_p)(void *objspace_ptr, const void *ptr); + bool (*live_object_p)(void *objspace_ptr, const void *ptr); bool (*garbage_object_p)(void *objspace_ptr, VALUE obj); void (*set_event_hook)(void *objspace_ptr, const rb_event_flag_t event); void (*copy_attributes)(void *objspace_ptr, VALUE dest, VALUE obj); @@ -850,7 +850,7 @@ ruby_modular_gc_init(void) load_modular_gc_func(active_gc_name); // Miscellaneous load_modular_gc_func(object_metadata); - load_modular_gc_func(pointer_to_heap_p); + load_modular_gc_func(live_object_p); load_modular_gc_func(garbage_object_p); load_modular_gc_func(set_event_hook); load_modular_gc_func(copy_attributes); @@ -938,7 +938,7 @@ ruby_modular_gc_init(void) # define rb_gc_impl_active_gc_name rb_gc_functions.active_gc_name // Miscellaneous # define rb_gc_impl_object_metadata rb_gc_functions.object_metadata -# define rb_gc_impl_pointer_to_heap_p rb_gc_functions.pointer_to_heap_p +# define rb_gc_impl_live_object_p rb_gc_functions.live_object_p # define rb_gc_impl_garbage_object_p rb_gc_functions.garbage_object_p # define rb_gc_impl_set_event_hook rb_gc_functions.set_event_hook # define rb_gc_impl_copy_attributes rb_gc_functions.copy_attributes @@ -2038,12 +2038,6 @@ rb_objspace_garbage_object_p(VALUE obj) return !SPECIAL_CONST_P(obj) && rb_gc_impl_garbage_object_p(rb_gc_get_objspace(), obj); } -bool -rb_gc_pointer_to_heap_p(VALUE obj) -{ - return rb_gc_impl_pointer_to_heap_p(rb_gc_get_objspace(), (void *)obj); -} - #define OBJ_ID_INCREMENT (RUBY_IMMEDIATE_MASK + 1) #define LAST_OBJECT_ID() (object_id_counter * OBJ_ID_INCREMENT) static VALUE id2ref_value = 0; @@ -2892,7 +2886,7 @@ ruby_stack_check(void) (func)(objspace, (obj_or_ptr)); \ } \ else if (check_obj ? \ - rb_gc_impl_pointer_to_heap_p(objspace, (const void *)obj) && \ + rb_gc_impl_live_object_p(objspace, (const void *)obj) && \ !rb_gc_impl_garbage_object_p(objspace, obj) : \ true) { \ GC_ASSERT(!rb_gc_impl_during_gc_p(objspace)); \ @@ -3139,8 +3133,6 @@ gc_location_internal(void *objspace, VALUE value) return value; } - GC_ASSERT(rb_gc_impl_pointer_to_heap_p(objspace, (void *)value)); - return rb_gc_impl_location(objspace, value); } @@ -3593,7 +3585,7 @@ rb_gc_mark_children(void *objspace, VALUE obj) if (BUILTIN_TYPE(obj) == T_ZOMBIE) rb_bug("rb_gc_mark(): %p is T_ZOMBIE", (void *)obj); rb_bug("rb_gc_mark(): unknown data type 0x%x(%p) %s", BUILTIN_TYPE(obj), (void *)obj, - rb_gc_impl_pointer_to_heap_p(objspace, (void *)obj) ? "corrupted object" : "non object"); + rb_gc_impl_live_object_p(objspace, (void *)obj) ? "corrupted object" : "non object"); } } @@ -3722,11 +3714,11 @@ rb_gc_ractor_cache_free(void *cache) void rb_gc_register_mark_object(VALUE obj) { - /* rb_gc_impl_pointer_to_heap_p() walks objspace->heap_pages.sorted, which + /* rb_gc_impl_live_object_p() walks objspace->heap_pages.sorted, which * another ractor may mutate while allocating heap pages under the VM lock, * so the lookup must be done under the VM lock as well. */ RB_VM_LOCKING() { - if (rb_gc_impl_pointer_to_heap_p(rb_gc_get_objspace(), (void *)obj)) { + if (rb_gc_impl_live_object_p(rb_gc_get_objspace(), (void *)obj)) { rb_vm_register_global_object(obj); } } @@ -4958,7 +4950,7 @@ rb_raw_obj_info_common(char *const buff, const size_t buff_size, const VALUE obj else { // const int age = RVALUE_AGE_GET(obj); - if (rb_gc_impl_pointer_to_heap_p(rb_gc_get_objspace(), (void *)obj)) { + if (rb_gc_impl_live_object_p(rb_gc_get_objspace(), (void *)obj)) { APPEND_F("%p %s/", (void *)obj, obj_type_name(obj)); // TODO: fixme // APPEND_F("%p [%d%s%s%s%s%s%s] %s ", @@ -5238,7 +5230,7 @@ rb_raw_obj_info(char *const buff, const size_t buff_size, VALUE obj) if (SPECIAL_CONST_P(obj)) { raw_obj_info(buff, buff_size, obj); } - else if (!rb_gc_impl_pointer_to_heap_p(objspace, (const void *)obj)) { + else if (!rb_gc_impl_live_object_p(objspace, (const void *)obj)) { snprintf(buff, buff_size, "out-of-heap:%p", (void *)obj); } #if 0 // maybe no need to check it? diff --git a/gc/default/default.c b/gc/default/default.c index 66b4c593eac06a..18d7a05b336430 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -1750,21 +1750,16 @@ rb_gc_impl_garbage_object_p(void *objspace_ptr, VALUE ptr) { rb_objspace_t *objspace = objspace_ptr; - bool dead = false; - + /* Asking whether a freed (T_NONE), moved (T_MOVED), or finalized (T_ZOMBIE) + * object is garbage gives an unreliable answer: the slot may since have been + * reused for an unrelated object. A reference to one of these is stale and a + * bug in the caller. */ asan_unpoisoning_object(ptr) { - switch (BUILTIN_TYPE(ptr)) { - case T_NONE: - case T_MOVED: - case T_ZOMBIE: - dead = true; - break; - default: - break; - } + GC_ASSERT(BUILTIN_TYPE(ptr) != T_NONE); + GC_ASSERT(BUILTIN_TYPE(ptr) != T_MOVED); + GC_ASSERT(BUILTIN_TYPE(ptr) != T_ZOMBIE); } - if (dead) return true; return is_lazy_sweeping(objspace) && GET_HEAP_PAGE(ptr)->flags.before_sweep && !RVALUE_MARKED(objspace, ptr); } @@ -2802,9 +2797,29 @@ is_pointer_to_heap(rb_objspace_t *objspace, const void *ptr) } bool -rb_gc_impl_pointer_to_heap_p(void *objspace_ptr, const void *ptr) +rb_gc_impl_live_object_p(void *objspace_ptr, const void *ptr) { - return is_pointer_to_heap(objspace_ptr, ptr); + rb_objspace_t *objspace = objspace_ptr; + + /* Whether ptr refers to a live object. is_pointer_to_heap is the + * address-only check; T_NONE, T_MOVED, and T_ZOMBIE slots are valid heap + * addresses but not live objects. */ + if (!is_pointer_to_heap(objspace, ptr)) return false; + + VALUE obj = (VALUE)ptr; + bool live = false; + asan_unpoisoning_object(obj) { + switch (BUILTIN_TYPE(obj)) { + case T_NONE: + case T_MOVED: + case T_ZOMBIE: + break; + default: + live = true; + break; + } + } + return live; } #define ZOMBIE_OBJ_KEPT_FLAGS (FL_FINALIZE) @@ -4369,6 +4384,8 @@ rb_gc_impl_location(void *objspace_ptr, VALUE value) { VALUE destination; + GC_ASSERT(is_pointer_to_heap(objspace_ptr, (void *)value)); + asan_unpoisoning_object(value) { if (BUILTIN_TYPE(value) == T_MOVED) { destination = (VALUE)RMOVED(value)->destination; @@ -5347,6 +5364,22 @@ check_children_i(const VALUE child, void *ptr) } } +/* Whether a heap slot currently holds a live object. Returns false for empty + * (T_NONE), moved (T_MOVED), and zombie (T_ZOMBIE) slots, and for garbage + * objects about to be swept. */ +static bool +gc_slot_live_object_p(rb_objspace_t *objspace, VALUE obj) +{ + switch (BUILTIN_TYPE(obj)) { + case T_NONE: + case T_MOVED: + case T_ZOMBIE: + return false; + default: + return !rb_gc_impl_garbage_object_p(objspace, obj); + } +} + static int verify_internal_consistency_i(void *page_start, void *page_end, size_t stride, struct verify_internal_consistency_struct *data) @@ -5356,7 +5389,7 @@ verify_internal_consistency_i(void *page_start, void *page_end, size_t stride, for (obj = (VALUE)page_start; obj != (VALUE)page_end; obj += stride) { asan_unpoisoning_object(obj) { - if (!rb_gc_impl_garbage_object_p(objspace, obj)) { + if (gc_slot_live_object_p(objspace, obj)) { /* count objects */ data->live_object_count++; data->parent = obj; diff --git a/gc/gc_impl.h b/gc/gc_impl.h index d9e44cc66d89e6..b8417fd03a1bba 100644 --- a/gc/gc_impl.h +++ b/gc/gc_impl.h @@ -117,7 +117,7 @@ GC_IMPL_FN VALUE rb_gc_impl_stat_heap(void *objspace_ptr, VALUE heap_name, VALUE GC_IMPL_FN const char *rb_gc_impl_active_gc_name(void); // Miscellaneous GC_IMPL_FN struct rb_gc_object_metadata_entry *rb_gc_impl_object_metadata(void *objspace_ptr, VALUE obj); -GC_IMPL_FN bool rb_gc_impl_pointer_to_heap_p(void *objspace_ptr, const void *ptr); +GC_IMPL_FN bool rb_gc_impl_live_object_p(void *objspace_ptr, const void *ptr); GC_IMPL_FN bool rb_gc_impl_garbage_object_p(void *objspace_ptr, VALUE obj); GC_IMPL_FN void rb_gc_impl_set_event_hook(void *objspace_ptr, const rb_event_flag_t event); GC_IMPL_FN void rb_gc_impl_copy_attributes(void *objspace_ptr, VALUE dest, VALUE obj); diff --git a/gc/mmtk/mmtk.c b/gc/mmtk/mmtk.c index 8be69b4fe65dc8..a725432c6e7c3a 100644 --- a/gc/mmtk/mmtk.c +++ b/gc/mmtk/mmtk.c @@ -1034,7 +1034,7 @@ rb_gc_impl_mark_and_pin(void *objspace_ptr, VALUE obj) void rb_gc_impl_mark_maybe(void *objspace_ptr, VALUE obj) { - if (rb_gc_impl_pointer_to_heap_p(objspace_ptr, (const void *)obj)) { + if (rb_gc_impl_live_object_p(objspace_ptr, (const void *)obj)) { rb_gc_impl_mark_and_pin(objspace_ptr, obj); } } @@ -1080,12 +1080,12 @@ rb_gc_impl_writebarrier(void *objspace_ptr, VALUE a, VALUE b) if (SPECIAL_CONST_P(b)) return; #ifdef MMTK_DEBUG - if (!rb_gc_impl_pointer_to_heap_p(objspace_ptr, (void *)a)) { + if (!rb_gc_impl_live_object_p(objspace_ptr, (void *)a)) { char buff[256]; rb_bug("a: %s is not an object", rb_raw_obj_info(buff, 256, a)); } - if (!rb_gc_impl_pointer_to_heap_p(objspace_ptr, (void *)b)) { + if (!rb_gc_impl_live_object_p(objspace_ptr, (void *)b)) { char buff[256]; rb_bug("b: %s is not an object", rb_raw_obj_info(buff, 256, b)); } @@ -1624,7 +1624,7 @@ rb_gc_impl_object_metadata(void *objspace_ptr, VALUE obj) } bool -rb_gc_impl_pointer_to_heap_p(void *objspace_ptr, const void *ptr) +rb_gc_impl_live_object_p(void *objspace_ptr, const void *ptr) { if (ptr == NULL) return false; if ((uintptr_t)ptr % sizeof(void*) != 0) return false; diff --git a/gc/wbcheck/wbcheck.c b/gc/wbcheck/wbcheck.c index 9a669daf8e9485..43936ef1953543 100644 --- a/gc/wbcheck/wbcheck.c +++ b/gc/wbcheck/wbcheck.c @@ -1215,7 +1215,7 @@ rb_gc_impl_mark_maybe(void *objspace_ptr, VALUE obj) { rb_wbcheck_objspace_t *objspace = objspace_ptr; - if (!rb_gc_impl_pointer_to_heap_p(objspace_ptr, (void *)obj)) return; + if (!rb_gc_impl_live_object_p(objspace_ptr, (void *)obj)) return; switch (objspace->phase) { case WBCHECK_PHASE_SNAPSHOT: @@ -1901,7 +1901,7 @@ rb_gc_impl_object_metadata(void *objspace_ptr, VALUE obj) } bool -rb_gc_impl_pointer_to_heap_p(void *objspace_ptr, const void *ptr) +rb_gc_impl_live_object_p(void *objspace_ptr, const void *ptr) { GC_ASSERT(wbcheck_global_objspace); diff --git a/imemo.c b/imemo.c index 796e078c89565f..9154f6bc6ac280 100644 --- a/imemo.c +++ b/imemo.c @@ -57,6 +57,7 @@ rb_imemo_tmpbuf_new(void) rb_gc_register_pinning_obj((VALUE)obj); + obj->marked = false; obj->ptr = NULL; obj->size = 0; @@ -64,7 +65,7 @@ rb_imemo_tmpbuf_new(void) } void * -rb_alloc_tmp_buffer(volatile VALUE *store, long len) +rb_alloc_tmp_buffer(volatile VALUE *store, long len, bool marked) { if (len < 0) { rb_raise(rb_eArgError, "negative buffer size (or size too big)"); @@ -75,18 +76,13 @@ rb_alloc_tmp_buffer(volatile VALUE *store, long len) rb_imemo_tmpbuf_t *tmpbuf = (rb_imemo_tmpbuf_t *)rb_imemo_tmpbuf_new(); *store = (VALUE)tmpbuf; void *ptr = ruby_xmalloc(len); + tmpbuf->marked = marked; tmpbuf->ptr = ptr; tmpbuf->size = len; return ptr; } -void * -rb_alloc_tmp_buffer_with_count(volatile VALUE *store, size_t size, size_t cnt) -{ - return rb_alloc_tmp_buffer(store, (long)size); -} - void rb_free_tmp_buffer(volatile VALUE *store) { @@ -336,12 +332,6 @@ rb_imemo_memsize(VALUE obj) * mark * ========================================================================= */ -static bool -moved_or_living_object_strictly_p(VALUE obj) -{ - return !SPECIAL_CONST_P(obj) && (!rb_objspace_garbage_object_p(obj) || BUILTIN_TYPE(obj) == T_MOVED); -} - static void mark_and_move_method_entry(rb_method_entry_t *ment, bool reference_updating) { @@ -424,17 +414,12 @@ rb_imemo_mark_and_move(VALUE obj, bool reference_updating) */ } else if (reference_updating) { - if (moved_or_living_object_strictly_p((VALUE)cc->cme_)) { - *((VALUE *)&cc->klass) = rb_gc_location(cc->klass); - *((struct rb_callable_method_entry_struct **)&cc->cme_) = - (struct rb_callable_method_entry_struct *)rb_gc_location((VALUE)cc->cme_); + *((VALUE *)&cc->klass) = rb_gc_location(cc->klass); + *((struct rb_callable_method_entry_struct **)&cc->cme_) = + (struct rb_callable_method_entry_struct *)rb_gc_location((VALUE)cc->cme_); - RUBY_ASSERT(RB_TYPE_P(cc->klass, T_CLASS) || RB_TYPE_P(cc->klass, T_ICLASS)); - RUBY_ASSERT(IMEMO_TYPE_P((VALUE)cc->cme_, imemo_ment)); - } - else { - vm_cc_invalidate(cc); - } + RUBY_ASSERT(RB_TYPE_P(cc->klass, T_CLASS) || RB_TYPE_P(cc->klass, T_ICLASS)); + RUBY_ASSERT(IMEMO_TYPE_P((VALUE)cc->cme_, imemo_ment)); } else { RUBY_ASSERT(RB_TYPE_P(cc->klass, T_CLASS) || RB_TYPE_P(cc->klass, T_ICLASS)); @@ -556,7 +541,7 @@ rb_imemo_mark_and_move(VALUE obj, bool reference_updating) case imemo_tmpbuf: { const rb_imemo_tmpbuf_t *m = (const rb_imemo_tmpbuf_t *)obj; - if (!reference_updating) { + if (m->marked && !reference_updating) { rb_gc_mark_locations(m->ptr, m->ptr + (m->size / sizeof(VALUE))); } diff --git a/include/ruby/internal/memory.h b/include/ruby/internal/memory.h index eb9c1d252580f7..2ebb92f051736c 100644 --- a/include/ruby/internal/memory.h +++ b/include/ruby/internal/memory.h @@ -304,7 +304,7 @@ typedef uint128_t DSIZE_T; #define RB_ALLOCV(v, n) \ ((n) < RUBY_ALLOCV_LIMIT ? \ ((v) = 0, alloca(n)) : \ - rb_alloc_tmp_buffer(&(v), (n))) + rb_alloc_tmp_buffer(&(v), (n), true)) /** * Allocates a memory region, possibly on stack. If the given size exceeds @@ -438,30 +438,7 @@ RBIMPL_ATTR_NONNULL(()) * @return Allocated `len` bytes array. * @post `store` holds the corresponding tmp buffer object. */ -void *rb_alloc_tmp_buffer(volatile VALUE *store, long len); - -RBIMPL_ATTR_RESTRICT() -RBIMPL_ATTR_RETURNS_NONNULL() -RBIMPL_ATTR_ALLOC_SIZE((2,3)) -RBIMPL_ATTR_NONNULL(()) -/** - * @private - * - * This is an implementation detail of #RB_ALLOCV_N(). People don't use this - * directly. - * - * @param[out] store Pointer to a variable. - * @param[in] len Requested number of bytes to allocate. - * @param[in] count Number of elements in an array. - * @return Allocated `len` bytes array. - * @post `store` holds the corresponding tmp buffer object. - * - * @internal - * - * Although the meaning of `count` variable is clear, @shyouhei doesn't - * understand its needs. - */ -void *rb_alloc_tmp_buffer_with_count(volatile VALUE *store, size_t len,size_t count); +void *rb_alloc_tmp_buffer(volatile VALUE *store, long len, bool mark); /** * @private @@ -741,7 +718,7 @@ static inline void * rb_alloc_tmp_buffer2(volatile VALUE *store, long count, size_t elsize) { const size_t total_size = rbimpl_size_mul_or_raise(RBIMPL_CAST((size_t)count), elsize); - return rb_alloc_tmp_buffer(store, (long)total_size); + return rb_alloc_tmp_buffer(store, (long)total_size, elsize >= sizeof(VALUE)); } RBIMPL_SYMBOL_EXPORT_BEGIN() diff --git a/internal/gc.h b/internal/gc.h index e21fb892676f61..8e85381f090f35 100644 --- a/internal/gc.h +++ b/internal/gc.h @@ -229,7 +229,6 @@ void rb_objspace_reachable_objects_from(VALUE obj, void (func)(VALUE, void *), v void rb_objspace_reachable_objects_from_root(void (func)(const char *category, VALUE, void *), void *data); int rb_objspace_internal_object_p(VALUE obj); int rb_objspace_garbage_object_p(VALUE obj); -bool rb_gc_pointer_to_heap_p(VALUE obj); void rb_gc_declare_weak_references(VALUE obj); bool rb_gc_handle_weak_references_alive_p(VALUE obj); diff --git a/internal/imemo.h b/internal/imemo.h index e8a5f0fc8eb66e..7562794506f3d6 100644 --- a/internal/imemo.h +++ b/internal/imemo.h @@ -97,6 +97,7 @@ struct rb_imemo_tmpbuf_struct { VALUE flags; VALUE *ptr; /* malloc'ed buffer */ size_t size; /* buffer size in bytes */ + bool marked; /* whether the buffer may contain object references */ }; struct rb_imemo_cdhash { @@ -142,7 +143,6 @@ struct MEMO { typedef struct rb_imemo_tmpbuf_struct rb_imemo_tmpbuf_t; #endif VALUE rb_imemo_new(enum imemo_type type, VALUE v0, size_t size, bool is_shareable); -VALUE rb_imemo_tmpbuf_new(void); struct MEMO *rb_imemo_memo_new(VALUE a, VALUE b, long c); struct MEMO *rb_imemo_memo_new_value(VALUE a, VALUE b, VALUE c); struct vm_ifunc *rb_vm_ifunc_new(rb_block_call_func_t func, const void *data, int min_argc, int max_argc); @@ -212,7 +212,7 @@ rb_imemo_tmpbuf_new_from_an_RString(VALUE str) StringValue(str); len = RSTRING_LEN(str); - rb_alloc_tmp_buffer(&imemo, len); + rb_alloc_tmp_buffer(&imemo, len, false); memcpy(RB_IMEMO_TMPBUF_PTR(imemo), RSTRING_PTR(str), len); return imemo; } diff --git a/process.c b/process.c index e443ccbe49af13..ac44167c05f2ca 100644 --- a/process.c +++ b/process.c @@ -2726,7 +2726,7 @@ open_func(void *ptr) static void rb_execarg_allocate_dup2_tmpbuf(struct rb_execarg *eargp, long len) { - rb_alloc_tmp_buffer(&eargp->dup2_tmpbuf, run_exec_dup2_tmpbuf_size(len)); + rb_alloc_tmp_buffer(&eargp->dup2_tmpbuf, run_exec_dup2_tmpbuf_size(len), false); } static VALUE diff --git a/vm_method.c b/vm_method.c index e2648c0ee9e627..d8a42621c8f448 100644 --- a/vm_method.c +++ b/vm_method.c @@ -850,7 +850,6 @@ rb_clear_all_refinement_method_cache(void) // All objects should be live as weak references are pruned in // cc_refinement_set_handle_weak_references - VM_ASSERT(rb_gc_pointer_to_heap_p(v)); VM_ASSERT(!rb_objspace_garbage_object_p(v)); const struct rb_callcache *cc = (const struct rb_callcache *)v; diff --git a/zjit/src/codegen_tests.rs b/zjit/src/codegen_tests.rs index 99d5f905166c9e..6dc607e399de31 100644 --- a/zjit/src/codegen_tests.rs +++ b/zjit/src/codegen_tests.rs @@ -2102,6 +2102,38 @@ fn test_opt_mult_overflow() { "), @"[6, -6, 9671406556917033397649408, -9671406556917033397649408, 21267647932558653966460912964485513216]"); } +#[test] +fn test_opt_plus_overflow() { + assert_snapshot!(inspect(" + def test(a, b) + a + b + end + test(1, 2) # profile opt_plus + + r1 = test(2, 3) + r2 = test(4611686018427387903, 1) # FIXNUM_MAX + 1 overflows + r3 = test(-4611686018427387904, -1) # FIXNUM_MIN - 1 overflows + + [r1, r2, r3] + "), @"[5, 4611686018427387904, -4611686018427387905]"); +} + +#[test] +fn test_opt_minus_overflow() { + assert_snapshot!(inspect(" + def test(a, b) + a - b + end + test(6, 4) # profile opt_minus + + r1 = test(6, 4) + r2 = test(4611686018427387903, -1) # FIXNUM_MAX - (-1) overflows + r3 = test(-4611686018427387904, 1) # FIXNUM_MIN - 1 overflows + + [r1, r2, r3] + "), @"[2, 4611686018427387904, -4611686018427387905]"); +} + #[test] fn test_opt_eq() { eval("