summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gdbinit4
-rwxr-xr-x.github/actions/compilers/entrypoint.sh74
-rw-r--r--.github/actions/launchable/setup/action.yml99
-rw-r--r--.github/actions/setup/directories/action.yml2
-rw-r--r--.github/workflows/auto_request_review.yml2
-rw-r--r--.github/workflows/bundled_gems.yml4
-rw-r--r--.github/workflows/check_misc.yml10
-rw-r--r--.github/workflows/default_gems.yml4
-rw-r--r--.github/workflows/dependabot_automerge.yml4
-rw-r--r--.github/workflows/macos.yml4
-rw-r--r--.github/workflows/release.yml2
-rw-r--r--.github/workflows/scorecards.yml44
-rw-r--r--.github/workflows/ubuntu.yml4
-rw-r--r--.github/workflows/windows.yml2
-rw-r--r--.github/workflows/wsl.yml5
-rw-r--r--.github/workflows/zjit-macos.yml43
-rw-r--r--.github/workflows/zjit-ubuntu.yml43
-rw-r--r--NEWS.md43
-rw-r--r--ast.c10
-rw-r--r--benchmark/README.md2
-rw-r--r--benchmark/nilclass.yml6
-rw-r--r--bootstraptest/test_yjit.rb2
-rw-r--r--class.c12
-rw-r--r--common.mk11
-rw-r--r--compile.c29
-rw-r--r--complex.c16
-rw-r--r--debug_counter.h1
-rw-r--r--doc/distribution.md2
-rw-r--r--doc/globals.rdoc2
-rw-r--r--doc/maintainers.md6
-rw-r--r--doc/standard_library.md2
-rw-r--r--enum.c5
-rw-r--r--enumerator.c94
-rw-r--r--ext/date/date_core.c39
-rw-r--r--ext/date/zonetab.h33
-rw-r--r--ext/json/lib/json/common.rb2
-rw-r--r--ext/objspace/objspace.c1
-rw-r--r--ext/objspace/objspace_dump.c20
-rw-r--r--ext/socket/lib/socket.rb34
-rw-r--r--ext/strscan/extconf.rb4
-rw-r--r--ext/win32/lib/win32/registry.rb6
-rw-r--r--file.c4
-rw-r--r--gc.c195
-rw-r--r--gc/mmtk/src/abi.rs7
-rw-r--r--gems/bundled_gems14
-rw-r--r--hash.c10
-rw-r--r--imemo.c152
-rw-r--r--include/ruby/internal/fl_type.h35
-rw-r--r--internal/class.h69
-rw-r--r--internal/imemo.h56
-rw-r--r--internal/string.h1
-rw-r--r--internal/thread.h3
-rw-r--r--internal/variable.h6
-rw-r--r--io_buffer.c33
-rw-r--r--iseq.c4
-rw-r--r--lib/bundler/cli.rb4
-rw-r--r--lib/bundler/cli/config.rb4
-rw-r--r--lib/bundler/cli/install.rb4
-rw-r--r--lib/bundler/cli/update.rb2
-rw-r--r--lib/bundler/definition.rb4
-rw-r--r--lib/bundler/dsl.rb2
-rw-r--r--lib/bundler/environment_preserver.rb1
-rw-r--r--lib/bundler/feature_flag.rb24
-rw-r--r--lib/bundler/lazy_specification.rb8
-rw-r--r--lib/bundler/lockfile_parser.rb5
-rw-r--r--lib/bundler/man/bundle-add.12
-rw-r--r--lib/bundler/man/bundle-binstubs.12
-rw-r--r--lib/bundler/man/bundle-cache.12
-rw-r--r--lib/bundler/man/bundle-check.12
-rw-r--r--lib/bundler/man/bundle-clean.12
-rw-r--r--lib/bundler/man/bundle-config.1268
-rw-r--r--lib/bundler/man/bundle-config.1.ronn2
-rw-r--r--lib/bundler/man/bundle-console.12
-rw-r--r--lib/bundler/man/bundle-doctor.12
-rw-r--r--lib/bundler/man/bundle-env.12
-rw-r--r--lib/bundler/man/bundle-exec.12
-rw-r--r--lib/bundler/man/bundle-fund.12
-rw-r--r--lib/bundler/man/bundle-gem.1105
-rw-r--r--lib/bundler/man/bundle-gem.1.ronn6
-rw-r--r--lib/bundler/man/bundle-help.12
-rw-r--r--lib/bundler/man/bundle-info.12
-rw-r--r--lib/bundler/man/bundle-init.12
-rw-r--r--lib/bundler/man/bundle-inject.12
-rw-r--r--lib/bundler/man/bundle-install.12
-rw-r--r--lib/bundler/man/bundle-issue.12
-rw-r--r--lib/bundler/man/bundle-licenses.12
-rw-r--r--lib/bundler/man/bundle-list.12
-rw-r--r--lib/bundler/man/bundle-lock.12
-rw-r--r--lib/bundler/man/bundle-open.12
-rw-r--r--lib/bundler/man/bundle-outdated.12
-rw-r--r--lib/bundler/man/bundle-platform.12
-rw-r--r--lib/bundler/man/bundle-plugin.12
-rw-r--r--lib/bundler/man/bundle-pristine.12
-rw-r--r--lib/bundler/man/bundle-remove.12
-rw-r--r--lib/bundler/man/bundle-show.12
-rw-r--r--lib/bundler/man/bundle-update.12
-rw-r--r--lib/bundler/man/bundle-version.12
-rw-r--r--lib/bundler/man/bundle-viz.12
-rw-r--r--lib/bundler/man/bundle.12
-rw-r--r--lib/bundler/man/gemfile.52
-rw-r--r--lib/bundler/rubygems_ext.rb12
-rw-r--r--lib/bundler/rubygems_integration.rb14
-rw-r--r--lib/bundler/self_manager.rb1
-rw-r--r--lib/bundler/source/git/git_proxy.rb2
-rw-r--r--lib/bundler/source/path.rb7
-rw-r--r--lib/bundler/source_map.rb2
-rw-r--r--lib/bundler/templates/newgem/github/workflows/main.yml.tt2
-rw-r--r--lib/bundler/version.rb2
-rw-r--r--lib/net/http/generic_request.rb34
-rw-r--r--lib/prism/polyfill/scan_byte.rb14
-rw-r--r--lib/prism/prism.gemspec1
-rw-r--r--lib/prism/translation/parser/lexer.rb7
-rw-r--r--lib/rubygems.rb2
-rw-r--r--lib/rubygems/bundler_integration.rb23
-rw-r--r--lib/rubygems/commands/pristine_command.rb21
-rw-r--r--lib/rubygems/ext/cargo_builder.rb4
-rw-r--r--lib/rubygems/request_set.rb9
-rw-r--r--lib/rubygems/resolver.rb2
-rw-r--r--lib/rubygems/ssl_certs/rubygems.org/GlobalSign.pem (renamed from lib/rubygems/ssl_certs/rubygems.org/GlobalSignRootCA_R3.pem)0
-rw-r--r--lib/rubygems/ssl_certs/rubygems.org/GlobalSignRootCA.pem21
-rw-r--r--lib/tempfile.rb2
-rw-r--r--lib/weakref.rb2
-rw-r--r--marshal.c6
-rw-r--r--[-rwxr-xr-x]misc/lldb_cruby.py1
-rw-r--r--misc/lldb_rb/commands/print_flags_command.py2
-rw-r--r--misc/tsan_suppressions.txt12
-rw-r--r--namespace.c3
-rw-r--r--nilclass.rb38
-rw-r--r--node_dump.c6
-rw-r--r--object.c8
-rw-r--r--parse.y42
-rw-r--r--prism_compile.c20
-rw-r--r--proc.c3
-rw-r--r--ractor.c145
-rw-r--r--ractor_sync.c3
-rw-r--r--rational.c35
-rw-r--r--re.c18
-rw-r--r--ruby_atomic.h6
-rw-r--r--rubyparser.h6
-rw-r--r--scheduler.c29
-rw-r--r--shape.c196
-rw-r--r--shape.h95
-rwxr-xr-xspec/bin/rspec6
-rw-r--r--spec/bundler/bundler/cli_spec.rb6
-rw-r--r--spec/bundler/bundler/current_ruby_spec.rb6
-rw-r--r--spec/bundler/bundler/dsl_spec.rb2
-rw-r--r--spec/bundler/bundler/friendly_errors_spec.rb3
-rw-r--r--spec/bundler/cache/path_spec.rb4
-rw-r--r--spec/bundler/commands/binstubs_spec.rb2
-rw-r--r--spec/bundler/commands/cache_spec.rb6
-rw-r--r--spec/bundler/commands/check_spec.rb4
-rw-r--r--spec/bundler/commands/clean_spec.rb6
-rw-r--r--spec/bundler/commands/exec_spec.rb21
-rw-r--r--spec/bundler/commands/inject_spec.rb4
-rw-r--r--spec/bundler/commands/install_spec.rb57
-rw-r--r--spec/bundler/commands/newgem_spec.rb26
-rw-r--r--spec/bundler/commands/outdated_spec.rb4
-rw-r--r--spec/bundler/commands/platform_spec.rb8
-rw-r--r--spec/bundler/commands/post_bundle_message_spec.rb2
-rw-r--r--spec/bundler/commands/remove_spec.rb2
-rw-r--r--spec/bundler/commands/show_spec.rb4
-rw-r--r--spec/bundler/commands/update_spec.rb6
-rw-r--r--spec/bundler/commands/version_spec.rb12
-rw-r--r--spec/bundler/commands/viz_spec.rb2
-rw-r--r--spec/bundler/install/deploy_spec.rb2
-rw-r--r--spec/bundler/install/gemfile/gemspec_spec.rb19
-rw-r--r--spec/bundler/install/gemfile/git_spec.rb2
-rw-r--r--spec/bundler/install/gemfile/groups_spec.rb20
-rw-r--r--spec/bundler/install/gemfile/path_spec.rb2
-rw-r--r--spec/bundler/install/gemfile/sources_spec.rb44
-rw-r--r--spec/bundler/install/gems/compact_index_spec.rb14
-rw-r--r--spec/bundler/install/gems/dependency_api_spec.rb16
-rw-r--r--spec/bundler/install/gems/mirror_probe_spec.rb49
-rw-r--r--spec/bundler/install/gems/standalone_spec.rb4
-rw-r--r--spec/bundler/install/path_spec.rb6
-rw-r--r--spec/bundler/install/redownload_spec.rb2
-rw-r--r--spec/bundler/lock/lockfile_spec.rb2
-rw-r--r--spec/bundler/other/major_deprecation_spec.rb122
-rw-r--r--spec/bundler/plugins/install_spec.rb2
-rw-r--r--spec/bundler/realworld/slow_perf_spec.rb2
-rw-r--r--spec/bundler/runtime/env_helpers_spec.rb8
-rw-r--r--spec/bundler/runtime/executable_spec.rb4
-rw-r--r--spec/bundler/runtime/self_management_spec.rb4
-rw-r--r--spec/bundler/runtime/setup_spec.rb5
-rw-r--r--spec/bundler/support/checksums.rb2
-rw-r--r--spec/bundler/support/path.rb2
-rw-r--r--spec/bundler/update/redownload_spec.rb4
-rw-r--r--spec/ruby/core/kernel/caller_locations_spec.rb14
-rw-r--r--spec/ruby/core/kernel/caller_spec.rb25
-rw-r--r--spec/ruby/library/net-http/http/post_spec.rb8
-rw-r--r--spec/ruby/library/net-http/httpgenericrequest/exec_spec.rb54
-rw-r--r--spec/ruby/optional/capi/digest_spec.rb6
-rw-r--r--spec/ruby/optional/capi/string_spec.rb4
-rw-r--r--st.c9
-rw-r--r--string.c13
-rw-r--r--struct.c3
-rw-r--r--test/-ext-/bug_reporter/test_bug_reporter.rb2
-rw-r--r--test/-ext-/gvl/test_last_thread.rb3
-rw-r--r--test/date/test_date.rb4
-rw-r--r--test/date/test_switch_hitter.rb5
-rw-r--r--test/net/http/test_http.rb14
-rw-r--r--test/net/http/utils.rb13
-rw-r--r--test/prism/fixtures/strings.txt4
-rw-r--r--test/prism/lex_test.rb2
-rw-r--r--test/ruby/namespace/instance_variables.rb21
-rw-r--r--test/ruby/test_ast.rb18
-rw-r--r--test/ruby/test_backtrace.rb6
-rw-r--r--test/ruby/test_compile_prism.rb3
-rw-r--r--test/ruby/test_data.rb6
-rw-r--r--test/ruby/test_encoding.rb2
-rw-r--r--test/ruby/test_gc.rb8
-rw-r--r--test/ruby/test_io_buffer.rb23
-rw-r--r--test/ruby/test_namespace.rb20
-rw-r--r--test/ruby/test_object_id.rb46
-rw-r--r--test/ruby/test_ractor.rb35
-rw-r--r--test/ruby/test_rubyoptions.rb29
-rw-r--r--test/ruby/test_struct.rb6
-rw-r--r--test/ruby/test_variable.rb14
-rw-r--r--test/ruby/test_vm_dump.rb2
-rw-r--r--test/ruby/test_zjit.rb168
-rw-r--r--test/rubygems/helper.rb8
-rw-r--r--test/rubygems/test_gem_commands_install_command.rb59
-rw-r--r--test/rubygems/test_gem_commands_pristine_command.rb54
-rw-r--r--test/rubygems/test_gem_commands_setup_command.rb9
-rw-r--r--test/rubygems/test_gem_dependency_installer.rb58
-rw-r--r--test/rubygems/test_gem_ext_cargo_builder.rb52
-rw-r--r--test/rubygems/test_gem_installer.rb50
-rw-r--r--test/socket/test_socket.rb26
-rw-r--r--thread.c74
-rw-r--r--thread_none.c6
-rw-r--r--thread_pthread.c25
-rw-r--r--thread_win32.c6
-rw-r--r--time.c72
-rwxr-xr-x[-rw-r--r--]tool/auto-style.rb21
-rw-r--r--tool/lib/_tmpdir.rb6
-rw-r--r--tool/lib/core_assertions.rb5
-rw-r--r--tool/lib/dump.gdb17
-rw-r--r--tool/lib/dump.lldb13
-rw-r--r--tool/lib/envutil.rb75
-rw-r--r--tool/lib/test/unit.rb8
-rwxr-xr-xtool/sync_default_gems.rb8
-rw-r--r--tool/test/testunit/test_parallel.rb26
-rw-r--r--tool/test/testunit/tests_for_parallel/ptest_forth.rb8
-rw-r--r--variable.c992
-rw-r--r--variable.h13
-rw-r--r--vm.c13
-rw-r--r--vm_backtrace.c68
-rw-r--r--vm_callinfo.h5
-rw-r--r--vm_insnhelper.c53
-rw-r--r--yjit.c8
-rw-r--r--yjit/bindgen/src/main.rs3
-rw-r--r--yjit/src/asm/x86_64/mod.rs10
-rw-r--r--yjit/src/asm/x86_64/tests.rs1
-rw-r--r--yjit/src/backend/x86_64/mod.rs30
-rw-r--r--yjit/src/codegen.rs10
-rw-r--r--yjit/src/cruby.rs12
-rw-r--r--yjit/src/cruby_bindings.inc.rs28
-rw-r--r--zjit/bindgen/src/main.rs1
-rw-r--r--zjit/src/asm/mod.rs14
-rw-r--r--zjit/src/asm/x86_64/mod.rs10
-rw-r--r--zjit/src/asm/x86_64/tests.rs10
-rw-r--r--zjit/src/assertions.rs21
-rw-r--r--zjit/src/backend/arm64/mod.rs49
-rw-r--r--zjit/src/backend/lir.rs26
-rw-r--r--zjit/src/backend/x86_64/mod.rs67
-rw-r--r--zjit/src/codegen.rs205
-rw-r--r--zjit/src/cruby.rs18
-rw-r--r--zjit/src/cruby_bindings.inc.rs25
-rw-r--r--zjit/src/hir.rs932
-rw-r--r--zjit/src/lib.rs2
-rw-r--r--zjit/src/state.rs30
271 files changed, 4780 insertions, 2362 deletions
diff --git a/.gdbinit b/.gdbinit
index a19a9bfc87..f624456d04 100644
--- a/.gdbinit
+++ b/.gdbinit
@@ -185,8 +185,8 @@ define rp
print (struct RBasic *)($arg0)
else
if ($flags & RUBY_T_MASK) == RUBY_T_DATA
- if ((struct RTypedData *)($arg0))->typed_flag == 1
- printf "%sT_DATA%s(%s): ", $color_type, $color_end, ((struct RTypedData *)($arg0))->type->wrap_struct_name
+ if ((struct RTypedData *)($arg0))->type & 1
+ printf "%sT_DATA%s(%s): ", $color_type, $color_end, ((const rb_data_type_t *)(((struct RTypedData *)($arg0))->type & ~1))->wrap_struct_name
print (struct RTypedData *)($arg0)
else
printf "%sT_DATA%s: ", $color_type, $color_end
diff --git a/.github/actions/compilers/entrypoint.sh b/.github/actions/compilers/entrypoint.sh
index 503143b293..1de7fce1d3 100755
--- a/.github/actions/compilers/entrypoint.sh
+++ b/.github/actions/compilers/entrypoint.sh
@@ -75,6 +75,18 @@ tests=''
spec_opts=''
# Launchable
+launchable_record_session() {
+ launchable record session \
+ --build "${build_name}" \
+ --flavor test_task=$1 \
+ --flavor workflow=Compilations \
+ --flavor with-gcc="${INPUT_WITH_GCC}" \
+ --flavor CFLAGS="${INPUT_CFLAGS}" \
+ --flavor CXXFLAGS="${INPUT_CXXFLAGS}" \
+ --flavor optflags="${INPUT_OPTFLAGS}" \
+ --flavor cppflags="${INPUT_CPPFLAGS}" \
+ --test-suite ${2-$1}
+}
setup_launchable() {
pushd ${srcdir}
# To prevent a slowdown in CI, disable request retries when the Launchable server is unstable.
@@ -85,56 +97,23 @@ setup_launchable() {
export LAUNCHABLE_SESSION_DIR=${builddir}
local github_ref="${GITHUB_REF//\//_}"
local build_name="${github_ref}"_"${GITHUB_PR_HEAD_SHA}"
- btests+=--launchable-test-reports="${btest_report_path}"
launchable record build --name "${build_name}" || true
- launchable record session \
- --build "${build_name}" \
- --flavor test_task=test \
- --flavor workflow=Compilations \
- --flavor with-gcc="${INPUT_WITH_GCC}" \
- --flavor CFLAGS="${INPUT_CFLAGS}" \
- --flavor CXXFLAGS="${INPUT_CXXFLAGS}" \
- --flavor optflags="${INPUT_OPTFLAGS}" \
- --flavor cppflags="${INPUT_CPPFLAGS}" \
- --test-suite btest \
- > "${builddir}"/${btest_session_file} \
- || true
+ btest_session=$(launchable_record_session test btest) \
+ && btests+=--launchable-test-reports="${btest_report_path}" || :
if [ "$INPUT_CHECK" = "true" ]; then
- tests+=--launchable-test-reports="${test_report_path}"
- launchable record session \
- --build "${build_name}" \
- --flavor test_task=test-all \
- --flavor workflow=Compilations \
- --flavor with-gcc="${INPUT_WITH_GCC}" \
- --flavor CFLAGS="${INPUT_CFLAGS}" \
- --flavor CXXFLAGS="${INPUT_CXXFLAGS}" \
- --flavor optflags="${INPUT_OPTFLAGS}" \
- --flavor cppflags="${INPUT_CPPFLAGS}" \
- --test-suite test-all \
- > "${builddir}"/${test_all_session_file} \
- || true
+ test_all_session=$(launchable_record_session test-all) \
+ && tests+=--launchable-test-reports="${test_report_path}" || :
mkdir "${builddir}"/"${test_spec_report_path}"
- spec_opts+=--launchable-test-reports="${test_spec_report_path}"
- launchable record session \
- --build "${build_name}" \
- --flavor test_task=test-spec \
- --flavor workflow=Compilations \
- --flavor with-gcc="${INPUT_WITH_GCC}" \
- --flavor CFLAGS="${INPUT_CFLAGS}" \
- --flavor CXXFLAGS="${INPUT_CXXFLAGS}" \
- --flavor optflags="${INPUT_OPTFLAGS}" \
- --flavor cppflags="${INPUT_CPPFLAGS}" \
- --test-suite test-spec \
- > "${builddir}"/${test_spec_session_file} \
- || true
+ test_spec_session=$(launchable_record_session test-spec) \
+ && spec_opts+=--launchable-test-reports="${test_spec_report_path}" || :
fi
}
launchable_record_test() {
pushd "${builddir}"
- grouped launchable record tests --session "$(cat "${btest_session_file}")" raw "${btest_report_path}" || true
+ grouped launchable record tests --session "${btest_session}" raw "${btest_report_path}" || true
if [ "$INPUT_CHECK" = "true" ]; then
- grouped launchable record tests --session "$(cat "${test_all_session_file}")" raw "${test_report_path}" || true
- grouped launchable record tests --session "$(cat "${test_spec_session_file}")" raw "${test_spec_report_path}"/* || true
+ grouped launchable record tests --session "${test_all_session}" raw "${test_report_path}" || true
+ grouped launchable record tests --session "${test_spec_session}" raw "${test_spec_report_path}"/* || true
fi
}
if [ "$LAUNCHABLE_ENABLED" = "true" ]; then
@@ -142,14 +121,13 @@ if [ "$LAUNCHABLE_ENABLED" = "true" ]; then
btest_report_path='launchable_bootstraptest.json'
test_report_path='launchable_test_all.json'
test_spec_report_path='launchable_test_spec_report'
- test_all_session_file='launchable_test_all_session.txt'
- btest_session_file='launchable_btest_session.txt'
- test_spec_session_file='launchable_test_spec_session.txt'
- setup_launchable & setup_pid=$!
- (sleep 180; echo "setup_launchable timed out; killing"; kill "$setup_pid" 2> /dev/null) & sleep_pid=$!
+ setup_pid=$$
+ (sleep 180; echo "setup_launchable timed out; killing"; kill -INT "-$setup_pid" 2> /dev/null) & sleep_pid=$!
launchable_failed=false
- wait -f "$setup_pid" || launchable_failed=true
+ trap "launchable_failed=true" INT
+ setup_launchable
kill "$sleep_pid" 2> /dev/null
+ trap - INT
echo "::endgroup::"
$launchable_failed || trap launchable_record_test EXIT
fi
diff --git a/.github/actions/launchable/setup/action.yml b/.github/actions/launchable/setup/action.yml
index 09a70516ae..1cd28bf2d7 100644
--- a/.github/actions/launchable/setup/action.yml
+++ b/.github/actions/launchable/setup/action.yml
@@ -111,9 +111,6 @@ runs:
echo test_all_enabled="${test_all_enabled}" >> $GITHUB_OUTPUT
echo btest_enabled="${btest_enabled}" >> $GITHUB_OUTPUT
echo test_spec_enabled="${test_spec_enabled}" >> $GITHUB_OUTPUT
- echo test_all_session_file='launchable_test_all_session.txt' >> $GITHUB_OUTPUT
- echo btest_session_file='launchable_btest_session.txt' >> $GITHUB_OUTPUT
- echo test_spec_session_file='launchable_test_spec_session.txt' >> $GITHUB_OUTPUT
echo test_all_report_file='launchable_test_all_report.json' >> $GITHUB_OUTPUT
echo btest_report_file='launchable_btest_report.json' >> $GITHUB_OUTPUT
echo test_spec_report_dir='launchable_test_spec_report' >> $GITHUB_OUTPUT
@@ -169,88 +166,48 @@ runs:
btest_test_suite="yjit-${btest_test_suite}"
test_spec_test_suite="yjit-${test_spec_test_suite}"
fi
- launchable record build --name "${build_name}"
- if [ "${test_all_enabled}" = "true" ]; then
- launchable record session \
+ # launchable_setup target var -- refers ${target} prefixed variables
+ launchable_setup() {
+ local target=$1 session
+ eval [ "\${${target}_enabled}" = "true" ] || return
+ eval local suite=\${${target}_test_suite}
+ session=$(launchable record session \
--build "${build_name}" \
--observation \
--flavor os="${{ inputs.os }}" \
--flavor test_task="${{ inputs.test-task }}" \
--flavor test_opts="${test_opts}" \
--flavor workflow="${{ github.workflow }}" \
- --test-suite ${test_all_test_suite} \
- > "${test_all_session_file}"
+ --test-suite ${suite} \
+ )
launchable subset \
--get-tests-from-previous-sessions \
--non-blocking \
--target 90% \
- --session "$(cat "${test_all_session_file}")" \
+ --session "${session}" \
raw > /dev/null
- echo "TESTS=${TESTS} --launchable-test-reports=${test_all_report_file}" >> $GITHUB_ENV
+ echo "${target}_session=${session}" >> $GITHUB_OUTPUT
+ }
+
+ launchable record build --name "${build_name}"
+ if launchable_setup test_all; then
+ echo "TESTS=${TESTS:+$TESTS }--launchable-test-reports=${test_all_report_file}" >> $GITHUB_ENV
fi
- if [ "${btest_enabled}" = "true" ]; then
- launchable record session \
- --build "${build_name}" \
- --observation \
- --flavor os="${{ inputs.os }}" \
- --flavor test_task="${{ inputs.test-task }}" \
- --flavor test_opts="${test_opts}" \
- --flavor workflow="${{ github.workflow }}" \
- --test-suite ${btest_test_suite} \
- > "${btest_session_file}"
- launchable subset \
- --get-tests-from-previous-sessions \
- --non-blocking \
- --target 90% \
- --session "$(cat "${btest_session_file}")" \
- raw > /dev/null
- echo "BTESTS=${BTESTS} --launchable-test-reports=${btest_report_file}" >> $GITHUB_ENV
+ if launchable_setup btest; then
+ echo "BTESTS=${BTESTS:+$BTESTS }--launchable-test-reports=${btest_report_file}" >> $GITHUB_ENV
fi
- if [ "${test_spec_enabled}" = "true" ]; then
- launchable record session \
- --build "${build_name}" \
- --observation \
- --flavor os="${{ inputs.os }}" \
- --flavor test_task="${{ inputs.test-task }}" \
- --flavor test_opts="${test_opts}" \
- --flavor workflow="${{ github.workflow }}" \
- --test-suite ${test_spec_test_suite} \
- > "${test_spec_session_file}"
- launchable subset \
- --get-tests-from-previous-sessions \
- --non-blocking \
- --target 90% \
- --session "$(cat "${test_spec_session_file}")" \
- raw > /dev/null
- echo "SPECOPTS=${SPECOPTS} --launchable-test-reports=${test_spec_report_dir}" >> $GITHUB_ENV
+ if launchable_setup test_spec; then
+ echo "SPECOPTS=${SPECOPTS:$SPECOPTS }--launchable-test-reports=${test_spec_report_dir}" >> $GITHUB_ENV
fi
if: steps.enable-launchable.outputs.enable-launchable
env:
test_all_enabled: ${{ steps.global.outputs.test_all_enabled }}
btest_enabled: ${{ steps.global.outputs.btest_enabled }}
test_spec_enabled: ${{ steps.global.outputs.test_spec_enabled }}
- test_all_session_file: ${{ steps.global.outputs.test_all_session_file }}
- btest_session_file: ${{ steps.global.outputs.btest_session_file }}
- test_spec_session_file: ${{ steps.global.outputs.test_spec_session_file }}
test_all_report_file: ${{ steps.global.outputs.test_all_report_file }}
btest_report_file: ${{ steps.global.outputs.btest_report_file }}
test_spec_report_dir: ${{ steps.global.outputs.test_spec_report_dir }}
- - name: Clean up session files in Launchable
- uses: gacts/run-and-post-run@674528335da98a7afc80915ff2b4b860a0b3553a # v1.4.0
- with:
- shell: bash
- working-directory: ${{ inputs.srcdir }}
- post: |
- rm -f "${test_all_session_file}"
- rm -f "${btest_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 }}
- btest_session_file: ${{ steps.global.outputs.btest_session_file }}
- test_spec_session_file: ${{ steps.global.outputs.test_spec_session_file }}
-
- name: Clean up test results in Launchable
uses: gacts/run-and-post-run@674528335da98a7afc80915ff2b4b860a0b3553a # v1.4.0
with:
@@ -317,31 +274,31 @@ runs:
post: |
if [[ "${test_all_enabled}" = "true" ]]; then \
launchable record attachment \
- --session "$(cat "${test_all_session_file}")" \
+ --session "${test_all_session}" \
"${stdout_report_path}" \
"${stderr_report_path}"; \
launchable record tests \
- --session "$(cat "${test_all_session_file}")" \
+ --session "${test_all_session}" \
raw "${test_report_path}" || true; \
fi
if [[ "${btest_enabled}" = "true" ]]; then \
launchable record attachment \
- --session "$(cat "${btest_session_file}")" \
+ --session "${btest_session}" \
"${stdout_report_path}" \
"${stderr_report_path}"; \
launchable record tests \
- --session "$(cat "${btest_session_file}")" \
+ --session "${btest_session}" \
raw "${btest_report_path}" || true; \
fi
if [[ "${test_spec_enabled}" = "true" ]]; then \
launchable record attachment \
- --session "$(cat "${test_spec_session_file}")" \
+ --session "${test_spec_session}" \
"${stdout_report_path}" \
"${stderr_report_path}"; \
launchable record tests \
- --session "$(cat "${test_spec_session_file}")" \
+ --session "${test_spec_session}" \
raw ${test_spec_report_path}/* || true; \
fi
if: ${{ always() && steps.enable-launchable.outputs.enable-launchable }}
@@ -352,8 +309,8 @@ runs:
test_all_enabled: ${{ steps.global.outputs.test_all_enabled }}
btest_enabled: ${{ steps.global.outputs.btest_enabled }}
test_spec_enabled: ${{ steps.global.outputs.test_spec_enabled }}
- test_all_session_file: ${{ steps.global.outputs.test_all_session_file }}
- btest_session_file: ${{ steps.global.outputs.btest_session_file }}
- test_spec_session_file: ${{ steps.global.outputs.test_spec_session_file }}
+ test_all_session: ${{ steps.setup-launchable.outputs.test_all_session }}
+ btest_session: ${{ steps.setup-launchable.outputs.btest_session }}
+ test_spec_session: ${{ steps.setup-launchable.outputs.test_spec_session }}
stdout_report_path: ${{ steps.variables.outputs.stdout_report_path }}
stderr_report_path: ${{ steps.variables.outputs.stderr_report_path }}
diff --git a/.github/actions/setup/directories/action.yml b/.github/actions/setup/directories/action.yml
index 48e2c64a96..f16ce21e0e 100644
--- a/.github/actions/setup/directories/action.yml
+++ b/.github/actions/setup/directories/action.yml
@@ -183,3 +183,5 @@ runs:
${{ steps.clean.outputs.distclean }}
${{ steps.clean.outputs.remained-files }}
${{ steps.clean.outputs.final }}
+ # rmdir randomly fails due to launchable files
+ continue-on-error: true
diff --git a/.github/workflows/auto_request_review.yml b/.github/workflows/auto_request_review.yml
index a6c81c78cd..207315a084 100644
--- a/.github/workflows/auto_request_review.yml
+++ b/.github/workflows/auto_request_review.yml
@@ -17,4 +17,4 @@ jobs:
uses: necojackarc/auto-request-review@e89da1a8cd7c8c16d9de9c6e763290b6b0e3d424 # v0.13.0
with:
# scope: public_repo
- token: ${{ secrets.MATZBOT_GITHUB_TOKEN }}
+ token: ${{ secrets.MATZBOT_AUTO_REQUEST_REVIEW_TOKEN }}
diff --git a/.github/workflows/bundled_gems.yml b/.github/workflows/bundled_gems.yml
index 233f624453..788fd9be8d 100644
--- a/.github/workflows/bundled_gems.yml
+++ b/.github/workflows/bundled_gems.yml
@@ -33,11 +33,11 @@ jobs:
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
- token: ${{ (github.repository == 'ruby/ruby' && !startsWith(github.event_name, 'pull')) && secrets.MATZBOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ token: ${{ (github.repository == 'ruby/ruby' && !startsWith(github.event_name, 'pull')) && secrets.MATZBOT_AUTO_UPDATE_TOKEN || secrets.GITHUB_TOKEN }}
- uses: ./.github/actions/setup/directories
with:
- # Skip overwriting MATZBOT_GITHUB_TOKEN
+ # Skip overwriting MATZBOT_AUTO_UPDATE_TOKEN
checkout: '' # false (ref: https://github.com/actions/runner/issues/2238)
- name: Set ENV
diff --git a/.github/workflows/check_misc.yml b/.github/workflows/check_misc.yml
index 543c54a3c9..630ba3e4dc 100644
--- a/.github/workflows/check_misc.yml
+++ b/.github/workflows/check_misc.yml
@@ -20,12 +20,12 @@ jobs:
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
- token: ${{ (github.repository == 'ruby/ruby' && !startsWith(github.event_name, 'pull')) && secrets.MATZBOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ token: ${{ (github.repository == 'ruby/ruby' && !startsWith(github.event_name, 'pull')) && secrets.MATZBOT_AUTO_UPDATE_TOKEN || secrets.GITHUB_TOKEN }}
- uses: ./.github/actions/setup/directories
with:
makeup: true
- # Skip overwriting MATZBOT_GITHUB_TOKEN
+ # Skip overwriting MATZBOT_AUTO_UPDATE_TOKEN
checkout: '' # false (ref: https://github.com/actions/runner/issues/2238)
# Run this step first to make sure auto-style commits are pushed
@@ -37,10 +37,10 @@ jobs:
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/default_gems.yml b/.github/workflows/default_gems.yml
index 89a4c7dd3a..cd15e34229 100644
--- a/.github/workflows/default_gems.yml
+++ b/.github/workflows/default_gems.yml
@@ -22,7 +22,7 @@ jobs:
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
- token: ${{ (github.repository == 'ruby/ruby' && !startsWith(github.event_name, 'pull')) && secrets.MATZBOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ token: ${{ (github.repository == 'ruby/ruby' && !startsWith(github.event_name, 'pull')) && secrets.MATZBOT_AUTO_UPDATE_TOKEN || secrets.GITHUB_TOKEN }}
- id: gems
run: true
@@ -31,7 +31,7 @@ jobs:
- uses: ./.github/actions/setup/directories
with:
makeup: true
- # Skip overwriting MATZBOT_GITHUB_TOKEN
+ # Skip overwriting MATZBOT_AUTO_UPDATE_TOKEN
checkout: '' # false (ref: https://github.com/actions/runner/issues/2238)
if: ${{ steps.gems.outcome == 'success' }}
diff --git a/.github/workflows/dependabot_automerge.yml b/.github/workflows/dependabot_automerge.yml
index b1293deb62..28721a1335 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
@@ -29,4 +29,4 @@ jobs:
run: gh pr merge --auto --rebase "$PR_URL"
env:
PR_URL: ${{ github.event.pull_request.html_url }}
- GITHUB_TOKEN: ${{ secrets.MATZBOT_GITHUB_TOKEN }}
+ GITHUB_TOKEN: ${{ secrets.MATZBOT_DEPENDABOT_MERGE_TOKEN }}
diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml
index 54161f888c..d418912f35 100644
--- a/.github/workflows/macos.yml
+++ b/.github/workflows/macos.yml
@@ -107,6 +107,10 @@ jobs:
- run: make hello
+ - name: runirb
+ run: |
+ echo IRB::VERSION | make runirb RUNOPT="-- -f"
+
- name: Set test options for skipped tests
run: |
set -x
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 25916066d6..284e336a29 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -55,7 +55,7 @@ jobs:
echo $PREVIOUS_RELEASE_TAG
tool/gen-github-release.rb $PREVIOUS_RELEASE_TAG $RELEASE_TAG --no-dry-run
env:
- GITHUB_TOKEN: ${{ secrets.MATZBOT_GITHUB_WORKFLOW_TOKEN }}
+ GITHUB_TOKEN: ${{ secrets.MATZBOT_AUTO_UPDATE_TOKEN }}
- name: Update versions index
run: |
diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml
index ef36e55c16..7afd96c3bb 100644
--- a/.github/workflows/scorecards.yml
+++ b/.github/workflows/scorecards.yml
@@ -2,7 +2,7 @@
# by a third-party and are governed by separate terms of service, privacy
# policy, and support documentation.
-name: Scorecards supply-chain security
+name: Scorecard supply-chain security
on:
# For Branch-Protection check. Only the default branch is supported. See
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
@@ -10,7 +10,7 @@ on:
# To guarantee Maintained check is occasionally updated. See
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
schedule:
- - cron: '22 4 * * 2'
+ - cron: '39 3 * * 5'
# push:
# branches: [ "master" ]
@@ -19,8 +19,10 @@ permissions: read-all
jobs:
analysis:
- name: Scorecards analysis
+ name: Scorecard analysis
runs-on: ubuntu-latest
+ # `publish_results: true` only works when run from the default branch. conditional can be removed if disabled.
+ if: github.event.repository.default_branch == github.ref_name || github.event_name == 'pull_request'
permissions:
# Needed to upload the results to code-scanning dashboard.
security-events: write
@@ -31,21 +33,21 @@ jobs:
# actions: read
steps:
- - name: 'Checkout code'
+ - name: "Checkout code"
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- - name: 'Run analysis'
- uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0
+ - name: "Run analysis"
+ uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1
with:
results_file: results.sarif
results_format: sarif
- # (Optional) Read-only PAT token. Uncomment the `repo_token` line below if:
+ # (Optional) "write" PAT token. Uncomment the `repo_token` line below if:
# - you want to enable the Branch-Protection check on a *public* repository, or
- # - you are installing Scorecards on a *private* repository
- # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat.
- repo_token: ${{ secrets.SCORECARD_READ_TOKEN }}
+ # - you are installing Scorecard on a *private* repository
+ # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action?tab=readme-ov-file#authentication-with-fine-grained-pat-optional.
+ # repo_token: ${{ secrets.SCORECARD_TOKEN }}
# Public repositories:
# - Publish results to OpenSSF REST API for easy access by consumers
@@ -56,17 +58,21 @@ jobs:
# of the value entered here.
publish_results: true
+ # (Optional) Uncomment file_mode if you have a .gitattributes with files marked export-ignore
+ # file_mode: git
+
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab.
- # - name: "Upload artifact"
- # uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
- # with:
- # name: SARIF file
- # path: results.sarif
- # retention-days: 5
+ - name: "Upload artifact"
+ uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
+ with:
+ name: SARIF file
+ path: results.sarif
+ retention-days: 5
- # Upload the results to GitHub's code scanning dashboard.
- - name: 'Upload to code-scanning'
- uses: github/codeql-action/upload-sarif@df409f7d9260372bd5f19e5b04e83cb3c43714ae # v3.27.9
+ # Upload the results to GitHub's code scanning dashboard (optional).
+ # Commenting out will disable upload of results to your repo's Code Scanning dashboard
+ - name: "Upload to code-scanning"
+ uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: results.sarif
diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml
index ac7963649b..041cb412fd 100644
--- a/.github/workflows/ubuntu.yml
+++ b/.github/workflows/ubuntu.yml
@@ -99,6 +99,10 @@ jobs:
- run: $SETARCH make hello
+ - name: runirb
+ run: |
+ echo IRB::VERSION | $SETARCH make runirb RUNOPT="-- -f"
+
- name: Set test options for skipped tests
run: |
set -x
diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml
index fa3d2f659e..e0719118b4 100644
--- a/.github/workflows/windows.yml
+++ b/.github/workflows/windows.yml
@@ -123,7 +123,7 @@ jobs:
- name: Restore vcpkg artifact
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
- path: C:\Users\runneradmin\AppData\Local\vcpkg\archives
+ path: ${{ github.workspace }}/src/vcpkg_installed
key: windows-${{ matrix.os }}-vcpkg-${{ hashFiles('src/vcpkg.json') }}
- name: Install libraries with vcpkg
diff --git a/.github/workflows/wsl.yml b/.github/workflows/wsl.yml
index e6b4133b76..af490dffd7 100644
--- a/.github/workflows/wsl.yml
+++ b/.github/workflows/wsl.yml
@@ -16,7 +16,7 @@ on:
jobs:
wsl:
- runs-on: windows-latest
+ runs-on: windows-2025
if: >-
${{!(false
@@ -29,9 +29,6 @@ jobs:
)}}
steps:
- - name: Install winget
- uses: Cyberboss/install-winget@v1
-
- name: Install or update WSL
uses: Ubuntu/WSL/.github/actions/wsl-install@main
with:
diff --git a/.github/workflows/zjit-macos.yml b/.github/workflows/zjit-macos.yml
index eb7dacd4e2..fa161b31a2 100644
--- a/.github/workflows/zjit-macos.yml
+++ b/.github/workflows/zjit-macos.yml
@@ -42,6 +42,9 @@ jobs:
configure: '--enable-zjit=dev'
tests: '../src/test/ruby/test_zjit.rb'
+ - test_task: 'btest'
+ configure: '--enable-zjit=dev'
+
env:
GITPULLOPTIONS: --no-tags origin ${{ github.ref }}
RUN_OPTS: ${{ matrix.zjit_opts }}
@@ -100,6 +103,45 @@ jobs:
ruby -ne 'raise "Disassembly seems broken in dev build (output has too few lines)" unless $_.to_i > 10'
if: ${{ contains(matrix.configure, 'jit=dev') }}
+ - name: btest
+ run: |
+ RUST_BACKTRACE=1 ruby --disable=gems ../src/bootstraptest/runner.rb --ruby="./miniruby -I../src/lib -I. -I.ext/common --zjit-call-threshold=1" \
+ ../src/bootstraptest/test_attr.rb \
+ ../src/bootstraptest/test_constant_cache.rb \
+ ../src/bootstraptest/test_env.rb \
+ ../src/bootstraptest/test_finalizer.rb \
+ ../src/bootstraptest/test_flip.rb \
+ ../src/bootstraptest/test_literal.rb \
+ ../src/bootstraptest/test_literal_suffix.rb \
+ ../src/bootstraptest/test_string.rb \
+ ../src/bootstraptest/test_struct.rb \
+ ../src/bootstraptest/test_yjit_30k_ifelse.rb \
+ ../src/bootstraptest/test_yjit_30k_methods.rb
+ # ../src/bootstraptest/test_autoload.rb \
+ # ../src/bootstraptest/test_block.rb \
+ # ../src/bootstraptest/test_class.rb \
+ # ../src/bootstraptest/test_eval.rb \
+ # ../src/bootstraptest/test_exception.rb \
+ # ../src/bootstraptest/test_fiber.rb \
+ # ../src/bootstraptest/test_flow.rb \
+ # ../src/bootstraptest/test_fork.rb \
+ # ../src/bootstraptest/test_gc.rb \
+ # ../src/bootstraptest/test_insns.rb \
+ # ../src/bootstraptest/test_io.rb \
+ # ../src/bootstraptest/test_jump.rb \
+ # ../src/bootstraptest/test_load.rb \
+ # ../src/bootstraptest/test_marshal.rb \
+ # ../src/bootstraptest/test_massign.rb \
+ # ../src/bootstraptest/test_method.rb \
+ # ../src/bootstraptest/test_objectspace.rb \
+ # ../src/bootstraptest/test_proc.rb \
+ # ../src/bootstraptest/test_ractor.rb \
+ # ../src/bootstraptest/test_syntax.rb \
+ # ../src/bootstraptest/test_thread.rb \
+ # ../src/bootstraptest/test_yjit.rb \
+ # ../src/bootstraptest/test_yjit_rust_port.rb \
+ if: ${{ matrix.test_task == 'btest' }}
+
- name: make ${{ matrix.test_task }}
run: >-
make -s ${{ matrix.test_task }} ${TESTS:+TESTS="$TESTS"}
@@ -113,6 +155,7 @@ jobs:
PRECHECK_BUNDLED_GEMS: 'no'
TESTS: ${{ matrix.tests }}
continue-on-error: ${{ matrix.continue-on-test_task || false }}
+ if: ${{ matrix.test_task != 'btest' }}
result:
if: ${{ always() }}
diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml
index d5b6c71f31..7a6c1dfe0b 100644
--- a/.github/workflows/zjit-ubuntu.yml
+++ b/.github/workflows/zjit-ubuntu.yml
@@ -44,6 +44,9 @@ jobs:
configure: '--enable-zjit=dev'
tests: '../src/test/ruby/test_zjit.rb'
+ - test_task: 'btest'
+ configure: '--enable-zjit=dev'
+
env:
GITPULLOPTIONS: --no-tags origin ${{ github.ref }}
RUN_OPTS: ${{ matrix.zjit_opts }}
@@ -122,6 +125,45 @@ jobs:
run: ./miniruby --zjit -v | grep "+ZJIT"
if: ${{ matrix.configure != '--disable-zjit' }}
+ - name: btest
+ run: |
+ RUST_BACKTRACE=1 ruby --disable=gems ../src/bootstraptest/runner.rb --ruby="./miniruby -I../src/lib -I. -I.ext/common --zjit-call-threshold=1" \
+ ../src/bootstraptest/test_attr.rb \
+ ../src/bootstraptest/test_constant_cache.rb \
+ ../src/bootstraptest/test_env.rb \
+ ../src/bootstraptest/test_finalizer.rb \
+ ../src/bootstraptest/test_flip.rb \
+ ../src/bootstraptest/test_literal.rb \
+ ../src/bootstraptest/test_literal_suffix.rb \
+ ../src/bootstraptest/test_massign.rb \
+ ../src/bootstraptest/test_string.rb \
+ ../src/bootstraptest/test_struct.rb \
+ ../src/bootstraptest/test_yjit_30k_ifelse.rb \
+ ../src/bootstraptest/test_yjit_30k_methods.rb
+ # ../src/bootstraptest/test_autoload.rb \
+ # ../src/bootstraptest/test_block.rb \
+ # ../src/bootstraptest/test_class.rb \
+ # ../src/bootstraptest/test_eval.rb \
+ # ../src/bootstraptest/test_exception.rb \
+ # ../src/bootstraptest/test_fiber.rb \
+ # ../src/bootstraptest/test_flow.rb \
+ # ../src/bootstraptest/test_fork.rb \
+ # ../src/bootstraptest/test_gc.rb \
+ # ../src/bootstraptest/test_insns.rb \
+ # ../src/bootstraptest/test_io.rb \
+ # ../src/bootstraptest/test_jump.rb \
+ # ../src/bootstraptest/test_load.rb \
+ # ../src/bootstraptest/test_marshal.rb \
+ # ../src/bootstraptest/test_method.rb \
+ # ../src/bootstraptest/test_objectspace.rb \
+ # ../src/bootstraptest/test_proc.rb \
+ # ../src/bootstraptest/test_ractor.rb \
+ # ../src/bootstraptest/test_syntax.rb \
+ # ../src/bootstraptest/test_thread.rb \
+ # ../src/bootstraptest/test_yjit.rb \
+ # ../src/bootstraptest/test_yjit_rust_port.rb \
+ if: ${{ matrix.test_task == 'btest' }}
+
- name: make ${{ matrix.test_task }}
run: >-
make -s ${{ matrix.test_task }} ${TESTS:+TESTS="$TESTS"}
@@ -137,6 +179,7 @@ jobs:
LIBCLANG_PATH: ${{ matrix.libclang_path }}
TESTS: ${{ matrix.tests }}
continue-on-error: ${{ matrix.continue-on-test_task || false }}
+ if: ${{ matrix.test_task != 'btest' }}
result:
if: ${{ always() }}
diff --git a/NEWS.md b/NEWS.md
index 6c901003ba..fd02bcf349 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -16,23 +16,25 @@ Note: We're only listing outstanding class updates.
* Kernel
- * `Kernel#inspect` now check for the existence of a `#instance_variables_to_inspect` method
- allowing to control which instance variables are displayed in the `#inspect` string:
-
- ```ruby
- class DatabaseConfig
- def initialize(host, user, password)
- @host = host
- @user = user
- @password = password
+ * `Kernel#inspect` now checks for the existence of a `#instance_variables_to_inspect` method,
+ allowing control over which instance variables are displayed in the `#inspect` string:
+
+ ```ruby
+ class DatabaseConfig
+ def initialize(host, user, password)
+ @host = host
+ @user = user
+ @password = password
+ end
+
+ private def instance_variables_to_inspect = [:@host, :@user]
end
- private def instance_variables_to_inspect = [:@host, :@user]
- end
+ conf = DatabaseConfig.new("localhost", "root", "hunter2")
+ conf.inspect #=> #<DatabaseConfig:0x0000000104def350 @host="localhost", @user="root">
+ ```
- conf = DatabaseConfig.new("localhost", "root", "hunter2")
- conf.inspect #=> #<DatabaseConfig:0x0000000104def350 @host="localhost", @user="root">
- ```
+ [[Feature #21219]]
* Binding
@@ -108,11 +110,11 @@ Note: We're only listing outstanding class updates.
The following bundled gems are promoted from default gems.
-* ostruct 0.6.1
+* ostruct 0.6.2
* pstore 0.2.0
* benchmark 0.4.1
* logger 1.7.0
-* rdoc 6.14.0
+* rdoc 6.14.1
* win32ole 1.9.2
* irb 1.15.2
* reline 0.6.1
@@ -141,6 +143,7 @@ The following default gems are updated.
* stringio 3.1.8.dev
* strscan 3.1.6.dev
* uri 1.0.3
+* weakref 0.1.4
The following bundled gems are added.
@@ -151,9 +154,12 @@ The following bundled gems are updated.
* rake 13.3.0
* test-unit 3.6.8
* rexml 3.4.1
-* net-imap 0.5.8
+* net-imap 0.5.9
* net-smtp 0.5.1
+* matrix 0.4.3
+* prime 0.1.4
* rbs 3.9.4
+* debug 1.11.0
* base64 0.3.0
* bigdecimal 3.2.2
* drb 2.2.3
@@ -165,7 +171,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`
@@ -216,6 +222,7 @@ The following bundled gems are updated.
[Bug #21049]: https://bugs.ruby-lang.org/issues/21049
[Feature #21166]: https://bugs.ruby-lang.org/issues/21166
[Feature #21216]: https://bugs.ruby-lang.org/issues/21216
+[Feature #21219]: https://bugs.ruby-lang.org/issues/21219
[Feature #21258]: https://bugs.ruby-lang.org/issues/21258
[Feature #21262]: https://bugs.ruby-lang.org/issues/21262
[Feature #21287]: https://bugs.ruby-lang.org/issues/21287
diff --git a/ast.c b/ast.c
index b98fba6fab..dde42e5921 100644
--- a/ast.c
+++ b/ast.c
@@ -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/benchmark/nilclass.yml b/benchmark/nilclass.yml
index da66e71068..66234c4cdf 100644
--- a/benchmark/nilclass.yml
+++ b/benchmark/nilclass.yml
@@ -1,10 +1,16 @@
prelude: |
def a = nil
benchmark:
+ rationalize:
+ nil.rationalize
+ to_c: |
+ nil.to_c
to_i: |
nil.to_i
to_f: |
nil.to_f
+ to_r: |
+ nil.to_r
splat: |
a(*nil)
loop_count: 100000
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"
diff --git a/class.c b/class.c
index fd3276990a..506054ad68 100644
--- a/class.c
+++ b/class.c
@@ -297,16 +297,8 @@ 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);
- // TODO: consider shapes for performance
- if (RCLASSEXT_FIELDS(orig)) {
- RUBY_ASSERT(!RB_TYPE_P(klass, T_ICLASS));
- RCLASSEXT_FIELDS(ext) = (VALUE *)st_copy((st_table *)RCLASSEXT_FIELDS(orig));
- rb_autoload_copy_table_for_namespace((st_table *)RCLASSEXT_FIELDS(ext), ns);
- }
- else {
- if (!RB_TYPE_P(klass, T_ICLASS)) {
- RCLASSEXT_FIELDS(ext) = (VALUE *)st_init_numtable();
- }
+ if (orig->fields_obj) {
+ RB_OBJ_WRITE(klass, &ext->fields_obj, rb_imemo_fields_clone(orig->fields_obj));
}
if (RCLASSEXT_SHARED_CONST_TBL(orig)) {
diff --git a/common.mk b/common.mk
index 34287c5782..e5a4d34a0a 100644
--- a/common.mk
+++ b/common.mk
@@ -1427,8 +1427,8 @@ run: yes-fake miniruby$(EXEEXT) PHONY
runruby: $(PROGRAM) PHONY
RUBY_ON_BUG='gdb -x $(srcdir)/.gdbinit -p' $(RUNRUBY) $(RUNOPT0) $(TESTRUN_SCRIPT) $(RUNOPT)
-runirb: $(PROGRAM) PHONY
- RUBY_ON_BUG='gdb -x $(srcdir)/.gdbinit -p' $(RUNRUBY) $(RUNOPT0) -r irb -e 'IRB.start("make runirb")' $(RUNOPT)
+runirb: $(PROGRAM) update-default-gemspecs
+ RUBY_ON_BUG='gdb -x $(srcdir)/.gdbinit -p' $(RUNRUBY) $(RUNOPT0) -rrubygems -r irb -e 'IRB.start("make runirb")' $(RUNOPT)
parse: yes-fake miniruby$(EXEEXT) PHONY
$(BTESTRUBY) --dump=parsetree_with_comment,insns $(TESTRUN_SCRIPT)
@@ -1685,8 +1685,7 @@ test-bundler: $(TEST_RUNNABLE)-test-bundler
yes-test-bundler: $(PREPARE_BUNDLER)
$(gnumake_recursive)$(XRUBY) \
-r./$(arch)-fake \
- -e "exec(*ARGV)" -- \
- $(XRUBY) -C $(srcdir) -Ispec/bundler -Ispec/lib .bundle/bin/rspec \
+ -C $(srcdir) -Ispec/bundler -Ispec/lib spec/bin/rspec \
-r spec_helper $(RSPECOPTS) spec/bundler/$(BUNDLER_SPECS)
no-test-bundler:
@@ -8118,6 +8117,7 @@ imemo.$(OBJEXT): $(top_srcdir)/internal/namespace.h
imemo.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h
imemo.$(OBJEXT): $(top_srcdir)/internal/serial.h
imemo.$(OBJEXT): $(top_srcdir)/internal/set_table.h
+imemo.$(OBJEXT): $(top_srcdir)/internal/st.h
imemo.$(OBJEXT): $(top_srcdir)/internal/static_assert.h
imemo.$(OBJEXT): $(top_srcdir)/internal/variable.h
imemo.$(OBJEXT): $(top_srcdir)/internal/vm.h
@@ -15119,6 +15119,8 @@ re.$(OBJEXT): {$(VPATH)}missing.h
re.$(OBJEXT): {$(VPATH)}node.h
re.$(OBJEXT): {$(VPATH)}onigmo.h
re.$(OBJEXT): {$(VPATH)}oniguruma.h
+re.$(OBJEXT): {$(VPATH)}ractor.h
+re.$(OBJEXT): {$(VPATH)}ractor_core.h
re.$(OBJEXT): {$(VPATH)}re.c
re.$(OBJEXT): {$(VPATH)}re.h
re.$(OBJEXT): {$(VPATH)}regenc.h
@@ -15134,6 +15136,7 @@ re.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h
re.$(OBJEXT): {$(VPATH)}thread_native.h
re.$(OBJEXT): {$(VPATH)}util.h
re.$(OBJEXT): {$(VPATH)}vm_core.h
+re.$(OBJEXT): {$(VPATH)}vm_debug.h
re.$(OBJEXT): {$(VPATH)}vm_opts.h
regcomp.$(OBJEXT): $(hdrdir)/ruby.h
regcomp.$(OBJEXT): $(hdrdir)/ruby/ruby.h
diff --git a/compile.c b/compile.c
index cbde124516..bb0b5ac681 100644
--- a/compile.c
+++ b/compile.c
@@ -3493,7 +3493,7 @@ iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcal
iobj->insn_id = BIN(opt_ary_freeze);
iobj->operand_size = 2;
iobj->operands = compile_data_calloc2(iseq, iobj->operand_size, sizeof(VALUE));
- iobj->operands[0] = rb_cArray_empty_frozen;
+ RB_OBJ_WRITE(iseq, &iobj->operands[0], rb_cArray_empty_frozen);
iobj->operands[1] = (VALUE)ci;
ELEM_REMOVE(next);
}
@@ -3516,7 +3516,7 @@ iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcal
iobj->insn_id = BIN(opt_hash_freeze);
iobj->operand_size = 2;
iobj->operands = compile_data_calloc2(iseq, iobj->operand_size, sizeof(VALUE));
- iobj->operands[0] = rb_cHash_empty_frozen;
+ RB_OBJ_WRITE(iseq, &iobj->operands[0], rb_cHash_empty_frozen);
iobj->operands[1] = (VALUE)ci;
ELEM_REMOVE(next);
}
@@ -4094,7 +4094,7 @@ iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcal
unsigned int flags = vm_ci_flag(ci);
if ((flags & set_flags) == set_flags && !(flags & unset_flags)) {
((INSN*)niobj)->insn_id = BIN(putobject);
- OPERAND_AT(niobj, 0) = rb_hash_freeze(rb_hash_resurrect(OPERAND_AT(niobj, 0)));
+ RB_OBJ_WRITE(iseq, &OPERAND_AT(niobj, 0), rb_hash_freeze(rb_hash_resurrect(OPERAND_AT(niobj, 0))));
const struct rb_callinfo *nci = vm_ci_new(vm_ci_mid(ci),
flags & ~VM_CALL_KW_SPLAT_MUT, vm_ci_argc(ci), vm_ci_kwarg(ci));
@@ -9257,12 +9257,13 @@ compile_builtin_mandatory_only_method(rb_iseq_t *iseq, const NODE *node, const N
VALUE ast_value = rb_ruby_ast_new(RNODE(&scope_node));
- ISEQ_BODY(iseq)->mandatory_only_iseq =
+ const rb_iseq_t *mandatory_only_iseq =
rb_iseq_new_with_opt(ast_value, rb_iseq_base_label(iseq),
rb_iseq_path(iseq), rb_iseq_realpath(iseq),
nd_line(line_node), NULL, 0,
ISEQ_TYPE_METHOD, ISEQ_COMPILE_DATA(iseq)->option,
ISEQ_BODY(iseq)->variable.script_lines);
+ RB_OBJ_WRITE(iseq, &ISEQ_BODY(iseq)->mandatory_only_iseq, (VALUE)mandatory_only_iseq);
ALLOCV_END(idtmp);
return COMPILE_OK;
@@ -13288,7 +13289,7 @@ ibf_dump_catch_table(struct ibf_dump *dump, const rb_iseq_t *iseq)
}
static struct iseq_catch_table *
-ibf_load_catch_table(const struct ibf_load *load, ibf_offset_t catch_table_offset, unsigned int size)
+ibf_load_catch_table(const struct ibf_load *load, ibf_offset_t catch_table_offset, unsigned int size, const rb_iseq_t *parent_iseq)
{
if (size) {
struct iseq_catch_table *table = ruby_xmalloc(iseq_catch_table_bytes(size));
@@ -13305,7 +13306,8 @@ ibf_load_catch_table(const struct ibf_load *load, ibf_offset_t catch_table_offse
table->entries[i].cont = (unsigned int)ibf_load_small_value(load, &reading_pos);
table->entries[i].sp = (unsigned int)ibf_load_small_value(load, &reading_pos);
- table->entries[i].iseq = ibf_load_iseq(load, (const rb_iseq_t *)(VALUE)iseq_index);
+ rb_iseq_t *catch_iseq = (rb_iseq_t *)ibf_load_iseq(load, (const rb_iseq_t *)(VALUE)iseq_index);
+ RB_OBJ_WRITE(parent_iseq, UNALIGNED_MEMBER_PTR(&table->entries[i], iseq), catch_iseq);
}
return table;
}
@@ -13384,7 +13386,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;
}
@@ -13822,10 +13825,14 @@ ibf_load_iseq_each(struct ibf_load *load, rb_iseq_t *iseq, ibf_offset_t offset)
load_body->insns_info.body = ibf_load_insns_info_body(load, insns_info_body_offset, insns_info_size);
load_body->insns_info.positions = ibf_load_insns_info_positions(load, insns_info_positions_offset, insns_info_size);
load_body->local_table = ibf_load_local_table(load, local_table_offset, local_table_size);
- load_body->catch_table = ibf_load_catch_table(load, catch_table_offset, catch_table_size);
- load_body->parent_iseq = ibf_load_iseq(load, (const rb_iseq_t *)(VALUE)parent_iseq_index);
- load_body->local_iseq = ibf_load_iseq(load, (const rb_iseq_t *)(VALUE)local_iseq_index);
- load_body->mandatory_only_iseq = ibf_load_iseq(load, (const rb_iseq_t *)(VALUE)mandatory_only_iseq_index);
+ load_body->catch_table = ibf_load_catch_table(load, catch_table_offset, catch_table_size, iseq);
+ const rb_iseq_t *parent_iseq = ibf_load_iseq(load, (const rb_iseq_t *)(VALUE)parent_iseq_index);
+ const rb_iseq_t *local_iseq = ibf_load_iseq(load, (const rb_iseq_t *)(VALUE)local_iseq_index);
+ const rb_iseq_t *mandatory_only_iseq = ibf_load_iseq(load, (const rb_iseq_t *)(VALUE)mandatory_only_iseq_index);
+
+ RB_OBJ_WRITE(iseq, &load_body->parent_iseq, parent_iseq);
+ RB_OBJ_WRITE(iseq, &load_body->local_iseq, local_iseq);
+ RB_OBJ_WRITE(iseq, &load_body->mandatory_only_iseq, mandatory_only_iseq);
// This must be done after the local table is loaded.
if (load_body->param.keyword != NULL) {
diff --git a/complex.c b/complex.c
index 05c991f35b..d6daee307c 100644
--- a/complex.c
+++ b/complex.c
@@ -1927,21 +1927,6 @@ nucomp_to_c(VALUE self)
/*
* call-seq:
- * to_c -> (0+0i)
- *
- * Returns zero as a Complex:
- *
- * nil.to_c # => (0+0i)
- *
- */
-static VALUE
-nilclass_to_c(VALUE self)
-{
- return rb_complex_new1(INT2FIX(0));
-}
-
-/*
- * call-seq:
* to_c -> complex
*
* Returns +self+ as a Complex object.
@@ -2693,7 +2678,6 @@ Init_Complex(void)
rb_define_method(rb_cComplex, "to_r", nucomp_to_r, 0);
rb_define_method(rb_cComplex, "rationalize", nucomp_rationalize, -1);
rb_define_method(rb_cComplex, "to_c", nucomp_to_c, 0);
- rb_define_method(rb_cNilClass, "to_c", nilclass_to_c, 0);
rb_define_method(rb_cNumeric, "to_c", numeric_to_c, 0);
rb_define_method(rb_cString, "to_c", string_to_c, 0);
diff --git a/debug_counter.h b/debug_counter.h
index c4ee26534f..fada7513aa 100644
--- a/debug_counter.h
+++ b/debug_counter.h
@@ -315,6 +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_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
diff --git a/doc/globals.rdoc b/doc/globals.rdoc
index 9d9fc57e6e..9466005be7 100644
--- a/doc/globals.rdoc
+++ b/doc/globals.rdoc
@@ -137,7 +137,7 @@ English - <tt>$DEFAULT_INPUT</tt>.
An output stream, initially <tt>$stdout</tt>.
-English - <tt>$DEFAULT_OUTPUT
+English - <tt>$DEFAULT_OUTPUT</tt>
=== <tt>$.</tt> (Input Position)
diff --git a/doc/maintainers.md b/doc/maintainers.md
index 7c939a96c8..7d217a1665 100644
--- a/doc/maintainers.md
+++ b/doc/maintainers.md
@@ -107,11 +107,9 @@ have commit right, others don't.
* https://github.com/rubygems/rubygems
* https://rubygems.org/gems/bundler
-#### lib/cgi.rb, lib/cgi/*
+#### lib/cgi/escape.rb
* *unmaintained*
-* https://github.com/ruby/cgi
-* https://rubygems.org/gems/cgi
#### lib/English.rb
@@ -312,8 +310,6 @@ have commit right, others don't.
#### ext/cgi
* Nobuyoshi Nakada ([nobu])
-* https://github.com/ruby/cgi
-* https://rubygems.org/gems/cgi
#### ext/date
diff --git a/doc/standard_library.md b/doc/standard_library.md
index 594667b4e2..97f46bc987 100644
--- a/doc/standard_library.md
+++ b/doc/standard_library.md
@@ -34,7 +34,6 @@ of each.
## Libraries
- Bundler ([GitHub][bundler]): Manage your Ruby application's gem dependencies
-- CGI ([GitHub][cgi]): Support for the Common Gateway Interface protocol
- Delegator ([GitHub][delegate]): Provides three abilities to delegate method calls to an object
- DidYouMean ([GitHub][did_you_mean]): "Did you mean?" experience in Ruby
- English ([GitHub][English]): Provides references to special global variables with less cryptic names
@@ -137,7 +136,6 @@ of each.
[benchmark]: https://github.com/ruby/benchmark
[bigdecimal]: https://github.com/ruby/bigdecimal
[bundler]: https://github.com/rubygems/rubygems
-[cgi]: https://github.com/ruby/cgi
[csv]: https://github.com/ruby/csv
[date]: https://github.com/ruby/date
[debug]: https://github.com/ruby/debug
diff --git a/enum.c b/enum.c
index 182e4f6e83..cbf74df484 100644
--- a/enum.c
+++ b/enum.c
@@ -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/enumerator.c b/enumerator.c
index faaa77cb49..b91b2eb940 100644
--- a/enumerator.c
+++ b/enumerator.c
@@ -162,10 +162,9 @@
*/
VALUE rb_cEnumerator;
static VALUE rb_cLazy;
-static ID id_rewind, id_new, id_to_enum, id_each_entry;
+static ID id_rewind, id_to_enum, id_each_entry;
static ID id_next, id_result, id_receiver, id_arguments, id_memo, id_method, id_force;
-static ID id_begin, id_end, id_step, id_exclude_end;
-static VALUE sym_each, sym_cycle, sym_yield;
+static VALUE sym_each, sym_yield;
static VALUE lazy_use_super_method;
@@ -3748,6 +3747,55 @@ enumerator_s_product(int argc, VALUE *argv, VALUE klass)
return obj;
}
+struct arith_seq {
+ struct enumerator enumerator;
+ VALUE begin;
+ VALUE end;
+ VALUE step;
+ bool exclude_end;
+};
+
+RUBY_REFERENCES(arith_seq_refs) = {
+ RUBY_REF_EDGE(struct enumerator, obj),
+ RUBY_REF_EDGE(struct enumerator, args),
+ RUBY_REF_EDGE(struct enumerator, fib),
+ RUBY_REF_EDGE(struct enumerator, dst),
+ RUBY_REF_EDGE(struct enumerator, lookahead),
+ RUBY_REF_EDGE(struct enumerator, feedvalue),
+ RUBY_REF_EDGE(struct enumerator, stop_exc),
+ RUBY_REF_EDGE(struct enumerator, size),
+ RUBY_REF_EDGE(struct enumerator, procs),
+
+ RUBY_REF_EDGE(struct arith_seq, begin),
+ RUBY_REF_EDGE(struct arith_seq, end),
+ RUBY_REF_EDGE(struct arith_seq, step),
+ RUBY_REF_END
+};
+
+static const rb_data_type_t arith_seq_data_type = {
+ "arithmetic_sequence",
+ {
+ RUBY_REFS_LIST_PTR(arith_seq_refs),
+ RUBY_TYPED_DEFAULT_FREE,
+ NULL, // Nothing allocated externally, so don't need a memsize function
+ NULL,
+ },
+ .parent = &enumerator_data_type,
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_DECL_MARKING | RUBY_TYPED_EMBEDDABLE
+};
+
+static VALUE
+arith_seq_allocate(VALUE klass)
+{
+ struct arith_seq *ptr;
+ VALUE enum_obj;
+
+ enum_obj = TypedData_Make_Struct(klass, struct arith_seq, &arith_seq_data_type, ptr);
+ ptr->enumerator.obj = Qundef;
+
+ return enum_obj;
+}
+
/*
* Document-class: Enumerator::ArithmeticSequence
*
@@ -3765,12 +3813,16 @@ rb_arith_seq_new(VALUE obj, VALUE meth, int argc, VALUE const *argv,
rb_enumerator_size_func *size_fn,
VALUE beg, VALUE end, VALUE step, int excl)
{
- VALUE aseq = enumerator_init(enumerator_allocate(rb_cArithSeq),
+ VALUE aseq = enumerator_init(arith_seq_allocate(rb_cArithSeq),
obj, meth, argc, argv, size_fn, Qnil, rb_keyword_given_p());
- rb_ivar_set(aseq, id_begin, beg);
- rb_ivar_set(aseq, id_end, end);
- rb_ivar_set(aseq, id_step, step);
- rb_ivar_set(aseq, id_exclude_end, RBOOL(excl));
+ struct arith_seq *ptr;
+ TypedData_Get_Struct(aseq, struct arith_seq, &enumerator_data_type, ptr);
+
+ RB_OBJ_WRITE(aseq, &ptr->begin, beg);
+ RB_OBJ_WRITE(aseq, &ptr->end, end);
+ RB_OBJ_WRITE(aseq, &ptr->step, step);
+ ptr->exclude_end = excl;
+
return aseq;
}
@@ -3783,7 +3835,9 @@ rb_arith_seq_new(VALUE obj, VALUE meth, int argc, VALUE const *argv,
static inline VALUE
arith_seq_begin(VALUE self)
{
- return rb_ivar_get(self, id_begin);
+ struct arith_seq *ptr;
+ TypedData_Get_Struct(self, struct arith_seq, &enumerator_data_type, ptr);
+ return ptr->begin;
}
/*
@@ -3794,7 +3848,9 @@ arith_seq_begin(VALUE self)
static inline VALUE
arith_seq_end(VALUE self)
{
- return rb_ivar_get(self, id_end);
+ struct arith_seq *ptr;
+ TypedData_Get_Struct(self, struct arith_seq, &enumerator_data_type, ptr);
+ return ptr->end;
}
/*
@@ -3806,7 +3862,9 @@ arith_seq_end(VALUE self)
static inline VALUE
arith_seq_step(VALUE self)
{
- return rb_ivar_get(self, id_step);
+ struct arith_seq *ptr;
+ TypedData_Get_Struct(self, struct arith_seq, &enumerator_data_type, ptr);
+ return ptr->step;
}
/*
@@ -3817,13 +3875,17 @@ arith_seq_step(VALUE self)
static inline VALUE
arith_seq_exclude_end(VALUE self)
{
- return rb_ivar_get(self, id_exclude_end);
+ struct arith_seq *ptr;
+ TypedData_Get_Struct(self, struct arith_seq, &enumerator_data_type, ptr);
+ return RBOOL(ptr->exclude_end);
}
static inline int
arith_seq_exclude_end_p(VALUE self)
{
- return RTEST(arith_seq_exclude_end(self));
+ struct arith_seq *ptr;
+ TypedData_Get_Struct(self, struct arith_seq, &enumerator_data_type, ptr);
+ return ptr->exclude_end;
}
int
@@ -4664,7 +4726,6 @@ void
Init_Enumerator(void)
{
id_rewind = rb_intern_const("rewind");
- id_new = rb_intern_const("new");
id_next = rb_intern_const("next");
id_result = rb_intern_const("result");
id_receiver = rb_intern_const("receiver");
@@ -4674,12 +4735,7 @@ Init_Enumerator(void)
id_force = rb_intern_const("force");
id_to_enum = rb_intern_const("to_enum");
id_each_entry = rb_intern_const("each_entry");
- id_begin = rb_intern_const("begin");
- id_end = rb_intern_const("end");
- id_step = rb_intern_const("step");
- id_exclude_end = rb_intern_const("exclude_end");
sym_each = ID2SYM(id_each);
- sym_cycle = ID2SYM(rb_intern_const("cycle"));
sym_yield = ID2SYM(rb_intern_const("yield"));
InitVM(Enumerator);
diff --git a/ext/date/date_core.c b/ext/date/date_core.c
index d01b99206f..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);
}
@@ -7517,10 +7528,7 @@ d_lite_marshal_dump_old(VALUE self)
m_of_in_day(dat),
DBL2NUM(m_sg(dat)));
- if (FL_TEST(self, FL_EXIVAR)) {
- rb_copy_generic_ivar(a, self);
- FL_SET(a, FL_EXIVAR);
- }
+ rb_copy_generic_ivar(a, self);
return a;
}
@@ -7542,10 +7550,8 @@ d_lite_marshal_dump(VALUE self)
INT2FIX(m_of(dat)),
DBL2NUM(m_sg(dat)));
- if (FL_TEST(self, FL_EXIVAR)) {
- rb_copy_generic_ivar(a, self);
- FL_SET(a, FL_EXIVAR);
- }
+
+ rb_copy_generic_ivar(a, self);
return a;
}
@@ -7618,10 +7624,7 @@ d_lite_marshal_load(VALUE self, VALUE a)
HAVE_JD | HAVE_DF);
}
- if (FL_TEST(a, FL_EXIVAR)) {
- rb_copy_generic_ivar(self, a);
- FL_SET(self, FL_EXIVAR);
- }
+ rb_copy_generic_ivar(self, a);
return self;
}
diff --git a/ext/date/zonetab.h b/ext/date/zonetab.h
index 4682c2cdbc..2a2e8910c9 100644
--- a/ext/date/zonetab.h
+++ b/ext/date/zonetab.h
@@ -1,4 +1,4 @@
-/* ANSI-C code produced by gperf version 3.3 */
+/* ANSI-C code produced by gperf version 3.1 */
/* Command-line: gperf --ignore-case -L ANSI-C -C -c -P -p -j1 -i 1 -g -o -t -N zonetab zonetab.list */
/* Computed positions: -k'1-4,9' */
@@ -51,7 +51,7 @@ struct zone;
#ifndef GPERF_DOWNCASE
#define GPERF_DOWNCASE 1
-static const unsigned char gperf_downcase[256] =
+static unsigned char gperf_downcase[256] =
{
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
@@ -144,11 +144,6 @@ hash (register const char *str, register size_t len)
{
default:
hval += asso_values[(unsigned char)str[8]];
-#if (defined __cplusplus && (__cplusplus >= 201703L || (__cplusplus >= 201103L && defined __clang__ && __clang_major__ + (__clang_minor__ >= 9) > 3))) || (defined __STDC_VERSION__ && __STDC_VERSION__ >= 202000L && ((defined __GNUC__ && __GNUC__ >= 10) || (defined __clang__ && __clang_major__ >= 9)))
- [[fallthrough]];
-#elif (defined __GNUC__ && __GNUC__ >= 7) || (defined __clang__ && __clang_major__ >= 10)
- __attribute__ ((__fallthrough__));
-#endif
/*FALLTHROUGH*/
case 8:
case 7:
@@ -156,27 +151,12 @@ hash (register const char *str, register size_t len)
case 5:
case 4:
hval += asso_values[(unsigned char)str[3]];
-#if (defined __cplusplus && (__cplusplus >= 201703L || (__cplusplus >= 201103L && defined __clang__ && __clang_major__ + (__clang_minor__ >= 9) > 3))) || (defined __STDC_VERSION__ && __STDC_VERSION__ >= 202000L && ((defined __GNUC__ && __GNUC__ >= 10) || (defined __clang__ && __clang_major__ >= 9)))
- [[fallthrough]];
-#elif (defined __GNUC__ && __GNUC__ >= 7) || (defined __clang__ && __clang_major__ >= 10)
- __attribute__ ((__fallthrough__));
-#endif
/*FALLTHROUGH*/
case 3:
hval += asso_values[(unsigned char)str[2]];
-#if (defined __cplusplus && (__cplusplus >= 201703L || (__cplusplus >= 201103L && defined __clang__ && __clang_major__ + (__clang_minor__ >= 9) > 3))) || (defined __STDC_VERSION__ && __STDC_VERSION__ >= 202000L && ((defined __GNUC__ && __GNUC__ >= 10) || (defined __clang__ && __clang_major__ >= 9)))
- [[fallthrough]];
-#elif (defined __GNUC__ && __GNUC__ >= 7) || (defined __clang__ && __clang_major__ >= 10)
- __attribute__ ((__fallthrough__));
-#endif
/*FALLTHROUGH*/
case 2:
hval += asso_values[(unsigned char)str[1]+6];
-#if (defined __cplusplus && (__cplusplus >= 201703L || (__cplusplus >= 201103L && defined __clang__ && __clang_major__ + (__clang_minor__ >= 9) > 3))) || (defined __STDC_VERSION__ && __STDC_VERSION__ >= 202000L && ((defined __GNUC__ && __GNUC__ >= 10) || (defined __clang__ && __clang_major__ >= 9)))
- [[fallthrough]];
-#elif (defined __GNUC__ && __GNUC__ >= 7) || (defined __clang__ && __clang_major__ >= 10)
- __attribute__ ((__fallthrough__));
-#endif
/*FALLTHROUGH*/
case 1:
hval += asso_values[(unsigned char)str[0]+52];
@@ -827,10 +807,6 @@ static const struct stringpool_t stringpool_contents =
const struct zone *
zonetab (register const char *str, register size_t len)
{
-#if (defined __GNUC__ && __GNUC__ + (__GNUC_MINOR__ >= 6) > 4) || (defined __clang__ && __clang_major__ >= 3)
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
-#endif
static const struct zone wordlist[] =
{
{-1}, {-1},
@@ -1565,9 +1541,6 @@ zonetab (register const char *str, register size_t len)
#line 141 "zonetab.list"
{(int)(size_t)&((struct stringpool_t *)0)->stringpool_str619, -10800}
};
-#if (defined __GNUC__ && __GNUC__ + (__GNUC_MINOR__ >= 6) > 4) || (defined __clang__ && __clang_major__ >= 3)
-#pragma GCC diagnostic pop
-#endif
if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH)
{
@@ -1585,7 +1558,7 @@ zonetab (register const char *str, register size_t len)
}
}
}
- return (struct zone *) 0;
+ return 0;
}
#line 330 "zonetab.list"
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 acd4a6864d..5e183e78ed 100644
--- a/ext/objspace/objspace.c
+++ b/ext/objspace/objspace.c
@@ -504,6 +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_fields);
#undef INIT_IMEMO_TYPE_ID
}
diff --git a/ext/objspace/objspace_dump.c b/ext/objspace/objspace_dump.c
index ac8bafaea9..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);
}
@@ -792,22 +793,21 @@ shape_id_i(shape_id_t shape_id, void *data)
return;
}
- rb_shape_t *shape = RSHAPE(shape_id);
dump_append(dc, "{\"address\":");
- dump_append_ref(dc, (VALUE)shape);
+ dump_append_ref(dc, (VALUE)RSHAPE(shape_id));
dump_append(dc, ", \"type\":\"SHAPE\", \"id\":");
dump_append_sizet(dc, shape_id);
- if (shape->type != SHAPE_ROOT) {
+ if (RSHAPE_TYPE(shape_id) != SHAPE_ROOT) {
dump_append(dc, ", \"parent_id\":");
- dump_append_lu(dc, shape->parent_id);
+ dump_append_lu(dc, RSHAPE_PARENT(shape_id));
}
dump_append(dc, ", \"depth\":");
dump_append_sizet(dc, rb_shape_depth(shape_id));
- switch((enum shape_type)shape->type) {
+ switch (RSHAPE_TYPE(shape_id)) {
case SHAPE_ROOT:
dump_append(dc, ", \"shape_type\":\"ROOT\"");
break;
@@ -815,7 +815,7 @@ shape_id_i(shape_id_t shape_id, void *data)
dump_append(dc, ", \"shape_type\":\"IVAR\"");
dump_append(dc, ",\"edge_name\":");
- dump_append_id(dc, shape->edge_name);
+ dump_append_id(dc, RSHAPE_EDGE_NAME(shape_id));
break;
case SHAPE_OBJ_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/strscan/extconf.rb b/ext/strscan/extconf.rb
index bd65606a4e..abcbdb3ad2 100644
--- a/ext/strscan/extconf.rb
+++ b/ext/strscan/extconf.rb
@@ -2,8 +2,8 @@
require 'mkmf'
if RUBY_ENGINE == 'ruby'
$INCFLAGS << " -I$(top_srcdir)" if $extmk
- have_func("onig_region_memsize", "ruby.h")
- have_func("rb_reg_onig_match", "ruby.h")
+ have_func("onig_region_memsize")
+ have_func("rb_reg_onig_match", "ruby/re.h")
create_makefile 'strscan'
else
File.write('Makefile', dummy_makefile("").join)
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
diff --git a/file.c b/file.c
index 322df6dbec..936e0cdb95 100644
--- a/file.c
+++ b/file.c
@@ -662,7 +662,7 @@ rb_stat_dev(VALUE self)
#if RUBY_USE_STATX
unsigned int m = get_stat(self)->stx_dev_major;
unsigned int n = get_stat(self)->stx_dev_minor;
- return DEVT2NUM(makedev(m, n));
+ return ULL2NUM(makedev(m, n));
#elif SIZEOF_STRUCT_STAT_ST_DEV <= SIZEOF_DEV_T
return DEVT2NUM(get_stat(self)->st_dev);
#elif SIZEOF_STRUCT_STAT_ST_DEV <= SIZEOF_LONG
@@ -833,7 +833,7 @@ rb_stat_rdev(VALUE self)
#if RUBY_USE_STATX
unsigned int m = get_stat(self)->stx_rdev_major;
unsigned int n = get_stat(self)->stx_rdev_minor;
- return DEVT2NUM(makedev(m, n));
+ return ULL2NUM(makedev(m, n));
#elif !defined(HAVE_STRUCT_STAT_ST_RDEV)
return Qnil;
#elif SIZEOF_STRUCT_STAT_ST_RDEV <= SIZEOF_DEV_T
diff --git a/gc.c b/gc.c
index 05cefc739b..b0876fca5e 100644
--- a/gc.c
+++ b/gc.c
@@ -1201,7 +1201,6 @@ rb_data_free(void *objspace, VALUE obj)
struct classext_foreach_args {
VALUE klass;
- bool obj_too_complex;
rb_objspace_t *objspace; // used for update_*
};
@@ -1213,12 +1212,6 @@ classext_free(rb_classext_t *ext, bool is_prime, VALUE namespace, void *arg)
rb_id_table_free(RCLASSEXT_M_TBL(ext));
rb_cc_tbl_free(RCLASSEXT_CC_TBL(ext), args->klass);
- if (args->obj_too_complex) {
- st_free_table((st_table *)RCLASSEXT_FIELDS(ext));
- }
- else {
- xfree(RCLASSEXT_FIELDS(ext));
- }
if (!RCLASSEXT_SHARED_CONST_TBL(ext) && (tbl = RCLASSEXT_CONST_TBL(ext)) != NULL) {
rb_free_const_table(tbl);
}
@@ -1292,8 +1285,6 @@ rb_gc_obj_free(void *objspace, VALUE obj)
case T_MODULE:
case T_CLASS:
args.klass = obj;
- args.obj_too_complex = rb_shape_obj_too_complex_p(obj) ? true : false;
-
rb_class_classext_foreach(obj, classext_free, (void *)&args);
if (RCLASS(obj)->ns_classext_tbl) {
st_free_table(RCLASS(obj)->ns_classext_tbl);
@@ -2024,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)) {
@@ -2052,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));
}
}
@@ -2079,9 +2060,8 @@ rb_gc_obj_free_vm_weak_references(VALUE obj)
{
obj_free_object_id(obj);
- if (FL_TEST_RAW(obj, FL_EXIVAR)) {
- rb_free_generic_ivar((VALUE)obj);
- FL_UNSET_RAW(obj, FL_EXIVAR);
+ if (rb_obj_exivar_p(obj)) {
+ rb_free_generic_ivar(obj);
}
switch (BUILTIN_TYPE(obj)) {
@@ -2306,18 +2286,6 @@ classext_memsize(rb_classext_t *ext, bool prime, VALUE namespace, void *arg)
}
static void
-classext_fields_hash_memsize(rb_classext_t *ext, bool prime, VALUE namespace, void *arg)
-{
- size_t *size = (size_t *)arg;
- size_t count;
- RB_VM_LOCKING() {
- count = rb_st_table_size((st_table *)RCLASSEXT_FIELDS(ext));
- }
- // class IV sizes are allocated as powers of two
- *size += SIZEOF_VALUE << bit_length(count);
-}
-
-static void
classext_superclasses_memsize(rb_classext_t *ext, bool prime, VALUE namespace, void *arg)
{
size_t *size = (size_t *)arg;
@@ -2338,10 +2306,6 @@ rb_obj_memsize_of(VALUE obj)
return 0;
}
- if (FL_TEST(obj, FL_EXIVAR)) {
- size += rb_generic_ivar_memsize(obj);
- }
-
switch (BUILTIN_TYPE(obj)) {
case T_OBJECT:
if (rb_shape_obj_too_complex_p(obj)) {
@@ -2354,15 +2318,6 @@ rb_obj_memsize_of(VALUE obj)
case T_MODULE:
case T_CLASS:
rb_class_classext_foreach(obj, classext_memsize, (void *)&size);
-
- if (rb_shape_obj_too_complex_p(obj)) {
- rb_class_classext_foreach(obj, classext_fields_hash_memsize, (void *)&size);
- }
- else {
- // class IV sizes are allocated as powers of two
- size += SIZEOF_VALUE << bit_length(RCLASS_FIELDS_COUNT(obj));
- }
-
rb_class_classext_foreach(obj, classext_superclasses_memsize, (void *)&size);
break;
case T_ICLASS:
@@ -3091,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();
@@ -3135,10 +3089,7 @@ gc_mark_classext_module(rb_classext_t *ext, bool prime, VALUE namespace, void *a
gc_mark_internal(RCLASSEXT_SUPER(ext));
}
mark_m_tbl(objspace, RCLASSEXT_M_TBL(ext));
- if (rb_shape_obj_too_complex_p(obj)) {
- gc_mark_tbl_no_pin((st_table *)RCLASSEXT_FIELDS(ext));
- // for the case ELSE is written in rb_gc_mark_children() because it's per RClass, not classext
- }
+ gc_mark_internal(RCLASSEXT_FIELDS_OBJ(ext));
if (!RCLASSEXT_SHARED_CONST_TBL(ext) && RCLASSEXT_CONST_TBL(ext)) {
mark_const_tbl(objspace, RCLASSEXT_CONST_TBL(ext));
}
@@ -3175,7 +3126,7 @@ rb_gc_mark_children(void *objspace, VALUE obj)
{
struct gc_mark_classext_foreach_arg foreach_args;
- if (FL_TEST_RAW(obj, FL_EXIVAR)) {
+ if (rb_obj_exivar_p(obj)) {
rb_mark_generic_ivar(obj);
}
@@ -3218,12 +3169,6 @@ rb_gc_mark_children(void *objspace, VALUE obj)
foreach_args.objspace = objspace;
foreach_args.obj = obj;
rb_class_classext_foreach(obj, gc_mark_classext_module, (void *)&foreach_args);
-
- if (!rb_shape_obj_too_complex_p(obj)) {
- for (attr_index_t i = 0; i < RCLASS_FIELDS_COUNT(obj); i++) {
- gc_mark_internal(RCLASS_PRIME_FIELDS(obj)[i]);
- }
- }
break;
case T_ICLASS:
@@ -3849,7 +3794,6 @@ static void
update_classext(rb_classext_t *ext, bool is_prime, VALUE namespace, void *arg)
{
struct classext_foreach_args *args = (struct classext_foreach_args *)arg;
- VALUE klass = args->klass;
rb_objspace_t *objspace = args->objspace;
if (RCLASSEXT_SUPER(ext)) {
@@ -3858,16 +3802,7 @@ update_classext(rb_classext_t *ext, bool is_prime, VALUE namespace, void *arg)
update_m_tbl(objspace, RCLASSEXT_M_TBL(ext));
- if (args->obj_too_complex) {
- gc_ref_update_table_values_only((st_table *)RCLASSEXT_FIELDS(ext));
- }
- else {
- // Classext is not copied in this case
- for (attr_index_t i = 0; i < RCLASS_FIELDS_COUNT(klass); i++) {
- UPDATE_IF_MOVED(objspace, RCLASSEXT_FIELDS(RCLASS_EXT_PRIME(klass))[i]);
- }
- }
-
+ UPDATE_IF_MOVED(objspace, ext->fields_obj);
if (!RCLASSEXT_SHARED_CONST_TBL(ext)) {
update_const_tbl(objspace, RCLASSEXT_CONST_TBL(ext));
}
@@ -3986,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
@@ -4054,61 +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;
@@ -4217,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);
}
@@ -4255,7 +4147,6 @@ rb_gc_update_object_references(void *objspace, VALUE obj)
// Continue to the shared T_CLASS/T_MODULE
case T_MODULE:
args.klass = obj;
- args.obj_too_complex = rb_shape_obj_too_complex_p(obj);
args.objspace = objspace;
rb_class_classext_foreach(obj, update_classext, (void *)&args);
break;
diff --git a/gc/mmtk/src/abi.rs b/gc/mmtk/src/abi.rs
index b425d9e50d..81e24679f0 100644
--- a/gc/mmtk/src/abi.rs
+++ b/gc/mmtk/src/abi.rs
@@ -12,9 +12,6 @@ pub const GC_THREAD_KIND_WORKER: libc::c_int = 1;
const HIDDEN_SIZE_MASK: usize = 0x0000FFFFFFFFFFFF;
-// Should keep in sync with C code.
-const RUBY_FL_EXIVAR: usize = 1 << 10;
-
// An opaque type for the C counterpart.
#[allow(non_camel_case_types)]
pub struct st_table;
@@ -93,10 +90,6 @@ impl RubyObjectAccess {
unsafe { self.flags_field().load::<usize>() }
}
- pub fn has_exivar_flag(&self) -> bool {
- (self.load_flags() & RUBY_FL_EXIVAR) != 0
- }
-
pub fn prefix_size() -> usize {
// Currently, a hidden size field of word size is placed before each object.
OBJREF_OFFSET
diff --git a/gems/bundled_gems b/gems/bundled_gems
index 6b24757a10..d00124cf37 100644
--- a/gems/bundled_gems
+++ b/gems/bundled_gems
@@ -13,14 +13,14 @@ test-unit 3.6.8 https://github.com/test-unit/test-unit
rexml 3.4.1 https://github.com/ruby/rexml
rss 0.3.1 https://github.com/ruby/rss
net-ftp 0.3.8 https://github.com/ruby/net-ftp
-net-imap 0.5.8 https://github.com/ruby/net-imap
+net-imap 0.5.9 https://github.com/ruby/net-imap
net-pop 0.1.2 https://github.com/ruby/net-pop
net-smtp 0.5.1 https://github.com/ruby/net-smtp
-matrix 0.4.2 https://github.com/ruby/matrix 200efebc35dc1a8d16fad671f7006c85cbd0e3f5
-prime 0.1.3 https://github.com/ruby/prime d97973271103f2bdde91f3f0bd3e42526401ad77
+matrix 0.4.3 https://github.com/ruby/matrix
+prime 0.1.4 https://github.com/ruby/prime
rbs 3.9.4 https://github.com/ruby/rbs
typeprof 0.30.1 https://github.com/ruby/typeprof
-debug 1.10.0 https://github.com/ruby/debug cf469f2b21710727abdd153b25a1e5123b002bb0
+debug 1.11.0 https://github.com/ruby/debug
racc 1.8.1 https://github.com/ruby/racc
mutex_m 0.3.0 https://github.com/ruby/mutex_m
getoptlong 0.2.1 https://github.com/ruby/getoptlong
@@ -35,13 +35,13 @@ nkf 0.2.0 https://github.com/ruby/nkf
syslog 0.3.0 https://github.com/ruby/syslog
csv 3.3.5 https://github.com/ruby/csv
repl_type_completor 0.1.11 https://github.com/ruby/repl_type_completor 25108aa8d69ddaba0b5da3feff1c0035371524b2
-ostruct 0.6.1 https://github.com/ruby/ostruct 50d51248bec5560a102a1024aff4174b31dca8cc
+ostruct 0.6.2 https://github.com/ruby/ostruct
pstore 0.2.0 https://github.com/ruby/pstore
benchmark 0.4.1 https://github.com/ruby/benchmark
logger 1.7.0 https://github.com/ruby/logger
-rdoc 6.14.0 https://github.com/ruby/rdoc
+rdoc 6.14.1 https://github.com/ruby/rdoc
win32ole 1.9.2 https://github.com/ruby/win32ole
-irb 1.15.2 https://github.com/ruby/irb
+irb 1.15.2 https://github.com/ruby/irb 331c4e851296b115db766c291e8cf54a2492fb36
reline 0.6.1 https://github.com/ruby/reline
readline 0.0.4 https://github.com/ruby/readline
fiddle 1.1.8 https://github.com/ruby/fiddle
diff --git a/hash.c b/hash.c
index 2cc6828bb0..379dac814b 100644
--- a/hash.c
+++ b/hash.c
@@ -1597,10 +1597,11 @@ VALUE
rb_hash_dup(VALUE hash)
{
const VALUE flags = RBASIC(hash)->flags;
- VALUE ret = hash_dup(hash, rb_obj_class(hash),
- flags & (FL_EXIVAR|RHASH_PROC_DEFAULT));
- if (flags & FL_EXIVAR)
+ VALUE ret = hash_dup(hash, rb_obj_class(hash), flags & RHASH_PROC_DEFAULT);
+
+ if (rb_obj_exivar_p(hash)) {
rb_copy_generic_ivar(ret, hash);
+ }
return ret;
}
@@ -2920,7 +2921,7 @@ hash_aset(st_data_t *key, st_data_t *val, struct update_arg *arg, int existing)
VALUE
rb_hash_key_str(VALUE key)
{
- if (!RB_FL_ANY_RAW(key, FL_EXIVAR) && RBASIC_CLASS(key) == rb_cString) {
+ if (!rb_obj_exivar_p(key) && RBASIC_CLASS(key) == rb_cString) {
return rb_fstring(key);
}
else {
@@ -3871,7 +3872,6 @@ rb_hash_values(VALUE hash)
}
rb_ary_set_len(values, size);
}
-
else {
rb_hash_foreach(hash, values_i, values);
}
diff --git a/imemo.c b/imemo.c
index 2245434e21..f8c0e3b171 100644
--- a/imemo.c
+++ b/imemo.c
@@ -3,6 +3,7 @@
#include "id_table.h"
#include "internal.h"
#include "internal/imemo.h"
+#include "internal/st.h"
#include "vm_callinfo.h"
size_t rb_iseq_memsize(const rb_iseq_t *iseq);
@@ -29,10 +30,10 @@ rb_imemo_name(enum imemo_type type)
IMEMO_NAME(svar);
IMEMO_NAME(throw_data);
IMEMO_NAME(tmpbuf);
+ IMEMO_NAME(fields);
#undef IMEMO_NAME
- default:
- rb_bug("unreachable");
}
+ rb_bug("unreachable");
}
/* =========================================================================
@@ -109,6 +110,108 @@ rb_imemo_tmpbuf_parser_heap(void *buf, rb_imemo_tmpbuf_t *old_heap, size_t cnt)
return tmpbuf;
}
+static VALUE
+imemo_fields_new(VALUE klass, size_t capa)
+{
+ 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_fields, klass, embedded_size);
+ RUBY_ASSERT(IMEMO_TYPE_P(fields, imemo_fields));
+ return fields;
+ }
+ else {
+ 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;
+ }
+}
+
+VALUE
+rb_imemo_fields_new(VALUE klass, size_t capa)
+{
+ return imemo_fields_new(klass, capa);
+}
+
+static VALUE
+imemo_fields_new_complex(VALUE klass, size_t capa)
+{
+ 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_fields_new_complex(VALUE klass, size_t 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_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_fields_new_complex(CLASS_OF(fields_obj), 0);
+ RBASIC_SET_SHAPE_ID(clone, shape_id);
+ 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_fields_new(CLASS_OF(fields_obj), RSHAPE_CAPACITY(shape_id));
+ RBASIC_SET_SHAPE_ID(clone, 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
* ========================================================================= */
@@ -156,6 +259,14 @@ rb_imemo_memsize(VALUE obj)
size += ((rb_imemo_tmpbuf_t *)obj)->cnt * sizeof(VALUE);
break;
+ case imemo_fields:
+ if (rb_shape_obj_too_complex_p(obj)) {
+ size += st_memsize(IMEMO_OBJ_FIELDS(obj)->as.complex.table);
+ }
+ else if (FL_TEST_RAW(obj, OBJ_FIELD_EXTERNAL)) {
+ size += RSHAPE_CAPACITY(RBASIC_SHAPE_ID(obj)) * sizeof(VALUE);
+ }
+ break;
default:
rb_bug("unreachable");
}
@@ -420,6 +531,27 @@ rb_imemo_mark_and_move(VALUE obj, bool reference_updating)
break;
}
+ 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_fields_complex_tbl(obj);
+ if (reference_updating) {
+ rb_gc_ref_update_table_values_only(tbl);
+ }
+ else {
+ rb_mark_tbl_no_pin(tbl);
+ }
+ }
+ else {
+ 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]);
+ }
+ }
+ break;
+ }
default:
rb_bug("unreachable");
}
@@ -513,6 +645,17 @@ rb_cc_tbl_free(struct rb_id_table *cc_tbl, VALUE klass)
rb_id_table_free(cc_tbl);
}
+static inline void
+imemo_fields_free(struct rb_fields *fields)
+{
+ if (rb_shape_obj_too_complex_p((VALUE)fields)) {
+ st_free_table(fields->as.complex.table);
+ }
+ else if (FL_TEST_RAW((VALUE)fields, OBJ_FIELD_EXTERNAL)) {
+ xfree(fields->as.external.ptr);
+ }
+}
+
void
rb_imemo_free(VALUE obj)
{
@@ -576,6 +719,7 @@ rb_imemo_free(VALUE obj)
break;
case imemo_svar:
RB_DEBUG_COUNTER_INC(obj_imemo_svar);
+
break;
case imemo_throw_data:
RB_DEBUG_COUNTER_INC(obj_imemo_throw_data);
@@ -586,6 +730,10 @@ rb_imemo_free(VALUE obj)
RB_DEBUG_COUNTER_INC(obj_imemo_tmpbuf);
break;
+ 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 620c7e9c53..f4677ae400 100644
--- a/internal/class.h
+++ b/internal/class.h
@@ -79,7 +79,7 @@ struct rb_cvar_class_tbl_entry {
struct rb_classext_struct {
const rb_namespace_t *ns;
VALUE super;
- VALUE *fields; // Fields are either ivar or other internal properties stored inline
+ VALUE fields_obj; // Fields are either ivar or other internal properties stored inline
struct rb_id_table *m_tbl;
struct rb_id_table *const_tbl;
struct rb_id_table *callable_m_tbl;
@@ -175,7 +175,8 @@ static inline rb_classext_t * RCLASS_EXT_WRITABLE(VALUE obj);
#define RCLASSEXT_NS(ext) (ext->ns)
#define RCLASSEXT_SUPER(ext) (ext->super)
-#define RCLASSEXT_FIELDS(ext) (ext->fields)
+#define RCLASSEXT_FIELDS(ext) (ext->fields_obj ? ROBJECT_FIELDS(ext->fields_obj) : NULL)
+#define RCLASSEXT_FIELDS_OBJ(ext) (ext->fields_obj)
#define RCLASSEXT_M_TBL(ext) (ext->m_tbl)
#define RCLASSEXT_CONST_TBL(ext) (ext->const_tbl)
#define RCLASSEXT_CALLABLE_M_TBL(ext) (ext->callable_m_tbl)
@@ -205,7 +206,7 @@ static inline void RCLASSEXT_SET_INCLUDER(rb_classext_t *ext, VALUE klass, VALUE
#define RCLASS_PRIME_NS(c) (RCLASS_EXT_PRIME(c)->ns)
// To invalidate CC by inserting&invalidating method entry into tables containing the target cme
// See clear_method_cache_by_id_in_class()
-#define RCLASS_PRIME_FIELDS(c) (RCLASS_EXT_PRIME(c)->fields)
+#define RCLASS_PRIME_FIELDS_OBJ(c) (RCLASS_EXT_PRIME(c)->fields_obj)
#define RCLASS_PRIME_M_TBL(c) (RCLASS_EXT_PRIME(c)->m_tbl)
#define RCLASS_PRIME_CONST_TBL(c) (RCLASS_EXT_PRIME(c)->const_tbl)
#define RCLASS_PRIME_CALLABLE_M_TBL(c) (RCLASS_EXT_PRIME(c)->callable_m_tbl)
@@ -255,11 +256,6 @@ static inline void RCLASSEXT_SET_INCLUDER(rb_classext_t *ext, VALUE klass, VALUE
static inline void RCLASS_SET_SUPER(VALUE klass, VALUE super);
static inline void RCLASS_WRITE_SUPER(VALUE klass, VALUE super);
-static inline st_table * RCLASS_FIELDS_HASH(VALUE obj);
-static inline st_table * RCLASS_WRITABLE_FIELDS_HASH(VALUE obj);
-static inline uint32_t RCLASS_FIELDS_COUNT(VALUE obj);
-static inline void RCLASS_SET_FIELDS_HASH(VALUE obj, const st_table *table);
-static inline void RCLASS_WRITE_FIELDS_HASH(VALUE obj, const st_table *table);
// TODO: rename RCLASS_SET_M_TBL_WORKAROUND (and _WRITE_) to RCLASS_SET_M_TBL with write barrier
static inline void RCLASS_SET_M_TBL_WORKAROUND(VALUE klass, struct rb_id_table *table, bool check_promoted);
static inline void RCLASS_WRITE_M_TBL_WORKAROUND(VALUE klass, struct rb_id_table *table, bool check_promoted);
@@ -407,10 +403,6 @@ RCLASS_EXT_WRITABLE_LOOKUP(VALUE obj, const rb_namespace_t *ns)
if (ext)
return ext;
- if (!rb_shape_obj_too_complex_p(obj)) {
- rb_evict_ivars_to_hash(obj); // fallback to ivptr for ivars from shapes
- }
-
RB_VM_LOCKING() {
// re-check the classext is not created to avoid the multi-thread race
ext = RCLASS_EXT_TABLE_LOOKUP_INTERNAL(obj, ns);
@@ -440,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
@@ -528,56 +520,57 @@ RCLASS_WRITE_SUPER(VALUE klass, VALUE super)
RB_OBJ_WRITE(klass, &RCLASSEXT_SUPER(RCLASS_EXT_WRITABLE(klass)), super);
}
-static inline st_table *
-RCLASS_FIELDS_HASH(VALUE obj)
+static inline VALUE
+RCLASS_WRITABLE_ENSURE_FIELDS_OBJ(VALUE obj)
{
RUBY_ASSERT(RB_TYPE_P(obj, RUBY_T_CLASS) || RB_TYPE_P(obj, RUBY_T_MODULE));
- RUBY_ASSERT(rb_shape_obj_too_complex_p(obj));
- return (st_table *)RCLASSEXT_FIELDS(RCLASS_EXT_READABLE(obj));
+ rb_classext_t *ext = RCLASS_EXT_WRITABLE(obj);
+ if (!ext->fields_obj) {
+ RB_OBJ_WRITE(obj, &ext->fields_obj, rb_imemo_fields_new(rb_singleton_class(obj), 1));
+ }
+ return ext->fields_obj;
}
-static inline st_table *
-RCLASS_WRITABLE_FIELDS_HASH(VALUE obj)
+static inline VALUE
+RCLASS_WRITABLE_FIELDS_OBJ(VALUE obj)
{
RUBY_ASSERT(RB_TYPE_P(obj, RUBY_T_CLASS) || RB_TYPE_P(obj, RUBY_T_MODULE));
- RUBY_ASSERT(rb_shape_obj_too_complex_p(obj));
- return (st_table *)RCLASSEXT_FIELDS(RCLASS_EXT_WRITABLE(obj));
+ return RCLASSEXT_FIELDS_OBJ(RCLASS_EXT_WRITABLE(obj));
}
static inline void
-RCLASS_SET_FIELDS_HASH(VALUE obj, const st_table *tbl)
+RCLASSEXT_SET_FIELDS_OBJ(VALUE obj, rb_classext_t *ext, VALUE fields_obj)
{
RUBY_ASSERT(RB_TYPE_P(obj, RUBY_T_CLASS) || RB_TYPE_P(obj, RUBY_T_MODULE));
- RUBY_ASSERT(rb_shape_obj_too_complex_p(obj));
- RCLASSEXT_FIELDS(RCLASS_EXT_PRIME(obj)) = (VALUE *)tbl;
+
+ VALUE old_fields_obj = ext->fields_obj;
+ RUBY_ATOMIC_VALUE_SET(ext->fields_obj, fields_obj);
+ RB_OBJ_WRITTEN(obj, old_fields_obj, fields_obj);
}
static inline void
-RCLASS_WRITE_FIELDS_HASH(VALUE obj, const st_table *tbl)
+RCLASS_WRITABLE_SET_FIELDS_OBJ(VALUE obj, VALUE fields_obj)
{
RUBY_ASSERT(RB_TYPE_P(obj, RUBY_T_CLASS) || RB_TYPE_P(obj, RUBY_T_MODULE));
- RUBY_ASSERT(rb_shape_obj_too_complex_p(obj));
- RCLASSEXT_FIELDS(RCLASS_EXT_WRITABLE(obj)) = (VALUE *)tbl;
+
+ RCLASSEXT_SET_FIELDS_OBJ(obj, RCLASS_EXT_WRITABLE(obj), fields_obj);
}
static inline uint32_t
RCLASS_FIELDS_COUNT(VALUE obj)
{
RUBY_ASSERT(RB_TYPE_P(obj, RUBY_T_CLASS) || RB_TYPE_P(obj, RUBY_T_MODULE));
- if (rb_shape_obj_too_complex_p(obj)) {
- uint32_t count;
- // "Too complex" classes could have their IV hash mutated in
- // parallel, so lets lock around getting the hash size.
- RB_VM_LOCKING() {
- count = (uint32_t)rb_st_table_size(RCLASS_FIELDS_HASH(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_fields_complex_tbl(fields_obj));
+ }
+ else {
+ return RSHAPE_LEN(RBASIC_SHAPE_ID(fields_obj));
}
-
- return count;
- }
- else {
- return RSHAPE(RBASIC_SHAPE_ID(obj))->next_field_index;
}
+ return 0;
}
#define RCLASS_SET_M_TBL_EVEN_WHEN_PROMOTED(klass, table) RCLASS_SET_M_TBL_WORKAROUND(klass, table, false)
diff --git a/internal/imemo.h b/internal/imemo.h
index 305d12d240..44b41d1b1c 100644
--- a/internal/imemo.h
+++ b/internal/imemo.h
@@ -42,6 +42,7 @@ enum imemo_type {
imemo_callinfo = 11,
imemo_callcache = 12,
imemo_constcache = 13,
+ imemo_fields = 14,
};
/* CREF (Class REFerence) is defined in method.h */
@@ -257,4 +258,59 @@ MEMO_V2_SET(struct MEMO *m, VALUE v)
RB_OBJ_WRITE(m, &m->v2, v);
}
+struct rb_fields {
+ struct RBasic basic;
+ union {
+ struct {
+ VALUE fields[1];
+ } embed;
+ struct {
+ VALUE *ptr;
+ } external;
+ struct {
+ // Note: the st_table could be embedded, but complex T_CLASS should be rare to
+ // non-existent, so not really worth the trouble.
+ st_table *table;
+ } complex;
+ } as;
+};
+
+#define OBJ_FIELD_EXTERNAL IMEMO_FL_USER0
+#define IMEMO_OBJ_FIELDS(fields) ((struct rb_fields *)fields)
+
+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_fields_ptr(VALUE obj_fields)
+{
+ if (!obj_fields) {
+ return NULL;
+ }
+
+ 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;
+ }
+ else {
+ return IMEMO_OBJ_FIELDS(obj_fields)->as.embed.fields;
+ }
+}
+
+static inline st_table *
+rb_imemo_fields_complex_tbl(VALUE obj_fields)
+{
+ if (!obj_fields) {
+ return NULL;
+ }
+
+ RUBY_ASSERT(IMEMO_TYPE_P(obj_fields, imemo_fields));
+
+ return IMEMO_OBJ_FIELDS(obj_fields)->as.complex.table;
+}
+
#endif /* INTERNAL_IMEMO_H */
diff --git a/internal/string.h b/internal/string.h
index 50561924f2..d6fea62061 100644
--- a/internal/string.h
+++ b/internal/string.h
@@ -30,6 +30,7 @@ enum ruby_rstring_private_flags {
#endif
/* string.c */
+VALUE rb_str_dup_m(VALUE str);
VALUE rb_fstring(VALUE);
VALUE rb_fstring_cstr(const char *str);
VALUE rb_fstring_enc_new(const char *ptr, long len, rb_encoding *enc);
diff --git a/internal/thread.h b/internal/thread.h
index 8403ac2663..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);
@@ -90,6 +92,7 @@ typedef VALUE (rb_interrupt_exec_func_t)(void *data);
enum rb_interrupt_exec_flag {
rb_interrupt_exec_flag_none = 0x00,
rb_interrupt_exec_flag_value_data = 0x01,
+ rb_interrupt_exec_flag_new_thread = 0x02,
};
// interrupt the target_th and run func.
diff --git a/internal/variable.h b/internal/variable.h
index a0608b22d1..92017d6184 100644
--- a/internal/variable.h
+++ b/internal/variable.h
@@ -13,12 +13,11 @@
#include "constant.h" /* for rb_const_entry_t */
#include "ruby/internal/stdbool.h" /* for bool */
#include "ruby/ruby.h" /* for VALUE */
-#include "shape.h" /* for rb_shape_t */
+#include "shape.h" /* for shape_id_t */
/* 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/iseq.c b/iseq.c
index dcde27ba1b..1201b877ab 100644
--- a/iseq.c
+++ b/iseq.c
@@ -602,11 +602,11 @@ set_relation(rb_iseq_t *iseq, const rb_iseq_t *piseq)
body->local_iseq = iseq;
}
else if (piseq) {
- body->local_iseq = ISEQ_BODY(piseq)->local_iseq;
+ RB_OBJ_WRITE(iseq, &body->local_iseq, ISEQ_BODY(piseq)->local_iseq);
}
if (piseq) {
- body->parent_iseq = piseq;
+ RB_OBJ_WRITE(iseq, &body->parent_iseq, piseq);
}
if (type == ISEQ_TYPE_MAIN) {
diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb
index 51f71af501..198c9e2846 100644
--- a/lib/bundler/cli.rb
+++ b/lib/bundler/cli.rb
@@ -130,7 +130,7 @@ module Bundler
if man_pages.include?(command)
man_page = man_pages[command]
- if Bundler.which("man") && !man_path.match?(%r{^file:/.+!/META-INF/jruby.home/.+})
+ if Bundler.which("man") && !man_path.match?(%r{^(?:file:/.+!|uri:classloader:)/META-INF/jruby.home/.+})
Kernel.exec("man", man_page)
else
puts File.read("#{man_path}/#{File.basename(man_page)}.ronn")
@@ -512,7 +512,7 @@ module Bundler
end
end
- unless Bundler.feature_flag.bundler_3_mode?
+ unless Bundler.feature_flag.bundler_4_mode?
desc "viz [OPTIONS]", "Generates a visual dependency graph", hide: true
long_desc <<-D
Viz generates a PNG file of the current Gemfile as a dependency graph.
diff --git a/lib/bundler/cli/config.rb b/lib/bundler/cli/config.rb
index 77b502fe60..d963679085 100644
--- a/lib/bundler/cli/config.rb
+++ b/lib/bundler/cli/config.rb
@@ -26,8 +26,8 @@ module Bundler
end
message = "Using the `config` command without a subcommand [list, get, set, unset] is deprecated and will be removed in the future. Use `bundle #{new_args.join(" ")}` instead."
- removed_message = "Using the `config` command without a subcommand [list, get, set, unset] is has been removed. Use `bundle #{new_args.join(" ")}` instead."
- SharedHelpers.major_deprecation 3, message, removed_message: removed_message
+ removed_message = "Using the `config` command without a subcommand [list, get, set, unset] has been removed. Use `bundle #{new_args.join(" ")}` instead."
+ SharedHelpers.major_deprecation 4, message, removed_message: removed_message
Base.new(options, name, value, self).run
end
diff --git a/lib/bundler/cli/install.rb b/lib/bundler/cli/install.rb
index b0b354cf10..94d485682d 100644
--- a/lib/bundler/cli/install.rb
+++ b/lib/bundler/cli/install.rb
@@ -66,7 +66,9 @@ module Bundler
Plugin.gemfile_install(Bundler.default_gemfile) if Bundler.feature_flag.plugins?
- definition = Bundler.definition
+ # For install we want to enable strict validation
+ # (rather than some optimizations we perform at app runtime).
+ definition = Bundler.definition(strict: true)
definition.validate_runtime!
installer = Installer.install(Bundler.root, definition, options)
diff --git a/lib/bundler/cli/update.rb b/lib/bundler/cli/update.rb
index ab31d00879..ba3f1ec056 100644
--- a/lib/bundler/cli/update.rb
+++ b/lib/bundler/cli/update.rb
@@ -26,7 +26,7 @@ module Bundler
if Bundler.feature_flag.update_requires_all_flag?
raise InvalidOption, "To update everything, pass the `--all` flag."
end
- SharedHelpers.major_deprecation 3, "Pass --all to `bundle update` to update everything"
+ SharedHelpers.major_deprecation 4, "Pass --all to `bundle update` to update everything"
elsif !full_update && options[:all]
raise InvalidOption, "Cannot specify --all along with specific options."
end
diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb
index 564589ebfa..32006af109 100644
--- a/lib/bundler/definition.rb
+++ b/lib/bundler/definition.rb
@@ -60,6 +60,7 @@ module Bundler
if unlock == true
@unlocking_all = true
+ strict = false
@unlocking_bundler = false
@unlocking = unlock
@sources_to_unlock = []
@@ -68,6 +69,7 @@ module Bundler
conservative = false
else
@unlocking_all = false
+ strict = unlock.delete(:strict)
@unlocking_bundler = unlock.delete(:bundler)
@unlocking = unlock.any? {|_k, v| !Array(v).empty? }
@sources_to_unlock = unlock.delete(:sources) || []
@@ -97,7 +99,7 @@ module Bundler
if lockfile_exists?
@lockfile_contents = Bundler.read_file(lockfile)
- @locked_gems = LockfileParser.new(@lockfile_contents)
+ @locked_gems = LockfileParser.new(@lockfile_contents, strict: strict)
@locked_platforms = @locked_gems.platforms
@most_specific_locked_platform = @locked_gems.most_specific_locked_platform
@platforms = @locked_platforms.dup
diff --git a/lib/bundler/dsl.rb b/lib/bundler/dsl.rb
index 8ebc3d0020..4f9fbc55b1 100644
--- a/lib/bundler/dsl.rb
+++ b/lib/bundler/dsl.rb
@@ -521,7 +521,7 @@ module Bundler
end
def multiple_global_source_warning
- if Bundler.feature_flag.bundler_3_mode?
+ if Bundler.feature_flag.bundler_4_mode?
msg = "This Gemfile contains multiple global sources. " \
"Each source after the first must include a block to indicate which gems " \
"should come from that source"
diff --git a/lib/bundler/environment_preserver.rb b/lib/bundler/environment_preserver.rb
index 444ab6fd37..ffffceb487 100644
--- a/lib/bundler/environment_preserver.rb
+++ b/lib/bundler/environment_preserver.rb
@@ -6,6 +6,7 @@ module Bundler
BUNDLER_KEYS = %w[
BUNDLE_BIN_PATH
BUNDLE_GEMFILE
+ BUNDLER_4_MODE
BUNDLER_VERSION
BUNDLER_SETUP
GEM_HOME
diff --git a/lib/bundler/feature_flag.rb b/lib/bundler/feature_flag.rb
index b19cf42cc3..38498b245f 100644
--- a/lib/bundler/feature_flag.rb
+++ b/lib/bundler/feature_flag.rb
@@ -27,20 +27,20 @@ module Bundler
(1..10).each {|v| define_method("bundler_#{v}_mode?") { @major_version >= v } }
- settings_flag(:allow_offline_install) { bundler_3_mode? }
- settings_flag(:auto_clean_without_path) { bundler_3_mode? }
- settings_flag(:cache_all) { bundler_3_mode? }
- settings_flag(:default_install_uses_path) { bundler_3_mode? }
- settings_flag(:forget_cli_options) { bundler_3_mode? }
- settings_flag(:global_gem_cache) { bundler_3_mode? }
- settings_flag(:lockfile_checksums) { bundler_3_mode? }
- settings_flag(:path_relative_to_cwd) { bundler_3_mode? }
+ settings_flag(:allow_offline_install) { bundler_4_mode? }
+ settings_flag(:auto_clean_without_path) { bundler_4_mode? }
+ settings_flag(:cache_all) { bundler_4_mode? }
+ settings_flag(:default_install_uses_path) { bundler_4_mode? }
+ settings_flag(:forget_cli_options) { bundler_4_mode? }
+ settings_flag(:global_gem_cache) { bundler_4_mode? }
+ settings_flag(:lockfile_checksums) { bundler_4_mode? }
+ settings_flag(:path_relative_to_cwd) { bundler_4_mode? }
settings_flag(:plugins) { @bundler_version >= Gem::Version.new("1.14") }
- settings_flag(:print_only_version_number) { bundler_3_mode? }
- settings_flag(:setup_makes_kernel_gem_public) { !bundler_3_mode? }
- settings_flag(:update_requires_all_flag) { bundler_4_mode? }
+ settings_flag(:print_only_version_number) { bundler_4_mode? }
+ settings_flag(:setup_makes_kernel_gem_public) { !bundler_4_mode? }
+ settings_flag(:update_requires_all_flag) { bundler_5_mode? }
- settings_option(:default_cli_command) { bundler_3_mode? ? :cli_help : :install }
+ settings_option(:default_cli_command) { bundler_4_mode? ? :cli_help : :install }
def initialize(bundler_version)
@bundler_version = Gem::Version.create(bundler_version)
diff --git a/lib/bundler/lazy_specification.rb b/lib/bundler/lazy_specification.rb
index 081cac48d2..81ded54797 100644
--- a/lib/bundler/lazy_specification.rb
+++ b/lib/bundler/lazy_specification.rb
@@ -33,7 +33,7 @@ module Bundler
lazy_spec
end
- def initialize(name, version, platform, source = nil)
+ def initialize(name, version, platform, source = nil, **materialization_options)
@name = name
@version = version
@dependencies = []
@@ -43,6 +43,7 @@ module Bundler
@original_source = source
@source = source
+ @materialization_options = materialization_options
@force_ruby_platform = default_force_ruby_platform
@most_specific_locked_platform = nil
@@ -226,12 +227,13 @@ module Bundler
# Validate dependencies of this locked spec are consistent with dependencies
# of the actual spec that was materialized.
#
- # Note that we don't validate dependencies of locally installed gems but
+ # Note that unless we are in strict mode (which we set during installation)
+ # we don't validate dependencies of locally installed gems but
# accept what's in the lockfile instead for performance, since loading
# dependencies of locally installed gems would mean evaluating all gemspecs,
# which would affect `bundler/setup` performance.
def validate_dependencies(spec)
- if spec.is_a?(StubSpecification)
+ if !@materialization_options[:strict] && spec.is_a?(StubSpecification)
spec.dependencies = dependencies
else
if !source.is_a?(Source::Path) && spec.runtime_dependencies.sort != dependencies.sort
diff --git a/lib/bundler/lockfile_parser.rb b/lib/bundler/lockfile_parser.rb
index 94fe90eb2e..d00ba4cc10 100644
--- a/lib/bundler/lockfile_parser.rb
+++ b/lib/bundler/lockfile_parser.rb
@@ -94,7 +94,7 @@ module Bundler
lockfile_contents.split(BUNDLED).last.strip
end
- def initialize(lockfile)
+ def initialize(lockfile, strict: false)
@platforms = []
@sources = []
@dependencies = {}
@@ -106,6 +106,7 @@ module Bundler
"Gemfile.lock"
end
@pos = Position.new(1, 1)
+ @strict = strict
if lockfile.match?(/<<<<<<<|=======|>>>>>>>|\|\|\|\|\|\|\|/)
raise LockfileError, "Your #{@lockfile_path} contains merge conflicts.\n" \
@@ -286,7 +287,7 @@ module Bundler
version = Gem::Version.new(version)
platform = platform ? Gem::Platform.new(platform) : Gem::Platform::RUBY
- @current_spec = LazySpecification.new(name, version, platform, @current_source)
+ @current_spec = LazySpecification.new(name, version, platform, @current_source, strict: @strict)
@current_source.add_dependency_names(name)
@specs[@current_spec.full_name] = @current_spec
diff --git a/lib/bundler/man/bundle-add.1 b/lib/bundler/man/bundle-add.1
index d0c32fcb2a..5a27a70173 100644
--- a/lib/bundler/man/bundle-add.1
+++ b/lib/bundler/man/bundle-add.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-ADD" "1" "May 2025" ""
+.TH "BUNDLE\-ADD" "1" "June 2025" ""
.SH "NAME"
\fBbundle\-add\fR \- Add gem to the Gemfile and run bundle install
.SH "SYNOPSIS"
diff --git a/lib/bundler/man/bundle-binstubs.1 b/lib/bundler/man/bundle-binstubs.1
index 5e8cf0753a..3ab9584653 100644
--- a/lib/bundler/man/bundle-binstubs.1
+++ b/lib/bundler/man/bundle-binstubs.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-BINSTUBS" "1" "May 2025" ""
+.TH "BUNDLE\-BINSTUBS" "1" "June 2025" ""
.SH "NAME"
\fBbundle\-binstubs\fR \- Install the binstubs of the listed gems
.SH "SYNOPSIS"
diff --git a/lib/bundler/man/bundle-cache.1 b/lib/bundler/man/bundle-cache.1
index 44d5040f91..54cbd8ebc6 100644
--- a/lib/bundler/man/bundle-cache.1
+++ b/lib/bundler/man/bundle-cache.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-CACHE" "1" "May 2025" ""
+.TH "BUNDLE\-CACHE" "1" "June 2025" ""
.SH "NAME"
\fBbundle\-cache\fR \- Package your needed \fB\.gem\fR files into your application
.SH "SYNOPSIS"
diff --git a/lib/bundler/man/bundle-check.1 b/lib/bundler/man/bundle-check.1
index 3a5c02f702..122299a99b 100644
--- a/lib/bundler/man/bundle-check.1
+++ b/lib/bundler/man/bundle-check.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-CHECK" "1" "May 2025" ""
+.TH "BUNDLE\-CHECK" "1" "June 2025" ""
.SH "NAME"
\fBbundle\-check\fR \- Verifies if dependencies are satisfied by installed gems
.SH "SYNOPSIS"
diff --git a/lib/bundler/man/bundle-clean.1 b/lib/bundler/man/bundle-clean.1
index c23a3939b8..52e1096c18 100644
--- a/lib/bundler/man/bundle-clean.1
+++ b/lib/bundler/man/bundle-clean.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-CLEAN" "1" "May 2025" ""
+.TH "BUNDLE\-CLEAN" "1" "June 2025" ""
.SH "NAME"
\fBbundle\-clean\fR \- Cleans up unused gems in your bundler directory
.SH "SYNOPSIS"
diff --git a/lib/bundler/man/bundle-config.1 b/lib/bundler/man/bundle-config.1
index 5ce284113f..0c1a8a7609 100644
--- a/lib/bundler/man/bundle-config.1
+++ b/lib/bundler/man/bundle-config.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-CONFIG" "1" "May 2025" ""
+.TH "BUNDLE\-CONFIG" "1" "June 2025" ""
.SH "NAME"
\fBbundle\-config\fR \- Set bundler configuration options
.SH "SYNOPSIS"
@@ -52,113 +52,165 @@ The canonical form of this configuration is \fB"without"\fR\. To convert the can
Any periods in the configuration keys must be replaced with two underscores when setting it via environment variables\. The configuration key \fBlocal\.rack\fR becomes the environment variable \fBBUNDLE_LOCAL__RACK\fR\.
.SH "LIST OF AVAILABLE KEYS"
The following is a list of all configuration keys and their purpose\. You can learn more about their operation in bundle install(1) \fIbundle\-install\.1\.html\fR\.
-.IP "\(bu" 4
-\fBallow_offline_install\fR (\fBBUNDLE_ALLOW_OFFLINE_INSTALL\fR): Allow Bundler to use cached data when installing without network access\.
-.IP "\(bu" 4
-\fBauto_clean_without_path\fR (\fBBUNDLE_AUTO_CLEAN_WITHOUT_PATH\fR): Automatically run \fBbundle clean\fR after installing when an explicit \fBpath\fR has not been set and Bundler is not installing into the system gems\.
-.IP "\(bu" 4
-\fBauto_install\fR (\fBBUNDLE_AUTO_INSTALL\fR): Automatically run \fBbundle install\fR when gems are missing\.
-.IP "\(bu" 4
-\fBbin\fR (\fBBUNDLE_BIN\fR): Install executables from gems in the bundle to the specified directory\. Defaults to \fBfalse\fR\.
-.IP "\(bu" 4
-\fBcache_all\fR (\fBBUNDLE_CACHE_ALL\fR): Cache all gems, including path and git gems\. This needs to be explicitly configured on bundler 1 and bundler 2, but will be the default on bundler 3\.
-.IP "\(bu" 4
-\fBcache_all_platforms\fR (\fBBUNDLE_CACHE_ALL_PLATFORMS\fR): Cache gems for all platforms\.
-.IP "\(bu" 4
-\fBcache_path\fR (\fBBUNDLE_CACHE_PATH\fR): The directory that bundler will place cached gems in when running \fBbundle package\fR, and that bundler will look in when installing gems\. Defaults to \fBvendor/cache\fR\.
-.IP "\(bu" 4
-\fBclean\fR (\fBBUNDLE_CLEAN\fR): Whether Bundler should run \fBbundle clean\fR automatically after \fBbundle install\fR\.
-.IP "\(bu" 4
-\fBconsole\fR (\fBBUNDLE_CONSOLE\fR): The console that \fBbundle console\fR starts\. Defaults to \fBirb\fR\.
-.IP "\(bu" 4
-\fBdefault_install_uses_path\fR (\fBBUNDLE_DEFAULT_INSTALL_USES_PATH\fR): Whether a \fBbundle install\fR without an explicit \fB\-\-path\fR argument defaults to installing gems in \fB\.bundle\fR\.
-.IP "\(bu" 4
-\fBdeployment\fR (\fBBUNDLE_DEPLOYMENT\fR): Equivalent to setting \fBfrozen\fR to \fBtrue\fR and \fBpath\fR to \fBvendor/bundle\fR\.
-.IP "\(bu" 4
-\fBdisable_checksum_validation\fR (\fBBUNDLE_DISABLE_CHECKSUM_VALIDATION\fR): Allow installing gems even if they do not match the checksum provided by RubyGems\.
-.IP "\(bu" 4
-\fBdisable_exec_load\fR (\fBBUNDLE_DISABLE_EXEC_LOAD\fR): Stop Bundler from using \fBload\fR to launch an executable in\-process in \fBbundle exec\fR\.
-.IP "\(bu" 4
-\fBdisable_local_branch_check\fR (\fBBUNDLE_DISABLE_LOCAL_BRANCH_CHECK\fR): Allow Bundler to use a local git override without a branch specified in the Gemfile\.
-.IP "\(bu" 4
-\fBdisable_local_revision_check\fR (\fBBUNDLE_DISABLE_LOCAL_REVISION_CHECK\fR): Allow Bundler to use a local git override without checking if the revision present in the lockfile is present in the repository\.
-.IP "\(bu" 4
-\fBdisable_shared_gems\fR (\fBBUNDLE_DISABLE_SHARED_GEMS\fR): Stop Bundler from accessing gems installed to RubyGems' normal location\.
-.IP "\(bu" 4
-\fBdisable_version_check\fR (\fBBUNDLE_DISABLE_VERSION_CHECK\fR): Stop Bundler from checking if a newer Bundler version is available on rubygems\.org\.
-.IP "\(bu" 4
-\fBforce_ruby_platform\fR (\fBBUNDLE_FORCE_RUBY_PLATFORM\fR): Ignore the current machine's platform and install only \fBruby\fR platform gems\. As a result, gems with native extensions will be compiled from source\.
-.IP "\(bu" 4
-\fBfrozen\fR (\fBBUNDLE_FROZEN\fR): Disallow any automatic changes to \fBGemfile\.lock\fR\. Bundler commands will be blocked unless the lockfile can be installed exactly as written\. Usually this will happen when changing the \fBGemfile\fR manually and forgetting to update the lockfile through \fBbundle lock\fR or \fBbundle install\fR\.
-.IP "\(bu" 4
-\fBgem\.github_username\fR (\fBBUNDLE_GEM__GITHUB_USERNAME\fR): Sets a GitHub username or organization to be used in \fBREADME\fR file when you create a new gem via \fBbundle gem\fR command\. It can be overridden by passing an explicit \fB\-\-github\-username\fR flag to \fBbundle gem\fR\.
-.IP "\(bu" 4
-\fBgem\.push_key\fR (\fBBUNDLE_GEM__PUSH_KEY\fR): Sets the \fB\-\-key\fR parameter for \fBgem push\fR when using the \fBrake release\fR command with a private gemstash server\.
-.IP "\(bu" 4
-\fBgemfile\fR (\fBBUNDLE_GEMFILE\fR): The name of the file that bundler should use as the \fBGemfile\fR\. This location of this file also sets the root of the project, which is used to resolve relative paths in the \fBGemfile\fR, among other things\. By default, bundler will search up from the current working directory until it finds a \fBGemfile\fR\.
-.IP "\(bu" 4
-\fBglobal_gem_cache\fR (\fBBUNDLE_GLOBAL_GEM_CACHE\fR): Whether Bundler should cache all gems globally, rather than locally to the installing Ruby installation\.
-.IP "\(bu" 4
-\fBignore_funding_requests\fR (\fBBUNDLE_IGNORE_FUNDING_REQUESTS\fR): When set, no funding requests will be printed\.
-.IP "\(bu" 4
-\fBignore_messages\fR (\fBBUNDLE_IGNORE_MESSAGES\fR): When set, no post install messages will be printed\. To silence a single gem, use dot notation like \fBignore_messages\.httparty true\fR\.
-.IP "\(bu" 4
-\fBinit_gems_rb\fR (\fBBUNDLE_INIT_GEMS_RB\fR): Generate a \fBgems\.rb\fR instead of a \fBGemfile\fR when running \fBbundle init\fR\.
-.IP "\(bu" 4
-\fBjobs\fR (\fBBUNDLE_JOBS\fR): The number of gems Bundler can install in parallel\. Defaults to the number of available processors\.
-.IP "\(bu" 4
-\fBlockfile_checksums\fR (\fBBUNDLE_LOCKFILE_CHECKSUMS\fR): Whether Bundler should include a checksums section in new lockfiles, to protect from compromised gem sources\.
-.IP "\(bu" 4
-\fBno_install\fR (\fBBUNDLE_NO_INSTALL\fR): Whether \fBbundle package\fR should skip installing gems\.
-.IP "\(bu" 4
-\fBno_prune\fR (\fBBUNDLE_NO_PRUNE\fR): Whether Bundler should leave outdated gems unpruned when caching\.
-.IP "\(bu" 4
-\fBonly\fR (\fBBUNDLE_ONLY\fR): A space\-separated list of groups to install only gems of the specified groups\.
-.IP "\(bu" 4
-\fBpath\fR (\fBBUNDLE_PATH\fR): The location on disk where all gems in your bundle will be located regardless of \fB$GEM_HOME\fR or \fB$GEM_PATH\fR values\. Bundle gems not found in this location will be installed by \fBbundle install\fR\. Defaults to \fBGem\.dir\fR\.
-.IP "\(bu" 4
-\fBpath\.system\fR (\fBBUNDLE_PATH__SYSTEM\fR): Whether Bundler will install gems into the default system path (\fBGem\.dir\fR)\.
-.IP "\(bu" 4
-\fBpath_relative_to_cwd\fR (\fBBUNDLE_PATH_RELATIVE_TO_CWD\fR) Makes \fB\-\-path\fR relative to the CWD instead of the \fBGemfile\fR\.
-.IP "\(bu" 4
-\fBplugins\fR (\fBBUNDLE_PLUGINS\fR): Enable Bundler's experimental plugin system\.
-.IP "\(bu" 4
-\fBprefer_patch\fR (BUNDLE_PREFER_PATCH): Prefer updating only to next patch version during updates\. Makes \fBbundle update\fR calls equivalent to \fBbundler update \-\-patch\fR\.
-.IP "\(bu" 4
-\fBprint_only_version_number\fR (\fBBUNDLE_PRINT_ONLY_VERSION_NUMBER\fR): Print only version number from \fBbundler \-\-version\fR\.
-.IP "\(bu" 4
-\fBredirect\fR (\fBBUNDLE_REDIRECT\fR): The number of redirects allowed for network requests\. Defaults to \fB5\fR\.
-.IP "\(bu" 4
-\fBretry\fR (\fBBUNDLE_RETRY\fR): The number of times to retry failed network requests\. Defaults to \fB3\fR\.
-.IP "\(bu" 4
-\fBsetup_makes_kernel_gem_public\fR (\fBBUNDLE_SETUP_MAKES_KERNEL_GEM_PUBLIC\fR): Have \fBBundler\.setup\fR make the \fBKernel#gem\fR method public, even though RubyGems declares it as private\.
-.IP "\(bu" 4
-\fBshebang\fR (\fBBUNDLE_SHEBANG\fR): The program name that should be invoked for generated binstubs\. Defaults to the ruby install name used to generate the binstub\.
-.IP "\(bu" 4
-\fBsilence_deprecations\fR (\fBBUNDLE_SILENCE_DEPRECATIONS\fR): Whether Bundler should silence deprecation warnings for behavior that will be changed in the next major version\.
-.IP "\(bu" 4
-\fBsilence_root_warning\fR (\fBBUNDLE_SILENCE_ROOT_WARNING\fR): Silence the warning Bundler prints when installing gems as root\.
-.IP "\(bu" 4
-\fBssl_ca_cert\fR (\fBBUNDLE_SSL_CA_CERT\fR): Path to a designated CA certificate file or folder containing multiple certificates for trusted CAs in PEM format\.
-.IP "\(bu" 4
-\fBssl_client_cert\fR (\fBBUNDLE_SSL_CLIENT_CERT\fR): Path to a designated file containing a X\.509 client certificate and key in PEM format\.
-.IP "\(bu" 4
-\fBssl_verify_mode\fR (\fBBUNDLE_SSL_VERIFY_MODE\fR): The SSL verification mode Bundler uses when making HTTPS requests\. Defaults to verify peer\.
-.IP "\(bu" 4
-\fBsystem_bindir\fR (\fBBUNDLE_SYSTEM_BINDIR\fR): The location where RubyGems installs binstubs\. Defaults to \fBGem\.bindir\fR\.
-.IP "\(bu" 4
-\fBtimeout\fR (\fBBUNDLE_TIMEOUT\fR): The seconds allowed before timing out for network requests\. Defaults to \fB10\fR\.
-.IP "\(bu" 4
-\fBupdate_requires_all_flag\fR (\fBBUNDLE_UPDATE_REQUIRES_ALL_FLAG\fR): Require passing \fB\-\-all\fR to \fBbundle update\fR when everything should be updated, and disallow passing no options to \fBbundle update\fR\.
-.IP "\(bu" 4
-\fBuser_agent\fR (\fBBUNDLE_USER_AGENT\fR): The custom user agent fragment Bundler includes in API requests\.
-.IP "\(bu" 4
-\fBversion\fR (\fBBUNDLE_VERSION\fR): The version of Bundler to use when running under Bundler environment\. Defaults to \fBlockfile\fR\. You can also specify \fBsystem\fR or \fBx\.y\.z\fR\. \fBlockfile\fR will use the Bundler version specified in the \fBGemfile\.lock\fR, \fBsystem\fR will use the system version of Bundler, and \fBx\.y\.z\fR will use the specified version of Bundler\.
-.IP "\(bu" 4
-\fBwith\fR (\fBBUNDLE_WITH\fR): A space\-separated or \fB:\fR\-separated list of groups whose gems bundler should install\.
-.IP "\(bu" 4
-\fBwithout\fR (\fBBUNDLE_WITHOUT\fR): A space\-separated or \fB:\fR\-separated list of groups whose gems bundler should not install\.
-.IP "" 0
+.TP
+\fBallow_offline_install\fR (\fBBUNDLE_ALLOW_OFFLINE_INSTALL\fR)
+Allow Bundler to use cached data when installing without network access\.
+.TP
+\fBauto_clean_without_path\fR (\fBBUNDLE_AUTO_CLEAN_WITHOUT_PATH\fR)
+Automatically run \fBbundle clean\fR after installing when an explicit \fBpath\fR has not been set and Bundler is not installing into the system gems\.
+.TP
+\fBauto_install\fR (\fBBUNDLE_AUTO_INSTALL\fR)
+Automatically run \fBbundle install\fR when gems are missing\.
+.TP
+\fBbin\fR (\fBBUNDLE_BIN\fR)
+Install executables from gems in the bundle to the specified directory\. Defaults to \fBfalse\fR\.
+.TP
+\fBcache_all\fR (\fBBUNDLE_CACHE_ALL\fR)
+Cache all gems, including path and git gems\. This needs to be explicitly configured on bundler 1 and bundler 2, but will be the default on bundler 3\.
+.TP
+\fBcache_all_platforms\fR (\fBBUNDLE_CACHE_ALL_PLATFORMS\fR)
+Cache gems for all platforms\.
+.TP
+\fBcache_path\fR (\fBBUNDLE_CACHE_PATH\fR)
+The directory that bundler will place cached gems in when running \fBbundle package\fR, and that bundler will look in when installing gems\. Defaults to \fBvendor/cache\fR\.
+.TP
+\fBclean\fR (\fBBUNDLE_CLEAN\fR)
+Whether Bundler should run \fBbundle clean\fR automatically after \fBbundle install\fR\.
+.TP
+\fBconsole\fR (\fBBUNDLE_CONSOLE\fR)
+The console that \fBbundle console\fR starts\. Defaults to \fBirb\fR\.
+.TP
+\fBdefault_install_uses_path\fR (\fBBUNDLE_DEFAULT_INSTALL_USES_PATH\fR)
+Whether a \fBbundle install\fR without an explicit \fB\-\-path\fR argument defaults to installing gems in \fB\.bundle\fR\.
+.TP
+\fBdeployment\fR (\fBBUNDLE_DEPLOYMENT\fR)
+Equivalent to setting \fBfrozen\fR to \fBtrue\fR and \fBpath\fR to \fBvendor/bundle\fR\.
+.TP
+\fBdisable_checksum_validation\fR (\fBBUNDLE_DISABLE_CHECKSUM_VALIDATION\fR)
+Allow installing gems even if they do not match the checksum provided by RubyGems\.
+.TP
+\fBdisable_exec_load\fR (\fBBUNDLE_DISABLE_EXEC_LOAD\fR)
+Stop Bundler from using \fBload\fR to launch an executable in\-process in \fBbundle exec\fR\.
+.TP
+\fBdisable_local_branch_check\fR (\fBBUNDLE_DISABLE_LOCAL_BRANCH_CHECK\fR)
+Allow Bundler to use a local git override without a branch specified in the Gemfile\.
+.TP
+\fBdisable_local_revision_check\fR (\fBBUNDLE_DISABLE_LOCAL_REVISION_CHECK\fR)
+Allow Bundler to use a local git override without checking if the revision present in the lockfile is present in the repository\.
+.TP
+\fBdisable_shared_gems\fR (\fBBUNDLE_DISABLE_SHARED_GEMS\fR)
+Stop Bundler from accessing gems installed to RubyGems' normal location\.
+.TP
+\fBdisable_version_check\fR (\fBBUNDLE_DISABLE_VERSION_CHECK\fR)
+Stop Bundler from checking if a newer Bundler version is available on rubygems\.org\.
+.TP
+\fBforce_ruby_platform\fR (\fBBUNDLE_FORCE_RUBY_PLATFORM\fR)
+Ignore the current machine's platform and install only \fBruby\fR platform gems\. As a result, gems with native extensions will be compiled from source\.
+.TP
+\fBfrozen\fR (\fBBUNDLE_FROZEN\fR)
+Disallow any automatic changes to \fBGemfile\.lock\fR\. Bundler commands will be blocked unless the lockfile can be installed exactly as written\. Usually this will happen when changing the \fBGemfile\fR manually and forgetting to update the lockfile through \fBbundle lock\fR or \fBbundle install\fR\.
+.TP
+\fBgem\.github_username\fR (\fBBUNDLE_GEM__GITHUB_USERNAME\fR)
+Sets a GitHub username or organization to be used in \fBREADME\fR file when you create a new gem via \fBbundle gem\fR command\. It can be overridden by passing an explicit \fB\-\-github\-username\fR flag to \fBbundle gem\fR\.
+.TP
+\fBgem\.push_key\fR (\fBBUNDLE_GEM__PUSH_KEY\fR)
+Sets the \fB\-\-key\fR parameter for \fBgem push\fR when using the \fBrake release\fR command with a private gemstash server\.
+.TP
+\fBgemfile\fR (\fBBUNDLE_GEMFILE\fR)
+The name of the file that bundler should use as the \fBGemfile\fR\. This location of this file also sets the root of the project, which is used to resolve relative paths in the \fBGemfile\fR, among other things\. By default, bundler will search up from the current working directory until it finds a \fBGemfile\fR\.
+.TP
+\fBglobal_gem_cache\fR (\fBBUNDLE_GLOBAL_GEM_CACHE\fR)
+Whether Bundler should cache all gems globally, rather than locally to the installing Ruby installation\.
+.TP
+\fBignore_funding_requests\fR (\fBBUNDLE_IGNORE_FUNDING_REQUESTS\fR)
+When set, no funding requests will be printed\.
+.TP
+\fBignore_messages\fR (\fBBUNDLE_IGNORE_MESSAGES\fR)
+When set, no post install messages will be printed\. To silence a single gem, use dot notation like \fBignore_messages\.httparty true\fR\.
+.TP
+\fBinit_gems_rb\fR (\fBBUNDLE_INIT_GEMS_RB\fR)
+Generate a \fBgems\.rb\fR instead of a \fBGemfile\fR when running \fBbundle init\fR\.
+.TP
+\fBjobs\fR (\fBBUNDLE_JOBS\fR)
+The number of gems Bundler can install in parallel\. Defaults to the number of available processors\.
+.TP
+\fBlockfile_checksums\fR (\fBBUNDLE_LOCKFILE_CHECKSUMS\fR)
+Whether Bundler should include a checksums section in new lockfiles, to protect from compromised gem sources\.
+.TP
+\fBno_install\fR (\fBBUNDLE_NO_INSTALL\fR)
+Whether \fBbundle package\fR should skip installing gems\.
+.TP
+\fBno_prune\fR (\fBBUNDLE_NO_PRUNE\fR)
+Whether Bundler should leave outdated gems unpruned when caching\.
+.TP
+\fBonly\fR (\fBBUNDLE_ONLY\fR)
+A space\-separated list of groups to install only gems of the specified groups\.
+.TP
+\fBpath\fR (\fBBUNDLE_PATH\fR)
+The location on disk where all gems in your bundle will be located regardless of \fB$GEM_HOME\fR or \fB$GEM_PATH\fR values\. Bundle gems not found in this location will be installed by \fBbundle install\fR\. Defaults to \fBGem\.dir\fR\.
+.TP
+\fBpath\.system\fR (\fBBUNDLE_PATH__SYSTEM\fR)
+Whether Bundler will install gems into the default system path (\fBGem\.dir\fR)\.
+.TP
+\fBpath_relative_to_cwd\fR (\fBBUNDLE_PATH_RELATIVE_TO_CWD\fR)
+Makes \fB\-\-path\fR relative to the CWD instead of the \fBGemfile\fR\.
+.TP
+\fBplugins\fR (\fBBUNDLE_PLUGINS\fR)
+Enable Bundler's experimental plugin system\.
+.TP
+\fBprefer_patch\fR (BUNDLE_PREFER_PATCH)
+Prefer updating only to next patch version during updates\. Makes \fBbundle update\fR calls equivalent to \fBbundler update \-\-patch\fR\.
+.TP
+\fBprint_only_version_number\fR (\fBBUNDLE_PRINT_ONLY_VERSION_NUMBER\fR)
+Print only version number from \fBbundler \-\-version\fR\.
+.TP
+\fBredirect\fR (\fBBUNDLE_REDIRECT\fR)
+The number of redirects allowed for network requests\. Defaults to \fB5\fR\.
+.TP
+\fBretry\fR (\fBBUNDLE_RETRY\fR)
+The number of times to retry failed network requests\. Defaults to \fB3\fR\.
+.TP
+\fBsetup_makes_kernel_gem_public\fR (\fBBUNDLE_SETUP_MAKES_KERNEL_GEM_PUBLIC\fR)
+Have \fBBundler\.setup\fR make the \fBKernel#gem\fR method public, even though RubyGems declares it as private\.
+.TP
+\fBshebang\fR (\fBBUNDLE_SHEBANG\fR)
+The program name that should be invoked for generated binstubs\. Defaults to the ruby install name used to generate the binstub\.
+.TP
+\fBsilence_deprecations\fR (\fBBUNDLE_SILENCE_DEPRECATIONS\fR)
+Whether Bundler should silence deprecation warnings for behavior that will be changed in the next major version\.
+.TP
+\fBsilence_root_warning\fR (\fBBUNDLE_SILENCE_ROOT_WARNING\fR)
+Silence the warning Bundler prints when installing gems as root\.
+.TP
+\fBssl_ca_cert\fR (\fBBUNDLE_SSL_CA_CERT\fR)
+Path to a designated CA certificate file or folder containing multiple certificates for trusted CAs in PEM format\.
+.TP
+\fBssl_client_cert\fR (\fBBUNDLE_SSL_CLIENT_CERT\fR)
+Path to a designated file containing a X\.509 client certificate and key in PEM format\.
+.TP
+\fBssl_verify_mode\fR (\fBBUNDLE_SSL_VERIFY_MODE\fR)
+The SSL verification mode Bundler uses when making HTTPS requests\. Defaults to verify peer\.
+.TP
+\fBsystem_bindir\fR (\fBBUNDLE_SYSTEM_BINDIR\fR)
+The location where RubyGems installs binstubs\. Defaults to \fBGem\.bindir\fR\.
+.TP
+\fBtimeout\fR (\fBBUNDLE_TIMEOUT\fR)
+The seconds allowed before timing out for network requests\. Defaults to \fB10\fR\.
+.TP
+\fBupdate_requires_all_flag\fR (\fBBUNDLE_UPDATE_REQUIRES_ALL_FLAG\fR)
+Require passing \fB\-\-all\fR to \fBbundle update\fR when everything should be updated, and disallow passing no options to \fBbundle update\fR\.
+.TP
+\fBuser_agent\fR (\fBBUNDLE_USER_AGENT\fR)
+The custom user agent fragment Bundler includes in API requests\.
+.TP
+\fBversion\fR (\fBBUNDLE_VERSION\fR)
+The version of Bundler to use when running under Bundler environment\. Defaults to \fBlockfile\fR\. You can also specify \fBsystem\fR or \fBx\.y\.z\fR\. \fBlockfile\fR will use the Bundler version specified in the \fBGemfile\.lock\fR, \fBsystem\fR will use the system version of Bundler, and \fBx\.y\.z\fR will use the specified version of Bundler\.
+.TP
+\fBwith\fR (\fBBUNDLE_WITH\fR)
+A space\-separated or \fB:\fR\-separated list of groups whose gems bundler should install\.
+.TP
+\fBwithout\fR (\fBBUNDLE_WITHOUT\fR)
+A space\-separated or \fB:\fR\-separated list of groups whose gems bundler should not install\.
.SH "REMEMBERING OPTIONS"
Flags passed to \fBbundle install\fR or the Bundler runtime, such as \fB\-\-path foo\fR or \fB\-\-without production\fR, are remembered between commands and saved to your local application's configuration (normally, \fB\./\.bundle/config\fR)\.
.P
diff --git a/lib/bundler/man/bundle-config.1.ronn b/lib/bundler/man/bundle-config.1.ronn
index fef8f2d26b..9750a5ae4c 100644
--- a/lib/bundler/man/bundle-config.1.ronn
+++ b/lib/bundler/man/bundle-config.1.ronn
@@ -171,7 +171,7 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html).
will be installed by `bundle install`. Defaults to `Gem.dir`.
* `path.system` (`BUNDLE_PATH__SYSTEM`):
Whether Bundler will install gems into the default system path (`Gem.dir`).
-* `path_relative_to_cwd` (`BUNDLE_PATH_RELATIVE_TO_CWD`)
+* `path_relative_to_cwd` (`BUNDLE_PATH_RELATIVE_TO_CWD`):
Makes `--path` relative to the CWD instead of the `Gemfile`.
* `plugins` (`BUNDLE_PLUGINS`):
Enable Bundler's experimental plugin system.
diff --git a/lib/bundler/man/bundle-console.1 b/lib/bundler/man/bundle-console.1
index b83d1c4dad..1dd6b278de 100644
--- a/lib/bundler/man/bundle-console.1
+++ b/lib/bundler/man/bundle-console.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-CONSOLE" "1" "May 2025" ""
+.TH "BUNDLE\-CONSOLE" "1" "June 2025" ""
.SH "NAME"
\fBbundle\-console\fR \- Open an IRB session with the bundle pre\-loaded
.SH "SYNOPSIS"
diff --git a/lib/bundler/man/bundle-doctor.1 b/lib/bundler/man/bundle-doctor.1
index fed818cfaf..0cf01e02e9 100644
--- a/lib/bundler/man/bundle-doctor.1
+++ b/lib/bundler/man/bundle-doctor.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-DOCTOR" "1" "May 2025" ""
+.TH "BUNDLE\-DOCTOR" "1" "June 2025" ""
.SH "NAME"
\fBbundle\-doctor\fR \- Checks the bundle for common problems
.SH "SYNOPSIS"
diff --git a/lib/bundler/man/bundle-env.1 b/lib/bundler/man/bundle-env.1
index 34631206ed..167d902c99 100644
--- a/lib/bundler/man/bundle-env.1
+++ b/lib/bundler/man/bundle-env.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-ENV" "1" "May 2025" ""
+.TH "BUNDLE\-ENV" "1" "June 2025" ""
.SH "NAME"
\fBbundle\-env\fR \- Print information about the environment Bundler is running under
.SH "SYNOPSIS"
diff --git a/lib/bundler/man/bundle-exec.1 b/lib/bundler/man/bundle-exec.1
index abce4f0112..062944b3ca 100644
--- a/lib/bundler/man/bundle-exec.1
+++ b/lib/bundler/man/bundle-exec.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-EXEC" "1" "May 2025" ""
+.TH "BUNDLE\-EXEC" "1" "June 2025" ""
.SH "NAME"
\fBbundle\-exec\fR \- Execute a command in the context of the bundle
.SH "SYNOPSIS"
diff --git a/lib/bundler/man/bundle-fund.1 b/lib/bundler/man/bundle-fund.1
index e79d38a2af..131b0e9d2d 100644
--- a/lib/bundler/man/bundle-fund.1
+++ b/lib/bundler/man/bundle-fund.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-FUND" "1" "May 2025" ""
+.TH "BUNDLE\-FUND" "1" "June 2025" ""
.SH "NAME"
\fBbundle\-fund\fR \- Lists information about gems seeking funding assistance
.SH "SYNOPSIS"
diff --git a/lib/bundler/man/bundle-gem.1 b/lib/bundler/man/bundle-gem.1
index ae6f9f7f8a..d4aacfe4fb 100644
--- a/lib/bundler/man/bundle-gem.1
+++ b/lib/bundler/man/bundle-gem.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-GEM" "1" "May 2025" ""
+.TH "BUNDLE\-GEM" "1" "June 2025" ""
.SH "NAME"
\fBbundle\-gem\fR \- Generate a project skeleton for creating a rubygem
.SH "SYNOPSIS"
@@ -19,67 +19,84 @@ The generated project skeleton can be customized with OPTIONS, as explained belo
\fBgem\.test\fR
.IP "" 0
.SH "OPTIONS"
-.IP "\(bu" 4
-\fB\-\-exe\fR, \fB\-\-bin\fR, \fB\-b\fR: Specify that Bundler should create a binary executable (as \fBexe/GEM_NAME\fR) in the generated rubygem project\. This binary will also be added to the \fBGEM_NAME\.gemspec\fR manifest\. This behavior is disabled by default\.
-.IP "\(bu" 4
-\fB\-\-no\-exe\fR: Do not create a binary (overrides \fB\-\-exe\fR specified in the global config)\.
-.IP "\(bu" 4
-\fB\-\-coc\fR: Add a \fBCODE_OF_CONDUCT\.md\fR file to the root of the generated project\. If this option is unspecified, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\.
-.IP "\(bu" 4
-\fB\-\-no\-coc\fR: Do not create a \fBCODE_OF_CONDUCT\.md\fR (overrides \fB\-\-coc\fR specified in the global config)\.
-.IP "\(bu" 4
-\fB\-\-changelog\fR Add a \fBCHANGELOG\.md\fR file to the root of the generated project\. If this option is unspecified, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\.
-.IP "\(bu" 4
-\fB\-\-no\-changelog\fR: Do not create a \fBCHANGELOG\.md\fR (overrides \fB\-\-changelog\fR specified in the global config)\.
-.IP "\(bu" 4
-\fB\-\-ext=c\fR, \fB\-\-ext=rust\fR: Add boilerplate for C or Rust (currently magnus \fIhttps://docs\.rs/magnus\fR based) extension code to the generated project\. This behavior is disabled by default\.
-.IP "\(bu" 4
-\fB\-\-no\-ext\fR: Do not add extension code (overrides \fB\-\-ext\fR specified in the global config)\.
-.IP "\(bu" 4
-\fB\-\-git\fR: Initialize a git repo inside your library\.
-.IP "\(bu" 4
-\fB\-\-github\-username=GITHUB_USERNAME\fR: Fill in GitHub username on README so that you don't have to do it manually\. Set a default with \fBbundle config set \-\-global gem\.github_username <your_username>\fR\.
-.IP "\(bu" 4
-\fB\-\-mit\fR: Add an MIT license to a \fBLICENSE\.txt\fR file in the root of the generated project\. Your name from the global git config is used for the copyright statement\. If this option is unspecified, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\.
-.IP "\(bu" 4
-\fB\-\-no\-mit\fR: Do not create a \fBLICENSE\.txt\fR (overrides \fB\-\-mit\fR specified in the global config)\.
-.IP "\(bu" 4
-\fB\-t\fR, \fB\-\-test=minitest\fR, \fB\-\-test=rspec\fR, \fB\-\-test=test\-unit\fR: Specify the test framework that Bundler should use when generating the project\. Acceptable values are \fBminitest\fR, \fBrspec\fR and \fBtest\-unit\fR\. The \fBGEM_NAME\.gemspec\fR will be configured and a skeleton test/spec directory will be created based on this option\. Given no option is specified:
+.TP
+\fB\-\-exe\fR, \fB\-\-bin\fR, \fB\-b\fR
+Specify that Bundler should create a binary executable (as \fBexe/GEM_NAME\fR) in the generated rubygem project\. This binary will also be added to the \fBGEM_NAME\.gemspec\fR manifest\. This behavior is disabled by default\.
+.TP
+\fB\-\-no\-exe\fR
+Do not create a binary (overrides \fB\-\-exe\fR specified in the global config)\.
+.TP
+\fB\-\-coc\fR
+Add a \fBCODE_OF_CONDUCT\.md\fR file to the root of the generated project\. If this option is unspecified, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\.
+.TP
+\fB\-\-no\-coc\fR
+Do not create a \fBCODE_OF_CONDUCT\.md\fR (overrides \fB\-\-coc\fR specified in the global config)\.
+.TP
+\fB\-\-changelog\fR
+Add a \fBCHANGELOG\.md\fR file to the root of the generated project\. If this option is unspecified, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\. Update the default with \fBbundle config set \-\-global gem\.changelog <true|false>\fR\.
+.TP
+\fB\-\-no\-changelog\fR
+Do not create a \fBCHANGELOG\.md\fR (overrides \fB\-\-changelog\fR specified in the global config)\.
+.TP
+\fB\-\-ext=c\fR, \fB\-\-ext=rust\fR
+Add boilerplate for C or Rust (currently magnus \fIhttps://docs\.rs/magnus\fR based) extension code to the generated project\. This behavior is disabled by default\.
+.TP
+\fB\-\-no\-ext\fR
+Do not add extension code (overrides \fB\-\-ext\fR specified in the global config)\.
+.TP
+\fB\-\-git\fR
+Initialize a git repo inside your library\.
+.TP
+\fB\-\-github\-username=GITHUB_USERNAME\fR
+Fill in GitHub username on README so that you don't have to do it manually\. Set a default with \fBbundle config set \-\-global gem\.github_username <your_username>\fR\.
+.TP
+\fB\-\-mit\fR
+Add an MIT license to a \fBLICENSE\.txt\fR file in the root of the generated project\. Your name from the global git config is used for the copyright statement\. If this option is unspecified, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\.
+.TP
+\fB\-\-no\-mit\fR
+Do not create a \fBLICENSE\.txt\fR (overrides \fB\-\-mit\fR specified in the global config)\.
+.TP
+\fB\-t\fR, \fB\-\-test=minitest\fR, \fB\-\-test=rspec\fR, \fB\-\-test=test\-unit\fR
+Specify the test framework that Bundler should use when generating the project\. Acceptable values are \fBminitest\fR, \fBrspec\fR and \fBtest\-unit\fR\. The \fBGEM_NAME\.gemspec\fR will be configured and a skeleton test/spec directory will be created based on this option\. Given no option is specified:
.IP
When Bundler is configured to generate tests, this defaults to Bundler's global config setting \fBgem\.test\fR\.
.IP
When Bundler is configured to not generate tests, an interactive prompt will be displayed and the answer will be used for the current rubygem project\.
.IP
When Bundler is unconfigured, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\.
-.IP "\(bu" 4
-\fB\-\-no\-test\fR: Do not use a test framework (overrides \fB\-\-test\fR specified in the global config)\.
-.IP "\(bu" 4
-\fB\-\-changelog\fR: Generate changelog file\. Set a default with \fBbundle config set \-\-global gem\.changelog true\fR\.
-.IP "\(bu" 4
-\fB\-\-ci\fR, \fB\-\-ci=circle\fR, \fB\-\-ci=github\fR, \fB\-\-ci=gitlab\fR: Specify the continuous integration service that Bundler should use when generating the project\. Acceptable values are \fBgithub\fR, \fBgitlab\fR and \fBcircle\fR\. A configuration file will be generated in the project directory\. Given no option is specified:
+.TP
+\fB\-\-no\-test\fR
+Do not use a test framework (overrides \fB\-\-test\fR specified in the global config)\.
+.TP
+\fB\-\-ci\fR, \fB\-\-ci=circle\fR, \fB\-\-ci=github\fR, \fB\-\-ci=gitlab\fR
+Specify the continuous integration service that Bundler should use when generating the project\. Acceptable values are \fBgithub\fR, \fBgitlab\fR and \fBcircle\fR\. A configuration file will be generated in the project directory\. Given no option is specified:
.IP
When Bundler is configured to generate CI files, this defaults to Bundler's global config setting \fBgem\.ci\fR\.
.IP
When Bundler is configured to not generate CI files, an interactive prompt will be displayed and the answer will be used for the current rubygem project\.
.IP
When Bundler is unconfigured, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\.
-.IP "\(bu" 4
-\fB\-\-no\-ci\fR: Do not use a continuous integration service (overrides \fB\-\-ci\fR specified in the global config)\.
-.IP "\(bu" 4
-\fB\-\-linter\fR, \fB\-\-linter=rubocop\fR, \fB\-\-linter=standard\fR: Specify the linter and code formatter that Bundler should add to the project's development dependencies\. Acceptable values are \fBrubocop\fR and \fBstandard\fR\. A configuration file will be generated in the project directory\. Given no option is specified:
+.TP
+\fB\-\-no\-ci\fR
+Do not use a continuous integration service (overrides \fB\-\-ci\fR specified in the global config)\.
+.TP
+\fB\-\-linter\fR, \fB\-\-linter=rubocop\fR, \fB\-\-linter=standard\fR
+Specify the linter and code formatter that Bundler should add to the project's development dependencies\. Acceptable values are \fBrubocop\fR and \fBstandard\fR\. A configuration file will be generated in the project directory\. Given no option is specified:
.IP
When Bundler is configured to add a linter, this defaults to Bundler's global config setting \fBgem\.linter\fR\.
.IP
When Bundler is configured not to add a linter, an interactive prompt will be displayed and the answer will be used for the current rubygem project\.
.IP
When Bundler is unconfigured, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\.
-.IP "\(bu" 4
-\fB\-\-no\-linter\fR: Do not add a linter (overrides \fB\-\-linter\fR specified in the global config)\.
-.IP "\(bu" 4
-\fB\-\-rubocop\fR: Add rubocop to the generated Rakefile and gemspec\. Set a default with \fBbundle config set \-\-global gem\.rubocop true\fR\.
-.IP "\(bu" 4
-\fB\-\-edit=EDIT\fR, \fB\-e=EDIT\fR: Open the resulting GEM_NAME\.gemspec in EDIT, or the default editor if not specified\. The default is \fB$BUNDLER_EDITOR\fR, \fB$VISUAL\fR, or \fB$EDITOR\fR\.
-.IP "" 0
+.TP
+\fB\-\-no\-linter\fR
+Do not add a linter (overrides \fB\-\-linter\fR specified in the global config)\.
+.TP
+\fB\-\-rubocop\fR
+Add rubocop to the generated Rakefile and gemspec\. Set a default with \fBbundle config set \-\-global gem\.rubocop true\fR\.
+.TP
+\fB\-\-edit=EDIT\fR, \fB\-e=EDIT\fR
+Open the resulting GEM_NAME\.gemspec in EDIT, or the default editor if not specified\. The default is \fB$BUNDLER_EDITOR\fR, \fB$VISUAL\fR, or \fB$EDITOR\fR\.
.SH "SEE ALSO"
.IP "\(bu" 4
bundle config(1) \fIbundle\-config\.1\.html\fR
diff --git a/lib/bundler/man/bundle-gem.1.ronn b/lib/bundler/man/bundle-gem.1.ronn
index 13dc55c310..049e0072aa 100644
--- a/lib/bundler/man/bundle-gem.1.ronn
+++ b/lib/bundler/man/bundle-gem.1.ronn
@@ -41,10 +41,11 @@ configuration file using the following names:
Do not create a `CODE_OF_CONDUCT.md` (overrides `--coc` specified in the
global config).
-* `--changelog`
+* `--changelog`:
Add a `CHANGELOG.md` file to the root of the generated project. If
this option is unspecified, an interactive prompt will be displayed and the
answer will be saved in Bundler's global config for future `bundle gem` use.
+ Update the default with `bundle config set --global gem.changelog <true|false>`.
* `--no-changelog`:
Do not create a `CHANGELOG.md` (overrides `--changelog` specified in the
@@ -95,9 +96,6 @@ configuration file using the following names:
Do not use a test framework (overrides `--test` specified in the global
config).
-* `--changelog`:
- Generate changelog file. Set a default with `bundle config set --global gem.changelog true`.
-
* `--ci`, `--ci=circle`, `--ci=github`, `--ci=gitlab`:
Specify the continuous integration service that Bundler should use when
generating the project. Acceptable values are `github`, `gitlab`
diff --git a/lib/bundler/man/bundle-help.1 b/lib/bundler/man/bundle-help.1
index 1af5a663d8..f24b050d37 100644
--- a/lib/bundler/man/bundle-help.1
+++ b/lib/bundler/man/bundle-help.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-HELP" "1" "May 2025" ""
+.TH "BUNDLE\-HELP" "1" "June 2025" ""
.SH "NAME"
\fBbundle\-help\fR \- Displays detailed help for each subcommand
.SH "SYNOPSIS"
diff --git a/lib/bundler/man/bundle-info.1 b/lib/bundler/man/bundle-info.1
index 30ab4cbeb4..82f39ebd0c 100644
--- a/lib/bundler/man/bundle-info.1
+++ b/lib/bundler/man/bundle-info.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-INFO" "1" "May 2025" ""
+.TH "BUNDLE\-INFO" "1" "June 2025" ""
.SH "NAME"
\fBbundle\-info\fR \- Show information for the given gem in your bundle
.SH "SYNOPSIS"
diff --git a/lib/bundler/man/bundle-init.1 b/lib/bundler/man/bundle-init.1
index 876c1f65a2..4571e09718 100644
--- a/lib/bundler/man/bundle-init.1
+++ b/lib/bundler/man/bundle-init.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-INIT" "1" "May 2025" ""
+.TH "BUNDLE\-INIT" "1" "June 2025" ""
.SH "NAME"
\fBbundle\-init\fR \- Generates a Gemfile into the current working directory
.SH "SYNOPSIS"
diff --git a/lib/bundler/man/bundle-inject.1 b/lib/bundler/man/bundle-inject.1
index 1433e7105d..acdf22a909 100644
--- a/lib/bundler/man/bundle-inject.1
+++ b/lib/bundler/man/bundle-inject.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-INJECT" "1" "May 2025" ""
+.TH "BUNDLE\-INJECT" "1" "June 2025" ""
.SH "NAME"
\fBbundle\-inject\fR \- Add named gem(s) with version requirements to Gemfile
.SH "SYNOPSIS"
diff --git a/lib/bundler/man/bundle-install.1 b/lib/bundler/man/bundle-install.1
index 4cd21c34cb..67a8df96fe 100644
--- a/lib/bundler/man/bundle-install.1
+++ b/lib/bundler/man/bundle-install.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-INSTALL" "1" "May 2025" ""
+.TH "BUNDLE\-INSTALL" "1" "June 2025" ""
.SH "NAME"
\fBbundle\-install\fR \- Install the dependencies specified in your Gemfile
.SH "SYNOPSIS"
diff --git a/lib/bundler/man/bundle-issue.1 b/lib/bundler/man/bundle-issue.1
index ee8bcc2749..62973e9892 100644
--- a/lib/bundler/man/bundle-issue.1
+++ b/lib/bundler/man/bundle-issue.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-ISSUE" "1" "May 2025" ""
+.TH "BUNDLE\-ISSUE" "1" "June 2025" ""
.SH "NAME"
\fBbundle\-issue\fR \- Get help reporting Bundler issues
.SH "SYNOPSIS"
diff --git a/lib/bundler/man/bundle-licenses.1 b/lib/bundler/man/bundle-licenses.1
index 4fd952e887..75e2b93d35 100644
--- a/lib/bundler/man/bundle-licenses.1
+++ b/lib/bundler/man/bundle-licenses.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-LICENSES" "1" "May 2025" ""
+.TH "BUNDLE\-LICENSES" "1" "June 2025" ""
.SH "NAME"
\fBbundle\-licenses\fR \- Print the license of all gems in the bundle
.SH "SYNOPSIS"
diff --git a/lib/bundler/man/bundle-list.1 b/lib/bundler/man/bundle-list.1
index cd6234797c..ed4e09e48e 100644
--- a/lib/bundler/man/bundle-list.1
+++ b/lib/bundler/man/bundle-list.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-LIST" "1" "May 2025" ""
+.TH "BUNDLE\-LIST" "1" "June 2025" ""
.SH "NAME"
\fBbundle\-list\fR \- List all the gems in the bundle
.SH "SYNOPSIS"
diff --git a/lib/bundler/man/bundle-lock.1 b/lib/bundler/man/bundle-lock.1
index c76c3e4233..0d78414aa4 100644
--- a/lib/bundler/man/bundle-lock.1
+++ b/lib/bundler/man/bundle-lock.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-LOCK" "1" "May 2025" ""
+.TH "BUNDLE\-LOCK" "1" "June 2025" ""
.SH "NAME"
\fBbundle\-lock\fR \- Creates / Updates a lockfile without installing
.SH "SYNOPSIS"
diff --git a/lib/bundler/man/bundle-open.1 b/lib/bundler/man/bundle-open.1
index 0e283e577f..b3016a5bbd 100644
--- a/lib/bundler/man/bundle-open.1
+++ b/lib/bundler/man/bundle-open.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-OPEN" "1" "May 2025" ""
+.TH "BUNDLE\-OPEN" "1" "June 2025" ""
.SH "NAME"
\fBbundle\-open\fR \- Opens the source directory for a gem in your bundle
.SH "SYNOPSIS"
diff --git a/lib/bundler/man/bundle-outdated.1 b/lib/bundler/man/bundle-outdated.1
index 616c1201ef..f98038ce69 100644
--- a/lib/bundler/man/bundle-outdated.1
+++ b/lib/bundler/man/bundle-outdated.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-OUTDATED" "1" "May 2025" ""
+.TH "BUNDLE\-OUTDATED" "1" "June 2025" ""
.SH "NAME"
\fBbundle\-outdated\fR \- List installed gems with newer versions available
.SH "SYNOPSIS"
diff --git a/lib/bundler/man/bundle-platform.1 b/lib/bundler/man/bundle-platform.1
index 47fdbf89d9..e9c40b8556 100644
--- a/lib/bundler/man/bundle-platform.1
+++ b/lib/bundler/man/bundle-platform.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-PLATFORM" "1" "May 2025" ""
+.TH "BUNDLE\-PLATFORM" "1" "June 2025" ""
.SH "NAME"
\fBbundle\-platform\fR \- Displays platform compatibility information
.SH "SYNOPSIS"
diff --git a/lib/bundler/man/bundle-plugin.1 b/lib/bundler/man/bundle-plugin.1
index e7650760f4..c1f95b05c6 100644
--- a/lib/bundler/man/bundle-plugin.1
+++ b/lib/bundler/man/bundle-plugin.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-PLUGIN" "1" "May 2025" ""
+.TH "BUNDLE\-PLUGIN" "1" "June 2025" ""
.SH "NAME"
\fBbundle\-plugin\fR \- Manage Bundler plugins
.SH "SYNOPSIS"
diff --git a/lib/bundler/man/bundle-pristine.1 b/lib/bundler/man/bundle-pristine.1
index e9df372482..84a02dfd47 100644
--- a/lib/bundler/man/bundle-pristine.1
+++ b/lib/bundler/man/bundle-pristine.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-PRISTINE" "1" "May 2025" ""
+.TH "BUNDLE\-PRISTINE" "1" "June 2025" ""
.SH "NAME"
\fBbundle\-pristine\fR \- Restores installed gems to their pristine condition
.SH "SYNOPSIS"
diff --git a/lib/bundler/man/bundle-remove.1 b/lib/bundler/man/bundle-remove.1
index c57aeb5898..00d9cf4319 100644
--- a/lib/bundler/man/bundle-remove.1
+++ b/lib/bundler/man/bundle-remove.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-REMOVE" "1" "May 2025" ""
+.TH "BUNDLE\-REMOVE" "1" "June 2025" ""
.SH "NAME"
\fBbundle\-remove\fR \- Removes gems from the Gemfile
.SH "SYNOPSIS"
diff --git a/lib/bundler/man/bundle-show.1 b/lib/bundler/man/bundle-show.1
index bba79d064e..d556c738f6 100644
--- a/lib/bundler/man/bundle-show.1
+++ b/lib/bundler/man/bundle-show.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-SHOW" "1" "May 2025" ""
+.TH "BUNDLE\-SHOW" "1" "June 2025" ""
.SH "NAME"
\fBbundle\-show\fR \- Shows all the gems in your bundle, or the path to a gem
.SH "SYNOPSIS"
diff --git a/lib/bundler/man/bundle-update.1 b/lib/bundler/man/bundle-update.1
index c76ed74d57..080d9b889f 100644
--- a/lib/bundler/man/bundle-update.1
+++ b/lib/bundler/man/bundle-update.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-UPDATE" "1" "May 2025" ""
+.TH "BUNDLE\-UPDATE" "1" "June 2025" ""
.SH "NAME"
\fBbundle\-update\fR \- Update your gems to the latest available versions
.SH "SYNOPSIS"
diff --git a/lib/bundler/man/bundle-version.1 b/lib/bundler/man/bundle-version.1
index 522a87383d..e3ccd023b6 100644
--- a/lib/bundler/man/bundle-version.1
+++ b/lib/bundler/man/bundle-version.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-VERSION" "1" "May 2025" ""
+.TH "BUNDLE\-VERSION" "1" "June 2025" ""
.SH "NAME"
\fBbundle\-version\fR \- Prints Bundler version information
.SH "SYNOPSIS"
diff --git a/lib/bundler/man/bundle-viz.1 b/lib/bundler/man/bundle-viz.1
index 5bb8c336a1..34a2cf1fff 100644
--- a/lib/bundler/man/bundle-viz.1
+++ b/lib/bundler/man/bundle-viz.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE\-VIZ" "1" "May 2025" ""
+.TH "BUNDLE\-VIZ" "1" "June 2025" ""
.SH "NAME"
\fBbundle\-viz\fR \- Generates a visual dependency graph for your Gemfile
.SH "SYNOPSIS"
diff --git a/lib/bundler/man/bundle.1 b/lib/bundler/man/bundle.1
index f87886cfcb..5c42b06547 100644
--- a/lib/bundler/man/bundle.1
+++ b/lib/bundler/man/bundle.1
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "BUNDLE" "1" "May 2025" ""
+.TH "BUNDLE" "1" "June 2025" ""
.SH "NAME"
\fBbundle\fR \- Ruby Dependency Management
.SH "SYNOPSIS"
diff --git a/lib/bundler/man/gemfile.5 b/lib/bundler/man/gemfile.5
index e1d433e924..8262ee0afc 100644
--- a/lib/bundler/man/gemfile.5
+++ b/lib/bundler/man/gemfile.5
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1
-.TH "GEMFILE" "5" "May 2025" ""
+.TH "GEMFILE" "5" "June 2025" ""
.SH "NAME"
\fBGemfile\fR \- A format for describing gem dependencies for Ruby programs
.SH "SYNOPSIS"
diff --git a/lib/bundler/rubygems_ext.rb b/lib/bundler/rubygems_ext.rb
index 6777c78194..8cf3b56b83 100644
--- a/lib/bundler/rubygems_ext.rb
+++ b/lib/bundler/rubygems_ext.rb
@@ -283,6 +283,18 @@ module Gem
end
end
+ if Gem.rubygems_version < Gem::Version.new("3.5.22")
+ module FixPathSourceMissingExtensions
+ def missing_extensions?
+ return false if %w[Bundler::Source::Path Bundler::Source::Gemspec].include?(source.class.name)
+
+ super
+ end
+ end
+
+ prepend FixPathSourceMissingExtensions
+ end
+
private
def dependencies_to_gemfile(dependencies, group = nil)
diff --git a/lib/bundler/rubygems_integration.rb b/lib/bundler/rubygems_integration.rb
index eddf36278c..5342c3dbf9 100644
--- a/lib/bundler/rubygems_integration.rb
+++ b/lib/bundler/rubygems_integration.rb
@@ -222,8 +222,6 @@ module Bundler
# Used to give better error messages when activating specs outside of the current bundle
def replace_bin_path(specs_by_name)
- gem_class = (class << Gem; self; end)
-
redefine_method(gem_class, :find_spec_for_exe) do |gem_name, *args|
exec_name = args.first
raise ArgumentError, "you must supply exec_name" unless exec_name
@@ -345,9 +343,13 @@ module Bundler
Gem::Specification.all = specs
end
- redefine_method((class << Gem; self; end), :finish_resolve) do |*|
+ redefine_method(gem_class, :finish_resolve) do |*|
[]
end
+
+ redefine_method(gem_class, :load_plugins) do |*|
+ load_plugin_files specs.flat_map(&:plugins)
+ end
end
def plain_specs
@@ -447,6 +449,12 @@ module Bundler
def default_stubs
Gem::Specification.default_stubs("*.gemspec")
end
+
+ private
+
+ def gem_class
+ class << Gem; self; end
+ end
end
def self.rubygems
diff --git a/lib/bundler/self_manager.rb b/lib/bundler/self_manager.rb
index 72bcb264ab..ab16061dc7 100644
--- a/lib/bundler/self_manager.rb
+++ b/lib/bundler/self_manager.rb
@@ -105,6 +105,7 @@ module Bundler
def autoswitching_applies?
ENV["BUNDLER_VERSION"].nil? &&
+ ENV["BUNDLER_4_MODE"].nil? &&
ruby_can_restart_with_same_arguments? &&
lockfile_version
end
diff --git a/lib/bundler/source/git/git_proxy.rb b/lib/bundler/source/git/git_proxy.rb
index 1a7a0959c9..f613377cb2 100644
--- a/lib/bundler/source/git/git_proxy.rb
+++ b/lib/bundler/source/git/git_proxy.rb
@@ -408,7 +408,7 @@ module Bundler
def capture3_args_for(cmd, dir)
return ["git", *cmd] unless dir
- if Bundler.feature_flag.bundler_3_mode? || supports_minus_c?
+ if Bundler.feature_flag.bundler_4_mode? || supports_minus_c?
["git", "-C", dir.to_s, *cmd]
else
["git", *cmd, { chdir: dir.to_s }]
diff --git a/lib/bundler/source/path.rb b/lib/bundler/source/path.rb
index 885dd96d85..ac76ae1fa0 100644
--- a/lib/bundler/source/path.rb
+++ b/lib/bundler/source/path.rb
@@ -167,6 +167,13 @@ module Bundler
next unless spec = load_gemspec(file)
spec.source = self
+ # The ignore attribute is for ignoring installed gems that don't
+ # have extensions correctly compiled for activation. In the case of
+ # path sources, there's a single version of each gem in the path
+ # source available to Bundler, so we always certainly want to
+ # consider that for activation and never makes sense to ignore it.
+ spec.ignored = false
+
# Validation causes extension_dir to be calculated, which depends
# on #source, so we validate here instead of load_gemspec
validate_spec(spec)
diff --git a/lib/bundler/source_map.rb b/lib/bundler/source_map.rb
index ca73e01f9d..a8e12d08c3 100644
--- a/lib/bundler/source_map.rb
+++ b/lib/bundler/source_map.rb
@@ -23,7 +23,7 @@ module Bundler
if previous_source.nil?
requirements[indirect_dependency_name] = source
else
- no_ambiguous_sources = Bundler.feature_flag.bundler_3_mode?
+ no_ambiguous_sources = Bundler.feature_flag.bundler_4_mode?
msg = ["The gem '#{indirect_dependency_name}' was found in multiple relevant sources."]
msg.concat [previous_source, source].map {|s| " * #{s}" }.sort
diff --git a/lib/bundler/templates/newgem/github/workflows/main.yml.tt b/lib/bundler/templates/newgem/github/workflows/main.yml.tt
index d1b5ae0534..9224ee0ca2 100644
--- a/lib/bundler/templates/newgem/github/workflows/main.yml.tt
+++ b/lib/bundler/templates/newgem/github/workflows/main.yml.tt
@@ -18,6 +18,8 @@ jobs:
steps:
- uses: actions/checkout@v4
+ with:
+ persist-credentials: false
<%- if config[:ext] == 'rust' -%>
- name: Set up Ruby & Rust
uses: oxidize-rb/actions/setup-ruby-and-rust@v1
diff --git a/lib/bundler/version.rb b/lib/bundler/version.rb
index fa24b4966e..a995f4f281 100644
--- a/lib/bundler/version.rb
+++ b/lib/bundler/version.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: false
module Bundler
- VERSION = "2.7.0.dev".freeze
+ VERSION = (ENV["BUNDLER_4_MODE"] == "true" ? "4.0.0" : "2.7.0.dev").freeze
def self.bundler_major_version
@bundler_major_version ||= VERSION.split(".").first.to_i
diff --git a/lib/net/http/generic_request.rb b/lib/net/http/generic_request.rb
index 44e329a0c8..c92004e557 100644
--- a/lib/net/http/generic_request.rb
+++ b/lib/net/http/generic_request.rb
@@ -102,6 +102,31 @@ class Net::HTTPGenericRequest
"\#<#{self.class} #{@method}>"
end
+ # Returns a string representation of the request with the details for pp:
+ #
+ # require 'pp'
+ # post = Net::HTTP::Post.new(uri)
+ # post.inspect # => "#<Net::HTTP::Post POST>"
+ # post.pretty_inspect
+ # # => #<Net::HTTP::Post
+ # POST
+ # path="/"
+ # headers={"accept-encoding" => ["gzip;q=1.0,deflate;q=0.6,identity;q=0.3"],
+ # "accept" => ["*/*"],
+ # "user-agent" => ["Ruby"],
+ # "host" => ["www.ruby-lang.org"]}>
+ #
+ def pretty_print(q)
+ q.object_group(self) {
+ q.breakable
+ q.text @method
+ q.breakable
+ q.text "path="; q.pp @path
+ q.breakable
+ q.text "headers="; q.pp to_hash
+ }
+ end
+
##
# Don't automatically decode response content-encoding if the user indicates
# they want to handle it.
@@ -260,7 +285,6 @@ class Net::HTTPGenericRequest
def send_request_with_body(sock, ver, path, body)
self.content_length = body.bytesize
delete 'Transfer-Encoding'
- supply_default_content_type
write_header sock, ver, path
wait_for_continue sock, ver if sock.continue_timeout
sock.write body
@@ -271,7 +295,6 @@ class Net::HTTPGenericRequest
raise ArgumentError,
"Content-Length not given and Transfer-Encoding is not `chunked'"
end
- supply_default_content_type
write_header sock, ver, path
wait_for_continue sock, ver if sock.continue_timeout
if chunked?
@@ -373,12 +396,6 @@ class Net::HTTPGenericRequest
buf.clear
end
- def supply_default_content_type
- return if content_type()
- warn 'net/http: Content-Type did not set; using application/x-www-form-urlencoded', uplevel: 1 if $VERBOSE
- set_content_type 'application/x-www-form-urlencoded'
- end
-
##
# Waits up to the continue timeout for a response from the server provided
# we're speaking HTTP 1.1 and are expecting a 100-continue response.
@@ -411,4 +428,3 @@ class Net::HTTPGenericRequest
end
end
-
diff --git a/lib/prism/polyfill/scan_byte.rb b/lib/prism/polyfill/scan_byte.rb
new file mode 100644
index 0000000000..2def4572c4
--- /dev/null
+++ b/lib/prism/polyfill/scan_byte.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+require "strscan"
+
+# Polyfill for StringScanner#scan_byte, which didn't exist until Ruby 3.4.
+if !(StringScanner.instance_methods.include?(:scan_byte))
+ StringScanner.include(
+ Module.new {
+ def scan_byte # :nodoc:
+ get_byte&.b&.ord
+ end
+ }
+ )
+end
diff --git a/lib/prism/prism.gemspec b/lib/prism/prism.gemspec
index 5cb5a98057..4daa511300 100644
--- a/lib/prism/prism.gemspec
+++ b/lib/prism/prism.gemspec
@@ -88,6 +88,7 @@ Gem::Specification.new do |spec|
"lib/prism/pattern.rb",
"lib/prism/polyfill/append_as_bytes.rb",
"lib/prism/polyfill/byteindex.rb",
+ "lib/prism/polyfill/scan_byte.rb",
"lib/prism/polyfill/unpack1.rb",
"lib/prism/polyfill/warn.rb",
"lib/prism/reflection.rb",
diff --git a/lib/prism/translation/parser/lexer.rb b/lib/prism/translation/parser/lexer.rb
index 349a0b257f..22ca3b6321 100644
--- a/lib/prism/translation/parser/lexer.rb
+++ b/lib/prism/translation/parser/lexer.rb
@@ -3,6 +3,7 @@
require "strscan"
require_relative "../../polyfill/append_as_bytes"
+require_relative "../../polyfill/scan_byte"
module Prism
module Translation
@@ -762,12 +763,12 @@ module Prism
elsif (value = scanner.scan(/M-\\?(?=[[:print:]])/))
# \M-x where x is an ASCII printable character
escape_read(result, scanner, control, true)
- elsif (byte = scanner.get_byte)
+ elsif (byte = scanner.scan_byte)
# Something else after an escape.
- if control && byte == "?"
+ if control && byte == 0x3f # ASCII '?'
result.append_as_bytes(escape_build(0x7f, false, meta))
else
- result.append_as_bytes(escape_build(byte.ord, control, meta))
+ result.append_as_bytes(escape_build(byte, control, meta))
end
end
end
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/commands/pristine_command.rb b/lib/rubygems/commands/pristine_command.rb
index 97f1646ba0..93503d2b69 100644
--- a/lib/rubygems/commands/pristine_command.rb
+++ b/lib/rubygems/commands/pristine_command.rb
@@ -137,11 +137,14 @@ extensions will be restored.
specs.group_by(&:full_name_with_location).values.each do |grouped_specs|
spec = grouped_specs.find {|s| !s.default_gem? } || grouped_specs.first
- unless only_executables_or_plugins?
+ only_executables = options[:only_executables]
+ only_plugins = options[:only_plugins]
+
+ unless only_executables || only_plugins
# Default gemspecs include changes provided by ruby-core installer that
# can't currently be pristined (inclusion of compiled extension targets in
# the file list). So stick to resetting executables if it's a default gem.
- options[:only_executables] = true if spec.default_gem?
+ only_executables = true if spec.default_gem?
end
if options.key? :skip
@@ -151,14 +154,14 @@ extensions will be restored.
end
end
- unless spec.extensions.empty? || options[:extensions] || only_executables_or_plugins?
+ unless spec.extensions.empty? || options[:extensions] || only_executables || only_plugins
say "Skipped #{spec.full_name_with_location}, it needs to compile an extension"
next
end
gem = spec.cache_file
- unless File.exist?(gem) || only_executables_or_plugins?
+ unless File.exist?(gem) || only_executables || only_plugins
require_relative "../remote_fetcher"
say "Cached gem for #{spec.full_name_with_location} not found, attempting to fetch..."
@@ -194,10 +197,10 @@ extensions will be restored.
bin_dir: bin_dir,
}
- if options[:only_executables]
+ if only_executables
installer = Gem::Installer.for_spec(spec, installer_options)
installer.generate_bin
- elsif options[:only_plugins]
+ elsif only_plugins
installer = Gem::Installer.for_spec(spec, installer_options)
installer.generate_plugins
else
@@ -208,10 +211,4 @@ extensions will be restored.
say "Restored #{spec.full_name_with_location}"
end
end
-
- private
-
- def only_executables_or_plugins?
- options[:only_executables] || options[:only_plugins]
- end
end
diff --git a/lib/rubygems/ext/cargo_builder.rb b/lib/rubygems/ext/cargo_builder.rb
index 03024a640e..21b50f394d 100644
--- a/lib/rubygems/ext/cargo_builder.rb
+++ b/lib/rubygems/ext/cargo_builder.rb
@@ -158,6 +158,10 @@ class Gem::Ext::CargoBuilder < Gem::Ext::Builder
# mkmf work properly.
def linker_args
cc_flag = self.class.shellsplit(makefile_config("CC"))
+ # Avoid to ccache like tool from Rust build
+ # see https://github.com/rubygems/rubygems/pull/8521#issuecomment-2689854359
+ # ex. CC="ccache gcc" or CC="sccache clang --any --args"
+ cc_flag.shift if cc_flag.size >= 2 && !cc_flag[1].start_with?("-")
linker = cc_flag.shift
link_args = cc_flag.flat_map {|a| ["-C", "link-arg=#{a}"] }
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/rubygems/resolver.rb b/lib/rubygems/resolver.rb
index 35d83abd2d..9bf5f80930 100644
--- a/lib/rubygems/resolver.rb
+++ b/lib/rubygems/resolver.rb
@@ -241,7 +241,7 @@ class Gem::Resolver
sources.each do |source|
groups[source].
- sort_by {|spec| [spec.version, spec.platform =~ Gem::Platform.local ? 1 : 0] }. # rubocop:disable Performance/RegexpMatch
+ sort_by {|spec| [spec.version, -Gem::Platform.platform_specificity_match(spec.platform, Gem::Platform.local)] }.
map {|spec| ActivationRequest.new spec, dependency }.
each {|activation_request| activation_requests << activation_request }
end
diff --git a/lib/rubygems/ssl_certs/rubygems.org/GlobalSignRootCA_R3.pem b/lib/rubygems/ssl_certs/rubygems.org/GlobalSign.pem
index 8afb219058..8afb219058 100644
--- a/lib/rubygems/ssl_certs/rubygems.org/GlobalSignRootCA_R3.pem
+++ b/lib/rubygems/ssl_certs/rubygems.org/GlobalSign.pem
diff --git a/lib/rubygems/ssl_certs/rubygems.org/GlobalSignRootCA.pem b/lib/rubygems/ssl_certs/rubygems.org/GlobalSignRootCA.pem
deleted file mode 100644
index f4ce4ca43d..0000000000
--- a/lib/rubygems/ssl_certs/rubygems.org/GlobalSignRootCA.pem
+++ /dev/null
@@ -1,21 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG
-A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv
-b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw
-MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i
-YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT
-aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ
-jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp
-xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp
-1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG
-snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ
-U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8
-9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E
-BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B
-AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz
-yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE
-38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP
-AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad
-DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME
-HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==
------END CERTIFICATE-----
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"
diff --git a/lib/weakref.rb b/lib/weakref.rb
index f0a7e7b318..0a09f7f993 100644
--- a/lib/weakref.rb
+++ b/lib/weakref.rb
@@ -17,7 +17,7 @@ require "delegate"
#
class WeakRef < Delegator
- VERSION = "0.1.3"
+ VERSION = "0.1.4"
##
# RefError is raised when a referenced object has been recycled by the
diff --git a/marshal.c b/marshal.c
index 55b3bf156a..7db4bfc6d9 100644
--- a/marshal.c
+++ b/marshal.c
@@ -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_cruby.py b/misc/lldb_cruby.py
index f263ca5732..b3d4fb509a 100755..100644
--- a/misc/lldb_cruby.py
+++ b/misc/lldb_cruby.py
@@ -1,4 +1,3 @@
-#!/usr/bin/env python
#coding: utf-8
#
# Usage: run `command script import -r misc/lldb_cruby.py` on LLDB
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/misc/tsan_suppressions.txt b/misc/tsan_suppressions.txt
index 18abf90571..e46f133a9e 100644
--- a/misc/tsan_suppressions.txt
+++ b/misc/tsan_suppressions.txt
@@ -65,6 +65,14 @@ race_top:rb_ractor_set_current_ec_
# Possible deadlock between Ractor lock and UBF lock
deadlock:ractor_sleep_interrupt
+# TSan reports a lock-order-inversion between thread_sched_lock_ and this lock.
+# It's unclear if that can cause a deadlock since the lock is on self
+deadlock:ractor_lock_self
+
+# TSan reports a deadlock when reacquiring the this lock after a barrier, but
+# we know the other threads have been stopped
+deadlock:rb_ractor_sched_barrier_start
+
# RVALUE_AGE_SET manipulates flag bits on objects which may be accessed in Ractors
race_top:RVALUE_AGE_SET
@@ -87,6 +95,10 @@ race:gccct_method_search
race:rb_ec_finalize
race:rb_ec_cleanup
+# TSan doesn't work well post-fork, this raises errors when creating the new
+# timer thread
+race:after_fork_ruby
+
# object_id races
race:object_id
diff --git a/namespace.c b/namespace.c
index 44afdd8f21..af7fb4459c 100644
--- a/namespace.c
+++ b/namespace.c
@@ -450,9 +450,6 @@ namespace_initialize(VALUE namespace)
// If a code in the namespace adds a constant, the constant will be visible even from root/main.
RCLASS_SET_PRIME_CLASSEXT_WRITABLE(namespace, true);
- // fallback to ivptr for ivars from shapes to manipulate the constant table
- rb_evict_ivars_to_hash(namespace);
-
// Get a clean constant table of Object even by writable one
// because ns was just created, so it has not touched any constants yet.
object_classext = RCLASS_EXT_WRITABLE_IN_NS(rb_cObject, ns);
diff --git a/nilclass.rb b/nilclass.rb
index 5a2e19680d..acd5666c71 100644
--- a/nilclass.rb
+++ b/nilclass.rb
@@ -1,6 +1,32 @@
class NilClass
#
# call-seq:
+ # rationalize(eps = nil) -> (0/1)
+ #
+ # Returns zero as a Rational:
+ #
+ # nil.rationalize # => (0/1)
+ #
+ # Argument +eps+ is ignored.
+ #
+ def rationalize(eps = nil)
+ 0r
+ end
+
+ #
+ # call-seq:
+ # to_c -> (0+0i)
+ #
+ # Returns zero as a Complex:
+ #
+ # nil.to_c # => (0+0i)
+ #
+ def to_c
+ 0i
+ end
+
+ #
+ # call-seq:
# nil.to_i -> 0
#
# Always returns zero.
@@ -22,4 +48,16 @@ class NilClass
def to_f
return 0.0
end
+
+ #
+ # call-seq:
+ # to_r -> (0/1)
+ #
+ # Returns zero as a Rational:
+ #
+ # nil.to_r # => (0/1)
+ #
+ def to_r
+ 0r
+ end
end
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:
diff --git a/object.c b/object.c
index 8e924b4e6a..ae1a8aa406 100644
--- a/object.c
+++ b/object.c
@@ -340,7 +340,7 @@ rb_obj_copy_ivar(VALUE dest, VALUE obj)
shape_id_t dest_shape_id = src_shape_id;
shape_id_t initial_shape_id = RBASIC_SHAPE_ID(dest);
- RUBY_ASSERT(RSHAPE(initial_shape_id)->type == SHAPE_ROOT);
+ RUBY_ASSERT(RSHAPE_TYPE_P(initial_shape_id, SHAPE_ROOT));
dest_shape_id = rb_shape_rebuild(initial_shape_id, src_shape_id);
if (UNLIKELY(rb_shape_too_complex_p(dest_shape_id))) {
@@ -373,12 +373,12 @@ init_copy(VALUE dest, VALUE obj)
if (OBJ_FROZEN(dest)) {
rb_raise(rb_eTypeError, "[bug] frozen object (%s) allocated", rb_obj_classname(dest));
}
- RBASIC(dest)->flags &= ~(T_MASK|FL_EXIVAR);
+ RBASIC(dest)->flags &= ~T_MASK;
// Copies the shape id from obj to dest
- RBASIC(dest)->flags |= RBASIC(obj)->flags & (T_MASK|FL_EXIVAR);
+ 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:
diff --git a/parse.y b/parse.y
index 156c78c0c6..3138061b98 100644
--- a/parse.y
+++ b/parse.y
@@ -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;
}
diff --git a/prism_compile.c b/prism_compile.c
index c71c1429b2..05697ff5cf 100644
--- a/prism_compile.c
+++ b/prism_compile.c
@@ -3497,7 +3497,7 @@ pm_compile_builtin_mandatory_only_method(rb_iseq_t *iseq, pm_scope_node_t *scope
pm_scope_node_init(&def.base, &next_scope_node, scope_node);
int error_state;
- ISEQ_BODY(iseq)->mandatory_only_iseq = pm_iseq_new_with_opt(
+ const rb_iseq_t *mandatory_only_iseq = pm_iseq_new_with_opt(
&next_scope_node,
rb_iseq_base_label(iseq),
rb_iseq_path(iseq),
@@ -3509,6 +3509,7 @@ pm_compile_builtin_mandatory_only_method(rb_iseq_t *iseq, pm_scope_node_t *scope
ISEQ_COMPILE_DATA(iseq)->option,
&error_state
);
+ RB_OBJ_WRITE(iseq, &ISEQ_BODY(iseq)->mandatory_only_iseq, (VALUE)mandatory_only_iseq);
if (error_state) {
RUBY_ASSERT(ISEQ_BODY(iseq)->mandatory_only_iseq == NULL);
@@ -5164,6 +5165,20 @@ pm_compile_target_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *cons
break;
}
+ case PM_SPLAT_NODE: {
+ // Splat nodes capture all values into an array. They can be used
+ // as targets in assignments or for loops.
+ //
+ // for *x in []; end
+ //
+ const pm_splat_node_t *cast = (const pm_splat_node_t *) node;
+
+ if (cast->expression != NULL) {
+ pm_compile_target_node(iseq, cast->expression, parents, writes, cleanup, scope_node, state);
+ }
+
+ break;
+ }
default:
rb_bug("Unexpected node type: %s", pm_node_type_to_str(PM_NODE_TYPE(node)));
break;
@@ -5277,7 +5292,8 @@ pm_compile_for_node_index(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *c
case PM_INSTANCE_VARIABLE_TARGET_NODE:
case PM_CONSTANT_PATH_TARGET_NODE:
case PM_CALL_TARGET_NODE:
- case PM_INDEX_TARGET_NODE: {
+ case PM_INDEX_TARGET_NODE:
+ case PM_SPLAT_NODE: {
// For other targets, we need to potentially compile the parent or
// owning expression of this target, then retrieve the value, expand it,
// and then compile the necessary writes.
diff --git a/proc.c b/proc.c
index 98aa10d59f..8543110476 100644
--- a/proc.c
+++ b/proc.c
@@ -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);
}
}
diff --git a/ractor.c b/ractor.c
index 177906ea1c..317b24dca2 100644
--- a/ractor.c
+++ b/ractor.c
@@ -1188,6 +1188,7 @@ obj_traverse_i(VALUE obj, struct obj_traverse_data *data)
// already traversed
return 0;
}
+ RB_OBJ_WRITTEN(data->rec_hash, Qundef, obj);
struct obj_traverse_callback_data d = {
.stop = false,
@@ -1357,8 +1358,25 @@ make_shareable_check_shareable(VALUE obj)
}
}
- if (RB_TYPE_P(obj, T_IMEMO)) {
+ 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;
}
if (!RB_OBJ_FROZEN_RAW(obj)) {
@@ -1627,6 +1645,8 @@ obj_traverse_replace_i(VALUE obj, struct obj_traverse_replace_data *data)
}
else {
st_insert(obj_traverse_replace_rec(data), (st_data_t)obj, replacement);
+ RB_OBJ_WRITTEN(data->rec_hash, Qundef, obj);
+ RB_OBJ_WRITTEN(data->rec_hash, Qundef, replacement);
}
if (!data->move) {
@@ -1639,9 +1659,9 @@ obj_traverse_replace_i(VALUE obj, struct obj_traverse_replace_data *data)
else if (data->replacement != _val) { RB_OBJ_WRITE(obj, &v, data->replacement); } \
} while (0)
- if (UNLIKELY(FL_TEST_RAW(obj, FL_EXIVAR))) {
- struct gen_fields_tbl *fields_tbl;
- rb_ivar_generic_fields_tbl_lookup(obj, &fields_tbl);
+ if (UNLIKELY(rb_obj_exivar_p(obj))) {
+ 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 = {
@@ -1650,7 +1670,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
@@ -1659,8 +1679,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]);
}
}
}
@@ -1864,11 +1885,14 @@ move_leave(VALUE obj, struct obj_traverse_replace_data *data)
rb_gc_obj_slot_size(obj) - sizeof(VALUE)
);
+ // We've copied obj's references to the replacement
+ rb_gc_writebarrier_remember(data->replacement);
+
void rb_replace_generic_ivar(VALUE clone, VALUE obj); // variable.c
rb_gc_obj_id_moved(data->replacement);
- if (UNLIKELY(FL_TEST_RAW(obj, FL_EXIVAR))) {
+ if (UNLIKELY(rb_obj_exivar_p(obj))) {
rb_replace_generic_ivar(data->replacement, obj);
}
@@ -2241,6 +2265,28 @@ struct cross_ractor_require {
ID name;
};
+static void
+cross_ractor_require_mark(void *ptr)
+{
+ struct cross_ractor_require *crr = (struct cross_ractor_require *)ptr;
+ rb_gc_mark(crr->port);
+ rb_gc_mark(crr->result);
+ rb_gc_mark(crr->exception);
+ rb_gc_mark(crr->feature);
+ rb_gc_mark(crr->module);
+}
+
+static const rb_data_type_t cross_ractor_require_data_type = {
+ "ractor/cross_ractor_require",
+ {
+ cross_ractor_require_mark,
+ RUBY_DEFAULT_FREE,
+ NULL, // memsize
+ NULL, // compact
+ },
+ 0, 0, RUBY_TYPED_FREE_IMMEDIATELY
+};
+
static VALUE
require_body(VALUE data)
{
@@ -2287,8 +2333,11 @@ require_result_copy_resuce(VALUE data, VALUE errinfo)
}
static VALUE
-ractor_require_protect(struct cross_ractor_require *crr, VALUE (*func)(VALUE))
+ractor_require_protect(VALUE crr_obj, VALUE (*func)(VALUE))
{
+ struct cross_ractor_require *crr;
+ TypedData_Get_Struct(crr_obj, struct cross_ractor_require, &cross_ractor_require_data_type, crr);
+
// catch any error
rb_rescue2(func, (VALUE)crr,
require_rescue, (VALUE)crr, rb_eException, 0);
@@ -2297,43 +2346,49 @@ ractor_require_protect(struct cross_ractor_require *crr, VALUE (*func)(VALUE))
require_result_copy_resuce, (VALUE)crr, rb_eException, 0);
ractor_port_send(GET_EC(), crr->port, Qtrue, Qfalse);
+ RB_GC_GUARD(crr_obj);
return Qnil;
}
static VALUE
-ractor_require_func(void *data)
+ractor_require_func(void *crr_obj)
{
- struct cross_ractor_require *crr = (struct cross_ractor_require *)data;
- return ractor_require_protect(crr, require_body);
+ return ractor_require_protect((VALUE)crr_obj, require_body);
}
VALUE
rb_ractor_require(VALUE feature)
{
- // TODO: make feature shareable
- struct cross_ractor_require crr = {
- .feature = feature, // TODO: ractor
- .port = ractor_port_new(GET_RACTOR()),
- .result = Qundef,
- .exception = Qundef,
- };
+ struct cross_ractor_require *crr;
+ VALUE crr_obj = TypedData_Make_Struct(0, struct cross_ractor_require, &cross_ractor_require_data_type, crr);
+ FL_SET_RAW(crr_obj, RUBY_FL_SHAREABLE);
+
+ // Convert feature to proper file path and make it shareable as fstring
+ crr->feature = rb_fstring(FilePathValue(feature));
+ crr->port = ractor_port_new(GET_RACTOR());
+ crr->result = Qundef;
+ crr->exception = Qundef;
rb_execution_context_t *ec = GET_EC();
rb_ractor_t *main_r = GET_VM()->ractor.main_ractor;
- rb_ractor_interrupt_exec(main_r, ractor_require_func, &crr, 0);
+ rb_ractor_interrupt_exec(main_r, ractor_require_func, (void *)crr_obj, rb_interrupt_exec_flag_value_data);
// wait for require done
- ractor_port_receive(ec, crr.port);
- ractor_port_close(ec, crr.port);
+ ractor_port_receive(ec, crr->port);
+ ractor_port_close(ec, crr->port);
+
+ VALUE exc = crr->exception;
+ VALUE result = crr->result;
+ RB_GC_GUARD(crr_obj);
- if (crr.exception != Qundef) {
- ractor_reset_belonging(crr.exception);
- rb_exc_raise(crr.exception);
+ if (exc != Qundef) {
+ ractor_reset_belonging(exc);
+ rb_exc_raise(exc);
}
else {
- RUBY_ASSERT(crr.result != Qundef);
- ractor_reset_belonging(crr.result);
- return crr.result;
+ RUBY_ASSERT(result != Qundef);
+ ractor_reset_belonging(result);
+ return result;
}
}
@@ -2352,36 +2407,40 @@ autoload_load_body(VALUE data)
}
static VALUE
-ractor_autoload_load_func(void *data)
+ractor_autoload_load_func(void *crr_obj)
{
- struct cross_ractor_require *crr = (struct cross_ractor_require *)data;
- return ractor_require_protect(crr, autoload_load_body);
+ return ractor_require_protect((VALUE)crr_obj, autoload_load_body);
}
VALUE
rb_ractor_autoload_load(VALUE module, ID name)
{
- struct cross_ractor_require crr = {
- .module = module,
- .name = name,
- .port = ractor_port_new(GET_RACTOR()),
- .result = Qundef,
- .exception = Qundef,
- };
+ struct cross_ractor_require *crr;
+ VALUE crr_obj = TypedData_Make_Struct(0, struct cross_ractor_require, &cross_ractor_require_data_type, crr);
+ FL_SET_RAW(crr_obj, RUBY_FL_SHAREABLE);
+ crr->module = module;
+ crr->name = name;
+ crr->port = ractor_port_new(GET_RACTOR());
+ crr->result = Qundef;
+ crr->exception = Qundef;
rb_execution_context_t *ec = GET_EC();
rb_ractor_t *main_r = GET_VM()->ractor.main_ractor;
- rb_ractor_interrupt_exec(main_r, ractor_autoload_load_func, &crr, 0);
+ rb_ractor_interrupt_exec(main_r, ractor_autoload_load_func, (void *)crr_obj, rb_interrupt_exec_flag_value_data);
// wait for require done
- ractor_port_receive(ec, crr.port);
- ractor_port_close(ec, crr.port);
+ ractor_port_receive(ec, crr->port);
+ ractor_port_close(ec, crr->port);
+
+ VALUE exc = crr->exception;
+ VALUE result = crr->result;
+ RB_GC_GUARD(crr_obj);
- if (crr.exception != Qundef) {
- rb_exc_raise(crr.exception);
+ if (exc != Qundef) {
+ rb_exc_raise(exc);
}
else {
- return crr.result;
+ return result;
}
}
diff --git a/ractor_sync.c b/ractor_sync.c
index 0fcc293504..30c386663c 100644
--- a/ractor_sync.c
+++ b/ractor_sync.c
@@ -81,6 +81,7 @@ ractor_port_init(VALUE rpv, rb_ractor_t *r)
struct ractor_port *rp = RACTOR_PORT_PTR(rpv);
rp->r = r;
+ RB_OBJ_WRITTEN(rpv, Qundef, r->pub.self);
rp->id_ = ractor_genid_for_port(r);
ractor_add_port(r, ractor_port_id(rp));
@@ -102,6 +103,7 @@ ractor_port_initialzie_copy(VALUE self, VALUE orig)
struct ractor_port *dst = RACTOR_PORT_PTR(self);
struct ractor_port *src = RACTOR_PORT_PTR(orig);
dst->r = src->r;
+ RB_OBJ_WRITTEN(self, Qundef, dst->r->pub.self);
dst->id_ = ractor_port_id(src);
return self;
@@ -1197,6 +1199,7 @@ ractor_send_basket(rb_execution_context_t *ec, const struct ractor_port *rp, str
RUBY_DEBUG_LOG("closed:%u@r%u", (unsigned int)ractor_port_id(rp), rb_ractor_id(rp->r));
if (raise_on_error) {
+ ractor_basket_free(b);
rb_raise(rb_eRactorClosedError, "The port was already closed");
}
}
diff --git a/rational.c b/rational.c
index f1547856b4..89e74c328d 100644
--- a/rational.c
+++ b/rational.c
@@ -2109,39 +2109,6 @@ rb_float_denominator(VALUE self)
/*
* call-seq:
- * to_r -> (0/1)
- *
- * Returns zero as a Rational:
- *
- * nil.to_r # => (0/1)
- *
- */
-static VALUE
-nilclass_to_r(VALUE self)
-{
- return rb_rational_new1(INT2FIX(0));
-}
-
-/*
- * call-seq:
- * rationalize(eps = nil) -> (0/1)
- *
- * Returns zero as a Rational:
- *
- * nil.rationalize # => (0/1)
- *
- * Argument +eps+ is ignored.
- *
- */
-static VALUE
-nilclass_rationalize(int argc, VALUE *argv, VALUE self)
-{
- rb_check_arity(argc, 0, 1);
- return nilclass_to_r(self);
-}
-
-/*
- * call-seq:
* int.to_r -> rational
*
* Returns the value as a rational.
@@ -2823,8 +2790,6 @@ Init_Rational(void)
rb_define_method(rb_cFloat, "numerator", rb_float_numerator, 0);
rb_define_method(rb_cFloat, "denominator", rb_float_denominator, 0);
- rb_define_method(rb_cNilClass, "to_r", nilclass_to_r, 0);
- rb_define_method(rb_cNilClass, "rationalize", nilclass_rationalize, -1);
rb_define_method(rb_cInteger, "to_r", integer_to_r, 0);
rb_define_method(rb_cInteger, "rationalize", integer_rationalize, -1);
rb_define_method(rb_cFloat, "to_r", float_to_r, 0);
diff --git a/re.c b/re.c
index 96a3cbeaa9..b47538d594 100644
--- a/re.c
+++ b/re.c
@@ -28,6 +28,7 @@
#include "ruby/encoding.h"
#include "ruby/re.h"
#include "ruby/util.h"
+#include "ractor_core.h"
VALUE rb_eRegexpError, rb_eRegexpTimeoutError;
@@ -1666,7 +1667,7 @@ rb_reg_prepare_re(VALUE re, VALUE str)
RSTRING_GETMEM(unescaped, ptr, len);
/* If there are no other users of this regex, then we can directly overwrite it. */
- if (RREGEXP(re)->usecnt == 0) {
+ if (ruby_single_main_ractor && RREGEXP(re)->usecnt == 0) {
regex_t tmp_reg;
r = onig_new_without_alloc(&tmp_reg, (UChar *)ptr, (UChar *)(ptr + len),
reg->options, enc,
@@ -3499,12 +3500,17 @@ static VALUE reg_cache;
VALUE
rb_reg_regcomp(VALUE str)
{
- if (reg_cache && RREGEXP_SRC_LEN(reg_cache) == RSTRING_LEN(str)
- && ENCODING_GET(reg_cache) == ENCODING_GET(str)
- && memcmp(RREGEXP_SRC_PTR(reg_cache), RSTRING_PTR(str), RSTRING_LEN(str)) == 0)
- return reg_cache;
+ if (rb_ractor_main_p()) {
+ if (reg_cache && RREGEXP_SRC_LEN(reg_cache) == RSTRING_LEN(str)
+ && ENCODING_GET(reg_cache) == ENCODING_GET(str)
+ && memcmp(RREGEXP_SRC_PTR(reg_cache), RSTRING_PTR(str), RSTRING_LEN(str)) == 0)
+ return reg_cache;
- return reg_cache = rb_reg_new_str(str, 0);
+ return reg_cache = rb_reg_new_str(str, 0);
+ }
+ else {
+ return rb_reg_new_str(str, 0);
+ }
}
static st_index_t reg_hash(VALUE re);
diff --git a/ruby_atomic.h b/ruby_atomic.h
index f5f32191af..04c5d6d9f8 100644
--- a/ruby_atomic.h
+++ b/ruby_atomic.h
@@ -36,8 +36,10 @@ rbimpl_atomic_load_relaxed(volatile rb_atomic_t *ptr)
}
#define ATOMIC_LOAD_RELAXED(var) rbimpl_atomic_load_relaxed(&(var))
+typedef RBIMPL_ALIGNAS(8) uint64_t rbimpl_atomic_uint64_t;
+
static inline uint64_t
-rbimpl_atomic_u64_load_relaxed(const volatile uint64_t *value)
+rbimpl_atomic_u64_load_relaxed(const volatile rbimpl_atomic_uint64_t *value)
{
#if defined(HAVE_GCC_ATOMIC_BUILTINS_64)
return __atomic_load_n(value, __ATOMIC_RELAXED);
@@ -54,7 +56,7 @@ rbimpl_atomic_u64_load_relaxed(const volatile uint64_t *value)
#define ATOMIC_U64_LOAD_RELAXED(var) rbimpl_atomic_u64_load_relaxed(&(var))
static inline void
-rbimpl_atomic_u64_set_relaxed(volatile uint64_t *address, uint64_t value)
+rbimpl_atomic_u64_set_relaxed(volatile rbimpl_atomic_uint64_t *address, uint64_t value)
{
#if defined(HAVE_GCC_ATOMIC_BUILTINS_64)
__atomic_store_n(address, value, __ATOMIC_RELAXED);
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;
}
diff --git a/shape.c b/shape.c
index 668850cdd4..50cf8dcc0d 100644
--- a/shape.c
+++ b/shape.c
@@ -33,9 +33,7 @@
#define MAX_SHAPE_ID (SHAPE_BUFFER_SIZE - 1)
#define ANCESTOR_SEARCH_MAX_DEPTH 2
-static ID id_frozen;
-static ID id_t_object;
-ID ruby_internal_object_id; // extern
+static ID id_object_id;
#define LEAF 0
#define BLACK 0x0
@@ -48,8 +46,8 @@ redblack_left(redblack_node_t *node)
return LEAF;
}
else {
- RUBY_ASSERT(node->l < GET_SHAPE_TREE()->cache_size);
- redblack_node_t *left = &GET_SHAPE_TREE()->shape_cache[node->l - 1];
+ RUBY_ASSERT(node->l < rb_shape_tree.cache_size);
+ redblack_node_t *left = &rb_shape_tree.shape_cache[node->l - 1];
return left;
}
}
@@ -61,8 +59,8 @@ redblack_right(redblack_node_t *node)
return LEAF;
}
else {
- RUBY_ASSERT(node->r < GET_SHAPE_TREE()->cache_size);
- redblack_node_t *right = &GET_SHAPE_TREE()->shape_cache[node->r - 1];
+ RUBY_ASSERT(node->r < rb_shape_tree.cache_size);
+ redblack_node_t *right = &rb_shape_tree.shape_cache[node->r - 1];
return right;
}
}
@@ -120,7 +118,7 @@ redblack_id_for(redblack_node_t *node)
return 0;
}
else {
- redblack_node_t *redblack_nodes = GET_SHAPE_TREE()->shape_cache;
+ redblack_node_t *redblack_nodes = rb_shape_tree.shape_cache;
redblack_id_t id = (redblack_id_t)(node - redblack_nodes);
return id + 1;
}
@@ -129,7 +127,7 @@ redblack_id_for(redblack_node_t *node)
static redblack_node_t *
redblack_new(char color, ID key, rb_shape_t *value, redblack_node_t *left, redblack_node_t *right)
{
- if (GET_SHAPE_TREE()->cache_size + 1 >= REDBLACK_CACHE_SIZE) {
+ if (rb_shape_tree.cache_size + 1 >= REDBLACK_CACHE_SIZE) {
// We're out of cache, just quit
return LEAF;
}
@@ -137,8 +135,8 @@ redblack_new(char color, ID key, rb_shape_t *value, redblack_node_t *left, redbl
RUBY_ASSERT(left == LEAF || left->key < key);
RUBY_ASSERT(right == LEAF || right->key > key);
- redblack_node_t *redblack_nodes = GET_SHAPE_TREE()->shape_cache;
- redblack_node_t *node = &redblack_nodes[(GET_SHAPE_TREE()->cache_size)++];
+ redblack_node_t *redblack_nodes = rb_shape_tree.shape_cache;
+ redblack_node_t *node = &redblack_nodes[(rb_shape_tree.cache_size)++];
node->key = key;
node->value = (rb_shape_t *)((uintptr_t)value | color);
node->l = redblack_id_for(left);
@@ -288,20 +286,20 @@ redblack_insert(redblack_node_t *tree, ID key, rb_shape_t *value)
}
#endif
-rb_shape_tree_t *rb_shape_tree_ptr = NULL;
+rb_shape_tree_t rb_shape_tree = { 0 };
static VALUE shape_tree_obj = Qfalse;
rb_shape_t *
rb_shape_get_root_shape(void)
{
- return GET_SHAPE_TREE()->root_shape;
+ return rb_shape_tree.root_shape;
}
static void
shape_tree_mark(void *data)
{
rb_shape_t *cursor = rb_shape_get_root_shape();
- rb_shape_t *end = RSHAPE(GET_SHAPE_TREE()->next_shape_id - 1);
+ rb_shape_t *end = RSHAPE(rb_shape_tree.next_shape_id - 1);
while (cursor < end) {
if (cursor->edges && !SINGLE_CHILD_P(cursor->edges)) {
rb_gc_mark_movable(cursor->edges);
@@ -314,7 +312,7 @@ static void
shape_tree_compact(void *data)
{
rb_shape_t *cursor = rb_shape_get_root_shape();
- rb_shape_t *end = RSHAPE(GET_SHAPE_TREE()->next_shape_id - 1);
+ rb_shape_t *end = RSHAPE(rb_shape_tree.next_shape_id - 1);
while (cursor < end) {
if (cursor->edges && !SINGLE_CHILD_P(cursor->edges)) {
cursor->edges = rb_gc_location(cursor->edges);
@@ -326,7 +324,7 @@ shape_tree_compact(void *data)
static size_t
shape_tree_memsize(const void *data)
{
- return GET_SHAPE_TREE()->cache_size * sizeof(redblack_node_t);
+ return rb_shape_tree.cache_size * sizeof(redblack_node_t);
}
static const rb_data_type_t shape_tree_type = {
@@ -349,14 +347,14 @@ static inline shape_id_t
raw_shape_id(rb_shape_t *shape)
{
RUBY_ASSERT(shape);
- return (shape_id_t)(shape - GET_SHAPE_TREE()->shape_list);
+ return (shape_id_t)(shape - rb_shape_tree.shape_list);
}
static inline shape_id_t
shape_id(rb_shape_t *shape, shape_id_t previous_shape_id)
{
RUBY_ASSERT(shape);
- shape_id_t raw_id = (shape_id_t)(shape - GET_SHAPE_TREE()->shape_list);
+ shape_id_t raw_id = (shape_id_t)(shape - rb_shape_tree.shape_list);
return raw_id | (previous_shape_id & SHAPE_ID_FLAGS_MASK);
}
@@ -373,22 +371,13 @@ rb_shape_each_shape_id(each_shape_callback callback, void *data)
{
rb_shape_t *start = rb_shape_get_root_shape();
rb_shape_t *cursor = start;
- rb_shape_t *end = RSHAPE(GET_SHAPE_TREE()->next_shape_id);
+ rb_shape_t *end = RSHAPE(rb_shape_tree.next_shape_id);
while (cursor < end) {
callback((shape_id_t)(cursor - start), data);
cursor += 1;
}
}
-RUBY_FUNC_EXPORTED rb_shape_t *
-rb_shape_lookup(shape_id_t shape_id)
-{
- uint32_t offset = (shape_id & SHAPE_ID_OFFSET_MASK);
- RUBY_ASSERT(offset != INVALID_SHAPE_ID);
-
- return &GET_SHAPE_TREE()->shape_list[offset];
-}
-
RUBY_FUNC_EXPORTED shape_id_t
rb_obj_shape_id(VALUE obj)
{
@@ -396,6 +385,13 @@ rb_obj_shape_id(VALUE obj)
return SPECIAL_CONST_SHAPE_ID;
}
+ if (BUILTIN_TYPE(obj) == T_CLASS || BUILTIN_TYPE(obj) == T_MODULE) {
+ VALUE fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj);
+ if (fields_obj) {
+ return RBASIC_SHAPE_ID(fields_obj);
+ }
+ return ROOT_SHAPE_ID;
+ }
return RBASIC_SHAPE_ID(obj);
}
@@ -416,14 +412,14 @@ rb_shape_depth(shape_id_t shape_id)
static rb_shape_t *
shape_alloc(void)
{
- shape_id_t shape_id = (shape_id_t)RUBY_ATOMIC_FETCH_ADD(GET_SHAPE_TREE()->next_shape_id, 1);
+ shape_id_t shape_id = (shape_id_t)RUBY_ATOMIC_FETCH_ADD(rb_shape_tree.next_shape_id, 1);
if (shape_id == (MAX_SHAPE_ID + 1)) {
// TODO: Make an OutOfShapesError ??
rb_bug("Out of shapes");
}
- return &GET_SHAPE_TREE()->shape_list[shape_id];
+ return &rb_shape_tree.shape_list[shape_id];
}
static rb_shape_t *
@@ -487,7 +483,7 @@ redblack_cache_ancestors(rb_shape_t *shape)
static attr_index_t
shape_grow_capa(attr_index_t current_capa)
{
- const attr_index_t *capacities = GET_SHAPE_TREE()->capacities;
+ const attr_index_t *capacities = rb_shape_tree.capacities;
// First try to use the next size that will be embeddable in a larger object slot.
attr_index_t capa;
@@ -566,7 +562,7 @@ retry:
if (!res) {
// If we're not allowed to create a new variation, of if we're out of shapes
// we return TOO_COMPLEX_SHAPE.
- if (!new_variations_allowed || GET_SHAPE_TREE()->next_shape_id > MAX_SHAPE_ID) {
+ if (!new_variations_allowed || rb_shape_tree.next_shape_id > MAX_SHAPE_ID) {
res = NULL;
}
else {
@@ -642,7 +638,7 @@ get_next_shape_internal(rb_shape_t *shape, ID id, enum shape_type shape_type, bo
if (!res) {
// If we're not allowed to create a new variation, of if we're out of shapes
// we return TOO_COMPLEX_SHAPE.
- if (!new_variations_allowed || GET_SHAPE_TREE()->next_shape_id > MAX_SHAPE_ID) {
+ if (!new_variations_allowed || rb_shape_tree.next_shape_id > MAX_SHAPE_ID) {
res = NULL;
}
else {
@@ -716,7 +712,7 @@ shape_transition_object_id(shape_id_t original_shape_id)
RUBY_ASSERT(!rb_shape_has_object_id(original_shape_id));
bool dont_care;
- rb_shape_t *shape = get_next_shape_internal(RSHAPE(original_shape_id), ruby_internal_object_id, SHAPE_OBJ_ID, &dont_care, true);
+ rb_shape_t *shape = get_next_shape_internal(RSHAPE(original_shape_id), id_object_id, SHAPE_OBJ_ID, &dont_care, true);
if (!shape) {
shape = RSHAPE(ROOT_SHAPE_WITH_OBJ_ID);
}
@@ -881,14 +877,11 @@ shape_get_next(rb_shape_t *shape, VALUE obj, ID id, bool emit_warnings)
#endif
VALUE klass;
- switch (BUILTIN_TYPE(obj)) {
- case T_CLASS:
- case T_MODULE:
- klass = rb_singleton_class(obj);
- break;
- default:
+ if (IMEMO_TYPE_P(obj, imemo_fields)) { // HACK
+ klass = CLASS_OF(obj);
+ }
+ else {
klass = rb_obj_class(obj);
- break;
}
bool allow_new_shape = RCLASS_VARIATION_COUNT(klass) < SHAPE_MAX_VARIATIONS;
@@ -1151,7 +1144,7 @@ rb_shape_copy_complex_ivars(VALUE dest, VALUE obj, shape_id_t src_shape_id, st_t
// obj is TOO_COMPLEX so we can copy its iv_hash
st_table *table = st_copy(fields_table);
if (rb_shape_has_object_id(src_shape_id)) {
- st_data_t id = (st_data_t)ruby_internal_object_id;
+ st_data_t id = (st_data_t)id_object_id;
st_delete(table, &id, NULL);
}
rb_obj_init_too_complex(dest, table);
@@ -1184,6 +1177,31 @@ rb_shape_memsize(shape_id_t shape_id)
return memsize;
}
+bool
+rb_shape_foreach_field(shape_id_t initial_shape_id, rb_shape_foreach_transition_callback func, void *data)
+{
+ RUBY_ASSERT(!rb_shape_too_complex_p(initial_shape_id));
+
+ rb_shape_t *shape = RSHAPE(initial_shape_id);
+ if (shape->type == SHAPE_ROOT) {
+ return true;
+ }
+
+ shape_id_t parent_id = shape_id(RSHAPE(shape->parent_id), initial_shape_id);
+ if (rb_shape_foreach_field(parent_id, func, data)) {
+ switch (func(shape_id(shape, initial_shape_id), data)) {
+ case ST_STOP:
+ return false;
+ case ST_CHECK:
+ case ST_CONTINUE:
+ break;
+ default:
+ rb_bug("unreachable");
+ }
+ }
+ return true;
+}
+
#if RUBY_DEBUG
bool
rb_shape_verify_consistency(VALUE obj, shape_id_t shape_id)
@@ -1216,18 +1234,35 @@ rb_shape_verify_consistency(VALUE obj, shape_id_t shape_id)
}
}
+ // Make sure SHAPE_ID_HAS_IVAR_MASK is valid.
+ if (rb_shape_too_complex_p(shape_id)) {
+ RUBY_ASSERT(shape_id & SHAPE_ID_HAS_IVAR_MASK);
+ }
+ else {
+ attr_index_t ivar_count = RSHAPE_LEN(shape_id);
+ if (has_object_id) {
+ ivar_count--;
+ }
+ if (ivar_count) {
+ RUBY_ASSERT(shape_id & SHAPE_ID_HAS_IVAR_MASK);
+ }
+ else {
+ RUBY_ASSERT(!(shape_id & SHAPE_ID_HAS_IVAR_MASK));
+ }
+ }
+
uint8_t flags_heap_index = rb_shape_heap_index(shape_id);
if (RB_TYPE_P(obj, T_OBJECT)) {
- size_t shape_id_slot_size = GET_SHAPE_TREE()->capacities[flags_heap_index - 1] * sizeof(VALUE) + sizeof(struct RBasic);
+ size_t shape_id_slot_size = rb_shape_tree.capacities[flags_heap_index - 1] * sizeof(VALUE) + sizeof(struct RBasic);
size_t actual_slot_size = rb_gc_obj_slot_size(obj);
if (shape_id_slot_size != actual_slot_size) {
- rb_bug("shape_id heap_index flags mismatch: shape_id_slot_size=%lu, gc_slot_size=%lu\n", shape_id_slot_size, actual_slot_size);
+ rb_bug("shape_id heap_index flags mismatch: shape_id_slot_size=%zu, gc_slot_size=%zu\n", shape_id_slot_size, actual_slot_size);
}
}
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));
}
}
@@ -1368,7 +1403,7 @@ rb_shape_root_shape(VALUE self)
static VALUE
rb_shape_shapes_available(VALUE self)
{
- return INT2NUM(MAX_SHAPE_ID - (GET_SHAPE_TREE()->next_shape_id - 1));
+ return INT2NUM(MAX_SHAPE_ID - (rb_shape_tree.next_shape_id - 1));
}
static VALUE
@@ -1376,7 +1411,7 @@ rb_shape_exhaust(int argc, VALUE *argv, VALUE self)
{
rb_check_arity(argc, 0, 1);
int offset = argc == 1 ? NUM2INT(argv[0]) : 0;
- GET_SHAPE_TREE()->next_shape_id = MAX_SHAPE_ID - offset + 1;
+ rb_shape_tree.next_shape_id = MAX_SHAPE_ID - offset + 1;
return Qnil;
}
@@ -1391,12 +1426,14 @@ static enum rb_id_table_iterator_result collect_keys_and_values(ID key, VALUE va
static VALUE edges(VALUE edges)
{
VALUE hash = rb_hash_new();
- if (SINGLE_CHILD_P(edges)) {
- rb_shape_t *child = SINGLE_CHILD(edges);
- collect_keys_and_values(child->edge_name, (VALUE)child, &hash);
- }
- else {
- rb_managed_id_table_foreach(edges, collect_keys_and_values, &hash);
+ if (edges) {
+ if (SINGLE_CHILD_P(edges)) {
+ rb_shape_t *child = SINGLE_CHILD(edges);
+ collect_keys_and_values(child->edge_name, (VALUE)child, &hash);
+ }
+ else {
+ rb_managed_id_table_foreach(edges, collect_keys_and_values, &hash);
+ }
}
return hash;
}
@@ -1430,7 +1467,7 @@ static VALUE
rb_shape_find_by_id(VALUE mod, VALUE id)
{
shape_id_t shape_id = NUM2UINT(id);
- if (shape_id >= GET_SHAPE_TREE()->next_shape_id) {
+ if (shape_id >= rb_shape_tree.next_shape_id) {
rb_raise(rb_eArgError, "Shape ID %d is out of bounds\n", shape_id);
}
return shape_id_t_to_rb_cShape(shape_id);
@@ -1444,8 +1481,6 @@ rb_shape_find_by_id(VALUE mod, VALUE id)
void
Init_default_shapes(void)
{
- rb_shape_tree_ptr = xcalloc(1, sizeof(rb_shape_tree_t));
-
size_t *heap_sizes = rb_gc_heap_sizes();
size_t heaps_count = 0;
while (heap_sizes[heaps_count]) {
@@ -1457,45 +1492,43 @@ Init_default_shapes(void)
for (index = 0; index < heaps_count; index++) {
capacities[index] = (heap_sizes[index] - sizeof(struct RBasic)) / sizeof(VALUE);
}
- GET_SHAPE_TREE()->capacities = capacities;
+ rb_shape_tree.capacities = capacities;
#ifdef HAVE_MMAP
size_t shape_list_mmap_size = rb_size_mul_or_raise(SHAPE_BUFFER_SIZE, sizeof(rb_shape_t), rb_eRuntimeError);
- rb_shape_tree_ptr->shape_list = (rb_shape_t *)mmap(NULL, shape_list_mmap_size,
+ rb_shape_tree.shape_list = (rb_shape_t *)mmap(NULL, shape_list_mmap_size,
PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
- if (GET_SHAPE_TREE()->shape_list == MAP_FAILED) {
- GET_SHAPE_TREE()->shape_list = 0;
+ if (rb_shape_tree.shape_list == MAP_FAILED) {
+ rb_shape_tree.shape_list = 0;
}
else {
- ruby_annotate_mmap(rb_shape_tree_ptr->shape_list, shape_list_mmap_size, "Ruby:Init_default_shapes:shape_list");
+ ruby_annotate_mmap(rb_shape_tree.shape_list, shape_list_mmap_size, "Ruby:Init_default_shapes:shape_list");
}
#else
- GET_SHAPE_TREE()->shape_list = xcalloc(SHAPE_BUFFER_SIZE, sizeof(rb_shape_t));
+ rb_shape_tree.shape_list = xcalloc(SHAPE_BUFFER_SIZE, sizeof(rb_shape_t));
#endif
- if (!GET_SHAPE_TREE()->shape_list) {
+ if (!rb_shape_tree.shape_list) {
rb_memerror();
}
- id_frozen = rb_make_internal_id();
- id_t_object = rb_make_internal_id();
- ruby_internal_object_id = rb_make_internal_id();
+ id_object_id = rb_make_internal_id();
#ifdef HAVE_MMAP
size_t shape_cache_mmap_size = rb_size_mul_or_raise(REDBLACK_CACHE_SIZE, sizeof(redblack_node_t), rb_eRuntimeError);
- rb_shape_tree_ptr->shape_cache = (redblack_node_t *)mmap(NULL, shape_cache_mmap_size,
+ rb_shape_tree.shape_cache = (redblack_node_t *)mmap(NULL, shape_cache_mmap_size,
PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
- rb_shape_tree_ptr->cache_size = 0;
+ rb_shape_tree.cache_size = 0;
// If mmap fails, then give up on the redblack tree cache.
// We set the cache size such that the redblack node allocators think
// the cache is full.
- if (GET_SHAPE_TREE()->shape_cache == MAP_FAILED) {
- GET_SHAPE_TREE()->shape_cache = 0;
- GET_SHAPE_TREE()->cache_size = REDBLACK_CACHE_SIZE;
+ if (rb_shape_tree.shape_cache == MAP_FAILED) {
+ rb_shape_tree.shape_cache = 0;
+ rb_shape_tree.cache_size = REDBLACK_CACHE_SIZE;
}
else {
- ruby_annotate_mmap(rb_shape_tree_ptr->shape_cache, shape_cache_mmap_size, "Ruby:Init_default_shapes:shape_cache");
+ ruby_annotate_mmap(rb_shape_tree.shape_cache, shape_cache_mmap_size, "Ruby:Init_default_shapes:shape_cache");
}
#endif
@@ -1506,21 +1539,24 @@ Init_default_shapes(void)
rb_shape_t *root = rb_shape_alloc_with_parent_id(0, INVALID_SHAPE_ID);
root->capacity = 0;
root->type = SHAPE_ROOT;
- GET_SHAPE_TREE()->root_shape = root;
- RUBY_ASSERT(raw_shape_id(GET_SHAPE_TREE()->root_shape) == ROOT_SHAPE_ID);
+ rb_shape_tree.root_shape = root;
+ RUBY_ASSERT(raw_shape_id(rb_shape_tree.root_shape) == ROOT_SHAPE_ID);
+ RUBY_ASSERT(!(raw_shape_id(rb_shape_tree.root_shape) & SHAPE_ID_HAS_IVAR_MASK));
- rb_shape_t *root_with_obj_id = rb_shape_alloc_with_parent_id(0, ROOT_SHAPE_ID);
- root_with_obj_id->type = SHAPE_OBJ_ID;
- root_with_obj_id->edge_name = ruby_internal_object_id;
- root_with_obj_id->next_field_index++;
+ bool dontcare;
+ rb_shape_t *root_with_obj_id = get_next_shape_internal(root, id_object_id, SHAPE_OBJ_ID, &dontcare, true);
RUBY_ASSERT(raw_shape_id(root_with_obj_id) == ROOT_SHAPE_WITH_OBJ_ID);
+ RUBY_ASSERT(root_with_obj_id->type == SHAPE_OBJ_ID);
+ RUBY_ASSERT(root_with_obj_id->edge_name == id_object_id);
+ RUBY_ASSERT(root_with_obj_id->next_field_index == 1);
+ RUBY_ASSERT(!(raw_shape_id(root_with_obj_id) & SHAPE_ID_HAS_IVAR_MASK));
+ (void)root_with_obj_id;
}
void
rb_shape_free_all(void)
{
- xfree((void *)GET_SHAPE_TREE()->capacities);
- xfree(GET_SHAPE_TREE());
+ xfree((void *)rb_shape_tree.capacities);
}
void
diff --git a/shape.h b/shape.h
index 7fec93af9f..c6eb1981d0 100644
--- a/shape.h
+++ b/shape.h
@@ -19,10 +19,14 @@ STATIC_ASSERT(shape_id_num_bits, SHAPE_ID_NUM_BITS == sizeof(shape_id_t) * CHAR_
#define SHAPE_ID_FL_NON_CANONICAL_MASK (SHAPE_FL_NON_CANONICAL_MASK << SHAPE_ID_OFFSET_NUM_BITS)
#define SHAPE_ID_HEAP_INDEX_BITS 3
-#define SHAPE_ID_HEAP_INDEX_OFFSET (SHAPE_ID_NUM_BITS - SHAPE_ID_HEAP_INDEX_BITS - 1) // FIXME: -1 to avoid crashing YJIT
+#define SHAPE_ID_HEAP_INDEX_OFFSET (SHAPE_ID_NUM_BITS - SHAPE_ID_HEAP_INDEX_BITS)
#define SHAPE_ID_HEAP_INDEX_MAX ((1 << SHAPE_ID_HEAP_INDEX_BITS) - 1)
#define SHAPE_ID_HEAP_INDEX_MASK (SHAPE_ID_HEAP_INDEX_MAX << SHAPE_ID_HEAP_INDEX_OFFSET)
+// This masks allows to check if a shape_id contains any ivar.
+// It rely on ROOT_SHAPE_WITH_OBJ_ID==1.
+#define SHAPE_ID_HAS_IVAR_MASK (SHAPE_ID_FL_TOO_COMPLEX | (SHAPE_ID_OFFSET_MASK - 1))
+
// The interpreter doesn't care about frozen status or slot size when reading ivars.
// So we normalize shape_id by clearing these bits to improve cache hits.
// JITs however might care about it.
@@ -45,8 +49,6 @@ typedef uint32_t redblack_id_t;
#define ROOT_TOO_COMPLEX_WITH_OBJ_ID (ROOT_SHAPE_WITH_OBJ_ID | SHAPE_ID_FL_TOO_COMPLEX | SHAPE_ID_FL_HAS_OBJECT_ID)
#define SPECIAL_CONST_SHAPE_ID (ROOT_SHAPE_ID | SHAPE_ID_FL_FROZEN)
-extern ID ruby_internal_object_id;
-
typedef struct redblack_node redblack_node_t;
struct rb_shape {
@@ -92,7 +94,10 @@ typedef struct {
redblack_node_t *shape_cache;
unsigned int cache_size;
} rb_shape_tree_t;
-RUBY_EXTERN rb_shape_tree_t *rb_shape_tree_ptr;
+
+RUBY_SYMBOL_EXPORT_BEGIN
+RUBY_EXTERN rb_shape_tree_t rb_shape_tree;
+RUBY_SYMBOL_EXPORT_END
union rb_attr_index_cache {
uint64_t pack;
@@ -102,18 +107,11 @@ union rb_attr_index_cache {
} unpack;
};
-static inline rb_shape_tree_t *
-rb_current_shape_tree(void)
-{
- return rb_shape_tree_ptr;
-}
-#define GET_SHAPE_TREE() rb_current_shape_tree()
-
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));
+ 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
@@ -137,8 +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));
- RUBY_ASSERT(rb_shape_verify_consistency(obj, shape_id));
+ 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
@@ -146,18 +143,28 @@ RBASIC_SET_SHAPE_ID(VALUE obj, shape_id_t shape_id)
RBASIC(obj)->flags &= SHAPE_FLAG_MASK;
RBASIC(obj)->flags |= ((VALUE)(shape_id) << SHAPE_FLAG_SHIFT);
#endif
+ RUBY_ASSERT(rb_shape_verify_consistency(obj, shape_id));
}
-#define RSHAPE rb_shape_lookup
+static inline rb_shape_t *
+RSHAPE(shape_id_t shape_id)
+{
+ uint32_t offset = (shape_id & SHAPE_ID_OFFSET_MASK);
+ RUBY_ASSERT(offset != INVALID_SHAPE_ID);
+
+ return &rb_shape_tree.shape_list[offset];
+}
int32_t rb_shape_id_offset(void);
-RUBY_FUNC_EXPORTED rb_shape_t *rb_shape_lookup(shape_id_t shape_id);
RUBY_FUNC_EXPORTED shape_id_t rb_obj_shape_id(VALUE obj);
shape_id_t rb_shape_get_next_iv_shape(shape_id_t shape_id, ID id);
bool rb_shape_get_iv_index(shape_id_t shape_id, ID id, attr_index_t *value);
bool rb_shape_get_iv_index_with_hint(shape_id_t shape_id, ID id, attr_index_t *value, shape_id_t *shape_id_hint);
+typedef int rb_shape_foreach_transition_callback(shape_id_t shape_id, void *data);
+bool rb_shape_foreach_field(shape_id_t shape_id, rb_shape_foreach_transition_callback func, void *data);
+
shape_id_t rb_shape_transition_frozen(VALUE obj);
shape_id_t rb_shape_transition_complex(VALUE obj);
shape_id_t rb_shape_transition_remove_ivar(VALUE obj, ID id, shape_id_t *removed_shape_id);
@@ -211,10 +218,22 @@ rb_shape_root(size_t heap_id)
return ROOT_SHAPE_ID | ((heap_index + 1) << SHAPE_ID_HEAP_INDEX_OFFSET);
}
+static inline shape_id_t
+RSHAPE_PARENT(shape_id_t shape_id)
+{
+ return RSHAPE(shape_id)->parent_id;
+}
+
+static inline enum shape_type
+RSHAPE_TYPE(shape_id_t shape_id)
+{
+ return RSHAPE(shape_id)->type;
+}
+
static inline bool
RSHAPE_TYPE_P(shape_id_t shape_id, enum shape_type type)
{
- return RSHAPE(shape_id)->type == type;
+ return RSHAPE_TYPE(shape_id) == type;
}
static inline attr_index_t
@@ -222,7 +241,7 @@ RSHAPE_EMBEDDED_CAPACITY(shape_id_t shape_id)
{
uint8_t heap_index = rb_shape_heap_index(shape_id);
if (heap_index) {
- return GET_SHAPE_TREE()->capacities[heap_index - 1];
+ return rb_shape_tree.capacities[heap_index - 1];
}
return 0;
}
@@ -311,6 +330,46 @@ rb_shape_obj_has_id(VALUE obj)
return rb_shape_has_object_id(RBASIC_SHAPE_ID(obj));
}
+static inline bool
+rb_shape_has_ivars(shape_id_t shape_id)
+{
+ return shape_id & SHAPE_ID_HAS_IVAR_MASK;
+}
+
+static inline bool
+rb_shape_obj_has_ivars(VALUE obj)
+{
+ return rb_shape_has_ivars(RBASIC_SHAPE_ID(obj));
+}
+
+static inline bool
+rb_shape_has_fields(shape_id_t shape_id)
+{
+ return shape_id & (SHAPE_ID_OFFSET_MASK | SHAPE_ID_FL_TOO_COMPLEX);
+}
+
+static inline bool
+rb_shape_obj_has_fields(VALUE obj)
+{
+ return rb_shape_has_fields(RBASIC_SHAPE_ID(obj));
+}
+
+static inline bool
+rb_obj_exivar_p(VALUE obj)
+{
+ switch (TYPE(obj)) {
+ case T_NONE:
+ case T_OBJECT:
+ case T_CLASS:
+ case T_MODULE:
+ case T_IMEMO:
+ return false;
+ default:
+ break;
+ }
+ return rb_shape_obj_has_fields(obj);
+}
+
// For ext/objspace
RUBY_SYMBOL_EXPORT_BEGIN
typedef void each_shape_callback(shape_id_t shape_id, void *data);
diff --git a/spec/bin/rspec b/spec/bin/rspec
new file mode 100755
index 0000000000..1f61e3c64c
--- /dev/null
+++ b/spec/bin/rspec
@@ -0,0 +1,6 @@
+#!/usr/bin/env ruby
+# frozen_string_literal: true
+
+require_relative "../bundler/support/rubygems_ext"
+
+Spec::Rubygems.gem_load("rspec-core", "rspec")
diff --git a/spec/bundler/bundler/cli_spec.rb b/spec/bundler/bundler/cli_spec.rb
index bfafe83589..63803600aa 100644
--- a/spec/bundler/bundler/cli_spec.rb
+++ b/spec/bundler/bundler/cli_spec.rb
@@ -87,7 +87,7 @@ RSpec.describe "bundle executable" do
end
context "with no arguments" do
- it "prints a concise help message", bundler: "3" do
+ it "prints a concise help message", bundler: "4" do
bundle ""
expect(err).to be_empty
expect(out).to include("Bundler version #{Bundler::VERSION}").
@@ -250,12 +250,12 @@ To update to the most recent version, run `bundle update --bundler`
end
RSpec.describe "bundler executable" do
- it "shows the bundler version just as the `bundle` executable does", bundler: "< 3" do
+ it "shows the bundler version just as the `bundle` executable does", bundler: "2" do
bundler "--version"
expect(out).to eq("Bundler version #{Bundler::VERSION}")
end
- it "shows the bundler version just as the `bundle` executable does", bundler: "3" do
+ it "shows the bundler version just as the `bundle` executable does", bundler: "4" do
bundler "--version"
expect(out).to eq(Bundler::VERSION)
end
diff --git a/spec/bundler/bundler/current_ruby_spec.rb b/spec/bundler/bundler/current_ruby_spec.rb
index 61206d258b..9d6cb518cd 100644
--- a/spec/bundler/bundler/current_ruby_spec.rb
+++ b/spec/bundler/bundler/current_ruby_spec.rb
@@ -139,18 +139,18 @@ RSpec.describe Bundler::CurrentRuby do
end
describe "Deprecated platform" do
- it "Outputs a deprecation warning when calling maglev?", bundler: "< 3" do
+ it "Outputs a deprecation warning when calling maglev?", bundler: "2" do
expect(Bundler.ui).to receive(:warn).with(/`CurrentRuby#maglev\?` is deprecated with no replacement./)
Bundler.current_ruby.maglev?
end
- it "Outputs a deprecation warning when calling maglev_31?", bundler: "< 3" do
+ it "Outputs a deprecation warning when calling maglev_31?", bundler: "2" do
expect(Bundler.ui).to receive(:warn).with(/`CurrentRuby#maglev_31\?` is deprecated with no replacement./)
Bundler.current_ruby.maglev_31?
end
- pending "is removed and shows a helpful error message about it", bundler: "3"
+ pending "is removed and shows a helpful error message about it", bundler: "4"
end
end
diff --git a/spec/bundler/bundler/dsl_spec.rb b/spec/bundler/bundler/dsl_spec.rb
index 9dca4ade05..1fb46e6ba9 100644
--- a/spec/bundler/bundler/dsl_spec.rb
+++ b/spec/bundler/bundler/dsl_spec.rb
@@ -103,7 +103,7 @@ RSpec.describe Bundler::Dsl do
)
end
- context "default hosts", bundler: "< 3" do
+ context "default hosts", bundler: "2" do
it "converts :github to URI using https" do
subject.gem("sparks", github: "indirect/sparks")
github_uri = "https://github.com/indirect/sparks.git"
diff --git a/spec/bundler/bundler/friendly_errors_spec.rb b/spec/bundler/bundler/friendly_errors_spec.rb
index e0310344fd..d6a9d4813d 100644
--- a/spec/bundler/bundler/friendly_errors_spec.rb
+++ b/spec/bundler/bundler/friendly_errors_spec.rb
@@ -2,7 +2,8 @@
require "bundler"
require "bundler/friendly_errors"
-require "cgi"
+require "cgi/escape"
+require "cgi/util" unless defined?(CGI::EscapeExt)
RSpec.describe Bundler, "friendly errors" do
context "with invalid YAML in .gemrc" do
diff --git a/spec/bundler/cache/path_spec.rb b/spec/bundler/cache/path_spec.rb
index 966cb6f531..0d77ee85e6 100644
--- a/spec/bundler/cache/path_spec.rb
+++ b/spec/bundler/cache/path_spec.rb
@@ -97,7 +97,7 @@ RSpec.describe "bundle cache with path" do
expect(bundled_app("vendor/cache/foo-1.0")).not_to exist
end
- it "does not cache path gems by default", bundler: "< 3" do
+ it "does not cache path gems by default", bundler: "2" do
build_lib "foo"
install_gemfile <<-G
@@ -110,7 +110,7 @@ RSpec.describe "bundle cache with path" do
expect(bundled_app("vendor/cache/foo-1.0")).not_to exist
end
- it "caches path gems by default", bundler: "3" do
+ it "caches path gems by default", bundler: "4" do
build_lib "foo"
install_gemfile <<-G
diff --git a/spec/bundler/commands/binstubs_spec.rb b/spec/bundler/commands/binstubs_spec.rb
index c66b9339ee..44456f8dbf 100644
--- a/spec/bundler/commands/binstubs_spec.rb
+++ b/spec/bundler/commands/binstubs_spec.rb
@@ -168,7 +168,7 @@ RSpec.describe "bundle binstubs <gem>" do
expect(bundled_app("exec/myrackup")).to exist
end
- it "setting is saved for bundle install", bundler: "< 3" do
+ it "setting is saved for bundle install", bundler: "2" do
install_gemfile <<-G
source "https://gem.repo1"
gem "myrack"
diff --git a/spec/bundler/commands/cache_spec.rb b/spec/bundler/commands/cache_spec.rb
index 3f7a627296..50289ca65a 100644
--- a/spec/bundler/commands/cache_spec.rb
+++ b/spec/bundler/commands/cache_spec.rb
@@ -158,7 +158,7 @@ RSpec.describe "bundle cache" do
end
end
- context "with --path", bundler: "< 3" do
+ context "with --path", bundler: "2" do
it "sets root directory for gems" do
gemfile <<-D
source "https://gem.repo1"
@@ -221,7 +221,7 @@ RSpec.describe "bundle cache" do
expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).to exist
end
- it "puts the gems in vendor/cache even for legacy windows rubies, but prints a warning", bundler: "< 3" do
+ it "puts the gems in vendor/cache even for legacy windows rubies, but prints a warning", bundler: "2" do
gemfile <<-D
source "https://gem.repo1"
gem 'myrack', :platforms => [:ruby_20, :x64_mingw_20]
@@ -232,7 +232,7 @@ RSpec.describe "bundle cache" do
expect(bundled_app("vendor/cache/myrack-1.0.0.gem")).to exist
end
- it "prints an error when using legacy windows rubies", bundler: "3" do
+ it "prints an error when using legacy windows rubies", bundler: "4" do
gemfile <<-D
source "https://gem.repo1"
gem 'myrack', :platforms => [:ruby_20, :x64_mingw_20]
diff --git a/spec/bundler/commands/check_spec.rb b/spec/bundler/commands/check_spec.rb
index 150ee62878..8a68a44f0d 100644
--- a/spec/bundler/commands/check_spec.rb
+++ b/spec/bundler/commands/check_spec.rb
@@ -123,7 +123,7 @@ RSpec.describe "bundle check" do
expect(err).to include("Bundler can't satisfy your Gemfile's dependencies.")
end
- it "remembers --without option from install", bundler: "< 3" do
+ it "remembers --without option from install", bundler: "2" do
gemfile <<-G
source "https://gem.repo1"
group :foo do
@@ -272,7 +272,7 @@ RSpec.describe "bundle check" do
expect(last_command).to be_failure
end
- context "--path", bundler: "< 3" do
+ context "--path", bundler: "2" do
context "after installing gems in the proper directory" do
before do
gemfile <<-G
diff --git a/spec/bundler/commands/clean_spec.rb b/spec/bundler/commands/clean_spec.rb
index 2559be0205..176a125a48 100644
--- a/spec/bundler/commands/clean_spec.rb
+++ b/spec/bundler/commands/clean_spec.rb
@@ -383,7 +383,7 @@ RSpec.describe "bundle clean" do
expect(out).to include("myrack (1.0.0)").and include("thin (1.0)")
end
- it "--clean should override the bundle setting on install", bundler: "< 3" do
+ it "--clean should override the bundle setting on install", bundler: "2" do
gemfile <<-G
source "https://gem.repo1"
@@ -405,7 +405,7 @@ RSpec.describe "bundle clean" do
should_not_have_gems "thin-1.0"
end
- it "--clean should override the bundle setting on update", bundler: "< 3" do
+ it "--clean should override the bundle setting on update", bundler: "2" do
build_repo2
gemfile <<-G
@@ -427,7 +427,7 @@ RSpec.describe "bundle clean" do
should_not_have_gems "foo-1.0"
end
- it "automatically cleans when path has not been set", bundler: "3" do
+ it "automatically cleans when path has not been set", bundler: "4" do
build_repo2
install_gemfile <<-G
diff --git a/spec/bundler/commands/exec_spec.rb b/spec/bundler/commands/exec_spec.rb
index aa504ea2a7..a09f714bb6 100644
--- a/spec/bundler/commands/exec_spec.rb
+++ b/spec/bundler/commands/exec_spec.rb
@@ -699,6 +699,27 @@ RSpec.describe "bundle exec" do
end
end
+ describe "running gem commands in presence of rubygems plugins" do
+ before do
+ build_repo4 do
+ build_gem "foo" do |s|
+ s.write "lib/rubygems_plugin.rb", "puts 'FAIL'"
+ end
+ end
+
+ system_gems "foo-1.0", path: default_bundle_path, gem_repo: gem_repo4
+
+ install_gemfile <<-G
+ source "https://gem.repo4"
+ G
+ end
+
+ it "does not load plugins outside of the bundle" do
+ bundle "exec #{gem_cmd} -v"
+ expect(out).not_to include("FAIL")
+ end
+ end
+
context "`load`ing a ruby file instead of `exec`ing" do
let(:path) { bundled_app("ruby_executable") }
let(:shebang) { "#!/usr/bin/env ruby" }
diff --git a/spec/bundler/commands/inject_spec.rb b/spec/bundler/commands/inject_spec.rb
index 4998b6e89d..2630b8993b 100644
--- a/spec/bundler/commands/inject_spec.rb
+++ b/spec/bundler/commands/inject_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-RSpec.describe "bundle inject", bundler: "< 3" do
+RSpec.describe "bundle inject", bundler: "2" do
before :each do
gemfile <<-G
source "https://gem.repo1"
@@ -79,7 +79,7 @@ Usage: "bundle inject GEM VERSION"
context "when frozen" do
before do
bundle "install"
- if Bundler.feature_flag.bundler_3_mode?
+ if Bundler.feature_flag.bundler_4_mode?
bundle "config set --local deployment true"
else
bundle "config set --local frozen true"
diff --git a/spec/bundler/commands/install_spec.rb b/spec/bundler/commands/install_spec.rb
index 41aa903f27..df30a63c36 100644
--- a/spec/bundler/commands/install_spec.rb
+++ b/spec/bundler/commands/install_spec.rb
@@ -29,7 +29,7 @@ RSpec.describe "bundle install with gem sources" do
expect(bundled_app_lock).to exist
end
- it "does not create ./.bundle by default", bundler: "< 3" do
+ it "does not create ./.bundle by default", bundler: "2" do
gemfile <<-G
source "https://gem.repo1"
gem "myrack"
@@ -334,14 +334,14 @@ RSpec.describe "bundle install with gem sources" do
expect(the_bundle).to include_gems "myrack 1.0"
end
- it "allows running bundle install --system without deleting foo", bundler: "< 3" do
+ it "allows running bundle install --system without deleting foo", bundler: "2" do
bundle "install --path vendor"
bundle "install --system"
FileUtils.rm_r(bundled_app("vendor"))
expect(the_bundle).to include_gems "myrack 1.0"
end
- it "allows running bundle install --system after deleting foo", bundler: "< 3" do
+ it "allows running bundle install --system after deleting foo", bundler: "2" do
bundle "install --path vendor"
FileUtils.rm_r(bundled_app("vendor"))
bundle "install --system"
@@ -349,7 +349,7 @@ RSpec.describe "bundle install with gem sources" do
end
end
- it "finds gems in multiple sources", bundler: "< 3" do
+ it "finds gems in multiple sources", bundler: "2" do
build_repo2 do
build_gem "myrack", "1.2" do |s|
s.executables = "myrackup"
@@ -1500,6 +1500,55 @@ RSpec.describe "bundle install with gem sources" do
end
end
+ context "when lockfile has incorrect dependencies" do
+ before do
+ build_repo2
+
+ gemfile <<-G
+ source "https://gem.repo2"
+ gem "myrack_middleware"
+ G
+
+ system_gems "myrack_middleware-1.0", path: default_bundle_path
+
+ # we want to raise when the 1.0 line should be followed by " myrack (= 0.9.1)" but isn't
+ lockfile <<-L
+ GEM
+ remote: https://gem.repo2/
+ specs:
+ myrack_middleware (1.0)
+
+ PLATFORMS
+ #{lockfile_platforms}
+
+ DEPENDENCIES
+ myrack_middleware
+
+ BUNDLED WITH
+ #{Bundler::VERSION}
+ L
+ end
+
+ it "raises a clear error message when frozen" do
+ bundle "config set frozen true"
+ bundle "install", raise_on_error: false
+
+ expect(exitstatus).to eq(41)
+ expect(err).to eq("Bundler found incorrect dependencies in the lockfile for myrack_middleware-1.0")
+ end
+
+ it "updates the lockfile when not frozen" do
+ missing_dep = "myrack (0.9.1)"
+ expect(lockfile).not_to include(missing_dep)
+
+ bundle "config set frozen false"
+ bundle :install
+
+ expect(lockfile).to include(missing_dep)
+ expect(out).to include("now installed")
+ end
+ end
+
context "with --local flag" do
before do
system_gems "myrack-1.0.0", path: default_bundle_path
diff --git a/spec/bundler/commands/newgem_spec.rb b/spec/bundler/commands/newgem_spec.rb
index dd2aa5c8c4..608f05418f 100644
--- a/spec/bundler/commands/newgem_spec.rb
+++ b/spec/bundler/commands/newgem_spec.rb
@@ -162,7 +162,7 @@ RSpec.describe "bundle gem" do
end
shared_examples_for "--rubocop flag" do
- context "is deprecated", bundler: "< 3" do
+ context "is deprecated", bundler: "2" do
before do
global_config "BUNDLE_GEM__LINTER" => nil
bundle "gem #{gem_name} --rubocop"
@@ -198,7 +198,7 @@ RSpec.describe "bundle gem" do
end
shared_examples_for "--no-rubocop flag" do
- context "is deprecated", bundler: "< 3" do
+ context "is deprecated", bundler: "2" do
define_negated_matcher :exclude, :include
before do
@@ -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
@@ -1378,7 +1374,7 @@ RSpec.describe "bundle gem" do
end
end
- context "gem.rubocop setting set to true", bundler: "< 3" do
+ context "gem.rubocop setting set to true", bundler: "2" do
before do
global_config "BUNDLE_GEM__LINTER" => nil
bundle "config set gem.rubocop true"
@@ -1661,7 +1657,7 @@ RSpec.describe "bundle gem" do
include_examples "generating a gem"
context "--ext parameter with no value" do
- context "is deprecated", bundler: "< 3" do
+ context "is deprecated", bundler: "2" do
it "prints deprecation when used after gem name" do
bundle ["gem", "--ext", gem_name].compact.join(" ")
expect(err).to include "[DEPRECATED]"
@@ -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/commands/outdated_spec.rb b/spec/bundler/commands/outdated_spec.rb
index 09b0e6f32f..5c7b574f6d 100644
--- a/spec/bundler/commands/outdated_spec.rb
+++ b/spec/bundler/commands/outdated_spec.rb
@@ -151,7 +151,7 @@ RSpec.describe "bundle outdated" do
end
end
- describe "with multiple, duplicated sources, with lockfile in old format", bundler: "< 3" do
+ describe "with multiple, duplicated sources, with lockfile in old format", bundler: "2" do
before do
build_repo2 do
build_gem "dotenv", "2.7.6"
@@ -819,7 +819,7 @@ RSpec.describe "bundle outdated" do
expect(out).to include("Installing foo 1.0")
end
- context "after bundle install --deployment", bundler: "< 3" do
+ context "after bundle install --deployment", bundler: "2" do
before do
build_repo2
diff --git a/spec/bundler/commands/platform_spec.rb b/spec/bundler/commands/platform_spec.rb
index 17183e7546..293b7ffa95 100644
--- a/spec/bundler/commands/platform_spec.rb
+++ b/spec/bundler/commands/platform_spec.rb
@@ -646,7 +646,7 @@ G
expect(out).to eq(default_bundle_path("gems", "rails-2.3.2").to_s)
end
- it "fails if ruby version doesn't match", bundler: "< 3" do
+ it "fails if ruby version doesn't match", bundler: "2" do
gemfile <<-G
source "https://gem.repo1"
gem "rails"
@@ -658,7 +658,7 @@ G
should_be_ruby_version_incorrect
end
- it "fails if engine doesn't match", bundler: "< 3" do
+ it "fails if engine doesn't match", bundler: "2" do
gemfile <<-G
source "https://gem.repo1"
gem "rails"
@@ -670,7 +670,7 @@ G
should_be_engine_incorrect
end
- it "fails if engine version doesn't match", bundler: "< 3", jruby_only: true do
+ it "fails if engine version doesn't match", bundler: "2", jruby_only: true do
gemfile <<-G
source "https://gem.repo1"
gem "rails"
@@ -682,7 +682,7 @@ G
should_be_engine_version_incorrect
end
- it "fails when patchlevel doesn't match", bundler: "< 3" do
+ it "fails when patchlevel doesn't match", bundler: "2" do
gemfile <<-G
source "https://gem.repo1"
gem "myrack"
diff --git a/spec/bundler/commands/post_bundle_message_spec.rb b/spec/bundler/commands/post_bundle_message_spec.rb
index 7b5ac1aec9..0920b43f9b 100644
--- a/spec/bundler/commands/post_bundle_message_spec.rb
+++ b/spec/bundler/commands/post_bundle_message_spec.rb
@@ -142,7 +142,7 @@ Could not find gem 'not-a-gem' in rubygems repository https://gem.repo1/ or inst
end
end
- describe "for second bundle install run", bundler: "< 3" do
+ describe "for second bundle install run", bundler: "2" do
it "without any options" do
2.times { bundle :install }
expect(out).to include(bundle_show_message)
diff --git a/spec/bundler/commands/remove_spec.rb b/spec/bundler/commands/remove_spec.rb
index 84505169ca..e137e74503 100644
--- a/spec/bundler/commands/remove_spec.rb
+++ b/spec/bundler/commands/remove_spec.rb
@@ -43,7 +43,7 @@ RSpec.describe "bundle remove" do
end
end
- context "when --install flag is specified", bundler: "< 3" do
+ context "when --install flag is specified", bundler: "2" do
it "removes gems from .bundle" do
gemfile <<-G
source "https://gem.repo1"
diff --git a/spec/bundler/commands/show_spec.rb b/spec/bundler/commands/show_spec.rb
index 0ff9416757..33ba0a2c04 100644
--- a/spec/bundler/commands/show_spec.rb
+++ b/spec/bundler/commands/show_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-RSpec.describe "bundle show", bundler: "< 3" do
+RSpec.describe "bundle show", bundler: "2" do
context "with a standard Gemfile" do
before :each do
install_gemfile <<-G
@@ -219,6 +219,6 @@ RSpec.describe "bundle show", bundler: "< 3" do
end
end
-RSpec.describe "bundle show", bundler: "3" do
+RSpec.describe "bundle show", bundler: "4" do
pending "shows a friendly error about the command removal"
end
diff --git a/spec/bundler/commands/update_spec.rb b/spec/bundler/commands/update_spec.rb
index e3624ca04d..b9c3cd46f9 100644
--- a/spec/bundler/commands/update_spec.rb
+++ b/spec/bundler/commands/update_spec.rb
@@ -772,7 +772,7 @@ RSpec.describe "bundle update" do
G
end
- it "should fail loudly", bundler: "< 3" do
+ it "should fail loudly", bundler: "2" do
bundle "install --deployment"
bundle "update", all: true, raise_on_error: false
@@ -1036,7 +1036,7 @@ RSpec.describe "bundle update" do
end
end
- context "with multiple, duplicated sources, with lockfile in old format", bundler: "< 3" do
+ context "with multiple, duplicated sources, with lockfile in old format", bundler: "2" do
before do
build_repo2 do
build_gem "dotenv", "2.7.6"
@@ -1558,7 +1558,7 @@ RSpec.describe "bundle update --bundler" do
G
lockfile lockfile.sub(/(^\s*)#{Bundler::VERSION}($)/, "2.99.9")
- bundle :update, bundler: true, verbose: true, preserve_ruby_flags: true
+ bundle :update, bundler: true, verbose: true, preserve_ruby_flags: true, env: { "BUNDLER_4_MODE" => nil }
expect(out).to include("Updating bundler to 999.0.0")
expect(out).to include("Running `bundle update --bundler \"> 0.a\" --verbose` with bundler 999.0.0")
diff --git a/spec/bundler/commands/version_spec.rb b/spec/bundler/commands/version_spec.rb
index 307058a5dd..e62c0baf8b 100644
--- a/spec/bundler/commands/version_spec.rb
+++ b/spec/bundler/commands/version_spec.rb
@@ -10,36 +10,36 @@ RSpec.describe "bundle version" do
end
context "with -v" do
- it "outputs the version", bundler: "< 3" do
+ it "outputs the version", bundler: "2" do
bundle "-v"
expect(out).to eq("Bundler version #{Bundler::VERSION}")
end
- it "outputs the version", bundler: "3" do
+ it "outputs the version", bundler: "4" do
bundle "-v"
expect(out).to eq(Bundler::VERSION)
end
end
context "with --version" do
- it "outputs the version", bundler: "< 3" do
+ it "outputs the version", bundler: "2" do
bundle "--version"
expect(out).to eq("Bundler version #{Bundler::VERSION}")
end
- it "outputs the version", bundler: "3" do
+ it "outputs the version", bundler: "4" do
bundle "--version"
expect(out).to eq(Bundler::VERSION)
end
end
context "with version" do
- it "outputs the version with build metadata", bundler: "< 3" do
+ it "outputs the version with build metadata", bundler: "2" do
bundle "version"
expect(out).to match(/\ABundler version #{Regexp.escape(Bundler::VERSION)} \(\d{4}-\d{2}-\d{2} commit #{COMMIT_HASH}\)\z/)
end
- it "outputs the version with build metadata", bundler: "3" do
+ it "outputs the version with build metadata", bundler: "4" do
bundle "version"
expect(out).to match(/\A#{Regexp.escape(Bundler::VERSION)} \(\d{4}-\d{2}-\d{2} commit #{COMMIT_HASH}\)\z/)
end
diff --git a/spec/bundler/commands/viz_spec.rb b/spec/bundler/commands/viz_spec.rb
index 712ded4bc4..bc02d0465d 100644
--- a/spec/bundler/commands/viz_spec.rb
+++ b/spec/bundler/commands/viz_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-RSpec.describe "bundle viz", bundler: "< 3", if: Bundler.which("dot") do
+RSpec.describe "bundle viz", bundler: "2", if: Bundler.which("dot") do
before do
realworld_system_gems "ruby-graphviz --version 1.2.5"
end
diff --git a/spec/bundler/install/deploy_spec.rb b/spec/bundler/install/deploy_spec.rb
index 7db12c0d0b..6a507ba57b 100644
--- a/spec/bundler/install/deploy_spec.rb
+++ b/spec/bundler/install/deploy_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe "install in deployment or frozen mode" do
G
end
- context "with CLI flags", bundler: "< 3" do
+ context "with CLI flags", bundler: "2" do
it "fails without a lockfile and says that --deployment requires a lock" do
bundle "install --deployment", raise_on_error: false
expect(err).to include("The --deployment flag requires a lockfile")
diff --git a/spec/bundler/install/gemfile/gemspec_spec.rb b/spec/bundler/install/gemfile/gemspec_spec.rb
index 4d3eaa37ca..d9469e5ca8 100644
--- a/spec/bundler/install/gemfile/gemspec_spec.rb
+++ b/spec/bundler/install/gemfile/gemspec_spec.rb
@@ -260,6 +260,25 @@ RSpec.describe "bundle install from an existing gemspec" do
expect(out).to eq("WIN")
end
+ it "does not make Gem.try_activate warn when local gem has extensions" do
+ build_lib("foo", path: tmp("foo")) do |s|
+ s.version = "1.0.0"
+ s.add_c_extension
+ end
+ build_repo2
+
+ install_gemfile <<-G
+ source "https://gem.repo2"
+ gemspec :path => '#{tmp("foo")}'
+ G
+
+ expect(the_bundle).to include_gems "foo 1.0.0"
+
+ run "Gem.try_activate('irb/lc/es/error.rb'); puts 'WIN'"
+ expect(out).to eq("WIN")
+ expect(err).to be_empty
+ end
+
it "handles downgrades" do
build_lib "omg", "2.0", path: lib_path("omg")
diff --git a/spec/bundler/install/gemfile/git_spec.rb b/spec/bundler/install/gemfile/git_spec.rb
index 36751c46f2..5d6a0a648d 100644
--- a/spec/bundler/install/gemfile/git_spec.rb
+++ b/spec/bundler/install/gemfile/git_spec.rb
@@ -26,7 +26,7 @@ RSpec.describe "bundle install with git sources" do
expect(out).to eq("WIN")
end
- it "caches the git repo", bundler: "< 3" do
+ it "caches the git repo", bundler: "2" do
expect(Dir["#{default_bundle_path}/cache/bundler/git/foo-1.0-*"]).to have_attributes size: 1
end
diff --git a/spec/bundler/install/gemfile/groups_spec.rb b/spec/bundler/install/gemfile/groups_spec.rb
index f6f3edd01c..148b600088 100644
--- a/spec/bundler/install/gemfile/groups_spec.rb
+++ b/spec/bundler/install/gemfile/groups_spec.rb
@@ -100,7 +100,7 @@ RSpec.describe "bundle install with groups" do
expect(out).to include("Set for the current user (#{home(".bundle/config")}): [:emo]")
end
- it "allows running application where groups where configured by a different user", bundler: "< 3" do
+ it "allows running application where groups where configured by a different user", bundler: "2" do
bundle "config set without emo"
bundle :install
bundle "exec ruby -e 'puts 42'", env: { "BUNDLE_USER_HOME" => tmp("new_home").to_s }
@@ -113,7 +113,7 @@ RSpec.describe "bundle install with groups" do
expect(the_bundle).not_to include_gems "activesupport 2.3.5", groups: [:default]
end
- it "remembers previous exclusion with `--without`", bundler: "< 3" do
+ it "remembers previous exclusion with `--without`", bundler: "2" do
bundle "install --without emo"
expect(the_bundle).not_to include_gems "activesupport 2.3.5"
bundle :install
@@ -159,14 +159,14 @@ RSpec.describe "bundle install with groups" do
ENV["BUNDLE_WITHOUT"] = nil
end
- it "clears --without when passed an empty list", bundler: "< 3" do
+ it "clears --without when passed an empty list", bundler: "2" do
bundle "install --without emo"
bundle "install --without ''"
expect(the_bundle).to include_gems "activesupport 2.3.5"
end
- it "doesn't clear without when nothing is passed", bundler: "< 3" do
+ it "doesn't clear without when nothing is passed", bundler: "2" do
bundle "install --without emo"
bundle :install
@@ -184,7 +184,7 @@ RSpec.describe "bundle install with groups" do
expect(the_bundle).to include_gems "thin 1.0"
end
- it "installs gems from the previously requested group", bundler: "< 3" do
+ it "installs gems from the previously requested group", bundler: "2" do
bundle "install --with debugging"
expect(the_bundle).to include_gems "thin 1.0"
bundle :install
@@ -198,25 +198,25 @@ RSpec.describe "bundle install with groups" do
ENV["BUNDLE_WITH"] = nil
end
- it "clears --with when passed an empty list", bundler: "< 3" do
+ it "clears --with when passed an empty list", bundler: "2" do
bundle "install --with debugging"
bundle "install --with ''"
expect(the_bundle).not_to include_gems "thin 1.0"
end
- it "removes groups from without when passed at --with", bundler: "< 3" do
+ it "removes groups from without when passed at --with", bundler: "2" do
bundle "config set --local without emo"
bundle "install --with emo"
expect(the_bundle).to include_gems "activesupport 2.3.5"
end
- it "removes groups from with when passed at --without", bundler: "< 3" do
+ it "removes groups from with when passed at --without", bundler: "2" do
bundle "config set --local with debugging"
bundle "install --without debugging", raise_on_error: false
expect(the_bundle).not_to include_gem "thin 1.0"
end
- it "errors out when passing a group to with and without via CLI flags", bundler: "< 3" do
+ it "errors out when passing a group to with and without via CLI flags", bundler: "2" do
bundle "install --with emo debugging --without emo", raise_on_error: false
expect(last_command).to be_failure
expect(err).to include("The offending groups are: emo")
@@ -235,7 +235,7 @@ RSpec.describe "bundle install with groups" do
expect(the_bundle).to include_gem "thin 1.0"
end
- it "can add and remove a group at the same time", bundler: "< 3" do
+ it "can add and remove a group at the same time", bundler: "2" do
bundle "install --with debugging --without emo"
expect(the_bundle).to include_gems "thin 1.0"
expect(the_bundle).not_to include_gems "activesupport 2.3.5"
diff --git a/spec/bundler/install/gemfile/path_spec.rb b/spec/bundler/install/gemfile/path_spec.rb
index 7525404b24..669e63eb9c 100644
--- a/spec/bundler/install/gemfile/path_spec.rb
+++ b/spec/bundler/install/gemfile/path_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
RSpec.describe "bundle install with explicit source paths" do
- it "fetches gems with a global path source", bundler: "< 3" do
+ it "fetches gems with a global path source", bundler: "2" do
build_lib "foo"
install_gemfile <<-G
diff --git a/spec/bundler/install/gemfile/sources_spec.rb b/spec/bundler/install/gemfile/sources_spec.rb
index e1a5245800..e705a835d6 100644
--- a/spec/bundler/install/gemfile/sources_spec.rb
+++ b/spec/bundler/install/gemfile/sources_spec.rb
@@ -27,7 +27,7 @@ RSpec.describe "bundle install with gems on multiple sources" do
G
end
- it "refuses to install mismatched checksum because one gem has been tampered with", bundler: "< 3" do
+ it "refuses to install mismatched checksum because one gem has been tampered with", bundler: "2" do
lockfile <<~L
GEM
remote: https://gem.repo3/
@@ -71,7 +71,7 @@ RSpec.describe "bundle install with gems on multiple sources" do
bundle "config set --local disable_checksum_validation true"
end
- it "warns about ambiguous gems, but installs anyway, prioritizing sources last to first", bundler: "< 3" do
+ it "warns about ambiguous gems, but installs anyway, prioritizing sources last to first", bundler: "2" do
bundle :install, artifice: "compact_index"
expect(err).to include("Warning: the gem 'myrack' was found in multiple sources.")
@@ -79,7 +79,7 @@ RSpec.describe "bundle install with gems on multiple sources" do
expect(the_bundle).to include_gems("myrack-obama 1.0.0", "myrack 1.0.0", source: "remote1")
end
- it "does not use the full index unnecessarily", bundler: "< 3" do
+ it "does not use the full index unnecessarily", bundler: "2" do
bundle :install, artifice: "compact_index", verbose: true
expect(out).to include("https://gem.repo1/versions")
@@ -88,7 +88,7 @@ RSpec.describe "bundle install with gems on multiple sources" do
expect(out).not_to include("https://gem.repo3/quick/Marshal.4.8/")
end
- it "fails", bundler: "3" do
+ it "fails", bundler: "4" do
bundle :install, artifice: "compact_index", raise_on_error: false
expect(err).to include("Each source after the first must include a block")
expect(exitstatus).to eq(4)
@@ -108,14 +108,14 @@ RSpec.describe "bundle install with gems on multiple sources" do
G
end
- it "warns about ambiguous gems, but installs anyway", bundler: "< 3" do
+ it "warns about ambiguous gems, but installs anyway", bundler: "2" do
bundle :install, artifice: "compact_index"
expect(err).to include("Warning: the gem 'myrack' was found in multiple sources.")
expect(err).to include("Installed from: https://gem.repo1")
expect(the_bundle).to include_gems("myrack-obama 1.0.0", "myrack 1.0.0", source: "remote1")
end
- it "fails", bundler: "3" do
+ it "fails", bundler: "4" do
bundle :install, artifice: "compact_index", raise_on_error: false
expect(err).to include("Each source after the first must include a block")
expect(exitstatus).to eq(4)
@@ -145,7 +145,7 @@ RSpec.describe "bundle install with gems on multiple sources" do
G
end
- it "works in standalone mode", bundler: "< 3" do
+ it "works in standalone mode", bundler: "2" do
gem_checksum = checksum_digest(gem_repo4, "foo", "1.0")
bundle "install --standalone", artifice: "compact_index", env: { "BUNDLER_SPEC_FOO_CHECKSUM" => gem_checksum }
end
@@ -325,7 +325,7 @@ RSpec.describe "bundle install with gems on multiple sources" do
G
end
- it "fails when the two sources don't have the same checksum", bundler: "< 3" do
+ it "fails when the two sources don't have the same checksum", bundler: "2" do
bundle :install, artifice: "compact_index", raise_on_error: false
expect(err).to eq(<<~E.strip)
@@ -347,7 +347,7 @@ RSpec.describe "bundle install with gems on multiple sources" do
expect(exitstatus).to eq(37)
end
- it "fails when the two sources agree, but the local gem calculates a different checksum", bundler: "< 3" do
+ it "fails when the two sources agree, but the local gem calculates a different checksum", bundler: "2" do
myrack_checksum = "c0ffee11" * 8
bundle :install, artifice: "compact_index", env: { "BUNDLER_SPEC_MYRACK_CHECKSUM" => myrack_checksum }, raise_on_error: false
@@ -370,7 +370,7 @@ RSpec.describe "bundle install with gems on multiple sources" do
expect(exitstatus).to eq(37)
end
- it "installs from the other source and warns about ambiguous gems when the sources have the same checksum", bundler: "< 3" do
+ it "installs from the other source and warns about ambiguous gems when the sources have the same checksum", bundler: "2" do
gem_checksum = checksum_digest(gem_repo2, "myrack", "1.0.0")
bundle :install, artifice: "compact_index", env: { "BUNDLER_SPEC_MYRACK_CHECKSUM" => gem_checksum, "DEBUG" => "1" }
@@ -410,7 +410,7 @@ RSpec.describe "bundle install with gems on multiple sources" do
expect(lockfile).to eq(previous_lockfile)
end
- it "installs from the other source and warns about ambiguous gems when checksum validation is disabled", bundler: "< 3" do
+ it "installs from the other source and warns about ambiguous gems when checksum validation is disabled", bundler: "2" do
bundle "config set --local disable_checksum_validation true"
bundle :install, artifice: "compact_index"
@@ -450,7 +450,7 @@ RSpec.describe "bundle install with gems on multiple sources" do
expect(lockfile).to eq(previous_lockfile)
end
- it "fails", bundler: "3" do
+ it "fails", bundler: "4" do
bundle :install, artifice: "compact_index", raise_on_error: false
expect(err).to include("Each source after the first must include a block")
expect(exitstatus).to eq(4)
@@ -475,7 +475,7 @@ RSpec.describe "bundle install with gems on multiple sources" do
G
end
- it "installs the dependency from the pinned source without warning", bundler: "< 3" do
+ it "installs the dependency from the pinned source without warning", bundler: "2" do
bundle :install, artifice: "compact_index"
expect(err).not_to include("Warning: the gem 'myrack' was found in multiple sources.")
@@ -490,7 +490,7 @@ RSpec.describe "bundle install with gems on multiple sources" do
expect(the_bundle).to include_gems("depends_on_myrack 1.0.1", "myrack 1.0.0")
end
- it "fails", bundler: "3" do
+ it "fails", bundler: "4" do
bundle :install, artifice: "compact_index", raise_on_error: false
expect(err).to include("Each source after the first must include a block")
expect(exitstatus).to eq(4)
@@ -524,7 +524,7 @@ RSpec.describe "bundle install with gems on multiple sources" do
end
end
- context "when an indirect dependency can't be found in the aggregate rubygems source", bundler: "< 3" do
+ context "when an indirect dependency can't be found in the aggregate rubygems source", bundler: "2" do
before do
build_repo2
@@ -896,7 +896,7 @@ RSpec.describe "bundle install with gems on multiple sources" do
L
end
- it "does not install newer versions or generate lockfile changes when running bundle install in frozen mode, and warns", bundler: "< 3" do
+ it "does not install newer versions or generate lockfile changes when running bundle install in frozen mode, and warns", bundler: "2" do
initial_lockfile = lockfile
bundle "config set --local frozen true"
@@ -914,7 +914,7 @@ RSpec.describe "bundle install with gems on multiple sources" do
expect(lockfile).to eq(initial_lockfile)
end
- it "fails when running bundle install in frozen mode", bundler: "3" do
+ it "fails when running bundle install in frozen mode", bundler: "4" do
initial_lockfile = lockfile
bundle "config set --local frozen true"
@@ -1264,7 +1264,7 @@ RSpec.describe "bundle install with gems on multiple sources" do
lockfile aggregate_gem_section_lockfile
end
- it "installs the existing lockfile but prints a warning when checksum validation is disabled", bundler: "< 3" do
+ it "installs the existing lockfile but prints a warning when checksum validation is disabled", bundler: "2" do
bundle "config set --local deployment true"
bundle "config set --local disable_checksum_validation true"
@@ -1275,7 +1275,7 @@ RSpec.describe "bundle install with gems on multiple sources" do
expect(the_bundle).to include_gems("myrack 0.9.1", source: "remote3")
end
- it "prints a checksum warning when the checksums from both sources do not match", bundler: "< 3" do
+ it "prints a checksum warning when the checksums from both sources do not match", bundler: "2" do
bundle "config set --local deployment true"
bundle "install", artifice: "compact_index", raise_on_error: false
@@ -1302,7 +1302,7 @@ RSpec.describe "bundle install with gems on multiple sources" do
E
end
- it "refuses to install the existing lockfile and prints an error", bundler: "3" do
+ it "refuses to install the existing lockfile and prints an error", bundler: "4" do
bundle "config set --local deployment true"
bundle "install", artifice: "compact_index", raise_on_error: false
@@ -1583,7 +1583,7 @@ RSpec.describe "bundle install with gems on multiple sources" do
expect(err).to include("Could not reach host gem.repo4. Check your network connection and try again.")
end
- context "when an indirect dependency is available from multiple ambiguous sources", bundler: "< 3" do
+ context "when an indirect dependency is available from multiple ambiguous sources", bundler: "2" do
it "succeeds but warns, suggesting a source block" do
build_repo4 do
build_gem "depends_on_myrack" do |s|
@@ -1614,7 +1614,7 @@ RSpec.describe "bundle install with gems on multiple sources" do
end
end
- context "when an indirect dependency is available from multiple ambiguous sources", bundler: "3" do
+ context "when an indirect dependency is available from multiple ambiguous sources", bundler: "4" do
it "raises, suggesting a source block" do
build_repo4 do
build_gem "depends_on_myrack" do |s|
diff --git a/spec/bundler/install/gems/compact_index_spec.rb b/spec/bundler/install/gems/compact_index_spec.rb
index 736c998d79..b7de398c23 100644
--- a/spec/bundler/install/gems/compact_index_spec.rb
+++ b/spec/bundler/install/gems/compact_index_spec.rb
@@ -316,7 +316,7 @@ RSpec.describe "compact index api" do
expect(stdboth).not_to include "Double checking"
end
- it "fetches again when more dependencies are found in subsequent sources", bundler: "< 3" do
+ it "fetches again when more dependencies are found in subsequent sources", bundler: "2" do
build_repo2 do
build_gem "back_deps" do |s|
s.add_dependency "foo"
@@ -375,7 +375,7 @@ RSpec.describe "compact index api" do
expect(the_bundle).to include_gems "myrack 1.2"
end
- it "considers all possible versions of dependencies from all api gem sources", bundler: "< 3" do
+ it "considers all possible versions of dependencies from all api gem sources", bundler: "2" do
# In this scenario, the gem "somegem" only exists in repo4. It depends on specific version of activesupport that
# exists only in repo1. There happens also be a version of activesupport in repo4, but not the one that version 1.0.0
# of somegem wants. This test makes sure that bundler actually finds version 1.2.3 of active support in the other
@@ -471,7 +471,7 @@ RSpec.describe "compact index api" do
expect(the_bundle).to include_gems "foo 1.0"
end
- it "fetches again when more dependencies are found in subsequent sources using deployment mode", bundler: "< 3" do
+ it "fetches again when more dependencies are found in subsequent sources using deployment mode", bundler: "2" do
build_repo2 do
build_gem "back_deps" do |s|
s.add_dependency "foo"
@@ -529,7 +529,7 @@ RSpec.describe "compact index api" do
expect(out).to include("Fetching gem metadata from #{source_uri}")
end
- it "installs the binstubs", bundler: "< 3" do
+ it "installs the binstubs", bundler: "2" do
gemfile <<-G
source "#{source_uri}"
gem "myrack"
@@ -541,7 +541,7 @@ RSpec.describe "compact index api" do
expect(out).to eq("1.0.0")
end
- it "installs the bins when using --path and uses autoclean", bundler: "< 3" do
+ it "installs the bins when using --path and uses autoclean", bundler: "2" do
gemfile <<-G
source "#{source_uri}"
gem "myrack"
@@ -552,7 +552,7 @@ RSpec.describe "compact index api" do
expect(vendored_gems("bin/myrackup")).to exist
end
- it "installs the bins when using --path and uses bundle clean", bundler: "< 3" do
+ it "installs the bins when using --path and uses bundle clean", bundler: "2" do
gemfile <<-G
source "#{source_uri}"
gem "myrack"
@@ -617,7 +617,7 @@ RSpec.describe "compact index api" do
expect(the_bundle).to include_gems "myrack 1.0.0"
end
- it "strips http basic auth creds when warning about ambiguous sources", bundler: "< 3" do
+ it "strips http basic auth creds when warning about ambiguous sources", bundler: "2" do
gemfile <<-G
source "#{basic_auth_source_uri}"
source "#{file_uri_for(gem_repo1)}"
diff --git a/spec/bundler/install/gems/dependency_api_spec.rb b/spec/bundler/install/gems/dependency_api_spec.rb
index 283f1208f2..4ea67b7e31 100644
--- a/spec/bundler/install/gems/dependency_api_spec.rb
+++ b/spec/bundler/install/gems/dependency_api_spec.rb
@@ -254,7 +254,7 @@ RSpec.describe "gemcutter's dependency API" do
end
end
- it "fetches again when more dependencies are found in subsequent sources", bundler: "< 3" do
+ it "fetches again when more dependencies are found in subsequent sources", bundler: "2" do
build_repo2 do
build_gem "back_deps" do |s|
s.add_dependency "foo"
@@ -313,7 +313,7 @@ RSpec.describe "gemcutter's dependency API" do
expect(the_bundle).to include_gems "myrack 1.2"
end
- it "considers all possible versions of dependencies from all api gem sources", bundler: "< 3" do
+ it "considers all possible versions of dependencies from all api gem sources", bundler: "2" do
# In this scenario, the gem "somegem" only exists in repo4. It depends on specific version of activesupport that
# exists only in repo1. There happens also be a version of activesupport in repo4, but not the one that version 1.0.0
# of somegem wants. This test makes sure that bundler actually finds version 1.2.3 of active support in the other
@@ -358,7 +358,7 @@ RSpec.describe "gemcutter's dependency API" do
expect(out).to include("Fetching source index from http://localgemserver.test/extra")
end
- it "does not fetch every spec when doing back deps", bundler: "< 3" do
+ it "does not fetch every spec when doing back deps", bundler: "2" do
build_repo2 do
build_gem "back_deps" do |s|
s.add_dependency "foo"
@@ -397,7 +397,7 @@ RSpec.describe "gemcutter's dependency API" do
expect(the_bundle).to include_gems "back_deps 1.0"
end
- it "fetches again when more dependencies are found in subsequent sources using deployment mode", bundler: "< 3" do
+ it "fetches again when more dependencies are found in subsequent sources using deployment mode", bundler: "2" do
build_repo2 do
build_gem "back_deps" do |s|
s.add_dependency "foo"
@@ -471,7 +471,7 @@ RSpec.describe "gemcutter's dependency API" do
expect(out).to include("Fetching gem metadata from #{source_uri}")
end
- it "installs the binstubs", bundler: "< 3" do
+ it "installs the binstubs", bundler: "2" do
gemfile <<-G
source "#{source_uri}"
gem "myrack"
@@ -483,7 +483,7 @@ RSpec.describe "gemcutter's dependency API" do
expect(out).to eq("1.0.0")
end
- it "installs the bins when using --path and uses autoclean", bundler: "< 3" do
+ it "installs the bins when using --path and uses autoclean", bundler: "2" do
gemfile <<-G
source "#{source_uri}"
gem "myrack"
@@ -494,7 +494,7 @@ RSpec.describe "gemcutter's dependency API" do
expect(vendored_gems("bin/myrackup")).to exist
end
- it "installs the bins when using --path and uses bundle clean", bundler: "< 3" do
+ it "installs the bins when using --path and uses bundle clean", bundler: "2" do
gemfile <<-G
source "#{source_uri}"
gem "myrack"
@@ -580,7 +580,7 @@ RSpec.describe "gemcutter's dependency API" do
expect(out).not_to include("#{user}:#{password}")
end
- it "strips http basic auth creds when warning about ambiguous sources", bundler: "< 3" do
+ it "strips http basic auth creds when warning about ambiguous sources", bundler: "2" do
gemfile <<-G
source "#{basic_auth_source_uri}"
source "#{file_uri_for(gem_repo1)}"
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..fd8db16966 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
@@ -472,7 +470,7 @@ RSpec.shared_examples "bundle install --standalone" do
end
end
- describe "with --binstubs", bundler: "< 3" do
+ describe "with --binstubs", bundler: "2" do
before do
gemfile <<-G
source "https://gem.repo1"
diff --git a/spec/bundler/install/path_spec.rb b/spec/bundler/install/path_spec.rb
index 1412e8dd24..a51501c348 100644
--- a/spec/bundler/install/path_spec.rb
+++ b/spec/bundler/install/path_spec.rb
@@ -44,13 +44,13 @@ RSpec.describe "bundle install" do
expect(out).to include("gems are installed into `./vendor/bundle`")
end
- it "disallows --path vendor/bundle --system", bundler: "< 3" do
+ it "disallows --path vendor/bundle --system", bundler: "2" do
bundle "install --path vendor/bundle --system", raise_on_error: false
expect(err).to include("Please choose only one option.")
expect(exitstatus).to eq(15)
end
- it "remembers to disable system gems after the first time with bundle --path vendor/bundle", bundler: "< 3" do
+ it "remembers to disable system gems after the first time with bundle --path vendor/bundle", bundler: "2" do
bundle "install --path vendor/bundle"
FileUtils.rm_r bundled_app("vendor")
bundle "install"
@@ -62,7 +62,7 @@ RSpec.describe "bundle install" do
context "with path_relative_to_cwd set to true" do
before { bundle "config set path_relative_to_cwd true" }
- it "installs the bundle relatively to current working directory", bundler: "< 3" do
+ it "installs the bundle relatively to current working directory", bundler: "2" do
bundle "install --gemfile='#{bundled_app}/Gemfile' --path vendor/bundle", dir: bundled_app.parent
expect(out).to include("installed into `./vendor/bundle`")
expect(bundled_app("../vendor/bundle")).to be_directory
diff --git a/spec/bundler/install/redownload_spec.rb b/spec/bundler/install/redownload_spec.rb
index b522e22bd5..84f983375d 100644
--- a/spec/bundler/install/redownload_spec.rb
+++ b/spec/bundler/install/redownload_spec.rb
@@ -57,7 +57,7 @@ RSpec.describe "bundle install" do
end
end
- describe "with --force", bundler: 2 do
+ describe "with --force", bundler: "2" do
it_behaves_like "an option to force redownloading gems" do
let(:flag) { "force" }
end
diff --git a/spec/bundler/lock/lockfile_spec.rb b/spec/bundler/lock/lockfile_spec.rb
index 6e3232d3de..8e9ee7dc31 100644
--- a/spec/bundler/lock/lockfile_spec.rb
+++ b/spec/bundler/lock/lockfile_spec.rb
@@ -109,7 +109,7 @@ RSpec.describe "the lockfile format" do
#{version}
L
- install_gemfile <<-G, verbose: true, preserve_ruby_flags: true
+ install_gemfile <<-G, verbose: true, preserve_ruby_flags: true, env: { "BUNDLER_4_MODE" => nil }
source "https://gem.repo4"
gem "myrack"
diff --git a/spec/bundler/other/major_deprecation_spec.rb b/spec/bundler/other/major_deprecation_spec.rb
index 036c855c4e..1d8c7bad80 100644
--- a/spec/bundler/other/major_deprecation_spec.rb
+++ b/spec/bundler/other/major_deprecation_spec.rb
@@ -17,14 +17,14 @@ RSpec.describe "major deprecations" do
bundle "exec ruby -e #{source.dump}"
end
- it "is deprecated in favor of .unbundled_env", bundler: "< 3" do
+ it "is deprecated in favor of .unbundled_env", bundler: "2" do
expect(deprecations).to include \
"`Bundler.clean_env` has been deprecated in favor of `Bundler.unbundled_env`. " \
"If you instead want the environment before bundler was originally loaded, use `Bundler.original_env` " \
"(called at -e:1)"
end
- pending "is removed and shows a helpful error message about it", bundler: "3"
+ pending "is removed and shows a helpful error message about it", bundler: "4"
end
describe ".with_clean_env" do
@@ -33,7 +33,7 @@ RSpec.describe "major deprecations" do
bundle "exec ruby -e #{source.dump}"
end
- it "is deprecated in favor of .unbundled_env", bundler: "< 3" do
+ it "is deprecated in favor of .unbundled_env", bundler: "2" do
expect(deprecations).to include(
"`Bundler.with_clean_env` has been deprecated in favor of `Bundler.with_unbundled_env`. " \
"If you instead want the environment before bundler was originally loaded, use `Bundler.with_original_env` " \
@@ -41,7 +41,7 @@ RSpec.describe "major deprecations" do
)
end
- pending "is removed and shows a helpful error message about it", bundler: "3"
+ pending "is removed and shows a helpful error message about it", bundler: "4"
end
describe ".clean_system" do
@@ -50,7 +50,7 @@ RSpec.describe "major deprecations" do
bundle "exec ruby -e #{source.dump}"
end
- it "is deprecated in favor of .unbundled_system", bundler: "< 3" do
+ it "is deprecated in favor of .unbundled_system", bundler: "2" do
expect(deprecations).to include(
"`Bundler.clean_system` has been deprecated in favor of `Bundler.unbundled_system`. " \
"If you instead want to run the command in the environment before bundler was originally loaded, use `Bundler.original_system` " \
@@ -58,7 +58,7 @@ RSpec.describe "major deprecations" do
)
end
- pending "is removed and shows a helpful error message about it", bundler: "3"
+ pending "is removed and shows a helpful error message about it", bundler: "4"
end
describe ".clean_exec" do
@@ -67,7 +67,7 @@ RSpec.describe "major deprecations" do
bundle "exec ruby -e #{source.dump}"
end
- it "is deprecated in favor of .unbundled_exec", bundler: "< 3" do
+ it "is deprecated in favor of .unbundled_exec", bundler: "2" do
expect(deprecations).to include(
"`Bundler.clean_exec` has been deprecated in favor of `Bundler.unbundled_exec`. " \
"If you instead want to exec to a command in the environment before bundler was originally loaded, use `Bundler.original_exec` " \
@@ -75,7 +75,7 @@ RSpec.describe "major deprecations" do
)
end
- pending "is removed and shows a helpful error message about it", bundler: "3"
+ pending "is removed and shows a helpful error message about it", bundler: "4"
end
describe ".environment" do
@@ -84,11 +84,11 @@ RSpec.describe "major deprecations" do
bundle "exec ruby -e #{source.dump}"
end
- it "is deprecated in favor of .load", bundler: "< 3" do
+ it "is deprecated in favor of .load", bundler: "2" do
expect(deprecations).to include "Bundler.environment has been removed in favor of Bundler.load (called at -e:1)"
end
- pending "is removed and shows a helpful error message about it", bundler: "3"
+ pending "is removed and shows a helpful error message about it", bundler: "4"
end
end
@@ -97,11 +97,11 @@ RSpec.describe "major deprecations" do
bundle "exec --no-keep-file-descriptors -e 1", raise_on_error: false
end
- it "is deprecated", bundler: "< 3" do
+ it "is deprecated", bundler: "2" do
expect(deprecations).to include "The `--no-keep-file-descriptors` has been deprecated. `bundle exec` no longer mess with your file descriptors. Close them in the exec'd script if you need to"
end
- pending "is removed and shows a helpful error message about it", bundler: "3"
+ pending "is removed and shows a helpful error message about it", bundler: "4"
end
describe "bundle update --quiet" do
@@ -121,7 +121,7 @@ RSpec.describe "major deprecations" do
bundle "check --path vendor/bundle", raise_on_error: false
end
- it "should print a deprecation warning", bundler: "< 3" do
+ it "should print a deprecation warning", bundler: "2" do
expect(deprecations).to include(
"The `--path` flag is deprecated because it relies on being " \
"remembered across bundler invocations, which bundler will no " \
@@ -130,7 +130,7 @@ RSpec.describe "major deprecations" do
)
end
- pending "fails with a helpful error", bundler: "3"
+ pending "fails with a helpful error", bundler: "4"
end
context "bundle check --path=" do
@@ -143,7 +143,7 @@ RSpec.describe "major deprecations" do
bundle "check --path=vendor/bundle", raise_on_error: false
end
- it "should print a deprecation warning", bundler: "< 3" do
+ it "should print a deprecation warning", bundler: "2" do
expect(deprecations).to include(
"The `--path` flag is deprecated because it relies on being " \
"remembered across bundler invocations, which bundler will no " \
@@ -152,7 +152,7 @@ RSpec.describe "major deprecations" do
)
end
- pending "fails with a helpful error", bundler: "3"
+ pending "fails with a helpful error", bundler: "4"
end
context "bundle cache --all" do
@@ -165,7 +165,7 @@ RSpec.describe "major deprecations" do
bundle "cache --all", raise_on_error: false
end
- it "should print a deprecation warning", bundler: "< 3" do
+ it "should print a deprecation warning", bundler: "2" do
expect(deprecations).to include(
"The `--all` flag is deprecated because it relies on being " \
"remembered across bundler invocations, which bundler will no " \
@@ -174,7 +174,7 @@ RSpec.describe "major deprecations" do
)
end
- pending "fails with a helpful error", bundler: "3"
+ pending "fails with a helpful error", bundler: "4"
end
context "bundle cache --path" do
@@ -187,7 +187,7 @@ RSpec.describe "major deprecations" do
bundle "cache --path foo", raise_on_error: false
end
- it "should print a deprecation warning", bundler: "< 3" do
+ it "should print a deprecation warning", bundler: "2" do
expect(deprecations).to include(
"The `--path` flag is deprecated because its semantics are unclear. " \
"Use `bundle config cache_path` to configure the path of your cache of gems, " \
@@ -196,7 +196,7 @@ RSpec.describe "major deprecations" do
)
end
- pending "fails with a helpful error", bundler: "3"
+ pending "fails with a helpful error", bundler: "4"
end
describe "bundle config" do
@@ -205,11 +205,11 @@ RSpec.describe "major deprecations" do
bundle "config"
end
- it "warns", bundler: "3" do
+ it "warns", bundler: "4" do
expect(deprecations).to include("Using the `config` command without a subcommand [list, get, set, unset] is deprecated and will be removed in the future. Use `bundle config list` instead.")
end
- pending "fails with a helpful error", bundler: "3"
+ pending "fails with a helpful error", bundler: "4"
end
describe "old get interface" do
@@ -217,11 +217,11 @@ RSpec.describe "major deprecations" do
bundle "config waka"
end
- it "warns", bundler: "3" do
+ it "warns", bundler: "4" do
expect(deprecations).to include("Using the `config` command without a subcommand [list, get, set, unset] is deprecated and will be removed in the future. Use `bundle config get waka` instead.")
end
- pending "fails with a helpful error", bundler: "3"
+ pending "fails with a helpful error", bundler: "4"
end
describe "old set interface" do
@@ -229,11 +229,11 @@ RSpec.describe "major deprecations" do
bundle "config waka wakapun"
end
- it "warns", bundler: "3" do
+ it "warns", bundler: "4" do
expect(deprecations).to include("Using the `config` command without a subcommand [list, get, set, unset] is deprecated and will be removed in the future. Use `bundle config set waka wakapun` instead.")
end
- pending "fails with a helpful error", bundler: "3"
+ pending "fails with a helpful error", bundler: "4"
end
describe "old set interface with --local" do
@@ -241,11 +241,11 @@ RSpec.describe "major deprecations" do
bundle "config --local waka wakapun"
end
- it "warns", bundler: "3" do
+ it "warns", bundler: "4" do
expect(deprecations).to include("Using the `config` command without a subcommand [list, get, set, unset] is deprecated and will be removed in the future. Use `bundle config set --local waka wakapun` instead.")
end
- pending "fails with a helpful error", bundler: "3"
+ pending "fails with a helpful error", bundler: "4"
end
describe "old set interface with --global" do
@@ -253,11 +253,11 @@ RSpec.describe "major deprecations" do
bundle "config --global waka wakapun"
end
- it "warns", bundler: "3" do
+ it "warns", bundler: "4" do
expect(deprecations).to include("Using the `config` command without a subcommand [list, get, set, unset] is deprecated and will be removed in the future. Use `bundle config set --global waka wakapun` instead.")
end
- pending "fails with a helpful error", bundler: "3"
+ pending "fails with a helpful error", bundler: "4"
end
describe "old unset interface" do
@@ -265,11 +265,11 @@ RSpec.describe "major deprecations" do
bundle "config --delete waka"
end
- it "warns", bundler: "3" do
+ it "warns", bundler: "4" do
expect(deprecations).to include("Using the `config` command without a subcommand [list, get, set, unset] is deprecated and will be removed in the future. Use `bundle config unset waka` instead.")
end
- pending "fails with a helpful error", bundler: "3"
+ pending "fails with a helpful error", bundler: "4"
end
describe "old unset interface with --local" do
@@ -277,11 +277,11 @@ RSpec.describe "major deprecations" do
bundle "config --delete --local waka"
end
- it "warns", bundler: "3" do
+ it "warns", bundler: "4" do
expect(deprecations).to include("Using the `config` command without a subcommand [list, get, set, unset] is deprecated and will be removed in the future. Use `bundle config unset --local waka` instead.")
end
- pending "fails with a helpful error", bundler: "3"
+ pending "fails with a helpful error", bundler: "4"
end
describe "old unset interface with --global" do
@@ -289,11 +289,11 @@ RSpec.describe "major deprecations" do
bundle "config --delete --global waka"
end
- it "warns", bundler: "3" do
+ it "warns", bundler: "4" do
expect(deprecations).to include("Using the `config` command without a subcommand [list, get, set, unset] is deprecated and will be removed in the future. Use `bundle config unset --global waka` instead.")
end
- pending "fails with a helpful error", bundler: "3"
+ pending "fails with a helpful error", bundler: "4"
end
end
@@ -305,12 +305,12 @@ RSpec.describe "major deprecations" do
G
end
- it "warns when no options are given", bundler: "3" do
+ it "warns when no options are given", bundler: "4" do
bundle "update"
expect(deprecations).to include("Pass --all to `bundle update` to update everything")
end
- pending "fails with a helpful error when no options are given", bundler: "3"
+ pending "fails with a helpful error when no options are given", bundler: "4"
it "does not warn when --all is passed" do
bundle "update --all"
@@ -326,11 +326,11 @@ RSpec.describe "major deprecations" do
G
end
- it "should output a deprecation warning", bundler: "< 3" do
+ it "should output a deprecation warning", bundler: "2" do
expect(deprecations).to include("The --binstubs option will be removed in favor of `bundle binstubs --all`")
end
- pending "fails with a helpful error", bundler: "3"
+ pending "fails with a helpful error", bundler: "4"
end
context "bundle install with both gems.rb and Gemfile present" do
@@ -390,7 +390,7 @@ RSpec.describe "major deprecations" do
bundle "install #{flag_name} #{value}"
end
- it "should print a deprecation warning", bundler: "< 3" do
+ it "should print a deprecation warning", bundler: "2" do
expect(deprecations).to include(
"The `#{flag_name}` flag is deprecated because it relies on " \
"being remembered across bundler invocations, which bundler " \
@@ -399,7 +399,7 @@ RSpec.describe "major deprecations" do
)
end
- pending "fails with a helpful error", bundler: "3"
+ pending "fails with a helpful error", bundler: "4"
end
end
end
@@ -412,7 +412,7 @@ RSpec.describe "major deprecations" do
G
end
- it "shows a deprecation", bundler: "< 3" do
+ it "shows a deprecation", bundler: "2" do
expect(deprecations).to include(
"Your Gemfile contains multiple global sources. " \
"Using `source` more than once without a block is a security risk, and " \
@@ -421,7 +421,7 @@ RSpec.describe "major deprecations" do
)
end
- it "doesn't show lockfile deprecations if there's a lockfile", bundler: "< 3" do
+ it "doesn't show lockfile deprecations if there's a lockfile", bundler: "2" do
bundle "install"
expect(deprecations).to include(
@@ -449,7 +449,7 @@ RSpec.describe "major deprecations" do
)
end
- pending "fails with a helpful error", bundler: "3"
+ pending "fails with a helpful error", bundler: "4"
end
context "bundle install in frozen mode with a lockfile with a single rubygems section with multiple remotes" do
@@ -485,13 +485,13 @@ RSpec.describe "major deprecations" do
bundle "config set --local frozen true"
end
- it "shows a deprecation", bundler: "< 3" do
+ it "shows a deprecation", bundler: "2" do
bundle "install"
expect(deprecations).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure. Make sure you run `bundle install` in non frozen mode and commit the result to make your lockfile secure.")
end
- pending "fails with a helpful error", bundler: "3"
+ pending "fails with a helpful error", bundler: "4"
end
context "when Bundler.setup is run in a ruby script" do
@@ -524,14 +524,14 @@ RSpec.describe "major deprecations" do
RUBY
end
- it "should print a capistrano deprecation warning", bundler: "< 3" do
+ it "should print a capistrano deprecation warning", bundler: "2" do
expect(deprecations).to include("Bundler no longer integrates " \
"with Capistrano, but Capistrano provides " \
"its own integration with Bundler via the " \
"capistrano-bundler gem. Use it instead.")
end
- pending "fails with a helpful error", bundler: "3"
+ pending "fails with a helpful error", bundler: "4"
end
context "bundle show" do
@@ -547,11 +547,11 @@ RSpec.describe "major deprecations" do
bundle "show --outdated"
end
- it "prints a deprecation warning informing about its removal", bundler: "< 3" do
+ it "prints a deprecation warning informing about its removal", bundler: "2" do
expect(deprecations).to include("the `--outdated` flag to `bundle show` was undocumented and will be removed without replacement")
end
- pending "fails with a helpful message", bundler: "3"
+ pending "fails with a helpful message", bundler: "4"
end
end
@@ -564,13 +564,13 @@ RSpec.describe "major deprecations" do
end
context "with --install" do
- it "shows a deprecation warning", bundler: "< 3" do
+ it "shows a deprecation warning", bundler: "2" do
bundle "remove myrack --install"
expect(err).to include "[DEPRECATED] The `--install` flag has been deprecated. `bundle install` is triggered by default."
end
- pending "fails with a helpful message", bundler: "3"
+ pending "fails with a helpful message", bundler: "4"
end
end
@@ -581,11 +581,11 @@ RSpec.describe "major deprecations" do
bundle "viz"
end
- it "prints a deprecation warning", bundler: "< 3" do
+ it "prints a deprecation warning", bundler: "2" do
expect(deprecations).to include "The `viz` command has been renamed to `graph` and moved to a plugin. See https://github.com/rubygems/bundler-graph"
end
- pending "fails with a helpful message", bundler: "3"
+ pending "fails with a helpful message", bundler: "4"
end
context "bundle plugin install --local_git" do
@@ -595,14 +595,14 @@ RSpec.describe "major deprecations" do
end
end
- it "prints a deprecation warning", bundler: "< 3" do
+ it "prints a deprecation warning", bundler: "2" do
bundle "plugin install foo --local_git #{lib_path("foo-1.0")}"
expect(out).to include("Installed plugin foo")
expect(deprecations).to include "--local_git is deprecated, use --git"
end
- pending "fails with a helpful message", bundler: "3"
+ pending "fails with a helpful message", bundler: "4"
end
describe "deprecating rubocop" do
@@ -616,7 +616,7 @@ RSpec.describe "major deprecations" do
bundle "gem my_new_gem --rubocop", raise_on_error: false
end
- it "prints a deprecation warning", bundler: "< 3" do
+ it "prints a deprecation warning", bundler: "2" do
expect(deprecations).to include \
"--rubocop is deprecated, use --linter=rubocop"
end
@@ -627,7 +627,7 @@ RSpec.describe "major deprecations" do
bundle "gem my_new_gem --no-rubocop", raise_on_error: false
end
- it "prints a deprecation warning", bundler: "< 3" do
+ it "prints a deprecation warning", bundler: "2" do
expect(deprecations).to include \
"--no-rubocop is deprecated, use --linter"
end
@@ -638,7 +638,7 @@ RSpec.describe "major deprecations" do
bundle "gem my_new_gem", env: { "BUNDLE_GEM__RUBOCOP" => "true" }, raise_on_error: false
end
- it "prints a deprecation warning", bundler: "< 3" do
+ it "prints a deprecation warning", bundler: "2" do
expect(deprecations).to include \
"config gem.rubocop is deprecated; we've updated your config to use gem.linter instead"
end
@@ -649,7 +649,7 @@ RSpec.describe "major deprecations" do
bundle "gem my_new_gem", env: { "BUNDLE_GEM__RUBOCOP" => "false" }, raise_on_error: false
end
- it "prints a deprecation warning", bundler: "< 3" do
+ it "prints a deprecation warning", bundler: "2" do
expect(deprecations).to include \
"config gem.rubocop is deprecated; we've updated your config to use gem.linter instead"
end
diff --git a/spec/bundler/plugins/install_spec.rb b/spec/bundler/plugins/install_spec.rb
index d0de607e6c..6464ce94b4 100644
--- a/spec/bundler/plugins/install_spec.rb
+++ b/spec/bundler/plugins/install_spec.rb
@@ -204,7 +204,7 @@ RSpec.describe "bundler plugin install" do
plugin_should_be_installed("foo")
end
- it "raises an error when both git and local git sources are specified", bundler: "< 3" do
+ it "raises an error when both git and local git sources are specified", bundler: "2" do
bundle "plugin install foo --git /phony/path/project --local_git [email protected]:/repo/project", raise_on_error: false
expect(exitstatus).not_to eq(0)
diff --git a/spec/bundler/realworld/slow_perf_spec.rb b/spec/bundler/realworld/slow_perf_spec.rb
index 32e266ff1b..d9d1aef81c 100644
--- a/spec/bundler/realworld/slow_perf_spec.rb
+++ b/spec/bundler/realworld/slow_perf_spec.rb
@@ -131,7 +131,7 @@ RSpec.describe "bundle install with complex dependencies", realworld: true do
end
G
- if Bundler.feature_flag.bundler_3_mode?
+ if Bundler.feature_flag.bundler_4_mode?
bundle "lock", env: { "DEBUG_RESOLVER" => "1" }, raise_on_error: false
expect(out).to include("backtracking").exactly(26).times
diff --git a/spec/bundler/runtime/env_helpers_spec.rb b/spec/bundler/runtime/env_helpers_spec.rb
index ce74ba8c19..9280a43334 100644
--- a/spec/bundler/runtime/env_helpers_spec.rb
+++ b/spec/bundler/runtime/env_helpers_spec.rb
@@ -139,7 +139,7 @@ RSpec.describe "env helpers" do
it_behaves_like "an unbundling helper"
end
- describe "Bundler.clean_env", bundler: 2 do
+ describe "Bundler.clean_env", bundler: "2" do
let(:modified_env) { "Bundler.clean_env" }
it_behaves_like "an unbundling helper"
@@ -161,7 +161,7 @@ RSpec.describe "env helpers" do
end
end
- describe "Bundler.with_clean_env", bundler: 2 do
+ describe "Bundler.with_clean_env", bundler: "2" do
it "should set ENV to unbundled_env in the block" do
expected = Bundler.unbundled_env
@@ -212,7 +212,7 @@ RSpec.describe "env helpers" do
end
end
- describe "Bundler.clean_system", bundler: 2 do
+ describe "Bundler.clean_system", bundler: "2" do
before do
create_file("source.rb", <<-'RUBY')
Bundler.ui.silence { Bundler.clean_system("ruby", "-e", "exit(42) unless ENV['BUNDLE_FOO'] == 'bar'") }
@@ -263,7 +263,7 @@ RSpec.describe "env helpers" do
end
end
- describe "Bundler.clean_exec", bundler: 2 do
+ describe "Bundler.clean_exec", bundler: "2" do
before do
create_file("source.rb", <<-'RUBY')
Process.fork do
diff --git a/spec/bundler/runtime/executable_spec.rb b/spec/bundler/runtime/executable_spec.rb
index a4226ed51e..6f7bb524f2 100644
--- a/spec/bundler/runtime/executable_spec.rb
+++ b/spec/bundler/runtime/executable_spec.rb
@@ -96,7 +96,7 @@ RSpec.describe "Running bin/* commands" do
expect(bundled_app("bin/myrackup")).not_to exist
end
- it "allows you to stop installing binstubs", bundler: "< 3" do
+ it "allows you to stop installing binstubs", bundler: "2" do
skip "delete permission error" if Gem.win_platform?
bundle "install --binstubs bin/"
@@ -109,7 +109,7 @@ RSpec.describe "Running bin/* commands" do
expect(out).to include("You have not configured a value for `bin`")
end
- it "remembers that the option was specified", bundler: "< 3" do
+ it "remembers that the option was specified", bundler: "2" do
gemfile <<-G
source "https://gem.repo1"
gem "activesupport"
diff --git a/spec/bundler/runtime/self_management_spec.rb b/spec/bundler/runtime/self_management_spec.rb
index a481ae3a4d..4b2ac2afc3 100644
--- a/spec/bundler/runtime/self_management_spec.rb
+++ b/spec/bundler/runtime/self_management_spec.rb
@@ -10,7 +10,7 @@ RSpec.describe "Self management" do
"9.4.0"
end
- before do
+ around do |example|
build_repo4 do
build_bundler previous_minor
@@ -26,6 +26,8 @@ RSpec.describe "Self management" do
G
pristine_system_gems "bundler-#{current_version}"
+
+ with_env_vars("BUNDLER_4_MODE" => nil, &example)
end
it "installs locked version when using system path and uses it" do
diff --git a/spec/bundler/runtime/setup_spec.rb b/spec/bundler/runtime/setup_spec.rb
index e47e64de29..cbb31f7350 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
@@ -1525,7 +1524,7 @@ end
end
describe "after setup" do
- it "allows calling #gem on random objects", bundler: "< 3" do
+ it "allows calling #gem on random objects", bundler: "2" do
install_gemfile <<-G
source "https://gem.repo1"
gem "myrack"
@@ -1540,7 +1539,7 @@ end
expect(out).to eq("myrack-1.0.0")
end
- it "keeps Kernel#gem private", bundler: "3" do
+ it "keeps Kernel#gem private", bundler: "4" do
install_gemfile <<-G
source "https://gem.repo1"
gem "myrack"
diff --git a/spec/bundler/support/checksums.rb b/spec/bundler/support/checksums.rb
index f3aa13ca9f..8e0dea4a71 100644
--- a/spec/bundler/support/checksums.rb
+++ b/spec/bundler/support/checksums.rb
@@ -58,7 +58,7 @@ module Spec
begin
enabled = (target_lockfile || lockfile).match?(/^CHECKSUMS$/)
rescue Errno::ENOENT
- enabled = Bundler.feature_flag.bundler_3_mode?
+ enabled = Bundler.feature_flag.bundler_4_mode?
end
checksums_section(enabled, &block)
end
diff --git a/spec/bundler/support/path.rb b/spec/bundler/support/path.rb
index b08a68f111..e8eb71d73a 100644
--- a/spec/bundler/support/path.rb
+++ b/spec/bundler/support/path.rb
@@ -265,7 +265,7 @@ module Spec
def replace_version_file(version, dir: source_root)
version_file = File.expand_path("lib/bundler/version.rb", dir)
contents = File.read(version_file)
- contents.sub!(/(^\s+VERSION\s*=\s*)"#{Gem::Version::VERSION_PATTERN}"/, %(\\1"#{version}"))
+ contents.sub!(/(^\s+VERSION\s*=\s*).*$/, %(\\1"#{version}"))
File.open(version_file, "w") {|f| f << contents }
end
diff --git a/spec/bundler/update/redownload_spec.rb b/spec/bundler/update/redownload_spec.rb
index 66437fb938..6f99a0c214 100644
--- a/spec/bundler/update/redownload_spec.rb
+++ b/spec/bundler/update/redownload_spec.rb
@@ -9,12 +9,12 @@ RSpec.describe "bundle update" do
end
describe "with --force" do
- it "shows a deprecation when single flag passed", bundler: 2 do
+ it "shows a deprecation when single flag passed", bundler: "2" do
bundle "update myrack --force"
expect(err).to include "[DEPRECATED] The `--force` option has been renamed to `--redownload`"
end
- it "shows a deprecation when multiple flags passed", bundler: 2 do
+ it "shows a deprecation when multiple flags passed", bundler: "2" do
bundle "update myrack --no-color --force"
expect(err).to include "[DEPRECATED] The `--force` option has been renamed to `--redownload`"
end
diff --git a/spec/ruby/core/kernel/caller_locations_spec.rb b/spec/ruby/core/kernel/caller_locations_spec.rb
index aaacd9a910..6074879d59 100644
--- a/spec/ruby/core/kernel/caller_locations_spec.rb
+++ b/spec/ruby/core/kernel/caller_locations_spec.rb
@@ -83,7 +83,7 @@ describe 'Kernel#caller_locations' do
end
end
- ruby_version_is "3.4" do
+ ruby_version_is "3.4"..."3.5" do
it "includes core library methods defined in Ruby" do
file, line = Kernel.instance_method(:tap).source_location
file.should.start_with?('<internal:')
@@ -94,5 +94,17 @@ describe 'Kernel#caller_locations' do
loc.path.should.start_with? "<internal:"
end
end
+
+ ruby_version_is "3.5" do
+ it "does not include core library methods defined in Ruby" do
+ file, line = Kernel.instance_method(:tap).source_location
+ file.should.start_with?('<internal:')
+
+ loc = nil
+ tap { loc = caller_locations(1, 1)[0] }
+ loc.label.should == "Kernel#tap"
+ loc.path.should == __FILE__
+ end
+ end
end
end
diff --git a/spec/ruby/core/kernel/caller_spec.rb b/spec/ruby/core/kernel/caller_spec.rb
index 33c7929a31..4bf9f7c2c2 100644
--- a/spec/ruby/core/kernel/caller_spec.rb
+++ b/spec/ruby/core/kernel/caller_spec.rb
@@ -84,13 +84,26 @@ describe 'Kernel#caller' do
end
guard -> { Kernel.instance_method(:tap).source_location } do
- it "includes core library methods defined in Ruby" do
- file, line = Kernel.instance_method(:tap).source_location
- file.should.start_with?('<internal:')
+ ruby_version_is ""..."3.5" do
+ it "includes core library methods defined in Ruby" do
+ file, line = Kernel.instance_method(:tap).source_location
+ file.should.start_with?('<internal:')
+
+ loc = nil
+ tap { loc = caller(1, 1)[0] }
+ loc.should =~ /\A<internal:.*in [`'](?:Kernel#)?tap'\z/
+ end
+ end
+
+ ruby_version_is "3.5" do
+ it "includes core library methods defined in Ruby" do
+ file, line = Kernel.instance_method(:tap).source_location
+ file.should.start_with?('<internal:')
- loc = nil
- tap { loc = caller(1, 1)[0] }
- loc.should =~ /\A<internal:.*in [`'](?:Kernel#)?tap'\z/
+ loc = nil
+ tap { loc = caller(1, 1)[0] }
+ loc.should =~ /\A#{ __FILE__ }:.*in [`'](?:Kernel#)?tap'\z/
+ end
end
end
end
diff --git a/spec/ruby/library/net-http/http/post_spec.rb b/spec/ruby/library/net-http/http/post_spec.rb
index ac020bd6be..cebbee4ff3 100644
--- a/spec/ruby/library/net-http/http/post_spec.rb
+++ b/spec/ruby/library/net-http/http/post_spec.rb
@@ -25,9 +25,11 @@ describe "Net::HTTP.post" do
response.should be_kind_of(Net::HTTPResponse)
end
- it "sends Content-Type: application/x-www-form-urlencoded by default" do
- response = Net::HTTP.post(URI("http://localhost:#{NetHTTPSpecs.port}/request/header"), "test=test")
- response.body.should include({ "Content-Type" => "application/x-www-form-urlencoded" }.inspect.delete("{}"))
+ ruby_version_is ""..."3.5" do
+ it "sends Content-Type: application/x-www-form-urlencoded by default" do
+ response = Net::HTTP.post(URI("http://localhost:#{NetHTTPSpecs.port}/request/header"), "test=test")
+ response.body.should include({ "Content-Type" => "application/x-www-form-urlencoded" }.inspect.delete("{}"))
+ end
end
it "does not support HTTP Basic Auth" do
diff --git a/spec/ruby/library/net-http/httpgenericrequest/exec_spec.rb b/spec/ruby/library/net-http/httpgenericrequest/exec_spec.rb
index 7de03d7da0..0912e5a71f 100644
--- a/spec/ruby/library/net-http/httpgenericrequest/exec_spec.rb
+++ b/spec/ruby/library/net-http/httpgenericrequest/exec_spec.rb
@@ -31,18 +31,20 @@ describe "Net::HTTPGenericRequest#exec when passed socket, version, path" do
end
describe "when a request body is set" do
- it "sets the 'Content-Type' header to 'application/x-www-form-urlencoded' unless the 'Content-Type' header is supplied" do
- request = Net::HTTPGenericRequest.new("POST", true, true, "/some/path")
- request.body = "Some Content"
-
- request.exec(@buffered_socket, "1.1", "/some/other/path")
- str = @socket.string
-
- str.should =~ %r[POST /some/other/path HTTP/1.1\r\n]
- str.should =~ %r[Accept: \*/\*\r\n]
- str.should =~ %r[Content-Type: application/x-www-form-urlencoded\r\n]
- str.should =~ %r[Content-Length: 12\r\n]
- str[-16..-1].should == "\r\n\r\nSome Content"
+ ruby_version_is ""..."3.5" do
+ it "sets the 'Content-Type' header to 'application/x-www-form-urlencoded' unless the 'Content-Type' header is supplied" do
+ request = Net::HTTPGenericRequest.new("POST", true, true, "/some/path")
+ request.body = "Some Content"
+
+ request.exec(@buffered_socket, "1.1", "/some/other/path")
+ str = @socket.string
+
+ str.should =~ %r[POST /some/other/path HTTP/1.1\r\n]
+ str.should =~ %r[Accept: \*/\*\r\n]
+ str.should =~ %r[Content-Type: application/x-www-form-urlencoded\r\n]
+ str.should =~ %r[Content-Length: 12\r\n]
+ str[-16..-1].should == "\r\n\r\nSome Content"
+ end
end
it "correctly sets the 'Content-Length' header and includes the body" do
@@ -62,19 +64,21 @@ describe "Net::HTTPGenericRequest#exec when passed socket, version, path" do
end
describe "when a body stream is set" do
- it "sets the 'Content-Type' header to 'application/x-www-form-urlencoded' unless the 'Content-Type' header is supplied" do
- request = Net::HTTPGenericRequest.new("POST", true, true, "/some/path",
- "Content-Length" => "10")
- request.body_stream = StringIO.new("a" * 20)
-
- request.exec(@buffered_socket, "1.1", "/some/other/path")
- str = @socket.string
-
- str.should =~ %r[POST /some/other/path HTTP/1.1\r\n]
- str.should =~ %r[Accept: \*/\*\r\n]
- str.should =~ %r[Content-Type: application/x-www-form-urlencoded\r\n]
- str.should =~ %r[Content-Length: 10\r\n]
- str[-24..-1].should == "\r\n\r\naaaaaaaaaaaaaaaaaaaa"
+ ruby_version_is ""..."3.5" do
+ it "sets the 'Content-Type' header to 'application/x-www-form-urlencoded' unless the 'Content-Type' header is supplied" do
+ request = Net::HTTPGenericRequest.new("POST", true, true, "/some/path",
+ "Content-Length" => "10")
+ request.body_stream = StringIO.new("a" * 20)
+
+ request.exec(@buffered_socket, "1.1", "/some/other/path")
+ str = @socket.string
+
+ str.should =~ %r[POST /some/other/path HTTP/1.1\r\n]
+ str.should =~ %r[Accept: \*/\*\r\n]
+ str.should =~ %r[Content-Type: application/x-www-form-urlencoded\r\n]
+ str.should =~ %r[Content-Length: 10\r\n]
+ str[-24..-1].should == "\r\n\r\naaaaaaaaaaaaaaaaaaaa"
+ end
end
it "sends the whole stream, regardless of the 'Content-Length' header" do
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)
diff --git a/st.c b/st.c
index f11e9efaf9..70da7daf83 100644
--- a/st.c
+++ b/st.c
@@ -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) {
diff --git a/string.c b/string.c
index 3ddd64ef25..403b8df15f 100644
--- a/string.c
+++ b/string.c
@@ -388,12 +388,7 @@ fstring_hash(VALUE str)
static inline bool
BARE_STRING_P(VALUE str)
{
- if (RBASIC_CLASS(str) != rb_cString) return false;
-
- if (FL_TEST_RAW(str, FL_EXIVAR)) {
- return rb_ivar_count(str) == 0;
- }
- return true;
+ return RBASIC_CLASS(str) == rb_cString && !rb_shape_obj_has_ivars(str);
}
static inline st_index_t
@@ -490,7 +485,7 @@ 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));
@@ -2316,7 +2311,7 @@ VALUE
rb_str_dup_m(VALUE str)
{
if (LIKELY(BARE_STRING_P(str))) {
- return str_duplicate(rb_obj_class(str), str);
+ return str_duplicate(rb_cString, str);
}
else {
return rb_obj_dup(str);
@@ -3669,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");
}
@@ -3679,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/struct.c b/struct.c
index 7cfc1f2a16..74ca9369a6 100644
--- a/struct.c
+++ b/struct.c
@@ -52,7 +52,8 @@ struct_ivar_get(VALUE c, ID id)
RUBY_ASSERT(RB_TYPE_P(c, T_CLASS));
ivar = rb_attr_get(c, id);
if (!NIL_P(ivar)) {
- return rb_ivar_set(orig, id, ivar);
+ if (!OBJ_FROZEN(orig)) rb_ivar_set(orig, id, ivar);
+ return ivar;
}
}
}
diff --git a/test/-ext-/bug_reporter/test_bug_reporter.rb b/test/-ext-/bug_reporter/test_bug_reporter.rb
index 8293408518..d402ab1382 100644
--- a/test/-ext-/bug_reporter/test_bug_reporter.rb
+++ b/test/-ext-/bug_reporter/test_bug_reporter.rb
@@ -6,8 +6,6 @@ require_relative '../../lib/parser_support'
class TestBugReporter < Test::Unit::TestCase
def test_bug_reporter_add
- pend "macOS 15 is not working with this test" if macos?(15)
-
description = RUBY_DESCRIPTION
description = description.sub(/\+PRISM /, '') unless ParserSupport.prism_enabled_in_subprocess?
expected_stderr = [
diff --git a/test/-ext-/gvl/test_last_thread.rb b/test/-ext-/gvl/test_last_thread.rb
index f1bebafeea..f63d98aab1 100644
--- a/test/-ext-/gvl/test_last_thread.rb
+++ b/test/-ext-/gvl/test_last_thread.rb
@@ -15,8 +15,7 @@ class TestLastThread < Test::Unit::TestCase
t1 = Time.now
t = t1 - t0
- assert_in_delta(1.0, t, 0.16)
+ assert_in_delta(1.0, t, 0.18)
end;
end
end
-
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/net/http/test_http.rb b/test/net/http/test_http.rb
index c9a27d87cb..366b4cd12c 100644
--- a/test/net/http/test_http.rb
+++ b/test/net/http/test_http.rb
@@ -494,12 +494,10 @@ module TestNetHTTP_version_1_1_methods
def test_s_post
url = "http://#{config('host')}:#{config('port')}/?q=a"
- res = assert_warning(/Content-Type did not set/) do
- Net::HTTP.post(
- URI.parse(url),
- "a=x")
- end
- assert_equal "application/x-www-form-urlencoded", res["Content-Type"]
+ res = Net::HTTP.post(
+ URI.parse(url),
+ "a=x")
+ assert_equal "application/octet-stream", res["Content-Type"]
assert_equal "a=x", res.body
assert_equal url, res["X-request-uri"]
@@ -570,9 +568,7 @@ module TestNetHTTP_version_1_1_methods
th = Thread.new do
err = !windows? ? Net::WriteTimeout : Net::ReadTimeout
assert_raise(err) do
- assert_warning(/Content-Type did not set/) do
- conn.post('/', "a"*50_000_000)
- end
+ conn.post('/', "a"*50_000_000)
end
end
assert th.join(EnvUtil.apply_timeout_scale(10))
diff --git a/test/net/http/utils.rb b/test/net/http/utils.rb
index b41341d0a0..067cca02e3 100644
--- a/test/net/http/utils.rb
+++ b/test/net/http/utils.rb
@@ -71,6 +71,11 @@ module TestNetHTTPUtils
socket.write "HTTP/1.1 100 Continue\r\n\r\n"
end
+ # Set default Content-Type if not provided
+ if !headers['Content-Type'] && (method == 'POST' || method == 'PUT' || method == 'PATCH')
+ headers['Content-Type'] = 'application/octet-stream'
+ end
+
req = Request.new(method, path, headers, socket)
if @procs.key?(req.path) || @procs.key?("#{req.path}/")
proc = @procs[req.path] || @procs["#{req.path}/"]
@@ -306,16 +311,18 @@ module TestNetHTTPUtils
scheme = headers['X-Request-Scheme'] || 'http'
host = @config['host']
port = socket.addr[1]
- charset = parse_content_type(headers['Content-Type'])[1]
+ content_type = headers['Content-Type'] || 'application/octet-stream'
+ charset = parse_content_type(content_type)[1]
path = "#{scheme}://#{host}:#{port}#{path}"
path = path.encode(charset) if charset
- response = "HTTP/1.1 200 OK\r\nContent-Type: #{headers['Content-Type']}\r\nContent-Length: #{body.bytesize}\r\nX-request-uri: #{path}\r\n\r\n#{body}"
+ response = "HTTP/1.1 200 OK\r\nContent-Type: #{content_type}\r\nContent-Length: #{body.bytesize}\r\nX-request-uri: #{path}\r\n\r\n#{body}"
socket.print(response)
end
def handle_patch(path, headers, socket)
body = socket.read(headers['Content-Length'].to_i)
- response = "HTTP/1.1 200 OK\r\nContent-Type: #{headers['Content-Type']}\r\nContent-Length: #{body.bytesize}\r\n\r\n#{body}"
+ content_type = headers['Content-Type'] || 'application/octet-stream'
+ response = "HTTP/1.1 200 OK\r\nContent-Type: #{content_type}\r\nContent-Length: #{body.bytesize}\r\n\r\n#{body}"
socket.print(response)
end
diff --git a/test/prism/fixtures/strings.txt b/test/prism/fixtures/strings.txt
index 0787152786..77e1e4acff 100644
--- a/test/prism/fixtures/strings.txt
+++ b/test/prism/fixtures/strings.txt
@@ -146,6 +146,10 @@ baz
%Q{abc}
+%Q(\«)
+
+%q(\«)
+
%^#$^#
%@#@#
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/namespace/instance_variables.rb b/test/ruby/namespace/instance_variables.rb
new file mode 100644
index 0000000000..1562ad5d45
--- /dev/null
+++ b/test/ruby/namespace/instance_variables.rb
@@ -0,0 +1,21 @@
+class String
+ class << self
+ attr_reader :str_ivar1
+
+ def str_ivar2
+ @str_ivar2
+ end
+ end
+
+ @str_ivar1 = 111
+ @str_ivar2 = 222
+end
+
+class StringDelegator < BasicObject
+private
+ def method_missing(...)
+ ::String.public_send(...)
+ end
+end
+
+StringDelegatorObj = StringDelegator.new
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_backtrace.rb b/test/ruby/test_backtrace.rb
index fca7b62030..01a757f827 100644
--- a/test/ruby/test_backtrace.rb
+++ b/test/ruby/test_backtrace.rb
@@ -454,4 +454,10 @@ class TestBacktrace < Test::Unit::TestCase
foo::Bar.baz
end;
end
+
+ def test_backtrace_internal_frame
+ backtrace = tap { break caller_locations(0) }
+ assert_equal(__FILE__, backtrace[1].path) # not "<internal:kernel>"
+ assert_equal("Kernel#tap", backtrace[1].label)
+ end
end
diff --git a/test/ruby/test_compile_prism.rb b/test/ruby/test_compile_prism.rb
index 819d0d35aa..86f7f0b14f 100644
--- a/test/ruby/test_compile_prism.rb
+++ b/test/ruby/test_compile_prism.rb
@@ -1053,6 +1053,9 @@ module Prism
assert_prism_eval("for foo, in [1,2,3] do end")
assert_prism_eval("for i, j in {a: 'b'} do; i; j; end")
+
+ # Test splat node as index in for loop
+ assert_prism_eval("for *x in [[1,2], [3,4]] do; x; end")
end
############################################################################
diff --git a/test/ruby/test_data.rb b/test/ruby/test_data.rb
index bb38f8ec91..dd698fdcc4 100644
--- a/test/ruby/test_data.rb
+++ b/test/ruby/test_data.rb
@@ -280,4 +280,10 @@ class TestData < Test::Unit::TestCase
assert_not_same(test, loaded)
assert_predicate(loaded, :frozen?)
end
+
+ def test_frozen_subclass
+ test = Class.new(Data.define(:a)).freeze.new(a: 0)
+ assert_kind_of(Data, test)
+ assert_equal([:a], test.members)
+ end
end
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_namespace.rb b/test/ruby/test_namespace.rb
index 395f244c8e..f13063be48 100644
--- a/test/ruby/test_namespace.rb
+++ b/test/ruby/test_namespace.rb
@@ -222,6 +222,26 @@ class TestNamespace < Test::Unit::TestCase
end;
end
+ def test_instance_variable
+ pend unless Namespace.enabled?
+
+ @n.require_relative('namespace/instance_variables')
+
+ assert_equal [], String.instance_variables
+ assert_equal [:@str_ivar1, :@str_ivar2], @n::StringDelegatorObj.instance_variables
+ assert_equal 111, @n::StringDelegatorObj.str_ivar1
+ assert_equal 222, @n::StringDelegatorObj.str_ivar2
+ assert_equal 222, @n::StringDelegatorObj.instance_variable_get(:@str_ivar2)
+
+ @n::StringDelegatorObj.instance_variable_set(:@str_ivar3, 333)
+ assert_equal 333, @n::StringDelegatorObj.instance_variable_get(:@str_ivar3)
+ @n::StringDelegatorObj.remove_instance_variable(:@str_ivar1)
+ assert_nil @n::StringDelegatorObj.str_ivar1
+ assert_equal [:@str_ivar2, :@str_ivar3], @n::StringDelegatorObj.instance_variables
+
+ assert_equal [], String.instance_variables
+ end
+
def test_methods_added_in_namespace_are_invisible_globally
pend unless Namespace.enabled?
diff --git a/test/ruby/test_object_id.rb b/test/ruby/test_object_id.rb
index 44421ea256..9c0099517b 100644
--- a/test/ruby/test_object_id.rb
+++ b/test/ruby/test_object_id.rb
@@ -198,3 +198,49 @@ class TestObjectIdTooComplexGeneric < TestObjectId
end
end
end
+
+class TestObjectIdRactor < Test::Unit::TestCase
+ def test_object_id_race_free
+ 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 = 10_000
+ objs = Ractor.make_shareable(N.times.map { MyClass.new })
+ 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
+
+ def test_external_object_id_ractor_move
+ assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
+ begin;
+ Warning[:experimental] = false
+ class MyClass
+ attr_reader :a, :b, :c
+ def initialize
+ @a = @b = @c = nil
+ end
+ end
+ obj = Ractor.make_shareable(MyClass.new)
+ object_id = obj.object_id
+ obj = Ractor.new { Ractor.receive }.send(obj, move: true).value
+ assert_equal object_id, obj.object_id
+ end;
+ end
+end
diff --git a/test/ruby/test_ractor.rb b/test/ruby/test_ractor.rb
index b423993df1..3fc891da23 100644
--- a/test/ruby/test_ractor.rb
+++ b/test/ruby/test_ractor.rb
@@ -79,6 +79,26 @@ class TestRactor < Test::Unit::TestCase
end;
end
+ def test_class_instance_variables
+ assert_ractor(<<~'RUBY')
+ # Once we're in multi-ractor mode, the codepaths
+ # for class instance variables are a bit different.
+ Ractor.new {}.value
+
+ class TestClass
+ @a = 1
+ @b = 2
+ @c = 3
+ @d = 4
+ end
+
+ assert_equal 4, TestClass.remove_instance_variable(:@d)
+ assert_nil TestClass.instance_variable_get(:@d)
+ assert_equal 4, TestClass.instance_variable_set(:@d, 4)
+ assert_equal 4, TestClass.instance_variable_get(:@d)
+ RUBY
+ end
+
def test_require_raises_and_no_ractor_belonging_issue
assert_ractor(<<~'RUBY')
require "tempfile"
@@ -98,6 +118,21 @@ class TestRactor < Test::Unit::TestCase
RUBY
end
+ def test_require_non_string
+ assert_ractor(<<~'RUBY')
+ require "tempfile"
+ require "pathname"
+ f = Tempfile.new(["file_to_require_from_ractor", ".rb"])
+ f.write("")
+ f.flush
+ result = Ractor.new(f.path) do |path|
+ require Pathname.new(path)
+ "success"
+ end.value
+ assert_equal "success", result
+ RUBY
+ end
+
def assert_make_shareable(obj)
refute Ractor.shareable?(obj), "object was already shareable"
Ractor.make_shareable(obj)
diff --git a/test/ruby/test_rubyoptions.rb b/test/ruby/test_rubyoptions.rb
index 833b6a3b7d..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.
@@ -836,8 +842,6 @@ class TestRubyOptions < Test::Unit::TestCase
end
def assert_segv(args, message=nil, list: SEGVTest::ExpectedStderrList, **opt, &block)
- pend "macOS 15 is not working with this assertion" if macos?(15)
-
# We want YJIT to be enabled in the subprocess if it's enabled for us
# so that the Ruby description matches.
env = Hash === args.first ? args.shift : {}
@@ -849,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)
@@ -862,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
@@ -881,8 +888,6 @@ class TestRubyOptions < Test::Unit::TestCase
end
def assert_crash_report(path, cmd = nil, &block)
- pend "macOS 15 is not working with this assertion" if macos?(15)
-
Dir.mktmpdir("ruby_crash_report") do |dir|
list = SEGVTest::ExpectedStderrList
if cmd
diff --git a/test/ruby/test_struct.rb b/test/ruby/test_struct.rb
index ecd8ed196c..db591c306e 100644
--- a/test/ruby/test_struct.rb
+++ b/test/ruby/test_struct.rb
@@ -550,6 +550,12 @@ module TestStruct
CODE
end
+ def test_frozen_subclass
+ test = Class.new(@Struct.new(:a)).freeze.new(a: 0)
+ assert_kind_of(@Struct, test)
+ assert_equal([:a], test.members)
+ end
+
class TopStruct < Test::Unit::TestCase
include TestStruct
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_vm_dump.rb b/test/ruby/test_vm_dump.rb
index 709fd5eadf..a3e7b69913 100644
--- a/test/ruby/test_vm_dump.rb
+++ b/test/ruby/test_vm_dump.rb
@@ -5,8 +5,6 @@ return unless /darwin/ =~ RUBY_PLATFORM
class TestVMDump < Test::Unit::TestCase
def assert_darwin_vm_dump_works(args, timeout=nil)
- pend "macOS 15 is not working with this assertion" if macos?(15)
-
assert_in_out_err(args, "", [], /^\[IMPORTANT\]/, timeout: timeout || 300)
end
diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb
index 47a9f6f7dc..6e0f274c30 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
@@ -62,6 +76,21 @@ class TestZJIT < Test::Unit::TestCase
}
end
+ def test_invokebuiltin
+ assert_compiles '["."]', %q{
+ def test = Dir.glob(".")
+ test
+ }
+ end
+
+ def test_invokebuiltin_delegate
+ assert_compiles '[[], true]', %q{
+ def test = [].clone(freeze: true)
+ r = test
+ [r, r.frozen?]
+ }
+ end
+
def test_opt_plus_const
assert_compiles '3', %q{
def test = 1 + 2
@@ -102,12 +131,39 @@ class TestZJIT < Test::Unit::TestCase
}, call_threshold: 2
end
+ def test_opt_plus_type_guard_exit_with_locals
+ assert_compiles '[6, 6.0]', %q{
+ def test(a)
+ local = 3
+ 1 + a + local
+ end
+ test(1) # profile opt_plus
+ [test(2), test(2.0)]
+ }, call_threshold: 2
+ end
+
def test_opt_plus_type_guard_nested_exit
- omit 'rewind_caller_frames is not implemented yet'
- assert_compiles '[3, 3.0]', %q{
+ assert_compiles '[4, 4.0]', %q{
def side_exit(n) = 1 + n
def jit_frame(n) = 1 + side_exit(n)
def entry(n) = jit_frame(n)
+ entry(2) # profile send
+ [entry(2), entry(2.0)]
+ }, call_threshold: 2
+ end
+
+ def test_opt_plus_type_guard_nested_exit_with_locals
+ assert_compiles '[9, 9.0]', %q{
+ def side_exit(n)
+ local = 2
+ 1 + n + local
+ end
+ def jit_frame(n)
+ local = 3
+ 1 + side_exit(n) + local
+ end
+ def entry(n) = jit_frame(n)
+ entry(2) # profile send
[entry(2), entry(2.0)]
}, call_threshold: 2
end
@@ -130,7 +186,6 @@ class TestZJIT < Test::Unit::TestCase
end
def test_opt_mult_overflow
- omit 'side exits are not implemented yet'
assert_compiles '[6, -6, 9671406556917033397649408, -9671406556917033397649408, 21267647932558653966460912964485513216]', %q{
def test(a, b)
a * b
@@ -255,6 +310,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 = []
@@ -610,6 +693,79 @@ 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_dupn
+ assert_compiles '[[1], [1, 1], :rhs, [nil, :rhs]]', <<~RUBY, insns: [:dupn]
+ def test(array) = (array[1, 2] ||= :rhs)
+
+ one = [1, 1]
+ start_empty = []
+ [test(one), one, test(start_empty), start_empty]
+ RUBY
+ end
+
+ def test_send_backtrace
+ backtrace = [
+ "-e:2:in 'Object#jit_frame1'",
+ "-e:3:in 'Object#entry'",
+ "-e:5:in 'block in <main>'",
+ "-e:6:in '<main>'",
+ ]
+ assert_compiles backtrace.inspect, %q{
+ def jit_frame2 = caller # 1
+ def jit_frame1 = jit_frame2 # 2
+ def entry = jit_frame1 # 3
+ entry # profile send # 4
+ entry # 5
+ }, 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
@@ -631,11 +787,7 @@ class TestZJIT < Test::Unit::TestCase
pipe_fd = 3
script = <<~RUBY
- _test_proc = -> {
- RubyVM::ZJIT.assert_compiles
- #{test_script}
- }
- ret_val = _test_proc.call
+ ret_val = (_test_proc = -> { RubyVM::ZJIT.assert_compiles; #{test_script.lstrip} }).call
result = {
ret_val:,
#{ unless insns.empty?
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 468aecde56..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
@@ -1005,6 +984,38 @@ ERROR: Possible alternatives: non_existent_with_hint
assert_equal %W[a-3-#{local}], @cmd.installed_specs.map(&:full_name)
end
+ def test_install_gem_platform_specificity_match
+ util_set_arch "arm64-darwin-20"
+
+ spec_fetcher do |fetcher|
+ %w[ruby universal-darwin universal-darwin-20 x64-darwin-20 arm64-darwin-20].each do |platform|
+ fetcher.download "a", 3 do |s|
+ s.platform = platform
+ end
+ end
+ end
+
+ @cmd.install_gem "a", ">= 0"
+
+ assert_equal %w[a-3-arm64-darwin-20], @cmd.installed_specs.map(&:full_name)
+ end
+
+ def test_install_gem_platform_specificity_match_reverse_order
+ util_set_arch "arm64-darwin-20"
+
+ spec_fetcher do |fetcher|
+ %w[ruby universal-darwin universal-darwin-20 x64-darwin-20 arm64-darwin-20].reverse_each do |platform|
+ fetcher.download "a", 3 do |s|
+ s.platform = platform
+ end
+ end
+ end
+
+ @cmd.install_gem "a", ">= 0"
+
+ assert_equal %w[a-3-arm64-darwin-20], @cmd.installed_specs.map(&:full_name)
+ end
+
def test_install_gem_ignore_dependencies_specific_file
spec = util_spec "a", 2
diff --git a/test/rubygems/test_gem_commands_pristine_command.rb b/test/rubygems/test_gem_commands_pristine_command.rb
index 46c06db014..e9c4d32945 100644
--- a/test/rubygems/test_gem_commands_pristine_command.rb
+++ b/test/rubygems/test_gem_commands_pristine_command.rb
@@ -125,8 +125,8 @@ class TestGemCommandsPristineCommand < Gem::TestCase
@cmd.execute
end
- assert File.exist?(gem_bin)
- assert File.exist?(gem_stub)
+ assert_path_exist gem_bin
+ assert_path_exist gem_stub
out = @ui.output.split "\n"
@@ -537,8 +537,8 @@ class TestGemCommandsPristineCommand < Gem::TestCase
@cmd.execute
end
- assert File.exist? gem_exec
- refute File.exist? gem_lib
+ assert_path_exist gem_exec
+ assert_path_not_exist gem_lib
end
def test_execute_only_plugins
@@ -572,9 +572,9 @@ class TestGemCommandsPristineCommand < Gem::TestCase
@cmd.execute
end
- refute File.exist? gem_exec
- assert File.exist? gem_plugin
- refute File.exist? gem_lib
+ assert_path_not_exist gem_exec
+ assert_path_exist gem_plugin
+ assert_path_not_exist gem_lib
end
def test_execute_bindir
@@ -606,8 +606,8 @@ class TestGemCommandsPristineCommand < Gem::TestCase
@cmd.execute
end
- refute File.exist? gem_exec
- assert File.exist? gem_bindir
+ assert_path_not_exist gem_exec
+ assert_path_exist gem_bindir
end
def test_execute_unknown_gem_at_remote_source
@@ -659,6 +659,42 @@ class TestGemCommandsPristineCommand < Gem::TestCase
refute_includes "ruby_executable_hooks", File.read(exe)
end
+ def test_execute_default_gem_and_regular_gem
+ a_default = new_default_spec("a", "1.2.0")
+
+ a = util_spec "a" do |s|
+ s.extensions << "ext/a/extconf.rb"
+ end
+
+ ext_path = File.join @tempdir, "ext", "a", "extconf.rb"
+ write_file ext_path do |io|
+ io.write <<-'RUBY'
+ File.open "Makefile", "w" do |f|
+ f.puts "clean:\n\techo cleaned\n"
+ f.puts "all:\n\techo built\n"
+ f.puts "install:\n\techo installed\n"
+ end
+ RUBY
+ end
+
+ install_default_gems a_default
+ install_gem a
+
+ # Remove the extension files for a
+ FileUtils.rm_rf a.gem_build_complete_path
+
+ @cmd.options[:args] = %w[a]
+
+ use_ui @ui do
+ @cmd.execute
+ end
+
+ assert_includes @ui.output, "Restored #{a.full_name}"
+
+ # Check extension files for a were restored
+ assert_path_exist a.gem_build_complete_path
+ end
+
def test_execute_multi_platform
a = util_spec "a" do |s|
s.extensions << "ext/a/extconf.rb"
diff --git a/test/rubygems/test_gem_commands_setup_command.rb b/test/rubygems/test_gem_commands_setup_command.rb
index 7105c1ccec..dfd951268d 100644
--- a/test/rubygems/test_gem_commands_setup_command.rb
+++ b/test/rubygems/test_gem_commands_setup_command.rb
@@ -4,13 +4,6 @@ require_relative "helper"
require "rubygems/commands/setup_command"
class TestGemCommandsSetupCommand < Gem::TestCase
- bundler_gemspec = File.expand_path("../../bundler/lib/bundler/version.rb", __dir__)
- if File.exist?(bundler_gemspec)
- BUNDLER_VERS = File.read(bundler_gemspec).match(/VERSION = "(#{Gem::Version::VERSION_PATTERN})"/)[1]
- else
- BUNDLER_VERS = "2.0.1"
- end
-
def setup
super
@@ -35,7 +28,7 @@ class TestGemCommandsSetupCommand < Gem::TestCase
create_dummy_files(filelist)
- gemspec = util_spec "bundler", BUNDLER_VERS do |s|
+ gemspec = util_spec "bundler", "9.9.9" do |s|
s.bindir = "exe"
s.executables = ["bundle", "bundler"]
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_ext_cargo_builder.rb b/test/rubygems/test_gem_ext_cargo_builder.rb
index 5035937544..b970e442c2 100644
--- a/test/rubygems/test_gem_ext_cargo_builder.rb
+++ b/test/rubygems/test_gem_ext_cargo_builder.rb
@@ -141,6 +141,58 @@ class TestGemExtCargoBuilder < Gem::TestCase
end
end
+ def test_linker_args
+ orig_cc = RbConfig::MAKEFILE_CONFIG["CC"]
+ RbConfig::MAKEFILE_CONFIG["CC"] = "clang"
+
+ builder = Gem::Ext::CargoBuilder.new
+ args = builder.send(:linker_args)
+
+ assert args[1], "linker=clang"
+ assert_nil args[2]
+ ensure
+ RbConfig::MAKEFILE_CONFIG["CC"] = orig_cc
+ end
+
+ def test_linker_args_with_options
+ orig_cc = RbConfig::MAKEFILE_CONFIG["CC"]
+ RbConfig::MAKEFILE_CONFIG["CC"] = "gcc -Wl,--no-undefined"
+
+ builder = Gem::Ext::CargoBuilder.new
+ args = builder.send(:linker_args)
+
+ assert args[1], "linker=clang"
+ assert args[3], "link-args=-Wl,--no-undefined"
+ ensure
+ RbConfig::MAKEFILE_CONFIG["CC"] = orig_cc
+ end
+
+ def test_linker_args_with_cachetools
+ orig_cc = RbConfig::MAKEFILE_CONFIG["CC"]
+ RbConfig::MAKEFILE_CONFIG["CC"] = "sccache clang"
+
+ builder = Gem::Ext::CargoBuilder.new
+ args = builder.send(:linker_args)
+
+ assert args[1], "linker=clang"
+ assert_nil args[2]
+ ensure
+ RbConfig::MAKEFILE_CONFIG["CC"] = orig_cc
+ end
+
+ def test_linker_args_with_cachetools_and_options
+ orig_cc = RbConfig::MAKEFILE_CONFIG["CC"]
+ RbConfig::MAKEFILE_CONFIG["CC"] = "ccache gcc -Wl,--no-undefined"
+
+ builder = Gem::Ext::CargoBuilder.new
+ args = builder.send(:linker_args)
+
+ assert args[1], "linker=clang"
+ assert args[3], "link-args=-Wl,--no-undefined"
+ ensure
+ RbConfig::MAKEFILE_CONFIG["CC"] = orig_cc
+ end
+
private
def skip_unsupported_platforms!
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
diff --git a/thread.c b/thread.c
index a637c8ec7c..5575157728 100644
--- a/thread.c
+++ b/thread.c
@@ -519,12 +519,8 @@ thread_cleanup_func(void *th_ptr, int atfork)
th->locking_mutex = Qfalse;
thread_cleanup_func_before_exec(th_ptr);
- /*
- * Unfortunately, we can't release native threading resource at fork
- * because libc may have unstable locking state therefore touching
- * a threading resource may cause a deadlock.
- */
if (atfork) {
+ native_thread_destroy_atfork(th->nt);
th->nt = NULL;
return;
}
@@ -1544,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,
@@ -1570,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;
}
@@ -6210,7 +6227,12 @@ threadptr_interrupt_exec_exec(rb_thread_t *th)
RUBY_DEBUG_LOG("task:%p", task);
if (task) {
- (*task->func)(task->data);
+ if (task->flags & rb_interrupt_exec_flag_new_thread) {
+ rb_thread_create(task->func, task->data);
+ }
+ else {
+ (*task->func)(task->data);
+ }
ruby_xfree(task);
}
else {
@@ -6233,43 +6255,15 @@ threadptr_interrupt_exec_cleanup(rb_thread_t *th)
rb_native_mutex_unlock(&th->interrupt_lock);
}
-struct interrupt_ractor_new_thread_data {
- rb_interrupt_exec_func_t *func;
- void *data;
-};
-
-static VALUE
-interrupt_ractor_new_thread_func(void *data)
-{
- struct interrupt_ractor_new_thread_data d = *(struct interrupt_ractor_new_thread_data *)data;
- ruby_xfree(data);
-
- d.func(d.data);
- return Qnil;
-}
-
-static VALUE
-interrupt_ractor_func(void *data)
-{
- rb_thread_create(interrupt_ractor_new_thread_func, data);
- return Qnil;
-}
-
// native thread safe
// func/data should be native thread safe
void
rb_ractor_interrupt_exec(struct rb_ractor_struct *target_r,
rb_interrupt_exec_func_t *func, void *data, enum rb_interrupt_exec_flag flags)
{
- struct interrupt_ractor_new_thread_data *d = ALLOC(struct interrupt_ractor_new_thread_data);
-
RUBY_DEBUG_LOG("flags:%d", (int)flags);
- d->func = func;
- d->data = data;
rb_thread_t *main_th = target_r->threads.main;
- rb_threadptr_interrupt_exec(main_th, interrupt_ractor_func, d, flags);
-
- // TODO MEMO: we can create a new thread in a ractor, but not sure how to do that now.
+ rb_threadptr_interrupt_exec(main_th, func, data, flags | rb_interrupt_exec_flag_new_thread);
}
diff --git a/thread_none.c b/thread_none.c
index d535d9af4c..38686e17c1 100644
--- a/thread_none.c
+++ b/thread_none.c
@@ -137,6 +137,12 @@ ruby_mn_threads_params(void)
{
}
+static void
+native_thread_destroy_atfork(struct rb_native_thread *nt)
+{
+ /* no-op */
+}
+
static int
native_thread_init_stack(rb_thread_t *th, void *local_in_parent_frame)
{
diff --git a/thread_pthread.c b/thread_pthread.c
index f9352bbb56..377e1d9f64 100644
--- a/thread_pthread.c
+++ b/thread_pthread.c
@@ -1817,6 +1817,27 @@ native_thread_assign(struct rb_native_thread *nt, rb_thread_t *th)
}
static void
+native_thread_destroy_atfork(struct rb_native_thread *nt)
+{
+ if (nt) {
+ /* We can't call rb_native_cond_destroy here because according to the
+ * specs of pthread_cond_destroy:
+ *
+ * Attempting to destroy a condition variable upon which other threads
+ * are currently blocked results in undefined behavior.
+ *
+ * Specifically, glibc's pthread_cond_destroy waits on all the other
+ * listeners. Since after forking all the threads are dead, the condition
+ * variable's listeners will never wake up, so it will hang forever.
+ */
+
+ RB_ALTSTACK_FREE(nt->altstack);
+ ruby_xfree(nt->nt_context);
+ ruby_xfree(nt);
+ }
+}
+
+static void
native_thread_destroy(struct rb_native_thread *nt)
{
if (nt) {
@@ -1826,9 +1847,7 @@ native_thread_destroy(struct rb_native_thread *nt)
rb_native_cond_destroy(&nt->cond.intr);
}
- RB_ALTSTACK_FREE(nt->altstack);
- ruby_xfree(nt->nt_context);
- ruby_xfree(nt);
+ native_thread_destroy_atfork(nt);
}
}
diff --git a/thread_win32.c b/thread_win32.c
index ed8a99dd88..576f617e8d 100644
--- a/thread_win32.c
+++ b/thread_win32.c
@@ -617,6 +617,12 @@ native_thread_init_stack(rb_thread_t *th, void *local_in_parent_frame)
th->ec->machine.stack_maxsize = size - space;
}
+static void
+native_thread_destroy_atfork(struct rb_native_thread *nt)
+{
+ /* no-op */
+}
+
#ifndef InterlockedExchangePointer
#define InterlockedExchangePointer(t, v) \
(void *)InterlockedExchange((long *)(t), (long)(v))
diff --git a/time.c b/time.c
index 1b02cf4259..1eb8f8da9c 100644
--- a/time.c
+++ b/time.c
@@ -249,6 +249,7 @@ divmodv(VALUE n, VALUE d, VALUE *q, VALUE *r)
# define FIXWV2WINT(w) FIX2LONG(WIDEVAL_GET(w))
#endif
+#define SIZEOF_WIDEINT SIZEOF_INT64_T
#define POSFIXWVABLE(wi) ((wi) < FIXWV_MAX+1)
#define NEGFIXWVABLE(wi) ((wi) >= FIXWV_MIN)
#define FIXWV_P(w) FIXWINT_P(WIDEVAL_GET(w))
@@ -1891,7 +1892,7 @@ time_mark(void *ptr)
{
struct time_object *tobj = ptr;
if (!FIXWV_P(tobj->timew)) {
- rb_gc_mark_movable(WIDEVAL_GET(tobj->timew));
+ rb_gc_mark_movable(w2v(tobj->timew));
}
rb_gc_mark_movable(tobj->vtm.year);
rb_gc_mark_movable(tobj->vtm.subsecx);
@@ -1904,7 +1905,7 @@ time_compact(void *ptr)
{
struct time_object *tobj = ptr;
if (!FIXWV_P(tobj->timew)) {
- WIDEVAL_GET(tobj->timew) = rb_gc_location(WIDEVAL_GET(tobj->timew));
+ WIDEVAL_GET(tobj->timew) = WIDEVAL_WRAP(rb_gc_location(w2v(tobj->timew)));
}
tobj->vtm.year = rb_gc_location(tobj->vtm.year);
@@ -1968,11 +1969,11 @@ time_modify(VALUE time)
}
static wideval_t
-timenano2timew(time_t sec, long nsec)
+timenano2timew(wideint_t sec, long nsec)
{
wideval_t timew;
- timew = rb_time_magnify(TIMET2WV(sec));
+ timew = rb_time_magnify(WINT2WV(sec));
if (nsec)
timew = wadd(timew, wmulquoll(WINT2WV(nsec), TIME_SCALE, 1000000000));
return timew;
@@ -2306,14 +2307,14 @@ utc_offset_arg(VALUE arg)
static void
zone_set_offset(VALUE zone, struct time_object *tobj,
- wideval_t tlocal, wideval_t tutc)
+ wideval_t tlocal, wideval_t tutc, VALUE time)
{
/* tlocal and tutc must be unmagnified and in seconds */
wideval_t w = wsub(tlocal, tutc);
VALUE off = w2v(w);
validate_utc_offset(off);
- tobj->vtm.utc_offset = off;
- tobj->vtm.zone = zone;
+ RB_OBJ_WRITE(time, &tobj->vtm.utc_offset, off);
+ RB_OBJ_WRITE(time, &tobj->vtm.zone, zone);
TZMODE_SET_LOCALTIME(tobj);
}
@@ -2428,7 +2429,7 @@ zone_timelocal(VALUE zone, VALUE time)
if (UNDEF_P(utc)) return 0;
s = extract_time(utc);
- zone_set_offset(zone, tobj, t, s);
+ zone_set_offset(zone, tobj, t, s, time);
s = rb_time_magnify(s);
if (tobj->vtm.subsecx != INT2FIX(0)) {
s = wadd(s, v2w(tobj->vtm.subsecx));
@@ -2457,7 +2458,7 @@ zone_localtime(VALUE zone, VALUE time)
s = extract_vtm(local, time, tobj, subsecx);
tobj->vtm.tm_got = 1;
- zone_set_offset(zone, tobj, s, t);
+ zone_set_offset(zone, tobj, s, t, time);
zone_set_dst(zone, tobj, tm);
RB_GC_GUARD(time);
@@ -2747,15 +2748,15 @@ only_year:
}
static void
-subsec_normalize(time_t *secp, long *subsecp, const long maxsubsec)
+subsec_normalize(wideint_t *secp, long *subsecp, const long maxsubsec)
{
- time_t sec = *secp;
+ wideint_t sec = *secp;
long subsec = *subsecp;
long sec2;
if (UNLIKELY(subsec >= maxsubsec)) { /* subsec positive overflow */
sec2 = subsec / maxsubsec;
- if (TIMET_MAX - sec2 < sec) {
+ if (WIDEINT_MAX - sec2 < sec) {
rb_raise(rb_eRangeError, "out of Time range");
}
subsec -= sec2 * maxsubsec;
@@ -2763,16 +2764,12 @@ subsec_normalize(time_t *secp, long *subsecp, const long maxsubsec)
}
else if (UNLIKELY(subsec < 0)) { /* subsec negative overflow */
sec2 = NDIV(subsec, maxsubsec); /* negative div */
- if (sec < TIMET_MIN - sec2) {
+ if (sec < WIDEINT_MIN - sec2) {
rb_raise(rb_eRangeError, "out of Time range");
}
subsec -= sec2 * maxsubsec;
sec += sec2;
}
-#ifndef NEGATIVE_TIME_T
- if (sec < 0)
- rb_raise(rb_eArgError, "time must be positive");
-#endif
*secp = sec;
*subsecp = subsec;
}
@@ -2780,13 +2777,6 @@ subsec_normalize(time_t *secp, long *subsecp, const long maxsubsec)
#define time_usec_normalize(secp, usecp) subsec_normalize(secp, usecp, 1000000)
#define time_nsec_normalize(secp, nsecp) subsec_normalize(secp, nsecp, 1000000000)
-static wideval_t
-nsec2timew(time_t sec, long nsec)
-{
- time_nsec_normalize(&sec, &nsec);
- return timenano2timew(sec, nsec);
-}
-
static VALUE
time_new_timew(VALUE klass, wideval_t timew)
{
@@ -2800,25 +2790,39 @@ time_new_timew(VALUE klass, wideval_t timew)
return time;
}
+static wideint_t
+TIMETtoWIDEINT(time_t t)
+{
+#if SIZEOF_TIME_T * CHAR_BIT - (SIGNEDNESS_OF_TIME_T < 0) > \
+ SIZEOF_WIDEINT * CHAR_BIT - 1
+ /* compare in bit size without sign bit */
+ if (t > WIDEINT_MAX) rb_raise(rb_eArgError, "out of Time range");
+#endif
+ return (wideint_t)t;
+}
+
VALUE
rb_time_new(time_t sec, long usec)
{
- time_usec_normalize(&sec, &usec);
- return time_new_timew(rb_cTime, timenano2timew(sec, usec * 1000));
+ wideint_t isec = TIMETtoWIDEINT(sec);
+ time_usec_normalize(&isec, &usec);
+ return time_new_timew(rb_cTime, timenano2timew(isec, usec * 1000));
}
/* returns localtime time object */
VALUE
rb_time_nano_new(time_t sec, long nsec)
{
- return time_new_timew(rb_cTime, nsec2timew(sec, nsec));
+ wideint_t isec = TIMETtoWIDEINT(sec);
+ time_nsec_normalize(&isec, &nsec);
+ return time_new_timew(rb_cTime, timenano2timew(isec, nsec));
}
VALUE
rb_time_timespec_new(const struct timespec *ts, int offset)
{
struct time_object *tobj;
- VALUE time = time_new_timew(rb_cTime, nsec2timew(ts->tv_sec, ts->tv_nsec));
+ VALUE time = rb_time_nano_new(ts->tv_sec, ts->tv_nsec);
if (-86400 < offset && offset < 86400) { /* fixoff */
GetTimeval(time, tobj);
@@ -4084,7 +4088,9 @@ time_init_copy(VALUE copy, VALUE time)
if (!OBJ_INIT_COPY(copy, time)) return copy;
GetTimeval(time, tobj);
GetNewTimeval(copy, tcopy);
- MEMCPY(tcopy, tobj, struct time_object, 1);
+
+ time_set_timew(copy, tcopy, tobj->timew);
+ time_set_vtm(copy, tcopy, tobj->vtm);
return copy;
}
@@ -5748,7 +5754,7 @@ end_submicro: ;
}
if (!NIL_P(zone)) {
zone = mload_zone(time, zone);
- tobj->vtm.zone = zone;
+ RB_OBJ_WRITE(time, &tobj->vtm.zone, zone);
zone_localtime(zone, time);
}
@@ -5794,8 +5800,10 @@ tm_from_time(VALUE klass, VALUE time)
tm = time_s_alloc(klass);
ttm = RTYPEDDATA_GET_DATA(tm);
v = &vtm;
- GMTIMEW(ttm->timew = tobj->timew, v);
- ttm->timew = wsub(ttm->timew, v->subsecx);
+
+ WIDEVALUE timew = tobj->timew;
+ GMTIMEW(timew, v);
+ time_set_timew(tm, ttm, wsub(timew, v->subsecx));
v->subsecx = INT2FIX(0);
v->zone = Qnil;
time_set_vtm(tm, ttm, *v);
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..101ea350c6 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}")), out: :err)
+ 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, **opts)
+ spawn(*%W[gdb --batch --quiet --pid #{pid}], *args, **opts)
+ 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, **opts)
+ spawn(*%W[lldb --batch -Q --attach-pid #{pid}], *args, **opts)
+ 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/sync_default_gems.rb b/tool/sync_default_gems.rb
index 932f37b77c..ca0b15dd19 100755
--- a/tool/sync_default_gems.rb
+++ b/tool/sync_default_gems.rb
@@ -136,9 +136,11 @@ module SyncDefaultGems
cp_r("#{upstream}/bundler/spec", "spec/bundler")
rm_rf("spec/bundler/bin")
- parallel_tests_content = File.read("#{upstream}/bundler/bin/parallel_rspec").gsub("../spec", "../bundler")
- File.write("spec/bin/parallel_rspec", parallel_tests_content)
- chmod("+x", "spec/bin/parallel_rspec")
+ ["parallel_rspec", "rspec"].each do |binstub|
+ content = File.read("#{upstream}/bundler/bin/#{binstub}").gsub("../spec", "../bundler")
+ File.write("spec/bin/#{binstub}", content)
+ chmod("+x", "spec/bin/#{binstub}")
+ end
%w[dev_gems test_gems rubocop_gems standard_gems].each do |gemfile|
["rb.lock", "rb"].each do |ext|
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 a54bebcec0..a2012823cd 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,116 +1239,71 @@ 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)
+rb_ivar_generic_fields_tbl_lookup(VALUE obj, VALUE *fields_obj)
{
- 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)
-{
- 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);
}
}
void
rb_free_generic_ivar(VALUE obj)
{
- st_data_t key = (st_data_t)obj, value;
+ 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);
- }
- }
-}
-
-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)));
+ RB_VM_LOCKING() {
+ 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
rb_obj_field_get(VALUE obj, shape_id_t target_shape_id)
{
RUBY_ASSERT(!SPECIAL_CONST_P(obj));
- RUBY_ASSERT(RSHAPE(target_shape_id)->type == SHAPE_IVAR || RSHAPE(target_shape_id)->type == SHAPE_OBJ_ID);
+ RUBY_ASSERT(RSHAPE_TYPE_P(target_shape_id, SHAPE_IVAR) || RSHAPE_TYPE_P(target_shape_id, SHAPE_OBJ_ID));
+
+ if (BUILTIN_TYPE(obj) == T_CLASS || BUILTIN_TYPE(obj) == T_MODULE) {
+ ASSERT_vm_locking();
+ VALUE field_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj);
+ if (field_obj) {
+ return rb_obj_field_get(field_obj, target_shape_id);
+ }
+ return Qundef;
+ }
if (rb_shape_too_complex_p(target_shape_id)) {
st_table *fields_hash;
switch (BUILTIN_TYPE(obj)) {
case T_CLASS:
case T_MODULE:
- ASSERT_vm_locking();
- fields_hash = RCLASS_FIELDS_HASH(obj);
+ rb_bug("Unreachable");
break;
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));
- 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;
+ RUBY_ASSERT(rb_obj_exivar_p(obj));
+ 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;
- st_lookup(fields_hash, RSHAPE(target_shape_id)->edge_name, &value);
+ st_lookup(fields_hash, RSHAPE_EDGE_NAME(target_shape_id), &value);
#if RUBY_DEBUG
if (UNDEF_P(value)) {
@@ -1337,23 +1315,26 @@ rb_obj_field_get(VALUE obj, shape_id_t target_shape_id)
return value;
}
- attr_index_t attr_index = RSHAPE(target_shape_id)->next_field_index - 1;
+ attr_index_t attr_index = RSHAPE_INDEX(target_shape_id);
VALUE *fields;
switch (BUILTIN_TYPE(obj)) {
case T_CLASS:
case T_MODULE:
- ASSERT_vm_locking();
- fields = RCLASS_PRIME_FIELDS(obj);
+ rb_bug("Unreachable");
break;
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));
- 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;
+ RUBY_ASSERT(rb_obj_exivar_p(obj));
+ 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];
@@ -1365,43 +1346,19 @@ rb_ivar_lookup(VALUE obj, ID id, VALUE undef)
if (SPECIAL_CONST_P(obj)) return undef;
shape_id_t shape_id;
- VALUE * ivar_list;
- shape_id = RBASIC_SHAPE_ID(obj);
+ VALUE *ivar_list;
switch (BUILTIN_TYPE(obj)) {
case T_CLASS:
case T_MODULE:
{
- bool found = false;
- VALUE val;
-
- RB_VM_LOCKING() {
- if (rb_shape_too_complex_p(shape_id)) {
- st_table * iv_table = RCLASS_FIELDS_HASH(obj);
- if (rb_st_lookup(iv_table, (st_data_t)id, (st_data_t *)&val)) {
- found = true;
- }
- else {
- val = undef;
- }
- }
- else {
- attr_index_t index = 0;
- found = rb_shape_get_iv_index(shape_id, id, &index);
-
- if (found) {
- ivar_list = RCLASS_PRIME_FIELDS(obj);
- RUBY_ASSERT(ivar_list);
-
- val = ivar_list[index];
- }
- else {
- val = undef;
- }
- }
+ VALUE val = undef;
+ VALUE fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj);
+ if (fields_obj) {
+ val = rb_ivar_lookup(fields_obj, id, undef);
}
- if (found &&
+ if (val != undef &&
rb_is_instance_id(id) &&
UNLIKELY(!rb_ractor_main_p()) &&
!rb_ractor_shareable_p(val)) {
@@ -1410,10 +1367,32 @@ rb_ivar_lookup(VALUE obj, ID id, VALUE undef)
}
return val;
}
+ case T_IMEMO:
+ // Handled like T_OBJECT
+ {
+ 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_fields_complex_tbl(obj);
+ VALUE val;
+ if (rb_st_lookup(iv_table, (st_data_t)id, (st_data_t *)&val)) {
+ return val;
+ }
+ else {
+ return undef;
+ }
+ }
+
+ RUBY_ASSERT(!rb_shape_obj_too_complex_p(obj));
+ ivar_list = rb_imemo_fields_ptr(obj);
+ break;
+ }
case T_OBJECT:
{
+ shape_id = RBASIC_SHAPE_ID(obj);
if (rb_shape_too_complex_p(shape_id)) {
- st_table * iv_table = ROBJECT_FIELDS_HASH(obj);
+ st_table *iv_table = ROBJECT_FIELDS_HASH(obj);
VALUE val;
if (rb_st_lookup(iv_table, (st_data_t)id, (st_data_t *)&val)) {
return val;
@@ -1428,20 +1407,23 @@ rb_ivar_lookup(VALUE obj, ID id, VALUE undef)
break;
}
default:
- if (FL_TEST_RAW(obj, FL_EXIVAR)) {
- struct gen_fields_tbl *fields_tbl;
- rb_gen_fields_tbl_get(obj, id, &fields_tbl);
+ shape_id = RBASIC_SHAPE_ID(obj);
+ if (rb_obj_exivar_p(obj)) {
+ 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;
@@ -1476,13 +1458,22 @@ rb_ivar_delete(VALUE obj, ID id, VALUE undef)
{
rb_check_frozen(obj);
- bool locked = false;
- unsigned int lev = 0;
VALUE val = undef;
if (BUILTIN_TYPE(obj) == T_CLASS || BUILTIN_TYPE(obj) == T_MODULE) {
IVAR_ACCESSOR_SHOULD_BE_MAIN_RACTOR(id);
- RB_VM_LOCK_ENTER_LEV(&lev);
- locked = true;
+
+ VALUE fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj);
+ if (fields_obj) {
+ if (rb_multi_ractor_p()) {
+ fields_obj = rb_imemo_fields_clone(fields_obj);
+ val = rb_ivar_delete(fields_obj, id, undef);
+ RCLASS_WRITABLE_SET_FIELDS_OBJ(obj, fields_obj);
+ }
+ else {
+ val = rb_ivar_delete(fields_obj, id, undef);
+ }
+ }
+ return val;
}
shape_id_t old_shape_id = rb_obj_shape_id(obj);
@@ -1494,9 +1485,6 @@ rb_ivar_delete(VALUE obj, ID id, VALUE undef)
shape_id_t next_shape_id = rb_shape_transition_remove_ivar(obj, id, &removed_shape_id);
if (next_shape_id == old_shape_id) {
- if (locked) {
- RB_VM_LOCK_LEAVE_LEV(&lev);
- }
return undef;
}
@@ -1505,34 +1493,43 @@ rb_ivar_delete(VALUE obj, ID id, VALUE undef)
goto too_complex;
}
- RUBY_ASSERT(RSHAPE(next_shape_id)->next_field_index == RSHAPE(old_shape_id)->next_field_index - 1);
+ RUBY_ASSERT(RSHAPE_LEN(next_shape_id) == RSHAPE_LEN(old_shape_id) - 1);
VALUE *fields;
switch(BUILTIN_TYPE(obj)) {
case T_CLASS:
case T_MODULE:
- fields = RCLASS_PRIME_FIELDS(obj);
+ rb_bug("Unreachable");
+ break;
+ case T_IMEMO:
+ 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;
}
}
RUBY_ASSERT(removed_shape_id != INVALID_SHAPE_ID);
- attr_index_t new_fields_count = RSHAPE(next_shape_id)->next_field_index;
-
- attr_index_t removed_index = RSHAPE(removed_shape_id)->next_field_index - 1;
+ attr_index_t removed_index = RSHAPE_INDEX(removed_shape_id);
val = fields[removed_index];
- size_t trailing_fields = new_fields_count - removed_index;
- MEMMOVE(&fields[removed_index], &fields[removed_index + 1], VALUE, trailing_fields);
+ attr_index_t new_fields_count = RSHAPE_LEN(next_shape_id);
+ if (new_fields_count) {
+ size_t trailing_fields = new_fields_count - removed_index;
+
+ MEMMOVE(&fields[removed_index], &fields[removed_index + 1], VALUE, trailing_fields);
+ }
+ else {
+ rb_free_generic_ivar(obj);
+ }
if (RB_TYPE_P(obj, T_OBJECT) &&
!RB_FL_TEST_RAW(obj, ROBJECT_EMBED) &&
@@ -1546,10 +1543,6 @@ rb_ivar_delete(VALUE obj, ID id, VALUE undef)
}
rb_obj_set_shape_id(obj, next_shape_id);
- if (locked) {
- RB_VM_LOCK_LEAVE_LEV(&lev);
- }
-
return val;
too_complex:
@@ -1558,7 +1551,12 @@ too_complex:
switch (BUILTIN_TYPE(obj)) {
case T_CLASS:
case T_MODULE:
- table = RCLASS_WRITABLE_FIELDS_HASH(obj);
+ rb_bug("Unreachable");
+ break;
+
+ case T_IMEMO:
+ RUBY_ASSERT(IMEMO_TYPE_P(obj, imemo_fields));
+ table = rb_imemo_fields_complex_tbl(obj);
break;
case T_OBJECT:
@@ -1566,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;
}
@@ -1581,10 +1579,6 @@ too_complex:
}
}
- if (locked) {
- RB_VM_LOCK_LEAVE_LEV(&lev);
- }
-
return val;
}
@@ -1594,54 +1588,49 @@ 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)
{
+ if (BUILTIN_TYPE(obj) == T_CLASS || BUILTIN_TYPE(obj) == T_MODULE) {
+ return obj_transition_too_complex(RCLASS_WRITABLE_ENSURE_FIELDS_OBJ(obj), 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:
- old_fields = RCLASS_PRIME_FIELDS(obj);
- RBASIC_SET_SHAPE_ID(obj, shape_id);
- RCLASS_SET_FIELDS_HASH(obj, table);
+ 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;
}
@@ -1656,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)));
@@ -1792,138 +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));
+
+ 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;
+ }
+
+ attr_index_t index;
+ if (!rb_shape_get_iv_index(current_shape_id, id, &index)) {
+ existing = false;
- 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;
+ index = RSHAPE_LEN(current_shape_id);
+ if (index >= SHAPE_MAX_FIELDS) {
+ rb_raise(rb_eArgError, "too many instance variables");
+ }
- if (!existing || fields_lookup->resize) {
- if (existing) {
- RUBY_ASSERT(RSHAPE(fields_lookup->shape_id)->type == SHAPE_IVAR || RSHAPE(fields_lookup->shape_id)->type == SHAPE_OBJ_ID);
- RUBY_ASSERT(RSHAPE_CAPACITY(RSHAPE(fields_lookup->shape_id)->parent_id) < RSHAPE_CAPACITY(fields_lookup->shape_id));
+ 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;
}
- else {
- FL_SET_RAW((VALUE)*k, FL_EXIVAR);
+
+ 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]);
+ }
+ }
}
- fields_tbl = gen_fields_tbl_resize(fields_tbl, RSHAPE_CAPACITY(fields_lookup->shape_id));
- *v = (st_data_t)fields_tbl;
+ RUBY_ASSERT(RSHAPE(next_shape_id)->type == SHAPE_IVAR);
+ RUBY_ASSERT(index == (RSHAPE_LEN(next_shape_id) - 1));
}
- RUBY_ASSERT(FL_TEST((VALUE)*k, FL_EXIVAR));
+ VALUE *fields = rb_imemo_fields_ptr(fields_obj);
+ RB_OBJ_WRITE(fields_obj, &fields[index], val);
- fields_lookup->fields_tbl = fields_tbl;
- if (fields_lookup->shape_id) {
- rb_obj_set_shape_id(fields_lookup->obj, fields_lookup->shape_id);
+ if (!existing) {
+ RBASIC_SET_SHAPE_ID(fields_obj, next_shape_id);
}
- return ST_CONTINUE;
-}
+ generic_update_fields_obj(obj, fields_obj, original_fields_obj);
-static VALUE *
-generic_ivar_set_shape_fields(VALUE obj, void *data)
-{
- RUBY_ASSERT(!rb_shape_obj_too_complex_p(obj));
+ if (!existing) {
+ RBASIC_SET_SHAPE_ID(obj, next_shape_id);
+ }
- struct gen_fields_lookup_ensure_size *fields_lookup = data;
+ RUBY_ASSERT(RBASIC_SHAPE_ID(obj) == RBASIC_SHAPE_ID(fields_obj));
- 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);
- }
+ return;
+
+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;
+ bool existing = true;
- fields_lookup->shape_id = shape_id;
-}
-
-static shape_id_t
-generic_ivar_set_transition_too_complex(VALUE obj, void *_data)
-{
- shape_id_t new_shape_id = rb_evict_fields_to_hash(obj);
- FL_SET_RAW(obj, FL_EXIVAR);
- 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
@@ -2035,11 +2060,20 @@ rb_vm_set_ivar_id(VALUE obj, ID id, VALUE val)
bool
rb_obj_set_shape_id(VALUE obj, shape_id_t shape_id)
{
- if (rb_obj_shape_id(obj) == shape_id) {
+ shape_id_t old_shape_id = rb_obj_shape_id(obj);
+ if (old_shape_id == shape_id) {
return false;
}
+ if (BUILTIN_TYPE(obj) == T_CLASS || BUILTIN_TYPE(obj) == T_MODULE) {
+ // Avoid creating the fields_obj just to freeze the class
+ if (!(shape_id == SPECIAL_CONST_SHAPE_ID && old_shape_id == ROOT_SHAPE_ID)) {
+ RBASIC_SET_SHAPE_ID(RCLASS_WRITABLE_ENSURE_FIELDS_OBJ(obj), shape_id);
+ }
+ }
+ // FIXME: How to do multi-shape?
RBASIC_SET_SHAPE_ID(obj, shape_id);
+
return true;
}
@@ -2100,8 +2134,6 @@ rb_ivar_set_internal(VALUE obj, ID id, VALUE val)
ivar_set(obj, id, val);
}
-static void class_field_set(VALUE obj, shape_id_t target_shape_id, VALUE val);
-
void
rb_obj_field_set(VALUE obj, shape_id_t target_shape_id, VALUE val)
{
@@ -2111,8 +2143,8 @@ rb_obj_field_set(VALUE obj, shape_id_t target_shape_id, VALUE val)
break;
case T_CLASS:
case T_MODULE:
- ASSERT_vm_locking();
- class_field_set(obj, target_shape_id, val);
+ // The only field is object_id and T_CLASS handle it differently.
+ rb_bug("Unreachable");
break;
default:
generic_field_set(obj, target_shape_id, val);
@@ -2131,7 +2163,12 @@ ivar_defined0(VALUE obj, ID id)
switch (BUILTIN_TYPE(obj)) {
case T_CLASS:
case T_MODULE:
- table = (st_table *)RCLASS_FIELDS_HASH(obj);
+ rb_bug("Unreachable");
+ break;
+
+ case T_IMEMO:
+ RUBY_ASSERT(IMEMO_TYPE_P(obj, imemo_fields));
+ table = rb_imemo_fields_complex_tbl(obj);
break;
case T_OBJECT:
@@ -2139,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;
}
}
@@ -2163,12 +2199,15 @@ rb_ivar_defined(VALUE obj, ID id)
{
if (SPECIAL_CONST_P(obj)) return Qfalse;
- VALUE defined;
+ VALUE defined = Qfalse;
switch (BUILTIN_TYPE(obj)) {
case T_CLASS:
case T_MODULE:
- RB_VM_LOCKING() {
- defined = ivar_defined0(obj, id);
+ {
+ VALUE fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj);
+ if (fields_obj) {
+ defined = ivar_defined0(fields_obj, id);
+ }
}
break;
default:
@@ -2183,60 +2222,46 @@ struct iv_itr_data {
struct gen_fields_tbl *fields_tbl;
st_data_t arg;
rb_ivar_foreach_callback_func *func;
+ VALUE *fields;
bool ivar_only;
};
-/*
- * Returns a flag to stop iterating depending on the result of +callback+.
- */
-static bool
-iterate_over_shapes_with_callback(rb_shape_t *shape, rb_ivar_foreach_callback_func *callback, struct iv_itr_data *itr_data)
+static int
+iterate_over_shapes_callback(shape_id_t shape_id, void *data)
{
- switch ((enum shape_type)shape->type) {
- case SHAPE_ROOT:
- return false;
- case SHAPE_OBJ_ID:
- if (itr_data->ivar_only) {
- return iterate_over_shapes_with_callback(RSHAPE(shape->parent_id), callback, itr_data);
- }
- // fallthrough
- case SHAPE_IVAR:
- ASSUME(callback);
- if (iterate_over_shapes_with_callback(RSHAPE(shape->parent_id), callback, itr_data)) {
- return true;
- }
+ struct iv_itr_data *itr_data = data;
- VALUE * iv_list;
- 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);
- break;
- case T_CLASS:
- case T_MODULE:
- RUBY_ASSERT(!rb_shape_obj_too_complex_p(itr_data->obj));
- iv_list = RCLASS_PRIME_FIELDS(itr_data->obj);
- break;
- default:
- iv_list = itr_data->fields_tbl->as.shape.fields;
- break;
- }
- VALUE val = iv_list[shape->next_field_index - 1];
- if (!UNDEF_P(val)) {
- switch (callback(shape->edge_name, val, itr_data->arg)) {
- case ST_CHECK:
- case ST_CONTINUE:
- break;
- case ST_STOP:
- return true;
- default:
- rb_bug("unreachable");
- }
- }
- return false;
+ if (itr_data->ivar_only && !RSHAPE_TYPE_P(shape_id, SHAPE_IVAR)) {
+ return ST_CONTINUE;
+ }
+
+ VALUE *fields;
+ switch (BUILTIN_TYPE(itr_data->obj)) {
+ case T_OBJECT:
+ RUBY_ASSERT(!rb_shape_obj_too_complex_p(itr_data->obj));
+ fields = ROBJECT_FIELDS(itr_data->obj);
+ break;
+ case T_IMEMO:
+ RUBY_ASSERT(IMEMO_TYPE_P(itr_data->obj, imemo_fields));
+ RUBY_ASSERT(!rb_shape_obj_too_complex_p(itr_data->obj));
+
+ fields = rb_imemo_fields_ptr(itr_data->obj);
+ break;
default:
- UNREACHABLE_RETURN(false);
+ rb_bug("Unreachable");
}
+
+ VALUE val = fields[RSHAPE_INDEX(shape_id)];
+ return itr_data->func(RSHAPE_EDGE_NAME(shape_id), val, itr_data->arg);
+}
+
+/*
+ * Returns a flag to stop iterating depending on the result of +callback+.
+ */
+static void
+iterate_over_shapes(shape_id_t shape_id, rb_ivar_foreach_callback_func *callback, struct iv_itr_data *itr_data)
+{
+ rb_shape_foreach_field(shape_id, iterate_over_shapes_callback, itr_data);
}
static int
@@ -2262,81 +2287,55 @@ obj_fields_each(VALUE obj, rb_ivar_foreach_callback_func *func, st_data_t arg, b
rb_st_foreach(ROBJECT_FIELDS_HASH(obj), each_hash_iv, (st_data_t)&itr_data);
}
else {
- iterate_over_shapes_with_callback(RSHAPE(shape_id), func, &itr_data);
+ itr_data.fields = ROBJECT_FIELDS(obj);
+ iterate_over_shapes(shape_id, func, &itr_data);
}
}
static void
-gen_fields_each(VALUE 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)
{
- 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 {
- iterate_over_shapes_with_callback(RSHAPE(shape_id), func, &itr_data);
- }
-}
-
-static void
-class_fields_each(VALUE obj, rb_ivar_foreach_callback_func *func, st_data_t arg, bool ivar_only)
-{
- RUBY_ASSERT(RB_TYPE_P(obj, T_CLASS) || RB_TYPE_P(obj, T_MODULE));
+ IMEMO_TYPE_P(fields_obj, imemo_fields);
struct iv_itr_data itr_data = {
- .obj = obj,
+ .obj = fields_obj,
.arg = arg,
.func = func,
.ivar_only = ivar_only,
};
- shape_id_t shape_id = RBASIC_SHAPE_ID(obj);
+ shape_id_t shape_id = RBASIC_SHAPE_ID(fields_obj);
if (rb_shape_too_complex_p(shape_id)) {
- rb_st_foreach(RCLASS_WRITABLE_FIELDS_HASH(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 {
- iterate_over_shapes_with_callback(RSHAPE(shape_id), func, &itr_data);
+ itr_data.fields = rb_imemo_fields_ptr(fields_obj);
+ iterate_over_shapes(shape_id, func, &itr_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);
- if (!FL_TEST(obj, FL_EXIVAR)) {
- goto clear;
- }
-
- unsigned long src_num_ivs = rb_ivar_count(obj);
- if (!src_num_ivs) {
- goto clear;
+ if (!rb_obj_exivar_p(obj)) {
+ return;
}
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;
}
@@ -2344,64 +2343,49 @@ rb_copy_generic_ivar(VALUE dest, VALUE obj)
shape_id_t initial_shape_id = rb_obj_shape_id(dest);
if (!rb_shape_canonical_p(src_shape_id)) {
- RUBY_ASSERT(RSHAPE(initial_shape_id)->type == SHAPE_ROOT);
+ RUBY_ASSERT(RSHAPE_TYPE_P(initial_shape_id, SHAPE_ROOT));
dest_shape_id = rb_shape_rebuild(initial_shape_id, src_shape_id);
if (UNLIKELY(rb_shape_too_complex_p(dest_shape_id))) {
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;
clear:
- if (FL_TEST(dest, FL_EXIVAR)) {
- RBASIC_SET_SHAPE_ID(dest, ROOT_SHAPE_ID);
- rb_free_generic_ivar(dest);
- FL_UNSET(dest, FL_EXIVAR);
- }
+ rb_free_generic_ivar(dest);
}
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");
@@ -2414,19 +2398,30 @@ 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_fields)) {
+ imemo_fields_each(obj, func, arg, ivar_only);
+ }
+ break;
case T_OBJECT:
obj_fields_each(obj, func, arg, ivar_only);
break;
case T_CLASS:
case T_MODULE:
- IVAR_ACCESSOR_SHOULD_BE_MAIN_RACTOR(0);
- RB_VM_LOCKING() {
- class_fields_each(obj, func, arg, ivar_only);
+ {
+ IVAR_ACCESSOR_SHOULD_BE_MAIN_RACTOR(0);
+ VALUE fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj);
+ if (fields_obj) {
+ imemo_fields_each(fields_obj, func, arg, ivar_only);
+ }
}
break;
default:
- if (FL_TEST(obj, FL_EXIVAR)) {
- gen_fields_each(obj, func, arg, ivar_only);
+ if (rb_obj_exivar_p(obj)) {
+ 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;
}
@@ -2448,16 +2443,46 @@ rb_ivar_count(VALUE obj)
case T_OBJECT:
iv_count = ROBJECT_FIELDS_COUNT(obj);
break;
+
case T_CLASS:
case T_MODULE:
- iv_count = RCLASS_FIELDS_COUNT(obj);
+ {
+ VALUE fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj);
+ if (!fields_obj) {
+ return 0;
+ }
+ if (rb_shape_obj_too_complex_p(fields_obj)) {
+ iv_count = rb_st_table_size(rb_imemo_fields_complex_tbl(fields_obj));
+ }
+ else {
+ iv_count = RBASIC_FIELDS_COUNT(fields_obj);
+ }
+ }
+ break;
+
+ case T_IMEMO:
+ RUBY_ASSERT(IMEMO_TYPE_P(obj, imemo_fields));
+
+ 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 (FL_TEST(obj, FL_EXIVAR)) {
- struct gen_fields_tbl *fields_tbl;
+ if (rb_obj_exivar_p(obj)) {
+
+ if (rb_shape_obj_too_complex_p(obj)) {
+ VALUE fields_obj;
- if (rb_gen_fields_tbl_get(obj, 0, &fields_tbl)) {
- iv_count = gen_fields_tbl_count(obj, fields_tbl);
+ 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;
@@ -4657,71 +4682,112 @@ rb_iv_set(VALUE obj, const char *name, VALUE val)
return rb_ivar_set(obj, id, val);
}
-static VALUE *
-class_ivar_set_shape_fields(VALUE obj, void *_data)
+static bool
+class_fields_ivar_set(VALUE klass, VALUE fields_obj, ID id, VALUE val, bool concurrent, VALUE *new_fields_obj)
{
- RUBY_ASSERT(!rb_shape_obj_too_complex_p(obj));
+ bool existing = true;
+ const VALUE original_fields_obj = fields_obj;
+ fields_obj = original_fields_obj ? original_fields_obj : rb_imemo_fields_new(rb_singleton_class(klass), 1);
- return RCLASS_PRIME_FIELDS(obj);
-}
+ shape_id_t current_shape_id = RBASIC_SHAPE_ID(fields_obj);
+ shape_id_t next_shape_id = current_shape_id;
-static void
-class_ivar_set_shape_resize_fields(VALUE obj, attr_index_t _old_capa, attr_index_t new_capa, void *_data)
-{
- REALLOC_N(RCLASS_PRIME_FIELDS(obj), VALUE, new_capa);
-}
+ if (UNLIKELY(rb_shape_too_complex_p(current_shape_id))) {
+ goto too_complex;
+ }
-static void
-class_ivar_set_set_shape_id(VALUE obj, shape_id_t shape_id, void *_data)
-{
- rb_obj_set_shape_id(obj, shape_id);
-}
+ attr_index_t index;
+ if (!rb_shape_get_iv_index(current_shape_id, id, &index)) {
+ existing = false;
-static shape_id_t
-class_ivar_set_transition_too_complex(VALUE obj, void *_data)
-{
- return rb_evict_fields_to_hash(obj);
-}
+ index = RSHAPE_LEN(current_shape_id);
+ if (index >= SHAPE_MAX_FIELDS) {
+ rb_raise(rb_eArgError, "too many instance variables");
+ }
-static st_table *
-class_ivar_set_too_complex_table(VALUE obj, void *_data)
-{
- RUBY_ASSERT(rb_shape_obj_too_complex_p(obj));
+ 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_singleton_class(klass), 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;
+ }
+
+ attr_index_t next_capacity = RSHAPE_CAPACITY(next_shape_id);
+ attr_index_t current_capacity = RSHAPE_CAPACITY(current_shape_id);
+
+ if (concurrent || next_capacity != current_capacity) {
+ RUBY_ASSERT(concurrent || next_capacity > current_capacity);
+
+ // 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_fields_new(rb_singleton_class(klass), next_capacity);
+ if (original_fields_obj) {
+ VALUE *fields = rb_imemo_fields_ptr(fields_obj);
+ attr_index_t fields_count = RSHAPE_LEN(current_shape_id);
+ 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));
+ }
+
+ 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);
+ }
+
+ *new_fields_obj = fields_obj;
+ return existing;
+
+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);
+
+ if (fields_obj != original_fields_obj) {
+ RBASIC_SET_SHAPE_ID(fields_obj, next_shape_id);
+ }
+ }
- return RCLASS_WRITABLE_FIELDS_HASH(obj);
+ *new_fields_obj = fields_obj;
+ return existing;
}
int
rb_class_ivar_set(VALUE obj, ID id, VALUE val)
{
RUBY_ASSERT(RB_TYPE_P(obj, T_CLASS) || RB_TYPE_P(obj, T_MODULE));
- bool existing = false;
rb_check_frozen(obj);
rb_class_ensure_writable(obj);
- RB_VM_LOCKING() {
- existing = general_ivar_set(obj, id, val, NULL,
- class_ivar_set_shape_fields,
- class_ivar_set_shape_resize_fields,
- class_ivar_set_set_shape_id,
- class_ivar_set_transition_too_complex,
- class_ivar_set_too_complex_table).existing;
+ const VALUE original_fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj);
+ VALUE new_fields_obj = 0;
+
+ bool existing = class_fields_ivar_set(obj, original_fields_obj, id, val, rb_multi_ractor_p(), &new_fields_obj);
+
+ if (new_fields_obj != original_fields_obj) {
+ RCLASS_WRITABLE_SET_FIELDS_OBJ(obj, new_fields_obj);
}
- return existing;
-}
+ // 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));
-static void
-class_field_set(VALUE obj, shape_id_t target_shape_id, VALUE val)
-{
- RUBY_ASSERT(RB_TYPE_P(obj, T_CLASS) || RB_TYPE_P(obj, T_MODULE));
- general_field_set(obj, target_shape_id, val, NULL,
- class_ivar_set_shape_fields,
- class_ivar_set_shape_resize_fields,
- class_ivar_set_set_shape_id,
- class_ivar_set_transition_too_complex,
- class_ivar_set_too_complex_table);
+ return existing;
}
static int
@@ -4737,9 +4803,7 @@ rb_fields_tbl_copy(VALUE dst, VALUE src)
{
RUBY_ASSERT(rb_type(dst) == rb_type(src));
RUBY_ASSERT(RB_TYPE_P(dst, T_CLASS) || RB_TYPE_P(dst, T_MODULE));
-
RUBY_ASSERT(RSHAPE_TYPE_P(RBASIC_SHAPE_ID(dst), SHAPE_ROOT));
- RUBY_ASSERT(!RCLASS_PRIME_FIELDS(dst));
rb_ivar_foreach(src, tbl_copy_i, dst);
}
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);
diff --git a/vm.c b/vm.c
index 4b254eaea1..a8822239cf 100644
--- a/vm.c
+++ b/vm.c
@@ -736,8 +736,8 @@ vm_stat(int argc, VALUE *argv, VALUE self)
SET(constant_cache_invalidations, ruby_vm_constant_cache_invalidations);
SET(constant_cache_misses, ruby_vm_constant_cache_misses);
SET(global_cvar_state, ruby_vm_global_cvar_state);
- SET(next_shape_id, (rb_serial_t)GET_SHAPE_TREE()->next_shape_id);
- SET(shape_cache_size, (rb_serial_t)GET_SHAPE_TREE()->cache_size);
+ SET(next_shape_id, (rb_serial_t)rb_shape_tree.next_shape_id);
+ SET(shape_cache_size, (rb_serial_t)rb_shape_tree.cache_size);
#undef SET
#if USE_DEBUG_COUNTER
@@ -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_backtrace.c b/vm_backtrace.c
index 9046f4aa29..68fc2b987b 100644
--- a/vm_backtrace.c
+++ b/vm_backtrace.c
@@ -262,6 +262,15 @@ retry:
}
}
+static bool
+is_internal_location(const rb_iseq_t *iseq)
+{
+ static const char prefix[] = "<internal:";
+ const size_t prefix_len = sizeof(prefix) - 1;
+ VALUE file = rb_iseq_path(iseq);
+ return strncmp(prefix, RSTRING_PTR(file), prefix_len) == 0;
+}
+
// Return true if a given location is a C method or supposed to behave like one.
static inline bool
location_cfunc_p(rb_backtrace_location_t *loc)
@@ -272,7 +281,7 @@ location_cfunc_p(rb_backtrace_location_t *loc)
case VM_METHOD_TYPE_CFUNC:
return true;
case VM_METHOD_TYPE_ISEQ:
- return rb_iseq_attr_p(loc->cme->def->body.iseq.iseqptr, BUILTIN_ATTR_C_TRACE);
+ return is_internal_location(loc->cme->def->body.iseq.iseqptr);
default:
return false;
}
@@ -605,15 +614,6 @@ backtrace_size(const rb_execution_context_t *ec)
}
static bool
-is_internal_location(const rb_control_frame_t *cfp)
-{
- static const char prefix[] = "<internal:";
- const size_t prefix_len = sizeof(prefix) - 1;
- VALUE file = rb_iseq_path(cfp->iseq);
- return strncmp(prefix, RSTRING_PTR(file), prefix_len) == 0;
-}
-
-static bool
is_rescue_or_ensure_frame(const rb_control_frame_t *cfp)
{
enum rb_iseq_type type = ISEQ_BODY(cfp->iseq)->type;
@@ -621,11 +621,11 @@ is_rescue_or_ensure_frame(const rb_control_frame_t *cfp)
}
static void
-bt_update_cfunc_loc(unsigned long cfunc_counter, rb_backtrace_location_t *cfunc_loc, const rb_iseq_t *iseq, const VALUE *pc)
+bt_backpatch_loc(unsigned long backpatch_counter, rb_backtrace_location_t *loc, const rb_iseq_t *iseq, const VALUE *pc)
{
- for (; cfunc_counter > 0; cfunc_counter--, cfunc_loc--) {
- cfunc_loc->iseq = iseq;
- cfunc_loc->pc = pc;
+ for (; backpatch_counter > 0; backpatch_counter--, loc--) {
+ loc->iseq = iseq;
+ loc->pc = pc;
}
}
@@ -648,7 +648,7 @@ rb_ec_partial_backtrace_object(const rb_execution_context_t *ec, long start_fram
rb_backtrace_t *bt = NULL;
VALUE btobj = Qnil;
rb_backtrace_location_t *loc = NULL;
- unsigned long cfunc_counter = 0;
+ unsigned long backpatch_counter = 0;
bool skip_next_frame = FALSE;
// In the case the thread vm_stack or cfp is not initialized, there is no backtrace.
@@ -691,26 +691,36 @@ rb_ec_partial_backtrace_object(const rb_execution_context_t *ec, long start_fram
if (start_frame > 0) {
start_frame--;
}
- else if (!(skip_internal && is_internal_location(cfp))) {
+ else {
+ bool internal = is_internal_location(cfp->iseq);
+ if (skip_internal && internal) continue;
if (!skip_next_frame) {
const rb_iseq_t *iseq = cfp->iseq;
const VALUE *pc = cfp->pc;
+ if (internal && backpatch_counter > 0) {
+ // To keep only one internal frame, discard the previous backpatch frames
+ bt->backtrace_size -= backpatch_counter;
+ backpatch_counter = 0;
+ }
loc = &bt->backtrace[bt->backtrace_size++];
RB_OBJ_WRITE(btobj, &loc->cme, rb_vm_frame_method_entry(cfp));
- // Ruby methods with `Primitive.attr! :c_trace` should behave like C methods
- if (rb_iseq_attr_p(cfp->iseq, BUILTIN_ATTR_C_TRACE)) {
- loc->iseq = NULL;
- loc->pc = NULL;
- cfunc_counter++;
+ // internal frames (`<internal:...>`) should behave like C methods
+ if (internal) {
+ // Typically, these iseq and pc are not needed because they will be backpatched later.
+ // But when the call stack starts with an internal frame (i.e., prelude.rb),
+ // they will be used to show the `<internal:...>` location.
+ RB_OBJ_WRITE(btobj, &loc->iseq, iseq);
+ loc->pc = pc;
+ backpatch_counter++;
}
else {
RB_OBJ_WRITE(btobj, &loc->iseq, iseq);
loc->pc = pc;
- bt_update_cfunc_loc(cfunc_counter, loc-1, iseq, pc);
+ bt_backpatch_loc(backpatch_counter, loc-1, iseq, pc);
if (do_yield) {
- bt_yield_loc(loc - cfunc_counter, cfunc_counter+1, btobj);
+ bt_yield_loc(loc - backpatch_counter, backpatch_counter+1, btobj);
}
- cfunc_counter = 0;
+ backpatch_counter = 0;
}
}
skip_next_frame = is_rescue_or_ensure_frame(cfp);
@@ -727,21 +737,21 @@ rb_ec_partial_backtrace_object(const rb_execution_context_t *ec, long start_fram
RB_OBJ_WRITE(btobj, &loc->cme, rb_vm_frame_method_entry(cfp));
loc->iseq = NULL;
loc->pc = NULL;
- cfunc_counter++;
+ backpatch_counter++;
}
}
}
// When a backtrace entry corresponds to a method defined in C (e.g. rb_define_method), the reported file:line
// is the one of the caller Ruby frame, so if the last entry is a C frame we find the caller Ruby frame here.
- if (cfunc_counter > 0) {
+ if (backpatch_counter > 0) {
for (; cfp != end_cfp; cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp)) {
- if (cfp->iseq && cfp->pc && !(skip_internal && is_internal_location(cfp))) {
+ if (cfp->iseq && cfp->pc && !(skip_internal && is_internal_location(cfp->iseq))) {
VM_ASSERT(!skip_next_frame); // ISEQ_TYPE_RESCUE/ISEQ_TYPE_ENSURE should have a caller Ruby ISEQ, not a cfunc
- bt_update_cfunc_loc(cfunc_counter, loc, cfp->iseq, cfp->pc);
+ bt_backpatch_loc(backpatch_counter, loc, cfp->iseq, cfp->pc);
RB_OBJ_WRITTEN(btobj, Qundef, cfp->iseq);
if (do_yield) {
- bt_yield_loc(loc - cfunc_counter, cfunc_counter, btobj);
+ bt_yield_loc(loc - backpatch_counter, backpatch_counter, btobj);
}
break;
}
diff --git a/vm_callinfo.h b/vm_callinfo.h
index d3d0555485..0ce25c2c0f 100644
--- a/vm_callinfo.h
+++ b/vm_callinfo.h
@@ -297,14 +297,13 @@ struct rb_callcache {
} aux_;
};
-#define VM_CALLCACHE_UNMARKABLE FL_FREEZE
-#define VM_CALLCACHE_ON_STACK FL_EXIVAR
-
/* VM_CALLCACHE_IVAR used for IVAR/ATTRSET/STRUCT_AREF/STRUCT_ASET methods */
#define VM_CALLCACHE_IVAR IMEMO_FL_USER0
#define VM_CALLCACHE_BF IMEMO_FL_USER1
#define VM_CALLCACHE_SUPER IMEMO_FL_USER2
#define VM_CALLCACHE_REFINEMENT IMEMO_FL_USER3
+#define VM_CALLCACHE_UNMARKABLE IMEMO_FL_USER4
+#define VM_CALLCACHE_ON_STACK IMEMO_FL_USER5
enum vm_cc_type {
cc_type_normal, // chained from ccs
diff --git a/vm_insnhelper.c b/vm_insnhelper.c
index 24709eee2e..2fe5e26928 100644
--- a/vm_insnhelper.c
+++ b/vm_insnhelper.c
@@ -1213,9 +1213,10 @@ ALWAYS_INLINE(static VALUE vm_getivar(VALUE, ID, const rb_iseq_t *, IVC, const s
static inline VALUE
vm_getivar(VALUE obj, ID id, const rb_iseq_t *iseq, IVC ic, const struct rb_callcache *cc, int is_attr, VALUE default_value)
{
+ VALUE fields_obj;
#if OPT_IC_FOR_IVAR
VALUE val = Qundef;
- VALUE * ivar_list;
+ VALUE *ivar_list;
if (SPECIAL_CONST_P(obj)) {
return default_value;
@@ -1247,14 +1248,22 @@ vm_getivar(VALUE obj, ID id, const rb_iseq_t *iseq, IVC ic, const struct rb_call
}
}
- ivar_list = RCLASS_PRIME_FIELDS(obj);
+ fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj);
+ if (!fields_obj) {
+ return default_value;
+ }
+ ivar_list = rb_imemo_fields_ptr(fields_obj);
+ shape_id = RBASIC_SHAPE_ID_FOR_READ(fields_obj);
+
break;
}
default:
- if (FL_TEST_RAW(obj, FL_EXIVAR)) {
- struct gen_fields_tbl *fields_tbl;
- rb_gen_fields_tbl_get(obj, id, &fields_tbl);
- ivar_list = fields_tbl->as.shape.fields;
+ if (rb_obj_exivar_p(obj)) {
+ 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;
@@ -1318,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 = (st_table *)RCLASS_FIELDS_HASH(obj);
+ table = rb_imemo_fields_complex_tbl(fields_obj);
break;
case T_OBJECT:
@@ -1326,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;
}
@@ -1374,6 +1383,7 @@ vm_getivar(VALUE obj, ID id, const rb_iseq_t *iseq, IVC ic, const struct rb_call
RUBY_ASSERT(!UNDEF_P(val));
}
+ RB_GC_GUARD(fields_obj);
return val;
general_path:
@@ -1448,16 +1458,14 @@ 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) {
RUBY_ASSERT(dest_shape_id != INVALID_SHAPE_ID && shape_id != INVALID_SHAPE_ID);
}
else if (dest_shape_id != INVALID_SHAPE_ID) {
- rb_shape_t *dest_shape = RSHAPE(dest_shape_id);
-
- if (shape_id == dest_shape->parent_id && dest_shape->edge_name == id && RSHAPE_CAPACITY(shape_id) == RSHAPE_CAPACITY(dest_shape_id)) {
+ if (shape_id == RSHAPE_PARENT(dest_shape_id) && RSHAPE_EDGE_NAME(dest_shape_id) == id && RSHAPE_CAPACITY(shape_id) == RSHAPE_CAPACITY(dest_shape_id)) {
RUBY_ASSERT(index < RSHAPE_CAPACITY(dest_shape_id));
}
else {
@@ -1468,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);
@@ -1498,10 +1506,9 @@ vm_setivar(VALUE obj, ID id, VALUE val, shape_id_t dest_shape_id, attr_index_t i
VM_ASSERT(!rb_ractor_shareable_p(obj));
}
else if (dest_shape_id != INVALID_SHAPE_ID) {
- rb_shape_t *dest_shape = RSHAPE(dest_shape_id);
- shape_id_t source_shape_id = dest_shape->parent_id;
+ shape_id_t source_shape_id = RSHAPE_PARENT(dest_shape_id);
- if (shape_id == source_shape_id && dest_shape->edge_name == id && RSHAPE_CAPACITY(shape_id) == RSHAPE_CAPACITY(dest_shape_id)) {
+ if (shape_id == source_shape_id && RSHAPE_EDGE_NAME(dest_shape_id) == id && RSHAPE_CAPACITY(shape_id) == RSHAPE_CAPACITY(dest_shape_id)) {
RUBY_ASSERT(dest_shape_id != INVALID_SHAPE_ID && shape_id != INVALID_SHAPE_ID);
RBASIC_SET_SHAPE_ID(obj, dest_shape_id);
@@ -5550,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.c b/yjit.c
index 2c51e6bf92..ae042a62aa 100644
--- a/yjit.c
+++ b/yjit.c
@@ -778,7 +778,7 @@ VALUE
rb_object_shape_count(void)
{
// next_shape_id starts from 0, so it's the same as the count
- return ULONG2NUM((unsigned long)GET_SHAPE_TREE()->next_shape_id);
+ return ULONG2NUM((unsigned long)rb_shape_tree.next_shape_id);
}
bool
@@ -799,6 +799,12 @@ rb_yjit_shape_capacity(shape_id_t shape_id)
return RSHAPE_CAPACITY(shape_id);
}
+attr_index_t
+rb_yjit_shape_index(shape_id_t shape_id)
+{
+ return RSHAPE_INDEX(shape_id);
+}
+
// Assert that we have the VM lock. Relevant mostly for multi ractor situations.
// The GC takes the lock before calling us, and this asserts that it indeed happens.
void
diff --git a/yjit/bindgen/src/main.rs b/yjit/bindgen/src/main.rs
index a139892741..41d383f8bd 100644
--- a/yjit/bindgen/src/main.rs
+++ b/yjit/bindgen/src/main.rs
@@ -95,13 +95,13 @@ fn main() {
// From shape.h
.allowlist_function("rb_obj_shape_id")
- .allowlist_function("rb_shape_lookup")
.allowlist_function("rb_shape_id_offset")
.allowlist_function("rb_shape_get_iv_index")
.allowlist_function("rb_shape_transition_add_ivar_no_warnings")
.allowlist_function("rb_yjit_shape_obj_too_complex_p")
.allowlist_function("rb_yjit_shape_too_complex_p")
.allowlist_function("rb_yjit_shape_capacity")
+ .allowlist_function("rb_yjit_shape_index")
.allowlist_var("SHAPE_ID_NUM_BITS")
// From ruby/internal/intern/object.h
@@ -228,6 +228,7 @@ fn main() {
.allowlist_function("rb_obj_as_string_result")
.allowlist_function("rb_str_byte_substr")
.allowlist_function("rb_str_substr_two_fixnums")
+ .allowlist_function("rb_str_dup_m")
// From include/ruby/internal/intern/parse.h
.allowlist_function("rb_backref_get")
diff --git a/yjit/src/asm/x86_64/mod.rs b/yjit/src/asm/x86_64/mod.rs
index fbbfa714d8..0ef5e92117 100644
--- a/yjit/src/asm/x86_64/mod.rs
+++ b/yjit/src/asm/x86_64/mod.rs
@@ -1027,7 +1027,10 @@ pub fn mov(cb: &mut CodeBlock, dst: X86Opnd, src: X86Opnd) {
}
let output_num_bits:u32 = if mem.num_bits > 32 { 32 } else { mem.num_bits.into() };
- assert!(imm_num_bits(imm.value) <= (output_num_bits as u8));
+ assert!(
+ mem.num_bits < 64 || imm_num_bits(imm.value) <= (output_num_bits as u8),
+ "immediate value should be small enough to survive sign extension"
+ );
cb.write_int(imm.value as u64, output_num_bits);
},
// M + UImm
@@ -1042,7 +1045,10 @@ pub fn mov(cb: &mut CodeBlock, dst: X86Opnd, src: X86Opnd) {
}
let output_num_bits = if mem.num_bits > 32 { 32 } else { mem.num_bits.into() };
- assert!(imm_num_bits(uimm.value as i64) <= (output_num_bits as u8));
+ assert!(
+ mem.num_bits < 64 || imm_num_bits(uimm.value as i64) <= (output_num_bits as u8),
+ "immediate value should be small enough to survive sign extension"
+ );
cb.write_int(uimm.value, output_num_bits);
},
// * + Imm/UImm
diff --git a/yjit/src/asm/x86_64/tests.rs b/yjit/src/asm/x86_64/tests.rs
index 5ae983270f..eefcbfd52e 100644
--- a/yjit/src/asm/x86_64/tests.rs
+++ b/yjit/src/asm/x86_64/tests.rs
@@ -193,6 +193,7 @@ fn test_mov() {
check_bytes("48c7470801000000", |cb| mov(cb, mem_opnd(64, RDI, 8), imm_opnd(1)));
//check_bytes("67c7400411000000", |cb| mov(cb, mem_opnd(32, EAX, 4), imm_opnd(0x34))); // We don't distinguish between EAX and RAX here - that's probably fine?
check_bytes("c7400411000000", |cb| mov(cb, mem_opnd(32, RAX, 4), imm_opnd(17)));
+ check_bytes("c7400401000080", |cb| mov(cb, mem_opnd(32, RAX, 4), uimm_opnd(0x80000001)));
check_bytes("41895814", |cb| mov(cb, mem_opnd(32, R8, 20), EBX));
check_bytes("4d8913", |cb| mov(cb, mem_opnd(64, R11, 0), R10));
check_bytes("48c742f8f4ffffff", |cb| mov(cb, mem_opnd(64, RDX, -8), imm_opnd(-12)));
diff --git a/yjit/src/backend/x86_64/mod.rs b/yjit/src/backend/x86_64/mod.rs
index c0d42e79e6..ef435bca7e 100644
--- a/yjit/src/backend/x86_64/mod.rs
+++ b/yjit/src/backend/x86_64/mod.rs
@@ -315,19 +315,24 @@ impl Assembler
let opnd1 = asm.load(*src);
asm.mov(*dest, opnd1);
},
- (Opnd::Mem(_), Opnd::UImm(value)) => {
- // 32-bit values will be sign-extended
- if imm_num_bits(*value as i64) > 32 {
+ (Opnd::Mem(Mem { num_bits, .. }), Opnd::UImm(value)) => {
+ // For 64 bit destinations, 32-bit values will be sign-extended
+ if *num_bits == 64 && imm_num_bits(*value as i64) > 32 {
let opnd1 = asm.load(*src);
asm.mov(*dest, opnd1);
} else {
asm.mov(*dest, *src);
}
},
- (Opnd::Mem(_), Opnd::Imm(value)) => {
- if imm_num_bits(*value) > 32 {
+ (Opnd::Mem(Mem { num_bits, .. }), Opnd::Imm(value)) => {
+ // For 64 bit destinations, 32-bit values will be sign-extended
+ if *num_bits == 64 && imm_num_bits(*value) > 32 {
let opnd1 = asm.load(*src);
asm.mov(*dest, opnd1);
+ } else if uimm_num_bits(*value as u64) <= *num_bits {
+ // If the bit string is short enough for the destination, use the unsigned representation.
+ // Note that 64-bit and negative values are ruled out.
+ asm.mov(*dest, Opnd::UImm(*value as u64));
} else {
asm.mov(*dest, *src);
}
@@ -1317,4 +1322,19 @@ mod tests {
0x13: mov qword ptr [rbx], rax
"});
}
+
+ #[test]
+ fn test_mov_m32_imm32() {
+ let (mut asm, mut cb) = setup_asm();
+
+ let shape_opnd = Opnd::mem(32, C_RET_OPND, 0);
+ asm.mov(shape_opnd, Opnd::UImm(0x8000_0001));
+ asm.mov(shape_opnd, Opnd::Imm(0x8000_0001));
+
+ asm.compile_with_num_regs(&mut cb, 0);
+ assert_disasm!(cb, "c70001000080c70001000080", {"
+ 0x0: mov dword ptr [rax], 0x80000001
+ 0x6: mov dword ptr [rax], 0x80000001
+ "});
+ }
}
diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs
index 5f7d61f8b3..3e08857295 100644
--- a/yjit/src/codegen.rs
+++ b/yjit/src/codegen.rs
@@ -3128,8 +3128,6 @@ fn gen_set_ivar(
if new_shape_too_complex {
Some((next_shape_id, None, 0_usize))
} else {
- let current_shape = unsafe { rb_shape_lookup(current_shape_id) };
-
let current_capacity = unsafe { rb_yjit_shape_capacity(current_shape_id) };
let next_capacity = unsafe { rb_yjit_shape_capacity(next_shape_id) };
@@ -3138,7 +3136,7 @@ fn gen_set_ivar(
let needs_extension = next_capacity != current_capacity;
// We can write to the object, but we need to transition the shape
- let ivar_index = unsafe { (*current_shape).next_field_index } as usize;
+ let ivar_index = unsafe { rb_yjit_shape_index(next_shape_id) } as usize;
let needs_extension = if needs_extension {
Some((current_capacity, next_capacity))
@@ -6277,16 +6275,12 @@ fn jit_rb_str_dup(
jit_prepare_call_with_gc(jit, asm);
- // Check !FL_ANY_RAW(str, FL_EXIVAR), which is part of BARE_STRING_P.
let recv_opnd = asm.stack_pop(1);
let recv_opnd = asm.load(recv_opnd);
- let flags_opnd = Opnd::mem(64, recv_opnd, RUBY_OFFSET_RBASIC_FLAGS);
- asm.test(flags_opnd, Opnd::Imm(RUBY_FL_EXIVAR as i64));
- asm.jnz(Target::side_exit(Counter::send_str_dup_exivar));
// Call rb_str_dup
let stack_ret = asm.stack_push(Type::CString);
- let ret_opnd = asm.ccall(rb_str_dup as *const u8, vec![recv_opnd]);
+ let ret_opnd = asm.ccall(rb_str_dup_m as *const u8, vec![recv_opnd]);
asm.mov(stack_ret, ret_opnd);
true
diff --git a/yjit/src/cruby.rs b/yjit/src/cruby.rs
index ecb6475319..725a29fa70 100644
--- a/yjit/src/cruby.rs
+++ b/yjit/src/cruby.rs
@@ -448,18 +448,6 @@ impl VALUE {
unsafe { rb_obj_shape_id(self) }
}
- pub fn shape_of(self) -> *mut rb_shape {
- unsafe {
- let shape = rb_shape_lookup(self.shape_id_of());
-
- if shape.is_null() {
- panic!("Shape should not be null");
- } else {
- shape
- }
- }
- }
-
pub fn embedded_p(self) -> bool {
unsafe {
FL_TEST_RAW(self, VALUE(ROBJECT_EMBED as usize)) != VALUE(0)
diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs
index 23682ac63c..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,6 +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_fields: imemo_type = 14;
pub type imemo_type = u32;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
@@ -687,27 +689,6 @@ pub const VM_ENV_FLAG_ISOLATED: vm_frame_env_flags = 16;
pub type vm_frame_env_flags = u32;
pub type attr_index_t = u16;
pub type shape_id_t = u32;
-pub type redblack_id_t = u32;
-pub type redblack_node_t = redblack_node;
-#[repr(C)]
-pub struct rb_shape {
- pub edges: VALUE,
- pub edge_name: ID,
- pub ancestor_index: *mut redblack_node_t,
- pub parent_id: shape_id_t,
- pub next_field_index: attr_index_t,
- pub capacity: attr_index_t,
- pub type_: u8,
-}
-pub type rb_shape_t = rb_shape;
-#[repr(C)]
-#[derive(Debug, Copy, Clone)]
-pub struct redblack_node {
- pub key: ID,
- pub value: *mut rb_shape_t,
- pub l: redblack_id_t,
- pub r: redblack_id_t,
-}
#[repr(C)]
pub struct rb_cvar_class_tbl_entry {
pub index: u32,
@@ -1132,7 +1113,6 @@ extern "C" {
pub fn rb_obj_info(obj: VALUE) -> *const ::std::os::raw::c_char;
pub fn rb_ec_stack_check(ec: *mut rb_execution_context_struct) -> ::std::os::raw::c_int;
pub fn rb_shape_id_offset() -> i32;
- pub fn rb_shape_lookup(shape_id: shape_id_t) -> *mut rb_shape_t;
pub fn rb_obj_shape_id(obj: VALUE) -> shape_id_t;
pub fn rb_shape_get_iv_index(shape_id: shape_id_t, id: ID, value: *mut attr_index_t) -> bool;
pub fn rb_shape_transition_add_ivar_no_warnings(obj: VALUE, id: ID) -> shape_id_t;
@@ -1140,6 +1120,7 @@ extern "C" {
pub fn rb_gvar_set(arg1: ID, arg2: VALUE) -> VALUE;
pub fn rb_ensure_iv_list_size(obj: VALUE, current_len: u32, newsize: u32);
pub fn rb_vm_barrier();
+ pub fn rb_str_dup_m(str_: VALUE) -> VALUE;
pub fn rb_str_byte_substr(str_: VALUE, beg: VALUE, len: VALUE) -> VALUE;
pub fn rb_str_substr_two_fixnums(
str_: VALUE,
@@ -1264,6 +1245,7 @@ extern "C" {
pub fn rb_yjit_shape_too_complex_p(shape_id: shape_id_t) -> bool;
pub fn rb_yjit_shape_obj_too_complex_p(obj: VALUE) -> bool;
pub fn rb_yjit_shape_capacity(shape_id: shape_id_t) -> attr_index_t;
+ pub fn rb_yjit_shape_index(shape_id: shape_id_t) -> attr_index_t;
pub fn rb_yjit_assert_holding_vm_lock();
pub fn rb_yjit_sendish_sp_pops(ci: *const rb_callinfo) -> usize;
pub fn rb_yjit_invokeblock_sp_pops(ci: *const rb_callinfo) -> usize;
diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs
index 4aff3193f0..cf328fc68c 100644
--- a/zjit/bindgen/src/main.rs
+++ b/zjit/bindgen/src/main.rs
@@ -108,7 +108,6 @@ fn main() {
// From shape.h
.allowlist_function("rb_obj_shape_id")
- .allowlist_function("rb_shape_lookup")
.allowlist_function("rb_shape_id_offset")
.allowlist_function("rb_shape_get_iv_index")
.allowlist_function("rb_shape_transition_add_ivar_no_warnings")
diff --git a/zjit/src/asm/mod.rs b/zjit/src/asm/mod.rs
index a7f2705af1..0b571f9aff 100644
--- a/zjit/src/asm/mod.rs
+++ b/zjit/src/asm/mod.rs
@@ -1,5 +1,5 @@
use std::collections::BTreeMap;
-//use std::fmt;
+use std::fmt;
use std::rc::Rc;
use std::cell::RefCell;
use std::mem;
@@ -260,6 +260,18 @@ impl CodeBlock {
}
}
+/// Produce hex string output from the bytes in a code block
+impl fmt::LowerHex for CodeBlock {
+ fn fmt(&self, fmtr: &mut fmt::Formatter) -> fmt::Result {
+ for pos in 0..self.write_pos {
+ let mem_block = &*self.mem_block.borrow();
+ let byte = unsafe { mem_block.start_ptr().raw_ptr(mem_block).add(pos).read() };
+ fmtr.write_fmt(format_args!("{:02x}", byte))?;
+ }
+ Ok(())
+ }
+}
+
#[cfg(test)]
impl CodeBlock {
/// Stubbed CodeBlock for testing. Can't execute generated code.
diff --git a/zjit/src/asm/x86_64/mod.rs b/zjit/src/asm/x86_64/mod.rs
index efc58dfdb8..fea66c8a3b 100644
--- a/zjit/src/asm/x86_64/mod.rs
+++ b/zjit/src/asm/x86_64/mod.rs
@@ -1024,7 +1024,10 @@ pub fn mov(cb: &mut CodeBlock, dst: X86Opnd, src: X86Opnd) {
}
let output_num_bits:u32 = if mem.num_bits > 32 { 32 } else { mem.num_bits.into() };
- assert!(imm_num_bits(imm.value) <= (output_num_bits as u8));
+ assert!(
+ mem.num_bits < 64 || imm_num_bits(imm.value) <= (output_num_bits as u8),
+ "immediate value should be small enough to survive sign extension"
+ );
cb.write_int(imm.value as u64, output_num_bits);
},
// M + UImm
@@ -1039,7 +1042,10 @@ pub fn mov(cb: &mut CodeBlock, dst: X86Opnd, src: X86Opnd) {
}
let output_num_bits = if mem.num_bits > 32 { 32 } else { mem.num_bits.into() };
- assert!(imm_num_bits(uimm.value as i64) <= (output_num_bits as u8));
+ assert!(
+ mem.num_bits < 64 || imm_num_bits(uimm.value as i64) <= (output_num_bits as u8),
+ "immediate value should be small enough to survive sign extension"
+ );
cb.write_int(uimm.value, output_num_bits);
},
// * + Imm/UImm
diff --git a/zjit/src/asm/x86_64/tests.rs b/zjit/src/asm/x86_64/tests.rs
index f2b949b7f7..ec490fd330 100644
--- a/zjit/src/asm/x86_64/tests.rs
+++ b/zjit/src/asm/x86_64/tests.rs
@@ -1,11 +1,10 @@
#![cfg(test)]
-//use crate::asm::x86_64::*;
+use crate::asm::x86_64::*;
-/*
/// Check that the bytes for an instruction sequence match a hex string
fn check_bytes<R>(bytes: &str, run: R) where R: FnOnce(&mut super::CodeBlock) {
- let mut cb = super::CodeBlock::new_dummy(4096);
+ let mut cb = super::CodeBlock::new_dummy();
run(&mut cb);
assert_eq!(format!("{:x}", cb), bytes);
}
@@ -194,6 +193,7 @@ fn test_mov() {
check_bytes("48c7470801000000", |cb| mov(cb, mem_opnd(64, RDI, 8), imm_opnd(1)));
//check_bytes("67c7400411000000", |cb| mov(cb, mem_opnd(32, EAX, 4), imm_opnd(0x34))); // We don't distinguish between EAX and RAX here - that's probably fine?
check_bytes("c7400411000000", |cb| mov(cb, mem_opnd(32, RAX, 4), imm_opnd(17)));
+ check_bytes("c7400401000080", |cb| mov(cb, mem_opnd(32, RAX, 4), uimm_opnd(0x80000001)));
check_bytes("41895814", |cb| mov(cb, mem_opnd(32, R8, 20), EBX));
check_bytes("4d8913", |cb| mov(cb, mem_opnd(64, R11, 0), R10));
check_bytes("48c742f8f4ffffff", |cb| mov(cb, mem_opnd(64, RDX, -8), imm_opnd(-12)));
@@ -439,9 +439,10 @@ fn basic_capstone_usage() -> std::result::Result<(), capstone::Error> {
}
#[test]
+#[ignore]
#[cfg(feature = "disasm")]
fn block_comments() {
- let mut cb = super::CodeBlock::new_dummy(4096);
+ let mut cb = super::CodeBlock::new_dummy();
let first_write_ptr = cb.get_write_ptr().raw_addr(&cb);
cb.add_comment("Beginning");
@@ -458,4 +459,3 @@ fn block_comments() {
assert_eq!(&vec!( "Two bytes in".to_string(), "Still two bytes in".to_string() ), cb.comments_at(second_write_ptr).unwrap());
assert_eq!(&vec!( "Ten bytes in".to_string() ), cb.comments_at(third_write_ptr).unwrap());
}
-*/
diff --git a/zjit/src/assertions.rs b/zjit/src/assertions.rs
new file mode 100644
index 0000000000..0dacc938fc
--- /dev/null
+++ b/zjit/src/assertions.rs
@@ -0,0 +1,21 @@
+/// Assert that CodeBlock has the code specified with hex. In addition, if tested with
+/// `cargo test --all-features`, it also checks it generates the specified disasm.
+#[cfg(test)]
+macro_rules! assert_disasm {
+ ($cb:expr, $hex:expr, $disasm:expr) => {
+ #[cfg(feature = "disasm")]
+ {
+ use $crate::disasm::disasm_addr_range;
+ use $crate::cruby::unindent;
+ let disasm = disasm_addr_range(
+ &$cb,
+ $cb.get_ptr(0).raw_addr(&$cb),
+ $cb.get_write_ptr().raw_addr(&$cb),
+ );
+ assert_eq!(unindent(&disasm, false), unindent(&$disasm, true));
+ }
+ assert_eq!(format!("{:x}", $cb), $hex);
+ };
+}
+#[cfg(test)]
+pub(crate) use assert_disasm;
diff --git a/zjit/src/backend/arm64/mod.rs b/zjit/src/backend/arm64/mod.rs
index f7e871523e..dd1eb52d34 100644
--- a/zjit/src/backend/arm64/mod.rs
+++ b/zjit/src/backend/arm64/mod.rs
@@ -211,11 +211,6 @@ impl Assembler
vec![X1_REG, X9_REG, X10_REG, X11_REG, X12_REG, X13_REG, X14_REG, X15_REG]
}
- /// Get the address that the current frame returns to
- pub fn return_addr_opnd() -> Opnd {
- Opnd::Reg(X30_REG)
- }
-
/// Split platform-specific instructions
/// The transformations done here are meant to make our lives simpler in later
/// stages of the compilation pipeline.
@@ -1345,14 +1340,30 @@ impl Assembler
}
}
-/*
#[cfg(test)]
mod tests {
use super::*;
- use crate::disasm::*;
+ use crate::assertions::assert_disasm;
+
+ static TEMP_REGS: [Reg; 5] = [X1_REG, X9_REG, X10_REG, X14_REG, X15_REG];
fn setup_asm() -> (Assembler, CodeBlock) {
- (Assembler::new(0), CodeBlock::new_dummy(1024))
+ (Assembler::new(), CodeBlock::new_dummy())
+ }
+
+ #[test]
+ fn test_mul_with_immediate() {
+ let (mut asm, mut cb) = setup_asm();
+
+ let out = asm.mul(Opnd::Reg(TEMP_REGS[1]), 3.into());
+ asm.mov(Opnd::Reg(TEMP_REGS[0]), out);
+ asm.compile_with_num_regs(&mut cb, 2);
+
+ assert_disasm!(cb, "600080d2207d009be10300aa", {"
+ 0x0: mov x0, #3
+ 0x4: mul x0, x9, x0
+ 0x8: mov x1, x0
+ "});
}
#[test]
@@ -1361,7 +1372,7 @@ mod tests {
let opnd = asm.add(Opnd::Reg(X0_REG), Opnd::Reg(X1_REG));
asm.store(Opnd::mem(64, Opnd::Reg(X2_REG), 0), opnd);
- asm.compile_with_regs(&mut cb, None, vec![X3_REG]);
+ asm.compile_with_regs(&mut cb, vec![X3_REG]);
// Assert that only 2 instructions were written.
assert_eq!(8, cb.get_write_pos());
@@ -1425,6 +1436,7 @@ mod tests {
asm.compile_with_num_regs(&mut cb, 0);
}
+ /*
#[test]
fn test_emit_lea_label() {
let (mut asm, mut cb) = setup_asm();
@@ -1438,6 +1450,7 @@ mod tests {
asm.compile_with_num_regs(&mut cb, 1);
}
+ */
#[test]
fn test_emit_load_mem_disp_fits_into_load() {
@@ -1648,6 +1661,7 @@ mod tests {
asm.compile_with_num_regs(&mut cb, 2);
}
+ /*
#[test]
fn test_bcond_straddling_code_pages() {
const LANDING_PAGE: usize = 65;
@@ -1784,20 +1798,5 @@ mod tests {
0x8: mov x1, x11
"});
}
-
- #[test]
- fn test_mul_with_immediate() {
- let (mut asm, mut cb) = setup_asm();
-
- let out = asm.mul(Opnd::Reg(TEMP_REGS[1]), 3.into());
- asm.mov(Opnd::Reg(TEMP_REGS[0]), out);
- asm.compile_with_num_regs(&mut cb, 2);
-
- assert_disasm!(cb, "6b0080d22b7d0b9be1030baa", {"
- 0x0: mov x11, #3
- 0x4: mul x11, x9, x11
- 0x8: mov x1, x11
- "});
- }
+ */
}
-*/
diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs
index e9ae8730f6..f46b35ded5 100644
--- a/zjit/src/backend/lir.rs
+++ b/zjit/src/backend/lir.rs
@@ -1,8 +1,8 @@
use std::collections::HashMap;
use std::fmt;
use std::mem::take;
-use crate::cruby::{Qundef, RUBY_OFFSET_CFP_PC, RUBY_OFFSET_CFP_SP, SIZEOF_VALUE_I32, VM_ENV_DATA_SIZE};
-use crate::state::ZJITState;
+use crate::codegen::local_size_and_idx_to_ep_offset;
+use crate::cruby::{Qundef, RUBY_OFFSET_CFP_PC, RUBY_OFFSET_CFP_SP, SIZEOF_VALUE_I32};
use crate::{cruby::VALUE};
use crate::backend::current::*;
use crate::virtualmem::CodePtr;
@@ -1751,6 +1751,15 @@ impl Assembler
ret
}
+ /// Compile with a limited number of registers. Used only for unit tests.
+ #[cfg(test)]
+ pub fn compile_with_num_regs(self, cb: &mut CodeBlock, num_regs: usize) -> (CodePtr, Vec<u32>)
+ {
+ let mut alloc_regs = Self::get_alloc_regs();
+ let alloc_regs = alloc_regs.drain(0..num_regs).collect();
+ self.compile_with_regs(cb, alloc_regs).unwrap()
+ }
+
/// Compile Target::SideExit and convert it into Target::CodePtr for all instructions
#[must_use]
pub fn compile_side_exits(&mut self) -> Option<()> {
@@ -1788,7 +1797,7 @@ impl Assembler
asm_comment!(self, "write locals: {locals:?}");
for (idx, &opnd) in locals.iter().enumerate() {
let opnd = split_store_source(self, opnd);
- self.store(Opnd::mem(64, SP, (-(VM_ENV_DATA_SIZE as i32) - locals.len() as i32 + idx as i32) * SIZEOF_VALUE_I32), opnd);
+ self.store(Opnd::mem(64, SP, (-local_size_and_idx_to_ep_offset(locals.len(), idx) - 1) * SIZEOF_VALUE_I32), opnd);
}
asm_comment!(self, "save cfp->pc");
@@ -1800,10 +1809,6 @@ impl Assembler
let cfp_sp = Opnd::mem(64, CFP, RUBY_OFFSET_CFP_SP);
self.store(cfp_sp, Opnd::Reg(Assembler::SCRATCH_REG));
- asm_comment!(self, "rewind caller frames");
- self.mov(C_ARG_OPNDS[0], Assembler::return_addr_opnd());
- self.ccall(Self::rewind_caller_frames as *const u8, vec![]);
-
asm_comment!(self, "exit to the interpreter");
self.frame_teardown();
self.mov(C_RET_OPND, Opnd::UImm(Qundef.as_u64()));
@@ -1814,13 +1819,6 @@ impl Assembler
}
Some(())
}
-
- #[unsafe(no_mangle)]
- extern "C" fn rewind_caller_frames(addr: *const u8) {
- if ZJITState::is_iseq_return_addr(addr) {
- unimplemented!("Can't side-exit from JIT-JIT call: rewind_caller_frames is not implemented yet");
- }
- }
}
impl fmt::Debug for Assembler {
diff --git a/zjit/src/backend/x86_64/mod.rs b/zjit/src/backend/x86_64/mod.rs
index cf62cdd7f5..d83fc184f9 100644
--- a/zjit/src/backend/x86_64/mod.rs
+++ b/zjit/src/backend/x86_64/mod.rs
@@ -109,11 +109,6 @@ impl Assembler
vec![RAX_REG, RCX_REG, RDX_REG, RSI_REG, RDI_REG, R8_REG, R9_REG, R10_REG, R11_REG]
}
- /// Get the address that the current frame returns to
- pub fn return_addr_opnd() -> Opnd {
- Opnd::mem(64, Opnd::Reg(RSP_REG), 0)
- }
-
// These are the callee-saved registers in the x86-64 SysV ABI
// RBX, RSP, RBP, and R12–R15
@@ -298,19 +293,24 @@ impl Assembler
let opnd1 = asm.load(*src);
asm.mov(*dest, opnd1);
},
- (Opnd::Mem(_), Opnd::UImm(value)) => {
- // 32-bit values will be sign-extended
- if imm_num_bits(*value as i64) > 32 {
+ (Opnd::Mem(Mem { num_bits, .. }), Opnd::UImm(value)) => {
+ // For 64 bit destinations, 32-bit values will be sign-extended
+ if *num_bits == 64 && imm_num_bits(*value as i64) > 32 {
let opnd1 = asm.load(*src);
asm.mov(*dest, opnd1);
} else {
asm.mov(*dest, *src);
}
},
- (Opnd::Mem(_), Opnd::Imm(value)) => {
- if imm_num_bits(*value) > 32 {
+ (Opnd::Mem(Mem { num_bits, .. }), Opnd::Imm(value)) => {
+ // For 64 bit destinations, 32-bit values will be sign-extended
+ if *num_bits == 64 && imm_num_bits(*value) > 32 {
let opnd1 = asm.load(*src);
asm.mov(*dest, opnd1);
+ } else if uimm_num_bits(*value as u64) <= *num_bits {
+ // If the bit string is short enough for the destination, use the unsigned representation.
+ // Note that 64-bit and negative values are ruled out.
+ asm.mov(*dest, Opnd::UImm(*value as u64));
} else {
asm.mov(*dest, *src);
}
@@ -859,20 +859,17 @@ impl Assembler
}
}
-/*
#[cfg(test)]
mod tests {
- use crate::disasm::assert_disasm;
- #[cfg(feature = "disasm")]
- use crate::disasm::{unindent, disasm_addr_range};
-
+ use crate::assertions::assert_disasm;
use super::*;
fn setup_asm() -> (Assembler, CodeBlock) {
- (Assembler::new(0), CodeBlock::new_dummy(1024))
+ (Assembler::new(), CodeBlock::new_dummy())
}
#[test]
+ #[ignore]
fn test_emit_add_lt_32_bits() {
let (mut asm, mut cb) = setup_asm();
@@ -883,6 +880,7 @@ mod tests {
}
#[test]
+ #[ignore]
fn test_emit_add_gt_32_bits() {
let (mut asm, mut cb) = setup_asm();
@@ -893,6 +891,7 @@ mod tests {
}
#[test]
+ #[ignore]
fn test_emit_and_lt_32_bits() {
let (mut asm, mut cb) = setup_asm();
@@ -903,6 +902,7 @@ mod tests {
}
#[test]
+ #[ignore]
fn test_emit_and_gt_32_bits() {
let (mut asm, mut cb) = setup_asm();
@@ -957,6 +957,7 @@ mod tests {
}
#[test]
+ #[ignore]
fn test_emit_or_lt_32_bits() {
let (mut asm, mut cb) = setup_asm();
@@ -967,6 +968,7 @@ mod tests {
}
#[test]
+ #[ignore]
fn test_emit_or_gt_32_bits() {
let (mut asm, mut cb) = setup_asm();
@@ -977,6 +979,7 @@ mod tests {
}
#[test]
+ #[ignore]
fn test_emit_sub_lt_32_bits() {
let (mut asm, mut cb) = setup_asm();
@@ -987,6 +990,7 @@ mod tests {
}
#[test]
+ #[ignore]
fn test_emit_sub_gt_32_bits() {
let (mut asm, mut cb) = setup_asm();
@@ -1017,6 +1021,7 @@ mod tests {
}
#[test]
+ #[ignore]
fn test_emit_xor_lt_32_bits() {
let (mut asm, mut cb) = setup_asm();
@@ -1027,6 +1032,7 @@ mod tests {
}
#[test]
+ #[ignore]
fn test_emit_xor_gt_32_bits() {
let (mut asm, mut cb) = setup_asm();
@@ -1050,6 +1056,7 @@ mod tests {
}
#[test]
+ #[ignore]
fn test_merge_lea_mem() {
let (mut asm, mut cb) = setup_asm();
@@ -1064,6 +1071,7 @@ mod tests {
}
#[test]
+ #[ignore]
fn test_replace_cmp_0() {
let (mut asm, mut cb) = setup_asm();
@@ -1216,6 +1224,7 @@ mod tests {
}
#[test]
+ #[ignore]
fn test_reorder_c_args_with_insn_out() {
let (mut asm, mut cb) = setup_asm();
@@ -1259,15 +1268,16 @@ mod tests {
asm.compile_with_num_regs(&mut cb, 1);
- assert_disasm!(cb, "48837b1001b804000000480f4f03488903", {"
+ assert_disasm!(cb, "48837b1001bf04000000480f4f3b48893b", {"
0x0: cmp qword ptr [rbx + 0x10], 1
- 0x5: mov eax, 4
- 0xa: cmovg rax, qword ptr [rbx]
- 0xe: mov qword ptr [rbx], rax
+ 0x5: mov edi, 4
+ 0xa: cmovg rdi, qword ptr [rbx]
+ 0xe: mov qword ptr [rbx], rdi
"});
}
#[test]
+ #[ignore]
fn test_csel_split() {
let (mut asm, mut cb) = setup_asm();
@@ -1284,6 +1294,19 @@ mod tests {
0x13: mov qword ptr [rbx], rax
"});
}
-}
-*/
+ #[test]
+ fn test_mov_m32_imm32() {
+ let (mut asm, mut cb) = setup_asm();
+
+ let shape_opnd = Opnd::mem(32, C_RET_OPND, 0);
+ asm.mov(shape_opnd, Opnd::UImm(0x8000_0001));
+ asm.mov(shape_opnd, Opnd::Imm(0x8000_0001));
+
+ asm.compile_with_num_regs(&mut cb, 0);
+ assert_disasm!(cb, "c70001000080c70001000080", {"
+ 0x0: mov dword ptr [rax], 0x80000001
+ 0x6: mov dword ptr [rax], 0x80000001
+ "});
+ }
+}
diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs
index 0dbe815c71..90c3ce640e 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,13 +252,15 @@ 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),
Insn::IfTrue { val, target } => return gen_if_true(jit, asm, opnd!(val), target),
Insn::IfFalse { val, target } => return gen_if_false(jit, asm, opnd!(val), target),
Insn::SendWithoutBlock { call_info, cd, state, self_val, args, .. } => gen_send_without_block(jit, asm, call_info, *cd, &function.frame_state(*state), self_val, args)?,
- Insn::SendWithoutBlockDirect { iseq, self_val, args, .. } => gen_send_without_block_direct(cb, jit, asm, *iseq, opnd!(self_val), args)?,
+ Insn::SendWithoutBlockDirect { cme, iseq, self_val, args, state, .. } => gen_send_without_block_direct(cb, jit, asm, *cme, *iseq, opnd!(self_val), args, &function.frame_state(*state))?,
+ Insn::InvokeBuiltin { bf, args, state } => gen_invokebuiltin(jit, asm, &function.frame_state(*state), bf, args)?,
Insn::Return { val } => return Some(gen_return(asm, opnd!(val))?),
Insn::FixnumAdd { left, right, state } => gen_fixnum_add(jit, asm, opnd!(left), opnd!(right), &function.frame_state(*state))?,
Insn::FixnumSub { left, right, state } => gen_fixnum_sub(jit, asm, opnd!(left), opnd!(right), &function.frame_state(*state))?,
@@ -275,19 +277,61 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
Insn::PatchPoint(_) => return Some(()), // For now, rb_zjit_bop_redefined() panics. TODO: leave a patch point and fix rb_zjit_bop_redefined()
Insn::CCall { cfun, args, name: _, return_type: _, elidable: _ } => gen_ccall(jit, asm, *cfun, args)?,
Insn::GetIvar { self_val, id, state: _ } => gen_getivar(asm, opnd!(self_val), *id),
- Insn::SetIvar { self_val, id, val, state: _ } => gen_setivar(asm, opnd!(self_val), *id, opnd!(val)),
+ 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;
}
};
+ assert!(insn.has_output(), "Cannot write LIR output of HIR instruction with no output");
+
// If the instruction has an output, remember it in jit.opnds
jit.opnds[insn_id.0] = Some(out_opnd);
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
+}
+
+fn gen_invokebuiltin(jit: &mut JITState, asm: &mut Assembler, state: &FrameState, bf: &rb_builtin_function, args: &Vec<InsnId>) -> Option<lir::Opnd> {
+ // Ensure we have enough room fit ec, self, and arguments
+ // TODO remove this check when we have stack args (we can use Time.new to test it)
+ if bf.argc + 2 > (C_ARG_OPNDS.len() as i32) {
+ return None;
+ }
+
+ gen_save_pc(asm, state);
+
+ let mut cargs = vec![EC];
+ for &arg in args.iter() {
+ let opnd = jit.get_opnd(arg)?;
+ cargs.push(opnd);
+ }
+
+ let val = asm.ccall(bf.func_ptr as *const u8, cargs);
+
+ Some(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> {
@@ -309,11 +353,50 @@ fn gen_getivar(asm: &mut Assembler, recv: Opnd, id: ID) -> Opnd {
}
/// Emit an uncached instance variable store
-fn gen_setivar(asm: &mut Assembler, recv: Opnd, id: ID, val: Opnd) -> Opnd {
+fn gen_setivar(asm: &mut Assembler, recv: Opnd, id: ID, val: Opnd) -> Option<()> {
asm_comment!(asm, "call rb_ivar_set");
asm.ccall(
rb_ivar_set as *const u8,
vec![recv, Opnd::UImm(id.0), val],
+ );
+ Some(())
+}
+
+/// Look up global variables
+fn gen_getglobal(asm: &mut Assembler, id: ID) -> Opnd {
+ asm_comment!(asm, "call rb_gvar_get");
+ asm.ccall(
+ rb_gvar_get as *const u8,
+ vec![Opnd::UImm(id.0)],
+ )
+}
+
+/// Set global variables
+fn gen_setglobal(asm: &mut Assembler, id: ID, val: Opnd) -> Opnd {
+ asm_comment!(asm, "call rb_gvar_set");
+ asm.ccall(
+ rb_gvar_set as *const u8,
+ vec![Opnd::UImm(id.0), val],
+ )
+}
+
+/// Side-exit into the interpreter
+fn gen_side_exit(jit: &mut JITState, asm: &mut Assembler, state: &FrameState) -> Option<()> {
+ asm.jmp(side_exit(jit, state)?);
+ 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))],
)
}
@@ -464,8 +547,16 @@ fn gen_send_without_block(
self_val: &InsnId,
args: &Vec<InsnId>,
) -> Option<lir::Opnd> {
- // Spill the receiver and the arguments onto the stack. They need to be marked by GC and may be caller-saved registers.
+ // Spill locals onto the stack.
+ // TODO: Don't spill locals eagerly; lazily reify frames
+ asm_comment!(asm, "spill locals");
+ for (idx, &insn_id) in state.locals().enumerate() {
+ asm.mov(Opnd::mem(64, SP, (-local_idx_to_ep_offset(jit.iseq, idx) - 1) * SIZEOF_VALUE_I32), jit.get_opnd(insn_id)?);
+ }
+ // Spill the receiver and the arguments onto the stack.
+ // They need to be on the interpreter stack to let the interpreter access them.
// TODO: Avoid spilling operands that have been spilled before.
+ asm_comment!(asm, "spill receiver and arguments");
for (idx, &insn_id) in [*self_val].iter().chain(args.iter()).enumerate() {
// Currently, we don't move the SP register. So it's equal to the base pointer.
let stack_opnd = Opnd::mem(64, SP, idx as i32 * SIZEOF_VALUE_I32);
@@ -495,10 +586,40 @@ fn gen_send_without_block_direct(
cb: &mut CodeBlock,
jit: &mut JITState,
asm: &mut Assembler,
+ cme: *const rb_callable_method_entry_t,
iseq: IseqPtr,
recv: Opnd,
args: &Vec<InsnId>,
+ state: &FrameState,
) -> Option<lir::Opnd> {
+ // Save cfp->pc and cfp->sp for the caller frame
+ gen_save_pc(asm, state);
+ gen_save_sp(asm, state.stack().len() - args.len() - 1); // -1 for receiver
+
+ // Spill the virtual stack and the locals of the caller onto the stack
+ // TODO: Lazily materialize caller frames on side exits or when needed
+ asm_comment!(asm, "spill locals and stack");
+ for (idx, &insn_id) in state.locals().enumerate() {
+ asm.mov(Opnd::mem(64, SP, (-local_idx_to_ep_offset(jit.iseq, idx) - 1) * SIZEOF_VALUE_I32), jit.get_opnd(insn_id)?);
+ }
+ for (idx, &insn_id) in state.stack().enumerate() {
+ asm.mov(Opnd::mem(64, SP, idx as i32 * SIZEOF_VALUE_I32), jit.get_opnd(insn_id)?);
+ }
+
+ // Set up the new frame
+ // TODO: Lazily materialize caller frames on side exits or when needed
+ gen_push_frame(asm, args.len(), state, ControlFrame {
+ recv,
+ iseq,
+ cme,
+ frame_type: VM_FRAME_MAGIC_METHOD | VM_ENV_FLAG_LOCAL,
+ });
+
+ asm_comment!(asm, "switch to new SP register");
+ let local_size = unsafe { get_iseq_body_local_table_size(iseq) } as usize;
+ let new_sp = asm.add(SP, ((state.stack().len() + local_size - args.len() + VM_ENV_DATA_SIZE as usize) * SIZEOF_VALUE).into());
+ asm.mov(SP, new_sp);
+
asm_comment!(asm, "switch to new CFP");
let new_cfp = asm.sub(CFP, RUBY_SIZEOF_CONTROL_FRAME.into());
asm.mov(CFP, new_cfp);
@@ -517,7 +638,26 @@ fn gen_send_without_block_direct(
jit.branch_iseqs.push((branch.clone(), iseq));
// TODO(max): Add a PatchPoint here that can side-exit the function if the callee messed with
// the frame's locals
- Some(asm.ccall_with_branch(dummy_ptr, c_args, &branch))
+ let ret = asm.ccall_with_branch(dummy_ptr, c_args, &branch);
+
+ // If a callee side-exits, i.e. returns Qundef, propagate the return value to the caller.
+ // The caller will side-exit the callee into the interpreter.
+ // TODO: Let side exit code pop all JIT frames to optimize away this cmp + je.
+ asm.cmp(ret, Qundef.into());
+ asm.je(ZJITState::get_exit_trampoline().into());
+
+ 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
@@ -729,6 +869,45 @@ fn gen_save_sp(asm: &mut Assembler, stack_size: usize) {
asm.mov(cfp_sp, sp_addr);
}
+/// Frame metadata written by gen_push_frame()
+struct ControlFrame {
+ recv: Opnd,
+ iseq: IseqPtr,
+ cme: *const rb_callable_method_entry_t,
+ frame_type: u32,
+}
+
+/// Compile an interpreter frame
+fn gen_push_frame(asm: &mut Assembler, argc: usize, state: &FrameState, frame: ControlFrame) {
+ // Locals are written by the callee frame on side-exits or non-leaf calls
+
+ // See vm_push_frame() for details
+ asm_comment!(asm, "push cme, specval, frame type");
+ // ep[-2]: cref of cme
+ let local_size = unsafe { get_iseq_body_local_table_size(frame.iseq) } as i32;
+ let ep_offset = state.stack().len() as i32 + local_size - argc as i32 + VM_ENV_DATA_SIZE as i32 - 1;
+ asm.store(Opnd::mem(64, SP, (ep_offset - 2) * SIZEOF_VALUE_I32), VALUE::from(frame.cme).into());
+ // ep[-1]: block_handler or prev EP
+ // block_handler is not supported for now
+ asm.store(Opnd::mem(64, SP, (ep_offset - 1) * SIZEOF_VALUE_I32), VM_BLOCK_HANDLER_NONE.into());
+ // ep[0]: ENV_FLAGS
+ asm.store(Opnd::mem(64, SP, ep_offset * SIZEOF_VALUE_I32), frame.frame_type.into());
+
+ // Write to the callee CFP
+ fn cfp_opnd(offset: i32) -> Opnd {
+ Opnd::mem(64, CFP, offset - (RUBY_SIZEOF_CONTROL_FRAME as i32))
+ }
+
+ asm_comment!(asm, "push callee control frame");
+ // cfp_opnd(RUBY_OFFSET_CFP_PC): written by the callee frame on side-exits or non-leaf calls
+ // cfp_opnd(RUBY_OFFSET_CFP_SP): written by the callee frame on side-exits or non-leaf calls
+ asm.mov(cfp_opnd(RUBY_OFFSET_CFP_ISEQ), VALUE::from(frame.iseq).into());
+ asm.mov(cfp_opnd(RUBY_OFFSET_CFP_SELF), frame.recv);
+ let ep = asm.lea(Opnd::mem(64, SP, ep_offset * SIZEOF_VALUE_I32));
+ asm.mov(cfp_opnd(RUBY_OFFSET_CFP_EP), ep);
+ asm.mov(cfp_opnd(RUBY_OFFSET_CFP_BLOCK_CODE), 0.into());
+}
+
/// Return a register we use for the basic block argument at a given index
fn param_reg(idx: usize) -> Reg {
// To simplify the implementation, allocate a fixed register for each basic block argument for now.
@@ -744,10 +923,13 @@ fn param_reg(idx: usize) -> Reg {
/// Inverse of ep_offset_to_local_idx(). See ep_offset_to_local_idx() for details.
fn local_idx_to_ep_offset(iseq: IseqPtr, local_idx: usize) -> i32 {
- let local_table_size: i32 = unsafe { get_iseq_body_local_table_size(iseq) }
- .try_into()
- .unwrap();
- local_table_size - local_idx as i32 - 1 + VM_ENV_DATA_SIZE as i32
+ let local_size = unsafe { get_iseq_body_local_table_size(iseq) };
+ local_size_and_idx_to_ep_offset(local_size as usize, local_idx)
+}
+
+/// Convert the number of locals and a local index to an offset in the EP
+pub fn local_size_and_idx_to_ep_offset(local_size: usize, local_idx: usize) -> i32 {
+ local_size as i32 - local_idx as i32 - 1 + VM_ENV_DATA_SIZE as i32
}
/// Convert ISEQ into High-level IR
@@ -796,9 +978,8 @@ impl Assembler {
move |code_ptr, _| {
start_branch.start_addr.set(Some(code_ptr));
},
- move |code_ptr, cb| {
+ move |code_ptr, _| {
end_branch.end_addr.set(Some(code_ptr));
- ZJITState::add_iseq_return_addr(code_ptr.raw_ptr(cb));
},
)
}
diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs
index d5be47e026..3a1c45ffd3 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 {
@@ -485,18 +487,6 @@ impl VALUE {
unsafe { rb_obj_shape_id(self) }
}
- pub fn shape_of(self) -> *mut rb_shape {
- unsafe {
- let shape = rb_shape_lookup(self.shape_id_of());
-
- if shape.is_null() {
- panic!("Shape should not be null");
- } else {
- shape
- }
- }
- }
-
pub fn embedded_p(self) -> bool {
unsafe {
FL_TEST_RAW(self, VALUE(ROBJECT_EMBED as usize)) != VALUE(0)
@@ -1048,8 +1038,8 @@ pub mod test_utils {
}
/// Get the ISeq of a specified method
- pub fn get_method_iseq(name: &str) -> *const rb_iseq_t {
- let wrapped_iseq = eval(&format!("RubyVM::InstructionSequence.of(method(:{}))", name));
+ pub fn get_method_iseq(recv: &str, name: &str) -> *const rb_iseq_t {
+ let wrapped_iseq = eval(&format!("RubyVM::InstructionSequence.of({}.method(:{}))", recv, name));
unsafe { rb_iseqw_to_iseq(wrapped_iseq) }
}
diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs
index 0447f46fd0..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,6 +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_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;
@@ -395,26 +397,6 @@ pub const VM_ENV_FLAG_ISOLATED: vm_frame_env_flags = 16;
pub type vm_frame_env_flags = u32;
pub type attr_index_t = u16;
pub type shape_id_t = u32;
-pub type redblack_id_t = u32;
-pub type redblack_node_t = redblack_node;
-#[repr(C)]
-pub struct rb_shape {
- pub edges: VALUE,
- pub edge_name: ID,
- pub ancestor_index: *mut redblack_node_t,
- pub parent_id: shape_id_t,
- pub next_field_index: attr_index_t,
- pub capacity: attr_index_t,
- pub type_: u8,
-}
-pub type rb_shape_t = rb_shape;
-#[repr(C)]
-pub struct redblack_node {
- pub key: ID,
- pub value: *mut rb_shape_t,
- pub l: redblack_id_t,
- pub r: redblack_id_t,
-}
#[repr(C)]
pub struct rb_cvar_class_tbl_entry {
pub index: u32,
@@ -865,7 +847,6 @@ unsafe extern "C" {
pub fn rb_obj_info(obj: VALUE) -> *const ::std::os::raw::c_char;
pub fn rb_ec_stack_check(ec: *mut rb_execution_context_struct) -> ::std::os::raw::c_int;
pub fn rb_shape_id_offset() -> i32;
- pub fn rb_shape_lookup(shape_id: shape_id_t) -> *mut rb_shape_t;
pub fn rb_obj_shape_id(obj: VALUE) -> shape_id_t;
pub fn rb_shape_get_iv_index(shape_id: shape_id_t, id: ID, value: *mut attr_index_t) -> bool;
pub fn rb_shape_transition_add_ivar_no_warnings(obj: VALUE, id: ID) -> shape_id_t;
diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs
index c93b603f53..7a9bb132aa 100644
--- a/zjit/src/hir.rs
+++ b/zjit/src/hir.rs
@@ -9,7 +9,7 @@ use crate::{
use std::{
cell::RefCell,
collections::{HashMap, HashSet, VecDeque},
- ffi::{c_int, c_void},
+ ffi::{c_int, c_void, CStr},
mem::{align_of, size_of},
ptr,
slice::Iter
@@ -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,12 @@ 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 },
+ /// Set a global variable named `id` to `val`
+ SetGlobal { id: ID, val: InsnId, state: InsnId },
//NewObject?
/// Get an instance variable `id` from `self_val`
@@ -421,7 +467,18 @@ pub enum Insn {
/// Ignoring keyword arguments etc for now
SendWithoutBlock { self_val: InsnId, call_info: CallInfo, cd: *const rb_call_data, args: Vec<InsnId>, state: InsnId },
Send { self_val: InsnId, call_info: CallInfo, cd: *const rb_call_data, blockiseq: IseqPtr, args: Vec<InsnId>, state: InsnId },
- SendWithoutBlockDirect { self_val: InsnId, call_info: CallInfo, cd: *const rb_call_data, iseq: IseqPtr, args: Vec<InsnId>, state: InsnId },
+ SendWithoutBlockDirect {
+ self_val: InsnId,
+ call_info: CallInfo,
+ cd: *const rb_call_data,
+ cme: *const rb_callable_method_entry_t,
+ iseq: IseqPtr,
+ args: Vec<InsnId>,
+ state: InsnId,
+ },
+
+ // Invoke a builtin function
+ InvokeBuiltin { bf: rb_builtin_function, args: Vec<InsnId>, state: InsnId },
/// Control flow instructions
Return { val: InsnId },
@@ -439,6 +496,9 @@ pub enum Insn {
FixnumGt { left: InsnId, right: InsnId },
FixnumGe { left: InsnId, right: InsnId },
+ // Distinct from `SendWithoutBlock` with `mid:to_s` because does not have a patch point for String to_s being redefined
+ ObjToString { val: InsnId, call_info: CallInfo, cd: *const rb_call_data, state: InsnId },
+
/// Side-exit if val doesn't have the expected type.
GuardType { val: InsnId, guard_type: Type, state: InsnId },
/// Side-exit if val is not the expected VALUE.
@@ -459,7 +519,7 @@ impl Insn {
Insn::ArraySet { .. } | Insn::Snapshot { .. } | Insn::Jump(_)
| Insn::IfTrue { .. } | Insn::IfFalse { .. } | Insn::Return { .. }
| Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::ArrayExtend { .. }
- | Insn::ArrayPush { .. } | Insn::SideExit { .. } => false,
+ | Insn::ArrayPush { .. } | Insn::SideExit { .. } | Insn::SetGlobal { .. } => false,
_ => true,
}
}
@@ -552,7 +612,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}") }
@@ -582,6 +642,13 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
}
Ok(())
}
+ Insn::InvokeBuiltin { bf, args, .. } => {
+ write!(f, "InvokeBuiltin {}", unsafe { CStr::from_ptr(bf.name) }.to_str().unwrap())?;
+ for arg in args {
+ write!(f, ", {arg}")?;
+ }
+ Ok(())
+ }
Insn::Return { val } => { write!(f, "Return {val}") }
Insn::FixnumAdd { left, right, .. } => { write!(f, "FixnumAdd {left}, {right}") },
Insn::FixnumSub { left, right, .. } => { write!(f, "FixnumSub {left}, {right}") },
@@ -597,7 +664,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 {
@@ -625,11 +692,17 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
Insn::DefinedIvar { self_val, id, .. } => write!(f, "DefinedIvar {self_val}, :{}", id.contents_lossy().into_owned()),
Insn::GetIvar { self_val, id, .. } => write!(f, "GetIvar {self_val}, :{}", id.contents_lossy().into_owned()),
Insn::SetIvar { self_val, id, val, .. } => write!(f, "SetIvar {self_val}, :{}, {val}", id.contents_lossy().into_owned()),
+ Insn::GetGlobal { id, .. } => write!(f, "GetGlobal :{}", id.contents_lossy().into_owned()),
+ Insn::SetGlobal { id, val, .. } => write!(f, "SetGlobal :{}, {val}", id.contents_lossy().into_owned()),
Insn::ToArray { val, .. } => write!(f, "ToArray {val}"),
Insn::ToNewArray { val, .. } => write!(f, "ToNewArray {val}"),
Insn::ArrayExtend { left, right, .. } => write!(f, "ArrayExtend {left}, {right}"),
Insn::ArrayPush { array, val, .. } => write!(f, "ArrayPush {array}, {val}"),
+ Insn::ObjToString { val, .. } => { write!(f, "ObjToString {val}") },
Insn::SideExit { .. } => write!(f, "SideExit"),
+ Insn::PutSpecialObject { value_type } => {
+ write!(f, "PutSpecialObject {}", value_type)
+ }
insn => { write!(f, "{insn:?}") }
}
}
@@ -923,7 +996,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) },
@@ -943,6 +1016,13 @@ 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 },
+ ObjToString { val, call_info, cd, state } => ObjToString {
+ val: find!(*val),
+ call_info: call_info.clone(),
+ cd: *cd,
+ state: *state,
+ },
SendWithoutBlock { self_val, call_info, cd, args, state } => SendWithoutBlock {
self_val: find!(*self_val),
call_info: call_info.clone(),
@@ -950,10 +1030,11 @@ impl Function {
args: args.iter().map(|arg| find!(*arg)).collect(),
state: *state,
},
- SendWithoutBlockDirect { self_val, call_info, cd, iseq, args, state } => SendWithoutBlockDirect {
+ SendWithoutBlockDirect { self_val, call_info, cd, cme, iseq, args, state } => SendWithoutBlockDirect {
self_val: find!(*self_val),
call_info: call_info.clone(),
cd: *cd,
+ cme: *cme,
iseq: *iseq,
args: args.iter().map(|arg| find!(*arg)).collect(),
state: *state,
@@ -966,6 +1047,7 @@ impl Function {
args: args.iter().map(|arg| find!(*arg)).collect(),
state: *state,
},
+ InvokeBuiltin { bf, args, state } => InvokeBuiltin { bf: *bf, args: find_vec!(*args), state: *state },
ArraySet { array, idx, val } => ArraySet { array: find!(*array), idx: *idx, val: find!(*val) },
ArrayDup { val , state } => ArrayDup { val: find!(*val), state: *state },
&HashDup { val , state } => HashDup { val: find!(val), state },
@@ -982,6 +1064,8 @@ impl Function {
}
&NewRange { low, high, flag, state } => NewRange { low: find!(low), high: find!(high), flag, state: find!(state) },
ArrayMax { elements, state } => ArrayMax { elements: find_vec!(*elements), state: find!(*state) },
+ &GetGlobal { id, state } => GetGlobal { id, state },
+ &SetGlobal { id, val, state } => SetGlobal { id, val: find!(val), state },
&GetIvar { self_val, id, state } => GetIvar { self_val: find!(self_val), id, state },
&SetIvar { self_val, id, val, state } => SetIvar { self_val: find!(self_val), id, val, state },
&ToArray { val, state } => ToArray { val: find!(val), state },
@@ -1012,7 +1096,7 @@ impl Function {
assert!(self.insns[insn.0].has_output());
match &self.insns[insn.0] {
Insn::Param { .. } => unimplemented!("params should not be present in block.insns"),
- Insn::ArraySet { .. } | Insn::Snapshot { .. } | Insn::Jump(_)
+ Insn::SetGlobal { .. } | Insn::ArraySet { .. } | Insn::Snapshot { .. } | Insn::Jump(_)
| Insn::IfTrue { .. } | Insn::IfFalse { .. } | Insn::Return { .. }
| Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::ArrayExtend { .. }
| Insn::ArrayPush { .. } | Insn::SideExit { .. } =>
@@ -1056,16 +1140,20 @@ 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,
+ Insn::InvokeBuiltin { .. } => types::BasicObject,
Insn::Defined { .. } => types::BasicObject,
Insn::DefinedIvar { .. } => types::BasicObject,
Insn::GetConstantPath { .. } => types::BasicObject,
Insn::ArrayMax { .. } => types::BasicObject,
+ Insn::GetGlobal { .. } => types::BasicObject,
Insn::GetIvar { .. } => types::BasicObject,
Insn::ToNewArray { .. } => types::ArrayExact,
Insn::ToArray { .. } => types::ArrayExact,
+ Insn::ObjToString { .. } => types::BasicObject,
}
}
@@ -1189,6 +1277,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) {
@@ -1219,6 +1339,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() {
@@ -1251,10 +1375,10 @@ impl Function {
if let Some(expected) = guard_equal_to {
self_val = self.push_insn(block, Insn::GuardBitEquals { val: self_val, expected, state });
}
- let send_direct = self.push_insn(block, Insn::SendWithoutBlockDirect { self_val, call_info, cd, iseq, args, state });
+ 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() {
@@ -1273,6 +1397,15 @@ impl Function {
let replacement = self.push_insn(block, Insn::Const { val: Const::Value(unsafe { (*ice).value }) });
self.make_equal_to(insn_id, replacement);
}
+ Insn::ObjToString { val, call_info, cd, state, .. } => {
+ if self.is_a(val, types::StringExact) {
+ // behaves differently from `SendWithoutBlock` with `mid:to_s` because ObjToString should not have a patch point for String to_s being redefined
+ self.make_equal_to(insn_id, val);
+ } else {
+ let replacement = self.push_insn(block, Insn::SendWithoutBlock { self_val: val, call_info, cd, args: vec![], state });
+ self.make_equal_to(insn_id, replacement)
+ }
+ }
_ => { self.push_insn_id(block, insn_id); }
}
}
@@ -1541,9 +1674,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);
@@ -1561,14 +1699,15 @@ 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, .. }
| Insn::Test { val }
| Insn::IsNil { val } =>
worklist.push_back(val),
- Insn::GuardType { val, state, .. }
+ Insn::SetGlobal { val, state, .. }
+ | Insn::GuardType { val, state, .. }
| Insn::GuardBitEquals { val, state, .. }
| Insn::ToArray { val, state }
| Insn::ToNewArray { val, state } => {
@@ -1620,6 +1759,10 @@ impl Function {
worklist.extend(args);
worklist.push_back(state);
}
+ Insn::InvokeBuiltin { args, state, .. } => {
+ worklist.extend(args);
+ worklist.push_back(state)
+ }
Insn::CCall { args, .. } => worklist.extend(args),
Insn::GetIvar { self_val, state, .. } | Insn::DefinedIvar { self_val, state, .. } => {
worklist.push_back(self_val);
@@ -1635,6 +1778,11 @@ impl Function {
worklist.push_back(val);
worklist.push_back(state);
}
+ Insn::ObjToString { val, state, .. } => {
+ worklist.push_back(val);
+ worklist.push_back(state);
+ }
+ Insn::GetGlobal { state, .. } |
Insn::SideExit { state } => worklist.push_back(state),
}
}
@@ -1644,6 +1792,67 @@ impl Function {
}
}
+ fn absorb_dst_block(&mut self, num_in_edges: &Vec<u32>, block: BlockId) -> bool {
+ let Some(terminator_id) = self.blocks[block.0].insns.last()
+ else { return false };
+ let Insn::Jump(BranchEdge { target, args }) = self.find(*terminator_id)
+ else { return false };
+ if target == block {
+ // Can't absorb self
+ return false;
+ }
+ if num_in_edges[target.0] != 1 {
+ // Can't absorb block if it's the target of more than one branch
+ return false;
+ }
+ // Link up params with block args
+ let params = std::mem::take(&mut self.blocks[target.0].params);
+ assert_eq!(args.len(), params.len());
+ for (arg, param) in args.iter().zip(params) {
+ self.make_equal_to(param, *arg);
+ }
+ // Remove branch instruction
+ self.blocks[block.0].insns.pop();
+ // Move target instructions into block
+ let target_insns = std::mem::take(&mut self.blocks[target.0].insns);
+ self.blocks[block.0].insns.extend(target_insns);
+ true
+ }
+
+ /// Clean up linked lists of blocks A -> B -> C into A (with B's and C's instructions).
+ fn clean_cfg(&mut self) {
+ // num_in_edges is invariant throughout cleaning the CFG:
+ // * we don't allocate new blocks
+ // * blocks that get absorbed are not in RPO anymore
+ // * blocks pointed to by blocks that get absorbed retain the same number of in-edges
+ let mut num_in_edges = vec![0; self.blocks.len()];
+ for block in self.rpo() {
+ for &insn in &self.blocks[block.0].insns {
+ if let Insn::IfTrue { target, .. } | Insn::IfFalse { target, .. } | Insn::Jump(target) = self.find(insn) {
+ num_in_edges[target.target.0] += 1;
+ }
+ }
+ }
+ let mut changed = false;
+ loop {
+ let mut iter_changed = false;
+ for block in self.rpo() {
+ // Ignore transient empty blocks
+ if self.blocks[block.0].insns.is_empty() { continue; }
+ loop {
+ let absorbed = self.absorb_dst_block(&num_in_edges, block);
+ if !absorbed { break; }
+ iter_changed = true;
+ }
+ }
+ if !iter_changed { break; }
+ changed = true;
+ }
+ if changed {
+ self.infer_types();
+ }
+ }
+
/// Return a traversal of the `Function`'s `BlockId`s in reverse post-order.
pub fn rpo(&self) -> Vec<BlockId> {
let mut result = self.po_from(self.entry_block);
@@ -1683,6 +1892,7 @@ impl Function {
self.optimize_direct_sends();
self.optimize_c_calls();
self.fold_constants();
+ self.clean_cfg();
self.eliminate_dead_code();
// Dump HIR after optimization
@@ -2074,10 +2284,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); }
@@ -2197,7 +2415,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();
@@ -2272,6 +2491,14 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
}
YARVINSN_pop => { state.stack_pop()?; }
YARVINSN_dup => { state.stack_push(state.stack_top()?); }
+ YARVINSN_dupn => {
+ // Duplicate the top N element of the stack. As we push, n-1 naturally
+ // points higher in the original stack.
+ let n = get_arg(pc, 0).as_usize();
+ for _ in 0..n {
+ state.stack_push(state.stack_topn(n-1)?);
+ }
+ }
YARVINSN_swap => {
let right = state.stack_pop()?;
let left = state.stack_pop()?;
@@ -2349,6 +2576,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()? });
@@ -2435,6 +2690,18 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
let send = fun.push_insn(block, Insn::Send { self_val: recv, call_info: CallInfo { method_name }, cd, blockiseq, args, state: exit_id });
state.stack_push(send);
}
+ YARVINSN_getglobal => {
+ let id = ID(get_arg(pc, 0).as_u64());
+ let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state });
+ let result = fun.push_insn(block, Insn::GetGlobal { id, state: exit_id });
+ state.stack_push(result);
+ }
+ YARVINSN_setglobal => {
+ let id = ID(get_arg(pc, 0).as_u64());
+ let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state });
+ let val = state.stack_pop()?;
+ fun.push_insn(block, Insn::SetGlobal { id, val, state: exit_id });
+ }
YARVINSN_getinstancevariable => {
let id = ID(get_arg(pc, 0).as_u64());
// ic is in arg 1
@@ -2449,6 +2716,16 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
let val = state.stack_pop()?;
fun.push_insn(block, Insn::SetIvar { self_val: self_param, id, val, state: exit_id });
}
+ YARVINSN_opt_reverse => {
+ // Reverse the order of the top N stack items.
+ let n = get_arg(pc, 0).as_usize();
+ for i in 0..n/2 {
+ let bottom = state.stack_topn(n - 1 - i)?;
+ let top = state.stack_topn(i)?;
+ state.stack_setn(i, bottom);
+ state.stack_setn(n - 1 - i, top);
+ }
+ }
YARVINSN_newrange => {
let flag = RangeType::from(get_arg(pc, 0).as_u32());
let high = state.stack_pop()?;
@@ -2457,6 +2734,55 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
let insn_id = fun.push_insn(block, Insn::NewRange { low, high, flag, state: exit_id });
state.stack_push(insn_id);
}
+ YARVINSN_invokebuiltin => {
+ let bf: rb_builtin_function = unsafe { *get_arg(pc, 0).as_ptr() };
+
+ let mut args = vec![];
+ for _ in 0..bf.argc {
+ args.push(state.stack_pop()?);
+ }
+ args.push(self_param);
+ args.reverse();
+
+ let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state });
+ let insn_id = fun.push_insn(block, Insn::InvokeBuiltin { bf, args, state: exit_id });
+ state.stack_push(insn_id);
+ }
+ YARVINSN_opt_invokebuiltin_delegate |
+ YARVINSN_opt_invokebuiltin_delegate_leave => {
+ let bf: rb_builtin_function = unsafe { *get_arg(pc, 0).as_ptr() };
+ let index = get_arg(pc, 1).as_usize();
+ let argc = bf.argc as usize;
+
+ let mut args = vec![self_param];
+ for &local in state.locals().skip(index).take(argc) {
+ args.push(local);
+ }
+
+ let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state });
+ let insn_id = fun.push_insn(block, Insn::InvokeBuiltin { bf, args, state: exit_id });
+ state.stack_push(insn_id);
+ }
+ YARVINSN_objtostring => {
+ let cd: *const rb_call_data = get_arg(pc, 0).as_ptr();
+ let call_info = unsafe { rb_get_call_data_ci(cd) };
+
+ if unknown_call_type(unsafe { rb_vm_ci_flag(call_info) }) {
+ assert!(false, "objtostring should not have unknown call type");
+ }
+ let argc = unsafe { vm_ci_argc((*cd).ci) };
+ assert_eq!(0, argc, "objtostring should not have args");
+
+ let method_name: String = unsafe {
+ let mid = rb_vm_ci_mid(call_info);
+ mid.contents_lossy().into_owned()
+ };
+
+ let recv = state.stack_pop()?;
+ let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state });
+ let objtostring = fun.push_insn(block, Insn::ObjToString { val: recv, call_info: CallInfo { method_name }, cd, state: exit_id });
+ state.stack_push(objtostring)
+ }
_ => {
// Unknown opcode; side-exit into the interpreter
let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state });
@@ -2750,7 +3076,7 @@ mod tests {
#[track_caller]
fn assert_method_hir(method: &str, hir: Expect) {
- let iseq = crate::cruby::with_rubyvm(|| get_method_iseq(method));
+ let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("self", method));
unsafe { crate::cruby::rb_zjit_profile_disable(iseq) };
let function = iseq_to_hir(iseq).unwrap();
assert_function_hir(function, hir);
@@ -2777,7 +3103,7 @@ mod tests {
#[track_caller]
fn assert_method_hir_with_opcodes(method: &str, opcodes: &[u32], hir: Expect) {
- let iseq = crate::cruby::with_rubyvm(|| get_method_iseq(method));
+ let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("self", method));
for &opcode in opcodes {
assert!(iseq_contains_opcode(iseq, opcode), "iseq {method} does not contain {}", insn_name(opcode as usize));
}
@@ -2799,7 +3125,7 @@ mod tests {
#[track_caller]
fn assert_compile_fails(method: &str, reason: ParseError) {
- let iseq = crate::cruby::with_rubyvm(|| get_method_iseq(method));
+ let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("self", method));
unsafe { crate::cruby::rb_zjit_profile_disable(iseq) };
let result = iseq_to_hir(iseq);
assert!(result.is_err(), "Expected an error but succesfully compiled to HIR: {}", FunctionPrinter::without_snapshot(&result.unwrap()));
@@ -3015,6 +3341,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
@@ -3490,6 +3872,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
"#]]);
}
@@ -3530,14 +3919,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
"#]]);
}
@@ -3712,6 +4101,35 @@ mod tests {
}
#[test]
+ fn test_setglobal() {
+ eval("
+ def test = $foo = 1
+ test
+ ");
+ assert_method_hir_with_opcode("test", YARVINSN_setglobal, expect![[r#"
+ fn test:
+ bb0(v0:BasicObject):
+ v2:Fixnum[1] = Const Value(1)
+ SetGlobal :$foo, v2
+ Return v2
+ "#]]);
+ }
+
+ #[test]
+ fn test_getglobal() {
+ eval("
+ def test = $foo
+ test
+ ");
+ assert_method_hir_with_opcode("test", YARVINSN_getglobal, expect![[r#"
+ fn test:
+ bb0(v0:BasicObject):
+ v3:BasicObject = GetGlobal :$foo
+ Return v3
+ "#]]);
+ }
+
+ #[test]
fn test_splatarray_mut() {
eval("
def test(a) = [*a]
@@ -3895,6 +4313,68 @@ 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 opt_reverse() {
+ eval("
+ def reverse_odd
+ a, b, c = @a, @b, @c
+ [a, b, c]
+ end
+
+ def reverse_even
+ a, b, c, d = @a, @b, @c, @d
+ [a, b, c, d]
+ end
+ ");
+ assert_method_hir_with_opcode("reverse_odd", YARVINSN_opt_reverse, expect![[r#"
+ fn reverse_odd:
+ bb0(v0:BasicObject):
+ v1:NilClassExact = Const Value(nil)
+ v2:NilClassExact = Const Value(nil)
+ v3:NilClassExact = Const Value(nil)
+ v6:BasicObject = GetIvar v0, :@a
+ v8:BasicObject = GetIvar v0, :@b
+ v10:BasicObject = GetIvar v0, :@c
+ v12:ArrayExact = NewArray v6, v8, v10
+ Return v12
+ "#]]);
+ assert_method_hir_with_opcode("reverse_even", YARVINSN_opt_reverse, expect![[r#"
+ fn reverse_even:
+ bb0(v0:BasicObject):
+ v1:NilClassExact = Const Value(nil)
+ v2:NilClassExact = Const Value(nil)
+ v3:NilClassExact = Const Value(nil)
+ v4:NilClassExact = Const Value(nil)
+ v7:BasicObject = GetIvar v0, :@a
+ v9:BasicObject = GetIvar v0, :@b
+ v11:BasicObject = GetIvar v0, :@c
+ v13:BasicObject = GetIvar v0, :@d
+ v15:ArrayExact = NewArray v7, v9, v11, v13
+ Return v15
+ "#]]);
+ }
+
+ #[test]
fn test_branchnil() {
eval("
def test(x) = x&.itself
@@ -3910,6 +4390,81 @@ mod tests {
Return v10
"#]]);
}
+
+ #[test]
+ fn test_invokebuiltin_delegate_with_args() {
+ assert_method_hir_with_opcode("Float", YARVINSN_opt_invokebuiltin_delegate_leave, expect![[r#"
+ fn Float:
+ bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject, v3:BasicObject):
+ v6:BasicObject = InvokeBuiltin rb_f_float, v0, v1, v2
+ Jump bb1(v0, v1, v2, v3, v6)
+ bb1(v8:BasicObject, v9:BasicObject, v10:BasicObject, v11:BasicObject, v12:BasicObject):
+ Return v12
+ "#]]);
+ }
+
+ #[test]
+ fn test_invokebuiltin_delegate_without_args() {
+ assert_method_hir_with_opcode("class", YARVINSN_opt_invokebuiltin_delegate_leave, expect![[r#"
+ fn class:
+ bb0(v0:BasicObject):
+ v3:BasicObject = InvokeBuiltin _bi20, v0
+ Jump bb1(v0, v3)
+ bb1(v5:BasicObject, v6:BasicObject):
+ Return v6
+ "#]]);
+ }
+
+ #[test]
+ fn test_invokebuiltin_with_args() {
+ let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("GC", "start"));
+ assert!(iseq_contains_opcode(iseq, YARVINSN_invokebuiltin), "iseq GC.start does not contain invokebuiltin");
+ let function = iseq_to_hir(iseq).unwrap();
+ assert_function_hir(function, expect![[r#"
+ fn start:
+ bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject, v3:BasicObject, v4:BasicObject):
+ v6:FalseClassExact = Const Value(false)
+ v8:BasicObject = InvokeBuiltin gc_start_internal, v0, v1, v2, v3, v6
+ Return v8
+ "#]]);
+ }
+
+ #[test]
+ fn dupn() {
+ eval("
+ def test(x) = (x[0, 1] ||= 2)
+ ");
+ assert_method_hir_with_opcode("test", YARVINSN_dupn, expect![[r#"
+ fn test:
+ bb0(v0:BasicObject, v1:BasicObject):
+ v3:NilClassExact = Const Value(nil)
+ v4:Fixnum[0] = Const Value(0)
+ v5:Fixnum[1] = Const Value(1)
+ v7:BasicObject = SendWithoutBlock v1, :[], v4, v5
+ v8:CBool = Test v7
+ IfTrue v8, bb1(v0, v1, v3, v1, v4, v5, v7)
+ v10:Fixnum[2] = Const Value(2)
+ v12:BasicObject = SendWithoutBlock v1, :[]=, v4, v5, v10
+ Return v10
+ bb1(v14:BasicObject, v15:BasicObject, v16:NilClassExact, v17:BasicObject, v18:Fixnum[0], v19:Fixnum[1], v20:BasicObject):
+ Return v20
+ "#]]);
+ }
+
+ #[test]
+ fn test_objtostring() {
+ eval("
+ def test = \"#{1}\"
+ ");
+ assert_method_hir_with_opcode("test", YARVINSN_objtostring, expect![[r#"
+ fn test:
+ bb0(v0:BasicObject):
+ v2:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
+ v3:Fixnum[1] = Const Value(1)
+ v5:BasicObject = ObjToString v3
+ SideExit
+ "#]]);
+ }
}
#[cfg(test)]
@@ -3920,7 +4475,7 @@ mod opt_tests {
#[track_caller]
fn assert_optimized_method_hir(method: &str, hir: Expect) {
- let iseq = crate::cruby::with_rubyvm(|| get_method_iseq(method));
+ let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("self", method));
unsafe { crate::cruby::rb_zjit_profile_disable(iseq) };
let mut function = iseq_to_hir(iseq).unwrap();
function.optimize();
@@ -3962,9 +4517,6 @@ mod opt_tests {
assert_optimized_method_hir("test", expect![[r#"
fn test:
bb0(v0:BasicObject):
- v3:FalseClassExact = Const Value(false)
- Jump bb1(v0, v3)
- bb1(v8:BasicObject, v9:FalseClassExact):
v11:Fixnum[4] = Const Value(4)
Return v11
"#]]);
@@ -4141,8 +4693,6 @@ mod opt_tests {
fn test:
bb0(v0:BasicObject):
PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_EQ)
- Jump bb1(v0)
- bb1(v10:BasicObject):
v12:Fixnum[4] = Const Value(4)
Return v12
"#]]);
@@ -4205,8 +4755,6 @@ mod opt_tests {
bb0(v0:BasicObject):
PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_EQ)
PatchPoint BOPRedefined(INTEGER_REDEFINED_OP_FLAG, BOP_NEQ)
- Jump bb1(v0)
- bb1(v10:BasicObject):
v12:Fixnum[4] = Const Value(4)
Return v12
"#]]);
@@ -4890,9 +5438,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
"#]]);
}
@@ -4961,8 +5509,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
"#]]);
}
@@ -5079,8 +5627,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
"#]]);
}
@@ -5094,8 +5642,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
"#]]);
}
@@ -5110,8 +5658,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
"#]]);
}
@@ -5132,8 +5680,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
"#]]);
}
@@ -5149,14 +5697,10 @@ 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)
+ v11:BasicObject = SendWithoutBlock v20, :new
+ Return v11
"#]]);
}
@@ -5176,15 +5720,11 @@ 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)
+ v13:BasicObject = SendWithoutBlock v22, :new, v5
+ Return v13
"#]]);
}
@@ -5244,4 +5784,258 @@ 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
+ "#]]);
+ }
+
+ #[test]
+ fn test_objtostring_string() {
+ eval(r##"
+ def test = "#{('foo')}"
+ "##);
+ assert_optimized_method_hir("test", expect![[r#"
+ fn test:
+ bb0(v0:BasicObject):
+ v2:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
+ v3:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008))
+ v4:StringExact = StringCopy v3
+ SideExit
+ "#]]);
+ }
+
+ #[test]
+ fn test_objtostring_with_non_string() {
+ eval(r##"
+ def test = "#{1}"
+ "##);
+ assert_optimized_method_hir("test", expect![[r#"
+ fn test:
+ bb0(v0:BasicObject):
+ v2:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
+ v3:Fixnum[1] = Const Value(1)
+ v8:BasicObject = SendWithoutBlock v3, :to_s
+ SideExit
+ "#]]);
+ }
}
diff --git a/zjit/src/lib.rs b/zjit/src/lib.rs
index 8ccb6ae4c1..9d139b9801 100644
--- a/zjit/src/lib.rs
+++ b/zjit/src/lib.rs
@@ -21,3 +21,5 @@ mod disasm;
mod options;
mod profile;
mod invariants;
+#[cfg(test)]
+mod assertions;
diff --git a/zjit/src/state.rs b/zjit/src/state.rs
index e8c389a5f8..acaac850c3 100644
--- a/zjit/src/state.rs
+++ b/zjit/src/state.rs
@@ -1,10 +1,10 @@
-use std::collections::HashSet;
-
use crate::cruby::{self, rb_bug_panic_hook, EcPtr, Qnil, VALUE};
use crate::cruby_methods;
use crate::invariants::Invariants;
use crate::options::Options;
use crate::asm::CodeBlock;
+use crate::backend::lir::{Assembler, C_RET_OPND};
+use crate::virtualmem::CodePtr;
#[allow(non_upper_case_globals)]
#[unsafe(no_mangle)]
@@ -32,8 +32,8 @@ pub struct ZJITState {
/// Properties of core library methods
method_annotations: cruby_methods::Annotations,
- /// The address of the instruction that JIT-to-JIT calls return to
- iseq_return_addrs: HashSet<*const u8>,
+ /// Trampoline to propagate a callee's side exit to the caller
+ exit_trampoline: Option<CodePtr>,
}
/// Private singleton instance of the codegen globals
@@ -88,9 +88,14 @@ impl ZJITState {
invariants: Invariants::default(),
assert_compiles: false,
method_annotations: cruby_methods::init(),
- iseq_return_addrs: HashSet::new(),
+ exit_trampoline: None,
};
unsafe { ZJIT_STATE = Some(zjit_state); }
+
+ // Generate trampolines after initializing ZJITState, which Assembler will use
+ let cb = ZJITState::get_code_block();
+ let exit_trampoline = Self::gen_exit_trampoline(cb).unwrap();
+ ZJITState::get_instance().exit_trampoline = Some(exit_trampoline);
}
/// Return true if zjit_state has been initialized
@@ -133,14 +138,17 @@ impl ZJITState {
instance.assert_compiles = true;
}
- /// Record an address that a JIT-to-JIT call returns to
- pub fn add_iseq_return_addr(addr: *const u8) {
- ZJITState::get_instance().iseq_return_addrs.insert(addr);
+ /// Generate a trampoline to propagate a callee's side exit to the caller
+ fn gen_exit_trampoline(cb: &mut CodeBlock) -> Option<CodePtr> {
+ let mut asm = Assembler::new();
+ asm.frame_teardown();
+ asm.cret(C_RET_OPND);
+ asm.compile(cb).map(|(start_ptr, _)| start_ptr)
}
- /// Returns true if a JIT-to-JIT call returns to a given address
- pub fn is_iseq_return_addr(addr: *const u8) -> bool {
- ZJITState::get_instance().iseq_return_addrs.contains(&addr)
+ /// Get the trampoline to propagate a callee's side exit to the caller
+ pub fn get_exit_trampoline() -> CodePtr {
+ ZJITState::get_instance().exit_trampoline.unwrap()
}
}