diff options
87 files changed, 1725 insertions, 885 deletions
diff --git a/.github/actions/compilers/entrypoint.sh b/.github/actions/compilers/entrypoint.sh index ad9fa87a11..16c3f9f21d 100755 --- a/.github/actions/compilers/entrypoint.sh +++ b/.github/actions/compilers/entrypoint.sh @@ -123,7 +123,7 @@ setup_launchable() { --flavor cppflags="${INPUT_CPPFLAGS}" \ --test-suite test-spec \ > "${builddir}"/${test_spec_session_file} \ - spec_opts+=--launchable-test-reports="${test_spec_report_path}" || : + && spec_opts+=--launchable-test-reports="${test_spec_report_path}" || : fi } launchable_record_test() { @@ -143,7 +143,7 @@ if [ "$LAUNCHABLE_ENABLED" = "true" ]; then btest_session_file='launchable_btest_session.txt' test_spec_session_file='launchable_test_spec_session.txt' setup_pid=$$ - (sleep 180; echo "setup_launchable timed out; killing"; kill -INT "$setup_pid" 2> /dev/null) & sleep_pid=$! + (sleep 180; echo "setup_launchable timed out; killing"; kill -INT "-$setup_pid" 2> /dev/null) & sleep_pid=$! launchable_failed=false trap "launchable_failed=true" INT setup_launchable diff --git a/.github/actions/launchable/setup/action.yml b/.github/actions/launchable/setup/action.yml index 09a70516ae..07990a885b 100644 --- a/.github/actions/launchable/setup/action.yml +++ b/.github/actions/launchable/setup/action.yml @@ -244,7 +244,7 @@ runs: post: | rm -f "${test_all_session_file}" rm -f "${btest_session_file}" - rm -f "${test_spec_session_file} + rm -f "${test_spec_session_file}" if: always() && steps.setup-launchable.outcome == 'success' env: test_all_session_file: ${{ steps.global.outputs.test_all_session_file }} diff --git a/.github/workflows/check_misc.yml b/.github/workflows/check_misc.yml index 543c54a3c9..2d73e1771a 100644 --- a/.github/workflows/check_misc.yml +++ b/.github/workflows/check_misc.yml @@ -37,10 +37,10 @@ jobs: EMAIL: [email protected] GIT_AUTHOR_NAME: git GIT_COMMITTER_NAME: git - GITHUB_OLD_SHA: ${{ startsWith(github.event_name, 'pull') && github.event.pull_request.base.sha || github.event.before }} - GITHUB_NEW_SHA: ${{ startsWith(github.event_name, 'pull') && github.event.pull_request.merge_commit_sha || github.event.after }} + GITHUB_OLD_SHA: ${{ github.event.pull_request.base.sha }} + GITHUB_NEW_SHA: ${{ github.event.pull_request.merge_commit_sha }} PUSH_REF: ${{ github.ref == 'refs/heads/master' && github.ref || '' }} - if: ${{ github.repository == 'ruby/ruby' }} + if: ${{ github.repository == 'ruby/ruby' && startsWith(github.event_name, 'pull') }} - name: Check if C-sources are US-ASCII run: | diff --git a/.github/workflows/dependabot_automerge.yml b/.github/workflows/dependabot_automerge.yml index b1293deb62..dd1f1bcdaa 100644 --- a/.github/workflows/dependabot_automerge.yml +++ b/.github/workflows/dependabot_automerge.yml @@ -19,7 +19,7 @@ jobs: - name: Wait for status checks uses: lewagon/wait-on-check-action@ccfb013c15c8afb7bf2b7c028fb74dc5a068cccc # v1.3.4 with: - repo-token: ${{ secrets.MATZBOT_GITHUB_TOKEN }} + repo-token: ${{ secrets.GITHUB_TOKEN }} ref: ${{ github.event.pull_request.head.sha || github.sha }} check-regexp: 'make \(check, .*\)' wait-interval: 30 @@ -165,7 +165,7 @@ The following bundled gems are updated. ## Compatibility issues -* The following methdos were removed from Ractor due because of `Ractor::Port`: +* The following methods were removed from Ractor due because of `Ractor::Port`: * `Ractor.yield` * `Ractor#take` @@ -812,6 +812,16 @@ node_locations(VALUE ast_value, const NODE *node) location_new(&RNODE_CLASS(node)->class_keyword_loc), location_new(&RNODE_CLASS(node)->inheritance_operator_loc), location_new(&RNODE_CLASS(node)->end_keyword_loc)); + case NODE_COLON2: + return rb_ary_new_from_args(3, + location_new(nd_code_loc(node)), + location_new(&RNODE_COLON2(node)->delimiter_loc), + location_new(&RNODE_COLON2(node)->name_loc)); + case NODE_COLON3: + return rb_ary_new_from_args(3, + location_new(nd_code_loc(node)), + location_new(&RNODE_COLON3(node)->delimiter_loc), + location_new(&RNODE_COLON3(node)->name_loc)); case NODE_DOT2: return rb_ary_new_from_args(2, location_new(nd_code_loc(node)), diff --git a/benchmark/README.md b/benchmark/README.md index c5c29d0daf..9f9192685e 100644 --- a/benchmark/README.md +++ b/benchmark/README.md @@ -40,7 +40,7 @@ Usage: benchmark-driver [options] RUBY|YAML... --filter REGEXP Filter out benchmarks with given regexp --run-duration SECONDS Warmup estimates loop_count to run for this duration (default: 3) --timeout SECONDS Timeout ruby command execution with timeout(1) - -v, --verbose Verbose mode. Multiple -v options increase visilibity (max: 2) + -v, --verbose Verbose mode. Multiple -v options increase visibility (max: 2) ``` ## make benchmark diff --git a/bootstraptest/test_yjit.rb b/bootstraptest/test_yjit.rb index 8d02998254..d480369c75 100644 --- a/bootstraptest/test_yjit.rb +++ b/bootstraptest/test_yjit.rb @@ -220,7 +220,7 @@ assert_equal 'Sub', %q{ call(Sub.new('o')).class } -# String#dup with FL_EXIVAR +# String#dup with generic ivars assert_equal '["str", "ivar"]', %q{ def str_dup(str) = str.dup str = "str" @@ -298,7 +298,7 @@ rb_class_duplicate_classext(rb_classext_t *orig, VALUE klass, const rb_namespace RCLASSEXT_M_TBL(ext) = duplicate_classext_m_tbl(RCLASSEXT_M_TBL(orig), klass, dup_iclass); if (orig->fields_obj) { - RB_OBJ_WRITE(klass, &ext->fields_obj, rb_imemo_class_fields_clone(orig->fields_obj)); + RB_OBJ_WRITE(klass, &ext->fields_obj, rb_imemo_fields_clone(orig->fields_obj)); } if (RCLASSEXT_SHARED_CONST_TBL(orig)) { @@ -13384,7 +13384,8 @@ outer_variable_cmp(const void *a, const void *b, void *arg) if (!ap->name) { return -1; - } else if (!bp->name) { + } + else if (!bp->name) { return 1; } diff --git a/debug_counter.h b/debug_counter.h index 3142ada0c3..fada7513aa 100644 --- a/debug_counter.h +++ b/debug_counter.h @@ -315,7 +315,7 @@ RB_DEBUG_COUNTER(obj_imemo_parser_strterm) RB_DEBUG_COUNTER(obj_imemo_callinfo) RB_DEBUG_COUNTER(obj_imemo_callcache) RB_DEBUG_COUNTER(obj_imemo_constcache) -RB_DEBUG_COUNTER(obj_imemo_class_fields) +RB_DEBUG_COUNTER(obj_imemo_fields) RB_DEBUG_COUNTER(opt_new_hit) RB_DEBUG_COUNTER(opt_new_miss) diff --git a/doc/distribution.md b/doc/distribution.md index 5a4d51da6f..164e1b7109 100644 --- a/doc/distribution.md +++ b/doc/distribution.md @@ -8,7 +8,7 @@ This document outlines the expected way to distribute Ruby, with a specific focu The tarball for official releases is created by the release manager. The release manager uploads the tarball to the [Ruby website](https://www.ruby-lang.org/en/downloads/). -Downstream distributors should use the official release tarballs as part of their build process. This ensures that the tarball is created in a consistent way, and that the tarball is crytographically verified. +Downstream distributors should use the official release tarballs as part of their build process. This ensures that the tarball is created in a consistent way, and that the tarball is cryptographically verified. ### Using the nightly tarball for testing @@ -1215,14 +1215,15 @@ tally_up(st_data_t *group, st_data_t *value, st_data_t arg, int existing) RB_OBJ_WRITTEN(hash, Qundef, tally); } *value = (st_data_t)tally; - if (!SPECIAL_CONST_P(*group)) RB_OBJ_WRITTEN(hash, Qundef, *group); return ST_CONTINUE; } static VALUE rb_enum_tally_up(VALUE hash, VALUE group) { - rb_hash_stlike_update(hash, group, tally_up, (st_data_t)hash); + if (!rb_hash_stlike_update(hash, group, tally_up, (st_data_t)hash)) { + RB_OBJ_WRITTEN(hash, Qundef, group); + } return hash; } diff --git a/ext/date/date_core.c b/ext/date/date_core.c index 44dbf4fbcf..dbee067f6b 100644 --- a/ext/date/date_core.c +++ b/ext/date/date_core.c @@ -1599,7 +1599,7 @@ m_ajd(union DateData *x) if (simple_dat_p(x)) { r = m_real_jd(x); - if (FIXNUM_P(r) && FIX2LONG(r) <= (FIXNUM_MAX / 2)) { + if (FIXNUM_P(r) && FIX2LONG(r) <= (FIXNUM_MAX / 2) && FIX2LONG(r) >= (FIXNUM_MIN + 1) / 2) { long ir = FIX2LONG(r); ir = ir * 2 - 1; return rb_rational_new2(LONG2FIX(ir), INT2FIX(2)); @@ -6936,13 +6936,24 @@ d_lite_eql_p(VALUE self, VALUE other) static VALUE d_lite_hash(VALUE self) { - st_index_t v, h[4]; + st_index_t v, h[5]; + VALUE nth; get_d1(self); - h[0] = m_nth(dat); - h[1] = m_jd(dat); - h[2] = m_df(dat); - h[3] = m_sf(dat); + nth = m_nth(dat); + + if (FIXNUM_P(nth)) { + h[0] = 0; + h[1] = (st_index_t)nth; + } else { + h[0] = 1; + h[1] = (st_index_t)FIX2LONG(rb_hash(nth)); + } + + h[2] = m_jd(dat); + h[3] = m_df(dat); + h[4] = m_sf(dat); + v = rb_memhash(h, sizeof(h)); return ST2FIX(v); } diff --git a/ext/json/lib/json/common.rb b/ext/json/lib/json/common.rb index 6393a6df55..486ec62a58 100644 --- a/ext/json/lib/json/common.rb +++ b/ext/json/lib/json/common.rb @@ -268,7 +268,7 @@ module JSON # to string interpolation. # # Note: no validation is performed on the provided string. It is the - # responsability of the caller to ensure the string contains valid JSON. + # responsibility of the caller to ensure the string contains valid JSON. Fragment = Struct.new(:json) do def initialize(json) unless string = String.try_convert(json) diff --git a/ext/objspace/objspace.c b/ext/objspace/objspace.c index 754c998ac6..5e183e78ed 100644 --- a/ext/objspace/objspace.c +++ b/ext/objspace/objspace.c @@ -504,7 +504,7 @@ count_imemo_objects(int argc, VALUE *argv, VALUE self) INIT_IMEMO_TYPE_ID(imemo_callinfo); INIT_IMEMO_TYPE_ID(imemo_callcache); INIT_IMEMO_TYPE_ID(imemo_constcache); - INIT_IMEMO_TYPE_ID(imemo_class_fields); + INIT_IMEMO_TYPE_ID(imemo_fields); #undef INIT_IMEMO_TYPE_ID } diff --git a/ext/objspace/objspace_dump.c b/ext/objspace/objspace_dump.c index 83b434c3a1..80732d0282 100644 --- a/ext/objspace/objspace_dump.c +++ b/ext/objspace/objspace_dump.c @@ -394,9 +394,10 @@ dump_object(VALUE obj, struct dump_config *dc) dc->cur_obj = obj; dc->cur_obj_references = 0; - if (BUILTIN_TYPE(obj) == T_NODE || BUILTIN_TYPE(obj) == T_IMEMO) { + if (BUILTIN_TYPE(obj) == T_NODE || (BUILTIN_TYPE(obj) == T_IMEMO && !IMEMO_TYPE_P(obj, imemo_fields))) { dc->cur_obj_klass = 0; - } else { + } + else { dc->cur_obj_klass = RBASIC_CLASS(obj); } @@ -414,8 +415,8 @@ dump_object(VALUE obj, struct dump_config *dc) dump_append(dc, obj_type(obj)); dump_append(dc, "\""); - if (BUILTIN_TYPE(obj) != T_IMEMO) { - size_t shape_id = rb_obj_shape_id(obj); + if (BUILTIN_TYPE(obj) != T_IMEMO || IMEMO_TYPE_P(obj, imemo_fields)) { + size_t shape_id = rb_obj_shape_id(obj) & SHAPE_ID_OFFSET_MASK; dump_append(dc, ", \"shape_id\":"); dump_append_sizet(dc, shape_id); } diff --git a/ext/socket/lib/socket.rb b/ext/socket/lib/socket.rb index 60dd45bd4f..7c3f6f5b91 100644 --- a/ext/socket/lib/socket.rb +++ b/ext/socket/lib/socket.rb @@ -643,6 +643,7 @@ class Socket < BasicSocket # # [:resolv_timeout] Specifies the timeout in seconds from when the hostname resolution starts. # [:connect_timeout] This method sequentially attempts connecting to all candidate destination addresses.<br>The +connect_timeout+ specifies the timeout in seconds from the start of the connection attempt to the last candidate.<br>By default, all connection attempts continue until the timeout occurs.<br>When +fast_fallback:false+ is explicitly specified,<br>a timeout is set for each connection attempt and any connection attempt that exceeds its timeout will be canceled. + # [:open_timeout] Specifies the timeout in seconds from the start of the method execution.<br>If this timeout is reached while there are still addresses that have not yet been attempted for connection, no further attempts will be made. # [:fast_fallback] Enables the Happy Eyeballs Version 2 algorithm (enabled by default). # # If a block is given, the block is called with the socket. @@ -656,11 +657,16 @@ class Socket < BasicSocket # sock.close_write # puts sock.read # } - def self.tcp(host, port, local_host = nil, local_port = nil, connect_timeout: nil, resolv_timeout: nil, fast_fallback: tcp_fast_fallback, &) # :yield: socket + def self.tcp(host, port, local_host = nil, local_port = nil, connect_timeout: nil, resolv_timeout: nil, open_timeout: nil, fast_fallback: tcp_fast_fallback, &) # :yield: socket + + if open_timeout && (connect_timeout || resolv_timeout) + raise ArgumentError, "Cannot specify open_timeout along with connect_timeout or resolv_timeout" + end + sock = if fast_fallback && !(host && ip_address?(host)) - tcp_with_fast_fallback(host, port, local_host, local_port, connect_timeout:, resolv_timeout:) + tcp_with_fast_fallback(host, port, local_host, local_port, connect_timeout:, resolv_timeout:, open_timeout:) else - tcp_without_fast_fallback(host, port, local_host, local_port, connect_timeout:, resolv_timeout:) + tcp_without_fast_fallback(host, port, local_host, local_port, connect_timeout:, resolv_timeout:, open_timeout:) end if block_given? @@ -674,7 +680,7 @@ class Socket < BasicSocket end end - def self.tcp_with_fast_fallback(host, port, local_host = nil, local_port = nil, connect_timeout: nil, resolv_timeout: nil) + def self.tcp_with_fast_fallback(host, port, local_host = nil, local_port = nil, connect_timeout: nil, resolv_timeout: nil, open_timeout: nil) if local_host || local_port local_addrinfos = Addrinfo.getaddrinfo(local_host, local_port, nil, :STREAM, timeout: resolv_timeout) resolving_family_names = local_addrinfos.map { |lai| ADDRESS_FAMILIES.key(lai.afamily) }.uniq @@ -692,6 +698,7 @@ class Socket < BasicSocket resolution_delay_expires_at = nil connection_attempt_delay_expires_at = nil user_specified_connect_timeout_at = nil + user_specified_open_timeout_at = open_timeout ? now + open_timeout : nil last_error = nil last_error_from_thread = false @@ -784,7 +791,10 @@ class Socket < BasicSocket ends_at = if resolution_store.any_addrinfos? - resolution_delay_expires_at || connection_attempt_delay_expires_at + [(resolution_delay_expires_at || connection_attempt_delay_expires_at), + user_specified_open_timeout_at].compact.min + elsif user_specified_open_timeout_at + user_specified_open_timeout_at else [user_specified_resolv_timeout_at, user_specified_connect_timeout_at].compact.max end @@ -885,6 +895,8 @@ class Socket < BasicSocket end end + raise(Errno::ETIMEDOUT, 'user specified timeout') if expired?(now, user_specified_open_timeout_at) + if resolution_store.empty_addrinfos? if connecting_sockets.empty? && resolution_store.resolved_all_families? if last_error_from_thread @@ -912,7 +924,7 @@ class Socket < BasicSocket end end - def self.tcp_without_fast_fallback(host, port, local_host, local_port, connect_timeout:, resolv_timeout:) + def self.tcp_without_fast_fallback(host, port, local_host, local_port, connect_timeout:, resolv_timeout:, open_timeout:) last_error = nil ret = nil @@ -921,7 +933,10 @@ class Socket < BasicSocket local_addr_list = Addrinfo.getaddrinfo(local_host, local_port, nil, :STREAM, nil) end - Addrinfo.foreach(host, port, nil, :STREAM, timeout: resolv_timeout) {|ai| + timeout = open_timeout ? open_timeout : resolv_timeout + starts_at = current_clock_time + + Addrinfo.foreach(host, port, nil, :STREAM, timeout:) {|ai| if local_addr_list local_addr = local_addr_list.find {|local_ai| local_ai.afamily == ai.afamily } next unless local_addr @@ -929,9 +944,10 @@ class Socket < BasicSocket local_addr = nil end begin + timeout = open_timeout ? open_timeout - (current_clock_time - starts_at) : connect_timeout sock = local_addr ? - ai.connect_from(local_addr, timeout: connect_timeout) : - ai.connect(timeout: connect_timeout) + ai.connect_from(local_addr, timeout:) : + ai.connect(timeout:) rescue SystemCallError last_error = $! next diff --git a/ext/win32/lib/win32/registry.rb b/ext/win32/lib/win32/registry.rb index d0cbb6afcf..8e2c8b2e1a 100644 --- a/ext/win32/lib/win32/registry.rb +++ b/ext/win32/lib/win32/registry.rb @@ -372,7 +372,7 @@ For detail, see the MSDN[http://msdn.microsoft.com/library/en-us/sysinfo/base/pr # Replace %\w+% into the environment value of what is contained between the %'s # This method is used for REG_EXPAND_SZ. # - # For detail, see expandEnvironmentStrings[http://msdn.microsoft.com/library/en-us/sysinfo/base/expandenvironmentstrings.asp] \Win32 \API. + # For detail, see expandEnvironmentStrings[https://learn.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-expandenvironmentstringsa] \Win32 \API. # def self.expand_environ(str) str.gsub(Regexp.compile("%([^%]+)%".encode(str.encoding))) { @@ -487,7 +487,7 @@ For detail, see the MSDN[http://msdn.microsoft.com/library/en-us/sysinfo/base/pr ObjectSpace.define_finalizer self, @@final.call(@hkeyfinal) end - # Win32::Registry object of parent key, or nil if predefeined key. + # Win32::Registry object of parent key, or nil if predefined key. attr_reader :parent # Same as subkey value of Registry.open or # Registry.create method. @@ -571,7 +571,7 @@ For detail, see the MSDN[http://msdn.microsoft.com/library/en-us/sysinfo/base/pr # For each value it yields key, type and data. # # key is a String which contains name of key. - # type is a type contant kind of Win32::Registry::REG_* + # type is a type constant kind of Win32::Registry::REG_* # data is the value of this key. # def each_value @@ -2015,27 +2015,6 @@ object_id_to_ref(void *objspace_ptr, VALUE object_id) static inline void obj_free_object_id(VALUE obj) { - if (RB_BUILTIN_TYPE(obj) == T_IMEMO) { - return; - } - -#if RUBY_DEBUG - switch (BUILTIN_TYPE(obj)) { - case T_CLASS: - case T_MODULE: - break; - default: - if (rb_shape_obj_has_id(obj)) { - VALUE id = object_id_get(obj, RBASIC_SHAPE_ID(obj)); // Crash if missing - if (!(FIXNUM_P(id) || RB_TYPE_P(id, T_BIGNUM))) { - rb_p(obj); - rb_bug("Corrupted object_id"); - } - } - break; - } -#endif - VALUE obj_id = 0; if (RB_UNLIKELY(id2ref_tbl)) { switch (BUILTIN_TYPE(obj)) { @@ -2043,21 +2022,32 @@ obj_free_object_id(VALUE obj) case T_MODULE: obj_id = RCLASS(obj)->object_id; break; - default: { + case T_IMEMO: + if (!IMEMO_TYPE_P(obj, imemo_fields)) { + return; + } + // fallthrough + case T_OBJECT: + { shape_id_t shape_id = RBASIC_SHAPE_ID(obj); if (rb_shape_has_object_id(shape_id)) { obj_id = object_id_get(obj, shape_id); } break; } + default: + // For generic_fields, the T_IMEMO/fields is responsible for freeing the id. + return; } if (RB_UNLIKELY(obj_id)) { RUBY_ASSERT(FIXNUM_P(obj_id) || RB_TYPE_P(obj_id, T_BIGNUM)); if (!st_delete(id2ref_tbl, (st_data_t *)&obj_id, NULL)) { - // If we're currently building the table then it's not a bug - if (id2ref_tbl_built) { + // If we're currently building the table then it's not a bug. + // The the object is a T_IMEMO/fields, then it's possible the actual object + // has been garbage collected already. + if (id2ref_tbl_built && !RB_TYPE_P(obj, T_IMEMO)) { rb_bug("Object ID seen, but not in _id2ref table: object_id=%llu object=%s", NUM2ULL(obj_id), rb_obj_info(obj)); } } @@ -2071,7 +2061,7 @@ rb_gc_obj_free_vm_weak_references(VALUE obj) obj_free_object_id(obj); if (rb_obj_exivar_p(obj)) { - rb_free_generic_ivar((VALUE)obj); + rb_free_generic_ivar(obj); } switch (BUILTIN_TYPE(obj)) { @@ -2316,10 +2306,6 @@ rb_obj_memsize_of(VALUE obj) return 0; } - if (rb_obj_exivar_p(obj)) { - size += rb_generic_ivar_memsize(obj); - } - switch (BUILTIN_TYPE(obj)) { case T_OBJECT: if (rb_shape_obj_too_complex_p(obj)) { @@ -3060,7 +3046,6 @@ rb_gc_mark_roots(void *objspace, const char **categoryp) MARK_CHECKPOINT("vm"); rb_vm_mark(vm); - if (vm->self) gc_mark_internal(vm->self); MARK_CHECKPOINT("end_proc"); rb_mark_end_proc(); @@ -3936,38 +3921,6 @@ vm_weak_table_foreach_update_weak_value(st_data_t *key, st_data_t *value, st_dat return iter_data->update_callback((VALUE *)value, iter_data->data); } -static void -free_gen_fields_tbl(VALUE obj, struct gen_fields_tbl *fields_tbl) -{ - if (UNLIKELY(rb_shape_obj_too_complex_p(obj))) { - st_free_table(fields_tbl->as.complex.table); - } - - xfree(fields_tbl); -} - -static int -vm_weak_table_gen_fields_foreach_too_complex_i(st_data_t _key, st_data_t value, st_data_t data, int error) -{ - struct global_vm_table_foreach_data *iter_data = (struct global_vm_table_foreach_data *)data; - - GC_ASSERT(!iter_data->weak_only); - - if (SPECIAL_CONST_P((VALUE)value)) return ST_CONTINUE; - - return iter_data->callback((VALUE)value, iter_data->data); -} - -static int -vm_weak_table_gen_fields_foreach_too_complex_replace_i(st_data_t *_key, st_data_t *value, st_data_t data, int existing) -{ - struct global_vm_table_foreach_data *iter_data = (struct global_vm_table_foreach_data *)data; - - GC_ASSERT(!iter_data->weak_only); - - return iter_data->update_callback((VALUE *)value, iter_data->data); -} - struct st_table *rb_generic_fields_tbl_get(void); static int @@ -4004,62 +3957,50 @@ vm_weak_table_gen_fields_foreach(st_data_t key, st_data_t value, st_data_t data) int ret = iter_data->callback((VALUE)key, iter_data->data); + VALUE new_value = (VALUE)value; + VALUE new_key = (VALUE)key; + switch (ret) { case ST_CONTINUE: break; case ST_DELETE: - free_gen_fields_tbl((VALUE)key, (struct gen_fields_tbl *)value); - - FL_UNSET((VALUE)key, FL_EXIVAR); RBASIC_SET_SHAPE_ID((VALUE)key, ROOT_SHAPE_ID); return ST_DELETE; case ST_REPLACE: { - VALUE new_key = (VALUE)key; ret = iter_data->update_callback(&new_key, iter_data->data); - if (key != new_key) ret = ST_DELETE; - DURING_GC_COULD_MALLOC_REGION_START(); - { - st_insert(rb_generic_fields_tbl_get(), (st_data_t)new_key, value); + if (key != new_key) { + ret = ST_DELETE; } - DURING_GC_COULD_MALLOC_REGION_END(); - key = (st_data_t)new_key; break; } default: - return ret; + rb_bug("vm_weak_table_gen_fields_foreach: return value %d not supported", ret); } if (!iter_data->weak_only) { - struct gen_fields_tbl *fields_tbl = (struct gen_fields_tbl *)value; + int ivar_ret = iter_data->callback(new_value, iter_data->data); + switch (ivar_ret) { + case ST_CONTINUE: + break; - if (rb_shape_obj_too_complex_p((VALUE)key)) { - st_foreach_with_replace( - fields_tbl->as.complex.table, - vm_weak_table_gen_fields_foreach_too_complex_i, - vm_weak_table_gen_fields_foreach_too_complex_replace_i, - data - ); + case ST_REPLACE: + iter_data->update_callback(&new_value, iter_data->data); + break; + + default: + rb_bug("vm_weak_table_gen_fields_foreach: return value %d not supported", ivar_ret); } - else { - uint32_t fields_count = RSHAPE_LEN(RBASIC_SHAPE_ID((VALUE)key)); - for (uint32_t i = 0; i < fields_count; i++) { - if (SPECIAL_CONST_P(fields_tbl->as.shape.fields[i])) continue; + } - int ivar_ret = iter_data->callback(fields_tbl->as.shape.fields[i], iter_data->data); - switch (ivar_ret) { - case ST_CONTINUE: - break; - case ST_REPLACE: - iter_data->update_callback(&fields_tbl->as.shape.fields[i], iter_data->data); - break; - default: - rb_bug("vm_weak_table_gen_fields_foreach: return value %d not supported", ivar_ret); - } - } + if (key != new_key || value != new_value) { + DURING_GC_COULD_MALLOC_REGION_START(); + { + st_insert(rb_generic_fields_tbl_get(), (st_data_t)new_key, new_value); } + DURING_GC_COULD_MALLOC_REGION_END(); } return ret; @@ -4168,7 +4109,7 @@ rb_gc_vm_weak_table_foreach(vm_table_foreach_callback_func callback, break; } case RB_GC_VM_WEAK_TABLE_COUNT: - rb_bug("Unreacheable"); + rb_bug("Unreachable"); default: rb_bug("rb_gc_vm_weak_table_foreach: unknown table %d", table); } @@ -3872,7 +3872,6 @@ rb_hash_values(VALUE hash) } rb_ary_set_len(values, size); } - else { rb_hash_foreach(hash, values_i, values); } @@ -30,7 +30,7 @@ rb_imemo_name(enum imemo_type type) IMEMO_NAME(svar); IMEMO_NAME(throw_data); IMEMO_NAME(tmpbuf); - IMEMO_NAME(class_fields); + IMEMO_NAME(fields); #undef IMEMO_NAME } rb_bug("unreachable"); @@ -111,16 +111,16 @@ rb_imemo_tmpbuf_parser_heap(void *buf, rb_imemo_tmpbuf_t *old_heap, size_t cnt) } static VALUE -imemo_class_fields_new(VALUE klass, size_t capa) +imemo_fields_new(VALUE klass, size_t capa) { - size_t embedded_size = offsetof(struct rb_class_fields, as.embed) + capa * sizeof(VALUE); + size_t embedded_size = offsetof(struct rb_fields, as.embed) + capa * sizeof(VALUE); if (rb_gc_size_allocatable_p(embedded_size)) { - VALUE fields = rb_imemo_new(imemo_class_fields, klass, embedded_size); - RUBY_ASSERT(IMEMO_TYPE_P(fields, imemo_class_fields)); + VALUE fields = rb_imemo_new(imemo_fields, klass, embedded_size); + RUBY_ASSERT(IMEMO_TYPE_P(fields, imemo_fields)); return fields; } else { - VALUE fields = rb_imemo_new(imemo_class_fields, klass, sizeof(struct rb_class_fields)); + VALUE fields = rb_imemo_new(imemo_fields, klass, sizeof(struct rb_fields)); FL_SET_RAW(fields, OBJ_FIELD_EXTERNAL); IMEMO_OBJ_FIELDS(fields)->as.external.ptr = ALLOC_N(VALUE, capa); return fields; @@ -128,46 +128,90 @@ imemo_class_fields_new(VALUE klass, size_t capa) } VALUE -rb_imemo_class_fields_new(VALUE klass, size_t capa) +rb_imemo_fields_new(VALUE klass, size_t capa) { - return imemo_class_fields_new(rb_singleton_class(klass), capa); + return imemo_fields_new(klass, capa); } static VALUE -imemo_class_fields_new_complex(VALUE klass, size_t capa) +imemo_fields_new_complex(VALUE klass, size_t capa) { - VALUE fields = imemo_class_fields_new(klass, sizeof(struct rb_class_fields)); + VALUE fields = imemo_fields_new(klass, sizeof(struct rb_fields)); IMEMO_OBJ_FIELDS(fields)->as.complex.table = st_init_numtable_with_size(capa); return fields; } VALUE -rb_imemo_class_fields_new_complex(VALUE klass, size_t capa) +rb_imemo_fields_new_complex(VALUE klass, size_t capa) { - return imemo_class_fields_new_complex(rb_singleton_class(klass), capa); + return imemo_fields_new_complex(klass, capa); +} + +static int +imemo_fields_trigger_wb_i(st_data_t key, st_data_t value, st_data_t arg) +{ + VALUE field_obj = (VALUE)arg; + RB_OBJ_WRITTEN(field_obj, Qundef, (VALUE)value); + return ST_CONTINUE; +} + +static int +imemo_fields_complex_wb_i(st_data_t key, st_data_t value, st_data_t arg) +{ + RB_OBJ_WRITTEN((VALUE)arg, Qundef, (VALUE)value); + return ST_CONTINUE; } VALUE -rb_imemo_class_fields_clone(VALUE fields_obj) +rb_imemo_fields_new_complex_tbl(VALUE klass, st_table *tbl) +{ + VALUE fields = imemo_fields_new(klass, sizeof(struct rb_fields)); + IMEMO_OBJ_FIELDS(fields)->as.complex.table = tbl; + st_foreach(tbl, imemo_fields_trigger_wb_i, (st_data_t)fields); + return fields; +} + +VALUE +rb_imemo_fields_clone(VALUE fields_obj) { shape_id_t shape_id = RBASIC_SHAPE_ID(fields_obj); VALUE clone; if (rb_shape_too_complex_p(shape_id)) { - clone = rb_imemo_class_fields_new_complex(CLASS_OF(fields_obj), 0); + clone = rb_imemo_fields_new_complex(CLASS_OF(fields_obj), 0); RBASIC_SET_SHAPE_ID(clone, shape_id); - st_table *src_table = rb_imemo_class_fields_complex_tbl(fields_obj); - st_replace(rb_imemo_class_fields_complex_tbl(clone), src_table); + st_table *src_table = rb_imemo_fields_complex_tbl(fields_obj); + st_table *dest_table = rb_imemo_fields_complex_tbl(clone); + st_replace(dest_table, src_table); + st_foreach(dest_table, imemo_fields_complex_wb_i, (st_data_t)clone); } else { - clone = imemo_class_fields_new(CLASS_OF(fields_obj), RSHAPE_CAPACITY(shape_id)); + clone = imemo_fields_new(CLASS_OF(fields_obj), RSHAPE_CAPACITY(shape_id)); RBASIC_SET_SHAPE_ID(clone, shape_id); - MEMCPY(rb_imemo_class_fields_ptr(clone), rb_imemo_class_fields_ptr(fields_obj), VALUE, RSHAPE_LEN(shape_id)); + VALUE *fields = rb_imemo_fields_ptr(clone); + attr_index_t fields_count = RSHAPE_LEN(shape_id); + MEMCPY(fields, rb_imemo_fields_ptr(fields_obj), VALUE, fields_count); + for (attr_index_t i = 0; i < fields_count; i++) { + RB_OBJ_WRITTEN(clone, Qundef, fields[i]); + } } return clone; } +void +rb_imemo_fields_clear(VALUE fields_obj) +{ + // When replacing an imemo/fields by another one, we must clear + // its shape so that gc.c:obj_free_object_id won't be called. + if (rb_shape_obj_too_complex_p(fields_obj)) { + RBASIC_SET_SHAPE_ID(fields_obj, ROOT_TOO_COMPLEX_SHAPE_ID); + } + else { + RBASIC_SET_SHAPE_ID(fields_obj, ROOT_SHAPE_ID); + } +} + /* ========================================================================= * memsize * ========================================================================= */ @@ -215,7 +259,7 @@ rb_imemo_memsize(VALUE obj) size += ((rb_imemo_tmpbuf_t *)obj)->cnt * sizeof(VALUE); break; - case imemo_class_fields: + case imemo_fields: if (rb_shape_obj_too_complex_p(obj)) { size += st_memsize(IMEMO_OBJ_FIELDS(obj)->as.complex.table); } @@ -487,11 +531,11 @@ rb_imemo_mark_and_move(VALUE obj, bool reference_updating) break; } - case imemo_class_fields: { + case imemo_fields: { rb_gc_mark_and_move((VALUE *)&RBASIC(obj)->klass); if (rb_shape_obj_too_complex_p(obj)) { - st_table *tbl = rb_imemo_class_fields_complex_tbl(obj); + st_table *tbl = rb_imemo_fields_complex_tbl(obj); if (reference_updating) { rb_gc_ref_update_table_values_only(tbl); } @@ -500,7 +544,7 @@ rb_imemo_mark_and_move(VALUE obj, bool reference_updating) } } else { - VALUE *fields = rb_imemo_class_fields_ptr(obj); + VALUE *fields = rb_imemo_fields_ptr(obj); attr_index_t len = RSHAPE_LEN(RBASIC_SHAPE_ID(obj)); for (attr_index_t i = 0; i < len; i++) { rb_gc_mark_and_move(&fields[i]); @@ -602,7 +646,7 @@ rb_cc_tbl_free(struct rb_id_table *cc_tbl, VALUE klass) } static inline void -imemo_class_fields_free(struct rb_class_fields *fields) +imemo_fields_free(struct rb_fields *fields) { if (rb_shape_obj_too_complex_p((VALUE)fields)) { st_free_table(fields->as.complex.table); @@ -686,9 +730,9 @@ rb_imemo_free(VALUE obj) RB_DEBUG_COUNTER_INC(obj_imemo_tmpbuf); break; - case imemo_class_fields: - imemo_class_fields_free(IMEMO_OBJ_FIELDS(obj)); - RB_DEBUG_COUNTER_INC(obj_imemo_class_fields); + case imemo_fields: + imemo_fields_free(IMEMO_OBJ_FIELDS(obj)); + RB_DEBUG_COUNTER_INC(obj_imemo_fields); break; default: rb_bug("unreachable"); diff --git a/include/ruby/internal/fl_type.h b/include/ruby/internal/fl_type.h index 701118ef25..e52ccecedd 100644 --- a/include/ruby/internal/fl_type.h +++ b/include/ruby/internal/fl_type.h @@ -253,6 +253,21 @@ ruby_fl_type { = 0, /** + * @deprecated This flag was an implementation detail that should never have + * no been exposed. Exists here for backwards + * compatibility only. You can safely forget about it. + */ + RUBY_FL_EXIVAR + +#if defined(RBIMPL_HAVE_ENUM_ATTRIBUTE) + RBIMPL_ATTR_DEPRECATED(("FL_EXIVAR is an outdated implementation detail, it shoudl be used.")) +#elif defined(_MSC_VER) +# pragma deprecated(RUBY_FL_EXIVAR) +#endif + + = 0, + + /** * This flag has something to do with Ractor. Multiple Ractors run without * protecting each other. Sharing an object among Ractors are basically * dangerous, disabled by default. This flag is used to bypass that @@ -286,18 +301,12 @@ ruby_fl_type { */ RUBY_FL_UNUSED9 = (1<<9), - /** - * This flag has something to do with instance variables. 3rd parties need - * not know, but there are several ways to store an object's instance - * variables. Objects with this flag use so-called "generic" backend - * storage. This distinction is purely an implementation detail. People - * need not be aware of this working behind-the-scene. - * - * @internal - * - * As of writing everything except ::RObject and RModule use this scheme. - */ - RUBY_FL_EXIVAR = (1<<10), + /** + * This flag is no longer in use + * + * @internal + */ + RUBY_FL_UNUSED10 = (1<<10), /** * This flag has something to do with data immutability. When this flag is @@ -399,7 +408,7 @@ enum { # pragma deprecated(RUBY_FL_DUPPED) #endif - = (int)RUBY_T_MASK | (int)RUBY_FL_EXIVAR + = (int)RUBY_T_MASK }; #undef RBIMPL_HAVE_ENUM_ATTRIBUTE diff --git a/internal/class.h b/internal/class.h index 2250d3f343..f4677ae400 100644 --- a/internal/class.h +++ b/internal/class.h @@ -432,7 +432,7 @@ static inline rb_classext_t * RCLASS_EXT_WRITABLE(VALUE obj) { const rb_namespace_t *ns; - if (RCLASS_PRIME_CLASSEXT_WRITABLE_P(obj)) { + if (LIKELY(RCLASS_PRIME_CLASSEXT_WRITABLE_P(obj))) { return RCLASS_EXT_PRIME(obj); } // delay namespace loading to optimize for unmodified classes @@ -526,7 +526,7 @@ RCLASS_WRITABLE_ENSURE_FIELDS_OBJ(VALUE obj) RUBY_ASSERT(RB_TYPE_P(obj, RUBY_T_CLASS) || RB_TYPE_P(obj, RUBY_T_MODULE)); rb_classext_t *ext = RCLASS_EXT_WRITABLE(obj); if (!ext->fields_obj) { - RB_OBJ_WRITE(obj, &ext->fields_obj, rb_imemo_class_fields_new(obj, 1)); + RB_OBJ_WRITE(obj, &ext->fields_obj, rb_imemo_fields_new(rb_singleton_class(obj), 1)); } return ext->fields_obj; } @@ -564,7 +564,7 @@ RCLASS_FIELDS_COUNT(VALUE obj) VALUE fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj); if (fields_obj) { if (rb_shape_obj_too_complex_p(fields_obj)) { - return (uint32_t)rb_st_table_size(rb_imemo_class_fields_complex_tbl(fields_obj)); + return (uint32_t)rb_st_table_size(rb_imemo_fields_complex_tbl(fields_obj)); } else { return RSHAPE_LEN(RBASIC_SHAPE_ID(fields_obj)); diff --git a/internal/imemo.h b/internal/imemo.h index 0806baa9a6..44b41d1b1c 100644 --- a/internal/imemo.h +++ b/internal/imemo.h @@ -42,7 +42,7 @@ enum imemo_type { imemo_callinfo = 11, imemo_callcache = 12, imemo_constcache = 13, - imemo_class_fields = 14, + imemo_fields = 14, }; /* CREF (Class REFerence) is defined in method.h */ @@ -258,7 +258,7 @@ MEMO_V2_SET(struct MEMO *m, VALUE v) RB_OBJ_WRITE(m, &m->v2, v); } -struct rb_class_fields { +struct rb_fields { struct RBasic basic; union { struct { @@ -276,20 +276,22 @@ struct rb_class_fields { }; #define OBJ_FIELD_EXTERNAL IMEMO_FL_USER0 -#define IMEMO_OBJ_FIELDS(fields) ((struct rb_class_fields *)fields) +#define IMEMO_OBJ_FIELDS(fields) ((struct rb_fields *)fields) -VALUE rb_imemo_class_fields_new(VALUE klass, size_t capa); -VALUE rb_imemo_class_fields_new_complex(VALUE klass, size_t capa); -VALUE rb_imemo_class_fields_clone(VALUE fields_obj); +VALUE rb_imemo_fields_new(VALUE klass, size_t capa); +VALUE rb_imemo_fields_new_complex(VALUE klass, size_t capa); +VALUE rb_imemo_fields_new_complex_tbl(VALUE klass, st_table *tbl); +VALUE rb_imemo_fields_clone(VALUE fields_obj); +void rb_imemo_fields_clear(VALUE fields_obj); static inline VALUE * -rb_imemo_class_fields_ptr(VALUE obj_fields) +rb_imemo_fields_ptr(VALUE obj_fields) { if (!obj_fields) { return NULL; } - RUBY_ASSERT(IMEMO_TYPE_P(obj_fields, imemo_class_fields)); + RUBY_ASSERT(IMEMO_TYPE_P(obj_fields, imemo_fields)); if (RB_UNLIKELY(FL_TEST_RAW(obj_fields, OBJ_FIELD_EXTERNAL))) { return IMEMO_OBJ_FIELDS(obj_fields)->as.external.ptr; @@ -300,13 +302,13 @@ rb_imemo_class_fields_ptr(VALUE obj_fields) } static inline st_table * -rb_imemo_class_fields_complex_tbl(VALUE obj_fields) +rb_imemo_fields_complex_tbl(VALUE obj_fields) { if (!obj_fields) { return NULL; } - RUBY_ASSERT(IMEMO_TYPE_P(obj_fields, imemo_class_fields)); + RUBY_ASSERT(IMEMO_TYPE_P(obj_fields, imemo_fields)); return IMEMO_OBJ_FIELDS(obj_fields)->as.complex.table; } diff --git a/internal/thread.h b/internal/thread.h index 00fcbfc560..928126c3b0 100644 --- a/internal/thread.h +++ b/internal/thread.h @@ -83,6 +83,8 @@ RUBY_SYMBOL_EXPORT_END int rb_threadptr_execute_interrupts(struct rb_thread_struct *th, int blocking_timing); bool rb_thread_mn_schedulable(VALUE thread); +bool rb_thread_resolve_unblock_function(rb_unblock_function_t **unblock_function, void **data2, struct rb_thread_struct *thread); + // interrupt exec typedef VALUE (rb_interrupt_exec_func_t)(void *data); diff --git a/internal/variable.h b/internal/variable.h index 8da6c678a5..92017d6184 100644 --- a/internal/variable.h +++ b/internal/variable.h @@ -18,7 +18,6 @@ /* variable.c */ void rb_gc_mark_global_tbl(void); void rb_gc_update_global_tbl(void); -size_t rb_generic_ivar_memsize(VALUE); VALUE rb_search_class_path(VALUE); VALUE rb_attr_delete(VALUE, ID); void rb_autoload_str(VALUE mod, ID id, VALUE file); @@ -47,8 +46,7 @@ void rb_gvar_namespace_ready(const char *name); */ VALUE rb_mod_set_temporary_name(VALUE, VALUE); -struct gen_fields_tbl; -int rb_gen_fields_tbl_get(VALUE obj, ID id, struct gen_fields_tbl **fields_tbl); +int rb_gen_fields_tbl_get(VALUE obj, ID id, VALUE *fields_obj); void rb_obj_copy_ivs_to_hash_table(VALUE obj, st_table *table); void rb_obj_init_too_complex(VALUE obj, st_table *table); void rb_evict_ivars_to_hash(VALUE obj); diff --git a/io_buffer.c b/io_buffer.c index 40c12ef5c1..96f13c364a 100644 --- a/io_buffer.c +++ b/io_buffer.c @@ -273,6 +273,21 @@ io_buffer_free(struct rb_io_buffer *buffer) } static void +rb_io_buffer_type_mark_and_move(void *_buffer) +{ + struct rb_io_buffer *buffer = _buffer; + if (buffer->source != Qnil) { + if (RB_TYPE_P(buffer->source, T_STRING)) { + // The `source` String has to be pinned, because the `base` may point to the embedded String content, + // which can be otherwise moved by GC compaction. + rb_gc_mark(buffer->source); + } else { + rb_gc_mark_and_move(&buffer->source); + } + } +} + +static void rb_io_buffer_type_free(void *_buffer) { struct rb_io_buffer *buffer = _buffer; @@ -293,20 +308,16 @@ rb_io_buffer_type_size(const void *_buffer) return total; } -RUBY_REFERENCES(io_buffer_refs) = { - RUBY_REF_EDGE(struct rb_io_buffer, source), - RUBY_REF_END -}; - static const rb_data_type_t rb_io_buffer_type = { .wrap_struct_name = "IO::Buffer", .function = { - .dmark = RUBY_REFS_LIST_PTR(io_buffer_refs), + .dmark = rb_io_buffer_type_mark_and_move, .dfree = rb_io_buffer_type_free, .dsize = rb_io_buffer_type_size, + .dcompact = rb_io_buffer_type_mark_and_move, }, .data = NULL, - .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE | RUBY_TYPED_DECL_MARKING, + .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE, }; static inline enum rb_io_buffer_flags @@ -496,7 +507,9 @@ io_buffer_for_yield_instance(VALUE _arguments) arguments->instance = io_buffer_for_make_instance(arguments->klass, arguments->string, arguments->flags); - rb_str_locktmp(arguments->string); + if (!RB_OBJ_FROZEN(arguments->string)) { + rb_str_locktmp(arguments->string); + } return rb_yield(arguments->instance); } @@ -510,7 +523,9 @@ io_buffer_for_yield_instance_ensure(VALUE _arguments) rb_io_buffer_free(arguments->instance); } - rb_str_unlocktmp(arguments->string); + if (!RB_OBJ_FROZEN(arguments->string)) { + rb_str_unlocktmp(arguments->string); + } return Qnil; } diff --git a/lib/rubygems.rb b/lib/rubygems.rb index 1225cbe5cb..fc97f5ff25 100644 --- a/lib/rubygems.rb +++ b/lib/rubygems.rb @@ -1144,7 +1144,7 @@ An Array (#{env.inspect}) was passed in from #{caller[3]} ENV["BUNDLE_GEMFILE"] ||= File.expand_path(path) require_relative "rubygems/user_interaction" - require "bundler" + require_relative "rubygems/bundler_integration" begin Gem::DefaultUserInteraction.use_ui(ui) do Bundler.ui.silence do diff --git a/lib/rubygems/bundler_integration.rb b/lib/rubygems/bundler_integration.rb new file mode 100644 index 0000000000..28228e2398 --- /dev/null +++ b/lib/rubygems/bundler_integration.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require "bundler/version" + +if Bundler::VERSION > "2.6.9" + require "bundler" +else + previous_platforms = {} + + platform_const_list = ["JAVA", "MSWIN", "MSWIN64", "MINGW", "X64_MINGW_LEGACY", "X64_MINGW", "UNIVERSAL_MINGW", "WINDOWS", "X64_LINUX", "X64_LINUX_MUSL"] + + platform_const_list.each do |platform| + previous_platforms[platform] = Gem::Platform.const_get(platform) + Gem::Platform.send(:remove_const, platform) + end + + require "bundler" + + platform_const_list.each do |platform| + Gem::Platform.send(:remove_const, platform) if Gem::Platform.const_defined?(platform) + Gem::Platform.const_set(platform, previous_platforms[platform]) + end +end diff --git a/lib/rubygems/request_set.rb b/lib/rubygems/request_set.rb index 875df7e019..5a855fdb10 100644 --- a/lib/rubygems/request_set.rb +++ b/lib/rubygems/request_set.rb @@ -181,13 +181,10 @@ class Gem::RequestSet # Install requested gems after they have been downloaded sorted_requests.each do |req| - if req.installed? + if req.installed? && @always_install.none? {|spec| spec == req.spec.spec } req.spec.spec.build_extensions - - if @always_install.none? {|spec| spec == req.spec.spec } - yield req, nil if block_given? - next - end + yield req, nil if block_given? + next end spec = diff --git a/lib/tempfile.rb b/lib/tempfile.rb index f3213c5684..7292e72c25 100644 --- a/lib/tempfile.rb +++ b/lib/tempfile.rb @@ -29,7 +29,7 @@ require 'tmpdir' # require 'tempfile' # # # Tempfile.create with a block -# # The filename are choosen automatically. +# # The filename are chosen automatically. # # (You can specify the prefix and suffix of the filename by an optional argument.) # Tempfile.create {|f| # f.puts "foo" @@ -145,12 +145,14 @@ rb_marshal_define_compat(VALUE newclass, VALUE oldclass, VALUE (*dumper)(VALUE), compat_allocator_table(); compat = ALLOC(marshal_compat_t); - RB_OBJ_WRITE(compat_allocator_tbl_wrapper, &compat->newclass, newclass); - RB_OBJ_WRITE(compat_allocator_tbl_wrapper, &compat->oldclass, oldclass); + compat->newclass = newclass; + compat->oldclass = oldclass; compat->dumper = dumper; compat->loader = loader; st_insert(compat_allocator_table(), (st_data_t)allocator, (st_data_t)compat); + RB_OBJ_WRITTEN(compat_allocator_tbl_wrapper, Qundef, newclass); + RB_OBJ_WRITTEN(compat_allocator_tbl_wrapper, Qundef, oldclass); } struct dump_arg { diff --git a/misc/lldb_rb/commands/print_flags_command.py b/misc/lldb_rb/commands/print_flags_command.py index 2b056dd098..bc494ae01a 100644 --- a/misc/lldb_rb/commands/print_flags_command.py +++ b/misc/lldb_rb/commands/print_flags_command.py @@ -17,7 +17,7 @@ class PrintFlagsCommand(RbBaseCommand): flags = [ "RUBY_FL_WB_PROTECTED", "RUBY_FL_PROMOTED", "RUBY_FL_FINALIZE", - "RUBY_FL_SHAREABLE", "RUBY_FL_EXIVAR", "RUBY_FL_FREEZE", + "RUBY_FL_SHAREABLE", "RUBY_FL_FREEZE", "RUBY_FL_USER0", "RUBY_FL_USER1", "RUBY_FL_USER2", "RUBY_FL_USER3", "RUBY_FL_USER4", "RUBY_FL_USER5", "RUBY_FL_USER6", "RUBY_FL_USER7", "RUBY_FL_USER8", "RUBY_FL_USER9", "RUBY_FL_USER10", "RUBY_FL_USER11", "RUBY_FL_USER12", "RUBY_FL_USER13", "RUBY_FL_USER14", diff --git a/node_dump.c b/node_dump.c index 24711f3d97..ff5cc268ec 100644 --- a/node_dump.c +++ b/node_dump.c @@ -1027,8 +1027,10 @@ dump_node(VALUE buf, VALUE indent, int comment, const NODE * node) ANN("format: [nd_head]::[nd_mid]"); ANN("example: M::C"); F_ID(nd_mid, RNODE_COLON2, "constant name"); - LAST_NODE; F_NODE(nd_head, RNODE_COLON2, "receiver"); + F_LOC(delimiter_loc, RNODE_COLON2); + LAST_NODE; + F_LOC(name_loc, RNODE_COLON2); return; case NODE_COLON3: @@ -1036,6 +1038,8 @@ dump_node(VALUE buf, VALUE indent, int comment, const NODE * node) ANN("format: ::[nd_mid]"); ANN("example: ::Object"); F_ID(nd_mid, RNODE_COLON3, "constant name"); + F_LOC(delimiter_loc, RNODE_COLON3); + F_LOC(name_loc, RNODE_COLON3); return; case NODE_DOT2: @@ -378,7 +378,7 @@ init_copy(VALUE dest, VALUE obj) RBASIC(dest)->flags |= RBASIC(obj)->flags & T_MASK; switch (BUILTIN_TYPE(obj)) { case T_IMEMO: - rb_bug("Unreacheable"); + rb_bug("Unreachable"); break; case T_CLASS: case T_MODULE: @@ -1146,8 +1146,8 @@ static rb_node_undef_t *rb_node_undef_new(struct parser_params *p, NODE *nd_unde static rb_node_class_t *rb_node_class_new(struct parser_params *p, NODE *nd_cpath, NODE *nd_body, NODE *nd_super, const YYLTYPE *loc, const YYLTYPE *class_keyword_loc, const YYLTYPE *inheritance_operator_loc, const YYLTYPE *end_keyword_loc); static rb_node_module_t *rb_node_module_new(struct parser_params *p, NODE *nd_cpath, NODE *nd_body, const YYLTYPE *loc); static rb_node_sclass_t *rb_node_sclass_new(struct parser_params *p, NODE *nd_recv, NODE *nd_body, const YYLTYPE *loc); -static rb_node_colon2_t *rb_node_colon2_new(struct parser_params *p, NODE *nd_head, ID nd_mid, const YYLTYPE *loc); -static rb_node_colon3_t *rb_node_colon3_new(struct parser_params *p, ID nd_mid, const YYLTYPE *loc); +static rb_node_colon2_t *rb_node_colon2_new(struct parser_params *p, NODE *nd_head, ID nd_mid, const YYLTYPE *loc, const YYLTYPE *delimiter_loc, const YYLTYPE *name_loc); +static rb_node_colon3_t *rb_node_colon3_new(struct parser_params *p, ID nd_mid, const YYLTYPE *loc, const YYLTYPE *delimiter_loc, const YYLTYPE *name_loc); static rb_node_dot2_t *rb_node_dot2_new(struct parser_params *p, NODE *nd_beg, NODE *nd_end, const YYLTYPE *loc, const YYLTYPE *operator_loc); static rb_node_dot3_t *rb_node_dot3_new(struct parser_params *p, NODE *nd_beg, NODE *nd_end, const YYLTYPE *loc, const YYLTYPE *operator_loc); static rb_node_self_t *rb_node_self_new(struct parser_params *p, const YYLTYPE *loc); @@ -1254,8 +1254,8 @@ static rb_node_error_t *rb_node_error_new(struct parser_params *p, const YYLTYPE #define NEW_CLASS(n,b,s,loc,ck_loc,io_loc,ek_loc) (NODE *)rb_node_class_new(p,n,b,s,loc,ck_loc,io_loc,ek_loc) #define NEW_MODULE(n,b,loc) (NODE *)rb_node_module_new(p,n,b,loc) #define NEW_SCLASS(r,b,loc) (NODE *)rb_node_sclass_new(p,r,b,loc) -#define NEW_COLON2(c,i,loc) (NODE *)rb_node_colon2_new(p,c,i,loc) -#define NEW_COLON3(i,loc) (NODE *)rb_node_colon3_new(p,i,loc) +#define NEW_COLON2(c,i,loc,d_loc,n_loc) (NODE *)rb_node_colon2_new(p,c,i,loc,d_loc,n_loc) +#define NEW_COLON3(i,loc,d_loc,n_loc) (NODE *)rb_node_colon3_new(p,i,loc,d_loc,n_loc) #define NEW_DOT2(b,e,loc,op_loc) (NODE *)rb_node_dot2_new(p,b,e,loc,op_loc) #define NEW_DOT3(b,e,loc,op_loc) (NODE *)rb_node_dot3_new(p,b,e,loc,op_loc) #define NEW_SELF(loc) (NODE *)rb_node_self_new(p,loc) @@ -3067,13 +3067,13 @@ rb_parser_ary_free(rb_parser_t *p, rb_parser_ary_t *ary) | primary_value tCOLON2 tCONSTANT tOP_ASGN lex_ctxt rhs { YYLTYPE loc = code_loc_gen(&@primary_value, &@tCONSTANT); - $$ = new_const_op_assign(p, NEW_COLON2($primary_value, $tCONSTANT, &loc), $tOP_ASGN, $rhs, $lex_ctxt, &@$); + $$ = new_const_op_assign(p, NEW_COLON2($primary_value, $tCONSTANT, &loc, &@tCOLON2, &@tCONSTANT), $tOP_ASGN, $rhs, $lex_ctxt, &@$); /*% ripper: opassign!(const_path_field!($:1, $:3), $:4, $:6) %*/ } | tCOLON3 tCONSTANT tOP_ASGN lex_ctxt rhs { YYLTYPE loc = code_loc_gen(&@tCOLON3, &@tCONSTANT); - $$ = new_const_op_assign(p, NEW_COLON3($tCONSTANT, &loc), $tOP_ASGN, $rhs, $lex_ctxt, &@$); + $$ = new_const_op_assign(p, NEW_COLON3($tCONSTANT, &loc, &@tCOLON3, &@tCONSTANT), $tOP_ASGN, $rhs, $lex_ctxt, &@$); /*% ripper: opassign!(top_const_field!($:2), $:3, $:5) %*/ } | backref tOP_ASGN lex_ctxt rhs @@ -3756,12 +3756,12 @@ mlhs_node : user_or_keyword_variable | primary_value tCOLON2 tCONSTANT { /*% ripper: const_path_field!($:1, $:3) %*/ - $$ = const_decl(p, NEW_COLON2($1, $3, &@$), &@$); + $$ = const_decl(p, NEW_COLON2($1, $3, &@$, &@2, &@3), &@$); } | tCOLON3 tCONSTANT { /*% ripper: top_const_field!($:2) %*/ - $$ = const_decl(p, NEW_COLON3($2, &@$), &@$); + $$ = const_decl(p, NEW_COLON3($2, &@$, &@1, &@2), &@$); } | backref { @@ -3794,12 +3794,12 @@ lhs : user_or_keyword_variable | primary_value tCOLON2 tCONSTANT { /*% ripper: const_path_field!($:1, $:3) %*/ - $$ = const_decl(p, NEW_COLON2($1, $3, &@$), &@$); + $$ = const_decl(p, NEW_COLON2($1, $3, &@$, &@2, &@3), &@$); } | tCOLON3 tCONSTANT { /*% ripper: top_const_field!($:2) %*/ - $$ = const_decl(p, NEW_COLON3($2, &@$), &@$); + $$ = const_decl(p, NEW_COLON3($2, &@$, &@1, &@2), &@$); } | backref { @@ -3822,17 +3822,17 @@ cname : tIDENTIFIER cpath : tCOLON3 cname { - $$ = NEW_COLON3($2, &@$); + $$ = NEW_COLON3($2, &@$, &@1, &@2); /*% ripper: top_const_ref!($:2) %*/ } | cname { - $$ = NEW_COLON2(0, $1, &@$); + $$ = NEW_COLON2(0, $1, &@$, &NULL_LOC, &@1); /*% ripper: const_ref!($:1) %*/ } | primary_value tCOLON2 cname { - $$ = NEW_COLON2($1, $3, &@$); + $$ = NEW_COLON2($1, $3, &@$, &@2, &@3); /*% ripper: const_path_ref!($:1, $:3) %*/ } ; @@ -4385,12 +4385,12 @@ primary : inline_primary } | primary_value tCOLON2 tCONSTANT { - $$ = NEW_COLON2($1, $3, &@$); + $$ = NEW_COLON2($1, $3, &@$, &@2, &@3); /*% ripper: const_path_ref!($:1, $:3) %*/ } | tCOLON3 tCONSTANT { - $$ = NEW_COLON3($2, &@$); + $$ = NEW_COLON3($2, &@$, &@1, &@2); /*% ripper: top_const_ref!($:2) %*/ } | tLBRACK aref_args ']' @@ -5810,12 +5810,12 @@ p_expr_ref : '^' tLPAREN expr_value rparen p_const : tCOLON3 cname { - $$ = NEW_COLON3($2, &@$); + $$ = NEW_COLON3($2, &@$, &@1, &@2); /*% ripper: top_const_ref!($:2) %*/ } | p_const tCOLON2 cname { - $$ = NEW_COLON2($1, $3, &@$); + $$ = NEW_COLON2($1, $3, &@$, &@2, &@3); /*% ripper: const_path_ref!($:1, $:3) %*/ } | tCONSTANT @@ -11553,20 +11553,24 @@ rb_node_until_new(struct parser_params *p, NODE *nd_cond, NODE *nd_body, long nd } static rb_node_colon2_t * -rb_node_colon2_new(struct parser_params *p, NODE *nd_head, ID nd_mid, const YYLTYPE *loc) +rb_node_colon2_new(struct parser_params *p, NODE *nd_head, ID nd_mid, const YYLTYPE *loc, const YYLTYPE *delimiter_loc, const YYLTYPE *name_loc) { rb_node_colon2_t *n = NODE_NEWNODE(NODE_COLON2, rb_node_colon2_t, loc); n->nd_head = nd_head; n->nd_mid = nd_mid; + n->delimiter_loc = *delimiter_loc; + n->name_loc = *name_loc; return n; } static rb_node_colon3_t * -rb_node_colon3_new(struct parser_params *p, ID nd_mid, const YYLTYPE *loc) +rb_node_colon3_new(struct parser_params *p, ID nd_mid, const YYLTYPE *loc, const YYLTYPE *delimiter_loc, const YYLTYPE *name_loc) { rb_node_colon3_t *n = NODE_NEWNODE(NODE_COLON3, rb_node_colon3_t, loc); n->nd_mid = nd_mid; + n->delimiter_loc = *delimiter_loc; + n->name_loc = *name_loc; return n; } @@ -1562,7 +1562,8 @@ rb_sym_to_proc(VALUE sym) RARRAY_ASET(sym_proc_cache, index, procval); return RB_GC_GUARD(procval); - } else { + } + else { return sym_proc_new(rb_cProc, sym); } } @@ -1358,24 +1358,24 @@ make_shareable_check_shareable(VALUE obj) } switch (TYPE(obj)) { - case T_IMEMO: - return traverse_skip; - case T_OBJECT: - { - // If a T_OBJECT is shared and has no free capacity, we can't safely store the object_id inline, - // as it would require to move the object content into an external buffer. - // This is only a problem for T_OBJECT, given other types have external fields and can do RCU. - // To avoid this issue, we proactively create the object_id. - shape_id_t shape_id = RBASIC_SHAPE_ID(obj); - attr_index_t capacity = RSHAPE_CAPACITY(shape_id); - attr_index_t free_capacity = capacity - RSHAPE_LEN(shape_id); - if (!rb_shape_has_object_id(shape_id) && capacity && !free_capacity) { - rb_obj_id(obj); - } - } - break; - default: - break; + case T_IMEMO: + return traverse_skip; + case T_OBJECT: + { + // If a T_OBJECT is shared and has no free capacity, we can't safely store the object_id inline, + // as it would require to move the object content into an external buffer. + // This is only a problem for T_OBJECT, given other types have external fields and can do RCU. + // To avoid this issue, we proactively create the object_id. + shape_id_t shape_id = RBASIC_SHAPE_ID(obj); + attr_index_t capacity = RSHAPE_CAPACITY(shape_id); + attr_index_t free_capacity = capacity - RSHAPE_LEN(shape_id); + if (!rb_shape_has_object_id(shape_id) && capacity && !free_capacity) { + rb_obj_id(obj); + } + } + break; + default: + break; } if (!RB_OBJ_FROZEN_RAW(obj)) { @@ -1657,8 +1657,8 @@ obj_traverse_replace_i(VALUE obj, struct obj_traverse_replace_data *data) } while (0) if (UNLIKELY(rb_obj_exivar_p(obj))) { - struct gen_fields_tbl *fields_tbl; - rb_ivar_generic_fields_tbl_lookup(obj, &fields_tbl); + VALUE fields_obj; + rb_ivar_generic_fields_tbl_lookup(obj, &fields_obj); if (UNLIKELY(rb_shape_obj_too_complex_p(obj))) { struct obj_traverse_replace_callback_data d = { @@ -1667,7 +1667,7 @@ obj_traverse_replace_i(VALUE obj, struct obj_traverse_replace_data *data) .src = obj, }; rb_st_foreach_with_replace( - fields_tbl->as.complex.table, + rb_imemo_fields_complex_tbl(fields_obj), obj_iv_hash_traverse_replace_foreach_i, obj_iv_hash_traverse_replace_i, (st_data_t)&d @@ -1676,8 +1676,9 @@ obj_traverse_replace_i(VALUE obj, struct obj_traverse_replace_data *data) } else { uint32_t fields_count = RSHAPE_LEN(RBASIC_SHAPE_ID(obj)); + VALUE *fields = rb_imemo_fields_ptr(fields_obj); for (uint32_t i = 0; i < fields_count; i++) { - CHECK_AND_REPLACE(fields_tbl->as.shape.fields[i]); + CHECK_AND_REPLACE(fields[i]); } } } @@ -3507,7 +3507,8 @@ rb_reg_regcomp(VALUE str) return reg_cache; return reg_cache = rb_reg_new_str(str, 0); - } else { + } + else { return rb_reg_new_str(str, 0); } } diff --git a/rubyparser.h b/rubyparser.h index 16f5cac81f..c63929abb2 100644 --- a/rubyparser.h +++ b/rubyparser.h @@ -915,12 +915,16 @@ typedef struct RNode_COLON2 { struct RNode *nd_head; ID nd_mid; + rb_code_location_t delimiter_loc; + rb_code_location_t name_loc; } rb_node_colon2_t; typedef struct RNode_COLON3 { NODE node; ID nd_mid; + rb_code_location_t delimiter_loc; + rb_code_location_t name_loc; } rb_node_colon3_t; /* NODE_DOT2, NODE_DOT3, NODE_FLIP2, NODE_FLIP3 */ @@ -1153,7 +1157,7 @@ typedef struct RNode_ERROR { #define RNODE_FILE(node) ((rb_node_file_t *)(node)) #define RNODE_ENCODING(node) ((rb_node_encoding_t *)(node)) -/* FL : 0..4: T_TYPES, 5: KEEP_WB, 6: PROMOTED, 7: FINALIZE, 8: UNUSED, 9: UNUSED, 10: EXIVAR, 11: FREEZE */ +/* FL : 0..4: T_TYPES, 5: KEEP_WB, 6: PROMOTED, 7: FINALIZE, 8..10: UNUSED, 11: FREEZE */ /* NODE_FL: 0..4: UNUSED, 5: UNUSED, 6: UNUSED, 7: NODE_FL_NEWLINE, * 8..14: nd_type, * 15..: nd_line diff --git a/scheduler.c b/scheduler.c index 11faca01d3..83b9681cc3 100644 --- a/scheduler.c +++ b/scheduler.c @@ -63,8 +63,10 @@ typedef enum { struct rb_fiber_scheduler_blocking_operation { void *(*function)(void *); void *data; + rb_unblock_function_t *unblock_function; void *data2; + int flags; struct rb_fiber_scheduler_blocking_operation_state *state; @@ -208,7 +210,10 @@ rb_fiber_scheduler_blocking_operation_execute(rb_fiber_scheduler_blocking_operat return -1; // Invalid blocking operation } - // Atomically check if we can transition from QUEUED to EXECUTING + // Resolve sentinel values for unblock_function and data2: + rb_thread_resolve_unblock_function(&blocking_operation->unblock_function, &blocking_operation->data2, GET_THREAD()); + + // Atomically check if we can transition from QUEUED to EXECUTING rb_atomic_t expected = RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_QUEUED; if (RUBY_ATOMIC_CAS(blocking_operation->status, expected, RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_EXECUTING) != expected) { // Already cancelled or in wrong state @@ -1124,25 +1129,33 @@ rb_fiber_scheduler_blocking_operation_cancel(rb_fiber_scheduler_blocking_operati rb_atomic_t current_state = RUBY_ATOMIC_LOAD(blocking_operation->status); - switch (current_state) { + switch (current_state) { case RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_QUEUED: - // Work hasn't started - just mark as cancelled + // Work hasn't started - just mark as cancelled: if (RUBY_ATOMIC_CAS(blocking_operation->status, current_state, RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_CANCELLED) == current_state) { - return 0; // Successfully cancelled before execution + // Successfully cancelled before execution: + return 0; } // Fall through if state changed between load and CAS case RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_EXECUTING: // Work is running - mark cancelled AND call unblock function - RUBY_ATOMIC_SET(blocking_operation->status, RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_CANCELLED); - if (blocking_operation->unblock_function) { + if (RUBY_ATOMIC_CAS(blocking_operation->status, current_state, RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_CANCELLED) != current_state) { + // State changed between load and CAS - operation may have completed: + return 0; + } + // Otherwise, we successfully marked it as cancelled, so we can call the unblock function: + rb_unblock_function_t *unblock_function = blocking_operation->unblock_function; + if (unblock_function) { + RUBY_ASSERT(unblock_function != (rb_unblock_function_t *)-1 && "unblock_function is still sentinel value -1, should have been resolved earlier"); blocking_operation->unblock_function(blocking_operation->data2); } - return 1; // Cancelled during execution (unblock function called) + // Cancelled during execution (unblock function called): + return 1; case RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_COMPLETED: case RB_FIBER_SCHEDULER_BLOCKING_OPERATION_STATUS_CANCELLED: - // Already finished or cancelled + // Already finished or cancelled: return 0; } @@ -877,7 +877,7 @@ shape_get_next(rb_shape_t *shape, VALUE obj, ID id, bool emit_warnings) #endif VALUE klass; - if (IMEMO_TYPE_P(obj, imemo_class_fields)) { // HACK + if (IMEMO_TYPE_P(obj, imemo_fields)) { // HACK klass = CLASS_OF(obj); } else { @@ -1262,17 +1262,10 @@ rb_shape_verify_consistency(VALUE obj, shape_id_t shape_id) } else { if (flags_heap_index) { - rb_bug("shape_id indicate heap_index > 0 but objet is not T_OBJECT: %s", rb_obj_info(obj)); + rb_bug("shape_id indicate heap_index > 0 but object is not T_OBJECT: %s", rb_obj_info(obj)); } } - if (FL_TEST_RAW(obj, FL_EXIVAR)) { - RUBY_ASSERT(rb_obj_exivar_p(obj)); - } - else { - RUBY_ASSERT(!rb_obj_exivar_p(obj)); - } - return true; } #endif @@ -111,7 +111,7 @@ static inline shape_id_t RBASIC_SHAPE_ID(VALUE obj) { RUBY_ASSERT(!RB_SPECIAL_CONST_P(obj)); - RUBY_ASSERT(!RB_TYPE_P(obj, T_IMEMO) || IMEMO_TYPE_P(obj, imemo_class_fields)); + RUBY_ASSERT(!RB_TYPE_P(obj, T_IMEMO) || IMEMO_TYPE_P(obj, imemo_fields)); #if RBASIC_SHAPE_ID_FIELD return (shape_id_t)((RBASIC(obj)->shape_id)); #else @@ -135,7 +135,7 @@ static inline void RBASIC_SET_SHAPE_ID(VALUE obj, shape_id_t shape_id) { RUBY_ASSERT(!RB_SPECIAL_CONST_P(obj)); - RUBY_ASSERT(!RB_TYPE_P(obj, T_IMEMO) || IMEMO_TYPE_P(obj, imemo_class_fields)); + RUBY_ASSERT(!RB_TYPE_P(obj, T_IMEMO) || IMEMO_TYPE_P(obj, imemo_fields)); #if RBASIC_SHAPE_ID_FIELD RBASIC(obj)->shape_id = (VALUE)shape_id; #else diff --git a/spec/bundler/commands/newgem_spec.rb b/spec/bundler/commands/newgem_spec.rb index dd2aa5c8c4..30655656a8 100644 --- a/spec/bundler/commands/newgem_spec.rb +++ b/spec/bundler/commands/newgem_spec.rb @@ -386,7 +386,6 @@ RSpec.describe "bundle gem" do it "has no rubocop offenses when using --ext=rust and --linter=rubocop flag" do skip "ruby_core has an 'ast.rb' file that gets in the middle and breaks this spec" if ruby_core? - skip "RubyGems incompatible with Rust builder" if ::Gem::Version.new("3.3.11") > ::Gem.rubygems_version bundle "gem #{gem_name} --ext=rust --linter=rubocop" bundle_exec_rubocop @@ -395,7 +394,6 @@ RSpec.describe "bundle gem" do it "has no rubocop offenses when using --ext=rust, --test=minitest, and --linter=rubocop flag" do skip "ruby_core has an 'ast.rb' file that gets in the middle and breaks this spec" if ruby_core? - skip "RubyGems incompatible with Rust builder" if ::Gem::Version.new("3.3.11") > ::Gem.rubygems_version bundle "gem #{gem_name} --ext=rust --test=minitest --linter=rubocop" bundle_exec_rubocop @@ -404,7 +402,6 @@ RSpec.describe "bundle gem" do it "has no rubocop offenses when using --ext=rust, --test=rspec, and --linter=rubocop flag" do skip "ruby_core has an 'ast.rb' file that gets in the middle and breaks this spec" if ruby_core? - skip "RubyGems incompatible with Rust builder" if ::Gem::Version.new("3.3.11") > ::Gem.rubygems_version bundle "gem #{gem_name} --ext=rust --test=rspec --linter=rubocop" bundle_exec_rubocop @@ -413,7 +410,6 @@ RSpec.describe "bundle gem" do it "has no rubocop offenses when using --ext=rust, --test=test-unit, and --linter=rubocop flag" do skip "ruby_core has an 'ast.rb' file that gets in the middle and breaks this spec" if ruby_core? - skip "RubyGems incompatible with Rust builder" if ::Gem::Version.new("3.3.11") > ::Gem.rubygems_version bundle "gem #{gem_name} --ext=rust --test=test-unit --linter=rubocop" bundle_exec_rubocop @@ -1724,24 +1720,10 @@ RSpec.describe "bundle gem" do end end - context "--ext parameter set with rust and old RubyGems" do - it "fails in friendly way" do - if ::Gem::Version.new("3.3.11") <= ::Gem.rubygems_version - skip "RubyGems compatible with Rust builder" - end - - expect do - bundle ["gem", gem_name, "--ext=rust"].compact.join(" ") - end.to raise_error(RuntimeError, /too old to build Rust extension/) - end - end - context "--ext parameter set with rust" do let(:flags) { "--ext=rust" } before do - skip "RubyGems incompatible with Rust builder" if ::Gem::Version.new("3.3.11") > ::Gem.rubygems_version - bundle ["gem", gem_name, flags].compact.join(" ") end diff --git a/spec/bundler/install/gems/mirror_probe_spec.rb b/spec/bundler/install/gems/mirror_probe_spec.rb index 5edd829e7b..fe9654e0a9 100644 --- a/spec/bundler/install/gems/mirror_probe_spec.rb +++ b/spec/bundler/install/gems/mirror_probe_spec.rb @@ -1,9 +1,6 @@ # frozen_string_literal: true RSpec.describe "fetching dependencies with a not available mirror" do - let(:mirror) { @mirror_uri } - let(:original) { @server_uri } - let(:server_port) { @server_port } let(:host) { "127.0.0.1" } before do @@ -20,13 +17,13 @@ RSpec.describe "fetching dependencies with a not available mirror" do context "with a specific fallback timeout" do before do - global_config("BUNDLE_MIRROR__HTTP://127__0__0__1:#{server_port}/__FALLBACK_TIMEOUT/" => "true", - "BUNDLE_MIRROR__HTTP://127__0__0__1:#{server_port}/" => mirror) + global_config("BUNDLE_MIRROR__HTTP://127__0__0__1:#{@server_port}/__FALLBACK_TIMEOUT/" => "true", + "BUNDLE_MIRROR__HTTP://127__0__0__1:#{@server_port}/" => @mirror_uri) end it "install a gem using the original uri when the mirror is not responding" do gemfile <<-G - source "#{original}" + source "#{@server_uri}" gem 'weakling' G @@ -41,12 +38,12 @@ RSpec.describe "fetching dependencies with a not available mirror" do context "with a global fallback timeout" do before do global_config("BUNDLE_MIRROR__ALL__FALLBACK_TIMEOUT/" => "1", - "BUNDLE_MIRROR__ALL" => mirror) + "BUNDLE_MIRROR__ALL" => @mirror_uri) end it "install a gem using the original uri when the mirror is not responding" do gemfile <<-G - source "#{original}" + source "#{@server_uri}" gem 'weakling' G @@ -60,47 +57,47 @@ RSpec.describe "fetching dependencies with a not available mirror" do context "with a specific mirror without a fallback timeout" do before do - global_config("BUNDLE_MIRROR__HTTP://127__0__0__1:#{server_port}/" => mirror) + global_config("BUNDLE_MIRROR__HTTP://127__0__0__1:#{@server_port}/" => @mirror_uri) end - it "fails to install the gem with a timeout error" do + it "fails to install the gem with a timeout error when the mirror is not responding" do gemfile <<-G - source "#{original}" + source "#{@server_uri}" gem 'weakling' G bundle :install, artifice: nil, raise_on_error: false - expect(out).to include("Fetching source index from #{mirror}") + expect(out).to include("Fetching source index from #{@mirror_uri}") err_lines = err.split("\n") - expect(err_lines).to include(%r{\ARetrying fetcher due to error \(2/4\): Bundler::HTTPError Could not fetch specs from #{mirror}/ due to underlying error <}) - expect(err_lines).to include(%r{\ARetrying fetcher due to error \(3/4\): Bundler::HTTPError Could not fetch specs from #{mirror}/ due to underlying error <}) - expect(err_lines).to include(%r{\ARetrying fetcher due to error \(4/4\): Bundler::HTTPError Could not fetch specs from #{mirror}/ due to underlying error <}) - expect(err_lines).to include(%r{\ACould not fetch specs from #{mirror}/ due to underlying error <}) + expect(err_lines).to include(%r{\ARetrying fetcher due to error \(2/4\): Bundler::HTTPError Could not fetch specs from #{@mirror_uri}/ due to underlying error <}) + expect(err_lines).to include(%r{\ARetrying fetcher due to error \(3/4\): Bundler::HTTPError Could not fetch specs from #{@mirror_uri}/ due to underlying error <}) + expect(err_lines).to include(%r{\ARetrying fetcher due to error \(4/4\): Bundler::HTTPError Could not fetch specs from #{@mirror_uri}/ due to underlying error <}) + expect(err_lines).to include(%r{\ACould not fetch specs from #{@mirror_uri}/ due to underlying error <}) end end context "with a global mirror without a fallback timeout" do before do - global_config("BUNDLE_MIRROR__ALL" => mirror) + global_config("BUNDLE_MIRROR__ALL" => @mirror_uri) end - it "fails to install the gem with a timeout error" do + it "fails to install the gem with a timeout error when the mirror is not responding" do gemfile <<-G - source "#{original}" + source "#{@server_uri}" gem 'weakling' G bundle :install, artifice: nil, raise_on_error: false - expect(out).to include("Fetching source index from #{mirror}") + expect(out).to include("Fetching source index from #{@mirror_uri}") err_lines = err.split("\n") - expect(err_lines).to include(%r{\ARetrying fetcher due to error \(2/4\): Bundler::HTTPError Could not fetch specs from #{mirror}/ due to underlying error <}) - expect(err_lines).to include(%r{\ARetrying fetcher due to error \(3/4\): Bundler::HTTPError Could not fetch specs from #{mirror}/ due to underlying error <}) - expect(err_lines).to include(%r{\ARetrying fetcher due to error \(4/4\): Bundler::HTTPError Could not fetch specs from #{mirror}/ due to underlying error <}) - expect(err_lines).to include(%r{\ACould not fetch specs from #{mirror}/ due to underlying error <}) + expect(err_lines).to include(%r{\ARetrying fetcher due to error \(2/4\): Bundler::HTTPError Could not fetch specs from #{@mirror_uri}/ due to underlying error <}) + expect(err_lines).to include(%r{\ARetrying fetcher due to error \(3/4\): Bundler::HTTPError Could not fetch specs from #{@mirror_uri}/ due to underlying error <}) + expect(err_lines).to include(%r{\ARetrying fetcher due to error \(4/4\): Bundler::HTTPError Could not fetch specs from #{@mirror_uri}/ due to underlying error <}) + expect(err_lines).to include(%r{\ACould not fetch specs from #{@mirror_uri}/ due to underlying error <}) end end @@ -108,13 +105,13 @@ RSpec.describe "fetching dependencies with a not available mirror" do @server_port = find_unused_port @server_uri = "http://#{host}:#{@server_port}" - require_relative "../../support/artifice/endpoint" + require_relative "../../support/artifice/compact_index" require_relative "../../support/silent_logger" require "rackup/server" @server_thread = Thread.new do - Rackup::Server.start(app: Endpoint, + Rackup::Server.start(app: CompactIndexAPI, Host: host, Port: @server_port, server: "webrick", diff --git a/spec/bundler/install/gems/standalone_spec.rb b/spec/bundler/install/gems/standalone_spec.rb index 50ef4dc3a7..7ad657a738 100644 --- a/spec/bundler/install/gems/standalone_spec.rb +++ b/spec/bundler/install/gems/standalone_spec.rb @@ -147,7 +147,6 @@ RSpec.shared_examples "bundle install --standalone" do it "works and points to the vendored copies, not to the default copies" do necessary_gems_in_bundle_path = ["optparse --version 0.1.1", "psych --version 3.3.2", "logger --version 1.4.3", "etc --version 1.4.3", "stringio --version 3.1.0"] - necessary_gems_in_bundle_path += ["yaml --version 0.1.1"] if Gem.rubygems_version < Gem::Version.new("3.4.a") realworld_system_gems(*necessary_gems_in_bundle_path, path: scoped_gem_path(bundled_app("bundle"))) build_gem "foo", "1.0.0", to_system: true, default: true do |s| @@ -186,7 +185,6 @@ RSpec.shared_examples "bundle install --standalone" do it "works for gems with extensions and points to the vendored copies, not to the default copies" do simulate_platform "arm64-darwin-23" do necessary_gems_in_bundle_path = ["optparse --version 0.1.1", "psych --version 3.3.2", "logger --version 1.4.3", "etc --version 1.4.3", "stringio --version 3.1.0", "shellwords --version 0.2.0", "open3 --version 0.2.1"] - necessary_gems_in_bundle_path += ["yaml --version 0.1.1"] if Gem.rubygems_version < Gem::Version.new("3.4.a") realworld_system_gems(*necessary_gems_in_bundle_path, path: scoped_gem_path(bundled_app("bundle"))) build_gem "baz", "1.0.0", to_system: true, default: true, &:add_c_extension diff --git a/spec/bundler/runtime/setup_spec.rb b/spec/bundler/runtime/setup_spec.rb index e47e64de29..b9b78cb044 100644 --- a/spec/bundler/runtime/setup_spec.rb +++ b/spec/bundler/runtime/setup_spec.rb @@ -1406,7 +1406,6 @@ end describe "default gem activation" do let(:exemptions) do exempts = %w[did_you_mean bundler uri pathname] - exempts << "etc" if (Gem.ruby_version < Gem::Version.new("3.2") || Gem.ruby_version >= Gem::Version.new("3.3.2")) && Gem.win_platform? exempts << "error_highlight" # added in Ruby 3.1 as a default gem exempts << "ruby2_keywords" # added in Ruby 3.1 as a default gem exempts << "syntax_suggest" # added in Ruby 3.2 as a default gem diff --git a/spec/ruby/optional/capi/digest_spec.rb b/spec/ruby/optional/capi/digest_spec.rb index c753733906..65c5ecebb1 100644 --- a/spec/ruby/optional/capi/digest_spec.rb +++ b/spec/ruby/optional/capi/digest_spec.rb @@ -1,6 +1,10 @@ require_relative 'spec_helper' -require 'fiddle' +begin + require 'fiddle' +rescue LoadError + return +end load_extension('digest') diff --git a/spec/ruby/optional/capi/string_spec.rb b/spec/ruby/optional/capi/string_spec.rb index 27f65c872a..be9cb9015f 100644 --- a/spec/ruby/optional/capi/string_spec.rb +++ b/spec/ruby/optional/capi/string_spec.rb @@ -1222,7 +1222,7 @@ describe "C-API String function" do -> { str.upcase! }.should raise_error(RuntimeError, 'can\'t modify string; temporarily locked') end - ruby_bug "#20998", ""..."3.6" do # TODO: check when Ruby 3.5 is released + ruby_version_is "3.5" do it "raises FrozenError if string is frozen" do str = -"rb_str_locktmp" -> { @s.rb_str_locktmp(str) }.should raise_error(FrozenError) @@ -1246,7 +1246,7 @@ describe "C-API String function" do -> { @s.rb_str_unlocktmp(+"test") }.should raise_error(RuntimeError, 'temporal unlocking already unlocked string') end - ruby_bug "#20998", ""..."3.6" do # TODO: check when Ruby 3.5 is released + ruby_version_is "3.5" do it "raises FrozenError if string is frozen" do str = -"rb_str_locktmp" -> { @s.rb_str_unlocktmp(str) }.should raise_error(FrozenError) @@ -1495,7 +1495,16 @@ st_update(st_table *tab, st_data_t key, value = entry->record; } old_key = key; + + unsigned int rebuilds_num = tab->rebuilds_num; + retval = (*func)(&key, &value, arg, existing); + + // We need to make sure that the callback didn't cause a table rebuild + // Ideally we would make sure no operations happened + assert(rebuilds_num == tab->rebuilds_num); + (void)rebuilds_num; + switch (retval) { case ST_CONTINUE: if (! existing) { @@ -485,7 +485,6 @@ build_fstring(VALUE str, struct fstr_update_arg *arg) RUBY_ASSERT(RB_TYPE_P(str, T_STRING)); RUBY_ASSERT(OBJ_FROZEN(str)); RUBY_ASSERT(!FL_TEST_RAW(str, STR_FAKESTR)); - RUBY_ASSERT(!FL_TEST_RAW(str, FL_EXIVAR)); RUBY_ASSERT(!rb_obj_exivar_p(str)); RUBY_ASSERT(RBASIC_CLASS(str) == rb_cString); RUBY_ASSERT(!rb_objspace_garbage_object_p(str)); @@ -3665,6 +3664,7 @@ RUBY_ALIAS_FUNCTION(rb_str_dup_frozen(VALUE str), rb_str_new_frozen, (str)) VALUE rb_str_locktmp(VALUE str) { + rb_check_frozen(str); if (FL_TEST(str, STR_TMPLOCK)) { rb_raise(rb_eRuntimeError, "temporal locking already locked string"); } @@ -3675,6 +3675,7 @@ rb_str_locktmp(VALUE str) VALUE rb_str_unlocktmp(VALUE str) { + rb_check_frozen(str); if (!FL_TEST(str, STR_TMPLOCK)) { rb_raise(rb_eRuntimeError, "temporal unlocking already unlocked string"); } diff --git a/test/date/test_date.rb b/test/date/test_date.rb index 3f9c893efa..7e37fc94d2 100644 --- a/test/date/test_date.rb +++ b/test/date/test_date.rb @@ -135,6 +135,10 @@ class TestDate < Test::Unit::TestCase assert_equal(9, h[DateTime.new(1999,5,25)]) h = {} + h[Date.new(3171505571716611468830131104691,2,19)] = 0 + assert_equal(true, h.key?(Date.new(3171505571716611468830131104691,2,19))) + + h = {} h[DateTime.new(1999,5,23)] = 0 h[DateTime.new(1999,5,24)] = 1 h[DateTime.new(1999,5,25)] = 2 diff --git a/test/date/test_switch_hitter.rb b/test/date/test_switch_hitter.rb index bdf299e030..cc75782537 100644 --- a/test/date/test_switch_hitter.rb +++ b/test/date/test_switch_hitter.rb @@ -97,6 +97,11 @@ class TestSH < Test::Unit::TestCase [d.year, d.mon, d.mday, d.hour, d.min, d.sec, d.offset]) end + def test_ajd + assert_equal(Date.civil(2008, 1, 16).ajd, 4908963r/2) + assert_equal(Date.civil(-11082381539297990, 2, 19).ajd, -8095679714453739481r/2) + end + def test_ordinal d = Date.ordinal assert_equal([-4712, 1], [d.year, d.yday]) diff --git a/test/prism/lex_test.rb b/test/prism/lex_test.rb index 2786c45a22..d34c3d9dd3 100644 --- a/test/prism/lex_test.rb +++ b/test/prism/lex_test.rb @@ -17,7 +17,7 @@ module Prism "spanning_heredoc.txt", "spanning_heredoc_newlines.txt", # Prism emits a single :on_tstring_content in <<- style heredocs when there - # is a line continuation preceeded by escaped backslashes. It should emit two, same + # is a line continuation preceded by escaped backslashes. It should emit two, same # as if the backslashes are not present. "heredocs_with_fake_newlines.txt", ] diff --git a/test/ruby/test_ast.rb b/test/ruby/test_ast.rb index 72a0d821a0..d22823470b 100644 --- a/test/ruby/test_ast.rb +++ b/test/ruby/test_ast.rb @@ -1397,6 +1397,24 @@ dummy assert_locations(node.children[-1].locations, [[1, 0, 1, 16], [1, 0, 1, 5], [1, 8, 1, 9], [1, 13, 1, 16]]) end + def test_colon2_locations + node = ast_parse("A::B") + assert_locations(node.children[-1].locations, [[1, 0, 1, 4], [1, 1, 1, 3], [1, 3, 1, 4]]) + + node = ast_parse("A::B::C") + assert_locations(node.children[-1].locations, [[1, 0, 1, 7], [1, 4, 1, 6], [1, 6, 1, 7]]) + assert_locations(node.children[-1].children[0].locations, [[1, 0, 1, 4], [1, 1, 1, 3], [1, 3, 1, 4]]) + end + + def test_colon3_locations + node = ast_parse("::A") + assert_locations(node.children[-1].locations, [[1, 0, 1, 3], [1, 0, 1, 2], [1, 2, 1, 3]]) + + node = ast_parse("::A::B") + assert_locations(node.children[-1].locations, [[1, 0, 1, 6], [1, 3, 1, 5], [1, 5, 1, 6]]) + assert_locations(node.children[-1].children[0].locations, [[1, 0, 1, 3], [1, 0, 1, 2], [1, 2, 1, 3]]) + end + def test_dot2_locations node = ast_parse("1..2") assert_locations(node.children[-1].locations, [[1, 0, 1, 4], [1, 1, 1, 3]]) diff --git a/test/ruby/test_encoding.rb b/test/ruby/test_encoding.rb index ee37199be0..0ab357f53a 100644 --- a/test/ruby/test_encoding.rb +++ b/test/ruby/test_encoding.rb @@ -33,7 +33,7 @@ class TestEncoding < Test::Unit::TestCase encodings.each do |e| assert_raise(TypeError) { e.dup } assert_raise(TypeError) { e.clone } - assert_equal(e.object_id, Marshal.load(Marshal.dump(e)).object_id) + assert_same(e, Marshal.load(Marshal.dump(e))) end end diff --git a/test/ruby/test_gc.rb b/test/ruby/test_gc.rb index daa645b349..3516cefedf 100644 --- a/test/ruby/test_gc.rb +++ b/test/ruby/test_gc.rb @@ -393,12 +393,10 @@ class TestGc < Test::Unit::TestCase # Create some objects and place it in a WeakMap wmap = ObjectSpace::WeakMap.new - ary = Array.new(count) - enum = count.times - enum.each.with_index do |i| + ary = Array.new(count) do |i| obj = Object.new - ary[i] = obj wmap[obj] = nil + obj end # Run full GC to collect stats about weak references @@ -421,7 +419,7 @@ class TestGc < Test::Unit::TestCase GC.start # Sometimes the WeakMap has a few elements, which might be held on by registers. - assert_operator(wmap.size, :<=, 2) + assert_operator(wmap.size, :<=, count / 1000) assert_operator(GC.latest_gc_info(:weak_references_count), :<=, before_weak_references_count - count + error_tolerance) assert_operator(GC.latest_gc_info(:retained_weak_references_count), :<=, before_retained_weak_references_count - count + error_tolerance) diff --git a/test/ruby/test_io_buffer.rb b/test/ruby/test_io_buffer.rb index 55296c1f23..62c4667888 100644 --- a/test/ruby/test_io_buffer.rb +++ b/test/ruby/test_io_buffer.rb @@ -121,6 +121,16 @@ class TestIOBuffer < Test::Unit::TestCase end end + def test_string_mapped_buffer_frozen + string = "Hello World".freeze + IO::Buffer.for(string) do |buffer| + assert_raise IO::Buffer::AccessError, "Buffer is not writable!" do + buffer.set_string("abc") + end + assert_equal "H".ord, buffer.get_value(:U8, 0) + end + end + def test_non_string not_string = Object.new @@ -683,4 +693,17 @@ class TestIOBuffer < Test::Unit::TestCase buf.set_string('a', 0, 0) assert_predicate buf, :empty? end + + # https://bugs.ruby-lang.org/issues/21210 + def test_bug_21210 + omit "compaction is not supported on this platform" unless GC.respond_to?(:compact) + + str = +"hello" + buf = IO::Buffer.for(str) + assert_predicate buf, :valid? + + GC.verify_compaction_references(expand_heap: true, toward: :empty) + + assert_predicate buf, :valid? + end end diff --git a/test/ruby/test_object_id.rb b/test/ruby/test_object_id.rb index 018cc81496..9c0099517b 100644 --- a/test/ruby/test_object_id.rb +++ b/test/ruby/test_object_id.rb @@ -243,35 +243,4 @@ class TestObjectIdRactor < Test::Unit::TestCase assert_equal object_id, obj.object_id end; end - - def test_object_id_race_free_with_stress_compact - assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") - begin; - Warning[:experimental] = false - class MyClass - attr_reader :a, :b, :c - def initialize - @a = @b = @c = nil - end - end - N = 20 - objs = Ractor.make_shareable(N.times.map { MyClass.new }) - - GC.stress = true - GC.auto_compact = true if GC.respond_to?(:auto_compact=) - - results = 4.times.map{ - Ractor.new(objs) { |objs| - vars = [] - ids = [] - objs.each do |obj| - vars << obj.a << obj.b << obj.c - ids << obj.object_id - end - [vars, ids] - } - }.map(&:value) - assert_equal 1, results.uniq.size - end; - end end diff --git a/test/ruby/test_rubyoptions.rb b/test/ruby/test_rubyoptions.rb index 3f79c2afd7..54ad953ee9 100644 --- a/test/ruby/test_rubyoptions.rb +++ b/test/ruby/test_rubyoptions.rb @@ -787,6 +787,12 @@ class TestRubyOptions < Test::Unit::TestCase unless /mswin|mingw/ =~ RUBY_PLATFORM opts[:rlimit_core] = 0 end + opts[:failed] = proc do |status, message = "", out = ""| + if (sig = status.termsig) && Signal.list["SEGV"] == sig + out = "" + end + Test::Unit::CoreAssertions::FailDesc[status, message] + end ExecOptions = opts.freeze # The regexp list that should match the entire stderr output. @@ -847,7 +853,11 @@ class TestRubyOptions < Test::Unit::TestCase args.unshift(env) test_stdin = "" - tests = [//, list] unless block + if !block + tests = [//, list, message] + elsif message + tests = [[], [], message] + end assert_in_out_err(args, test_stdin, *tests, encoding: "ASCII-8BIT", **SEGVTest::ExecOptions, **opt, &block) @@ -860,13 +870,12 @@ class TestRubyOptions < Test::Unit::TestCase def test_segv_loaded_features bug7402 = '[ruby-core:49573]' - status = assert_segv(['-e', "END {#{SEGVTest::KILL_SELF}}", - '-e', 'class Bogus; def to_str; exit true; end; end', - '-e', '$".clear', - '-e', '$".unshift Bogus.new', - '-e', '(p $"; abort) unless $".size == 1', - ]) - assert_not_predicate(status, :success?, "segv but success #{bug7402}") + assert_segv(['-e', "END {#{SEGVTest::KILL_SELF}}", + '-e', 'class Bogus; def to_str; exit true; end; end', + '-e', '$".clear', + '-e', '$".unshift Bogus.new', + '-e', '(p $"; abort) unless $".size == 1', + ], success: false) end def test_segv_setproctitle diff --git a/test/ruby/test_variable.rb b/test/ruby/test_variable.rb index 49fec2d40e..984045e05d 100644 --- a/test/ruby/test_variable.rb +++ b/test/ruby/test_variable.rb @@ -407,6 +407,20 @@ class TestVariable < Test::Unit::TestCase } end + def test_exivar_resize_with_compaction_stress + omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077 + objs = 10_000.times.map do + ExIvar.new + end + EnvUtil.under_gc_compact_stress do + 10.times do + x = ExIvar.new + x.instance_variable_set(:@resize, 1) + x + end + end + end + def test_local_variables_with_kwarg bug11674 = '[ruby-core:71437] [Bug #11674]' v = with_kwargs_11(v1:1,v2:2,v3:3,v4:4,v5:5,v6:6,v7:7,v8:8,v9:9,v10:10,v11:11) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index 6095b0b734..2b171b02b1 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -31,6 +31,20 @@ class TestZJIT < Test::Unit::TestCase } end + def test_putstring + assert_compiles '""', %q{ + def test = "#{""}" + test + }, insns: [:putstring] + end + + def test_putchilldedstring + assert_compiles '""', %q{ + def test = "" + test + }, insns: [:putchilledstring] + end + def test_leave_param assert_compiles '5', %q{ def test(n) = n @@ -281,6 +295,34 @@ class TestZJIT < Test::Unit::TestCase }, insns: [:opt_ge], call_threshold: 2 end + def test_opt_hash_freeze + assert_compiles '{}', <<~RUBY, insns: [:opt_hash_freeze] + def test = {}.freeze + test + RUBY + end + + def test_opt_ary_freeze + assert_compiles '[]', <<~RUBY, insns: [:opt_ary_freeze] + def test = [].freeze + test + RUBY + end + + def test_opt_str_freeze + assert_compiles '""', <<~RUBY, insns: [:opt_str_freeze] + def test = "".freeze + test + RUBY + end + + def test_opt_str_uminus + assert_compiles '""', <<~RUBY, insns: [:opt_str_uminus] + def test = -"" + test + RUBY + end + def test_new_array_empty assert_compiles '[]', %q{ def test = [] @@ -636,6 +678,27 @@ class TestZJIT < Test::Unit::TestCase } end + def test_uncached_getconstant_path + assert_compiles RUBY_COPYRIGHT.dump, %q{ + def test = RUBY_COPYRIGHT + test + }, call_threshold: 1, insns: [:opt_getconstant_path] + end + + def test_getconstant_path_autoload + # A constant-referencing expression can run arbitrary code through Kernel#autoload. + Dir.mktmpdir('autoload') do |tmpdir| + autoload_path = File.join(tmpdir, 'test_getconstant_path_autoload.rb') + File.write(autoload_path, 'X = RUBY_COPYRIGHT') + + assert_compiles RUBY_COPYRIGHT.dump, %Q{ + Object.autoload(:X, #{File.realpath(autoload_path).inspect}) + def test = X + test + }, call_threshold: 1, insns: [:opt_getconstant_path] + end + end + def test_send_backtrace backtrace = [ "-e:2:in 'Object#jit_frame1'", @@ -652,6 +715,32 @@ class TestZJIT < Test::Unit::TestCase }, call_threshold: 2 end + def test_putspecialobject_vm_core_and_cbase + assert_compiles '10', %q{ + def test + alias bar test + 10 + end + + test + bar + }, insns: [:putspecialobject] + end + + def test_putspecialobject_const_base + assert_compiles '1', %q{ + Foo = 1 + + def test = Foo + + # First call: populates the constant cache + test + # Second call: triggers ZJIT compilation with warm cache + # RubyVM::ZJIT.assert_compiles will panic if this fails to compile + test + }, call_threshold: 2 + end + # tool/ruby_vm/views/*.erb relies on the zjit instructions a) being contiguous and # b) being reliably ordered after all the other instructions. def test_instruction_order diff --git a/test/rubygems/helper.rb b/test/rubygems/helper.rb index d847d3b35e..af78bab724 100644 --- a/test/rubygems/helper.rb +++ b/test/rubygems/helper.rb @@ -683,6 +683,14 @@ class Gem::TestCase < Test::Unit::TestCase path end + def write_dummy_extconf(gem_name) + write_file File.join(@tempdir, "extconf.rb") do |io| + io.puts "require 'mkmf'" + yield io if block_given? + io.puts "create_makefile '#{gem_name}'" + end + end + ## # Load a YAML string, the psych 3 way diff --git a/test/rubygems/test_gem_commands_install_command.rb b/test/rubygems/test_gem_commands_install_command.rb index 77525aed2c..d05cfef653 100644 --- a/test/rubygems/test_gem_commands_install_command.rb +++ b/test/rubygems/test_gem_commands_install_command.rb @@ -647,17 +647,10 @@ ERROR: Possible alternatives: non_existent_with_hint @cmd.options[:args] = %w[a] use_ui @ui do - # Don't use Dir.chdir with a block, it warnings a lot because - # of a downstream Dir.chdir with a block - old = Dir.getwd - - begin - Dir.chdir @tempdir + Dir.chdir @tempdir do assert_raise Gem::MockGemUi::SystemExitException, @ui.error do @cmd.execute end - ensure - Dir.chdir old end end @@ -684,17 +677,10 @@ ERROR: Possible alternatives: non_existent_with_hint @cmd.options[:args] = %w[a] use_ui @ui do - # Don't use Dir.chdir with a block, it warnings a lot because - # of a downstream Dir.chdir with a block - old = Dir.getwd - - begin - Dir.chdir @tempdir + Dir.chdir @tempdir do assert_raise Gem::MockGemUi::SystemExitException, @ui.error do @cmd.execute end - ensure - Dir.chdir old end end @@ -720,17 +706,10 @@ ERROR: Possible alternatives: non_existent_with_hint @cmd.options[:args] = %w[a] use_ui @ui do - # Don't use Dir.chdir with a block, it warnings a lot because - # of a downstream Dir.chdir with a block - old = Dir.getwd - - begin - Dir.chdir @tempdir + Dir.chdir @tempdir do assert_raise Gem::MockGemUi::SystemExitException, @ui.error do @cmd.execute end - ensure - Dir.chdir old end end diff --git a/test/rubygems/test_gem_dependency_installer.rb b/test/rubygems/test_gem_dependency_installer.rb index 56b84160c4..f84881579a 100644 --- a/test/rubygems/test_gem_dependency_installer.rb +++ b/test/rubygems/test_gem_dependency_installer.rb @@ -382,13 +382,9 @@ class TestGemDependencyInstaller < Gem::TestCase FileUtils.mv f1_gem, @tempdir inst = nil - pwd = Dir.getwd - Dir.chdir @tempdir - begin + Dir.chdir @tempdir do inst = Gem::DependencyInstaller.new inst.install "f" - ensure - Dir.chdir pwd end assert_equal %w[f-1], inst.installed_gems.map(&:full_name) @@ -523,6 +519,58 @@ class TestGemDependencyInstaller < Gem::TestCase assert_equal %w[a-1], inst.installed_gems.map(&:full_name) end + def test_install_local_with_extensions_already_installed + pend "needs investigation" if Gem.java_platform? + pend "ruby.h is not provided by ruby repo" if ruby_repo? + + @spec = quick_gem "a" do |s| + s.extensions << "extconf.rb" + s.files += %w[extconf.rb a.c] + end + + write_dummy_extconf "a" + + c_source_path = File.join(@tempdir, "a.c") + + write_file c_source_path do |io| + io.write <<-C + #include <ruby.h> + void Init_a() { } + C + end + + package_path = Gem::Package.build @spec + installer = Gem::Installer.at(package_path) + + # Make sure the gem is installed and backup the correct package + + installer.install + + package_bkp_path = "#{package_path}.bkp" + FileUtils.cp package_path, package_bkp_path + + # Break the extension, rebuild it, and try to install it + + write_file c_source_path do |io| + io.write "typo" + end + + Gem::Package.build @spec + + assert_raise Gem::Ext::BuildError do + installer.install + end + + # Make sure installing the good package again still works + + FileUtils.cp "#{package_path}.bkp", package_path + + Dir.chdir @tempdir do + inst = Gem::DependencyInstaller.new domain: :local + inst.install package_path + end + end + def test_install_minimal_deps util_setup_gems diff --git a/test/rubygems/test_gem_installer.rb b/test/rubygems/test_gem_installer.rb index dfa8df283c..6d8a523507 100644 --- a/test/rubygems/test_gem_installer.rb +++ b/test/rubygems/test_gem_installer.rb @@ -1478,12 +1478,7 @@ end @spec = setup_base_spec @spec.extensions << "extconf.rb" - write_file File.join(@tempdir, "extconf.rb") do |io| - io.write <<-RUBY - require "mkmf" - create_makefile("#{@spec.name}") - RUBY - end + write_dummy_extconf @spec.name @spec.files += %w[extconf.rb] @@ -1503,12 +1498,7 @@ end @spec = setup_base_spec @spec.extensions << "extconf.rb" - write_file File.join(@tempdir, "extconf.rb") do |io| - io.write <<-RUBY - require "mkmf" - create_makefile("#{@spec.name}") - RUBY - end + write_dummy_extconf @spec.name @spec.files += %w[extconf.rb] @@ -1539,12 +1529,7 @@ end def test_install_user_extension_dir @spec = setup_base_spec @spec.extensions << "extconf.rb" - write_file File.join(@tempdir, "extconf.rb") do |io| - io.write <<-RUBY - require "mkmf" - create_makefile("#{@spec.name}") - RUBY - end + write_dummy_extconf @spec.name @spec.files += %w[extconf.rb] @@ -1571,15 +1556,13 @@ end @spec = setup_base_spec @spec.extensions << "extconf.rb" - write_file File.join(@tempdir, "extconf.rb") do |io| + write_dummy_extconf @spec.name do |io| io.write <<-RUBY - require "mkmf" CONFIG['CC'] = '$(TOUCH) $@ ||' CONFIG['LDSHARED'] = '$(TOUCH) $@ ||' $ruby = '#{Gem.ruby}' - create_makefile("#{@spec.name}") RUBY end @@ -1618,12 +1601,7 @@ end @spec = setup_base_spec @spec.extensions << "extconf.rb" - write_file File.join(@tempdir, "extconf.rb") do |io| - io.write <<-RUBY - require "mkmf" - create_makefile("#{@spec.name}") - RUBY - end + write_dummy_extconf @spec.name rb = File.join("lib", "#{@spec.name}.rb") @spec.files += [rb] @@ -1663,15 +1641,13 @@ end @spec.extensions << "extconf.rb" - write_file File.join(@tempdir, "extconf.rb") do |io| + write_dummy_extconf @spec.name do |io| io.write <<-RUBY - require "mkmf" CONFIG['CC'] = '$(TOUCH) $@ ||' CONFIG['LDSHARED'] = '$(TOUCH) $@ ||' $ruby = '#{Gem.ruby}' - create_makefile("#{@spec.name}") RUBY end @@ -1698,13 +1674,13 @@ end @spec.require_paths = ["."] @spec.extensions << "extconf.rb" - File.write File.join(@tempdir, "extconf.rb"), <<-RUBY - require "mkmf" - CONFIG['CC'] = '$(TOUCH) $@ ||' - CONFIG['LDSHARED'] = '$(TOUCH) $@ ||' - $ruby = '#{Gem.ruby}' - create_makefile("#{@spec.name}") - RUBY + write_dummy_extconf @spec.name do |io| + io.write <<~RUBY + CONFIG['CC'] = '$(TOUCH) $@ ||' + CONFIG['LDSHARED'] = '$(TOUCH) $@ ||' + $ruby = '#{Gem.ruby}' + RUBY + end # empty depend file for no auto dependencies @spec.files += %W[depend #{@spec.name}.c].each do |file| diff --git a/test/socket/test_socket.rb b/test/socket/test_socket.rb index 165990dd64..4b85d43291 100644 --- a/test/socket/test_socket.rb +++ b/test/socket/test_socket.rb @@ -937,6 +937,32 @@ class TestSocket < Test::Unit::TestCase RUBY end + def test_tcp_socket_open_timeout + opts = %w[-rsocket -W1] + assert_separately opts, <<~RUBY + Addrinfo.define_singleton_method(:getaddrinfo) do |_, _, family, *_| + if family == Socket::AF_INET6 + sleep + else + [Addrinfo.tcp("127.0.0.1", 12345)] + end + end + + assert_raise(Errno::ETIMEDOUT) do + Socket.tcp("localhost", 12345, open_timeout: 0.01) + end + RUBY + end + + def test_tcp_socket_open_timeout_with_other_timeouts + opts = %w[-rsocket -W1] + assert_separately opts, <<~RUBY + assert_raise(ArgumentError) do + Socket.tcp("localhost", 12345, open_timeout: 0.01, resolv_timout: 0.01) + end + RUBY + end + def test_tcp_socket_one_hostname_resolution_succeeded_at_least opts = %w[-rsocket -W1] assert_separately opts, <<~RUBY @@ -1540,6 +1540,29 @@ blocking_region_end(rb_thread_t *th, struct rb_blocking_region_buffer *region) #endif } +/* + * Resolve sentinel unblock function values to their actual function pointers + * and appropriate data2 values. This centralizes the logic for handling + * RUBY_UBF_IO and RUBY_UBF_PROCESS sentinel values. + * + * @param unblock_function Pointer to unblock function pointer (modified in place) + * @param data2 Pointer to data2 pointer (modified in place) + * @param thread Thread context for resolving data2 when needed + * @return true if sentinel values were resolved, false otherwise + */ +bool +rb_thread_resolve_unblock_function(rb_unblock_function_t **unblock_function, void **data2, struct rb_thread_struct *thread) +{ + rb_unblock_function_t *ubf = *unblock_function; + + if ((ubf == RUBY_UBF_IO) || (ubf == RUBY_UBF_PROCESS)) { + *unblock_function = ubf_select; + *data2 = thread; + return true; + } + return false; +} + void * rb_nogvl(void *(*func)(void *), void *data1, rb_unblock_function_t *ubf, void *data2, @@ -1566,11 +1589,9 @@ rb_nogvl(void *(*func)(void *), void *data1, bool is_main_thread = vm->ractor.main_thread == th; int saved_errno = 0; - if ((ubf == RUBY_UBF_IO) || (ubf == RUBY_UBF_PROCESS)) { - ubf = ubf_select; - data2 = th; - } - else if (ubf && rb_ractor_living_thread_num(th->ractor) == 1 && is_main_thread) { + rb_thread_resolve_unblock_function(&ubf, &data2, th); + + if (ubf && rb_ractor_living_thread_num(th->ractor) == 1 && is_main_thread) { if (flags & RB_NOGVL_UBF_ASYNC_SAFE) { vm->ubf_async_safe = 1; } @@ -6208,7 +6229,8 @@ threadptr_interrupt_exec_exec(rb_thread_t *th) if (task) { if (task->flags & rb_interrupt_exec_flag_new_thread) { rb_thread_create(task->func, task->data); - } else { + } + else { (*task->func)(task->data); } ruby_xfree(task); diff --git a/tool/auto-style.rb b/tool/auto-style.rb index d2b007bd51..0c6ce6848a 100644..100755 --- a/tool/auto-style.rb +++ b/tool/auto-style.rb @@ -69,7 +69,7 @@ class Git def git(*args) cmd = ['git', *args].shelljoin puts "+ #{cmd}" - unless with_clean_env { system(cmd) } + unless with_clean_env { system('git', *args) } abort "Failed to run: #{cmd}" end end @@ -173,6 +173,10 @@ IGNORED_FILES = [ %r{\Asample/trick[^/]*/}, ] +DIFFERENT_STYLE_FILES = %w[ + addr2line.c io_buffer.c prism*.c scheduler.c +] + oldrev, newrev, pushref = ARGV unless dry_run = pushref.empty? branch = IO.popen(['git', 'rev-parse', '--symbolic', '--abbrev-ref', pushref], &:read).strip @@ -194,7 +198,7 @@ if files.empty? exit end -trailing = eofnewline = expandtab = false +trailing = eofnewline = expandtab = indent = false edited_files = files.select do |f| src = File.binread(f) rescue next @@ -202,6 +206,8 @@ edited_files = files.select do |f| trailing0 = false expandtab0 = false + indent0 = false + src.gsub!(/^.*$/).with_index do |line, lineno| trailing = trailing0 = true if line.sub!(/[ \t]+$/, '') line @@ -225,7 +231,15 @@ edited_files = files.select do |f| end end - if trailing0 or eofnewline0 or expandtab0 + if File.fnmatch?("*.[ch]", f, File::FNM_PATHNAME) && + !DIFFERENT_STYLE_FILES.any? {|pat| File.fnmatch?(pat, f, File::FNM_PATHNAME)} + indent0 = true if src.gsub!(/^\w+\([^(\n)]*?\)\K[ \t]*(?=\{$)/, "\n") + indent0 = true if src.gsub!(/^([ \t]*)\}\K[ \t]*(?=else\b)/, "\n" '\1') + indent0 = true if src.gsub!(/^[ \t]*\}\n\K\n+(?=[ \t]*else\b)/, '') + indent ||= indent0 + end + + if trailing0 or eofnewline0 or expandtab0 or indent0 File.binwrite(f, src) true end @@ -236,6 +250,7 @@ else msg = [('remove trailing spaces' if trailing), ('append newline at EOF' if eofnewline), ('expand tabs' if expandtab), + ('adjust indents' if indent), ].compact message = "* #{msg.join(', ')}. [ci skip]" if expandtab diff --git a/tool/lib/_tmpdir.rb b/tool/lib/_tmpdir.rb index fd429dab37..daa1a1f235 100644 --- a/tool/lib/_tmpdir.rb +++ b/tool/lib/_tmpdir.rb @@ -4,11 +4,11 @@ template = "rubytest." # Assume the directory by these environment variables are safe. base = [ENV["TMPDIR"], ENV["TMP"], "/tmp"].find do |tmp| next unless tmp and tmp.size <= 50 and File.directory?(tmp) - # On macOS, the default TMPDIR is very long, inspite of UNIX socket - # path length is limited. + # On macOS, the default TMPDIR is very long, in spite of UNIX socket + # path length being limited. # # Also Rubygems creates its own temporary directory per tests, and - # some tests copy the full path of gemhome there. In that caes, the + # some tests copy the full path of gemhome there. In that case, the # path contains both temporary names twice, and can exceed path name # limit very easily. tmp diff --git a/tool/lib/core_assertions.rb b/tool/lib/core_assertions.rb index 1900b7088d..ece6ca1dc8 100644 --- a/tool/lib/core_assertions.rb +++ b/tool/lib/core_assertions.rb @@ -97,11 +97,12 @@ module Test end def assert_in_out_err(args, test_stdin = "", test_stdout = [], test_stderr = [], message = nil, - success: nil, **opt) + success: nil, failed: nil, **opt) args = Array(args).dup args.insert((Hash === args[0] ? 1 : 0), '--disable=gems') stdout, stderr, status = EnvUtil.invoke_ruby(args, test_stdin, true, true, **opt) - desc = FailDesc[status, message, stderr] + desc = failed[status, message, stderr] if failed + desc ||= FailDesc[status, message, stderr] if block_given? raise "test_stdout ignored, use block only or without block" if test_stdout != [] raise "test_stderr ignored, use block only or without block" if test_stderr != [] diff --git a/tool/lib/dump.gdb b/tool/lib/dump.gdb new file mode 100644 index 0000000000..56b420a546 --- /dev/null +++ b/tool/lib/dump.gdb @@ -0,0 +1,17 @@ +set height 0 +set width 0 +set confirm off + +echo \n>>> Threads\n\n +info threads + +echo \n>>> Machine level backtrace\n\n +thread apply all info stack full + +echo \n>>> Dump Ruby level backtrace (if possible)\n\n +call rb_vmdebug_stack_dump_all_threads() +call fflush(stderr) + +echo ">>> Finish\n" +detach +quit diff --git a/tool/lib/dump.lldb b/tool/lib/dump.lldb new file mode 100644 index 0000000000..ed9cb89010 --- /dev/null +++ b/tool/lib/dump.lldb @@ -0,0 +1,13 @@ +script print("\n>>> Threads\n\n") +thread list + +script print("\n>>> Machine level backtrace\n\n") +thread backtrace all + +script print("\n>>> Dump Ruby level backtrace (if possible)\n\n") +call rb_vmdebug_stack_dump_all_threads() +call fflush(stderr) + +script print(">>> Finish\n") +detach +quit diff --git a/tool/lib/envutil.rb b/tool/lib/envutil.rb index 65c86c1685..573fd5122c 100644 --- a/tool/lib/envutil.rb +++ b/tool/lib/envutil.rb @@ -79,6 +79,70 @@ module EnvUtil end module_function :timeout + class Debugger + @list = [] + + attr_accessor :name + + def self.register(name, &block) + @list << new(name, &block) + end + + def initialize(name, &block) + @name = name + instance_eval(&block) + end + + def usable?; false; end + + def start(pid, *args) end + + def dump(pid, timeout: 60, reprieve: timeout&.div(4)) + dpid = start(pid, *command_file(File.join(__dir__, "dump.#{name}"))) + rescue Errno::ENOENT + return + else + return unless dpid + [[timeout, :TERM], [reprieve, :KILL]].find do |t, sig| + return EnvUtil.timeout(t) {Process.wait(dpid)} + rescue Timeout::Error + Process.kill(sig, dpid) + end + true + end + + # sudo -n: --non-interactive + PRECOMMAND = (%[sudo -n] if /darwin/ =~ RUBY_PLATFORM) + + def spawn(*args, **opts) + super(*PRECOMMAND, *args, **opts) + end + + register("gdb") do + class << self + def usable?; system(*%w[gdb --batch --quiet --nx -ex exit]); end + def start(pid, *args) + spawn(*%w[gdb --batch --quiet --pid #{pid}], *args) + end + def command_file(file) "--command=#{file}"; end + end + end + + register("lldb") do + class << self + def usable?; system(*%w[lldb -Q --no-lldbinit -o exit]); end + def start(pid, *args) + spawn(*%w[lldb --batch -Q --attach-pid #{pid}]) + end + def command_file(file) ["--source", file]; end + end + end + + def self.search + @debugger ||= @list.find(&:usable?) + end + end + def terminate(pid, signal = :TERM, pgroup = nil, reprieve = 1) reprieve = apply_timeout_scale(reprieve) if reprieve @@ -94,17 +158,10 @@ module EnvUtil pgroup = pid end - lldb = true if /darwin/ =~ RUBY_PLATFORM - while signal = signals.shift - if lldb and [:ABRT, :KILL].include?(signal) - lldb = false - # sudo -n: --non-interactive - # lldb -p: attach - # -o: run command - system(*%W[sudo -n lldb -p #{pid} --batch -o bt\ all -o call\ rb_vmdebug_stack_dump_all_threads() -o quit]) - true + if (dbg = Debugger.search) and [:ABRT, :KILL].include?(signal) + dbg.dump(pid) end begin diff --git a/tool/lib/test/unit.rb b/tool/lib/test/unit.rb index 9ca29b6e64..7d43e825e1 100644 --- a/tool/lib/test/unit.rb +++ b/tool/lib/test/unit.rb @@ -421,6 +421,7 @@ module Test end def kill + EnvUtil::Debugger.search&.dump(@pid) signal = RUBY_PLATFORM =~ /mswin|mingw/ ? :KILL : :SEGV Process.kill(signal, @pid) warn "worker #{to_s} does not respond; #{signal} is sent" @@ -1298,10 +1299,15 @@ module Test parser.on '--repeat-count=NUM', "Number of times to repeat", Integer do |n| options[:repeat_count] = n end + options[:keep_repeating] = false + parser.on '--[no-]keep-repeating', "Keep repeating even failed" do |n| + options[:keep_repeating] = true + end end def _run_anything(type) @repeat_count = @options[:repeat_count] + @keep_repeating = @options[:keep_repeating] super end end @@ -1623,7 +1629,7 @@ module Test [(@repeat_count ? "(#{@@current_repeat_count}/#{@repeat_count}) " : ""), type, t, @test_count.fdiv(t), @assertion_count.fdiv(t)] end while @repeat_count && @@current_repeat_count < @repeat_count && - report.empty? && failures.zero? && errors.zero? + (@keep_repeating || report.empty? && failures.zero? && errors.zero?) output.sync = old_sync if sync diff --git a/tool/test/testunit/test_parallel.rb b/tool/test/testunit/test_parallel.rb index a0cbca69eb..d87e0ed327 100644 --- a/tool/test/testunit/test_parallel.rb +++ b/tool/test/testunit/test_parallel.rb @@ -126,19 +126,19 @@ module TestParallel assert_not_nil($1, "'done' was not found") result = Marshal.load($1.chomp.unpack1("m")) - assert_equal(5, result[0]) - pend "TODO: result[1] returns 17. We should investigate it" do # TODO: misusage of pend (pend doens't use given block) - assert_equal(12, result[1]) - end - assert_kind_of(Array,result[2]) - assert_kind_of(Array,result[3]) - assert_kind_of(Array,result[4]) - assert_kind_of(Array,result[2][1]) - assert_kind_of(Test::Unit::AssertionFailedError,result[2][0][2]) - assert_kind_of(Test::Unit::PendedError,result[2][1][2]) - assert_kind_of(Test::Unit::PendedError,result[2][2][2]) - assert_kind_of(Exception, result[2][3][2]) - assert_equal(result[5], "TestE") + tests, asserts, reports, failures, loadpaths, suite = result + assert_equal(5, tests) + assert_equal(12, asserts) + assert_kind_of(Array, reports) + assert_kind_of(Array, failures) + assert_kind_of(Array, loadpaths) + reports.sort_by! {|_, t| t} + assert_kind_of(Array, reports[1]) + assert_kind_of(Test::Unit::AssertionFailedError, reports[0][2]) + assert_kind_of(Test::Unit::PendedError, reports[1][2]) + assert_kind_of(Test::Unit::PendedError, reports[2][2]) + assert_kind_of(Exception, reports[3][2]) + assert_equal("TestE", suite) end end diff --git a/tool/test/testunit/tests_for_parallel/ptest_forth.rb b/tool/test/testunit/tests_for_parallel/ptest_forth.rb index 8831676e19..54474c828d 100644 --- a/tool/test/testunit/tests_for_parallel/ptest_forth.rb +++ b/tool/test/testunit/tests_for_parallel/ptest_forth.rb @@ -8,19 +8,19 @@ class TestE < Test::Unit::TestCase assert_equal(1,1) end - def test_always_skip - skip "always" + def test_always_omit + omit "always" end def test_always_fail assert_equal(0,1) end - def test_skip_after_unknown_error + def test_pend_after_unknown_error begin raise UnknownError, "unknown error" rescue - skip "after raise" + pend "after raise" end end diff --git a/variable.c b/variable.c index f2561c0dfa..e535aefe27 100644 --- a/variable.c +++ b/variable.c @@ -1197,8 +1197,31 @@ rb_generic_fields_tbl_get(void) return generic_fields_tbl_; } +static inline VALUE +generic_fields_lookup(VALUE obj, ID id, bool force_check_ractor) +{ + VALUE fields_obj = Qfalse; + RB_VM_LOCKING() { + st_table *generic_tbl = generic_fields_tbl(obj, id, false); + st_lookup(generic_tbl, obj, (st_data_t *)&fields_obj); + } + return fields_obj; +} + +static inline void +generic_fields_insert(VALUE obj, VALUE fields_obj) +{ + RUBY_ASSERT(IMEMO_TYPE_P(fields_obj, imemo_fields)); + + RB_VM_LOCKING() { + st_table *generic_tbl = generic_fields_tbl_no_ractor_check(obj); + st_insert(generic_tbl, obj, fields_obj); + } + RB_OBJ_WRITTEN(obj, Qundef, fields_obj); +} + int -rb_gen_fields_tbl_get(VALUE obj, ID id, struct gen_fields_tbl **fields_tbl) +rb_gen_fields_tbl_get(VALUE obj, ID id, VALUE *fields_obj) { RUBY_ASSERT(!RB_TYPE_P(obj, T_ICLASS)); @@ -1207,7 +1230,7 @@ rb_gen_fields_tbl_get(VALUE obj, ID id, struct gen_fields_tbl **fields_tbl) RB_VM_LOCKING() { if (st_lookup(generic_fields_tbl(obj, id, false), (st_data_t)obj, &data)) { - *fields_tbl = (struct gen_fields_tbl *)data; + *fields_obj = (VALUE)data; r = 1; } } @@ -1216,39 +1239,17 @@ rb_gen_fields_tbl_get(VALUE obj, ID id, struct gen_fields_tbl **fields_tbl) } int -rb_ivar_generic_fields_tbl_lookup(VALUE obj, struct gen_fields_tbl **fields_tbl) -{ - return rb_gen_fields_tbl_get(obj, 0, fields_tbl); -} - -static size_t -gen_fields_tbl_bytes(size_t n) -{ - return offsetof(struct gen_fields_tbl, as.shape.fields) + n * sizeof(VALUE); -} - -static struct gen_fields_tbl * -gen_fields_tbl_resize(struct gen_fields_tbl *old, uint32_t new_capa) +rb_ivar_generic_fields_tbl_lookup(VALUE obj, VALUE *fields_obj) { - RUBY_ASSERT(new_capa > 0); - return xrealloc(old, gen_fields_tbl_bytes(new_capa)); + return rb_gen_fields_tbl_get(obj, 0, fields_obj); } void rb_mark_generic_ivar(VALUE obj) { - st_data_t data; - if (st_lookup(generic_fields_tbl_no_ractor_check(obj), (st_data_t)obj, &data)) { - struct gen_fields_tbl *fields_tbl = (struct gen_fields_tbl *)data; - if (rb_shape_obj_too_complex_p(obj)) { - rb_mark_tbl_no_pin(fields_tbl->as.complex.table); - } - else { - uint32_t fields_count = RSHAPE_LEN(RBASIC_SHAPE_ID(obj)); - for (uint32_t i = 0; i < fields_count; i++) { - rb_gc_mark_movable(fields_tbl->as.shape.fields[i]); - } - } + VALUE data; + if (st_lookup(generic_fields_tbl_no_ractor_check(obj), (st_data_t)obj, (st_data_t *)&data)) { + rb_gc_mark_movable(data); } } @@ -1258,49 +1259,10 @@ rb_free_generic_ivar(VALUE obj) if (rb_obj_exivar_p(obj)) { st_data_t key = (st_data_t)obj, value; - bool too_complex = rb_shape_obj_too_complex_p(obj); - RB_VM_LOCKING() { - if (st_delete(generic_fields_tbl_no_ractor_check(obj), &key, &value)) { - struct gen_fields_tbl *fields_tbl = (struct gen_fields_tbl *)value; - - if (UNLIKELY(too_complex)) { - st_free_table(fields_tbl->as.complex.table); - } - - xfree(fields_tbl); - } - } - FL_UNSET_RAW(obj, FL_EXIVAR); - RBASIC_SET_SHAPE_ID(obj, ROOT_SHAPE_ID); - } -} - -size_t -rb_generic_ivar_memsize(VALUE obj) -{ - struct gen_fields_tbl *fields_tbl; - - if (rb_gen_fields_tbl_get(obj, 0, &fields_tbl)) { - if (rb_shape_obj_too_complex_p(obj)) { - return sizeof(struct gen_fields_tbl) + st_memsize(fields_tbl->as.complex.table); - } - else { - return gen_fields_tbl_bytes(RSHAPE_CAPACITY(RBASIC_SHAPE_ID(obj))); + st_delete(generic_fields_tbl_no_ractor_check(obj), &key, &value); } } - return 0; -} - -static size_t -gen_fields_tbl_count(VALUE obj, const struct gen_fields_tbl *fields_tbl) -{ - if (rb_shape_obj_too_complex_p(obj)) { - return st_table_size(fields_tbl->as.complex.table); - } - else { - return RSHAPE_LEN(RBASIC_SHAPE_ID(obj)); - } } VALUE @@ -1328,13 +1290,16 @@ rb_obj_field_get(VALUE obj, shape_id_t target_shape_id) case T_OBJECT: fields_hash = ROBJECT_FIELDS_HASH(obj); break; + case T_IMEMO: + RUBY_ASSERT(IMEMO_TYPE_P(obj, imemo_fields)); + fields_hash = rb_imemo_fields_complex_tbl(obj); + break; default: - RUBY_ASSERT(FL_TEST_RAW(obj, FL_EXIVAR)); RUBY_ASSERT(rb_obj_exivar_p(obj)); - struct gen_fields_tbl *fields_tbl = NULL; - rb_ivar_generic_fields_tbl_lookup(obj, &fields_tbl); - RUBY_ASSERT(fields_tbl); - fields_hash = fields_tbl->as.complex.table; + VALUE fields_obj = 0; + rb_ivar_generic_fields_tbl_lookup(obj, &fields_obj); + RUBY_ASSERT(fields_obj); + fields_hash = rb_imemo_fields_complex_tbl(fields_obj); break; } VALUE value = Qundef; @@ -1360,13 +1325,16 @@ rb_obj_field_get(VALUE obj, shape_id_t target_shape_id) case T_OBJECT: fields = ROBJECT_FIELDS(obj); break; + case T_IMEMO: + RUBY_ASSERT(IMEMO_TYPE_P(obj, imemo_fields)); + fields = rb_imemo_fields_ptr(obj); + break; default: - RUBY_ASSERT(FL_TEST_RAW(obj, FL_EXIVAR)); RUBY_ASSERT(rb_obj_exivar_p(obj)); - struct gen_fields_tbl *fields_tbl = NULL; - rb_ivar_generic_fields_tbl_lookup(obj, &fields_tbl); - RUBY_ASSERT(fields_tbl); - fields = fields_tbl->as.shape.fields; + VALUE fields_obj = 0; + rb_ivar_generic_fields_tbl_lookup(obj, &fields_obj); + RUBY_ASSERT(fields_obj); + fields = rb_imemo_fields_ptr(fields_obj); break; } return fields[attr_index]; @@ -1402,11 +1370,11 @@ rb_ivar_lookup(VALUE obj, ID id, VALUE undef) case T_IMEMO: // Handled like T_OBJECT { - RUBY_ASSERT(IMEMO_TYPE_P(obj, imemo_class_fields)); + RUBY_ASSERT(IMEMO_TYPE_P(obj, imemo_fields)); shape_id = RBASIC_SHAPE_ID(obj); if (rb_shape_too_complex_p(shape_id)) { - st_table *iv_table = rb_imemo_class_fields_complex_tbl(obj); + st_table *iv_table = rb_imemo_fields_complex_tbl(obj); VALUE val; if (rb_st_lookup(iv_table, (st_data_t)id, (st_data_t *)&val)) { return val; @@ -1417,7 +1385,7 @@ rb_ivar_lookup(VALUE obj, ID id, VALUE undef) } RUBY_ASSERT(!rb_shape_obj_too_complex_p(obj)); - ivar_list = rb_imemo_class_fields_ptr(obj); + ivar_list = rb_imemo_fields_ptr(obj); break; } case T_OBJECT: @@ -1441,19 +1409,21 @@ rb_ivar_lookup(VALUE obj, ID id, VALUE undef) default: shape_id = RBASIC_SHAPE_ID(obj); if (rb_obj_exivar_p(obj)) { - struct gen_fields_tbl *fields_tbl; - rb_gen_fields_tbl_get(obj, id, &fields_tbl); + VALUE fields_obj = 0; + rb_gen_fields_tbl_get(obj, id, &fields_obj); - if (rb_shape_obj_too_complex_p(obj)) { + RUBY_ASSERT(fields_obj); + + if (rb_shape_obj_too_complex_p(fields_obj)) { VALUE val; - if (rb_st_lookup(fields_tbl->as.complex.table, (st_data_t)id, (st_data_t *)&val)) { + if (rb_st_lookup(rb_imemo_fields_complex_tbl(fields_obj), (st_data_t)id, (st_data_t *)&val)) { return val; } else { return undef; } } - ivar_list = fields_tbl->as.shape.fields; + ivar_list = rb_imemo_fields_ptr(fields_obj); } else { return undef; @@ -1495,7 +1465,7 @@ rb_ivar_delete(VALUE obj, ID id, VALUE undef) VALUE fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj); if (fields_obj) { if (rb_multi_ractor_p()) { - fields_obj = rb_imemo_class_fields_clone(fields_obj); + fields_obj = rb_imemo_fields_clone(fields_obj); val = rb_ivar_delete(fields_obj, id, undef); RCLASS_WRITABLE_SET_FIELDS_OBJ(obj, fields_obj); } @@ -1532,16 +1502,16 @@ rb_ivar_delete(VALUE obj, ID id, VALUE undef) rb_bug("Unreachable"); break; case T_IMEMO: - RUBY_ASSERT(IMEMO_TYPE_P(obj, imemo_class_fields)); - fields = rb_imemo_class_fields_ptr(obj); + RUBY_ASSERT(IMEMO_TYPE_P(obj, imemo_fields)); + fields = rb_imemo_fields_ptr(obj); break; case T_OBJECT: fields = ROBJECT_FIELDS(obj); break; default: { - struct gen_fields_tbl *fields_tbl; - rb_gen_fields_tbl_get(obj, id, &fields_tbl); - fields = fields_tbl->as.shape.fields; + VALUE fields_obj; + rb_gen_fields_tbl_get(obj, id, &fields_obj); + fields = rb_imemo_fields_ptr(fields_obj); break; } } @@ -1585,8 +1555,8 @@ too_complex: break; case T_IMEMO: - RUBY_ASSERT(IMEMO_TYPE_P(obj, imemo_class_fields)); - table = rb_imemo_class_fields_complex_tbl(obj); + RUBY_ASSERT(IMEMO_TYPE_P(obj, imemo_fields)); + table = rb_imemo_fields_complex_tbl(obj); break; case T_OBJECT: @@ -1594,9 +1564,9 @@ too_complex: break; default: { - struct gen_fields_tbl *fields_tbl; - if (rb_gen_fields_tbl_get(obj, 0, &fields_tbl)) { - table = fields_tbl->as.complex.table; + VALUE fields_obj; + if (rb_gen_fields_tbl_get(obj, 0, &fields_obj)) { + table = rb_imemo_fields_complex_tbl(fields_obj); } break; } @@ -1618,6 +1588,8 @@ rb_attr_delete(VALUE obj, ID id) return rb_ivar_delete(obj, id, Qnil); } +static inline void generic_update_fields_obj(VALUE obj, VALUE fields_obj, const VALUE original_fields_obj); + static shape_id_t obj_transition_too_complex(VALUE obj, st_table *table) { @@ -1628,46 +1600,37 @@ obj_transition_too_complex(VALUE obj, st_table *table) RUBY_ASSERT(!rb_shape_obj_too_complex_p(obj)); shape_id_t shape_id = rb_shape_transition_complex(obj); - VALUE *old_fields = NULL; - switch (BUILTIN_TYPE(obj)) { case T_OBJECT: - if (!(RBASIC(obj)->flags & ROBJECT_EMBED)) { - old_fields = ROBJECT_FIELDS(obj); + { + VALUE *old_fields = NULL; + if (!(RBASIC(obj)->flags & ROBJECT_EMBED)) { + old_fields = ROBJECT_FIELDS(obj); + } + RBASIC_SET_SHAPE_ID(obj, shape_id); + ROBJECT_SET_FIELDS_HASH(obj, table); + if (old_fields) { + xfree(old_fields); + } } - RBASIC_SET_SHAPE_ID(obj, shape_id); - ROBJECT_SET_FIELDS_HASH(obj, table); break; case T_CLASS: case T_MODULE: rb_bug("Unreachable"); break; default: - RB_VM_LOCKING() { - struct st_table *gen_ivs = generic_fields_tbl_no_ractor_check(obj); - - struct gen_fields_tbl *old_fields_tbl = NULL; - st_lookup(gen_ivs, (st_data_t)obj, (st_data_t *)&old_fields_tbl); - - if (old_fields_tbl) { - /* We need to modify old_fields_tbl to have the too complex shape - * and hold the table because the xmalloc could trigger a GC - * compaction. We want the table to be updated rather than - * the original fields. */ - rb_obj_set_shape_id(obj, shape_id); - old_fields_tbl->as.complex.table = table; - old_fields = (VALUE *)old_fields_tbl; - } - - struct gen_fields_tbl *fields_tbl = xmalloc(sizeof(struct gen_fields_tbl)); - fields_tbl->as.complex.table = table; - st_insert(gen_ivs, (st_data_t)obj, (st_data_t)fields_tbl); + { + VALUE fields_obj = rb_imemo_fields_new_complex_tbl(rb_obj_class(obj), table); + RBASIC_SET_SHAPE_ID(fields_obj, shape_id); + RB_VM_LOCKING() { + const VALUE original_fields_obj = generic_fields_lookup(obj, 0, false); + generic_update_fields_obj(obj, fields_obj, original_fields_obj); + } RBASIC_SET_SHAPE_ID(obj, shape_id); } } - xfree(old_fields); return shape_id; } @@ -1682,12 +1645,12 @@ rb_obj_init_too_complex(VALUE obj, st_table *table) obj_transition_too_complex(obj, table); } +void rb_obj_copy_fields_to_hash_table(VALUE obj, st_table *table); + // Copy all object fields, including ivars and internal object_id, etc shape_id_t rb_evict_fields_to_hash(VALUE obj) { - void rb_obj_copy_fields_to_hash_table(VALUE obj, st_table *table); - RUBY_ASSERT(!rb_shape_obj_too_complex_p(obj)); st_table *table = st_init_numtable_with_size(RSHAPE_LEN(RBASIC_SHAPE_ID(obj))); @@ -1818,139 +1781,174 @@ general_field_set(VALUE obj, shape_id_t target_shape_id, VALUE val, void *data, } } -struct gen_fields_lookup_ensure_size { - VALUE obj; - ID id; - struct gen_fields_tbl *fields_tbl; - shape_id_t shape_id; - bool resize; -}; +static inline void +generic_update_fields_obj(VALUE obj, VALUE fields_obj, const VALUE original_fields_obj) +{ + if (fields_obj != original_fields_obj) { + if (original_fields_obj) { + // Clear root shape to avoid triggering cleanup such as free_object_id. + rb_imemo_fields_clear(original_fields_obj); + } -static int -generic_fields_lookup_ensure_size(st_data_t *k, st_data_t *v, st_data_t u, int existing) + generic_fields_insert(obj, fields_obj); + } +} + +static void +generic_ivar_set(VALUE obj, ID id, VALUE val) { - ASSERT_vm_locking(); + bool existing = true; + + VALUE fields_obj = generic_fields_lookup(obj, id, false); + + const VALUE original_fields_obj = fields_obj; + if (!fields_obj) { + fields_obj = rb_imemo_fields_new(rb_obj_class(obj), 1); + } + RUBY_ASSERT(RBASIC_SHAPE_ID(obj) == RBASIC_SHAPE_ID(fields_obj)); - struct gen_fields_lookup_ensure_size *fields_lookup = (struct gen_fields_lookup_ensure_size *)u; - struct gen_fields_tbl *fields_tbl = existing ? (struct gen_fields_tbl *)*v : NULL; + shape_id_t current_shape_id = RBASIC_SHAPE_ID(fields_obj); + shape_id_t next_shape_id = current_shape_id; + + if (UNLIKELY(rb_shape_too_complex_p(current_shape_id))) { + goto too_complex; + } - if (!existing || fields_lookup->resize) { - if (existing) { - RUBY_ASSERT(RSHAPE_TYPE_P(fields_lookup->shape_id, SHAPE_IVAR) || RSHAPE_TYPE_P(fields_lookup->shape_id, SHAPE_OBJ_ID)); - RUBY_ASSERT(RSHAPE_CAPACITY(RSHAPE_PARENT(fields_lookup->shape_id)) < RSHAPE_CAPACITY(fields_lookup->shape_id)); + attr_index_t index; + if (!rb_shape_get_iv_index(current_shape_id, id, &index)) { + existing = false; + + index = RSHAPE_LEN(current_shape_id); + if (index >= SHAPE_MAX_FIELDS) { + rb_raise(rb_eArgError, "too many instance variables"); } - else { - FL_SET_RAW((VALUE)*k, FL_EXIVAR); + + next_shape_id = rb_shape_transition_add_ivar(fields_obj, id); + if (UNLIKELY(rb_shape_too_complex_p(next_shape_id))) { + attr_index_t current_len = RSHAPE_LEN(current_shape_id); + fields_obj = rb_imemo_fields_new_complex(rb_obj_class(obj), current_len + 1); + if (current_len) { + rb_obj_copy_fields_to_hash_table(original_fields_obj, rb_imemo_fields_complex_tbl(fields_obj)); + } + RBASIC_SET_SHAPE_ID(fields_obj, next_shape_id); + goto too_complex; } - fields_tbl = gen_fields_tbl_resize(fields_tbl, RSHAPE_CAPACITY(fields_lookup->shape_id)); - *v = (st_data_t)fields_tbl; + attr_index_t next_capacity = RSHAPE_CAPACITY(next_shape_id); + attr_index_t current_capacity = RSHAPE_CAPACITY(current_shape_id); + + if (next_capacity != current_capacity) { + RUBY_ASSERT(next_capacity > current_capacity); + + fields_obj = rb_imemo_fields_new(rb_obj_class(obj), next_capacity); + if (original_fields_obj) { + attr_index_t fields_count = RSHAPE_LEN(current_shape_id); + VALUE *fields = rb_imemo_fields_ptr(fields_obj); + MEMCPY(fields, rb_imemo_fields_ptr(original_fields_obj), VALUE, fields_count); + for (attr_index_t i = 0; i < fields_count; i++) { + RB_OBJ_WRITTEN(fields_obj, Qundef, fields[i]); + } + } + } + + RUBY_ASSERT(RSHAPE(next_shape_id)->type == SHAPE_IVAR); + RUBY_ASSERT(index == (RSHAPE_LEN(next_shape_id) - 1)); } - fields_lookup->fields_tbl = fields_tbl; - if (fields_lookup->shape_id) { - rb_obj_set_shape_id(fields_lookup->obj, fields_lookup->shape_id); + VALUE *fields = rb_imemo_fields_ptr(fields_obj); + RB_OBJ_WRITE(fields_obj, &fields[index], val); + + if (!existing) { + RBASIC_SET_SHAPE_ID(fields_obj, next_shape_id); } - RUBY_ASSERT(FL_TEST((VALUE)*k, FL_EXIVAR)); - RUBY_ASSERT(rb_obj_exivar_p((VALUE)*k)); + generic_update_fields_obj(obj, fields_obj, original_fields_obj); - return ST_CONTINUE; -} + if (!existing) { + RBASIC_SET_SHAPE_ID(obj, next_shape_id); + } -static VALUE * -generic_ivar_set_shape_fields(VALUE obj, void *data) -{ - RUBY_ASSERT(!rb_shape_obj_too_complex_p(obj)); + RUBY_ASSERT(RBASIC_SHAPE_ID(obj) == RBASIC_SHAPE_ID(fields_obj)); - struct gen_fields_lookup_ensure_size *fields_lookup = data; + return; - RB_VM_LOCKING() { - st_update(generic_fields_tbl(obj, fields_lookup->id, false), (st_data_t)obj, generic_fields_lookup_ensure_size, (st_data_t)fields_lookup); - } +too_complex: + { + st_table *table = rb_imemo_fields_complex_tbl(fields_obj); + existing = st_insert(table, (st_data_t)id, (st_data_t)val); + RB_OBJ_WRITTEN(fields_obj, Qundef, val); - FL_SET_RAW(obj, FL_EXIVAR); + generic_update_fields_obj(obj, fields_obj, original_fields_obj); - return fields_lookup->fields_tbl->as.shape.fields; -} + if (!existing) { + RBASIC_SET_SHAPE_ID(obj, next_shape_id); + } + } -static void -generic_ivar_set_shape_resize_fields(VALUE obj, attr_index_t _old_capa, attr_index_t new_capa, void *data) -{ - struct gen_fields_lookup_ensure_size *fields_lookup = data; + RUBY_ASSERT(RBASIC_SHAPE_ID(obj) == RBASIC_SHAPE_ID(fields_obj)); - fields_lookup->resize = true; + return; } static void -generic_ivar_set_set_shape_id(VALUE obj, shape_id_t shape_id, void *data) +generic_field_set(VALUE obj, shape_id_t target_shape_id, VALUE val) { - struct gen_fields_lookup_ensure_size *fields_lookup = data; - - fields_lookup->shape_id = shape_id; -} + bool existing = true; -static shape_id_t -generic_ivar_set_transition_too_complex(VALUE obj, void *_data) -{ - FL_SET_RAW(obj, FL_EXIVAR); - shape_id_t new_shape_id = rb_evict_fields_to_hash(obj); - return new_shape_id; -} + VALUE fields_obj = generic_fields_lookup(obj, RSHAPE_EDGE_NAME(target_shape_id), false); + const VALUE original_fields_obj = fields_obj; -static st_table * -generic_ivar_set_too_complex_table(VALUE obj, void *data) -{ - struct gen_fields_lookup_ensure_size *fields_lookup = data; + shape_id_t current_shape_id = fields_obj ? RBASIC_SHAPE_ID(fields_obj) : ROOT_SHAPE_ID; - struct gen_fields_tbl *fields_tbl; - if (!rb_gen_fields_tbl_get(obj, 0, &fields_tbl)) { - fields_tbl = xmalloc(sizeof(struct gen_fields_tbl)); - fields_tbl->as.complex.table = st_init_numtable_with_size(1); + if (UNLIKELY(rb_shape_too_complex_p(target_shape_id))) { + if (UNLIKELY(!rb_shape_too_complex_p(current_shape_id))) { + attr_index_t current_len = RSHAPE_LEN(current_shape_id); + fields_obj = rb_imemo_fields_new_complex(rb_obj_class(obj), current_len + 1); + if (current_len) { + rb_obj_copy_fields_to_hash_table(original_fields_obj, rb_imemo_fields_complex_tbl(fields_obj)); + } - RB_VM_LOCKING() { - st_insert(generic_fields_tbl(obj, fields_lookup->id, false), (st_data_t)obj, (st_data_t)fields_tbl); + current_shape_id = target_shape_id; } - FL_SET_RAW(obj, FL_EXIVAR); - } + existing = false; + st_table *table = rb_imemo_fields_complex_tbl(fields_obj); - RUBY_ASSERT(rb_shape_obj_too_complex_p(obj)); + RUBY_ASSERT(RSHAPE_EDGE_NAME(target_shape_id)); + st_insert(table, (st_data_t)RSHAPE_EDGE_NAME(target_shape_id), (st_data_t)val); + RB_OBJ_WRITTEN(fields_obj, Qundef, val); + RBASIC_SET_SHAPE_ID(fields_obj, target_shape_id); + } + else { + attr_index_t index = RSHAPE_INDEX(target_shape_id); + if (index >= RSHAPE_CAPACITY(current_shape_id)) { + fields_obj = rb_imemo_fields_new(rb_obj_class(obj), index); + if (original_fields_obj) { + attr_index_t fields_count = RSHAPE_LEN(current_shape_id); + VALUE *fields = rb_imemo_fields_ptr(fields_obj); + MEMCPY(fields, rb_imemo_fields_ptr(original_fields_obj), VALUE, fields_count); + for (attr_index_t i = 0; i < fields_count; i++) { + RB_OBJ_WRITTEN(fields_obj, Qundef, fields[i]); + } + } + } - return fields_tbl->as.complex.table; -} + VALUE *table = rb_imemo_fields_ptr(fields_obj); + RB_OBJ_WRITE(fields_obj, &table[index], val); -static void -generic_ivar_set(VALUE obj, ID id, VALUE val) -{ - struct gen_fields_lookup_ensure_size fields_lookup = { - .obj = obj, - .id = id, - .resize = false, - }; + if (RSHAPE_LEN(target_shape_id) > RSHAPE_LEN(current_shape_id)) { + existing = false; + RBASIC_SET_SHAPE_ID(fields_obj, target_shape_id); + } + } - general_ivar_set(obj, id, val, &fields_lookup, - generic_ivar_set_shape_fields, - generic_ivar_set_shape_resize_fields, - generic_ivar_set_set_shape_id, - generic_ivar_set_transition_too_complex, - generic_ivar_set_too_complex_table); -} + generic_update_fields_obj(obj, fields_obj, original_fields_obj); -static void -generic_field_set(VALUE obj, shape_id_t target_shape_id, VALUE val) -{ - struct gen_fields_lookup_ensure_size fields_lookup = { - .obj = obj, - .resize = false, - }; + if (!existing) { + RBASIC_SET_SHAPE_ID(obj, target_shape_id); + } - general_field_set(obj, target_shape_id, val, &fields_lookup, - generic_ivar_set_shape_fields, - generic_ivar_set_shape_resize_fields, - generic_ivar_set_set_shape_id, - generic_ivar_set_transition_too_complex, - generic_ivar_set_too_complex_table); + RUBY_ASSERT(RBASIC_SHAPE_ID(obj) == RBASIC_SHAPE_ID(fields_obj)); } void @@ -2169,8 +2167,8 @@ ivar_defined0(VALUE obj, ID id) break; case T_IMEMO: - RUBY_ASSERT(IMEMO_TYPE_P(obj, imemo_class_fields)); - table = rb_imemo_class_fields_complex_tbl(obj); + RUBY_ASSERT(IMEMO_TYPE_P(obj, imemo_fields)); + table = rb_imemo_fields_complex_tbl(obj); break; case T_OBJECT: @@ -2178,11 +2176,10 @@ ivar_defined0(VALUE obj, ID id) break; default: { - struct gen_fields_tbl *fields_tbl; - if (rb_gen_fields_tbl_get(obj, 0, &fields_tbl)) { - table = fields_tbl->as.complex.table; + VALUE fields_obj; + if (rb_gen_fields_tbl_get(obj, 0, &fields_obj)) { + table = rb_imemo_fields_complex_tbl(fields_obj); } - break; } } @@ -2238,27 +2235,23 @@ iterate_over_shapes_callback(shape_id_t shape_id, void *data) return ST_CONTINUE; } - VALUE *iv_list; + VALUE *fields; switch (BUILTIN_TYPE(itr_data->obj)) { case T_OBJECT: RUBY_ASSERT(!rb_shape_obj_too_complex_p(itr_data->obj)); - iv_list = ROBJECT_FIELDS(itr_data->obj); + fields = ROBJECT_FIELDS(itr_data->obj); break; - case T_CLASS: - case T_MODULE: - rb_bug("Unreachable"); case T_IMEMO: - RUBY_ASSERT(IMEMO_TYPE_P(itr_data->obj, imemo_class_fields)); + RUBY_ASSERT(IMEMO_TYPE_P(itr_data->obj, imemo_fields)); RUBY_ASSERT(!rb_shape_obj_too_complex_p(itr_data->obj)); - iv_list = rb_imemo_class_fields_ptr(itr_data->obj); + fields = rb_imemo_fields_ptr(itr_data->obj); break; default: - iv_list = itr_data->fields_tbl->as.shape.fields; - break; + rb_bug("Unreachable"); } - VALUE val = iv_list[RSHAPE_INDEX(shape_id)]; + VALUE val = fields[RSHAPE_INDEX(shape_id)]; return itr_data->func(RSHAPE_EDGE_NAME(shape_id), val, itr_data->arg); } @@ -2300,33 +2293,9 @@ obj_fields_each(VALUE obj, rb_ivar_foreach_callback_func *func, st_data_t arg, b } static void -gen_fields_each(VALUE obj, rb_ivar_foreach_callback_func *func, st_data_t arg, bool ivar_only) -{ - struct gen_fields_tbl *fields_tbl; - if (!rb_gen_fields_tbl_get(obj, 0, &fields_tbl)) return; - - struct iv_itr_data itr_data = { - .obj = obj, - .fields_tbl = fields_tbl, - .arg = arg, - .func = func, - .ivar_only = ivar_only, - }; - - shape_id_t shape_id = RBASIC_SHAPE_ID(obj); - if (rb_shape_too_complex_p(shape_id)) { - rb_st_foreach(fields_tbl->as.complex.table, each_hash_iv, (st_data_t)&itr_data); - } - else { - itr_data.fields = fields_tbl->as.shape.fields; - iterate_over_shapes(shape_id, func, &itr_data); - } -} - -static void -class_fields_each(VALUE fields_obj, rb_ivar_foreach_callback_func *func, st_data_t arg, bool ivar_only) +imemo_fields_each(VALUE fields_obj, rb_ivar_foreach_callback_func *func, st_data_t arg, bool ivar_only) { - IMEMO_TYPE_P(fields_obj, imemo_class_fields); + IMEMO_TYPE_P(fields_obj, imemo_fields); struct iv_itr_data itr_data = { .obj = fields_obj, @@ -2337,10 +2306,10 @@ class_fields_each(VALUE fields_obj, rb_ivar_foreach_callback_func *func, st_data shape_id_t shape_id = RBASIC_SHAPE_ID(fields_obj); if (rb_shape_too_complex_p(shape_id)) { - rb_st_foreach(rb_imemo_class_fields_complex_tbl(fields_obj), each_hash_iv, (st_data_t)&itr_data); + rb_st_foreach(rb_imemo_fields_complex_tbl(fields_obj), each_hash_iv, (st_data_t)&itr_data); } else { - itr_data.fields = rb_imemo_class_fields_ptr(fields_obj); + itr_data.fields = rb_imemo_fields_ptr(fields_obj); iterate_over_shapes(shape_id, func, &itr_data); } } @@ -2348,8 +2317,8 @@ class_fields_each(VALUE fields_obj, rb_ivar_foreach_callback_func *func, st_data void rb_copy_generic_ivar(VALUE dest, VALUE obj) { - struct gen_fields_tbl *obj_fields_tbl; - struct gen_fields_tbl *new_fields_tbl; + VALUE fields_obj; + VALUE new_fields_obj; rb_check_frozen(dest); @@ -2357,21 +2326,16 @@ rb_copy_generic_ivar(VALUE dest, VALUE obj) return; } - unsigned long src_num_ivs = rb_ivar_count(obj); - if (!src_num_ivs) { - goto clear; - } - shape_id_t src_shape_id = rb_obj_shape_id(obj); - if (rb_gen_fields_tbl_get(obj, 0, &obj_fields_tbl)) { - if (gen_fields_tbl_count(obj, obj_fields_tbl) == 0) + if (rb_gen_fields_tbl_get(obj, 0, &fields_obj)) { + unsigned long src_num_ivs = rb_ivar_count(fields_obj); + if (!src_num_ivs) { goto clear; - - FL_SET(dest, FL_EXIVAR); + } if (rb_shape_too_complex_p(src_shape_id)) { - rb_shape_copy_complex_ivars(dest, obj, src_shape_id, obj_fields_tbl->as.complex.table); + rb_shape_copy_complex_ivars(dest, obj, src_shape_id, rb_imemo_fields_complex_tbl(fields_obj)); return; } @@ -2386,34 +2350,28 @@ rb_copy_generic_ivar(VALUE dest, VALUE obj) st_table *table = rb_st_init_numtable_with_size(src_num_ivs); rb_obj_copy_ivs_to_hash_table(obj, table); rb_obj_init_too_complex(dest, table); - return; } } if (!RSHAPE_LEN(dest_shape_id)) { rb_obj_set_shape_id(dest, dest_shape_id); - FL_UNSET(dest, FL_EXIVAR); return; } - new_fields_tbl = gen_fields_tbl_resize(0, RSHAPE_CAPACITY(dest_shape_id)); - - VALUE *src_buf = obj_fields_tbl->as.shape.fields; - VALUE *dest_buf = new_fields_tbl->as.shape.fields; - + new_fields_obj = rb_imemo_fields_new(rb_obj_class(dest), RSHAPE_CAPACITY(dest_shape_id)); + VALUE *src_buf = rb_imemo_fields_ptr(fields_obj); + VALUE *dest_buf = rb_imemo_fields_ptr(new_fields_obj); rb_shape_copy_fields(dest, dest_buf, dest_shape_id, obj, src_buf, src_shape_id); + RBASIC_SET_SHAPE_ID(new_fields_obj, dest_shape_id); - /* - * c.fields_tbl may change in gen_fields_copy due to realloc, - * no need to free - */ RB_VM_LOCKING() { generic_fields_tbl_no_ractor_check(dest); - st_insert(generic_fields_tbl_no_ractor_check(obj), (st_data_t)dest, (st_data_t)new_fields_tbl); + st_insert(generic_fields_tbl_no_ractor_check(obj), (st_data_t)dest, (st_data_t)new_fields_obj); + RB_OBJ_WRITTEN(dest, Qundef, new_fields_obj); } - rb_obj_set_shape_id(dest, dest_shape_id); + RBASIC_SET_SHAPE_ID(dest, dest_shape_id); } return; @@ -2424,15 +2382,10 @@ rb_copy_generic_ivar(VALUE dest, VALUE obj) void rb_replace_generic_ivar(VALUE clone, VALUE obj) { - RUBY_ASSERT(FL_TEST(obj, FL_EXIVAR)); - RB_VM_LOCKING() { st_data_t fields_tbl, obj_data = (st_data_t)obj; if (st_delete(generic_fields_tbl_, &obj_data, &fields_tbl)) { - FL_UNSET_RAW(obj, FL_EXIVAR); - st_insert(generic_fields_tbl_, (st_data_t)clone, fields_tbl); - FL_SET_RAW(clone, FL_EXIVAR); } else { rb_bug("unreachable"); @@ -2446,8 +2399,8 @@ rb_field_foreach(VALUE obj, rb_ivar_foreach_callback_func *func, st_data_t arg, if (SPECIAL_CONST_P(obj)) return; switch (BUILTIN_TYPE(obj)) { case T_IMEMO: - if (IMEMO_TYPE_P(obj, imemo_class_fields)) { - class_fields_each(obj, func, arg, ivar_only); + if (IMEMO_TYPE_P(obj, imemo_fields)) { + imemo_fields_each(obj, func, arg, ivar_only); } break; case T_OBJECT: @@ -2459,13 +2412,16 @@ rb_field_foreach(VALUE obj, rb_ivar_foreach_callback_func *func, st_data_t arg, IVAR_ACCESSOR_SHOULD_BE_MAIN_RACTOR(0); VALUE fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj); if (fields_obj) { - class_fields_each(fields_obj, func, arg, ivar_only); + imemo_fields_each(fields_obj, func, arg, ivar_only); } } break; default: if (rb_obj_exivar_p(obj)) { - gen_fields_each(obj, func, arg, ivar_only); + VALUE fields_obj = 0; + if (!rb_gen_fields_tbl_get(obj, 0, &fields_obj)) return; + + imemo_fields_each(fields_obj, func, arg, ivar_only); } break; } @@ -2487,6 +2443,7 @@ rb_ivar_count(VALUE obj) case T_OBJECT: iv_count = ROBJECT_FIELDS_COUNT(obj); break; + case T_CLASS: case T_MODULE: { @@ -2495,16 +2452,37 @@ rb_ivar_count(VALUE obj) return 0; } if (rb_shape_obj_too_complex_p(fields_obj)) { - return rb_st_table_size(rb_imemo_class_fields_complex_tbl(fields_obj)); + iv_count = rb_st_table_size(rb_imemo_fields_complex_tbl(fields_obj)); + } + else { + iv_count = RBASIC_FIELDS_COUNT(fields_obj); } - return RBASIC_FIELDS_COUNT(fields_obj); } + break; + + case T_IMEMO: + RUBY_ASSERT(IMEMO_TYPE_P(obj, imemo_fields)); + + if (rb_shape_obj_too_complex_p(obj)) { + iv_count = rb_st_table_size(rb_imemo_fields_complex_tbl(obj)); + } + else { + iv_count = RBASIC_FIELDS_COUNT(obj); + } + break; + default: if (rb_obj_exivar_p(obj)) { - struct gen_fields_tbl *fields_tbl; - if (rb_gen_fields_tbl_get(obj, 0, &fields_tbl)) { - iv_count = gen_fields_tbl_count(obj, fields_tbl); + if (rb_shape_obj_too_complex_p(obj)) { + VALUE fields_obj; + + if (rb_gen_fields_tbl_get(obj, 0, &fields_obj)) { + iv_count = rb_st_table_size(rb_imemo_fields_complex_tbl(fields_obj)); + } + } + else { + iv_count = RBASIC_FIELDS_COUNT(obj); } } break; @@ -4709,7 +4687,7 @@ class_fields_ivar_set(VALUE klass, VALUE fields_obj, ID id, VALUE val, bool conc { bool existing = true; const VALUE original_fields_obj = fields_obj; - fields_obj = original_fields_obj ? original_fields_obj : rb_imemo_class_fields_new(klass, 1); + fields_obj = original_fields_obj ? original_fields_obj : rb_imemo_fields_new(rb_singleton_class(klass), 1); shape_id_t current_shape_id = RBASIC_SHAPE_ID(fields_obj); shape_id_t next_shape_id = current_shape_id; @@ -4730,9 +4708,9 @@ class_fields_ivar_set(VALUE klass, VALUE fields_obj, ID id, VALUE val, bool conc next_shape_id = rb_shape_transition_add_ivar(fields_obj, id); if (UNLIKELY(rb_shape_too_complex_p(next_shape_id))) { attr_index_t current_len = RSHAPE_LEN(current_shape_id); - fields_obj = rb_imemo_class_fields_new_complex(klass, current_len + 1); + fields_obj = rb_imemo_fields_new_complex(rb_singleton_class(klass), current_len + 1); if (current_len) { - rb_obj_copy_fields_to_hash_table(original_fields_obj, rb_imemo_class_fields_complex_tbl(fields_obj)); + rb_obj_copy_fields_to_hash_table(original_fields_obj, rb_imemo_fields_complex_tbl(fields_obj)); RBASIC_SET_SHAPE_ID(fields_obj, next_shape_id); } goto too_complex; @@ -4746,9 +4724,9 @@ class_fields_ivar_set(VALUE klass, VALUE fields_obj, ID id, VALUE val, bool conc // We allocate a new fields_obj even when concurrency isn't a concern // so that we're embedded as long as possible. - fields_obj = rb_imemo_class_fields_new(klass, next_capacity); + fields_obj = rb_imemo_fields_new(rb_singleton_class(klass), next_capacity); if (original_fields_obj) { - MEMCPY(rb_imemo_class_fields_ptr(fields_obj), rb_imemo_class_fields_ptr(original_fields_obj), VALUE, RSHAPE_LEN(current_shape_id)); + MEMCPY(rb_imemo_fields_ptr(fields_obj), rb_imemo_fields_ptr(original_fields_obj), VALUE, RSHAPE_LEN(current_shape_id)); } } @@ -4756,7 +4734,7 @@ class_fields_ivar_set(VALUE klass, VALUE fields_obj, ID id, VALUE val, bool conc RUBY_ASSERT(index == (RSHAPE_LEN(next_shape_id) - 1)); } - VALUE *fields = rb_imemo_class_fields_ptr(fields_obj); + VALUE *fields = rb_imemo_fields_ptr(fields_obj); RB_OBJ_WRITE(fields_obj, &fields[index], val); if (!existing) { @@ -4768,7 +4746,7 @@ class_fields_ivar_set(VALUE klass, VALUE fields_obj, ID id, VALUE val, bool conc too_complex: { - st_table *table = rb_imemo_class_fields_complex_tbl(fields_obj); + st_table *table = rb_imemo_fields_complex_tbl(fields_obj); existing = st_insert(table, (st_data_t)id, (st_data_t)val); RB_OBJ_WRITTEN(fields_obj, Qundef, val); @@ -4796,13 +4774,14 @@ rb_class_ivar_set(VALUE obj, ID id, VALUE val) if (new_fields_obj != original_fields_obj) { RCLASS_WRITABLE_SET_FIELDS_OBJ(obj, new_fields_obj); - - // TODO: What should we set as the T_CLASS shape_id? - // In most case we can replicate the single `fields_obj` shape - // but in namespaced case? - // Perhaps INVALID_SHAPE_ID? - RBASIC_SET_SHAPE_ID(obj, RBASIC_SHAPE_ID(new_fields_obj)); } + + // TODO: What should we set as the T_CLASS shape_id? + // In most case we can replicate the single `fields_obj` shape + // but in namespaced case? + // Perhaps INVALID_SHAPE_ID? + RBASIC_SET_SHAPE_ID(obj, RBASIC_SHAPE_ID(new_fields_obj)); + return existing; } diff --git a/variable.h b/variable.h index 54b7fc5461..82a79c63ce 100644 --- a/variable.h +++ b/variable.h @@ -12,18 +12,7 @@ #include "shape.h" -struct gen_fields_tbl { - union { - struct { - VALUE fields[1]; - } shape; - struct { - st_table *table; - } complex; - } as; -}; - -int rb_ivar_generic_fields_tbl_lookup(VALUE obj, struct gen_fields_tbl **); +int rb_ivar_generic_fields_tbl_lookup(VALUE obj, VALUE *); void rb_copy_complex_ivars(VALUE dest, VALUE obj, shape_id_t src_shape_id, st_table *fields_table); void rb_free_rb_global_tbl(void); @@ -2982,6 +2982,7 @@ rb_vm_update_references(void *ptr) if (ptr) { rb_vm_t *vm = ptr; + vm->self = rb_gc_location(vm->self); vm->mark_object_ary = rb_gc_location(vm->mark_object_ary); vm->load_path = rb_gc_location(vm->load_path); vm->load_path_snapshot = rb_gc_location(vm->load_path_snapshot); @@ -3068,6 +3069,8 @@ rb_vm_mark(void *ptr) rb_gc_mark_maybe(*list->varptr); } + rb_gc_mark_movable(vm->self); + if (vm->main_namespace) { rb_namespace_entry_mark((void *)vm->main_namespace); } @@ -3672,10 +3675,10 @@ rb_ec_initialize_vm_stack(rb_execution_context_t *ec, VALUE *stack, size_t size) void rb_ec_clear_vm_stack(rb_execution_context_t *ec) { - rb_ec_set_vm_stack(ec, NULL, 0); - - // Avoid dangling pointers: + // set cfp to NULL before clearing the stack in case `thread_profile_frames` + // gets called in this middle of `rb_ec_set_vm_stack` via signal handler. ec->cfp = NULL; + rb_ec_set_vm_stack(ec, NULL, 0); } static void diff --git a/vm_insnhelper.c b/vm_insnhelper.c index 7efcdba8a4..2fe5e26928 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -1252,16 +1252,18 @@ vm_getivar(VALUE obj, ID id, const rb_iseq_t *iseq, IVC ic, const struct rb_call if (!fields_obj) { return default_value; } - ivar_list = rb_imemo_class_fields_ptr(fields_obj); - shape_id = rb_obj_shape_id(fields_obj); + ivar_list = rb_imemo_fields_ptr(fields_obj); + shape_id = RBASIC_SHAPE_ID_FOR_READ(fields_obj); break; } default: if (rb_obj_exivar_p(obj)) { - struct gen_fields_tbl *fields_tbl; - rb_gen_fields_tbl_get(obj, id, &fields_tbl); - ivar_list = fields_tbl->as.shape.fields; + VALUE fields_obj = 0; + if (!rb_gen_fields_tbl_get(obj, id, &fields_obj)) { + return default_value; + } + ivar_list = rb_imemo_fields_ptr(fields_obj); } else { return default_value; @@ -1325,7 +1327,7 @@ vm_getivar(VALUE obj, ID id, const rb_iseq_t *iseq, IVC ic, const struct rb_call switch (BUILTIN_TYPE(obj)) { case T_CLASS: case T_MODULE: - table = rb_imemo_class_fields_complex_tbl(fields_obj); + table = rb_imemo_fields_complex_tbl(fields_obj); break; case T_OBJECT: @@ -1333,9 +1335,9 @@ vm_getivar(VALUE obj, ID id, const rb_iseq_t *iseq, IVC ic, const struct rb_call break; default: { - struct gen_fields_tbl *fields_tbl; - if (rb_gen_fields_tbl_get(obj, 0, &fields_tbl)) { - table = fields_tbl->as.complex.table; + VALUE fields_obj; + if (rb_gen_fields_tbl_get(obj, 0, &fields_obj)) { + table = rb_imemo_fields_complex_tbl(fields_obj); } break; } @@ -1456,7 +1458,7 @@ vm_setivar_default(VALUE obj, ID id, VALUE val, shape_id_t dest_shape_id, attr_i { shape_id_t shape_id = RBASIC_SHAPE_ID(obj); - struct gen_fields_tbl *fields_tbl = 0; + VALUE fields_obj = 0; // Cache hit case if (shape_id == dest_shape_id) { @@ -1474,13 +1476,13 @@ vm_setivar_default(VALUE obj, ID id, VALUE val, shape_id_t dest_shape_id, attr_i return Qundef; } - rb_gen_fields_tbl_get(obj, 0, &fields_tbl); + rb_gen_fields_tbl_get(obj, 0, &fields_obj); if (shape_id != dest_shape_id) { RBASIC_SET_SHAPE_ID(obj, dest_shape_id); } - RB_OBJ_WRITE(obj, &fields_tbl->as.shape.fields[index], val); + RB_OBJ_WRITE(obj, &rb_imemo_fields_ptr(fields_obj)[index], val); RB_DEBUG_COUNTER_INC(ivar_set_ic_hit); @@ -5555,6 +5557,14 @@ vm_get_special_object(const VALUE *const reg_ep, } } +// ZJIT implementation is using the C function +// and needs to call a non-static function +VALUE +rb_vm_get_special_object(const VALUE *reg_ep, enum vm_special_object_type type) +{ + return vm_get_special_object(reg_ep, type); +} + static VALUE vm_concat_array(VALUE ary1, VALUE ary2st) { diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index a1c7464805..8aa874f4dd 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -225,10 +225,11 @@ pub const RUBY_FL_PROMOTED: ruby_fl_type = 32; pub const RUBY_FL_UNUSED6: ruby_fl_type = 64; pub const RUBY_FL_FINALIZE: ruby_fl_type = 128; pub const RUBY_FL_TAINT: ruby_fl_type = 0; +pub const RUBY_FL_EXIVAR: ruby_fl_type = 0; pub const RUBY_FL_SHAREABLE: ruby_fl_type = 256; pub const RUBY_FL_UNTRUSTED: ruby_fl_type = 0; pub const RUBY_FL_UNUSED9: ruby_fl_type = 512; -pub const RUBY_FL_EXIVAR: ruby_fl_type = 1024; +pub const RUBY_FL_UNUSED10: ruby_fl_type = 1024; pub const RUBY_FL_FREEZE: ruby_fl_type = 2048; pub const RUBY_FL_USER0: ruby_fl_type = 4096; pub const RUBY_FL_USER1: ruby_fl_type = 8192; @@ -409,7 +410,7 @@ pub const imemo_parser_strterm: imemo_type = 10; pub const imemo_callinfo: imemo_type = 11; pub const imemo_callcache: imemo_type = 12; pub const imemo_constcache: imemo_type = 13; -pub const imemo_class_fields: imemo_type = 14; +pub const imemo_fields: imemo_type = 14; pub type imemo_type = u32; #[repr(C)] #[derive(Debug, Copy, Clone)] diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index f274a64ca6..b1869f71c0 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -7,7 +7,7 @@ use crate::state::ZJITState; use crate::{asm::CodeBlock, cruby::*, options::debug, virtualmem::CodePtr}; use crate::invariants::{iseq_escapes_ep, track_no_ep_escape_assumption}; use crate::backend::lir::{self, asm_comment, Assembler, Opnd, Target, CFP, C_ARG_OPNDS, C_RET_OPND, EC, SP}; -use crate::hir::{iseq_to_hir, Block, BlockId, BranchEdge, CallInfo, RangeType, SELF_PARAM_IDX}; +use crate::hir::{iseq_to_hir, Block, BlockId, BranchEdge, CallInfo, RangeType, SELF_PARAM_IDX, SpecialObjectType}; use crate::hir::{Const, FrameState, Function, Insn, InsnId}; use crate::hir_type::{types::Fixnum, Type}; use crate::options::get_option; @@ -252,6 +252,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::NewArray { elements, state } => gen_new_array(jit, asm, elements, &function.frame_state(*state)), Insn::NewRange { low, high, flag, state } => gen_new_range(asm, opnd!(low), opnd!(high), *flag, &function.frame_state(*state)), Insn::ArrayDup { val, state } => gen_array_dup(asm, opnd!(val), &function.frame_state(*state)), + Insn::StringCopy { val, chilled } => gen_string_copy(asm, opnd!(val), *chilled), Insn::Param { idx } => unreachable!("block.insns should not have Insn::Param({idx})"), Insn::Snapshot { .. } => return Some(()), // we don't need to do anything for this instruction at the moment Insn::Jump(branch) => return gen_jump(jit, asm, branch), @@ -277,8 +278,10 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::GetIvar { self_val, id, state: _ } => gen_getivar(asm, opnd!(self_val), *id), Insn::SetGlobal { id, val, state: _ } => gen_setglobal(asm, *id, opnd!(val)), Insn::GetGlobal { id, state: _ } => gen_getglobal(asm, *id), + Insn::GetConstantPath { ic, state } => gen_get_constant_path(asm, *ic, &function.frame_state(*state)), Insn::SetIvar { self_val, id, val, state: _ } => return gen_setivar(asm, opnd!(self_val), *id, opnd!(val)), Insn::SideExit { state } => return gen_side_exit(jit, asm, &function.frame_state(*state)), + Insn::PutSpecialObject { value_type } => gen_putspecialobject(asm, *value_type), _ => { debug!("ZJIT: gen_function: unexpected insn {:?}", insn); return None; @@ -293,6 +296,21 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Some(()) } +fn gen_get_constant_path(asm: &mut Assembler, ic: *const iseq_inline_constant_cache, state: &FrameState) -> Opnd { + unsafe extern "C" { + fn rb_vm_opt_getconstant_path(ec: EcPtr, cfp: CfpPtr, ic: *const iseq_inline_constant_cache) -> VALUE; + } + + // Save PC since the call can allocate an IC + gen_save_pc(asm, state); + + let val = asm.ccall( + rb_vm_opt_getconstant_path as *const u8, + vec![EC, CFP, Opnd::const_ptr(ic as *const u8)], + ); + val +} + /// 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(jit: &mut JITState, asm: &mut Assembler, cfun: *const u8, args: &[InsnId]) -> Option<lir::Opnd> { @@ -347,6 +365,20 @@ fn gen_side_exit(jit: &mut JITState, asm: &mut Assembler, state: &FrameState) -> Some(()) } +/// Emit a special object lookup +fn gen_putspecialobject(asm: &mut Assembler, value_type: SpecialObjectType) -> Opnd { + asm_comment!(asm, "call rb_vm_get_special_object"); + + // Get the EP of the current CFP and load it into a register + let ep_opnd = Opnd::mem(64, CFP, RUBY_OFFSET_CFP_EP); + let ep_reg = asm.load(ep_opnd); + + asm.ccall( + rb_vm_get_special_object as *const u8, + vec![ep_reg, Opnd::UImm(u64::from(value_type))], + ) +} + /// Compile an interpreter entry block to be inserted into an ISEQ fn gen_entry_prologue(asm: &mut Assembler, iseq: IseqPtr) { asm_comment!(asm, "ZJIT entry point: {}", iseq_get_location(iseq, 0)); @@ -596,6 +628,17 @@ fn gen_send_without_block_direct( Some(ret) } +/// Compile a string resurrection +fn gen_string_copy(asm: &mut Assembler, recv: Opnd, chilled: bool) -> Opnd { + asm_comment!(asm, "call rb_ec_str_resurrect"); + // TODO: split rb_ec_str_resurrect into separate functions + let chilled = if chilled { Opnd::Imm(1) } else { Opnd::Imm(0) }; + asm.ccall( + rb_ec_str_resurrect as *const u8, + vec![EC, recv, chilled], + ) +} + /// Compile an array duplication instruction fn gen_array_dup( asm: &mut Assembler, diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index de1c86e8d6..e0334ed44d 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -133,6 +133,7 @@ unsafe extern "C" { pub fn rb_str_setbyte(str: VALUE, index: VALUE, value: VALUE) -> VALUE; pub fn rb_vm_splat_array(flag: VALUE, ary: VALUE) -> VALUE; pub fn rb_vm_concat_array(ary1: VALUE, ary2st: VALUE) -> VALUE; + pub fn rb_vm_get_special_object(reg_ep: *const VALUE, value_type: vm_special_object_type) -> VALUE; pub fn rb_vm_concat_to_array(ary1: VALUE, ary2st: VALUE) -> VALUE; pub fn rb_vm_defined( ec: EcPtr, @@ -213,6 +214,7 @@ pub use rb_vm_ci_flag as vm_ci_flag; pub use rb_vm_ci_kwarg as vm_ci_kwarg; pub use rb_METHOD_ENTRY_VISI as METHOD_ENTRY_VISI; pub use rb_RCLASS_ORIGIN as RCLASS_ORIGIN; +pub use rb_vm_get_special_object as vm_get_special_object; /// Helper so we can get a Rust string for insn_name() pub fn insn_name(opcode: usize) -> String { diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index bcc8f48c37..d5e54955c8 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -101,10 +101,11 @@ pub const RUBY_FL_PROMOTED: ruby_fl_type = 32; pub const RUBY_FL_UNUSED6: ruby_fl_type = 64; pub const RUBY_FL_FINALIZE: ruby_fl_type = 128; pub const RUBY_FL_TAINT: ruby_fl_type = 0; +pub const RUBY_FL_EXIVAR: ruby_fl_type = 0; pub const RUBY_FL_SHAREABLE: ruby_fl_type = 256; pub const RUBY_FL_UNTRUSTED: ruby_fl_type = 0; pub const RUBY_FL_UNUSED9: ruby_fl_type = 512; -pub const RUBY_FL_EXIVAR: ruby_fl_type = 1024; +pub const RUBY_FL_UNUSED10: ruby_fl_type = 1024; pub const RUBY_FL_FREEZE: ruby_fl_type = 2048; pub const RUBY_FL_USER0: ruby_fl_type = 4096; pub const RUBY_FL_USER1: ruby_fl_type = 8192; @@ -226,7 +227,7 @@ pub const imemo_parser_strterm: imemo_type = 10; pub const imemo_callinfo: imemo_type = 11; pub const imemo_callcache: imemo_type = 12; pub const imemo_constcache: imemo_type = 13; -pub const imemo_class_fields: imemo_type = 14; +pub const imemo_fields: imemo_type = 14; pub type imemo_type = u32; pub const METHOD_VISI_UNDEF: rb_method_visibility_t = 0; pub const METHOD_VISI_PUBLIC: rb_method_visibility_t = 1; diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 45a9024ca9..fbca1f4418 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -138,6 +138,40 @@ impl Invariant { } } +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum SpecialObjectType { + VMCore = 1, + CBase = 2, + ConstBase = 3, +} + +impl From<u32> for SpecialObjectType { + fn from(value: u32) -> Self { + match value { + VM_SPECIAL_OBJECT_VMCORE => SpecialObjectType::VMCore, + VM_SPECIAL_OBJECT_CBASE => SpecialObjectType::CBase, + VM_SPECIAL_OBJECT_CONST_BASE => SpecialObjectType::ConstBase, + _ => panic!("Invalid special object type: {}", value), + } + } +} + +impl From<SpecialObjectType> for u64 { + fn from(special_type: SpecialObjectType) -> Self { + special_type as u64 + } +} + +impl std::fmt::Display for SpecialObjectType { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + SpecialObjectType::VMCore => write!(f, "VMCore"), + SpecialObjectType::CBase => write!(f, "CBase"), + SpecialObjectType::ConstBase => write!(f, "ConstBase"), + } + } +} + /// Print adaptor for [`Invariant`]. See [`PtrPrintMap`]. pub struct InvariantPrinter<'a> { inner: Invariant, @@ -151,7 +185,9 @@ impl<'a> std::fmt::Display for InvariantPrinter<'a> { write!(f, "BOPRedefined(")?; match klass { INTEGER_REDEFINED_OP_FLAG => write!(f, "INTEGER_REDEFINED_OP_FLAG")?, + STRING_REDEFINED_OP_FLAG => write!(f, "STRING_REDEFINED_OP_FLAG")?, ARRAY_REDEFINED_OP_FLAG => write!(f, "ARRAY_REDEFINED_OP_FLAG")?, + HASH_REDEFINED_OP_FLAG => write!(f, "HASH_REDEFINED_OP_FLAG")?, _ => write!(f, "{klass}")?, } write!(f, ", ")?; @@ -167,6 +203,8 @@ impl<'a> std::fmt::Display for InvariantPrinter<'a> { BOP_LE => write!(f, "BOP_LE")?, BOP_GT => write!(f, "BOP_GT")?, BOP_GE => write!(f, "BOP_GE")?, + BOP_FREEZE => write!(f, "BOP_FREEZE")?, + BOP_UMINUS => write!(f, "BOP_UMINUS")?, BOP_MAX => write!(f, "BOP_MAX")?, _ => write!(f, "{bop}")?, } @@ -362,9 +400,12 @@ pub enum Insn { /// SSA block parameter. Also used for function parameters in the function's entry block. Param { idx: usize }, - StringCopy { val: InsnId }, + StringCopy { val: InsnId, chilled: bool }, StringIntern { val: InsnId }, + /// Put special object (VMCORE, CBASE, etc.) based on value_type + PutSpecialObject { value_type: SpecialObjectType }, + /// Call `to_a` on `val` if the method is defined, or make a new array `[val]` otherwise. ToArray { val: InsnId, state: InsnId }, /// Call `to_a` on `val` if the method is defined, or make a new array `[val]` otherwise. If we @@ -390,7 +431,7 @@ pub enum Insn { /// Return C `true` if `val` is `Qnil`, else `false`. IsNil { val: InsnId }, Defined { op_type: usize, obj: VALUE, pushval: VALUE, v: InsnId }, - GetConstantPath { ic: *const iseq_inline_constant_cache }, + GetConstantPath { ic: *const iseq_inline_constant_cache, state: InsnId }, /// Get a global variable named `id` GetGlobal { id: ID, state: InsnId }, @@ -565,7 +606,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::ArraySet { array, idx, val } => { write!(f, "ArraySet {array}, {idx}, {val}") } Insn::ArrayDup { val, .. } => { write!(f, "ArrayDup {val}") } Insn::HashDup { val, .. } => { write!(f, "HashDup {val}") } - Insn::StringCopy { val } => { write!(f, "StringCopy {val}") } + Insn::StringCopy { val, .. } => { write!(f, "StringCopy {val}") } Insn::Test { val } => { write!(f, "Test {val}") } Insn::IsNil { val } => { write!(f, "IsNil {val}") } Insn::Jump(target) => { write!(f, "Jump {target}") } @@ -610,7 +651,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::GuardType { val, guard_type, .. } => { write!(f, "GuardType {val}, {}", guard_type.print(self.ptr_map)) }, Insn::GuardBitEquals { val, expected, .. } => { write!(f, "GuardBitEquals {val}, {}", expected.print(self.ptr_map)) }, Insn::PatchPoint(invariant) => { write!(f, "PatchPoint {}", invariant.print(self.ptr_map)) }, - Insn::GetConstantPath { ic } => { write!(f, "GetConstantPath {:p}", self.ptr_map.map_ptr(ic)) }, + Insn::GetConstantPath { ic, .. } => { write!(f, "GetConstantPath {:p}", self.ptr_map.map_ptr(ic)) }, Insn::CCall { cfun, args, name, return_type: _, elidable: _ } => { write!(f, "CCall {}@{:p}", name.contents_lossy(), self.ptr_map.map_ptr(cfun))?; for arg in args { @@ -645,6 +686,9 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::ArrayExtend { left, right, .. } => write!(f, "ArrayExtend {left}, {right}"), Insn::ArrayPush { array, val, .. } => write!(f, "ArrayPush {array}, {val}"), Insn::SideExit { .. } => write!(f, "SideExit"), + Insn::PutSpecialObject { value_type } => { + write!(f, "PutSpecialObject {}", value_type) + } insn => { write!(f, "{insn:?}") } } } @@ -938,7 +982,7 @@ impl Function { } }, Return { val } => Return { val: find!(*val) }, - StringCopy { val } => StringCopy { val: find!(*val) }, + StringCopy { val, chilled } => StringCopy { val: find!(*val), chilled: *chilled }, StringIntern { val } => StringIntern { val: find!(*val) }, Test { val } => Test { val: find!(*val) }, &IsNil { val } => IsNil { val: find!(val) }, @@ -958,6 +1002,7 @@ impl Function { FixnumGe { left, right } => FixnumGe { left: find!(*left), right: find!(*right) }, FixnumLt { left, right } => FixnumLt { left: find!(*left), right: find!(*right) }, FixnumLe { left, right } => FixnumLe { left: find!(*left), right: find!(*right) }, + PutSpecialObject { value_type } => PutSpecialObject { value_type: *value_type }, SendWithoutBlock { self_val, call_info, cd, args, state } => SendWithoutBlock { self_val: find!(*self_val), call_info: call_info.clone(), @@ -1074,6 +1119,7 @@ impl Function { Insn::FixnumLe { .. } => types::BoolExact, Insn::FixnumGt { .. } => types::BoolExact, Insn::FixnumGe { .. } => types::BoolExact, + Insn::PutSpecialObject { .. } => types::BasicObject, Insn::SendWithoutBlock { .. } => types::BasicObject, Insn::SendWithoutBlockDirect { .. } => types::BasicObject, Insn::Send { .. } => types::BasicObject, @@ -1208,6 +1254,38 @@ impl Function { } } + fn rewrite_if_frozen(&mut self, block: BlockId, orig_insn_id: InsnId, self_val: InsnId, klass: u32, bop: u32) { + let self_type = self.type_of(self_val); + if let Some(obj) = self_type.ruby_object() { + if obj.is_frozen() { + self.push_insn(block, Insn::PatchPoint(Invariant::BOPRedefined { klass, bop })); + self.make_equal_to(orig_insn_id, self_val); + return; + } + } + self.push_insn_id(block, orig_insn_id); + } + + fn try_rewrite_freeze(&mut self, block: BlockId, orig_insn_id: InsnId, self_val: InsnId) { + if self.is_a(self_val, types::StringExact) { + self.rewrite_if_frozen(block, orig_insn_id, self_val, STRING_REDEFINED_OP_FLAG, BOP_FREEZE); + } else if self.is_a(self_val, types::ArrayExact) { + self.rewrite_if_frozen(block, orig_insn_id, self_val, ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE); + } else if self.is_a(self_val, types::HashExact) { + self.rewrite_if_frozen(block, orig_insn_id, self_val, HASH_REDEFINED_OP_FLAG, BOP_FREEZE); + } else { + self.push_insn_id(block, orig_insn_id); + } + } + + fn try_rewrite_uminus(&mut self, block: BlockId, orig_insn_id: InsnId, self_val: InsnId) { + if self.is_a(self_val, types::StringExact) { + self.rewrite_if_frozen(block, orig_insn_id, self_val, STRING_REDEFINED_OP_FLAG, BOP_UMINUS); + } else { + self.push_insn_id(block, orig_insn_id); + } + } + /// Rewrite SendWithoutBlock opcodes into SendWithoutBlockDirect opcodes if we know the target /// ISEQ statically. This removes run-time method lookups and opens the door for inlining. fn optimize_direct_sends(&mut self) { @@ -1238,6 +1316,10 @@ impl Function { self.try_rewrite_fixnum_op(block, insn_id, &|left, right| Insn::FixnumGt { left, right }, BOP_GT, self_val, args[0], state), Insn::SendWithoutBlock { self_val, call_info: CallInfo { method_name }, args, state, .. } if method_name == ">=" && args.len() == 1 => self.try_rewrite_fixnum_op(block, insn_id, &|left, right| Insn::FixnumGe { left, right }, BOP_GE, self_val, args[0], state), + Insn::SendWithoutBlock { self_val, call_info: CallInfo { method_name }, args, .. } if method_name == "freeze" && args.len() == 0 => + self.try_rewrite_freeze(block, insn_id, self_val), + Insn::SendWithoutBlock { self_val, call_info: CallInfo { method_name }, args, .. } if method_name == "-@" && args.len() == 0 => + self.try_rewrite_uminus(block, insn_id, self_val), Insn::SendWithoutBlock { mut self_val, call_info, cd, args, state } => { let frame_state = self.frame_state(state); let (klass, guard_equal_to) = if let Some(klass) = self.type_of(self_val).runtime_exact_ruby_class() { @@ -1273,7 +1355,7 @@ impl Function { let send_direct = self.push_insn(block, Insn::SendWithoutBlockDirect { self_val, call_info, cd, cme, iseq, args, state }); self.make_equal_to(insn_id, send_direct); } - Insn::GetConstantPath { ic } => { + Insn::GetConstantPath { ic, .. } => { let idlist: *const ID = unsafe { (*ic).segments }; let ice = unsafe { (*ic).entry }; if ice.is_null() { @@ -1560,9 +1642,14 @@ impl Function { if necessary[insn_id.0] { continue; } necessary[insn_id.0] = true; match self.find(insn_id) { - Insn::Const { .. } | Insn::Param { .. } - | Insn::PatchPoint(..) | Insn::GetConstantPath { .. } => + Insn::Const { .. } + | Insn::Param { .. } + | Insn::PatchPoint(..) + | Insn::PutSpecialObject { .. } => {} + Insn::GetConstantPath { ic: _, state } => { + worklist.push_back(state); + } Insn::ArrayMax { elements, state } | Insn::NewArray { elements, state } => { worklist.extend(elements); @@ -1580,7 +1667,7 @@ impl Function { worklist.push_back(high); worklist.push_back(state); } - Insn::StringCopy { val } + Insn::StringCopy { val, .. } | Insn::StringIntern { val } | Insn::Return { val } | Insn::Defined { v: val, .. } @@ -2095,10 +2182,18 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> { YARVINSN_nop => {}, YARVINSN_putnil => { state.stack_push(fun.push_insn(block, Insn::Const { val: Const::Value(Qnil) })); }, YARVINSN_putobject => { state.stack_push(fun.push_insn(block, Insn::Const { val: Const::Value(get_arg(pc, 0)) })); }, - YARVINSN_putstring | YARVINSN_putchilledstring => { - // TODO(max): Do something different for chilled string + YARVINSN_putspecialobject => { + let value_type = SpecialObjectType::from(get_arg(pc, 0).as_u32()); + state.stack_push(fun.push_insn(block, Insn::PutSpecialObject { value_type })); + } + YARVINSN_putstring => { let val = fun.push_insn(block, Insn::Const { val: Const::Value(get_arg(pc, 0)) }); - let insn_id = fun.push_insn(block, Insn::StringCopy { val }); + let insn_id = fun.push_insn(block, Insn::StringCopy { val, chilled: false }); + state.stack_push(insn_id); + } + YARVINSN_putchilledstring => { + let val = fun.push_insn(block, Insn::Const { val: Const::Value(get_arg(pc, 0)) }); + let insn_id = fun.push_insn(block, Insn::StringCopy { val, chilled: true }); state.stack_push(insn_id); } YARVINSN_putself => { state.stack_push(self_param); } @@ -2218,7 +2313,8 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> { } YARVINSN_opt_getconstant_path => { let ic = get_arg(pc, 0).as_ptr(); - state.stack_push(fun.push_insn(block, Insn::GetConstantPath { ic })); + let snapshot = fun.push_insn(block, Insn::Snapshot { state: exit_state }); + state.stack_push(fun.push_insn(block, Insn::GetConstantPath { ic, state: snapshot })); } YARVINSN_branchunless => { let offset = get_arg(pc, 0).as_i64(); @@ -2370,6 +2466,34 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> { let send = fun.push_insn(block, Insn::SendWithoutBlock { self_val: recv, call_info: CallInfo { method_name }, cd, args, state: exit_id }); state.stack_push(send); } + YARVINSN_opt_hash_freeze | + YARVINSN_opt_ary_freeze | + YARVINSN_opt_str_freeze | + YARVINSN_opt_str_uminus => { + // NB: these instructions have the recv for the call at get_arg(0) + let cd: *const rb_call_data = get_arg(pc, 1).as_ptr(); + let call_info = unsafe { rb_get_call_data_ci(cd) }; + if unknown_call_type(unsafe { rb_vm_ci_flag(call_info) }) { + // Unknown call type; side-exit into the interpreter + let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); + fun.push_insn(block, Insn::SideExit { state: exit_id }); + break; // End the block + } + let argc = unsafe { vm_ci_argc((*cd).ci) }; + let name = insn_name(opcode as usize); + assert_eq!(0, argc, "{name} should not have args"); + let args = vec![]; + + let method_name = unsafe { + let mid = rb_vm_ci_mid(call_info); + mid.contents_lossy().into_owned() + }; + + let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); + let recv = fun.push_insn(block, Insn::Const { val: Const::Value(get_arg(pc, 0)) }); + let send = fun.push_insn(block, Insn::SendWithoutBlock { self_val: recv, call_info: CallInfo { method_name }, cd, args, state: exit_id }); + state.stack_push(send); + } YARVINSN_leave => { fun.push_insn(block, Insn::Return { val: state.stack_pop()? }); @@ -3048,6 +3172,62 @@ mod tests { } #[test] + fn test_opt_hash_freeze() { + eval(" + def test = {}.freeze + "); + assert_method_hir_with_opcode("test", YARVINSN_opt_hash_freeze, expect![[r#" + fn test: + bb0(v0:BasicObject): + v3:HashExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v4:BasicObject = SendWithoutBlock v3, :freeze + Return v4 + "#]]); + } + + #[test] + fn test_opt_ary_freeze() { + eval(" + def test = [].freeze + "); + assert_method_hir_with_opcode("test", YARVINSN_opt_ary_freeze, expect![[r#" + fn test: + bb0(v0:BasicObject): + v3:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v4:BasicObject = SendWithoutBlock v3, :freeze + Return v4 + "#]]); + } + + #[test] + fn test_opt_str_freeze() { + eval(" + def test = ''.freeze + "); + assert_method_hir_with_opcode("test", YARVINSN_opt_str_freeze, expect![[r#" + fn test: + bb0(v0:BasicObject): + v3:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v4:BasicObject = SendWithoutBlock v3, :freeze + Return v4 + "#]]); + } + + #[test] + fn test_opt_str_uminus() { + eval(" + def test = -'' + "); + assert_method_hir_with_opcode("test", YARVINSN_opt_str_uminus, expect![[r#" + fn test: + bb0(v0:BasicObject): + v3:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v4:BasicObject = SendWithoutBlock v3, :-@ + Return v4 + "#]]); + } + + #[test] fn test_setlocal_getlocal() { eval(" def test @@ -3523,6 +3703,13 @@ mod tests { assert_method_hir("test", expect![[r#" fn test: bb0(v0:BasicObject, v1:BasicObject): + v3:BasicObject = PutSpecialObject VMCore + v5:HashExact = NewHash + v7:BasicObject = SendWithoutBlock v3, :core#hash_merge_kwd, v5, v1 + v8:BasicObject = PutSpecialObject VMCore + v9:StaticSymbol[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v10:Fixnum[1] = Const Value(1) + v12:BasicObject = SendWithoutBlock v8, :core#hash_merge_ptr, v7, v9, v10 SideExit "#]]); } @@ -3563,14 +3750,14 @@ mod tests { assert_method_hir_with_opcode("test", YARVINSN_opt_new, expect![[r#" fn test: bb0(v0:BasicObject): - v2:BasicObject = GetConstantPath 0x1000 - v3:NilClassExact = Const Value(nil) - Jump bb1(v0, v3, v2) - bb1(v5:BasicObject, v6:NilClassExact, v7:BasicObject): - v10:BasicObject = SendWithoutBlock v7, :new - Jump bb2(v5, v10, v6) - bb2(v12:BasicObject, v13:BasicObject, v14:NilClassExact): - Return v13 + v3:BasicObject = GetConstantPath 0x1000 + v4:NilClassExact = Const Value(nil) + Jump bb1(v0, v4, v3) + bb1(v6:BasicObject, v7:NilClassExact, v8:BasicObject): + v11:BasicObject = SendWithoutBlock v8, :new + Jump bb2(v6, v11, v7) + bb2(v13:BasicObject, v14:BasicObject, v15:NilClassExact): + Return v14 "#]]); } @@ -3957,6 +4144,27 @@ mod tests { } #[test] + // Tests for ConstBase requires either constant or class definition, both + // of which can't be performed inside a method. + fn test_putspecialobject_vm_core_and_cbase() { + eval(" + def test + alias aliased __callee__ + end + "); + assert_method_hir_with_opcode("test", YARVINSN_putspecialobject, expect![[r#" + fn test: + bb0(v0:BasicObject): + v2:BasicObject = PutSpecialObject VMCore + v3:BasicObject = PutSpecialObject CBase + v4:StaticSymbol[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v5:StaticSymbol[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v7:BasicObject = SendWithoutBlock v2, :core#set_method_alias, v3, v4, v5 + Return v7 + "#]]); + } + + #[test] fn test_branchnil() { eval(" def test(x) = x&.itself @@ -4952,9 +5160,9 @@ mod opt_tests { assert_optimized_method_hir("test", expect![[r#" fn test: bb0(v0:BasicObject): - v2:BasicObject = GetConstantPath 0x1000 - v3:Fixnum[5] = Const Value(5) - Return v3 + v3:BasicObject = GetConstantPath 0x1000 + v4:Fixnum[5] = Const Value(5) + Return v4 "#]]); } @@ -5023,8 +5231,8 @@ mod opt_tests { PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, M) PatchPoint MethodRedefined(Module@0x1008, name@0x1010) - v6:Fixnum[1] = Const Value(1) - Return v6 + v7:Fixnum[1] = Const Value(1) + Return v7 "#]]); } @@ -5141,8 +5349,8 @@ mod opt_tests { assert_optimized_method_hir("test", expect![[r#" fn test: bb0(v0:BasicObject): - v2:BasicObject = GetConstantPath 0x1000 - Return v2 + v3:BasicObject = GetConstantPath 0x1000 + Return v3 "#]]); } @@ -5156,8 +5364,8 @@ mod opt_tests { assert_optimized_method_hir("test", expect![[r#" fn test: bb0(v0:BasicObject): - v2:BasicObject = GetConstantPath 0x1000 - Return v2 + v3:BasicObject = GetConstantPath 0x1000 + Return v3 "#]]); } @@ -5172,8 +5380,8 @@ mod opt_tests { bb0(v0:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, Kernel) - v6:BasicObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - Return v6 + v7:BasicObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + Return v7 "#]]); } @@ -5194,8 +5402,8 @@ mod opt_tests { bb0(v0:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, Foo::Bar::C) - v6:BasicObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - Return v6 + v7:BasicObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + Return v7 "#]]); } @@ -5211,14 +5419,14 @@ mod opt_tests { bb0(v0:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, C) - v19:BasicObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - v3:NilClassExact = Const Value(nil) - Jump bb1(v0, v3, v19) - bb1(v5:BasicObject, v6:NilClassExact, v7:BasicObject[VALUE(0x1008)]): - v10:BasicObject = SendWithoutBlock v7, :new - Jump bb2(v5, v10, v6) - bb2(v12:BasicObject, v13:BasicObject, v14:NilClassExact): - Return v13 + v20:BasicObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v4:NilClassExact = Const Value(nil) + Jump bb1(v0, v4, v20) + bb1(v6:BasicObject, v7:NilClassExact, v8:BasicObject[VALUE(0x1008)]): + v11:BasicObject = SendWithoutBlock v8, :new + Jump bb2(v6, v11, v7) + bb2(v13:BasicObject, v14:BasicObject, v15:NilClassExact): + Return v14 "#]]); } @@ -5238,15 +5446,15 @@ mod opt_tests { bb0(v0:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, C) - v21:BasicObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - v3:NilClassExact = Const Value(nil) - v4:Fixnum[1] = Const Value(1) - Jump bb1(v0, v3, v21, v4) - bb1(v6:BasicObject, v7:NilClassExact, v8:BasicObject[VALUE(0x1008)], v9:Fixnum[1]): - v12:BasicObject = SendWithoutBlock v8, :new, v9 - Jump bb2(v6, v12, v7) - bb2(v14:BasicObject, v15:BasicObject, v16:NilClassExact): - Return v15 + v22:BasicObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v4:NilClassExact = Const Value(nil) + v5:Fixnum[1] = Const Value(1) + Jump bb1(v0, v4, v22, v5) + bb1(v7:BasicObject, v8:NilClassExact, v9:BasicObject[VALUE(0x1008)], v10:Fixnum[1]): + v13:BasicObject = SendWithoutBlock v9, :new, v10 + Jump bb2(v7, v13, v8) + bb2(v15:BasicObject, v16:BasicObject, v17:NilClassExact): + Return v16 "#]]); } @@ -5306,4 +5514,228 @@ mod opt_tests { Return v2 "#]]); } + + #[test] + fn test_elide_freeze_with_frozen_hash() { + eval(" + def test = {}.freeze + "); + assert_optimized_method_hir("test", expect![[r#" + fn test: + bb0(v0:BasicObject): + v3:HashExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + PatchPoint BOPRedefined(HASH_REDEFINED_OP_FLAG, BOP_FREEZE) + Return v3 + "#]]); + } + + #[test] + fn test_elide_freeze_with_refrozen_hash() { + eval(" + def test = {}.freeze.freeze + "); + assert_optimized_method_hir("test", expect![[r#" + fn test: + bb0(v0:BasicObject): + v3:HashExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + PatchPoint BOPRedefined(HASH_REDEFINED_OP_FLAG, BOP_FREEZE) + PatchPoint BOPRedefined(HASH_REDEFINED_OP_FLAG, BOP_FREEZE) + Return v3 + "#]]); + } + + #[test] + fn test_no_elide_freeze_with_unfrozen_hash() { + eval(" + def test = {}.dup.freeze + "); + assert_optimized_method_hir("test", expect![[r#" + fn test: + bb0(v0:BasicObject): + v3:HashExact = NewHash + v5:BasicObject = SendWithoutBlock v3, :dup + v7:BasicObject = SendWithoutBlock v5, :freeze + Return v7 + "#]]); + } + + #[test] + fn test_no_elide_freeze_hash_with_args() { + eval(" + def test = {}.freeze(nil) + "); + assert_optimized_method_hir("test", expect![[r#" + fn test: + bb0(v0:BasicObject): + v3:HashExact = NewHash + v4:NilClassExact = Const Value(nil) + v6:BasicObject = SendWithoutBlock v3, :freeze, v4 + Return v6 + "#]]); + } + + #[test] + fn test_elide_freeze_with_frozen_ary() { + eval(" + def test = [].freeze + "); + assert_optimized_method_hir("test", expect![[r#" + fn test: + bb0(v0:BasicObject): + v3:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) + Return v3 + "#]]); + } + + #[test] + fn test_elide_freeze_with_refrozen_ary() { + eval(" + def test = [].freeze.freeze + "); + assert_optimized_method_hir("test", expect![[r#" + fn test: + bb0(v0:BasicObject): + v3:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) + PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) + Return v3 + "#]]); + } + + #[test] + fn test_no_elide_freeze_with_unfrozen_ary() { + eval(" + def test = [].dup.freeze + "); + assert_optimized_method_hir("test", expect![[r#" + fn test: + bb0(v0:BasicObject): + v3:ArrayExact = NewArray + v5:BasicObject = SendWithoutBlock v3, :dup + v7:BasicObject = SendWithoutBlock v5, :freeze + Return v7 + "#]]); + } + + #[test] + fn test_no_elide_freeze_ary_with_args() { + eval(" + def test = [].freeze(nil) + "); + assert_optimized_method_hir("test", expect![[r#" + fn test: + bb0(v0:BasicObject): + v3:ArrayExact = NewArray + v4:NilClassExact = Const Value(nil) + v6:BasicObject = SendWithoutBlock v3, :freeze, v4 + Return v6 + "#]]); + } + + #[test] + fn test_elide_freeze_with_frozen_str() { + eval(" + def test = ''.freeze + "); + assert_optimized_method_hir("test", expect![[r#" + fn test: + bb0(v0:BasicObject): + v3:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_FREEZE) + Return v3 + "#]]); + } + + #[test] + fn test_elide_freeze_with_refrozen_str() { + eval(" + def test = ''.freeze.freeze + "); + assert_optimized_method_hir("test", expect![[r#" + fn test: + bb0(v0:BasicObject): + v3:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_FREEZE) + PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_FREEZE) + Return v3 + "#]]); + } + + #[test] + fn test_no_elide_freeze_with_unfrozen_str() { + eval(" + def test = ''.dup.freeze + "); + assert_optimized_method_hir("test", expect![[r#" + fn test: + bb0(v0:BasicObject): + v2:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v3:StringExact = StringCopy v2 + v5:BasicObject = SendWithoutBlock v3, :dup + v7:BasicObject = SendWithoutBlock v5, :freeze + Return v7 + "#]]); + } + + #[test] + fn test_no_elide_freeze_str_with_args() { + eval(" + def test = ''.freeze(nil) + "); + assert_optimized_method_hir("test", expect![[r#" + fn test: + bb0(v0:BasicObject): + v2:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v3:StringExact = StringCopy v2 + v4:NilClassExact = Const Value(nil) + v6:BasicObject = SendWithoutBlock v3, :freeze, v4 + Return v6 + "#]]); + } + + #[test] + fn test_elide_uminus_with_frozen_str() { + eval(" + def test = -'' + "); + assert_optimized_method_hir("test", expect![[r#" + fn test: + bb0(v0:BasicObject): + v3:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_UMINUS) + Return v3 + "#]]); + } + + #[test] + fn test_elide_uminus_with_refrozen_str() { + eval(" + def test = -''.freeze + "); + assert_optimized_method_hir("test", expect![[r#" + fn test: + bb0(v0:BasicObject): + v3:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_FREEZE) + PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_UMINUS) + Return v3 + "#]]); + } + + #[test] + fn test_no_elide_uminus_with_unfrozen_str() { + eval(" + def test = -''.dup + "); + assert_optimized_method_hir("test", expect![[r#" + fn test: + bb0(v0:BasicObject): + v2:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v3:StringExact = StringCopy v2 + v5:BasicObject = SendWithoutBlock v3, :dup + v7:BasicObject = SendWithoutBlock v5, :-@ + Return v7 + "#]]); + } } |